Skip to main content
? Dieu of Hemoroids

ADVANCED PROGRAMMING .. BACKING UP YOUR HARD DISK
by Stefan Posthuma

It's  here!  The super-duper fantastic computer controlled  fully 
automatic  85  watt Kenwood stereo-tower!  Like  I  said,  it  is 
automatic.  Even the record player scans the record with a light-
beam  so it can be programmed to play certain tracks.  Right  now 
the 120 watt Magnat loudpspeakers are reproducing the  vibrations 
earlier recorded on a single-CD by the Pet Shop Boys.  It  sounds 
fantastic. YEAH!

Sorry  folks,  but I just had to tell you about this  mega-stereo 
that cost me a fortune. Let's get down to programming.

I  have  had a hard disk now for about six months,  and  in  that 
time,  hundreds of files have been put on it.  Ranging from Degas 
pictures  drawn in an artistic mood while  drinking  Bacardi-Cola 
and  listening to Jean Michel Jarre (digitally from now  on),  to 
heave assembler sources programmed while eating  crips,  drinking 
beer and listening to hip-hop music.
I  am very fond of all these files and it would really affect  my 
happiness when they would be deleted for some reason. Also, there 
are  a  lot  of people who use their  hard-disks  to  store  huge 
databases  like  all  members of  the  local  underwater-checkers 
team  (number  of  matches  played  without  having  to   undergo 
artifical  respiration,  number of minutes they managed  to  stay 
under  water without turning purple etc.) or large  book-accounts 
from their mouse-breeding companies.
I  think  having your hard disk erased  (entirely  or  partially, 
unhappy  the  user  will be) or even  formatted  by  your  little 
brother exploring GEM is a downright disaster.  It will result in 
loud cries of anger,  head-banging,  cursing,  beating up  little 
brothers,  consuming large amounts of alcoholic  fluids,  terribe 
tempers  and  more things that are not good for your  mental  and 
physical condition.

Fortunately  there are programs which enable you to back up  your 
hard disk.  These programs read the hard-disk and copy the  files 
to  floppy-disks.  They will use special floppy-disk  formats  so 
they  can  write faster and store more information  on  a  single 
disk.
Some of these programs are commercial and some of them are PD. Of 
course, a backup program must also be able to work in reverse. If 
you  made  a backup,  and your hard disk was  formatted  by  your 
little  brother  (in which case it is handy to  have  two  little 
brothers so you can bang their heads together),  you can  reverse 
the process and recover the files that were deleted.  Of  course, 
all  changes that you made since the last backup  are  lost,  but 
it's  better  than  nothing.  (If your little  brother  used  the 
floppies you backed-up on to store his Star Wars high  scores,  I 
suggest you wrap him up in Ajax scarves and drop him between  the 
hard-core  FC-Den Haag  supporters during a match in  which  Ajax 
beats FC-Den Haag with let's say 6-0).

Two days a week I have to go to Amstelveen to work at a  client's 
office.  They make a lot of backups and I worked with a couple of 
backup programs.  I was impressed by their performance, and I was 
inspired  to write my own backup-program on my ST.  I  found  out 
that  it isn't so hard,  and I decided to write an article  about 
it.  We  have to write a program that reads files from  the  hard 
disk  and writes those files to floppy disks.  Now we don't  just 
copy the files,  like you would using the desktop.  Because  this 
will  present some problems.  Let's say you have  a  double-sided 
diskdrive  and  you are copying files from the hard disk  to  the 
floppy disk.  At a certain point, there is still 300K free on the 
disk and you have to write a large file of 310K.  This means  you 
have  to take a new floppy and leave 300K unused unless you  want 
to do a lot of diskswapping which is likely to cause errors,  and 
you don't want errors on the backup! Or maybe you have very large 
database files bigger than 720K. Now you can't even back it up! I 
have thought of it and came up with the following solution:

First, we create a buffer in memory as large as possible. Then we 
read  the files from the hard disk into this  buffer.  Meanwhile, 
another  routine  is  writing the buffer track by  track  to  the 
floppy disk. This means that a file can be split across disks and 
that  folders and stuff are not copied.  They will be created  if 
nessecary  when  reading  back the  files.  Every  file  will  be 
preceded by a header containing full pathname and the size of the 
file.
Two interesting problems arise here:

