* TOUCH.ASM: A recreation in assembler of the UN*X command of the same name. * Works by read & write rather than attempting to muck directly * with filesystem datestamps. * Perpetrator: Dewi Williams ..!ihnp4!druca!dewi * Unconditionally placed in the public domain. * Included equate files. * ----------------------------------------------------------------------- NOLIST INCLUDE "exec/types.i" INCLUDE "exec/libraries.i" INCLUDE "libraries/dos.i" INCLUDE "libraries/dosextens.i" INCLUDE "exec/memory.i" LIST * External references * ----------------------------------------------------------------------- EXTERN_LIB OpenLibrary EXTERN_LIB Read EXTERN_LIB Write EXTERN_LIB Output EXTERN_LIB Open EXTERN_LIB Close EXTERN_LIB Lock EXTERN_LIB UnLock EXTERN_LIB IoErr EXTERN_LIB Seek EXTERN_LIB AllocMem EXTERN_LIB FreeMem EXTERN_LIB Examine EXTERN_LIB DeleteFile * Local Equates * ------------------------------------------------------------------------ SysBase EQU 4 ; The one known address in the system. TRUE EQU -1 FALSE EQU 0 SEEKFAIL EQU -1 * Macros * ------------------------------------------------------------------------ * Calls to dos.library and exec library. Differing calls scrog different * registers. I chose to take no chances... callsys MACRO MOVEM.L D1-D7/A0-A6,-(SP) ; save registers CALLLIB _LVO\1 MOVEM.L (SP)+,D1-D7/A0-A6 ; and restore (AmigaDOS scrogs!). ENDM callexec MACRO MOVEM.L D1-D7/A0-A6,-(SP) ; save registers LINKLIB _LVO\1,SysBase MOVEM.L (SP)+,D1-D7/A0-A6 ; and restore (AmigaDOS scrogs!). ENDM * The print routine and argument setup PRINT MACRO lea.l \1(PC),A0 move.l D1,-(SP) move.l d5,d1 jsr print move.l (SP)+,D1 ENDM * General purpose close, print, and quit macro QUIT MACRO callsys IoErr ; return from IoErr will be exit code move.l D0,D6 ; save error return. Could use stack... PRINT \1 callsys Close ; assumes handle in D1 move.l d6,d0 ; restore error return bra FINISHED ENDM * The code segment * ------------------------------------------------------------------------- RORG 0 ; Relocatable code. jsr argtrim ; clean up command line ;------ get Exec's library base pointer: LEA.L DOSName(PC),A1 ; name of dos library move.l #LIBRARY_VERSION,d0 callexec OpenLibrary,SysBase MOVE.L D0,A6 ; Save library pointer (always in A6). BNE.S gotdos ; Should really issue an alert here... moveq #RETURN_FAIL,D0 ; give up bra FINISHED gotdos: * Obtain the output handle (needed for error prints). callsys Output MOVE.L D0,D5 ; Save for the write * Now check if we have an argument. cmpi.b #0,(A0) ; a null string for an argument ? beq noarg ; yes ; Actions differ depending on whether or not the file exists. If it ; does, we read and write the first byte (if it isn't empty!). If it ; doesn't, we create it as a zero length file. So we start off by ; attempting to lock the given name. move.l a0,a5 ; safe copy move.l a0,d1 ; supply the name move.l #ACCESS_READ,d2 ; only checking for existence callsys Lock move.l d0,d1 ; remember for later beq nolock ; did we get a lock? ; It exists: let's Examine it and see what it is. Need to deny a touch ; of a directory and do special actions for a zero length file. jsr CheckFile cmpi.l #0,D0 beq proceed cmpi.l #1,D0 beq unlinkit cmpi.l #2,D0 beq isadir ; touch a directory?! ; Get here if we encountered an error in CheckFile bra examerror ; It's a zero length file, so unlink it and re-create. unlinkit: callsys UnLock ; lock's still in D1 move.l a5,d1 ; the filename callsys DeleteFile ; blow it away bra nolock ; create a new zero length file ; It exists and is not empty, so unlock it and proceed with open read and ; write. proceed: callsys UnLock move.l a5,d1 ; the filename move.l #MODE_OLDFILE,d2 ; allows reading & writing, no creation. callsys Open move.l d0,d1 ; for read, write, seek and close beq noopen ; couldn't open, but it exists! lea.l char(pc),a4 ; where the single char read goes move.l a4,d2 moveq #1,d3 ; single character read callsys Read cmpi.l #1,d0 ; did it work? bne noread ; Seek back to the beginning. moveq #0,d2 move.l #OFFSET_BEGINNING,d3 ; seek mode -- 0 bytes from beginning callsys Seek cmpi.l #SEEKFAIL,D0 beq noseek ; seek failed for some reason move.l a4,d2 ; the character that was read moveq #1,d3 ; the count of characters callsys Write cmpi.l #1,d0 ; did it work? bne nowrite ; no callsys Close ; clean up move.l #RETURN_OK,D0 bra FINISHED ; finally nolock: ; It doesn't exist, so just create a zero length file. move.l a5,d1 ; the filename move.l #MODE_NEWFILE,d2 ; create callsys Open move.l d0,d1 ; for close beq nocreate callsys Close ; this should leave us with a zero length file move.l #RETURN_OK,D0 bra FINISHED * Error traps. isadir: callsys UnLock ; lock's still in D1 PRINT S_ISADIR move.l #RETURN_FAIL,D0 bra FINISHED examerror: PRINT S_EXAMERR move.l #RETURN_FAIL,D0 bra FINISHED noseek: QUIT S_NOSEEK nowrite: QUIT S_NOWRITE noread: QUIT S_NOREAD noopen: PRINT S_NOOPEN callsys IoErr ; return from IoErr will be exit code bra FINISHED nocreate: PRINT S_NOCREATE callsys IoErr ; return from IoErr will be exit code bra FINISHED noarg: PRINT S_NOARG move.l #RETURN_ERROR,D0 ; Fall through FINISHED: rts * Subroutines * ------------------------------------------------------------------------ * argtrim: a routine to trim the end of a command line and null terminate * it. This is achieved in the following manner: * Start at the end and run back until a non-whitespace (nl/blank) * character is encountered. If this is a quote, toss it. * If the *first* character is a quote, bump the start address by * one. No pretense of quote matching here. * Inputs: address in A0, length in D0 * Outputs: address in A0. argtrim: movem.l d1-d7/a1-a6,-(sp) ; save registers cmpi.b #'"',(a0) ; starts with a quote? bne.s checkline ; no subq #1,d0 ; yes - decrement count addq #1,a0 ; and bump start address checkline: cmpi.b #1,D0 ; length includes the newline bne.s isline ; yes - there is something there move.b #0,(a0) ; create null string bra.s argfini ; done isline: ; strip off any run of blanks on the end... move.l a0,a1 ; computing end address of line add.l d0,a1 ; subq #2,a1 ; toploop: cmp.l a0,a1 beq.s empty ; single char or run of blanks cmpi.b #' ',(a1) bne.s endloop ; finished the scan subq #1,a1 ; else back one more bra.s toploop ; and try again endloop: cmpi.b #'"',(a1) beq.s nullit nullnext: addq #1,a1 nullit: move.b #0,(a1) bra.s argfini empty: ; could be blanks or a single char cmpi.b #' ',(a1) bne.s endloop ; or maybe a single quote! move.b #0,(a0) argfini: movem.l (sp)+,d1-d7/a1-a6 ; restore registers rts * PRINT: Passed an EA in A0, and a handle in D1, print the string. print: MOVEM.L D0-D7/A0-A6,-(SP) ; Let's find out how long this string is... JSR strlen ; Addr in A0 returns len in D0 MOVE.L D0,D3 ; length MOVE.L A0,D2 ; Address callsys Write ; Handle already in D1 ; We don't check error returns from write, because there isn't a whole ; lot we can do if console writes fail. MOVEM.L (SP)+,D0-D7/A0-A6 ; Restore registers rts * STRLEN: How long is a piece of string? Pass a string in A0, the * result comes back in D0. strlen: MOVEM.L D1-D7/A0-A6,-(SP) ; Save registers A0, D1 on stack. MOVEQ #0,D0 ; Initialize count STRLOOP: ; Investigate current character. cmpi.b #0,(A0)+ ; Is it a NUL? beq.s STRFINI ADDQ #1,D0 ; Up the count bra STRLOOP STRFINI: MOVEM.L (SP)+,D1-D7/A0-A6 ; Restore registers rts ; Passed a lock in register D1 this routine checks out the file attributes. ; Returns 0 to proceed, 1 if empty, 2 if a directory, and 3 for error. CheckFile: MOVEM.L D1-D7/A0-A6,-(SP) ; Save registers on stack. move.l d1,d4 ; save it for future use ; A lock in D1 and a FileInfoBlock address in D2 are all that are needed. ; We allocmem the FileInfoBlock to assure alignment. move.l #fib_SIZEOF,D0 ; size of the block move.l #MEMF_PUBLIC,D1 ; memory requirement callexec AllocMem ; args D0, D1 returns into D0 move.l d0,a5 ; save it for later beq examnomem ; but did it work? move.l d0,d2 ; fileinfoblock address move.l d4,d1 ; the lock callsys Examine ; see what the lock corresponds to. cmpi.l #FALSE,d0 ; it should work... beq noexam ; but it didn't! ; We have an Examine structure. Now check out if it's a directory or not. ; fib_DirEntryType is >0 for a directory and < 0 for a file tst.l fib_DirEntryType(A5) bgt directory ; it's a directory ; It's just a filename. Let's see how large it is. tst.l fib_Size(A5) beq isempty ; nothing in it -- so we can't read moveq #0,D6 ; the flag character bra examfree * Error & cleanup for this routine examnomem: ; couldn't get memory moveq #3,D6 bra examfini noexam: ; couldn't Examine moveq #3,D6 bra examfree directory: ; it's a directory moveq #2,D6 ; set return code bra examfree ; release memory isempty: ; it's empty moveq #1,D6 ; fall through examfree: ; free the memory here move.l #fib_SIZEOF,D0 ; Address in A1, size in D0 move.l a5,a1 callexec FreeMem ; restore memory examfini: move.l d6,d0 ; set return code MOVEM.L (SP)+,D1-D7/A0-A6 ; Restore registers rts * Data declarations * ------------------------------------------------------------------------- DOSName DOSNAME char DS.B 1 ; scratch S_NOARG DC.B 'Usage: touch file',10,0 S_NOCREATE DC.B 'Failed to create file',10,0 S_NOOPEN DC.B 'Cannot open file',10,0 S_NOREAD DC.B 'Cannot read from file',10,0 S_NOWRITE DC.B 'Cannot write to file',10,0 S_NOSEEK DC.B 'Cannot rewind file',10,0 S_EXAMERR DC.B 'Error in examining file',10,0 S_ISADIR DC.B 'Cannot touch a directory!',10,0 END