eject.c revision 1.16 1 1.16 bjh21 /* $NetBSD: eject.c,v 1.16 2001/10/06 15:43:33 bjh21 Exp $ */
2 1.8 tron
3 1.8 tron /*-
4 1.8 tron * Copyright (c) 1999 The NetBSD Foundation, Inc.
5 1.8 tron * All rights reserved.
6 1.8 tron *
7 1.8 tron * This code is derived from software contributed to The NetBSD Foundation
8 1.8 tron * by Chris Jones.
9 1.2 pk *
10 1.2 pk * Redistribution and use in source and binary forms, with or without
11 1.2 pk * modification, are permitted provided that the following conditions
12 1.2 pk * are met:
13 1.2 pk * 1. Redistributions of source code must retain the above copyright
14 1.2 pk * notice, this list of conditions and the following disclaimer.
15 1.2 pk * 2. Redistributions in binary form must reproduce the above copyright
16 1.2 pk * notice, this list of conditions and the following disclaimer in the
17 1.2 pk * documentation and/or other materials provided with the distribution.
18 1.2 pk * 3. All advertising materials mentioning features or use of this software
19 1.2 pk * must display the following acknowledgement:
20 1.8 tron * This product includes software developed by the NetBSD
21 1.8 tron * Foundation, Inc. and its contributors.
22 1.8 tron * 4. Neither the name of The NetBSD Foundation nor the names of its
23 1.8 tron * contributors may be used to endorse or promote products derived
24 1.8 tron * from this software without specific prior written permission.
25 1.2 pk *
26 1.8 tron * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 1.8 tron * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 1.8 tron * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 1.15 bjh21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 1.8 tron * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 1.8 tron * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 1.8 tron * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 1.8 tron * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 1.8 tron * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 1.8 tron * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 1.8 tron * POSSIBILITY OF SUCH DAMAGE.
37 1.2 pk */
38 1.2 pk
39 1.8 tron #include <sys/cdefs.h>
40 1.8 tron #ifndef lint
41 1.9 cjs __COPYRIGHT("@(#) Copyright (c) 1999 The NetBSD Foundation, Inc.\n\
42 1.9 cjs All rights reserved.\n");
43 1.15 bjh21 #endif /* not lint */
44 1.9 cjs
45 1.9 cjs #ifndef lint
46 1.16 bjh21 __RCSID("$NetBSD: eject.c,v 1.16 2001/10/06 15:43:33 bjh21 Exp $");
47 1.15 bjh21 #endif /* not lint */
48 1.8 tron
49 1.14 ad #include <sys/types.h>
50 1.14 ad #include <sys/cdio.h>
51 1.14 ad #include <sys/disklabel.h>
52 1.14 ad #include <sys/ioctl.h>
53 1.14 ad #include <sys/param.h>
54 1.14 ad #include <sys/ucred.h>
55 1.14 ad #include <sys/mount.h>
56 1.14 ad #include <sys/mtio.h>
57 1.14 ad
58 1.8 tron #include <ctype.h>
59 1.8 tron #include <err.h>
60 1.8 tron #include <fcntl.h>
61 1.8 tron #include <stdio.h>
62 1.8 tron #include <stdlib.h>
63 1.8 tron #include <string.h>
64 1.8 tron #include <unistd.h>
65 1.14 ad #include <util.h>
66 1.8 tron
67 1.8 tron struct nicknames_s {
68 1.15 bjh21 char *name; /* The name given on the command line. */
69 1.15 bjh21 char *devname; /* The base name of the device */
70 1.15 bjh21 int type; /* The type of device, for determining what
71 1.15 bjh21 * ioctl to use. */
72 1.8 tron #define TAPE 0x10
73 1.8 tron #define DISK 0x20
74 1.15 bjh21 /* OR one of the above with one of the below: */
75 1.8 tron #define NOTLOADABLE 0x00
76 1.8 tron #define LOADABLE 0x01
77 1.8 tron #define TYPEMASK ((int)~0x01)
78 1.15 bjh21 } nicknames[] = {
79 1.15 bjh21 { "diskette", "fd", DISK | NOTLOADABLE },
80 1.15 bjh21 { "floppy", "fd", DISK | NOTLOADABLE },
81 1.15 bjh21 { "fd", "fd", DISK | NOTLOADABLE },
82 1.15 bjh21 { "sd", "sd", DISK | NOTLOADABLE },
83 1.15 bjh21 { "cdrom", "cd", DISK | LOADABLE },
84 1.15 bjh21 { "cd", "cd", DISK | LOADABLE },
85 1.15 bjh21 { "cdr", "cd", DISK | LOADABLE },
86 1.15 bjh21 { "cdrw", "cd", DISK | LOADABLE },
87 1.15 bjh21 { "dvdrom", "cd", DISK | LOADABLE },
88 1.15 bjh21 { "dvd", "cd", DISK | LOADABLE },
89 1.15 bjh21 { "dvdr", "cd", DISK | LOADABLE },
90 1.15 bjh21 { "dvdrw", "cd", DISK | LOADABLE },
91 1.15 bjh21 { "mcd", "mcd", DISK | LOADABLE }, /* XXX Is this true? */
92 1.15 bjh21 { "tape", "st", TAPE | NOTLOADABLE },
93 1.15 bjh21 { "st", "st", TAPE | NOTLOADABLE },
94 1.15 bjh21 { "dat", "st", TAPE | NOTLOADABLE },
95 1.15 bjh21 { "exabyte", "st", TAPE | NOTLOADABLE },
96 1.8 tron };
97 1.15 bjh21 #define MAXNICKLEN 12 /* at least enough room for the longest
98 1.15 bjh21 * nickname */
99 1.15 bjh21 #define MAXDEVLEN (MAXNICKLEN + 7) /* "/dev/r" ... "a" */
100 1.8 tron
101 1.8 tron struct devtypes_s {
102 1.15 bjh21 char *name;
103 1.15 bjh21 int type;
104 1.15 bjh21 } devtypes[] = {
105 1.15 bjh21 { "diskette", DISK | NOTLOADABLE },
106 1.15 bjh21 { "floppy", DISK | NOTLOADABLE },
107 1.15 bjh21 { "cdrom", DISK | LOADABLE },
108 1.15 bjh21 { "disk", DISK | NOTLOADABLE },
109 1.15 bjh21 { "tape", TAPE | NOTLOADABLE },
110 1.8 tron };
111 1.8 tron
112 1.16 bjh21 enum eject_op {
113 1.16 bjh21 OP_EJECT, OP_LOAD, OP_LOCK, OP_UNLOCK
114 1.16 bjh21 };
115 1.16 bjh21
116 1.8 tron int verbose_f = 0;
117 1.8 tron int umount_f = 1;
118 1.8 tron
119 1.15 bjh21 int main(int, char *[]);
120 1.15 bjh21 void usage(void);
121 1.15 bjh21 char *nick2dev(char *);
122 1.15 bjh21 char *nick2rdev(char *);
123 1.15 bjh21 int guess_devtype(char *);
124 1.15 bjh21 char *guess_nickname(char *);
125 1.16 bjh21 void eject_tape(char *, enum eject_op);
126 1.16 bjh21 void eject_disk(char *, enum eject_op);
127 1.15 bjh21 void unmount_dev(char *);
128 1.8 tron
129 1.8 tron int
130 1.15 bjh21 main(int argc, char *argv[])
131 1.7 bouyer {
132 1.15 bjh21 int ch;
133 1.15 bjh21 int devtype = -1;
134 1.15 bjh21 int n, i;
135 1.15 bjh21 char *devname = NULL;
136 1.16 bjh21 enum eject_op op = OP_EJECT;
137 1.15 bjh21
138 1.16 bjh21 while ((ch = getopt(argc, argv, "d:flLnt:Uv")) != -1) {
139 1.15 bjh21 switch (ch) {
140 1.15 bjh21 case 'd':
141 1.15 bjh21 devname = optarg;
142 1.15 bjh21 break;
143 1.15 bjh21 case 'f':
144 1.15 bjh21 umount_f = 0;
145 1.15 bjh21 break;
146 1.15 bjh21 case 'l':
147 1.16 bjh21 if (op != OP_EJECT)
148 1.16 bjh21 usage();
149 1.16 bjh21 op = OP_LOAD;
150 1.16 bjh21 break;
151 1.16 bjh21 case 'L':
152 1.16 bjh21 if (op != OP_EJECT)
153 1.16 bjh21 usage();
154 1.16 bjh21 op = OP_LOCK;
155 1.15 bjh21 break;
156 1.15 bjh21 case 'n':
157 1.15 bjh21 for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]);
158 1.15 bjh21 n++) {
159 1.15 bjh21 struct nicknames_s *np = &nicknames[n];
160 1.15 bjh21
161 1.15 bjh21 printf("%s -> %s\n", np->name, nick2dev(np->name));
162 1.15 bjh21 }
163 1.15 bjh21 return (0);
164 1.15 bjh21 case 't':
165 1.15 bjh21 for (i = 0; i < sizeof(devtypes) / sizeof(devtypes[0]);
166 1.15 bjh21 i++) {
167 1.15 bjh21 if (strcasecmp(devtypes[i].name, optarg) == 0) {
168 1.15 bjh21 devtype = devtypes[i].type;
169 1.15 bjh21 break;
170 1.15 bjh21 }
171 1.15 bjh21 }
172 1.15 bjh21 if (devtype == -1)
173 1.16 bjh21 errx(1, "%s: unknown device type", optarg);
174 1.16 bjh21 break;
175 1.16 bjh21 case 'U':
176 1.16 bjh21 if (op != OP_EJECT)
177 1.16 bjh21 usage();
178 1.16 bjh21 op = OP_UNLOCK;
179 1.15 bjh21 break;
180 1.15 bjh21 case 'v':
181 1.15 bjh21 verbose_f = 1;
182 1.15 bjh21 break;
183 1.15 bjh21 default:
184 1.15 bjh21 usage();
185 1.15 bjh21 /* NOTREACHED */
186 1.8 tron }
187 1.15 bjh21 }
188 1.15 bjh21 argc -= optind;
189 1.15 bjh21 argv += optind;
190 1.8 tron
191 1.15 bjh21 if (devname == NULL) {
192 1.15 bjh21 if (argc == 0) {
193 1.15 bjh21 usage();
194 1.15 bjh21 /* NOTREACHED */
195 1.15 bjh21 } else
196 1.15 bjh21 devname = argv[0];
197 1.15 bjh21 }
198 1.15 bjh21 if (devtype == -1)
199 1.15 bjh21 devtype = guess_devtype(devname);
200 1.15 bjh21 if (devtype == -1)
201 1.15 bjh21 errx(1, "%s: unable to determine type of device",
202 1.15 bjh21 devname);
203 1.15 bjh21 if (verbose_f) {
204 1.15 bjh21 printf("device type == ");
205 1.15 bjh21 if ((devtype & TYPEMASK) == TAPE)
206 1.15 bjh21 printf("tape\n");
207 1.15 bjh21 else
208 1.15 bjh21 printf("disk, floppy, or cdrom\n");
209 1.15 bjh21 }
210 1.15 bjh21 if (umount_f)
211 1.15 bjh21 unmount_dev(devname);
212 1.8 tron
213 1.15 bjh21 /* XXX Tapes and disks have different ioctl's: */
214 1.15 bjh21 if ((devtype & TYPEMASK) == TAPE)
215 1.16 bjh21 eject_tape(devname, op);
216 1.15 bjh21 else
217 1.16 bjh21 eject_disk(devname, op);
218 1.8 tron
219 1.15 bjh21 if (verbose_f)
220 1.15 bjh21 printf("done.\n");
221 1.8 tron
222 1.15 bjh21 return (0);
223 1.7 bouyer }
224 1.7 bouyer
225 1.8 tron void
226 1.8 tron usage(void)
227 1.8 tron {
228 1.15 bjh21
229 1.16 bjh21 fprintf(stderr, "Usage: eject [-fv] [-l | -L | -U] "
230 1.16 bjh21 "[-t device-type] [-d] device\n");
231 1.16 bjh21 fprintf(stderr, " eject -n\n");
232 1.16 bjh21 exit(1);
233 1.8 tron }
234 1.2 pk
235 1.8 tron int
236 1.8 tron guess_devtype(char *devname)
237 1.2 pk {
238 1.15 bjh21 int n;
239 1.15 bjh21
240 1.15 bjh21 /* Nickname match: */
241 1.15 bjh21 for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]);
242 1.15 bjh21 n++) {
243 1.15 bjh21 if (strncasecmp(nicknames[n].name, devname,
244 1.15 bjh21 strlen(nicknames[n].name)) == 0)
245 1.15 bjh21 return (nicknames[n].type);
246 1.15 bjh21 }
247 1.2 pk
248 1.8 tron /*
249 1.15 bjh21 * If we still don't know it, then try to compare vs. dev
250 1.15 bjh21 * and rdev names that we know.
251 1.15 bjh21 */
252 1.15 bjh21 /* dev first: */
253 1.15 bjh21 for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
254 1.15 bjh21 char *name;
255 1.15 bjh21 name = nick2dev(nicknames[n].name);
256 1.15 bjh21 /*
257 1.15 bjh21 * Assume that the part of the name that distinguishes the
258 1.15 bjh21 * instance of this device begins with a 0.
259 1.15 bjh21 */
260 1.15 bjh21 *(strchr(name, '0')) = '\0';
261 1.15 bjh21 if (strncmp(name, devname, strlen(name)) == 0)
262 1.15 bjh21 return (nicknames[n].type);
263 1.15 bjh21 }
264 1.15 bjh21
265 1.15 bjh21 /* Now rdev: */
266 1.15 bjh21 for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
267 1.15 bjh21 char *name = nick2rdev(nicknames[n].name);
268 1.15 bjh21 *(strchr(name, '0')) = '\0';
269 1.15 bjh21 if (strncmp(name, devname, strlen(name)) == 0)
270 1.15 bjh21 return (nicknames[n].type);
271 1.15 bjh21 }
272 1.8 tron
273 1.15 bjh21 /* Not found. */
274 1.15 bjh21 return (-1);
275 1.8 tron }
276 1.8 tron /* "floppy5" -> "/dev/fd5a". Yep, this uses a static buffer. */
277 1.8 tron char *
278 1.8 tron nick2dev(char *nn)
279 1.8 tron {
280 1.15 bjh21 int n;
281 1.15 bjh21 static char devname[MAXDEVLEN];
282 1.15 bjh21 int devnum = 0;
283 1.15 bjh21
284 1.15 bjh21 for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
285 1.15 bjh21 if (strncasecmp(nicknames[n].name, nn,
286 1.15 bjh21 strlen(nicknames[n].name)) == 0) {
287 1.15 bjh21 sscanf(nn, "%*[^0-9]%d", &devnum);
288 1.15 bjh21 sprintf(devname, "/dev/%s%d", nicknames[n].devname,
289 1.15 bjh21 devnum);
290 1.15 bjh21 if ((nicknames[n].type & TYPEMASK) != TAPE)
291 1.15 bjh21 strcat(devname, "a");
292 1.15 bjh21 return (devname);
293 1.15 bjh21 }
294 1.2 pk }
295 1.2 pk
296 1.15 bjh21 return (NULL);
297 1.8 tron }
298 1.8 tron /* "floppy5" -> "/dev/rfd5c". Static buffer. */
299 1.8 tron char *
300 1.8 tron nick2rdev(char *nn)
301 1.8 tron {
302 1.15 bjh21 int n;
303 1.15 bjh21 static char devname[MAXDEVLEN];
304 1.15 bjh21 int devnum = 0;
305 1.15 bjh21
306 1.15 bjh21 for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
307 1.15 bjh21 if (strncasecmp(nicknames[n].name, nn,
308 1.15 bjh21 strlen(nicknames[n].name)) == 0) {
309 1.15 bjh21 sscanf(nn, "%*[^0-9]%d", &devnum);
310 1.15 bjh21 sprintf(devname, "/dev/r%s%d", nicknames[n].devname,
311 1.15 bjh21 devnum);
312 1.15 bjh21 if ((nicknames[n].type & TYPEMASK) != TAPE) {
313 1.15 bjh21 strcat(devname, "a");
314 1.15 bjh21 devname[strlen(devname) - 1] += getrawpartition();
315 1.15 bjh21 }
316 1.15 bjh21 return (devname);
317 1.15 bjh21 }
318 1.2 pk }
319 1.2 pk
320 1.15 bjh21 return (NULL);
321 1.2 pk }
322 1.8 tron /* Unmount all filesystems attached to dev. */
323 1.8 tron void
324 1.8 tron unmount_dev(char *name)
325 1.2 pk {
326 1.15 bjh21 struct statfs *mounts;
327 1.15 bjh21 int i, nmnts, len;
328 1.15 bjh21 char *dn;
329 1.15 bjh21
330 1.15 bjh21 nmnts = getmntinfo(&mounts, MNT_NOWAIT);
331 1.15 bjh21 if (nmnts == 0) {
332 1.15 bjh21 err(1, "getmntinfo");
333 1.15 bjh21 }
334 1.15 bjh21 /* Make sure we have a device name: */
335 1.15 bjh21 dn = nick2dev(name);
336 1.15 bjh21 if (dn == NULL)
337 1.15 bjh21 dn = name;
338 1.15 bjh21
339 1.15 bjh21 /* Set len to strip off the partition name: */
340 1.15 bjh21 len = strlen(dn);
341 1.15 bjh21 if (!isdigit(dn[len - 1]))
342 1.15 bjh21 len--;
343 1.15 bjh21 if (!isdigit(dn[len - 1])) {
344 1.15 bjh21 errx(1, "Can't figure out base name for dev name %s", dn);
345 1.15 bjh21 }
346 1.15 bjh21 for (i = 0; i < nmnts; i++) {
347 1.15 bjh21 if (strncmp(mounts[i].f_mntfromname, dn, len) == 0) {
348 1.15 bjh21 if (verbose_f)
349 1.15 bjh21 printf("Unmounting %s from %s...\n",
350 1.15 bjh21 mounts[i].f_mntfromname,
351 1.15 bjh21 mounts[i].f_mntonname);
352 1.15 bjh21
353 1.15 bjh21 if (unmount(mounts[i].f_mntonname, 0) == -1) {
354 1.15 bjh21 err(1, "unmount: %s", mounts[i].f_mntonname);
355 1.15 bjh21 }
356 1.15 bjh21 }
357 1.2 pk }
358 1.8 tron
359 1.15 bjh21 return;
360 1.8 tron }
361 1.2 pk
362 1.8 tron void
363 1.16 bjh21 eject_tape(char *name, enum eject_op op)
364 1.8 tron {
365 1.15 bjh21 struct mtop m;
366 1.15 bjh21 int fd;
367 1.15 bjh21 char *dn;
368 1.15 bjh21
369 1.15 bjh21 dn = nick2rdev(name);
370 1.15 bjh21 if (dn == NULL)
371 1.15 bjh21 dn = name; /* Hope for the best. */
372 1.15 bjh21 fd = open(dn, O_RDONLY);
373 1.15 bjh21 if (fd == -1)
374 1.15 bjh21 err(1, "open: %s", dn);
375 1.16 bjh21 switch (op) {
376 1.16 bjh21 case OP_EJECT:
377 1.16 bjh21 if (verbose_f)
378 1.16 bjh21 printf("Ejecting %s...\n", dn);
379 1.15 bjh21
380 1.16 bjh21 m.mt_op = MTOFFL;
381 1.16 bjh21 m.mt_count = 0;
382 1.16 bjh21 if (ioctl(fd, MTIOCTOP, &m) == -1)
383 1.16 bjh21 err(1, "ioctl: MTIOCTOP: %s", dn);
384 1.16 bjh21 break;
385 1.16 bjh21 case OP_LOAD:
386 1.16 bjh21 errx(1, "cannot load tapes");
387 1.16 bjh21 /* NOTREACHED */
388 1.16 bjh21 case OP_LOCK:
389 1.16 bjh21 errx(1, "cannot lock tapes");
390 1.16 bjh21 /* NOTREACHED */
391 1.16 bjh21 case OP_UNLOCK:
392 1.16 bjh21 errx(1, "cannot unlock tapes");
393 1.16 bjh21 /* NOTREACHED */
394 1.16 bjh21 }
395 1.15 bjh21 close(fd);
396 1.15 bjh21 return;
397 1.8 tron }
398 1.2 pk
399 1.8 tron void
400 1.16 bjh21 eject_disk(char *name, enum eject_op op)
401 1.8 tron {
402 1.15 bjh21 int fd;
403 1.15 bjh21 char *dn;
404 1.15 bjh21 int arg;
405 1.15 bjh21
406 1.15 bjh21 dn = nick2rdev(name);
407 1.15 bjh21 if (dn == NULL)
408 1.15 bjh21 dn = name; /* Hope for the best. */
409 1.15 bjh21 fd = open(dn, O_RDONLY);
410 1.15 bjh21 if (fd == -1)
411 1.15 bjh21 err(1, "open: %s", dn);
412 1.16 bjh21 switch (op) {
413 1.16 bjh21 case OP_LOAD:
414 1.15 bjh21 if (verbose_f)
415 1.15 bjh21 printf("Closing %s...\n", dn);
416 1.15 bjh21
417 1.15 bjh21 if (ioctl(fd, CDIOCCLOSE, NULL) == -1)
418 1.15 bjh21 err(1, "ioctl: CDIOCCLOSE: %s", dn);
419 1.16 bjh21 break;
420 1.16 bjh21 case OP_EJECT:
421 1.15 bjh21 if (verbose_f)
422 1.15 bjh21 printf("Ejecting %s...\n", dn);
423 1.15 bjh21
424 1.15 bjh21 arg = 0;
425 1.15 bjh21 if (umount_f == 0) {
426 1.15 bjh21 /* force eject, unlock the device first */
427 1.15 bjh21 if (ioctl(fd, DIOCLOCK, &arg) == -1)
428 1.16 bjh21 err(1, "ioctl: DIOCLOCK: %s", dn);
429 1.15 bjh21 arg = 1;
430 1.15 bjh21 }
431 1.15 bjh21 if (ioctl(fd, DIOCEJECT, &arg) == -1)
432 1.15 bjh21 err(1, "ioctl: DIOCEJECT: %s", dn);
433 1.16 bjh21 break;
434 1.16 bjh21 case OP_LOCK:
435 1.16 bjh21 if (verbose_f)
436 1.16 bjh21 printf("Locking %s...\n", dn);
437 1.16 bjh21
438 1.16 bjh21 arg = 1;
439 1.16 bjh21 if (ioctl(fd, DIOCLOCK, &arg) == -1)
440 1.16 bjh21 err(1, "ioctl: DIOCLOCK: %s", dn);
441 1.16 bjh21 break;
442 1.16 bjh21 case OP_UNLOCK:
443 1.16 bjh21 if (verbose_f)
444 1.16 bjh21 printf("Unlocking %s...\n", dn);
445 1.16 bjh21
446 1.16 bjh21 arg = 0;
447 1.16 bjh21 if (ioctl(fd, DIOCLOCK, &arg) == -1)
448 1.16 bjh21 err(1, "ioctl: DIOCLOCK: %s", dn);
449 1.16 bjh21 break;
450 1.15 bjh21 }
451 1.2 pk
452 1.15 bjh21 close(fd);
453 1.15 bjh21 return;
454 1.2 pk }
455