How  can I read all files on the hard disk,  including  files  in 
folders?
How can I write a disk track by track?
Some  more interesting things will be covered like how to  detect 
the insertion or removal of a (non-write protected disk) and  the 
use of two routines that use a buffer completely independendly so 
it  looks like the program is reading the hard disk  and  writing 
the floppy disk at the same time. 
The  sample program has been written in GfA 3.0 and uses some  of 
the great new functions,  so it will be interesting to fresh  GfA 
3.0 programmers.

The program:

First we have to allocate a buffer and reserve memory for it. The 
idea behind this has been explained in  'advanced GfA' article in 
the previous issue of ST NEWS (3.3)

RESERVE
mem_top%=HIMEM
RESERVE 25000    ! 25K for program & variables.
@init

The init routine will inialize a lot of stuff used:

PROCEDURE init
  dta%=HIMEM        ! adress of DTA buffer to be used
  wbuf%=HIMEM+4400  ! max 100 levels of sub-directory nesting
  buf%=HIMEM+13000  ! adress of buffer
  bufsize%=mem_top%-buf%  ! get buffer size
  bufpoint%=0             ! buffer is still empty
  IF bufsize%<4608        ! if buffer size < 1 track, forget it
    ALERT 1,"Not enough free memory|available!",1," OK ",d%
    EDIT
  ENDIF
  spc|=0            ! number of spaces to indent
  dnr|=1            ! backup disk #
  dr$="A"           ! disk identifier
  format!=FALSE     ! don't format destination yet
  sides|=2          ! double sided disk drive
  drive|=0          ! drive number zero (drive A)
  track|=0          ! track number zero
  side|=0           ! size zero
  wstatus|=2        ! status of write routine 
  treshold%=4608    ! minimal buffer size
  @get_wp           ! get write-protect adress 
  @message(1)       ! "remove disk from drive ";dr$
  old_path$=DIR$(0) ! get old path
RETURN

These are a lot of variables used by the various routines.  Their 
meaning will become clear when they are used and discussed.

The  next routine will be the actual backup routine.  It will  be 
called with the pathname where the backup should start,  the name 
of  the  file(s)  to be backed up (with  wildcards)  and  a  flag 
indicating if you want to backup folders as well.

