mirror of
				https://github.com/FDOS/kernel.git
				synced 2025-10-31 03:14:40 +01:00 
			
		
		
		
	git-svn-id: https://svn.code.sf.net/p/freedos/svn/kernel/branches/UNSTABLE@1025 6ac86273-5f31-0410-b378-82cca8765d1b
		
			
				
	
	
		
			1111 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1111 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /***************************************************************
 | |
| 
 | |
|                                     sys.c
 | |
|                                     DOS-C
 | |
| 
 | |
|                             sys utility for DOS-C
 | |
| 
 | |
|                              Copyright (c) 1991
 | |
|                              Pasquale J. Villani
 | |
|                              All Rights Reserved
 | |
| 
 | |
|  This file is part of DOS-C.
 | |
| 
 | |
|  DOS-C is free software; you can redistribute it and/or modify it under the
 | |
|  terms of the GNU General Public License as published by the Free Software
 | |
|  Foundation; either version 2, or (at your option) any later version.
 | |
| 
 | |
|  DOS-C is distributed in the hope that it will be useful, but WITHOUT ANY
 | |
|  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | |
|  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | |
|  details.
 | |
| 
 | |
|  You should have received a copy of the GNU General Public License along with
 | |
|  DOS-C; see the file COPYING.  If not, write to the Free Software Foundation,
 | |
|  675 Mass Ave, Cambridge, MA 02139, USA.
 | |
| 
 | |
| ***************************************************************/
 | |
| 
 | |
| #define DEBUG
 | |
| /* #define DDEBUG */
 | |
| 
 | |
| #define SYS_VERSION "v3.4"
 | |
| 
 | |
| #include <stdlib.h>
 | |
| #include <dos.h>
 | |
| #include <ctype.h>
 | |
| #include <fcntl.h>
 | |
| #include <sys/stat.h>
 | |
| #ifdef __TURBOC__
 | |
| #include <mem.h>
 | |
| #else
 | |
| #include <memory.h>
 | |
| #endif
 | |
| #include <string.h>
 | |
| #ifdef __TURBOC__
 | |
| #include <dir.h>
 | |
| #endif
 | |
| #define SYS_MAXPATH   260
 | |
| #include "portab.h"
 | |
| #include "algnbyte.h"
 | |
| #include "device.h"
 | |
| #include "dcb.h"
 | |
| #include "xstructs.h"
 | |
| #include "date.h"
 | |
| #include "../hdr/time.h"
 | |
| #include "fat.h"
 | |
| 
 | |
| /* These definitions deliberately put here instead of
 | |
|  * #including <stdio.h> to make executable MUCH smaller
 | |
|  * using [s]printf from prf.c!
 | |
|  */
 | |
| extern int VA_CDECL printf(const char * fmt, ...);
 | |
| extern int VA_CDECL sprintf(char * buff, const char * fmt, ...);
 | |
| 
 | |
| #include "fat12com.h"
 | |
| #include "fat16com.h"
 | |
| #ifdef WITHFAT32
 | |
| #include "fat32chs.h"
 | |
| #include "fat32lba.h"
 | |
| #endif
 | |
| 
 | |
| #ifndef __WATCOMC__
 | |
| 
 | |
| #include <io.h>
 | |
| 
 | |
| #else
 | |
| 
 | |
| long filelength(int __handle);
 | |
| #pragma aux filelength = \
 | |
|       "mov ax, 0x4202" \
 | |
|       "xor cx, cx" \
 | |
|       "xor dx, dx" \
 | |
|       "int 0x21" \
 | |
|       "push ax" \
 | |
|       "push dx" \
 | |
|       "mov ax, 0x4200" \
 | |
|       "xor cx, cx" \
 | |
|       "xor dx, dx" \
 | |
|       "int 0x21" \
 | |
|       "pop dx" \
 | |
|       "pop ax" \
 | |
|       parm [bx] \
 | |
|       modify [cx] \
 | |
|       value [dx ax];
 | |
| 
 | |
| extern int unlink(const char *pathname);
 | |
| 
 | |
| /* some non-conforming functions to make the executable smaller */
 | |
| int open(const char *pathname, int flags, ...)
 | |
| {
 | |
|   int handle;
 | |
|   int result = (flags & O_CREAT ?
 | |
|                 _dos_creat(pathname, _A_NORMAL, &handle) :
 | |
|                 _dos_open(pathname, flags & (O_RDONLY | O_WRONLY | O_RDWR),
 | |
|                           &handle));
 | |
| 
 | |
|   return (result == 0 ? handle : -1);
 | |
| }
 | |
| 
 | |
| int read(int fd, void *buf, unsigned count)
 | |
| {
 | |
|   unsigned bytes;
 | |
|   int result = _dos_read(fd, buf, count, &bytes);
 | |
| 
 | |
|   return (result == 0 ? bytes : -1);
 | |
| }
 | |
| 
 | |
| int write(int fd, const void *buf, unsigned count)
 | |
| {
 | |
|   unsigned bytes;
 | |
|   int result = _dos_write(fd, buf, count, &bytes);
 | |
| 
 | |
|   return (result == 0 ? bytes : -1);
 | |
| }
 | |
| 
 | |
| #define close _dos_close
 | |
| 
 | |
| int stat(const char *file_name, struct stat *buf)
 | |
| {
 | |
|   struct find_t find_tbuf;
 | |
|   UNREFERENCED_PARAMETER(buf);
 | |
| 
 | |
|   return _dos_findfirst(file_name, _A_NORMAL | _A_HIDDEN | _A_SYSTEM, &find_tbuf);
 | |
| }
 | |
| 
 | |
| /* WATCOM's getenv is case-insensitive which wastes a lot of space
 | |
|    for our purposes. So here's a simple case-sensitive one */
 | |
| char *getenv(const char *name)
 | |
