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