PROCEDURE backup(path$,name$,r!)
  LOCAL e%,fnam$
  '
  ' first, search all files
  CHDIR path$            ! make sure it exists
  ~FSETDTA(dta%)         ! set DTA buffer
  e%=FSFIRST(path$+name$,-1)  ! get first file
  DO UNTIL e%                 
    IF (BYTE{dta%+21} AND 3)<>0 OR BYTE{dta%+21}=0
      PRINT AT(1,spc|/3+1);TAB(spc|);CHAR{dta%+30};SPACE$(20)
      rstatus|=0    ! read a new file
      REPEAT
        @read(CHAR{dta%+30})  ! read the file or a part of it
        @write                ! write a track from the buffer 
      UNTIL rstatus|=3        ! repeat until entire file is read
    ENDIF
    e%=FSNEXT()               ! get next file
  LOOP
  '
  ' next, search all directories and recursively search them
  IF r!=TRUE
    e%=FSFIRST(path$+"*.*",-1)     ! all files
    DO UNTIL e%
      IF BTST(BYTE{dta%+21},4)     ! is it a directory?
        fnam$=CHAR{dta%+30}
        IF BTST(BYTE{dta%+21},4)
          IF fnam$<>"." AND fnam$<>".."      ! skip these
            PRINT AT(1,spc|/3+1);TAB(spc|);">> ";fnam$;SPACE$(20)
            ADD dta%,44            ! next DTA buffer
            ADD spc|,3             ! 3 more spaces to indent
            CHDIR fnam$            ! go to the directory
            backup(path$+fnam$+"\",name$,r!) ! call yourself 
            CHDIR ".."                       ! go back
            PRINT AT(1,spc|/3+1);SPACE$(60)  ! erase filename
            SUB dta%,44                      ! get old DTA buffer
            SUB spc|,3                      
            ~FSETDTA(dta%)                   ! set it
          ENDIF
        ENDIF
      ENDIF
      e%=FSNEXT()                            ! get next file
    LOOP
  ENDIF
RETURN

This actually is a routine that searches all files on the current 
disk and prints their names.  If it is a folder,  a '>>' will  be 
added  to  the filename and the contents of the  folder  will  be 
shown  one  line down and 3 spaces further to the  right  on  the 
screen.  For  every file,  it calls the read and  write  routines 
until the entire file has been read into the buffer.
The routine consists of two loops.  The first one will search all 
normal,  read-only  and hidden files matching the given  filename 
and will back them up.  The next loop is more  interesting.  This 
one  will search all files and when this file is a folder (bit  4 
of the attribute is set),  it will enter this folder and call the 
backup  routine (recursion) with the new pathname.  But there  is 
one little trick here and that is the stacking of DTA buffers. If 
you wouldn't do this,  the called backup routine will destroy the 
current  DTA  buffer  and  the next  FSNEXT  calls  will  produce 
unpredictable results.  Fortunately,  SETDTA does not destroy the 
DTA  buffer,  else it would be a lot more  difficult!  Some  more 
complex lines: 

IF (BYTE{dta%+21} AND 3)<>0 OR BYTE{dta%+21}=0
  PRINT AT(spc|,spc|/3+1);CHAR{dta%+30};SPACE$(20)

The BYTE function is the same as PEEK.  So it PEEKs DTA%+21. This 
is where the attributes of the current file resides. It ANDs this 
attribute  with  three  and thus checks if bits 0 or  1  are  set 
(read-only and hidden),  or no bits are set at all (normal, read-
write  file).  If  this is TRUE,  the filename  which  starts  at 
dta%+30 and is terminated with a null-byte will be printed  using 
the great new CHAR function.  The variable spc| is used to indent 
the printing so it will look nicely on the screen.

The next routine to discuss is the read routine which will read a 
file into the buffer.  The read routine has four  stages:

0 - open the file
1 - create a header in the buffer providing there is room for it.
2 - read the entire file or a part of it depening on buffer-space
3 - the file is read, do nothing
4 - create  a  special last file header to mark the  end  of  the  
    backup.

The variable rstatus| will hold the current stage.
The name of the file is passed to the routine. (fnam$)

PROCEDURE read(fnam$)
  LOCAL x$
  '
  bfree%=bufsize%-bufpoint%-512    ! get free buffer space
  '
  IF rstatus|=0         ! 0 : open file           
    OPEN "i",#1,fnam$
    fsize%=LOF(#1)
    rstatus|=1
  ENDIF
  '
  IF rstatus|=1 AND bfree%>=132    ! still room for header?
    x$=SPACE$(132)       
    LSET x$=DIR$(0)+"\"+fnam$   ! pathname and filename in x$      
    MID$(x$,129,4)=MKL$(fsize%) ! filesize in x$   
    BMOVE VARPTR(x$),buf%+bufpoint%,132  ! move x$ to buffer
    ADD bufpoint%,132     ! increase buffer pointer        
    SUB bfree%,132        ! decrease free buffer space
    rstatus|=2            ! status=2: read file
  ENDIF
  '
  IF rstatus|=2 AND bfree%>0       ! still room in buffer?
    toread%=MIN(fsize%,bfree%)     ! number of bytes to read
    BGET #1,buf%+bufpoint%,toread% ! read them into buffer
    ADD bufpoint%,toread%          ! increase pointer
    SUB fsize%,toread%             ! decrease free space
    @bufusage                      ! inform user
    IF fsize%=0                    ! entire file read?
      rstatus|=3                   ! yes, inform main routine
      CLOSE #1                     ! close file
    ENDIF
  ENDIF
  '
  IF rstatus|=4 AND bfree%>=132    ! write 'last file' header
    x$="..."+SPACE$(129)           ! impossibe filename "..."
    BMOVE VARPTR(x$),buf%+bufpoint%,132
    ADD bufpoint%,132
    SUB bfree%,132
    rstatus|=5                     ! the end.
  ENDIF
RETURN

This routine is quite simple. Thanks to the 'stage' construction, 
it  won't  just stop and wait when the buffer is  full.  It  will 
always  return,  no matter what.  (This does not  include  system 
crashes and little brothers)

The  only thing we need now is a routine that will write to  disk 
track  by track.  This is a little more complex one,  because  it 
does  things like writing tracks,  formatting them if  nessecary, 
checking disk swaps etc. This routine also has various stages:

0 - create boot sector on disk
1 - write a track from the buffer
2 - wait for disk to be removed from drive
3 - wait for disk to be inserted

The first stage is quite easy. Just write side 0, track 0, sector 
1 with a certain pattern.
The  second stage has to write a track to disk when there  is  at 
least  one track in the buffer.  It writes a track,  and when  an 
error  occures  (non-formatted disk perhaps) it will  format  the 
track  and  all  forthcoming tracks on the disk  by  setting  the 
format flag. The next two stages are quite interesting.
To make the program a little smart and to relieve the user of the 
enormous task to press a key when he has inserted a new disk, the 
program   detects   disk-swaps  itself.   There  is   a   certain 
(undocumented, the routine get_wp will determine it) adress which 
will indicate whether or not a disk is write protected.  It  will 
always  indicate  write-proteced  when there is no  disk  in  the 
drive.  So  if  you use non-write protected disks (they  have  to 
be!),  it will work.  If you want to wait for the user to  remove 
the disk,  wait for this value to become 255. If you want to wait 
for the user to insert a disk,  wait for this value to become  0! 
It  won't  detect write-protected disks being  inserted,  but  we 
don't want these! So its near perfect. (We can't tell the user to 
remove the write protection) This is the complete routine:

PROCEDURE write
  IF wstatus|=0
    @createboot  ! create a boot sector
    wstatus|=1
  ENDIF
  '
  IF (wstatus|=1 AND bufpoint%>=treshold%) 
    ' at least one track in buffer?
    @wrtrack             ! write a track to disk
    ADD written%,4608    ! to inform user
    BMOVE buf%+4608,buf%,bufsize%-4608  ! scroll buffer
    SUB bufpoint%,4608   ! decrease buffer pointer
    @bufusage            ! inform user
    @diskfull            ! increase track number, side etc.
    IF full!=TRUE        ! disk full?
      wstatus|=2
      message(1)         ! "remove disk from drive..."
    ENDIF
  ENDIF
  '
  IF wstatus|=2 AND PEEK(prot%)=255  ! disk removed (WP on)
    wstatus|=3
    message(2)
    INC dnr|        ! next disk
  ENDIF
  '
  IF wstatus|=3 AND PEEK(prot%)<>255 ! disk inserted?
    wstatus|=0
    track|=0
    side|=0
    format!=FALSE   ! initialize some stuff
  ENDIF
RETURN

The business about scrolling the buffer has to explained a little 
perhaps.  The track is written from the beginning of the  buffer, 
and  the 4608 bytes written have to be removed.  So we just  move 
the entire buffer 4608 bytes 'to the left', and adjust the buffer 
pointers.

The  next routine is the one that writes a track from the  buffer 
to  the floppy disk.  It uses standard low-level XBIOS  routines. 
They  will not produce irritating alert boxes when a floppy  disk 
is not formatted but will return a neat error code.
PROCEDURE wrtrack
  LOCAL ok!,d%
  '
  IF format!=TRUE
    message(5)      ! inform user (formatting track...)
    errproc(XBIOS(10,L:wbuf%,L:0,drive|,9,track|,side|,1,
       L:&H87654321,&HE5E5))
    ' flopformat Xbios function takes a lot of parameters...
  ENDIF
  ok!=FALSE
  WHILE ok!=FALSE
    message(4)      ! writing track...
    IF XBIOS(9,L:buf%,L:0,drive|,1,track|,side|,9)<>0
      ' error occured..try to format the track
      IF format!=FALSE
        message(5)
        errproc(XBIOS(10,L:wbuf%,L:0,drive|,9,track|,side|,1,
          L:&H87654321,&HE5E5))
        format!=TRUE
      ELSE
        ' real bad....
        ALERT 1,"Error occured!",1," OK ",d%
        EDIT
      ENDIF
    ELSE
      ok!=TRUE
    ENDIF
  WEND
RETURN

The  exact  description  of the Xbios  functions  Flopformat  and 
Writetrack  can  be found in so many documents that  I  will  not 
repeat it. I just don't feel like it. PERIOD!

Sorry 'bout that.

The  next  routine will increase the track and  side  number  (if 
nessecary)  and will set a flag when the disk is full and  should 
be swapped.
PROCEDURE diskfull
  full!=FALSE
  IF track|=79 AND ((sides|=2 AND side|=1) OR sides|=1)
    full!=TRUE
  ELSE
    IF sides|=2
      IF side|=0
        INC side|
      ELSE
        side|=0
        INC track|
      ENDIF
    ELSE
      INC track|
    ENDIF
  ENDIF
RETURN

Not much to explain about the next routine.  Its just some little 
thing to inform the user about what's going on.
PROCEDURE bufusage
  PRINT AT(2,20);
  PRINT USING "Buffer size: ######  ",bufsize%;
  PRINT USING "Buffer pointer: ######  ",MAX(0,bufpoint%);
  PRINT USING "Buffer usage: ###%",MAX(0,CINT((bufpoint%/
    bufsize%)*100))
RETURN

this  routine  determines the illegal  write-protect  adress.  It 
works for TOS-ROM, TOS-RAM and MEGA-ST ROM.
PROCEDURE get_wp
  LOCAL buf%
  '
  buf%=LPEEK(&H4F2)
  IF DPEEK(buf%)<>&H601E
    prot%=&H9F8
  ELSE
    prot%=&H9B2
  ENDIF
RETURN

Some nice messages....
PROCEDURE message(x|)
  IF x|<4
    PRINT AT(2,21);SPACE$(78)
  ENDIF
  PRINT AT(2,21);
  SELECT x|
  CASE 1
    PRINT "Remove disk from drive ";dr$
  CASE 2
    PRINT "Insert backup disk #";dnr|;" in drive ";dr$
  CASE 3
    PRINT "Creating bootsector..."
  CASE 4
    PRINT USING "   Writing track ##, side #, #####K written ",
       track|,side|,CINT(written%/1024)
  CASE 5
    PRINT USING "Formatting track ##, side #, #####K written ",
       track|,side|,CINT(written%/1024)
  ENDSELECT
RETURN

Create a boot sector and put it in the buffer
PROCEDURE createboot
  LOCAL boot%
  '
  INLINE boot%,512
  ~XBIOS(18,L:boot%,L:-1,1+sides|,0)
  BMOVE buf%,buf%+512,bufsize%-512
  ADD bufpoint%,512
  @bufusage
  BMOVE boot%,buf%,512
RETURN

The  INLINE command here is a little special.  In this  case,  it 
will  reserve  512  bytes inside  the  GfA  source  program.  The 
variable boot% will receive the adress of the 512 bytes.  In  the 
sample program, the 512 bytes have been filled with a valid boot-
sector  from a (non-auto boot) diskette.  If you create your  own 
program using this boot sector code,  read in the file (using the 
HELP key) 'BOOTSEC.INL' which can be found in the PROGRAMS folder 
on this very ST NEWS diskette. 

Catch unexpected TOS-errors when using XBIOS routines
PROCEDURE errproc(e%)
  IF e%<>0
    ALERT 1,"Error number "+STR$(e%)+" occured!",1," OK ",d%
    EDIT
  ENDIF
RETURN

That's about it. 

Cool.

But this is just the backbone of a full-grown backup program.  It 
has no GEM driven extremely-friendly 'even a sucker can use' user 
interface.  So  get programming and I expect the PD market to  be 
flooding with backup programs in a few weeks. Understand?

It  also  has no capabilities of doing a  'update'  backup.  This 
means  that  you just back up the files changed  since  the  last 
backup. TOS has a nice little file status bit named 'archive'. It 
will be set as soon as the file has been touched.  But I did  not 
exactly test it so you'll have to find out for yourself.

Also....

The restore routine isn't here!  I will present you with one next 
issue of ST-NEWS. I already wrote it, but I think there is enough 
GfA right now. Just wait 'till next issue and check it out.

See you around.

Can we who man the ship of State, deny that it is somewhat out of
control?

(was not was .. tell me that I'm dreaming .. an oldie)

Disclaimer
The text of the articles is identical to the originals like they appeared in old ST NEWS issues. Please take into consideration that the author(s) was (were) a lot younger and less responsible back then. So bad jokes, bad English, youthful arrogance, insults, bravura, over-crediting and tastelessness should be taken with at least a grain of salt. Any contact and/or payment information, as well as deadlines/release dates of any kind should be regarded as outdated. Due to the fact that these pages are not actually contained in an Atari executable here, references to scroll texts, featured demo screens and hidden articles may also be irrelevant.