move gbp.conf into debian/
[debian/cpmtools] / device_win32.c
1 /* #includes */ /*{{{C}}}*//*{{{*/
2 #include "config.h"
3
4 #include <assert.h>
5 #include <errno.h>
6 #include <ctype.h>
7
8 #include "cpmdir.h"
9 #include "cpmfs.h"
10
11 #ifdef USE_DMALLOC
12 #include <dmalloc.h>
13 #endif
14 /*}}}*/
15 /* types */ /*{{{*/
16 #define PHYSICAL_SECTOR_1       1 /* First physical sector */
17
18 /* Use the INT13 interface rather than INT25/INT26. This appears to
19  * improve performance, but is less well tested. */
20 #define USE_INT13
21
22 /* Windows 95 disk I/O functions - based on Stan Mitchell's DISKDUMP.C */
23 #define VWIN32_DIOC_DOS_IOCTL   1   // DOS ioctl calls 4400h-4411h
24 #define VWIN32_DIOC_DOS_INT25   2   // absolute disk read, DOS int 25h
25 #define VWIN32_DIOC_DOS_INT26   3   // absolute disk write, DOS int 26h
26 #define VWIN32_DIOC_DOS_INT13   4   // BIOS INT13 functions
27
28 typedef struct _DIOC_REGISTERS {
29     DWORD reg_EBX;
30     DWORD reg_EDX;
31     DWORD reg_ECX;
32     DWORD reg_EAX;
33     DWORD reg_EDI;
34     DWORD reg_ESI;
35     DWORD reg_Flags;
36     }
37     DIOC_REGISTERS, *PDIOC_REGISTERS;
38
39 #define   LEVEL0_LOCK   0
40 #define   LEVEL1_LOCK   1
41 #define   LEVEL2_LOCK   2
42 #define   LEVEL3_LOCK   3
43 #define   LEVEL1_LOCK_MAX_PERMISSION      0x0001
44
45 #define   DRIVE_IS_REMOTE                 0x1000
46 #define   DRIVE_IS_SUBST                  0x8000
47
48 /*********************************************************
49  **** Note: all MS-DOS data structures must be packed ****
50  ****       on a one-byte boundary.                   ****
51  *********************************************************/
52 #pragma pack(1)
53
54 typedef struct _DISKIO {
55     DWORD diStartSector;    // sector number to start at
56     WORD  diSectors;        // number of sectors
57     DWORD diBuffer;         // address of buffer
58     }
59     DISKIO, *PDISKIO;
60
61 typedef struct MID {
62     WORD  midInfoLevel;       // information level, must be 0
63     DWORD midSerialNum;       // serial number for the medium
64     char  midVolLabel[11];    // volume label for the medium
65     char  midFileSysType[8];  // type of file system as 8-byte ASCII
66     }
67     MID, *PMID;
68
69 typedef struct driveparams {    /* Disk geometry */
70     BYTE special;
71     BYTE devicetype;
72     WORD deviceattrs;
73     WORD cylinders;
74     BYTE mediatype;
75     /* BPB starts here */
76     WORD bytespersector;
77     BYTE sectorspercluster;
78     WORD reservedsectors;
79     BYTE numberofFATs;
80     WORD rootdirsize;
81     WORD totalsectors;
82     BYTE mediaid;
83     WORD sectorsperfat;
84     WORD sectorspertrack;
85     WORD heads;
86     DWORD hiddensectors;
87     DWORD bigtotalsectors;
88     BYTE  reserved[6];
89     /* BPB ends here */
90     WORD sectorcount;
91     WORD sectortable[80];
92     } DRIVEPARAMS, *PDRIVEPARAMS;
93 /*}}}*/
94
95 static char *strwin32error(void) /*{{{*/
96 {
97     static char buffer[1024];
98
99     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
100                   NULL,
101                   GetLastError(),
102                   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
103                   (LPTSTR)buffer,
104                   1023, NULL);
105     return buffer;
106 }
107 /*}}}*/
108 static BOOL LockVolume( HANDLE hDisk ) /*{{{*/
109 {
110     DWORD ReturnedByteCount;
111
112     return DeviceIoControl( hDisk, FSCTL_LOCK_VOLUME, NULL, 0, NULL,
113                 0, &ReturnedByteCount, NULL );
114 }
115 /*}}}*/
116 static BOOL UnlockVolume( HANDLE hDisk )  /*{{{*/
117 {
118     DWORD ReturnedByteCount;
119
120     return DeviceIoControl( hDisk, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL,
121                 0, &ReturnedByteCount, NULL );
122 }
123 /*}}}*/
124 static BOOL DismountVolume( HANDLE hDisk ) /*{{{*/
125 {
126     DWORD ReturnedByteCount;
127
128     return DeviceIoControl( hDisk, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL,
129                 0, &ReturnedByteCount, NULL );
130 }
131 /*}}}*/
132 static int GetDriveParams( HANDLE hVWin32Device, int volume, DRIVEPARAMS* pParam ) /*{{{*/
133   {
134   DIOC_REGISTERS reg;
135   BOOL bResult;
136   DWORD cb;
137
138   reg.reg_EAX = 0x440d; // IOCTL for block device
139   reg.reg_EBX = volume; // one-based drive number
140   reg.reg_ECX = 0x0860; // Get Device params
141   reg.reg_EDX = (DWORD)pParam;
142   reg.reg_Flags = 1; // preset the carry flag
143
144   bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
145               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 ); 
146
147   if ( !bResult || (reg.reg_Flags & 1) ) 
148       return (reg.reg_EAX & 0xffff);
149
150   return 0;
151   }
152 /*}}}*/
153 static int SetDriveParams( HANDLE hVWin32Device, int volume, DRIVEPARAMS* pParam ) /*{{{*/
154   {
155   DIOC_REGISTERS reg;
156   BOOL bResult;
157   DWORD cb;
158
159   reg.reg_EAX = 0x440d; // IOCTL for block device
160   reg.reg_EBX = volume; // one-based drive number
161   reg.reg_ECX = 0x0840; // Set Device params
162   reg.reg_EDX = (DWORD)pParam;
163   reg.reg_Flags = 1; // preset the carry flag
164
165   bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
166               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 ); 
167
168   if ( !bResult || (reg.reg_Flags & 1) ) 
169       return (reg.reg_EAX & 0xffff);
170
171   return 0;
172   }
173 /*}}}*/
174 static int GetMediaID( HANDLE hVWin32Device, int volume, MID* pMid ) /*{{{*/
175   {
176   DIOC_REGISTERS reg;
177   BOOL bResult;
178   DWORD cb;
179
180   reg.reg_EAX = 0x440d; // IOCTL for block device
181   reg.reg_EBX = volume; // one-based drive number
182   reg.reg_ECX = 0x0866; // Get Media ID
183   reg.reg_EDX = (DWORD)pMid;
184   reg.reg_Flags = 1; // preset the carry flag
185
186   bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
187               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 );
188
189   if ( !bResult || (reg.reg_Flags & 1) ) 
190       return (reg.reg_EAX & 0xffff);
191
192   return 0;
193   }
194 /*}}}*/
195 static int VolumeCheck(HANDLE hVWin32Device, int volume, WORD* flags ) /*{{{*/
196 {
197   DIOC_REGISTERS reg;
198   BOOL bResult;
199   DWORD cb;
200
201   reg.reg_EAX = 0x4409; // Is Drive Remote
202   reg.reg_EBX = volume; // one-based drive number
203   reg.reg_Flags = 1; // preset the carry flag
204
205   bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
206               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 ); 
207
208   if ( !bResult || (reg.reg_Flags & 1) ) 
209       return (reg.reg_EAX & 0xffff);
210
211   *flags = (WORD)(reg.reg_EDX & 0xffff);
212   return 0;
213 }
214 /*}}}*/
215 static int LockLogicalVolume(HANDLE hVWin32Device, int volume, int lock_level, int permissions) /*{{{*/
216 {
217   DIOC_REGISTERS reg;
218   BOOL bResult;
219   DWORD cb;
220
221   reg.reg_EAX = 0x440d; // generic IOCTL
222   reg.reg_ECX = 0x084a; // lock logical volume 
223   reg.reg_EBX = volume | (lock_level << 8);
224   reg.reg_EDX = permissions;
225   reg.reg_Flags = 1; // preset the carry flag
226
227   bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
228               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 ); 
229
230   if ( !bResult || (reg.reg_Flags & 1) ) 
231       return (reg.reg_EAX & 0xffff);
232
233   return 0;
234 }
235 /*}}}*/
236 static int UnlockLogicalVolume( HANDLE hVWin32Device, int volume ) /*{{{*/
237 {
238   DIOC_REGISTERS reg;
239   BOOL bResult;
240   DWORD cb;
241
242   reg.reg_EAX = 0x440d;
243   reg.reg_ECX = 0x086a; // lock logical volume 
244   reg.reg_EBX = volume;
245   reg.reg_Flags = 1; // preset the carry flag
246
247   bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
248               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 ); 
249
250   if ( !bResult || (reg.reg_Flags & 1) ) return -1;
251   return 0;
252 }
253 /*}}}*/
254 static int w32mode(int mode) /*{{{*/
255 {
256     switch(mode)
257     {
258         case O_RDONLY: return GENERIC_READ;
259         case O_WRONLY: return GENERIC_WRITE;
260     }
261     return GENERIC_READ | GENERIC_WRITE;
262 }
263 /*}}}*/
264
265 /* Device_open           -- Open an image file                      */ /*{{{*/
266 const char *Device_open(struct Device *sb, const char *filename, int mode, const char *deviceOpts)
267 {
268     /* Windows 95/NT: floppy drives using handles */ 
269     if (strlen(filename) == 2 && filename[1] == ':')    /* Drive name */
270     {
271         char vname[20];
272         DWORD dwVers;
273
274         sb->fd = -1;
275         dwVers = GetVersion();
276
277         if (dwVers & 0x80000000L) /* Win32s (3.1) or Win32c (Win95) */
278         {
279             int lock, driveno, res, permissions;
280             unsigned short drive_flags;
281             MID media;
282
283             vname[0] = toupper(filename[0]);
284             driveno = vname[0] - 'A' + 1;   // 1=A: 2=B:
285             sb->drvtype = CPMDRV_WIN95;
286             sb->hdisk   = CreateFile( "\\\\.\\vwin32",
287                                        0,
288                                        0,
289                                        NULL,
290                                        0,
291                                        FILE_FLAG_DELETE_ON_CLOSE,
292                                        NULL );
293             if (!sb->hdisk)
294             {
295                 return "Failed to open VWIN32 driver.";
296             }
297             if (VolumeCheck(sb->hdisk, driveno, &drive_flags))
298             {
299                 CloseHandle(sb->hdisk);
300                 return "Invalid drive";
301             } 
302             res = GetMediaID( sb->hdisk, driveno, &media );
303             if ( res )
304             {
305                 const char *lboo = NULL;
306
307                 if ( res == ERROR_INVALID_FUNCTION && 
308                             (drive_flags & DRIVE_IS_REMOTE )) 
309                      lboo = "Network drive";
310                 else if (res == ERROR_ACCESS_DENIED) lboo = "Access denied";
311                 /* nb: It's perfectly legitimate for GetMediaID() to fail; most CP/M */
312                 /*     CP/M disks won't have a media ID. */ 
313            
314                 if (lboo != NULL)
315                 {
316                    CloseHandle(sb->hdisk);
317                    return lboo;
318                 }
319             }
320             if (!res && 
321                 (!memcmp( media.midFileSysType, "CDROM", 5 ) ||
322                  !memcmp( media.midFileSysType, "CD001", 5 ) ||
323                  !memcmp( media.midFileSysType, "CDAUDIO", 5 )))
324             {
325                 CloseHandle(sb->hdisk);
326                 return "CD-ROM drive";
327             }
328             if (w32mode(mode) & GENERIC_WRITE)
329             {
330                 lock = LEVEL0_LOCK; /* Exclusive access */
331                 permissions = 0;
332             }
333             else
334             {
335                 lock = LEVEL1_LOCK; /* Allow other processes access */
336                 permissions = LEVEL1_LOCK_MAX_PERMISSION;
337             }
338             if (LockLogicalVolume( sb->hdisk, driveno, lock, permissions))
339             {
340                 CloseHandle(sb->hdisk);
341                 return "Could not acquire a lock on the drive.";
342             }
343  
344             sb->fd = driveno;   /* 1=A: 2=B: etc - we will need this later */
345             
346         }
347         else
348         {
349             sprintf(vname, "\\\\.\\%s", filename);
350             sb->drvtype = CPMDRV_WINNT;
351             sb->hdisk   = CreateFile(vname,         /* Name */
352                                      w32mode(mode), /* Access mode */
353                                      FILE_SHARE_READ|FILE_SHARE_WRITE, /*Sharing*/
354                                      NULL,          /* Security attributes */ 
355                                      OPEN_EXISTING, /* See MSDN */
356                                      0,             /* Flags & attributes */
357                                      NULL);         /* Template file */
358
359             if (sb->hdisk != INVALID_HANDLE_VALUE)
360             {
361                 sb->fd = 1;   /* Arbitrary value >0 */
362                 if (LockVolume(sb->hdisk) == FALSE)    /* Lock drive */
363                 {
364                     char *lboo = strwin32error();
365                     CloseHandle(sb->hdisk);
366                     sb->fd = -1;
367                     return lboo;
368                 }
369             }
370             else return strwin32error();
371         }
372         sb->opened = 1;
373         return NULL;
374     }
375
376     /* Not a floppy. Treat it as a normal file */
377
378     mode |= O_BINARY;
379     sb->fd = open(filename, mode);
380     if (sb->fd == -1) return strerror(errno);
381     sb->drvtype = CPMDRV_FILE;
382     sb->opened  = 1;
383     return NULL;
384 }
385 /*}}}*/
386 /* Device_setGeometry    -- Set disk geometry                       */ /*{{{*/
387 void Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks)
388 {
389   int n;
390
391   this->secLength=secLength;
392   this->sectrk=sectrk;
393   this->tracks=tracks;
394   if (this->drvtype == CPMDRV_WIN95)
395   {
396       DRIVEPARAMS drvp;
397       memset(&drvp, 0, sizeof(drvp));
398       if (GetDriveParams( this->hdisk, this->fd, &drvp )) return;
399
400       drvp.bytespersector  = secLength;
401       drvp.sectorspertrack = sectrk;
402       drvp.totalsectors    = sectrk * tracks;
403
404 /* Guess the cylinder/head configuration from the track count. This will
405  * get single-sided 80-track discs wrong, but it's that or double-sided
406  * 40-track (or add cylinder/head counts to diskdefs) 
407  */
408       if (tracks < 44)
409       {
410         drvp.cylinders       = tracks;
411         drvp.heads           = 1;
412       }
413       else
414       {
415         drvp.cylinders       = tracks / 2;
416         drvp.heads           = 2;
417       }
418
419 /* Set up "reasonable" values for the other members */
420
421       drvp.sectorspercluster = 1024 / secLength;
422       drvp.reservedsectors   = 1;
423       drvp.numberofFATs      = 2;
424       drvp.sectorcount       = sectrk;
425       drvp.rootdirsize       = 64;
426       drvp.mediaid           = 0xF0;
427       drvp.hiddensectors     = 0;
428       drvp.sectorsperfat     = 3;
429       for (n = 0; n < sectrk; n++)
430       {
431           drvp.sectortable[n*2]   = n + PHYSICAL_SECTOR_1;    /* Physical sector numbers */ 
432           drvp.sectortable[n*2+1] = secLength;
433       }
434       drvp.special = 6;
435 /* We have not set:
436
437     drvp.mediatype   
438     drvp.devicetype  
439     drvp.deviceattrs  
440
441     which should have been read correctly by GetDriveParams().
442   */
443       SetDriveParams( this->hdisk, this->fd, &drvp );
444   }
445 }
446 /*}}}*/
447 /* Device_close          -- Close an image file                     */ /*{{{*/
448 const char *Device_close(struct Device *sb)
449 {
450     sb->opened = 0;
451     switch(sb->drvtype)
452     {
453         case CPMDRV_WIN95:
454             UnlockLogicalVolume(sb->hdisk, sb->fd );
455             if (!CloseHandle( sb->hdisk )) return strwin32error();
456             return NULL;
457
458         case CPMDRV_WINNT:
459             DismountVolume(sb->hdisk);
460             UnlockVolume(sb->hdisk);
461             if (!CloseHandle(sb->hdisk)) return strwin32error();
462             return NULL; 
463     }
464     if (close(sb->fd)) return strerror(errno);
465     return NULL; 
466 }
467 /*}}}*/
468 /* Device_readSector     -- read a physical sector                  */ /*{{{*/
469 const char *Device_readSector(const struct Device *drive, int track, int sector, char *buf)
470 {
471   int res;
472   off_t offset;
473
474   assert(sector>=0);
475   assert(sector<drive->sectrk);
476   assert(track>=0);
477   assert(track<drive->tracks);
478
479   offset = ((sector+track*drive->sectrk)*drive->secLength);
480
481   if (drive->drvtype == CPMDRV_WINNT)
482   {
483         LPVOID iobuffer;
484         DWORD  bytesread;
485     
486         if (SetFilePointer(drive->hdisk, offset, NULL, FILE_BEGIN) == INVALID_FILE_SIZE)
487         {
488             return strwin32error();
489         }
490         iobuffer = VirtualAlloc(NULL, drive->secLength, MEM_COMMIT, PAGE_READWRITE);
491         if (!iobuffer) 
492         {
493             return strwin32error();
494         }
495         res = ReadFile(drive->hdisk, iobuffer, drive->secLength, &bytesread, NULL);
496         if (!res)
497         {
498             char *lboo = strwin32error();
499             VirtualFree(iobuffer, drive->secLength, MEM_RELEASE);
500             return lboo;
501         } 
502
503         memcpy(buf, iobuffer, drive->secLength);
504         VirtualFree(iobuffer, drive->secLength, MEM_RELEASE);
505
506         if (bytesread < (unsigned)drive->secLength)
507         {
508             memset(buf + bytesread, 0, drive->secLength - bytesread);
509         }
510         return NULL;
511   }
512
513   if (drive->drvtype == CPMDRV_WIN95)
514   {
515         DIOC_REGISTERS reg;
516         BOOL bResult;
517         DWORD cb;
518
519 #ifdef USE_INT13
520         int cyl, head;
521
522         if (drive->tracks < 44) { cyl = track;    head = 0; }
523         else                    { cyl = track/2;  head = track & 1; }
524
525         reg.reg_EAX      = 0x0201;  // Read 1 sector
526         reg.reg_EBX      = (DWORD)buf;
527         reg.reg_ECX      = (cyl << 8)  | (sector + PHYSICAL_SECTOR_1);
528         reg.reg_EDX      = (head << 8) | (drive->fd - 1);
529         reg.reg_Flags    = 1;       // preset the carry flag
530         bResult          = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT13,
531               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 );
532 #else
533         DISKIO di;
534
535         reg.reg_EAX      = drive->fd - 1;  // zero-based volume number
536         reg.reg_EBX      = (DWORD)&di;
537         reg.reg_ECX      = 0xffff;  // use DISKIO structure
538         reg.reg_Flags    = 1;       // preset the carry flag
539         di.diStartSector = sector+track*drive->sectrk;
540         di.diSectors     = 1;
541         di.diBuffer      = (DWORD)buf;
542         bResult          = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT25,
543              &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 );
544
545 #endif
546         if ( !bResult || (reg.reg_Flags & 1) )
547         {
548             if (GetLastError()) return strwin32error();
549             return "Unknown read error.";
550         }
551         return 0;
552   }
553
554   if (lseek(drive->fd,offset,SEEK_SET)==-1)
555   {
556     return strerror(errno);
557   }
558   if ((res=read(drive->fd, buf, drive->secLength)) != drive->secLength)
559   {
560     if (res==-1)
561     {
562       return strerror(errno);
563     }
564     else memset(buf+res,0,drive->secLength-res); /* hit end of disk image */
565   }
566   return NULL;
567 }
568 /*}}}*/
569 /* Device_writeSector    -- write physical sector                   */ /*{{{*/
570 const char *Device_writeSector(const struct Device *drive, int track, int sector, const char *buf)
571 {
572   off_t offset;
573   int res;
574
575   assert(sector>=0);
576   assert(sector<drive->sectrk);
577   assert(track>=0);
578   assert(track<drive->tracks);
579
580   offset = ((sector+track*drive->sectrk)*drive->secLength);
581
582   if (drive->drvtype == CPMDRV_WINNT)
583   {
584         LPVOID iobuffer;
585         DWORD  byteswritten;
586
587         if (SetFilePointer(drive->hdisk, offset, NULL, FILE_BEGIN) == INVALID_FILE_SIZE)
588         {
589             return strwin32error();
590         }
591         iobuffer = VirtualAlloc(NULL, drive->secLength, MEM_COMMIT, PAGE_READWRITE);
592         if (!iobuffer)
593         {
594             return strwin32error();
595         }
596         memcpy(iobuffer, buf, drive->secLength);
597         res = WriteFile(drive->hdisk, iobuffer, drive->secLength, &byteswritten, NULL);
598         if (!res || (byteswritten < (unsigned)drive->secLength))
599         {
600             char *lboo = strwin32error();
601             VirtualFree(iobuffer, drive->secLength, MEM_RELEASE);
602             return lboo;
603         }
604
605         VirtualFree(iobuffer, drive->secLength, MEM_RELEASE);
606         return NULL;
607   }
608
609   if (drive->drvtype == CPMDRV_WIN95)
610   {
611         DIOC_REGISTERS reg;
612         BOOL bResult;
613         DWORD cb;
614
615 #ifdef USE_INT13
616         int cyl, head;
617
618         if (drive->tracks < 44) { cyl = track;    head = 0; }
619         else                    { cyl = track/2;  head = track & 1; }
620
621         reg.reg_EAX      = 0x0301;  // Write 1 sector
622         reg.reg_EBX      = (DWORD)buf;
623         reg.reg_ECX      = (cyl << 8)  | (sector + PHYSICAL_SECTOR_1);
624         reg.reg_EDX      = (head << 8) | (drive->fd - 1);
625         reg.reg_Flags    = 1;       // preset the carry flag
626         bResult          = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT13,
627               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 );
628 #else
629         DISKIO di;
630
631         reg.reg_EAX      = drive->fd - 1;  // zero-based volume number
632         reg.reg_EBX      = (DWORD)&di;
633         reg.reg_ECX      = 0xffff;  // use DISKIO structure
634         reg.reg_Flags    = 1;       // preset the carry flag
635         di.diStartSector = sector+track*drive->sectrk;
636         di.diSectors     = 1;
637         di.diBuffer      = (DWORD)buf;
638         bResult          = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT26,
639               &reg, sizeof( reg ), &reg, sizeof( reg ), &cb, 0 ); 
640 #endif
641
642         if ( !bResult || (reg.reg_Flags & 1) )
643         {
644             if (GetLastError()) return strwin32error();
645             return "Unknown write error.";
646         }
647         return NULL;
648   }
649
650   if (lseek(drive->fd,offset, SEEK_SET)==-1)
651   {
652     return strerror(errno);
653   }
654   if (write(drive->fd, buf, drive->secLength) == drive->secLength) return NULL;
655   return strerror(errno);
656 }
657 /*}}}*/