Import Upstream version 1.8.5
[hcoop/debian/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 = calloc(1, sizeof(*usd));
470
471 _ASSERT(sizeof(devhandle) <= sizeof(usd->handle));
472 usd->handle = (void *)devhandle;
473
474 usd->read = usd_DeviceRead;
475 usd->write = usd_DeviceWrite;
476 usd->seek = usd_DeviceSeek;
477 usd->ioctl = usd_DeviceIoctl;
478 usd->close = usd_DeviceClose;
479
480 usd->fullPathName = strdup(path);
481 usd->openFlags = oflag;
482
483 /* For devices, this is the first real reference, so many errors show up
484 * here. Also this call also sets the size (stored in usd->privateData)
485 * based on the results of the call to DeviceIoControl(). */
486 code = USD_IOCTL(usd, USD_IOCTL_GETTYPE, &mode);
487
488
489 /* If we're trying to obtain a write lock on a real disk, then the
490 * aggregate must not be attached by the kernel. If so, unlock it
491 * and fail.
492 * WARNING: The code to check for the above has been removed when this
493 * file was ported from DFS src. It should be put back if
494 * this library is used to access hard disks
495 */
496
497 if (code == 0 && usdP)
498 *usdP = usd;
499 else
500 usd_DeviceClose(usd);
501 return code;
502 }
503
504 int
505 usd_Open(const char *path, int oflag, int mode, usd_handle_t * usdP)
506 {
507 return usd_DeviceOpen(path, oflag, mode, usdP);
508 }
509
510 static int
511 usd_DeviceDummyClose(usd_handle_t usd)
512 {
513 free(usd);
514 return 0;
515 }
516
517 static int
518 usd_DeviceStandardInput(usd_handle_t * usdP)
519 {
520 usd_handle_t usd;
521
522 if (usdP)
523 *usdP = NULL;
524
525 usd = calloc(1, sizeof(*usd));
526 usd->handle = (void *)0;
527 usd->read = usd_DeviceRead;
528 usd->write = usd_DeviceWrite;
529 usd->seek = usd_DeviceSeek;
530 usd->ioctl = usd_DeviceIoctl;
531 usd->close = usd_DeviceDummyClose;
532 usd->fullPathName = "STDIN";
533 usd->openFlags = 0;
534
535 return 0;
536 }
537
538 int
539 usd_StandardInput(usd_handle_t * usdP)
540 {
541 return usd_DeviceStandardInput(usdP);
542 }
543
544 static int
545 usd_DeviceStandardOutput(usd_handle_t * usdP)
546 {
547 usd_handle_t usd;
548
549 if (usdP)
550 *usdP = NULL;
551
552 usd = calloc(1, sizeof(*usd));
553 usd->handle = (void *)1;
554 usd->read = usd_DeviceRead;
555 usd->write = usd_DeviceWrite;
556 usd->seek = usd_DeviceSeek;
557 usd->ioctl = usd_DeviceIoctl;
558 usd->close = usd_DeviceDummyClose;
559 usd->fullPathName = "STDOUT";
560 usd->openFlags = 0;
561
562 return 0;
563 }
564
565 int
566 usd_StandardOutput(usd_handle_t * usdP)
567 {
568 return usd_DeviceStandardOutput(usdP);
569 }