Adafruit_Learning_System_Gu.../runcpm-rp2350-hstx-usb/runcpm-pico/disk.h

635 lines
14 KiB
C

// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
#ifndef DISK_H
#define DISK_H
/* see main.c for definition */
#ifdef __linux__
#include <sys/stat.h>
#endif
#ifdef __DJGPP__
#include <dirent.h>
#endif
/*
Disk errors
*/
#define errWRITEPROT 1
#define errSELECT 2
#define RW (roVector & (1 << cDrive))
// Prints out a BDOS error
void _error(uint8 error) {
_puts("\r\nBdos Err on ");
_putcon('A' + cDrive);
_puts(": ");
switch (error) {
case errWRITEPROT:
_puts("R/O");
break;
case errSELECT:
_puts("Select");
break;
default:
_puts("\r\nCP/M ERR");
break;
}
Status = _getch();
_puts("\r\n");
cDrive = oDrive = _RamRead(DSKByte) & 0x0f;
Status = 2;
}
// Selects the disk to be used by the next disk function
int _SelectDisk(uint8 dr) {
uint8 result = 0xff;
uint8 disk[2] = { 'A', 0 };
if (!dr || dr == '?') {
dr = cDrive; // This will set dr to defDisk in case no disk is passed
} else {
--dr; // Called from BDOS, set dr back to 0=A: format
}
disk[0] += dr;
if (_sys_select(&disk[0])) {
loginVector = loginVector | (1 << (disk[0] - 'A'));
result = 0x00;
} else {
_error(errSELECT);
}
return(result);
}
// Converts a FCB entry onto a host OS filename string
uint8 _FCBtoHostname(uint16 fcbaddr, uint8* filename) {
uint8 addDot = TRUE;
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 i = 0;
uint8 unique = TRUE;
uint8 c;
if (F->dr && F->dr != '?') {
*(filename++) = (F->dr - 1) + 'A';
} else {
*(filename++) = cDrive + 'A';
}
*(filename++) = FOLDERCHAR;
*(filename++) = toupper(tohex(userCode));
*(filename++) = FOLDERCHAR;
if (F->dr != '?') {
while (i < 8) {
c = F->fn[i] & 0x7F;
#ifdef NOSLASH
if (c == '/')
c = '_';
#endif
if (c > 32)
*(filename++) = toupper(c);
if (c == '?')
unique = FALSE;
++i;
}
i = 0;
while (i < 3) {
c = F->tp[i] & 0x7F;
if (c > 32) {
if (addDot) {
addDot = FALSE;
*(filename++) = '.'; // Only add the dot if there's an extension
}
#ifdef NOSLASH
if (c == '/')
c = '_';
#endif
*(filename++) = toupper(c);
}
if (c == '?')
unique = FALSE;
++i;
}
} else {
for (i = 0; i < 8; ++i)
*(filename++) = '?';
*(filename++) = '.';
for (i = 0; i < 3; ++i)
*(filename++) = '?';
unique = FALSE;
}
*filename = 0x00;
return(unique);
}
// Convers a host OS filename string onto a FCB entry
void _HostnameToFCB(uint16 fcbaddr, uint8* filename) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 i = 0;
++filename;
if (*filename == FOLDERCHAR) { // Skips the drive and / if needed
filename += 3;
} else {
--filename;
}
while (*filename != 0 && *filename != '.') {
F->fn[i] = toupper(*filename);
++filename;
++i;
}
while (i < 8) {
F->fn[i] = ' ';
++i;
}
if (*filename == '.')
++filename;
i = 0;
while (*filename != 0) {
F->tp[i] = toupper(*filename);
++filename;
++i;
}
while (i < 3) {
F->tp[i] = ' ';
++i;
}
}
// Converts a string name (AB.TXT) onto FCB name (AB TXT)
void _HostnameToFCBname(uint8* from, uint8* to) {
int i = 0;
++from;
if (*from == FOLDERCHAR) { // Skips the drive and / if needed
from += 3;
} else {
--from;
}
while (*from != 0 && *from != '.') {
*to = toupper(*from);
++to; ++from; ++i;
}
while (i < 8) {
*to = ' ';
++to; ++i;
}
if (*from == '.')
++from;
i = 0;
while (*from != 0) {
*to = toupper(*from);
++to; ++from; ++i;
}
while (i < 3) {
*to = ' ';
++to; ++i;
}
*to = 0;
}
// Creates a fake directory entry for the current dmaAddr FCB
void _mockupDirEntry(void) {
CPM_DIRENTRY* DE = (CPM_DIRENTRY*)_RamSysAddr(dmaAddr);
uint8 blocks, i;
for (i = 0; i < sizeof(CPM_DIRENTRY); ++i)
_RamWrite(dmaAddr + i, 0x00); // zero out directory entry
_HostnameToFCB(dmaAddr, (uint8*)findNextDirName);
if (allUsers) {
DE->dr = currFindUser; // set user code for return
} else {
DE->dr = userCode;
}
// does file fit in a single directory entry?
if (fileExtents <= extentsPerDirEntry) {
if (fileExtents) {
DE->ex = (fileExtents - 1 + fileExtentsUsed) % (MaxEX + 1);
DE->s2 = (fileExtents - 1 + fileExtentsUsed) / (MaxEX + 1);
DE->rc = fileRecords - (BlkEX * (fileExtents - 1));
}
blocks = (fileRecords >> blockShift) + ((fileRecords & blockMask) ? 1 : 0);
fileRecords = 0;
fileExtents = 0;
fileExtentsUsed = 0;
} else { // no, max out the directory entry
DE->ex = (extentsPerDirEntry - 1 + fileExtentsUsed) % (MaxEX + 1);
DE->s2 = (extentsPerDirEntry - 1 + fileExtentsUsed) / (MaxEX + 1);
DE->rc = BlkEX;
blocks = numAllocBlocks < 256 ? 16 : 8;
// update remaining records and extents for next call
fileRecords -= BlkEX * extentsPerDirEntry;
fileExtents -= extentsPerDirEntry;
fileExtentsUsed += extentsPerDirEntry;
}
// phoney up an appropriate number of allocation blocks
if (numAllocBlocks < 256) {
for (i = 0; i < blocks; ++i)
DE->al[i] = (uint8)firstFreeAllocBlock++;
} else {
for (i = 0; i < 2 * blocks; i += 2) {
DE->al[i] = firstFreeAllocBlock & 0xFF;
DE->al[i + 1] = firstFreeAllocBlock >> 8;
++firstFreeAllocBlock;
}
}
}
// Matches a FCB name to a search pattern
uint8 match(uint8* fcbname, uint8* pattern) {
uint8 result = 1;
uint8 i;
for (i = 0; i < 12; ++i) {
if (*pattern == '?' || *pattern == *fcbname) {
++pattern; ++fcbname;
continue;
} else {
result = 0;
break;
}
}
return(result);
}
// Returns the size of a file
long _FileSize(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
long r, l = -1;
if (!_SelectDisk(F->dr)) {
_FCBtoHostname(fcbaddr, &filename[0]);
l = _sys_filesize(filename);
r = l % BlkSZ;
if (r)
l = l + BlkSZ - r;
}
return(l);
}
// Opens a file
uint8 _OpenFile(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
long len;
int32 i;
if (!_SelectDisk(F->dr)) {
_FCBtoHostname(fcbaddr, &filename[0]);
if (_sys_openfile(&filename[0])) {
len = _FileSize(fcbaddr) / BlkSZ; // Compute the len on the file in blocks
F->s1 = 0x00;
F->s2 = 0x80; // set unmodified flag
F->rc = len > MaxRC ? MaxRC : (uint8)len;
for (i = 0; i < 16; ++i) // Clean up AL
F->al[i] = 0x00;
result = 0x00;
}
}
return(result);
}
// Closes a file
uint8 _CloseFile(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
if (!_SelectDisk(F->dr)) {
if (!(F->s2 & 0x80)) { // if file is modified
if (!RW) {
_FCBtoHostname(fcbaddr, &filename[0]);
if (fcbaddr == BatchFCB)
_Truncate((char*)filename, F->rc); // Truncate $$$.SUB to F->rc CP/M records so SUBMIT.COM can work
result = 0x00;
} else {
_error(errWRITEPROT);
}
} else {
result = 0x00;
}
}
return(result);
}
// Creates a file
uint8 _MakeFile(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
uint8 i;
if (!_SelectDisk(F->dr)) {
if (!RW) {
_FCBtoHostname(fcbaddr, &filename[0]);
if (_sys_makefile(&filename[0])) {
F->ex = 0x00; // Makefile also initializes the FCB (file becomes "open")
F->s1 = 0x00;
F->s2 = 0x00; // newly created files are already modified
F->rc = 0x00;
for (i = 0; i < 16; ++i) // Clean up AL
F->al[i] = 0x00;
F->cr = 0x00;
result = 0x00;
}
} else {
_error(errWRITEPROT);
}
}
return(result);
}
// Searches for the first directory file
uint8 _SearchFirst(uint16 fcbaddr, uint8 isdir) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
if (!_SelectDisk(F->dr)) {
_FCBtoHostname(fcbaddr, &filename[0]);
allUsers = F->dr == '?';
allExtents = F->ex == '?';
if (allUsers) {
result = _findfirstallusers(isdir);
} else {
result = _findfirst(isdir);
}
}
return(result);
}
// Searches for the next directory file
uint8 _SearchNext(uint16 fcbaddr, uint8 isdir) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(tmpFCB);
uint8 result = 0xff;
if (!_SelectDisk(F->dr)) {
if (allUsers) {
result = _findnextallusers(isdir);
} else {
result = _findnext(isdir);
}
}
return(result);
}
// Deletes a file
uint8 _DeleteFile(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
#if defined(USE_PUN) || defined(USE_LST)
CPM_FCB* T = (CPM_FCB*)_RamSysAddr(tmpFCB);
#endif
uint8 result = 0xff;
uint8 deleted = 0xff;
if (!_SelectDisk(F->dr)) {
if (!RW) {
result = _SearchFirst(fcbaddr, FALSE); // FALSE = Does not create a fake dir entry when finding the file
while (result != 0xff) {
#ifdef USE_PUN
if (!strcmp((char*)T->fn, "PUN TXT") && pun_open) {
_sys_fclose(pun_dev);
pun_open = FALSE;
}
#endif
#ifdef USE_LST
if (!strcmp((char*)T->fn, "LST TXT") && lst_open) {
_sys_fclose(lst_dev);
lst_open = FALSE;
}
#endif
_FCBtoHostname(tmpFCB, &filename[0]);
if (_sys_deletefile(&filename[0]))
deleted = 0x00;
result = _SearchFirst(fcbaddr, FALSE); // FALSE = Does not create a fake dir entry when finding the file
}
} else {
_error(errWRITEPROT);
}
}
return(deleted);
}
// Renames a file
uint8 _RenameFile(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
if (!_SelectDisk(F->dr)) {
if (!RW) {
_RamWrite(fcbaddr + 16, _RamRead(fcbaddr)); // Prevents rename from moving files among folders
_FCBtoHostname(fcbaddr + 16, &newname[0]);
_FCBtoHostname(fcbaddr, &filename[0]);
if (_sys_renamefile(&filename[0], &newname[0]))
result = 0x00;
} else {
_error(errWRITEPROT);
}
}
return(result);
}
// Sequential read
uint8 _ReadSeq(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
long fpos = ((F->s2 & MaxS2) * BlkS2 * BlkSZ) +
(F->ex * BlkEX * BlkSZ) +
(F->cr * BlkSZ);
if (!_SelectDisk(F->dr)) {
_FCBtoHostname(fcbaddr, &filename[0]);
result = _sys_readseq(&filename[0], fpos);
if (!result) { // Read succeeded, adjust FCB
++F->cr;
if (F->cr > MaxCR) {
F->cr = 1;
++F->ex;
}
if (F->ex > MaxEX) {
F->ex = 0;
++F->s2;
}
if ((F->s2 & 0x7F) > MaxS2)
result = 0xfe; // (todo) not sure what to do
}
}
return(result);
}
// Sequential write
uint8 _WriteSeq(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
long fpos = ((F->s2 & MaxS2) * BlkS2 * BlkSZ) +
(F->ex * BlkEX * BlkSZ) +
(F->cr * BlkSZ);
if (!_SelectDisk(F->dr)) {
if (!RW) {
_FCBtoHostname(fcbaddr, &filename[0]);
result = _sys_writeseq(&filename[0], fpos);
if (!result) { // Write succeeded, adjust FCB
F->s2 &= 0x7F; // reset unmodified flag
++F->cr;
if (F->cr > MaxCR) {
F->cr = 1;
++F->ex;
}
if (F->ex > MaxEX) {
F->ex = 0;
++F->s2;
}
if (F->s2 > MaxS2)
result = 0xfe; // (todo) not sure what to do
}
} else {
_error(errWRITEPROT);
}
}
return(result);
}
// Random read
uint8 _ReadRand(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
int32 record = (F->r2 << 16) | (F->r1 << 8) | F->r0;
long fpos = record * BlkSZ;
if (!_SelectDisk(F->dr)) {
_FCBtoHostname(fcbaddr, &filename[0]);
result = _sys_readrand(&filename[0], fpos);
if (result == 0 || result == 1 || result == 4) {
// adjust FCB unless error #6 (seek past 8MB - max CP/M file & disk size)
F->cr = record & 0x7F;
F->ex = (record >> 7) & 0x1f;
if (F->s2 & 0x80) {
F->s2 = ((record >> 12) & MaxS2) | 0x80;
} else {
F->s2 = (record >> 12) & MaxS2;
}
}
}
return(result);
}
// Random write
uint8 _WriteRand(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
int32 record = (F->r2 << 16) | (F->r1 << 8) | F->r0;
long fpos = record * BlkSZ;
if (!_SelectDisk(F->dr)) {
if (!RW) {
_FCBtoHostname(fcbaddr, &filename[0]);
result = _sys_writerand(&filename[0], fpos);
if (!result) { // Write succeeded, adjust FCB
F->cr = record & 0x7F;
F->ex = (record >> 7) & 0x1f;
F->s2 = (record >> 12) & MaxS2; // resets unmodified flag
}
} else {
_error(errWRITEPROT);
}
}
return(result);
}
// Returns the size of a CP/M file
uint8 _GetFileSize(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0xff;
int32 count = _FileSize(DE) >> 7;
if (count != -1) {
F->r0 = count & 0xff;
F->r1 = (count >> 8) & 0xff;
F->r2 = (count >> 16) & 0xff;
}
return(result);
}
// Set the next random record
uint8 _SetRandom(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
uint8 result = 0x00;
int32 count = F->cr & 0x7f;
count += (F->ex & 0x1f) << 7;
count += (F->s2 & MaxS2) << 12;
F->r0 = count & 0xff;
F->r1 = (count >> 8) & 0xff;
F->r2 = (count >> 16) & 0xff;
return(result);
}
// Sets the current user area
void _SetUser(uint8 user) {
userCode = user & 0x1f; // BDOS unoficially allows user areas 0-31
// this may create folders from G-V if this function is called from an user program
// It is an unwanted behavior, but kept as BDOS does it
#ifdef NOHIGHUSER
if(userCode < 16)
#endif
_MakeUserDir(); // Creates the user dir (0-F[G-V]) if needed
}
// Creates a disk directory folder
uint8 _MakeDisk(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
return(_sys_makedisk(F->dr));
}
// Checks if there's a temp submit file present
uint8 _CheckSUB(void) {
uint8 result;
uint8 oCode = userCode; // Saves the current user code (original BDOS does not do this)
_HostnameToFCB(tmpFCB, (uint8*)"$???????.???"); // The original BDOS in fact only looks for a file which start with $
#ifdef BATCHA
_RamWrite(tmpFCB, 1); // Forces it to be checked on drive A:
#endif
#ifdef BATCH0
userCode = 0; // Forces it to be checked on user 0
#endif
result = (_SearchFirst(tmpFCB, FALSE) == 0x00) ? 0xff : 0x00;
userCode = oCode; // Restores the current user code
return(result);
}
#ifdef HASLUA
// Executes a Lua script
uint8 _RunLua(uint16 fcbaddr) {
uint8 luascript[17];
uint8 result = 0xff;
if (_FCBtoHostname(fcbaddr, &luascript[0])) { // Script name must be unique
if (!_SearchFirst(fcbaddr, FALSE)) { // and must exist
result = _RunLuaScript((char*)&luascript[0]);
}
}
return(result);
}
#endif
#endif