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