df5fff26ac4bb75b9048d250e3597ad362b50889
[openafs.git] / src / usd / usd_nt.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12
13
14 #include <windows.h>
15 #include <stdio.h>
16 #include <io.h>
17 #include <winioctl.h>
18 #include <sys/stat.h>
19 #include <crtdbg.h>
20
21 #include <afs/errmap_nt.h>
22 #include <afs/usd.h>
23
24
25 /* WinNT-specific implementation of user space device I/O for WinNT devices. */
26
27 /* This module uses the following usd_handle fields:
28  * handle -- the Win32 (HANDLE) returned by CreateFile.
29  * fullPathName -- allocated ptr (char *) to pathname used to open device.
30  * openFlags -- unused
31  * privateData -- Device size (afs_uint32) in 1Kb units.
32  */
33
34 /* Module-specific constants */
35 #define TAPEOP_RETRYMAX  5
36
37 #define TRANSIENT_TAPE_ERROR(err) \
38     ((err) == ERROR_BUS_RESET || (err) == ERROR_MEDIA_CHANGED)
39
40 /* Interface Functions */
41
42 static int
43 usd_DeviceRead(usd_handle_t usd, char *buf, afs_uint32 nbytes,
44                afs_uint32 * xferdP)
45 {
46     HANDLE fd = usd->handle;
47     DWORD bytesRead;
48
49     if (xferdP == NULL)
50         xferdP = &bytesRead;
51     else
52         *xferdP = 0;
53
54     if (ReadFile(fd, buf, nbytes, xferdP, NULL)) {
55         /* read was successful */
56         return 0;
57     } else {
58         /* read failed */
59         return nterr_nt2unix(GetLastError(), EIO);
60     }
61 }
62
63 static int
64 usd_DeviceWrite(usd_handle_t usd, char *buf, afs_uint32 nbytes,
65                 afs_uint32 * xferdP)
66 {
67     HANDLE fd = usd->handle;
68     DWORD bytesWritten;
69     DWORD nterr;
70
71     if (xferdP == NULL)
72         xferdP = &bytesWritten;
73     else
74         *xferdP = 0;
75
76     if (WriteFile(fd, buf, nbytes, xferdP, NULL)) {
77         /* write was successful */
78         return 0;
79     } else {
80         /* write failed */
81         nterr = GetLastError();
82         if (nterr == ERROR_END_OF_MEDIA) {
83             *xferdP = 0;
84             return 0;           /* indicate end of tape condition */
85         } else
86             return nterr_nt2unix(nterr, EIO);
87     }
88 }
89
90 /* usd_DeviceSeek --
91  *
92  * TODO -- Episode tries to determine the size of a disk device
93  * empirically by binary searching for the last region it can successfully
94  * read from the device.  It uses only USD_SEEK and USD_READ operations, so
95  * this is portable as long as the underlying device driver fails
96  * gracefully when an attempt is made to access past the end of the device.
97  * Unfortunately this fails on WinNT when accessing a floppy disk.  Reads
98  * from floppy disks at tremendous offsets blithely succeed.  Luckily, the
99  * Win32 interface provides a way to determine the disk size.
100  *
101  * Add a check of the offset against the disk size to fix this problem. */
102
103 static int
104 usd_DeviceSeek(usd_handle_t usd, afs_hyper_t reqOff, int whence,
105                afs_hyper_t * curOffP)
106 {
107     HANDLE fd = usd->handle;
108     DWORD method, result;
109     DWORD error;
110     long loOffset, hiOffset;
111
112     /* determine move method based on value of whence */
113
114     if (whence == SEEK_SET) {
115         method = FILE_BEGIN;
116     } else if (whence == SEEK_CUR) {
117         method = FILE_CURRENT;
118     } else if (whence == SEEK_END) {
119         method = FILE_END;
120     } else {
121         /* whence is invalid */
122         return EINVAL;
123     }
124     _ASSERT(sizeof(DWORD) == 4);
125
126     /* attempt seek */
127
128     loOffset = hgetlo(reqOff);
129     hiOffset = hgethi(reqOff);
130
131     if (usd->privateData) {
132
133         /* For disk devices that know their size, check the offset against the
134          * limit provided by DeviceIoControl(). */
135
136         DWORDLONG offset =
137             ((DWORDLONG) hgethi(reqOff) << 32 | (DWORDLONG) hgetlo(reqOff));
138
139         DWORDLONG k = (DWORDLONG) ((afs_uint32) usd->privateData);
140
141         if (offset >= (k << 10))
142             return EINVAL;
143     }
144
145     result = SetFilePointer(fd, loOffset, &hiOffset, method);
146     if (result == 0xffffffff && (error = GetLastError()) != NO_ERROR)
147         return nterr_nt2unix(error, EIO);
148     if (curOffP)
149         hset64(*curOffP, hiOffset, result);
150
151     return 0;
152 }
153
154 static int
155 usd_DeviceIoctl(usd_handle_t usd, int req, void *arg)
156 {
157     HANDLE fd = usd->handle;
158     DWORD result;
159     DWORD hiPart;
160     int code = 0;
161
162     switch (req) {
163     case USD_IOCTL_GETTYPE:
164         {
165             int mode;
166
167             BY_HANDLE_FILE_INFORMATION info;
168             DISK_GEOMETRY geom;
169             DWORD nbytes;
170             DWORD fileError = 0;
171             DWORD diskError = 0;
172
173             if (!GetFileInformationByHandle(fd, &info))
174                 fileError = GetLastError();
175
176             if (!DeviceIoControl
177                 (fd, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &geom,
178                  sizeof(geom), &nbytes, NULL))
179                 diskError = GetLastError();
180
181             mode = 0;
182             if ((fileError == ERROR_INVALID_PARAMETER
183                  || fileError == ERROR_INVALID_FUNCTION)
184                 && diskError == 0) {
185                 mode = S_IFCHR; /* a disk device */
186                 if ((afs_uint32) (usd->privateData) == 0) {
187
188                     /* Fill in the device size from disk geometry info.  Note
189                      * that this is the whole disk, not just the partition, so
190                      * it will serve only as an upper bound. */
191
192                     DWORDLONG size = geom.Cylinders.QuadPart;
193                     afs_uint32 k;
194                     size *= geom.TracksPerCylinder;
195                     size *= geom.SectorsPerTrack;
196                     size *= geom.BytesPerSector;
197                     if (size == 0)
198                         return ENODEV;
199                     size >>= 10;        /* convert to Kilobytes */
200                     if (size >> 31)
201                         k = 0x7fffffff;
202                     else
203                         k = (afs_uint32) size;
204                     usd->privateData = (void *)k;
205                 }
206             } else if (diskError == ERROR_INVALID_PARAMETER && fileError == 0)
207                 mode = S_IFREG; /* a regular file */
208             else {
209                 /* check to see if device is a tape drive */
210                 result = GetTapeStatus(fd);
211
212                 if (result != ERROR_INVALID_FUNCTION
213                     && result != ERROR_INVALID_PARAMETER) {
214                     /* looks like a tape drive */
215                     mode = S_IFCHR;
216                 }
217             }
218
219             if (!mode)
220                 return EINVAL;
221             *(int *)arg = mode;
222             return 0;
223         }
224
225     case USD_IOCTL_GETDEV:
226         return EINVAL;
227         *(dev_t *) arg = 0;
228         break;
229
230     case USD_IOCTL_GETFULLNAME:
231         *(char **)arg = usd->fullPathName;
232         break;
233
234     case USD_IOCTL_GETSIZE:
235         result = GetFileSize(fd, &hiPart);
236         if (result == 0xffffffff && (code = GetLastError()) != NO_ERROR)
237             return nterr_nt2unix(code, EIO);
238         hset64(*(afs_hyper_t *) arg, hiPart, result);
239         return 0;
240
241     case USD_IOCTL_SETSIZE:
242         code = usd_DeviceSeek(usd, *(afs_hyper_t *) arg, SEEK_SET, NULL);
243         if (!code) {
244             if (!SetEndOfFile(fd))
245                 code = nterr_nt2unix(GetLastError(), EIO);
246         }
247         return code;
248
249     case USD_IOCTL_TAPEOPERATION:
250         {
251             TAPE_GET_MEDIA_PARAMETERS mediaParam;
252             TAPE_GET_DRIVE_PARAMETERS driveParam;
253             DWORD mediaParamSize = sizeof(TAPE_GET_MEDIA_PARAMETERS);
254             DWORD driveParamSize = sizeof(TAPE_GET_DRIVE_PARAMETERS);
255             DWORD reloffset, fmarkType;
256             int retrycount;
257             usd_tapeop_t *tapeOpp = (usd_tapeop_t *) arg;
258
259             /* Execute specified tape command */
260
261             switch (tapeOpp->tp_op) {
262             case USDTAPE_WEOF:
263                 /* Determine type of filemark supported by device */
264                 result =
265                     GetTapeParameters(fd, GET_TAPE_DRIVE_INFORMATION,
266                                       &driveParamSize, &driveParam);
267
268                 if (result == NO_ERROR) {
269                     /* drive must support either normal or long filemarks */
270                     if (driveParam.FeaturesHigh & TAPE_DRIVE_WRITE_FILEMARKS) {
271                         fmarkType = TAPE_FILEMARKS;
272                     } else if (driveParam.
273                                FeaturesHigh & TAPE_DRIVE_WRITE_LONG_FMKS) {
274                         fmarkType = TAPE_LONG_FILEMARKS;
275                     } else {
276                         result = ERROR_NOT_SUPPORTED;
277                     }
278                 }
279
280                 /* Write specified number of filemarks */
281                 if (result == NO_ERROR) {
282                     result =
283                         WriteTapemark(fd, fmarkType, tapeOpp->tp_count,
284                                       FALSE);
285                 }
286                 break;
287
288             case USDTAPE_REW:
289                 /* Rewind tape */
290                 retrycount = 0;
291                 do {
292                     /* absorb non-persistant errors, e.g. ERROR_MEDIA_CHANGED. */
293                     result = SetTapePosition(fd, TAPE_REWIND, 0, 0, 0, FALSE);
294                 } while ((result != NO_ERROR)
295                          && (retrycount++ < TAPEOP_RETRYMAX));
296
297                 break;
298
299             case USDTAPE_FSF:
300             case USDTAPE_BSF:
301                 /* Space over specified number of file marks */
302                 if (tapeOpp->tp_count < 0) {
303                     result = ERROR_INVALID_PARAMETER;
304                 } else {
305                     if (tapeOpp->tp_op == USDTAPE_FSF) {
306                         reloffset = tapeOpp->tp_count;
307                     } else {
308                         reloffset = 0 - tapeOpp->tp_count;
309                     }
310
311                     result =
312                         SetTapePosition(fd, TAPE_SPACE_FILEMARKS, 0,
313                                         reloffset, 0, FALSE);
314                 }
315                 break;
316
317             case USDTAPE_PREPARE:
318                 /* Prepare tape drive for operation; do after open. */
319
320                 retrycount = 0;
321                 do {
322                     /* absorb non-persistant errors */
323                     if (retrycount > 0) {
324                         Sleep(2 * 1000);
325                     }
326                     result = PrepareTape(fd, TAPE_LOCK, FALSE);
327                 } while (TRANSIENT_TAPE_ERROR(result)
328                          && retrycount++ < TAPEOP_RETRYMAX);
329
330                 if (result == NO_ERROR) {
331                     retrycount = 0;
332                     do {
333                         /* absorb non-persistant errors */
334                         if (retrycount > 0) {
335                             Sleep(2 * 1000);
336                         }
337                         result = GetTapeStatus(fd);
338                     } while (TRANSIENT_TAPE_ERROR(result)
339                              && retrycount++ < TAPEOP_RETRYMAX);
340                 }
341
342                 /* Querying media/drive info seems to clear bad tape state */
343                 if (result == NO_ERROR) {
344                     result =
345                         GetTapeParameters(fd, GET_TAPE_MEDIA_INFORMATION,
346                                           &mediaParamSize, &mediaParam);
347                 }
348
349                 if (result == NO_ERROR) {
350                     result =
351                         GetTapeParameters(fd, GET_TAPE_DRIVE_INFORMATION,
352                                           &driveParamSize, &driveParam);
353                 }
354                 break;
355
356             case USDTAPE_SHUTDOWN:
357                 /* Decommission tape drive after operation; do before close. */
358                 result = PrepareTape(fd, TAPE_UNLOCK, FALSE);
359                 break;
360
361             default:
362                 /* Invalid command */
363                 result = ERROR_INVALID_PARAMETER;
364                 break;
365             }
366
367             if (result == NO_ERROR) {
368                 return (0);
369             } else {
370                 return nterr_nt2unix(result, EIO);
371             }
372         }
373
374     case USD_IOCTL_GETBLKSIZE:
375         *((long *)arg) = (long)4096;
376         return 0;
377
378     default:
379         return EINVAL;
380     }
381     return code;
382 }
383
384
385 static int
386 usd_DeviceClose(usd_handle_t usd)
387 {
388     HANDLE fd = usd->handle;
389     int code;
390
391     if (CloseHandle(fd))
392         code = 0;
393     else
394         code = nterr_nt2unix(GetLastError(), EIO);
395
396     if (usd->fullPathName)
397         free(usd->fullPathName);
398     free(usd);
399
400     return code;
401 }
402
403 /*
404  * usd_DeviceOpen() -- open WinNT device (or regular file)
405  *
406  * PARAMETERS --
407  *     oflag -- Various combinations of USD_OPEN_XXX defined in usd.h.
408  *     pmode -- ignored; file's security descriptor set to default on create.
409  *     usdP -- if NULL device is immediately closed after being opened.
410  *         Otherwise, *usdP is set to the user space device handle.
411  *
412  * RETURN CODES -- On error a unix-style errno value is *returned*.  Else zero.
413  */
414
415 static int
416 usd_DeviceOpen(const char *path, int oflag, int pmode, usd_handle_t * usdP)
417 {
418     HANDLE devhandle;
419     DWORD access, share, create, attr;
420     usd_handle_t usd;
421     int mode;                   /* type of opened object */
422     int code;
423
424     if (usdP)
425         *usdP = NULL;
426
427     /* set access as specified in oflag */
428
429     if (oflag & USD_OPEN_SYNC)
430         attr = FILE_FLAG_WRITE_THROUGH;
431     else
432         attr = 0;
433
434     /* should we always set:
435      *     FILE_FLAG_NO_BUFFERING?
436      *     FILE_FLAG_RANDOM_ACCESS?
437      */
438
439     access = GENERIC_READ;
440     if (oflag & USD_OPEN_RDWR)
441         access |= GENERIC_WRITE;
442
443     /* set create as specified in oflag */
444
445     if (oflag & USD_OPEN_CREATE) {
446         /* must be opening a file; allow it to be created */
447         create = OPEN_ALWAYS;
448     } else {
449         create = OPEN_EXISTING;
450     }
451
452
453     if (oflag & (USD_OPEN_RLOCK | USD_OPEN_WLOCK)) {
454
455         /* make sure both lock bits aren't set */
456         _ASSERT(~oflag & (USD_OPEN_RLOCK | USD_OPEN_WLOCK));
457
458         share =
459             ((oflag & USD_OPEN_RLOCK) ? FILE_SHARE_READ : 0 /*no sharing */ );
460
461     } else {
462         share = FILE_SHARE_READ + FILE_SHARE_WRITE;
463     }
464
465     /* attempt to open the device/file */
466
467     devhandle = CreateFile(path, access, share, NULL, create, attr, NULL);
468
469     if (devhandle == INVALID_HANDLE_VALUE)
470         return nterr_nt2unix(GetLastError(), EIO);
471
472     usd = (usd_handle_t) malloc(sizeof(*usd));
473     memset(usd, 0, sizeof(*usd));
474
475
476     _ASSERT(sizeof(devhandle) <= sizeof(usd->handle));
477     usd->handle = (void *)devhandle;
478
479     usd->read = usd_DeviceRead;
480     usd->write = usd_DeviceWrite;
481     usd->seek = usd_DeviceSeek;
482     usd->ioctl = usd_DeviceIoctl;
483     usd->close = usd_DeviceClose;
484
485     usd->fullPathName = (char *)malloc(strlen(path) + 1);
486     strcpy(usd->fullPathName, path);
487     usd->openFlags = oflag;
488
489     /* For devices, this is the first real reference, so many errors show up
490      * here.  Also this call also sets the size (stored in usd->privateData)
491      * based on the results of the call to DeviceIoControl(). */
492     code = USD_IOCTL(usd, USD_IOCTL_GETTYPE, &mode);
493
494
495     /* If we're trying to obtain a write lock on a real disk, then the
496      * aggregate must not be attached by the kernel.  If so, unlock it
497      * and fail.
498      * WARNING: The code to check for the above has been removed when this
499      * file was ported from DFS src. It should be put back if
500      * this library is used to access hard disks
501      */
502
503     if (code == 0 && usdP)
504         *usdP = usd;
505     else
506         usd_DeviceClose(usd);
507     return code;
508 }
509
510 int
511 usd_Open(const char *path, int oflag, int mode, usd_handle_t * usdP)
512 {
513     return usd_DeviceOpen(path, oflag, mode, usdP);
514 }
515
516 static int
517 usd_DeviceDummyClose(usd_handle_t usd)
518 {
519     free(usd);
520     return 0;
521 }
522
523 static int
524 usd_DeviceStandardInput(usd_handle_t * usdP)
525 {
526     usd_handle_t usd;
527
528     if (usdP)
529         *usdP = NULL;
530
531     usd = (usd_handle_t) malloc(sizeof(*usd));
532     memset(usd, 0, sizeof(*usd));
533     usd->handle = (void *)0;
534     usd->read = usd_DeviceRead;
535     usd->write = usd_DeviceWrite;
536     usd->seek = usd_DeviceSeek;
537     usd->ioctl = usd_DeviceIoctl;
538     usd->close = usd_DeviceDummyClose;
539     usd->fullPathName = "STDIN";
540     usd->openFlags = 0;
541
542     return 0;
543 }
544
545 int
546 usd_StandardInput(usd_handle_t * usdP)
547 {
548     return usd_DeviceStandardInput(usdP);
549 }
550
551 static int
552 usd_DeviceStandardOutput(usd_handle_t * usdP)
553 {
554     usd_handle_t usd;
555
556     if (usdP)
557         *usdP = NULL;
558
559     usd = (usd_handle_t) malloc(sizeof(*usd));
560     memset(usd, 0, sizeof(*usd));
561     usd->handle = (void *)1;
562     usd->read = usd_DeviceRead;
563     usd->write = usd_DeviceWrite;
564     usd->seek = usd_DeviceSeek;
565     usd->ioctl = usd_DeviceIoctl;
566     usd->close = usd_DeviceDummyClose;
567     usd->fullPathName = "STDOUT";
568     usd->openFlags = 0;
569
570     return 0;
571 }
572
573 int
574 usd_StandardOutput(usd_handle_t * usdP)
575 {
576     return usd_DeviceStandardOutput(usdP);
577 }