* 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
