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