 day 11 Let's do submit files So this is how CPM does scripting. It's just a simple batch processor Here is one I made and All it does is it runs the commands in the file in order. There's some simple Parameter substitution, but that's about it. Now the way this works is to run it You use this command called submit And you give it the script and any parameters and when you run it it will then generate a $$$.sub file on drive a and then the CCP reads the commands from that file Into the keyboard buffer and just does that rather than you know reading from the keyboard and It's a pretty simple set up if we dump it Each command takes up a complete record. That's the size of the keyboard buffer With the size in front so it can just be loaded directly into the keyboard buffer instead of calling the BDoS read string function and It's in reverse order So in order to execute the script all you do is you open the file you find the last record which in this case is this one shove that into the keyboard buffer and Reduce the size of the file by one record Which actually happens through a bit of a nasty hack now the submit command here I Actually got from my other cpm project cpm ish Which is a open-source cpm distribution for the 8080 and z80 how I hear you ask Was I able to use a version of submit written for the 8080 on this 6502 system? Well The answer is fairly straightforward It's written in C because I have a C compiler for the 6502 I'm actually using LLVM MOS, which is a port of LLVM to the 6502 it generates decent enough code the 6502 is notoriously difficult to generate code for so the That file is too big to be honest I'm gonna make some changes later there you go 3k for submit 8k for stat, which is huge so I Recon that one of the reasons why it's so big is that the program is not really well tailored for the 6502 So, you know, it's got like division and modulus in it and so on so I'm gonna do some fiddling later But for now, let's just try and make submit work So all the code for this actually lives in the CCP and We'll be here so What we want to do is First let's put a Do I actually have Don't actually have a Define CCP here. Let's let's make one. So that's going to be one byte for the drive two three four five six seven eight That's for the file name like so and on entry We get the bios we get the drive Try and open the Sub file now in real CPM the the loader the bios Actually, hang on. Let me get that right in real CPM. The CCP will reset the BDOS here and The BDOS will then reply with a hint as to whether this file exists this avoids needing to scan the directory when the CCP starts up But we're going to do it easy way for now. So low byte of submit FCP Jgsr to Xfcb open So this will set carry on Error so if the carry is Set we need a way to figure out whether the Whether the submit file is actually open. We're just going to overwrite Drive byte with an invalid value Yeah Okay fill This should be dot res. I've been using a Different assembler Okay, so that will open it now here So we want to read it from the submit file so to do that We are going to Get the record count If the record count is zero Let me think how this works if the record count is zero then we actually want to delete the file and Go back to the keyboard. I am going to list stuff out. I think Command keyboard and we're also going to have a read command from submit file. So if the Record count is zero delete it mark the file as Used We are going to Say To there so this means that the next time through we won't try to read from the submit file and Actually read the command from the keyboard so that here we have Exes the record count to me. No, it's not zero so Decrement set the current record to the last Record in the file We now want to go to Muddly commode line We're now going to set the DMA address then Read one record Into the command line We now want to We now want to shorten the file by one So we've already We have not updated the record count So we are going to do this again so load the record count decrement it and Write it back and because we're fiddling with the FCB directly We will also want record count mark FCB as modified and Then It's FCB close and Then we're going to close the file and remember that this actually just syncs the FCB to disk So this will write the updated record count to disk and we'll need to import that Okay, so we now want to Print the command so we're going to do that By We are going to need a counter for this. It's not going to be right Because this is testing at the end of the loop and we want to test at the beginning of the loop So that a zero length command will do the right thing like so and do a new line and Exit back to the system. Well exit back to the code above Okay, so let's give that a try and in fact Because that file already exists on the system then as soon as I start up CPM it will try start trying to execute it And it didn't do anything. Oh No, it's been it's probably been deleted. Yes, it was deleted because I changed something Right that did not do anything Because the CCP only looks for this file on A warm boot so now if I type control C Then nothing happens It's not working So First let's Check this Okay, that looks right This is backwards if the drive is ff. It's invalid therefore it's negative So we do the keyboard Okay, now, let's try it test sub. I got it worked So now if I do a dir The submit files gone. Wow that actually worked. I'm amazed Awesome. Okay. Um, I was expecting that to take way longer We should probably put some error handling in Let's finally cannot open file Sub and then we want to Warm boot the system that goes wrong because this isn't a syntax error. So Actually, we're going to do that. That was my phone Because here we actually want to make sure the file is deleted. Yeah, I'll leave that up here. Okay, so BDOS exit is undefined Did I call it warm boot by any chance? I did not exit program it was Ah wordy symbols the best kind Okay, that seems to work the The way these work is really cunning by taking the last record from the file The state is as it that is how far through the file. We're currently executing Just happens automatically. We just keep going until we run out of lines and then you know, everything is fine um We can do a warm boot halfway through running the submit file and the next time round It'll resume from where it left off. It is very elegant It also involves a lot of disk access So let's fire up the the other emulator. Whoops. That's not what I wanted to do just one And just see how horribly slow this is okay, so This is now running a bbc master with lots lots more memory So we start So we can type test sub Not too bad The The acorn moss disk system is actually caching write Updating Running these commands is only ever modifying it's only ever accessing the directory So And we're using two directory records Which is 256 bytes which is the size of a moss sector So this is all fitting within mosses disk buffer. So it should actually be slower if If these were doing anything else like running transient commands, then it would be slower But it works Yes, it's removed the sub file Fantastic Okay, well that was easy Oh, yeah, I will fire this up again So the other c program that I have is stat Which is an all-purpose program that does all sorts of things in dubious ways So the simplest things it just tells you the status of the current disks Uh, you can ask it for help using the world's worst user interface And it will dump everything it can do here you can Get the size of files except that doesn't work and I'm still trying to figure out why Something about my c cpm bindings aren't working You can show the current device mapping using the iobite Which in our system is constant. So you can't actually change this even though Like you should be able to end up at my equals sign. There we go so I can say that the console is on the printer It should have done something but it hasn't You can see Extender disk information So this shows you shows us that we have, you know, this many records 63 kilobyte drive capacity is wrong I think we should have twice that 256 records per extent I am not convinced about Uh, so an extent is 16384 bytes A record is 128 bytes. Yes, that is also wrong Okay, there's some bugs. I need to fix here Um, I didn't understand the cpm file system as well as I did when I wrote this Anyway, let's get rid of that. Okay. Uh, we have a few more commands we need to do One of which is rename So let's do that So first we need to implement the The ccp command for doing it So this is going to It's going to parse an fcb For the first one Hang on a second. What are we doing? Okay, parse the first fcb We now want to So this is really annoying. It's not our fault. It's just the way cpm works The rename system call takes some very special Uh Parameters So parse valid user fcb will parse an fcb into user fcb We are now going to parse the second one into user fcb2 Which is in the second 16 bytes of user fcb So this is actually overlapping the allocation map of user fcb And it's worse than that. There's actually some more work we need to do Uh, yes, sorry. We're going to have to do this elsewhere parse valid user fcb2 Because this needs to be within jump range in valid file name. That's why And we're just going to rename a theme for consistency Like so So now we are going to load user fcb and Call bdos rename and exit Nothing much to it We do need to implement bdos rename which will go somewhere So the rename system call then takes the two fcbs one after the other overlapping This is in fact the same syntax That transient commands use And I think there is actually something else we need to do Um, I think we need to take equals out of the list of invalid file name characters Make it make it a Terminator That's going to be irritating. It's going to have to be it needs to be there and there and there and there and Now this file is too big So this is trying to go to exit which is here. I think this wants to be So if we do this Then We can call that when we need to test for a terminator character And then it will set carry if it is Actually actually Let's have it set zed when it is so Then here all we need to do is to set non z Which we wanted to do without Changing any of the registers Uh, no, there is in fact no easy way to do that. So we are in fact going to Do it like hang on a second if we if If this compares equal Then we know that carry is set because we figured out earlier that a subtracting One value from itself Gives you zed and c set So that we know that carry is set here So we just clear carry there. Okay So is terminator character branch if carries set to exit And this is valid file name character branching to invalid fcp. That's an error Oh, oh, we have this handy Okay, I think that will work Yeah, and it's now fits Uh, I hate doing this kind of flag mangling. Okay rename through bar It calls rename rename food That's sure to produce an error Oh, it doesn't produce an error because a empty Um Fcp is valid, but we don't want to allow that so So we're actually going to user fcp plus xfcp First byte of the file name Cannot be a space. It must be a valid file name If it is Then this is an error and if you want to do that as well for user fcp2 Okay, right Right Not right Right So I believe that in our parse fcp code. I have forgotten to change all the terminators All the terminator code So wipe check for drive read the file name. Here we go Uh gsr is Terminator char branch if carry set to exit That means it is no longer We be stop the fcp here Is valid file hr bcs exit Terminator char This terminator char bcs exit brand foo equals bar bad file name gr I'm not convinced I'm getting that right. So Let's just do it like that Okay, that has in fact done the thing Um, but it's done it kind of badly It has overwritten Something Yeah, I did not So that's tried to wipe user fcp2's allocation buffer And the stuff after the end of it, but I did not allocate space for that So that's actually going to want to be Um size of x fcp minus 16 No Size of yes Well, this was wrong anyway, but that hasn't helped So it has overwritten the current user But why The current user Is not stored in the ccp's variables. It's stored in the bedoss So I would expect I would not expect that to overwrite it at that point So let's just These out put in a return there I'm not convinced this is actually running it No, it is if I put an rts there That should be doing nothing at all, right She's doing something Has this got nothing to do with rename at all? Yeah, it has it's got nothing to I'll try that again with a command that's not user actually Right I have just broken something that's what's wrong And I bet I've broken parse fcp So This will be the terminator stuff That should be clear carry. That's what's wrong That's better Okay, Ren Fu bar unimplemented good Ren Fu equals bar By file name again Ha clear Check for the drive Is it a colon? Yeah Dot It's term is terminated character If yes exit Is terminated character if yes exit Is terminator if yes exit in fact We should be hitting this one So If it's space Set carry return if it's an equal sign Set carry return otherwise clear carry return That should work But it isn't So we're gonna have to debug this We are at 7 8 e 4 Okay, Ren Fu equals bar So the first one will be Ren The second one will be Fu. This is the one we want to debug So We skip the white space Set the user field number in the fcb We get the current user Which is zero and 8 a is Where the pointer for the fcb is 7 b 9 5 is the fcb 7 b 9 5 is here down here This is the top of video memory. So we're right up at the top of memory Yeah, which is correct. That's what we wanted So let's store that Wipe the fcb We can skip this Are we we are at 7 8 f 4 We've just done this. We've loaded Loaded the drive dyi ld 20 overall our cpy 20 that must be yeah, that's that one Uh, so This line 7 iob is where the where we actually start parsing. Okay, we're here Command offset is 4 as we expect our Buffer is at 7 af1. You can see it contains Ren fu equals bar So get the drive letter There is no drive letter Or rather it's not zero Look for a colon No colon. We just leave that unset Uh Here is our fcb being filled out. We now read the file name I know what's going on that Okay, I think it is working. I think it's stopping at the equal sign Then uh, it's trying to read the next fcb And that is succeeding too It's seeing the equal sign as the first character of the next fcb And now that's producing an empty fcb and then it's failing validation later Because it doesn't let like having two fcbs. I will continue to step through this So we've read the character is 46 Is terminator character is to space no is it an equal sign? No clear carry return Asterisk it's not an asterisk Is it a valid file name character? Yes, it is store And again, this is the last time through this is this is the last correct character for f store Okay, and this one is 3d. It's the equal sign Is it a terminator? Carry is set. Yes, it is So we go to the exit routine We write back the command Offset which is 7 and exit with a valid Yeah, that is actually working So what we actually want to do is This is the only place where A equals Sign is this the only place where an equal sign is valid I think that We want to um I think we want to reuse this to be honest But this is inside This is to find inside this procedure above. I think we can do this Yeah, that's kind of this is all kind of terrible, but Is I'll do cleanup later. Is that a valid file name character? Uh Is it? No, we can't do that there. That's a pain Okay, if it's a space Then we actually we want to continue So if it is not a space Is it an equal sign? If it's not an equal sign It is then it must be neither a space or an equal sign Otherwise continue Good Good, that's working. Okay. We've done the ccp side of things We now need to do the actual implementation in the bedos So we need to add the Entry point Here Okay, now I need to look something up at this point the rename instruction The rename command in the ccp Actually takes the parameters Backwards So you it takes the new file first and the old file second right, but The rename system call takes the old name first So We want to It's largely the same code as in the as for delete Find all directory entries That match The file name part in the first 16 bytes of the fcb then We are going to Replace the Well the file name in the drent with the one in the The second 16 bytes of the fcb, so This is more annoying than it should be So we are going to load One byte from the fcb That's irritating we need um We need two pointer In the c's and there's only one register So And we're using a So it's not like we can just move shuffle things around through a we are in fact using all three registers So Do we do this the answer is annoyingly And with temporaries So x is the counter So this gets the pointer Into the fcb No, no, that's not what we want to do you want to add 16 at this point This gets the byte from the new file name We now want to Get x plus 1 into y Now this will work, but it just makes me really miserable. So we're not going to do it like that at all drent index So this is going to be a simple current drent comma y Ink 10 plus 0 ink 10 plus 1 lda 10 plus 0 and this can be fcb f1 compare with fcb t3 plus 1 meaning that we've reached the end of the file name Okay, that's not that bad right And there is in fact a bug here Which is I haven't inverted the names of the Files So this is actually taking old file new file, which is wrong So i'm going to have to flip that but uh, why are we getting invalid file name? See this was working and also shouldn't this be Shouldn't this be a hard error this should indeed Be a hard error. This should be Rewinding the stack and going back to the Uh I'm going back to the main loop So t transfer the stack pointer into x store that in stack pointer Wait a minute the message in x a And Return to the main loop So this will be call that print a new line Rewind the stack and Jump back to the main loop. So this then becomes jump error And we are printing a new line here because these cost three bytes So by taking off this and this We actually save one byte Jump abs across page border. Why is it warning r? um cause I believe that some versions of the 6.02 have a bug Where if the address being loaded by the jump Is across a page border. Let me jump in then Bad stuff happens But it's not documented in this file as far as I can tell Well, it's going to move as soon as we put some more code in so let's just Right Okay, so I think that just tried to rename something but there was no foo test sub bar Why am I getting an invalid file name there? Have I broken? The fcb parsing no that works fine. So that works that doesn't work I think it's the extensions. It doesn't like but that works Even deleted it Okay more debugger Yeah, looking at the time. I suspect that this is going to be the Last thing I do today I think that's bailed here. Sometimes if I use a question mark Like equals sign Yes, that's not even reaching this code Okay, it's the second file. It doesn't like test sub test bars That worked test sub test That didn't work. It's insisting that the second file has a Uh extension So read the file name skip non dot character Skip non dot file name characters. Yeah If the character we see is a terminator character this Needs to be here Because we want to do this before we call inks not after Because we want to be left looking at the dot Or Uh, hang on hang on At this point, we don't want to break If we're looking at a terminator Put that back the way it was Okay, the order of this code is weird Right, we look at If the character we're currently looking at is a dot Is not a dot Move on to the next one Was it a zero then stop Is it a terminator? Then stop Is it a valid file name character? What we want to do here is consume bytes Until we reach the end of the file name So this is the diffs of what we've actually done So yeah, this all looks fine. That's the wrong piece of code Yeah, I don't think there's anything particularly Uh, wait a minute. Wait a minute This doesn't look right We are here branch if equal to exit Uh, we I don't believe we were properly testing for the end of Line Okay, Ren Test dot sub to foo Bad file name. That's a different error Okay, we want to check to see whether The remaining characters are valid, but then we discard them if they're not valid then we fail That's right. That's right That's right. This is why you should always be using the version control system It's not so much of preserving changes And your revision history It's more about being able to look at what you've done today and find the stupid bugs That produces bad file name So I think that when we reach the end of the line We are Not parsing things correctly. Okay, I'm gonna have to hit the debugger again I'm trying to rather trying to avoid that really Okay, um Okay, right. So seven b a a is where our Well seven b Nine oh ish is where our fcb is And we can see it right there So we have that's at seven b a a six Seven b a six seven eight nine Okay, here are scbs. Those are both fine So we are looking at seven b a a Nine a so we're looking at the first fcb It is not a space now. We look at seven b b a that should be this one It is also Not a space. So now we go into bdos rename Did I just forget to rebuild? I think I forgot to rebuild Now I get a bad file name Right, it's more extensions. It's a stupid parse code. That's the problem Also, I'm thinking that I want to change some of this stuff. So the parse code doesn't wipe the allocation Table, I think the parse code should just set the 16 bytes of the Uh, the top half of the fcb Then the xfcb library that we're using will Wipe the rest of it When you call create or open But I will do that later Okay, that's time Let's go for the debugger. Okay, so seven nine five f Six oh so a contains two e. We're looking at the dot So is it dot? Yes, it is we go to here. We read the extension So skip the dot Set up y seven b o eight Here is our input buffer Is nine One two three four five six seven eight Nine. Yep. We're on the the byte after we're on the s of sub So Load it Yes Not the end of the line Not the After the third extension character Is it terminator character? carry is clear is not Is it another dot Why is that there? But that's kind of irrelevant a file name with two dots and it is actually actually a file name with two dots And it is invalid character set to Return with carry So we're not a dot Uh, is it a star? It's not a star Is it a valid file name character? Yes, it is so we store it Go around again We get the u exactly the same thing should apply here. We're on the b Okay, this is the first interesting one. We have read a space is Hang on a second We missed something compare and see Break if ah Ah, okay We've hit this condition because we this that was the third extension character So we now skip over to here was the thing we just read a Okay, I know what's happening Do I yes, I do know what's happening So we are looking at The Yes This code is wrong We are currently looking at the character after the file name That's that space there This code is expecting to find an extension Which there isn't one Because I just cut and pasted all this stuff from further up So that's why this is here So in fact, we're going to ignore this We're going to ignore an extra dot at this point Uh What we want to do is to skip valid file name characters until we get a terminator So Break if carry is set Otherwise Naturally, we want to do Is valid file name char If it's not a valid file name char Fail with the yes, this is an invalid fcb otherwise Fetch the next character And this code We actually want this to be Here see how does this work If we're looking at a space If we're looking at a dot Then Stop scanning If it's a terminator character Then we've reached the end of the fcb so Exit successfully if it's not valid fail Otherwise Go around again And I think that this only Is causing a problem because this is the first time we're actually using two Parameters To a command. So let's try this Okay, that passed but it didn't actually do anything Which is, you know, not so great, but at least it passed Okay, that's correct And that's correct So now we go into the bdos code e1 3 4 5 6 Okay, we're here We are reading Sure want to go around this again We are reading bytes We did indeed find a file Oh, I know what the problem is I'm not writing this back to disk. That's what I'm not doing It's renamed it So let's move and put it back again And that didn't work Why didn't that work? That failed to find a file called foo. That's why it didn't work So is 7b9b the fcb? Yes, it is Well, that looks fine F00 not for spaces So why is Look at find first. Why is that not finding it? a is c So zero one two three four five six seven eight nine abc That's should be correct So home drive reset dear pause read dear entry check dear pause No more files. We do have files That's the user want to see deleted files Just hadn't thought did I remember did I erase the metadata part of the file when I did the rename? No, I didn't I only replace the file name characters. Okay, we're here so We load a byte from the fcb Is it a question mark? Okay compare characters. We're here So here is the drn to we're looking at So we actually want to go four times to get to the next directory entry I didn't put a break point there f7 five Uh one two three Four who should be on the second sector Yep, and now we want to go on to the third sector. So we go another four two three four Four that was five times It's late. Okay One two three four One two three four right Here is the directory buffer It put the file name in the wrong place. There is a zero byte before the f. So obviously nothing matches That's why that's not working So we are actually copying from We're copying like that Oh the foos that is invalid. So can I do Star dot yes that matches So I should be able to do ren star dot bar Okay, let's use the power of wildcards to rename it and now I should be able to put this back to test.sub the way it was good Okay, in fact, we shouldn't be able to use wildcard there. I should be checking and not letting the user do it uh it doesn't say anything here but Renaming with a wildcard doesn't actually make any sense because it will rename all directory entries of all files that match To be the same and you'll end up with duplicate directory entries Which is wrong Traditionally, it's the ccp that does this Speaking of which We need to fix this So Paa's fcb needs to change We don't want to wipe all the fcb We actually want to wipe up to The four bytes of metadata The rest of it we leave untouched In fact, I forgot to update the comment from last time. So uh These what we're dealing with here are x fcb. So we do have the user number But I think we also want No, no This is all the job of clear So this has cleared Uh Ex and cr but we actually want to do all the rest. So ldy sfcb Uh We're actually going to wipe everything up to their r2. So we're going to want to go all the way up to r2 plus one until Equals and we want the sif library Okay So we run it test.sub to foo. Did that work? It did It's like foo that works Okay, I think that's working fine. Let's just do Yep, that's working too In fact, if I go to user one do dump test.sub uh I haven't done any of the parse codes to parse a user number on the front of a drive Because it should eventually work like using file names like This but I haven't done that. That's an extension. That's not part of core cpm As I said earlier cpm actually handles user bytes very badly, which is why I'm putting these xfcb things in Okay, so that works. So this allows me to go to Entry Rename And we just swap these two round We also no longer need this So So that should be ren foo equals test.sub And it works ren test.sub equals foo And that works Okay, I think that's better now Took long enough, but I think it's better and it's 22 47 my local time so So next time I need to do update file attributes, which is actually it's essentially the same code as Rename in the bedos, but there isn't a command for it in the ccp We actually now have implementations for all of these which is nice You're supposed to be able to set attributes using stat and stat doesn't work So I will do some investigation to figure out how that works But we've got two more system calls to do Yeah, nothing It's supposed It's supposed to list the For each file that matches the wildcard. It's supposed to list the number of records the size in bytes and Whatever these are I can't remember what these are This is supposed to be a byte for byte Port of the original cpm version written in plm But I'm not sure I got it right Yes, and the last system calls are the random access ones That should actually be reasonably straightforward Set file attributes is here Random access Oh, and there's this one That'll be nice Then it's done Apart from the bugs and the missing features