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.