| {
 | |
|   char **envp, *ep;
 | |
|   const char *np;
 | |
|   char ec, nc;
 | |
| 
 | |
|   for (envp = environ; (ep = *envp) != NULL; envp++) {
 | |
|     np = name;
 | |
|     do {
 | |
|       ec = *ep++;
 | |
|       nc = *np++;
 | |
|       if (nc == 0) {
 | |
|         if (ec == '=')
 | |
|           return ep;
 | |
|         break;
 | |
|       }
 | |
|     } while (ec == nc);
 | |
|   }
 | |
|   return NULL;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| BYTE pgm[] = "SYS";
 | |
| 
 | |
| void put_boot(int, char *, char *, int, int);
 | |
| BOOL check_space(COUNT, ULONG);
 | |
| BOOL get_full_path(BYTE * srcPath, BYTE * rootPath, BYTE * filename, BYTE *source);
 | |
| BOOL copy(BYTE *source, COUNT drive, BYTE * filename);
 | |
| 
 | |
| #define SEC_SIZE        512
 | |
| #define COPY_SIZE	0x7e00
 | |
| 
 | |
| struct bootsectortype {
 | |
|   UBYTE bsJump[3];
 | |
|   char OemName[8];
 | |
|   UWORD bsBytesPerSec;
 | |
|   UBYTE bsSecPerClust;
 | |
|   UWORD bsResSectors;
 | |
|   UBYTE bsFATs;
 | |
|   UWORD bsRootDirEnts;
 | |
|   UWORD bsSectors;
 | |
|   UBYTE bsMedia;
 | |
|   UWORD bsFATsecs;
 | |
|   UWORD bsSecPerTrack;
 | |
|   UWORD bsHeads;
 | |
|   ULONG bsHiddenSecs;
 | |
|   ULONG bsHugeSectors;
 | |
|   UBYTE bsDriveNumber;
 | |
|   UBYTE bsReserved1;
 | |
|   UBYTE bsBootSignature;
 | |
|   ULONG bsVolumeID;
 | |
|   char bsVolumeLabel[11];
 | |
|   char bsFileSysType[8];
 | |
| };
 | |
| 
 | |
| struct bootsectortype32 {
 | |
|   UBYTE bsJump[3];
 | |
|   char OemName[8];
 | |
|   UWORD bsBytesPerSec;
 | |
|   UBYTE bsSecPerClust;
 | |
|   UWORD bsResSectors;
 | |
|   UBYTE bsFATs;
 | |
|   UWORD bsRootDirEnts;
 | |
|   UWORD bsSectors;
 | |
|   UBYTE bsMedia;
 | |
|   UWORD bsFATsecs;
 | |
|   UWORD bsSecPerTrack;
 | |
|   UWORD bsHeads;
 | |
|   ULONG bsHiddenSecs;
 | |
|   ULONG bsHugeSectors;
 | |
|   ULONG bsBigFatSize;
 | |
|   UBYTE bsFlags;
 | |
|   UBYTE bsMajorVersion;
 | |
|   UWORD bsMinorVersion;
 | |
|   ULONG bsRootCluster;
 | |
|   UWORD bsFSInfoSector;
 | |
|   UWORD bsBackupBoot;
 | |
|   ULONG bsReserved2[3];
 | |
|   UBYTE bsDriveNumber;
 | |
|   UBYTE bsReserved3;
 | |
|   UBYTE bsExtendedSignature;
 | |
|   ULONG bsSerialNumber;
 | |
|   char bsVolumeLabel[11];
 | |
|   char bsFileSystemID[8];
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * globals needed by put_boot & check_space
 | |
|  */
 | |
| enum {FAT12 = 12, FAT16 = 16, FAT32 = 32} fs;  /* file system type */
 | |
| /* static */ struct xfreespace x; /* we make this static to be 0 by default -
 | |
|                                      this avoids FAT misdetections */
 | |
| 
 | |
| #define SBOFFSET        11
 | |
| #define SBSIZE          (sizeof(struct bootsectortype) - SBOFFSET)
 | |
| #define SBSIZE32        (sizeof(struct bootsectortype32) - SBOFFSET)
 | |
| 
 | |
| /* essentially - verify alignment on byte boundaries at compile time  */
 | |
| struct VerifyBootSectorSize {
 | |
|   char failure1[sizeof(struct bootsectortype) == 62 ? 1 : -1];
 | |
|   char failure2[sizeof(struct bootsectortype) == 62 ? 1 : 0];
 | |
| /* (Watcom has a nice warning for this, by the way) */
 | |
| };
 | |
| 
 | |
| int FDKrnConfigMain(int argc, char **argv);
 | |
| 
 | |
| /* FreeDOS sys, we default to our kernel and load segment, but
 | |
|    if not found (or explicitly given) support OEM DOS variants
 | |
|    (such as DR-DOS or a FreeDOS kernel mimicing other DOSes).
 | |
|    PC-DOS requires particular steps from the boot loader, which
 | |
|    we do not currently fullfill, so it will not boot and MS-DOS
 | |
|    uses different names, so it also will not work.
 | |
| */
 | |
| #define FDKERNEL   "KERNEL.SYS"
 | |
| #define OEMKERNEL  "IBMBIO.COM"
 | |
| #define OEMDOS     "IBMDOS.COM"
 | |
| #define FDLOADSEG  0x60
 | |
| #define OEMLOADSEG 0x70
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
|   COUNT drive;                  /* destination drive */
 | |
|   COUNT drivearg = 0;           /* drive argument position */
 | |
|   COUNT srcarg = 0;             /* source argument position */
 | |
|   BYTE *bsFile = NULL;          /* user specified destination boot sector */
 | |
|   unsigned srcDrive;            /* source drive */
 | |
|   BYTE srcPath[SYS_MAXPATH];    /* user specified source drive and/or path */
 | |
|   BYTE rootPath[4];             /* alternate source path to try if not '\0' */
 | |
|   BYTE srcFile[SYS_MAXPATH];    /* full path+name of [kernel] file to copy */
 | |
|   WORD slen;
 | |
|   int argno = 0;
 | |
|   int bootonly = 0;
 | |
|   int both = 0;
 | |
|   char *kernel_name = FDKERNEL;
 | |
|   int load_segment = FDLOADSEG;
 | |
|   int altkern = 0;              /* use OEM kernel values instead of FD ones */
 | |
| 
 | |
|   printf("FreeDOS System Installer " SYS_VERSION ", " __DATE__ "\n\n");
 | |
| 
 | |
|   if (argc > 1 && memicmp(argv[1], "CONFIG", 6) == 0)
 | |
|   {
 | |
|     exit(FDKrnConfigMain(argc, argv));
 | |
|   }
 | |
| 
 | |
|   for(argno = 1; argno < argc; argno++)
 | |
|   {
 | |
|     char *argp = argv[argno];
 | |
| 
 | |
|     if (argp[1] == ':' && argp[2] == '\0' && drivearg <= srcarg)
 | |
|       drivearg = argno;
 | |
| 
 | |
|     if (srcarg == 0)
 | |
|     {
 | |
|       srcarg = argno;
 | |
|     }
 | |
|     else if (memicmp(argp, "BOTH", 4) == 0 && !both)
 | |
|     {
 | |
|       both = 1;
 | |
|     }
 | |
|     else if (argp[0] == '/')  /* optional switch */
 | |
|     {
 | |
|       argp++;  /* skip past the '/' character */
 | |
| 
 | |
|       if (memicmp(argp, "OEM", 3) == 0)
 | |
|       {
 | |
|         altkern = 1;  /* kernel is split into 2 files and update BS name */
 | |
|         kernel_name = OEMKERNEL;
 | |
|         load_segment = OEMLOADSEG;
 | |
|       }
 | |
|       else if (memicmp(argp, "BOOTONLY", 8) == 0)
 | |
|       {
 | |
|         bootonly = 1;
 | |
|       }
 | |
|       else if (argno + 1 < argc)   /* two part options, /SWITCH VALUE */
 | |
|       {
 | |
|         argno++;
 | |
|         if (toupper(*argp) == 'K')      /* set Kernel name */
 | |
|         {
 | |
|           kernel_name = argv[argno];
 | |
|         }
 | |
|         else if (toupper(*argp) == 'L') /* set Load segment */
 | |
|         {
 | |
|           load_segment = (int)strtol(argv[argno], NULL, 16);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else if (drivearg != argno)
 | |
|     {
 | |
|       if (bsFile == NULL)
 | |
|       {
 | |
|         bsFile = argp;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         drivearg = 0;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   } /* for() */
 | |
| 
 | |
|   if (drivearg == 0)
 | |
|   {
 | |
|     printf(
 | |
|       "Usage: \n"
 | |
|       "%s [source] drive: [bootsect [BOTH]] [/BOOTONLY] [/OEM] [/K name] [/L segm]\n"
 | |
|       "  source   = A:,B:,C:\\KERNEL\\BIN\\,etc., or current directory if not given\n"
 | |
|       "  drive    = A,B,etc.\n"
 | |
|       "  bootsect = name of 512-byte boot sector file image for drive:\n"
 | |
|       "             to write to *instead* of real boot sector\n"
 | |
|       "  BOTH     : write to *both* the real boot sector and the image file\n"
 | |
|       "  /BOOTONLY: do *not* copy kernel / shell, only update boot sector or image\n"
 | |
|       "  /OEM     : indicates kernel is IBMIO.SYS/IBMDOS.SYS loaded at 0x70\n"
 | |
|       "  /K name  : name of kernel to use instead of KERNEL.SYS\n"
 | |
|       "  /L segm  : hex load segment to use instead of 60\n"
 | |
|       "%s CONFIG /help\n", pgm, pgm);
 | |
|     exit(1);
 | |
|   }
 | |
|   drive = toupper(argv[drivearg][0]) - 'A';
 | |
| 
 | |
|   if (drive < 0 || drive >= 26)
 | |
|   {
 | |
|     printf("%s: drive %c must be A:..Z:\n", pgm,
 | |
|            *argv[(argc == 3 ? 2 : 1)]);
 | |
|     exit(1);
 | |
|   }
 | |
| 
 | |
|   srcPath[0] = '\0';
 | |
|   if (drivearg > srcarg && srcarg)
 | |
|   {
 | |
|     strncpy(srcPath, argv[srcarg], SYS_MAXPATH - 12);
 | |
|     /* leave room for COMMAND.COM\0 */
 | |
|     srcPath[SYS_MAXPATH - 13] = '\0';
 | |
|     /* make sure srcPath + "file" is a valid path */
 | |
|     slen = strlen(srcPath);
 | |
|     if ((srcPath[slen - 1] != ':') &&
 | |
|         ((srcPath[slen - 1] != '\\') || (srcPath[slen - 1] != '/')))
 | |
|     {
 | |
|       srcPath[slen] = '\\';
 | |
|       slen++;
 | |
|       srcPath[slen] = '\0';
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Get source drive */
 | |
|   if ((strlen(srcPath) > 1) && (srcPath[1] == ':'))     /* src specifies drive */
 | |
|     srcDrive = toupper(*srcPath) - 'A';
 | |
|   else                          /* src doesn't specify drive, so assume current drive */
 | |
|   {
 | |
| #ifdef __TURBOC__
 | |
|     srcDrive = (unsigned) getdisk();
 | |
| #else
 | |
|     _dos_getdrive(&srcDrive);
 | |
|     srcDrive--;
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   /* Don't try root if src==dst drive or source path given */
 | |
|   if ((drive == srcDrive)
 | |
|       || (*srcPath
 | |
|           && ((srcPath[1] != ':') || ((srcPath[1] == ':') && srcPath[2]))))
 | |
|     *rootPath = '\0';
 | |
|   else
 | |
|     sprintf(rootPath, "%c:\\", 'A' + srcDrive);
 | |
| 
 | |
| 
 | |
|   /* unless we are only setting boot sector, verify kernel file exists */
 | |
|   if (!bootonly)
 | |
|   {
 | |
|     /* if FDKERNEL not found and an explicit kernel name was not specified,
 | |
|        then see if OEM kernel available and switch to it instead. */
 | |
|     if (!get_full_path(srcPath, rootPath, kernel_name, srcFile) && (kernel_name == FDKERNEL))
 | |
|     {
 | |
|       if (!get_full_path(srcPath, rootPath, OEMKERNEL, srcFile))
 | |
|       {
 | |
|         printf("\n%s: failed to find kernel file %s\n", pgm, kernel_name);
 | |
|         exit(1);
 | |
|       }
 | |
|       else /* else OEM kernel found, so switch modes */
 | |
|       {
 | |
|         altkern = 1;
 | |
|         kernel_name = OEMKERNEL;
 | |
|         load_segment = OEMLOADSEG;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   printf("Processing boot sector...\n");
 | |
|   put_boot(drive, bsFile, kernel_name, load_segment, both);
 | |
| 
 | |
|   if (!bootonly)
 | |
|   {
 | |
|     if (!copy(srcFile, drive, kernel_name))
 | |
|     {
 | |
|       printf("\n%s: cannot copy \"%s\"\n", pgm, kernel_name);
 | |
|       exit(1);
 | |
|     } /* copy kernel */
 | |
| 
 | |
|     if (altkern)
 | |
|     {
 | |
|       if ( (!get_full_path(srcPath, rootPath, OEMDOS, srcFile)) ||
 | |
|            (!copy(srcFile, drive, OEMDOS)) )
 | |
|       {
 | |
|         printf("\n%s: cannot copy \"%s\"\n", pgm, OEMDOS);
 | |
|         exit(1);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /* copy command.com, 1st try source path, then try %COMSPEC% */
 | |
|     if (!get_full_path(srcPath, rootPath, "COMMAND.COM", srcFile))
 | |
|     {
 | |
|       char *comspec = getenv("COMSPEC");
 | |
|       if ( (comspec == NULL) ||
 | |
|            (!get_full_path(comspec, NULL, "COMMAND.COM", srcFile)) )
 | |
|       {
 | |
|           printf("\n%s: failed to find command interpreter (shell) file %s\n", pgm, "COMMAND.COM");
 | |
|           exit(1);
 | |
|       }
 | |
|       printf("%s: Using shell from %COMSPEC%  \"%s\"\n", pgm, comspec);
 | |
|     }
 | |
|     if (!copy(srcFile, drive, "COMMAND.COM"))
 | |
|     {
 | |
|       printf("\n%s: cannot copy \"%s\"\n", pgm, "COMMAND.COM");
 | |
|       exit(1);
 | |
|     } /* copy shell */
 | |
|   }
 | |
| 
 | |
|   printf("\nSystem transferred.\n");
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| #ifdef DDEBUG
 | |
| VOID dump_sector(unsigned char far * sec)
 | |
| {
 | |
|   COUNT x, y;
 | |
|   char c;
 | |
| 
 | |
|   for (x = 0; x < 32; x++)
 | |
|   {
 | |
|     printf("%03X  ", x * 16);
 | |
|     for (y = 0; y < 16; y++)
 | |
|     {
 | |
|       printf("%02X ", sec[x * 16 + y]);
 | |
|     }
 | |
|     for (y = 0; y < 16; y++)
 | |
|     {
 | |
|       c = sec[x * 16 + y];
 | |
|       if (isprint(c))
 | |
|         printf("%c", c);
 | |
|       else
 | |
|         printf(".");
 | |
|     }
 | |
|     printf("\n");
 | |
|   }
 | |
| 
 | |
|   printf("\n");
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #ifdef __WATCOMC__
 | |
| 
 | |
| int absread(int DosDrive, int nsects, int foo, void *diskReadPacket);
 | |
| #pragma aux absread =  \
 | |
|       "push bp"           \
 | |
|       "int 0x25"          \
 | |
|       "sbb ax, ax"        \
 | |
|       "popf"              \
 | |
|       "pop bp"            \
 | |
|       parm [ax] [cx] [dx] [bx] \
 | |
|       modify [si di] \
 | |
|       value [ax];
 | |
| 
 | |
| int abswrite(int DosDrive, int nsects, int foo, void *diskReadPacket);
 | |
| #pragma aux abswrite =  \
 | |
|       "push bp"           \
 | |
|       "int 0x26"          \
 | |
|       "sbb ax, ax"        \
 | |
|       "popf"              \
 | |
|       "pop bp"            \
 | |
|       parm [ax] [cx] [dx] [bx] \
 | |
|       modify [si di] \
 | |
|       value [ax];
 | |
| 
 | |
| int fat32readwrite(int DosDrive, void *diskReadPacket, unsigned intno);
 | |
| #pragma aux fat32readwrite =  \
 | |
|       "mov ax, 0x7305"    \
 | |
|       "mov cx, 0xffff"    \
 | |
|       "int 0x21"          \
 | |
|       "sbb ax, ax"        \
 | |
|       parm [dx] [bx] [si] \
 | |
|       modify [cx dx si]   \
 | |
|       value [ax];
 | |
| 
 | |
| void reset_drive(int DosDrive);
 | |
| #pragma aux reset_drive = \
 | |
|       "push ds" \
 | |
|       "inc dx" \
 | |
|       "mov ah, 0xd" \ 
 | |
|       "int 0x21" \
 | |
|       "mov ah,0x32" \
 | |
|       "int 0x21" \
 | |
|       "pop ds" \
 | |
|       parm [dx] \
 | |
|       modify [ax bx];
 | |
| 
 | |
| void truename(char far *dest, const char *src);
 | |
| #pragma aux truename = \
 | |
|       "mov ah,0x60"	  \
 | |
|       "int 0x21"          \
 | |
|       parm [es di] [si];
 | |
| 
 | |
| int generic_block_ioctl(unsigned drive, unsigned cx, unsigned char *par);
 | |
| #pragma aux generic_block_ioctl = \
 | |
|       "mov ax, 0x440d" \
 | |
|       "int 0x21" \
 | |
|       "sbb ax, ax" \
 | |
|       value [ax] \
 | |
|       parm [bx] [cx] [dx]; /* BH must be 0 for lock! */
 | |
| 
 | |
| #else
 | |
| 
 | |
| #ifndef __TURBOC__
 | |
| 
 | |
| int2526readwrite(int DosDrive, void *diskReadPacket, unsigned intno)
 | |
| {
 | |
|   union REGS regs;
 | |
| 
 | |
|   regs.h.al = (BYTE) DosDrive;
 | |
|   regs.x.bx = (short)diskReadPacket;
 | |
|   regs.x.cx = 0xffff;
 | |
| 
 | |
|   int86(intno, ®s, ®s);
 | |
| 
 | |
|   return regs.x.cflag;
 | |
| }
 | |
| 
 | |
| #define absread(DosDrive, foo, cx, diskReadPacket) \
 | |
| int2526readwrite(DosDrive, diskReadPacket, 0x25)
 | |
| 
 | |
| #define abswrite(DosDrive, foo, cx, diskReadPacket) \
 | |
| int2526readwrite(DosDrive, diskReadPacket, 0x26)
 | |
| 
 | |
| #endif
 | |
| 
 | |
| int fat32readwrite(int DosDrive, void *diskReadPacket, unsigned intno)
 | |
| {
 | |
|   union REGS regs;
 | |
| 
 | |
|   regs.x.ax = 0x7305;
 | |
|   regs.h.dl = DosDrive;
 | |
|   regs.x.bx = (short)diskReadPacket;
 | |
|   regs.x.cx = 0xffff;
 | |
|   regs.x.si = intno;
 | |
|   intdos(®s, ®s);
 | |
|   
 | |
|   return regs.x.cflag;
 | |
| } /* fat32readwrite */
 | |
| 
 | |
| void reset_drive(int DosDrive)
 | |
| {
 | |
|   union REGS regs;
 | |
| 
 | |
|   regs.h.ah = 0xd;
 | |
|   intdos(®s, ®s);
 | |
|   regs.h.ah = 0x32;
 | |
|   regs.h.dl = DosDrive + 1;
 | |
|   intdos(®s, ®s);
 | |
| } /* reset_drive */
 | |
| 
 | |
| int generic_block_ioctl(unsigned drive, unsigned cx, unsigned char *par)
 | |
| {
 | |
|   union REGS regs;
 | |
| 
 | |
|   regs.x.ax = 0x440d;
 | |
|   regs.x.cx = cx;
 | |
|   regs.x.dx = (unsigned)par;
 | |
|   regs.x.bx = drive; /* BH must be 0 for lock! */
 | |
|   intdos(®s, ®s);
 | |
|   return regs.x.cflag;
 | |
| } /* generic_block_ioctl */
 | |
| 
 | |
| void truename(char *dest, const char *src)
 | |
| {
 | |
|   union REGS regs;
 | |
|   struct SREGS sregs;
 | |
| 
 | |
|   regs.h.ah = 0x60;
 | |
|   sregs.es = FP_SEG(dest);
 | |
|   regs.x.di = FP_OFF(dest);
 | |
|   sregs.ds = FP_SEG(src);
 | |
|   regs.x.si = FP_OFF(src);
 | |
|   intdosx(®s, ®s, &sregs);
 | |
| } /* truename */
 | |
| 
 | |
| #endif
 | |
| 
 | |
| int MyAbsReadWrite(int DosDrive, int count, ULONG sector, void *buffer,
 | |
|                    int write)
 | |
| {
 | |
|   struct {
 | |
|     unsigned long sectorNumber;
 | |
|     unsigned short count;
 | |
|     void far *address;
 | |
|   } diskReadPacket;
 | |
| 
 | |
|   diskReadPacket.sectorNumber = sector;
 | |
|   diskReadPacket.count = count;
 | |
|   diskReadPacket.address = buffer;
 | |
| 
 | |
|   if ((!write && absread(DosDrive, -1, -1, &diskReadPacket) == -1)
 | |
|       || (write && abswrite(DosDrive, -1, -1, &diskReadPacket) == -1))
 | |
|   {
 | |
| #ifdef WITHFAT32
 | |
|     return fat32readwrite(DosDrive + 1, &diskReadPacket, write);
 | |
| #else
 | |
|     return 0xff;
 | |
| #endif
 | |
|   }
 | |
|   return 0;
 | |
| } /* MyAbsReadWrite */
 | |
| 
 | |
| #ifdef __WATCOMC__
 | |
| 
 | |
| unsigned getextdrivespace(void far *drivename, void *buf, unsigned buf_size);
 | |
| #pragma aux getextdrivespace =  \
 | |
|       "mov ax, 0x7303"    \
 | |
|       "stc"		  \
 | |
|       "int 0x21"          \
 | |
|       "sbb ax, ax"        \
 | |
|       parm [es dx] [di] [cx] \
 | |
|       value [ax];
 | |
| 
 | |
| #else /* !defined __WATCOMC__ */
 | |
| 
 | |
| unsigned getextdrivespace(void *drivename, void *buf, unsigned buf_size)
 | |
| {
 | |
|   union REGS regs;
 | |
|   struct SREGS sregs;
 | |
| 
 | |
|   regs.x.ax = 0x7303;         /* get extended drive free space */
 | |
| 
 | |
|   sregs.es = FP_SEG(buf);
 | |
|   regs.x.di = FP_OFF(buf);
 | |
|   sregs.ds = FP_SEG(drivename);
 | |
|   regs.x.dx = FP_OFF(drivename);
 | |
| 
 | |
|   regs.x.cx = buf_size;
 | |
| 
 | |
|   intdosx(®s, ®s, &sregs);
 | |
|   return regs.x.ax == 0x7300 || regs.x.cflag;
 | |
| } /* getextdrivespace */
 | |
| 
 | |
| #endif /* defined __WATCOMC__ */
 | |
| 
 | |
| #ifdef __WATCOMC__
 | |
| /*
 | |
|  * If BIOS has got LBA extensions, after the Int 13h call BX will be 0xAA55.
 | |
|  * If extended disk access functions are supported, bit 0 of CX will be set.
 | |
|  */
 | |
| BOOL haveLBA(void);     /* return TRUE if we have LBA BIOS, FALSE otherwise */
 | |
| #pragma aux haveLBA =  \
 | |
|       "mov ax, 0x4100"  /* IBM/MS Int 13h Extensions - installation check */ \
 | |
|       "mov bx, 0x55AA" \
 | |
|       "mov dl, 0x80"   \
 | |
|       "int 0x13"       \
 | |
|       "xor ax, ax"     \
 | |
|       "cmp bx, 0xAA55" \
 | |
|       "jne quit"       \
 | |
|       "and cx, 1"      \
 | |
|       "xchg cx, ax"    \
 | |
| "quit:"                \
 | |
|       modify [bx cx]   \
 | |
|       value [ax];
 | |
| #else
 | |
| 
 | |
| BOOL haveLBA(void)
 | |
| {
 | |
|   union REGS r;
 | |
|   r.x.ax = 0x4100;
 | |
|   r.x.bx = 0x55AA;
 | |
|   r.h.dl = 0x80;
 | |
|   int86(0x13, &r, &r);
 | |
|   return r.x.bx == 0xAA55 && r.x.cx & 1;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void correct_bpb(struct bootsectortype *default_bpb,
 | |
|                  struct bootsectortype *oldboot)
 | |
| {
 | |
|   /* don't touch partitions (floppies most likely) that don't have hidden
 | |
|      sectors */
 | |
|   if (default_bpb->bsHiddenSecs == 0)
 | |
|     return;
 | |
| #ifdef DEBUG
 | |
|   printf("Old boot sector values: sectors/track: %u, heads: %u, hidden: %lu\n",
 | |
|          oldboot->bsSecPerTrack, oldboot->bsHeads, oldboot->bsHiddenSecs);
 | |
|   printf("Default and new boot sector values: sectors/track: %u, heads: %u, "
 | |
|          "hidden: %lu\n", default_bpb->bsSecPerTrack, default_bpb->bsHeads,
 | |
|          default_bpb->bsHiddenSecs);
 | |
| #endif
 | |
| 
 | |
|   oldboot->bsSecPerTrack = default_bpb->bsSecPerTrack;
 | |
|   oldboot->bsHeads = default_bpb->bsHeads;
 | |
|   oldboot->bsHiddenSecs = default_bpb->bsHiddenSecs;
 | |
| }
 | |
| 
 | |
| void put_boot(int drive, char *bsFile, char *kernel_name, int load_seg, int both)
 | |
| {
 | |
| #ifdef WITHFAT32
 | |
|   struct bootsectortype32 *bs32;
 | |
| #endif
 | |
|   struct bootsectortype *bs;
 | |
|   static unsigned char oldboot[SEC_SIZE], newboot[SEC_SIZE];
 | |
|   static unsigned char default_bpb[0x5c];
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   printf("Reading old bootsector from drive %c:\n", drive + 'A');
 | |
| #endif
 | |
| 
 | |
|   /* lock drive */
 | |
|   generic_block_ioctl(drive + 1, 0x84a, NULL);
 | |
| 
 | |
|   reset_drive(drive);
 | |
|   /* suggestion: allow reading from a boot sector or image file here */
 | |
|   if (MyAbsReadWrite(drive, 1, 0, oldboot, 0) != 0)
 | |
|   {
 | |
|     printf("can't read old boot sector for drive %c:\n", drive + 'A');
 | |
|     exit(1);
 | |
|   }
 | |
| 
 | |
| #ifdef DDEBUG
 | |
|   printf("Old Boot Sector:\n");
 | |
|   dump_sector(oldboot);
 | |
| #endif
 | |
| 
 | |
|   bs = (struct bootsectortype *)&oldboot;
 | |
| 
 | |
|   if (bs->bsBytesPerSec != SEC_SIZE)
 | |
|   {
 | |
|     printf("Sector size is not 512 but %d bytes - not currently supported!\n",
 | |
|       bs->bsBytesPerSec);
 | |
|     exit(1); /* Japan?! */
 | |
|   }
 | |
| 
 | |
|   {
 | |
|    /* see "FAT: General Overview of On-Disk Format" v1.02, 5.V.1999
 | |
|     * (http://www.nondot.org/sabre/os/files/FileSystems/FatFormat.pdf)
 | |
|     */
 | |
|     ULONG fatSize, totalSectors, dataSectors, clusters;
 | |
|     UCOUNT rootDirSectors;
 | |
| 
 | |
|     bs32 = (struct bootsectortype32 *)&oldboot;
 | |
|     rootDirSectors = (bs->bsRootDirEnts * DIRENT_SIZE  /* 32 */
 | |
|                  + bs32->bsBytesPerSec - 1) / bs32->bsBytesPerSec;
 | |
|     fatSize      = bs32->bsFATsecs ? bs32->bsFATsecs : bs32->bsBigFatSize;
 | |
|     totalSectors = bs32->bsSectors ? bs32->bsSectors : bs32->bsHugeSectors;
 | |
|     dataSectors = totalSectors
 | |
|       - bs32->bsResSectors - (bs32->bsFATs * fatSize) - rootDirSectors;
 | |
|     clusters = dataSectors / bs32->bsSecPerClust;
 | |
|  
 | |
|     if (clusters < FAT_MAGIC)        /* < 4085 */
 | |
|       fs = FAT12;
 | |
|     else if (clusters < FAT_MAGIC16) /* < 65525 */
 | |
|       fs = FAT16;
 | |
|     else
 | |
|       fs = FAT32;
 | |
|   }
 | |
| 
 | |
|   /* bit 0 set if function to use current BPB, clear if Device
 | |
|            BIOS Parameter Block field contains new default BPB
 | |
|      bit 1 set if function to use track layout fields only
 | |
|            must be clear if CL=60h
 | |
|      bit 2 set if all sectors in track same size (should be set) (RBIL) */
 | |
|   default_bpb[0] = 4;
 | |
| 
 | |
|   if (fs == FAT32)
 | |
|   {
 | |
|     printf("FAT type: FAT32\n");
 | |
|     /* get default bpb (but not for floppies) */
 | |
|     if (drive >= 2 &&
 | |
|         generic_block_ioctl(drive + 1, 0x4860, default_bpb) == 0)
 | |
|       correct_bpb((struct bootsectortype *)(default_bpb + 7 - 11), bs);
 | |
| 
 | |
| #ifdef WITHFAT32                /* copy one of the FAT32 boot sectors */
 | |
|     memcpy(newboot, haveLBA() ? fat32lba : fat32chs, SEC_SIZE);
 | |
| #else
 | |
|     printf("SYS hasn't been compiled with FAT32 support.\n"
 | |
|            "Consider using -DWITHFAT32 option.\n");
 | |
|     exit(1);
 | |
| #endif
 | |
|   }
 | |
|   else
 | |
|   { /* copy the FAT12/16 CHS+LBA boot sector */
 | |
|     printf("FAT type: FAT1%c\n", fs + '0' - 10);
 | |
|     if (drive >= 2 &&
 | |
|         generic_block_ioctl(drive + 1, 0x860, default_bpb) == 0)
 | |
|       correct_bpb((struct bootsectortype *)(default_bpb + 7 - 11), bs);
 | |
|     memcpy(newboot, fs == FAT16 ? fat16com : fat12com, SEC_SIZE);
 | |
|   }
 | |
| 
 | |
|   /* Copy disk parameter from old sector to new sector */
 | |
| #ifdef WITHFAT32
 | |
|   if (fs == FAT32)
 | |
|     memcpy(&newboot[SBOFFSET], &oldboot[SBOFFSET], SBSIZE32);
 | |
|   else
 | |
| #endif
 | |
|     memcpy(&newboot[SBOFFSET], &oldboot[SBOFFSET], SBSIZE);
 | |
| 
 | |
|   bs = (struct bootsectortype *)&newboot;
 | |
| 
 | |
|   /* originally OemName was "FreeDOS", changed for better compatibility */
 | |
|   memcpy(bs->OemName, "FRDOS4.1", 8);
 | |
| 
 | |
| #ifdef WITHFAT32
 | |
|   if (fs == FAT32)
 | |
|   {
 | |
|     bs32 = (struct bootsectortype32 *)&newboot;
 | |
|     /* put 0 for A: or B: (force booting from A:), otherwise use DL */
 | |
|     bs32->bsDriveNumber = drive < 2 ? 0 : 0xff;
 | |
|     /* the location of the "0060" segment portion of the far pointer
 | |
|        in the boot sector is just before cont: in boot*.asm.
 | |
|        This happens to be offset 0x78 (=0x3c * 2) for FAT32 and
 | |
|        offset 0x5c (=0x2e * 2) for FAT16 */
 | |
|     /* i.e. BE CAREFUL WHEN YOU CHANGE THE BOOT SECTORS !!! */
 | |
|     ((int *)newboot)[0x3C] = load_seg;
 | |
| #ifdef DEBUG
 | |
|     printf(" FAT starts at sector %lx + %x\n",
 | |
|            bs32->bsHiddenSecs, bs32->bsResSectors);
 | |
| #endif
 | |
|   }
 | |
|   else
 | |
| #endif
 | |
|   {
 | |
|     /* put 0 for A: or B: (force booting from A:), otherwise use DL */
 | |
|     bs->bsDriveNumber = drive < 2 ? 0 : 0xff;
 | |
|     ((int *)newboot)[0x2E] = load_seg;
 | |
|   }
 | |
| 
 | |
| #ifdef DEBUG /* add an option to display this on user request? */
 | |
|   printf("Root dir entries = %u\n", bs->bsRootDirEnts);
 | |
| 
 | |
|   printf("FAT starts at sector (%lu + %u)\n",
 | |
|          bs->bsHiddenSecs, bs->bsResSectors);
 | |
|   printf("Root directory starts at sector (PREVIOUS + %u * %u)\n",
 | |
|          bs->bsFATsecs, bs->bsFATs);
 | |
| #endif
 | |
|   {
 | |
|     int i = 0;
 | |
|     memset(&newboot[0x1f1], ' ', 11);
 | |
|     while (kernel_name[i] && kernel_name[i] != '.')
 | |
|     {
 | |
|       if (i < 8)
 | |
|         newboot[0x1f1+i] = toupper(kernel_name[i]);
 | |
|       i++;
 | |
|     }
 | |
|     if (kernel_name[i] == '.')
 | |
|     {
 | |
|       /* copy extension */
 | |
|       int j = 0;
 | |
|       i++;
 | |
|       while (kernel_name[i+j] && j < 3)
 | |
|       {
 | |
|         newboot[0x1f9+j] = toupper(kernel_name[i+j]);
 | |
|         j++;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   /* there's a zero past the kernel name in all boot sectors */
 | |
|   printf("Boot sector kernel name set to %s\n", &newboot[0x1f1]);
 | |
|   printf("Boot sector load segment set to %Xh\n", load_seg);
 | |
| #endif
 | |
| 
 | |
| #ifdef DDEBUG
 | |
|   printf("\nNew Boot Sector:\n");
 | |
|   dump_sector(newboot);
 | |
| #endif
 | |
| 
 | |
|   if ((bsFile == NULL) || both)
 | |
|   {
 | |
| 
 | |
| #ifdef DEBUG
 | |
|     printf("writing new bootsector to drive %c:\n", drive + 'A');
 | |
| #endif
 | |
| 
 | |
|     /* write newboot to a drive */
 | |
|     if (MyAbsReadWrite(drive, 1, 0, newboot, 1) != 0)
 | |
|     {
 | |
|       printf("Can't write new boot sector to drive %c:\n", drive + 'A');
 | |
|       exit(1);
 | |
|     }
 | |
|   } /* if write boot sector */
 | |
| 
 | |
|   if (bsFile != NULL)
 | |
|   {
 | |
|     int fd;
 | |
| 
 | |
| #ifdef DEBUG
 | |
|     printf("writing new bootsector to file %s\n", bsFile);
 | |
| #endif
 | |
| 
 | |
|     /* write newboot to bsFile */
 | |
|     if ((fd = /* suggestion: do not trunc - allows to write to images */
 | |
|          open(bsFile, O_RDWR | O_TRUNC | O_CREAT | O_BINARY,
 | |
|               S_IREAD | S_IWRITE)) < 0)
 | |
|     {
 | |
|       printf(" %s: can't create\"%s\"\nDOS errnum %d", pgm, bsFile, errno);
 | |
|       exit(1);
 | |
|     }
 | |
|     if (write(fd, newboot, SEC_SIZE) != SEC_SIZE)
 | |
|     {
 | |
|       printf("Can't write %u bytes to %s\n", SEC_SIZE, bsFile);
 | |
|       close(fd);
 | |
|       unlink(bsFile);
 | |
|       exit(1);
 | |
|     }
 | |
|     close(fd);
 | |
|   } /* if write boot sector file */
 | |
|   reset_drive(drive);
 | |
| 
 | |
|   /* unlock_drive */
 | |
|   generic_block_ioctl(drive + 1, 0x86a, NULL);
 | |
| } /* put_boot */
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Returns TRUE if `drive` has at least `bytes` free space, FALSE otherwise.
 | |
|  * put_sector() must have been already called to determine file system type.
 | |
|  */
 | |
| BOOL check_space(COUNT drive, ULONG bytes)
 | |
| {
 | |
| #ifdef WITHFAT32
 | |
|   if (fs == FAT32)
 | |
|   {
 | |
|     char *drivename = "A:\\";
 | |
|     drivename[0] = 'A' + drive;
 | |
|     getextdrivespace(drivename, &x, sizeof(x));
 | |
|     return x.xfs_freeclusters > (bytes / (x.xfs_clussize * x.xfs_secsize));
 | |
|   }
 | |
|   else
 | |
| #endif
 | |
|   {
 | |
| #ifdef __TURBOC__
 | |
|     struct dfree df;
 | |
|     getdfree(drive + 1, &df);
 | |
|     return (ULONG)df.df_avail * df.df_sclus * df.df_bsec >= bytes;
 | |
| #else
 | |
|     struct _diskfree_t df;
 | |
|     _dos_getdiskfree(drive + 1, &df);
 | |
|     return (ULONG)df.avail_clusters * df.sectors_per_cluster
 | |
|       * df.bytes_per_sector >= bytes;
 | |
| #endif
 | |
|   }
 | |
| } /* check_space */
 | |
| 
 | |
| 
 | |
| /* returns TRUE if file exists, FALSE otherwise
 | |
|    1st checks for srcPath + filename, if that does not exist then
 | |
|    will check for rootPath + filename
 | |
|    On failure, source is undefined. source must be >= SYS_MAXPATH bytes
 | |
|  */
 | |
| BOOL get_full_path(BYTE * srcPath, BYTE * rootPath, BYTE * filename, BYTE *source)
 | |
| {
 | |
|   struct stat fstatbuf;
 | |
| 
 | |
|   strcpy(source, srcPath);
 | |
|   if (rootPath != NULL) /* trick for comspec, append filename if srcPath doesn't include it */
 | |
|     strcat(source, filename);
 | |
| 
 | |
|   /* check if file exists in source directory */
 | |
|   if (stat(source, &fstatbuf))
 | |
|   {
 | |
|     /* not found in source path, so try root of source drive */
 | |
|     if ((rootPath != NULL) && (*rootPath) /* && (errno == ENOENT) */ )
 | |
|     {
 | |
|       sprintf(source, "%s%s", rootPath, filename);
 | |
|       if (stat(source, &fstatbuf))
 | |
|         return FALSE;
 | |
|     }
 | |
|     else
 | |
|       return FALSE;
 | |
|   }
 | |
|   return TRUE;
 | |
| } /* get_full_path */
 | |
| 
 | |
| BYTE copybuffer[COPY_SIZE];
 | |
| 
 | |
| /* copies file (path+filename specified by srcFile) to drive:\filename */
 | |
| BOOL copy(BYTE *source, COUNT drive, BYTE * filename)
 | |
| {
 | |
|   static BYTE dest[SYS_MAXPATH];
 | |
|   unsigned ret;
 | |
|   int fdin, fdout;
 | |
|   ULONG copied = 0;
 | |
| 
 | |
|   printf("\nCopying %s...\n", source);
 | |
| 
 | |
|   truename(dest, source);
 | |
|   strcpy(source, dest);
 | |
|   sprintf(dest, "%c:\\%s", 'A' + drive, filename);
 | |
|   if (stricmp(source, dest) == 0)
 | |
|   {
 | |
|     printf("%s: source and destination are identical: skipping \"%s\"\n",
 | |
|            pgm, source);
 | |
|     return TRUE;
 | |
|   }
 | |
| 
 | |
|   if ((fdin = open(source, O_RDONLY | O_BINARY)) < 0)
 | |
|   {
 | |
|     printf("%s: failed to open \"%s\"\n", pgm, source);
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   if (!check_space(drive, filelength(fdin)))
 | |
|   {
 | |
|     printf("%s: Not enough space to transfer %s\n", pgm, filename);
 | |
|     close(fdin);
 | |
|     exit(1);
 | |
|   }
 | |
| 
 | |
|   if ((fdout =
 | |
|        open(dest, O_RDWR | O_TRUNC | O_CREAT | O_BINARY,
 | |
|             S_IREAD | S_IWRITE)) < 0)
 | |
|   {
 | |
|     printf(" %s: can't create\"%s\"\nDOS errnum %d", pgm, dest, errno);
 | |
|     close(fdin);
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   while ((ret = read(fdin, copybuffer, COPY_SIZE)) > 0)
 | |
|   {
 | |
|     if (write(fdout, copybuffer, ret) != ret)
 | |
|     {
 | |
|       printf("Can't write %u bytes to %s\n", ret, dest);
 | |
|       close(fdout);
 | |
|       unlink(dest);
 | |
|       break;
 | |
|     }
 | |
|     copied += ret;
 | |
|   }
 | |
| 
 | |
|   {
 | |
| #if defined __WATCOMC__ || defined _MSC_VER /* || defined __BORLANDC__ */
 | |
|     unsigned short date, time;	  
 | |
|     _dos_getftime(fdin, &date, &time);
 | |
|     _dos_setftime(fdout, date, time);
 | |
| #elif defined __TURBOC__
 | |
|     struct ftime ftime;
 | |
|     getftime(fdin, &ftime);
 | |
|     setftime(fdout, &ftime);
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   close(fdin);
 | |
|   close(fdout);
 | |
| 
 | |
| #ifdef __SOME_OTHER_COMPILER__
 | |
|   {
 | |
| #include <utime.h>
 | |
|     struct utimbuf utimb;
 | |
| 
 | |
|     utimb.actime =              /* access time */
 | |
|         utimb.modtime = fstatbuf.st_mtime;      /* modification time */
 | |
|     utime(dest, &utimb);
 | |
|   };
 | |
| #endif
 | |
| 
 | |
|   printf("%lu Bytes transferred", copied);
 | |
| 
 | |
|   return TRUE;
 | |
| } /* copy */
 | |
| 
 |