]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 1994-2005 Jeff Tranter (tranter@pobox.com) | |
3 | * Copyright (C) 2012 Karel Zak <kzak@redhat.com> | |
4 | * Copyright (C) Michal Luscon <mluscon@redhat.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | */ | |
20 | ||
21 | #include <unistd.h> | |
22 | #include <stdlib.h> | |
23 | #include <stdio.h> | |
24 | #include <string.h> | |
25 | #include <fcntl.h> | |
26 | #include <limits.h> | |
27 | #include <err.h> | |
28 | #include <stdarg.h> | |
29 | ||
30 | #include <getopt.h> | |
31 | #include <errno.h> | |
32 | #include <regex.h> | |
33 | #include <sys/types.h> | |
34 | #include <sys/stat.h> | |
35 | #include <sys/ioctl.h> | |
36 | #include <sys/wait.h> | |
37 | #include <sys/mtio.h> | |
38 | #include <linux/cdrom.h> | |
39 | #include <linux/fd.h> | |
40 | #include <sys/mount.h> | |
41 | #include <scsi/scsi.h> | |
42 | #include <scsi/sg.h> | |
43 | #include <scsi/scsi_ioctl.h> | |
44 | #include <sys/time.h> | |
45 | ||
46 | #include <libmount.h> | |
47 | ||
48 | #include "c.h" | |
49 | #include "closestream.h" | |
50 | #include "nls.h" | |
51 | #include "strutils.h" | |
52 | #include "xalloc.h" | |
53 | #include "pathnames.h" | |
54 | #include "sysfs.h" | |
55 | #include "monotonic.h" | |
56 | ||
57 | /* | |
58 | * sg_io_hdr_t driver_status -- see kernel include/scsi/sg.h | |
59 | */ | |
60 | #ifndef DRIVER_SENSE | |
61 | # define DRIVER_SENSE 0x08 | |
62 | #endif | |
63 | ||
64 | ||
65 | #define EJECT_DEFAULT_DEVICE "/dev/cdrom" | |
66 | ||
67 | ||
68 | /* Used by the toggle_tray() function. If ejecting the tray takes this | |
69 | * time or less, the tray was probably already ejected, so we close it | |
70 | * again. | |
71 | */ | |
72 | #define TRAY_WAS_ALREADY_OPEN_USECS 200000 /* about 0.2 seconds */ | |
73 | ||
74 | struct eject_control { | |
75 | struct libmnt_table *mtab; | |
76 | char *device; /* device or mount point to be ejected */ | |
77 | int fd; /* file descriptor for device */ | |
78 | bool /* command flags and arguments */ | |
79 | a_option, | |
80 | c_option, | |
81 | d_option, | |
82 | F_option, | |
83 | f_option, | |
84 | i_option, | |
85 | M_option, | |
86 | m_option, | |
87 | n_option, | |
88 | p_option, | |
89 | q_option, | |
90 | r_option, | |
91 | s_option, | |
92 | T_option, | |
93 | t_option, | |
94 | v_option, | |
95 | X_option, | |
96 | x_option, | |
97 | a_arg, | |
98 | i_arg; | |
99 | ||
100 | unsigned int force_exclusive; /* use O_EXCL */ | |
101 | ||
102 | long int c_arg; /* changer slot number */ | |
103 | long int x_arg; /* cd speed */ | |
104 | }; | |
105 | ||
106 | static void vinfo(const char *fmt, va_list va) | |
107 | { | |
108 | fprintf(stdout, "%s: ", program_invocation_short_name); | |
109 | vprintf(fmt, va); | |
110 | fputc('\n', stdout); | |
111 | } | |
112 | ||
113 | static inline void __attribute__ ((__format__ (__printf__, 2, 3))) | |
114 | verbose(const struct eject_control *ctl, const char *fmt, ...) | |
115 | { | |
116 | va_list va; | |
117 | ||
118 | if (!ctl->v_option) | |
119 | return; | |
120 | ||
121 | va_start(va, fmt); | |
122 | vinfo(fmt, va); | |
123 | va_end(va); | |
124 | } | |
125 | ||
126 | static inline __attribute__ ((__format__ (__printf__, 1, 2))) | |
127 | void info(const char *fmt, ...) | |
128 | { | |
129 | va_list va; | |
130 | va_start(va, fmt); | |
131 | vinfo(fmt, va); | |
132 | va_end(va); | |
133 | } | |
134 | ||
135 | static void __attribute__((__noreturn__)) usage(void) | |
136 | { | |
137 | FILE *out = stdout; | |
138 | fputs(USAGE_HEADER, out); | |
139 | fprintf(out, | |
140 | _(" %s [options] [<device>|<mountpoint>]\n"), program_invocation_short_name); | |
141 | ||
142 | fputs(USAGE_SEPARATOR, out); | |
143 | fputs(_("Eject removable media.\n"), out); | |
144 | ||
145 | fputs(USAGE_OPTIONS, out); | |
146 | fputs(_(" -a, --auto <on|off> turn auto-eject feature on or off\n" | |
147 | " -c, --changerslot <slot> switch discs on a CD-ROM changer\n" | |
148 | " -d, --default display default device\n" | |
149 | " -f, --floppy eject floppy\n" | |
150 | " -F, --force don't care about device type\n" | |
151 | " -i, --manualeject <on|off> toggle manual eject protection on/off\n" | |
152 | " -m, --no-unmount do not unmount device even if it is mounted\n" | |
153 | " -M, --no-partitions-unmount do not unmount another partitions\n" | |
154 | " -n, --noop don't eject, just show device found\n" | |
155 | " -p, --proc use /proc/mounts instead of /etc/mtab\n" | |
156 | " -q, --tape eject tape\n" | |
157 | " -r, --cdrom eject CD-ROM\n" | |
158 | " -s, --scsi eject SCSI device\n" | |
159 | " -t, --trayclose close tray\n" | |
160 | " -T, --traytoggle toggle tray\n" | |
161 | " -v, --verbose enable verbose output\n" | |
162 | " -x, --cdspeed <speed> set CD-ROM max speed\n" | |
163 | " -X, --listspeed list CD-ROM available speeds\n"), | |
164 | out); | |
165 | ||
166 | fputs(USAGE_SEPARATOR, out); | |
167 | fprintf(out, USAGE_HELP_OPTIONS(29)); | |
168 | ||
169 | fputs(_("\nBy default tries -r, -s, -f, and -q in order until success.\n"), out); | |
170 | fprintf(out, USAGE_MAN_TAIL("eject(1)")); | |
171 | ||
172 | exit(EXIT_SUCCESS); | |
173 | } | |
174 | ||
175 | ||
176 | /* Handle command line options. */ | |
177 | static void parse_args(struct eject_control *ctl, int argc, char **argv) | |
178 | { | |
179 | static const struct option long_opts[] = | |
180 | { | |
181 | {"auto", required_argument, NULL, 'a'}, | |
182 | {"cdrom", no_argument, NULL, 'r'}, | |
183 | {"cdspeed", required_argument, NULL, 'x'}, | |
184 | {"changerslot", required_argument, NULL, 'c'}, | |
185 | {"default", no_argument, NULL, 'd'}, | |
186 | {"floppy", no_argument, NULL, 'f'}, | |
187 | {"force", no_argument, NULL, 'F'}, | |
188 | {"help", no_argument, NULL, 'h'}, | |
189 | {"listspeed", no_argument, NULL, 'X'}, | |
190 | {"manualeject", required_argument, NULL, 'i'}, | |
191 | {"noop", no_argument, NULL, 'n'}, | |
192 | {"no-unmount", no_argument, NULL, 'm'}, | |
193 | {"no-partitions-unmount", no_argument, NULL, 'M' }, | |
194 | {"proc", no_argument, NULL, 'p'}, | |
195 | {"scsi", no_argument, NULL, 's'}, | |
196 | {"tape", no_argument, NULL, 'q'}, | |
197 | {"trayclose", no_argument, NULL, 't'}, | |
198 | {"traytoggle", no_argument, NULL, 'T'}, | |
199 | {"verbose", no_argument, NULL, 'v'}, | |
200 | {"version", no_argument, NULL, 'V'}, | |
201 | {NULL, 0, NULL, 0} | |
202 | }; | |
203 | int c; | |
204 | ||
205 | while ((c = getopt_long(argc, argv, | |
206 | "a:c:i:x:dfFhnqrstTXvVpmM", long_opts, NULL)) != -1) { | |
207 | switch (c) { | |
208 | case 'a': | |
209 | ctl->a_option = 1; | |
210 | ctl->a_arg = ul_parse_switch(optarg, "on", "off", "1", "0", NULL); | |
211 | break; | |
212 | case 'c': | |
213 | ctl->c_option = 1; | |
214 | ctl->c_arg = strtoul_or_err(optarg, _("invalid argument to --changerslot/-c option")); | |
215 | break; | |
216 | case 'x': | |
217 | ctl->x_option = 1; | |
218 | ctl->x_arg = strtoul_or_err(optarg, _("invalid argument to --cdspeed/-x option")); | |
219 | break; | |
220 | case 'd': | |
221 | ctl->d_option = 1; | |
222 | break; | |
223 | case 'f': | |
224 | ctl->f_option = 1; | |
225 | break; | |
226 | case 'F': | |
227 | ctl->F_option = 1; | |
228 | break; | |
229 | case 'i': | |
230 | ctl->i_option = 1; | |
231 | ctl->i_arg = ul_parse_switch(optarg, "on", "off", "1", "0", NULL); | |
232 | break; | |
233 | case 'm': | |
234 | ctl->m_option = 1; | |
235 | break; | |
236 | case 'M': | |
237 | ctl->M_option = 1; | |
238 | break; | |
239 | case 'n': | |
240 | ctl->n_option = 1; | |
241 | break; | |
242 | case 'p': | |
243 | ctl->p_option = 1; | |
244 | break; | |
245 | case 'q': | |
246 | ctl->q_option = 1; | |
247 | break; | |
248 | case 'r': | |
249 | ctl->r_option = 1; | |
250 | break; | |
251 | case 's': | |
252 | ctl->s_option = 1; | |
253 | break; | |
254 | case 't': | |
255 | ctl->t_option = 1; | |
256 | break; | |
257 | case 'T': | |
258 | ctl->T_option = 1; | |
259 | break; | |
260 | case 'X': | |
261 | ctl->X_option = 1; | |
262 | break; | |
263 | case 'v': | |
264 | ctl->v_option = 1; | |
265 | break; | |
266 | ||
267 | case 'h': | |
268 | usage(); | |
269 | case 'V': | |
270 | print_version(EXIT_SUCCESS); | |
271 | default: | |
272 | errtryhelp(EXIT_FAILURE); | |
273 | break; | |
274 | } | |
275 | } | |
276 | ||
277 | /* check for a single additional argument */ | |
278 | if ((argc - optind) > 1) | |
279 | errx(EXIT_FAILURE, _("too many arguments")); | |
280 | ||
281 | if ((argc - optind) == 1) | |
282 | ctl->device = xstrdup(argv[optind]); | |
283 | } | |
284 | ||
285 | /* | |
286 | * Given name, such as foo, see if any of the following exist: | |
287 | * | |
288 | * foo (if foo starts with '.' or '/') | |
289 | * /dev/foo | |
290 | * | |
291 | * If found, return the full path. If not found, return 0. | |
292 | * Returns pointer to dynamically allocated string. | |
293 | */ | |
294 | static char *find_device(const char *name) | |
295 | { | |
296 | if (!name) | |
297 | return NULL; | |
298 | ||
299 | if ((*name == '.' || *name == '/') && access(name, F_OK) == 0) | |
300 | return xstrdup(name); | |
301 | ||
302 | char buf[PATH_MAX]; | |
303 | ||
304 | snprintf(buf, sizeof(buf), "/dev/%s", name); | |
305 | if (access(buf, F_OK) == 0) | |
306 | return xstrdup(buf); | |
307 | ||
308 | return NULL; | |
309 | } | |
310 | ||
311 | /* Set or clear auto-eject mode. */ | |
312 | static void auto_eject(const struct eject_control *ctl) | |
313 | { | |
314 | int status = -1; | |
315 | ||
316 | #if defined(CDROM_SET_OPTIONS) && defined(CDROM_CLEAR_OPTIONS) | |
317 | if (ctl->a_arg) | |
318 | status = ioctl(ctl->fd, CDROM_SET_OPTIONS, CDO_AUTO_EJECT); | |
319 | else | |
320 | status = ioctl(ctl->fd, CDROM_CLEAR_OPTIONS, CDO_AUTO_EJECT); | |
321 | #else | |
322 | errno = ENOSYS; | |
323 | #endif | |
324 | if (status < 0) | |
325 | err(EXIT_FAILURE,_("CD-ROM auto-eject command failed")); | |
326 | } | |
327 | ||
328 | /* | |
329 | * Stops CDROM from opening on manual eject button press. | |
330 | * This can be useful when you carry your laptop | |
331 | * in your bag while it's on and no CD is inserted in its tray. | |
332 | * Implemented as found in Documentation/userspace-api/ioctl/cdrom.rst | |
333 | */ | |
334 | static void manual_eject(const struct eject_control *ctl) | |
335 | { | |
336 | if (ioctl(ctl->fd, CDROM_LOCKDOOR, ctl->i_arg) < 0) { | |
337 | switch (errno) { | |
338 | case EDRIVE_CANT_DO_THIS: | |
339 | errx(EXIT_FAILURE, _("CD-ROM door lock is not supported")); | |
340 | case EBUSY: | |
341 | errx(EXIT_FAILURE, _("other users have the drive open and not CAP_SYS_ADMIN")); | |
342 | default: | |
343 | err(EXIT_FAILURE, _("CD-ROM lock door command failed")); | |
344 | } | |
345 | } | |
346 | ||
347 | if (ctl->i_arg) | |
348 | info(_("CD-Drive may NOT be ejected with device button")); | |
349 | else | |
350 | info(_("CD-Drive may be ejected with device button")); | |
351 | } | |
352 | ||
353 | /* | |
354 | * Changer select. CDROM_SELECT_DISC is preferred, older kernels used | |
355 | * CDROMLOADFROMSLOT. | |
356 | */ | |
357 | static void changer_select(const struct eject_control *ctl) | |
358 | { | |
359 | #ifdef CDROM_SELECT_DISC | |
360 | if (ioctl(ctl->fd, CDROM_SELECT_DISC, ctl->c_arg) < 0) | |
361 | err(EXIT_FAILURE, _("CD-ROM select disc command failed")); | |
362 | ||
363 | #elif defined CDROMLOADFROMSLOT | |
364 | if (ioctl(ctl->fd, CDROMLOADFROMSLOT, ctl->c_arg) != 0) | |
365 | err(EXIT_FAILURE, _("CD-ROM load from slot command failed")); | |
366 | #else | |
367 | warnx(_("IDE/ATAPI CD-ROM changer not supported by this kernel\n") ); | |
368 | #endif | |
369 | } | |
370 | ||
371 | /* | |
372 | * Close tray. Not supported by older kernels. | |
373 | */ | |
374 | static void close_tray(int fd) | |
375 | { | |
376 | int status; | |
377 | ||
378 | #if defined(CDROMCLOSETRAY) || defined(CDIOCCLOSE) | |
379 | #if defined(CDROMCLOSETRAY) | |
380 | status = ioctl(fd, CDROMCLOSETRAY); | |
381 | #elif defined(CDIOCCLOSE) | |
382 | status = ioctl(fd, CDIOCCLOSE); | |
383 | #endif | |
384 | if (status != 0) | |
385 | err(EXIT_FAILURE, _("CD-ROM tray close command failed")); | |
386 | #else | |
387 | warnx(_("CD-ROM tray close command not supported by this kernel\n")); | |
388 | #endif | |
389 | } | |
390 | ||
391 | /* | |
392 | * Eject using CDROMEJECT ioctl. | |
393 | */ | |
394 | static int eject_cdrom(int fd) | |
395 | { | |
396 | #if defined(CDROMEJECT) | |
397 | int ret = ioctl(fd, CDROM_LOCKDOOR, 0); | |
398 | if (ret < 0) | |
399 | return 0; | |
400 | return ioctl(fd, CDROMEJECT) >= 0; | |
401 | #elif defined(CDIOCEJECT) | |
402 | return ioctl(fd, CDIOCEJECT) >= 0; | |
403 | #else | |
404 | warnx(_("CD-ROM eject unsupported")); | |
405 | errno = ENOSYS; | |
406 | return 0; | |
407 | #endif | |
408 | } | |
409 | ||
410 | /* | |
411 | * Toggle tray. | |
412 | * | |
413 | * Written by Benjamin Schwenk <benjaminschwenk@yahoo.de> and | |
414 | * Sybren Stuvel <sybren@thirdtower.com> | |
415 | * | |
416 | * Not supported by older kernels because it might use | |
417 | * CloseTray(). | |
418 | * | |
419 | */ | |
420 | static void toggle_tray(int fd) | |
421 | { | |
422 | #ifdef CDROM_DRIVE_STATUS | |
423 | /* First ask the CDROM for info, otherwise fall back to manual. */ | |
424 | switch (ioctl(fd, CDROM_DRIVE_STATUS)) { | |
425 | case CDS_TRAY_OPEN: | |
426 | close_tray(fd); | |
427 | return; | |
428 | ||
429 | case CDS_NO_DISC: | |
430 | case CDS_DISC_OK: | |
431 | if (!eject_cdrom(fd)) | |
432 | err(EXIT_FAILURE, _("CD-ROM eject command failed")); | |
433 | return; | |
434 | case CDS_NO_INFO: | |
435 | warnx(_("no CD-ROM information available")); | |
436 | return; | |
437 | case CDS_DRIVE_NOT_READY: | |
438 | warnx(_("CD-ROM drive is not ready")); | |
439 | return; | |
440 | default: | |
441 | err(EXIT_FAILURE, _("CD-ROM status command failed")); | |
442 | } | |
443 | #else | |
444 | struct timeval time_start, time_stop; | |
445 | int time_elapsed; | |
446 | ||
447 | /* Try to open the CDROM tray and measure the time therefore | |
448 | * needed. In my experience the function needs less than 0.05 | |
449 | * seconds if the tray was already open, and at least 1.5 seconds | |
450 | * if it was closed. */ | |
451 | gettime_monotonic(&time_start); | |
452 | ||
453 | /* Send the CDROMEJECT command to the device. */ | |
454 | if (!eject_cdrom(fd)) | |
455 | err(EXIT_FAILURE, _("CD-ROM eject command failed")); | |
456 | ||
457 | /* Get the second timestamp, to measure the time needed to open | |
458 | * the tray. */ | |
459 | gettime_monotonic(&time_stop); | |
460 | ||
461 | time_elapsed = (time_stop.tv_sec * 1000000 + time_stop.tv_usec) - | |
462 | (time_start.tv_sec * 1000000 + time_start.tv_usec); | |
463 | ||
464 | /* If the tray "opened" too fast, we can be nearly sure, that it | |
465 | * was already open. In this case, close it now. Else the tray was | |
466 | * closed before. This would mean that we are done. */ | |
467 | if (time_elapsed < TRAY_WAS_ALREADY_OPEN_USECS) | |
468 | close_tray(fd); | |
469 | #endif | |
470 | } | |
471 | ||
472 | /* | |
473 | * Select Speed of CD-ROM drive. | |
474 | * Thanks to Roland Krivanek (krivanek@fmph.uniba.sk) | |
475 | * http://dmpc.dbp.fmph.uniba.sk/~krivanek/cdrom_speed/ | |
476 | */ | |
477 | static void select_speed(const struct eject_control *ctl) | |
478 | { | |
479 | #ifdef CDROM_SELECT_SPEED | |
480 | if (ioctl(ctl->fd, CDROM_SELECT_SPEED, ctl->x_arg) != 0) | |
481 | err(EXIT_FAILURE, _("CD-ROM select speed command failed")); | |
482 | #else | |
483 | warnx(_("CD-ROM select speed command not supported by this kernel")); | |
484 | #endif | |
485 | } | |
486 | ||
487 | /* | |
488 | * Read Speed of CD-ROM drive. From Linux 2.6.13, the current speed | |
489 | * is correctly reported | |
490 | */ | |
491 | static int read_speed(const char *devname) | |
492 | { | |
493 | int drive_number = -1; | |
494 | char *name; | |
495 | FILE *f; | |
496 | ||
497 | f = fopen(_PATH_PROC_CDROMINFO, "r"); | |
498 | if (!f) | |
499 | err(EXIT_FAILURE, _("cannot open %s"), _PATH_PROC_CDROMINFO); | |
500 | ||
501 | name = strrchr(devname, '/') + 1; | |
502 | ||
503 | while (name && !feof(f)) { | |
504 | char line[512]; | |
505 | char *str; | |
506 | ||
507 | if (!fgets(line, sizeof(line), f)) | |
508 | break; | |
509 | ||
510 | /* find drive number in line "drive name" */ | |
511 | if (drive_number == -1) { | |
512 | if (strncmp(line, "drive name:", 11) == 0) { | |
513 | str = strtok(&line[11], "\t "); | |
514 | drive_number = 0; | |
515 | while (str && strncmp(name, str, strlen(name)) != 0) { | |
516 | drive_number++; | |
517 | str = strtok(NULL, "\t "); | |
518 | if (!str) | |
519 | errx(EXIT_FAILURE, | |
520 | _("%s: failed to finding CD-ROM name"), | |
521 | _PATH_PROC_CDROMINFO); | |
522 | } | |
523 | } | |
524 | /* find line "drive speed" and read the correct speed */ | |
525 | } else { | |
526 | if (strncmp(line, "drive speed:", 12) == 0) { | |
527 | int n; | |
528 | ||
529 | fclose(f); | |
530 | ||
531 | str = line + 12; | |
532 | normalize_whitespace((unsigned char *) str); | |
533 | ||
534 | if (ul_strtos32(str, &n, 10) == 0) | |
535 | return n; | |
536 | ||
537 | errx(EXIT_FAILURE, _("%s: failed to read speed"), | |
538 | _PATH_PROC_CDROMINFO); | |
539 | } | |
540 | } | |
541 | } | |
542 | ||
543 | errx(EXIT_FAILURE, _("failed to read speed")); | |
544 | } | |
545 | ||
546 | /* | |
547 | * List Speed of CD-ROM drive. | |
548 | */ | |
549 | static void list_speeds(struct eject_control *ctl) | |
550 | { | |
551 | int max_speed, curr_speed = 0; | |
552 | ||
553 | select_speed(ctl); | |
554 | max_speed = read_speed(ctl->device); | |
555 | ||
556 | while (curr_speed < max_speed) { | |
557 | ctl->x_arg = curr_speed + 1; | |
558 | select_speed(ctl); | |
559 | curr_speed = read_speed(ctl->device); | |
560 | if (ctl->x_arg < curr_speed) | |
561 | printf("%d ", curr_speed); | |
562 | else | |
563 | curr_speed = ctl->x_arg + 1; | |
564 | } | |
565 | ||
566 | printf("\n"); | |
567 | } | |
568 | ||
569 | /* | |
570 | * Eject using SCSI SG_IO commands. Return 1 if successful, 0 otherwise. | |
571 | */ | |
572 | static int eject_scsi(const struct eject_control *ctl) | |
573 | { | |
574 | int status, k; | |
575 | sg_io_hdr_t io_hdr; | |
576 | unsigned char allowRmBlk[6] = {ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0}; | |
577 | unsigned char startStop1Blk[6] = {START_STOP, 0, 0, 0, 1, 0}; | |
578 | unsigned char startStop2Blk[6] = {START_STOP, 0, 0, 0, 2, 0}; | |
579 | unsigned char inqBuff[2]; | |
580 | unsigned char sense_buffer[32]; | |
581 | ||
582 | if ((ioctl(ctl->fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { | |
583 | verbose(ctl, _("not an sg device, or old sg driver")); | |
584 | return 0; | |
585 | } | |
586 | ||
587 | memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); | |
588 | io_hdr.interface_id = 'S'; | |
589 | io_hdr.cmd_len = 6; | |
590 | io_hdr.mx_sb_len = sizeof(sense_buffer); | |
591 | io_hdr.dxfer_direction = SG_DXFER_NONE; | |
592 | io_hdr.dxfer_len = 0; | |
593 | io_hdr.dxferp = inqBuff; | |
594 | io_hdr.sbp = sense_buffer; | |
595 | io_hdr.timeout = 10000; | |
596 | ||
597 | io_hdr.cmdp = allowRmBlk; | |
598 | status = ioctl(ctl->fd, SG_IO, (void *)&io_hdr); | |
599 | if (status < 0 || io_hdr.host_status || io_hdr.driver_status) | |
600 | return 0; | |
601 | ||
602 | io_hdr.cmdp = startStop1Blk; | |
603 | status = ioctl(ctl->fd, SG_IO, (void *)&io_hdr); | |
604 | if (status < 0 || io_hdr.host_status) | |
605 | return 0; | |
606 | ||
607 | /* Ignore errors when there is not medium -- in this case driver sense | |
608 | * buffer sets MEDIUM NOT PRESENT (3a) bit. For more details see: | |
609 | * http://www.tldp.org/HOWTO/archived/SCSI-Programming-HOWTO/SCSI-Programming-HOWTO-22.html#sec-sensecodes | |
610 | * -- kzak Jun 2013 | |
611 | */ | |
612 | if (io_hdr.driver_status != 0 && | |
613 | !(io_hdr.driver_status == DRIVER_SENSE && io_hdr.sbp && | |
614 | io_hdr.sbp[12] == 0x3a)) | |
615 | return 0; | |
616 | ||
617 | io_hdr.cmdp = startStop2Blk; | |
618 | status = ioctl(ctl->fd, SG_IO, (void *)&io_hdr); | |
619 | if (status < 0 || io_hdr.host_status || io_hdr.driver_status) | |
620 | return 0; | |
621 | ||
622 | /* force kernel to reread partition table when new disc inserted */ | |
623 | ioctl(ctl->fd, BLKRRPART); | |
624 | return 1; | |
625 | } | |
626 | ||
627 | /* | |
628 | * Eject using FDEJECT ioctl. Return 1 if successful, 0 otherwise. | |
629 | */ | |
630 | static int eject_floppy(int fd) | |
631 | { | |
632 | return ioctl(fd, FDEJECT) >= 0; | |
633 | } | |
634 | ||
635 | ||
636 | /* | |
637 | * Rewind and eject using tape ioctl. Return 1 if successful, 0 otherwise. | |
638 | */ | |
639 | static int eject_tape(int fd) | |
640 | { | |
641 | struct mtop op = { .mt_op = MTOFFL, .mt_count = 0 }; | |
642 | ||
643 | return ioctl(fd, MTIOCTOP, &op) >= 0; | |
644 | } | |
645 | ||
646 | ||
647 | /* umount a device. */ | |
648 | static void umount_one(const struct eject_control *ctl, const char *name) | |
649 | { | |
650 | int status; | |
651 | ||
652 | if (!name) | |
653 | return; | |
654 | ||
655 | verbose(ctl, _("%s: unmounting"), name); | |
656 | ||
657 | switch (fork()) { | |
658 | case 0: /* child */ | |
659 | if (drop_permissions() != 0) | |
660 | err(EXIT_FAILURE, _("drop permissions failed")); | |
661 | if (ctl->p_option) | |
662 | execl("/bin/umount", "/bin/umount", name, "-n", (char *)NULL); | |
663 | else | |
664 | execl("/bin/umount", "/bin/umount", name, (char *)NULL); | |
665 | ||
666 | errexec("/bin/umount"); | |
667 | ||
668 | case -1: | |
669 | warn( _("unable to fork")); | |
670 | break; | |
671 | ||
672 | default: /* parent */ | |
673 | wait(&status); | |
674 | if (WIFEXITED(status) == 0) | |
675 | errx(EXIT_FAILURE, | |
676 | _("unmount of `%s' did not exit normally"), name); | |
677 | ||
678 | if (WEXITSTATUS(status) != 0) | |
679 | errx(EXIT_FAILURE, _("unmount of `%s' failed\n"), name); | |
680 | break; | |
681 | } | |
682 | } | |
683 | ||
684 | /* Open a device file. */ | |
685 | static void open_device(struct eject_control *ctl) | |
686 | { | |
687 | int extra = ctl->F_option == 0 && /* never use O_EXCL on --force */ | |
688 | ctl->force_exclusive ? O_EXCL : 0; | |
689 | ||
690 | ctl->fd = open(ctl->device, O_RDWR | O_NONBLOCK | extra); | |
691 | if (ctl->fd < 0) | |
692 | ctl->fd = open(ctl->device, O_RDONLY | O_NONBLOCK | extra); | |
693 | if (ctl->fd == -1) | |
694 | err(EXIT_FAILURE, _("cannot open %s"), ctl->device); | |
695 | } | |
696 | ||
697 | /* | |
698 | * See if device has been mounted by looking in mount table. If so, set | |
699 | * device name and mount point name, and return 1, otherwise return 0. | |
700 | */ | |
701 | static int device_get_mountpoint(struct eject_control *ctl, char **devname, char **mnt) | |
702 | { | |
703 | struct libmnt_fs *fs; | |
704 | int rc; | |
705 | ||
706 | *mnt = NULL; | |
707 | ||
708 | if (!ctl->mtab) { | |
709 | struct libmnt_cache *cache; | |
710 | ||
711 | ctl->mtab = mnt_new_table(); | |
712 | if (!ctl->mtab) | |
713 | err(EXIT_FAILURE, _("failed to initialize libmount table")); | |
714 | ||
715 | cache = mnt_new_cache(); | |
716 | mnt_table_set_cache(ctl->mtab, cache); | |
717 | mnt_unref_cache(cache); | |
718 | ||
719 | if (ctl->p_option) | |
720 | rc = mnt_table_parse_file(ctl->mtab, _PATH_PROC_MOUNTINFO); | |
721 | else | |
722 | rc = mnt_table_parse_mtab(ctl->mtab, NULL); | |
723 | if (rc) | |
724 | err(EXIT_FAILURE, _("failed to parse mount table")); | |
725 | } | |
726 | ||
727 | fs = mnt_table_find_source(ctl->mtab, *devname, MNT_ITER_BACKWARD); | |
728 | if (!fs) { | |
729 | /* maybe 'devname' is mountpoint rather than a real device */ | |
730 | fs = mnt_table_find_target(ctl->mtab, *devname, MNT_ITER_BACKWARD); | |
731 | if (fs) { | |
732 | free(*devname); | |
733 | *devname = xstrdup(mnt_fs_get_source(fs)); | |
734 | } | |
735 | } | |
736 | ||
737 | if (fs) | |
738 | *mnt = xstrdup(mnt_fs_get_target(fs)); | |
739 | return *mnt ? 0 : -1; | |
740 | } | |
741 | ||
742 | static char *get_disk_devname(const char *device) | |
743 | { | |
744 | struct stat st; | |
745 | dev_t diskno = 0; | |
746 | char diskname[128]; | |
747 | ||
748 | if (stat(device, &st) != 0) | |
749 | return NULL; | |
750 | ||
751 | /* get whole-disk devno */ | |
752 | if (sysfs_devno_to_wholedisk(st.st_rdev, diskname, | |
753 | sizeof(diskname), &diskno) != 0) | |
754 | return NULL; | |
755 | ||
756 | return st.st_rdev == diskno ? NULL : find_device(diskname); | |
757 | } | |
758 | ||
759 | /* umount all partitions if -M not specified, otherwise returns | |
760 | * number of the mounted partitions only. | |
761 | */ | |
762 | static int umount_partitions(struct eject_control *ctl) | |
763 | { | |
764 | struct path_cxt *pc = NULL; | |
765 | dev_t devno; | |
766 | DIR *dir = NULL; | |
767 | struct dirent *d; | |
768 | int count = 0; | |
769 | ||
770 | devno = sysfs_devname_to_devno(ctl->device); | |
771 | if (devno) | |
772 | pc = ul_new_sysfs_path(devno, NULL, NULL); | |
773 | if (!pc) | |
774 | return 0; | |
775 | ||
776 | /* open /sys/block/<wholedisk> */ | |
777 | if (!(dir = ul_path_opendir(pc, NULL))) | |
778 | goto done; | |
779 | ||
780 | /* scan for partition subdirs */ | |
781 | while ((d = readdir(dir))) { | |
782 | if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) | |
783 | continue; | |
784 | ||
785 | if (sysfs_blkdev_is_partition_dirent(dir, d, ctl->device)) { | |
786 | char *mnt = NULL; | |
787 | char *dev = find_device(d->d_name); | |
788 | ||
789 | if (dev && device_get_mountpoint(ctl, &dev, &mnt) == 0) { | |
790 | verbose(ctl, _("%s: mounted on %s"), dev, mnt); | |
791 | if (!ctl->M_option) | |
792 | umount_one(ctl, mnt); | |
793 | count++; | |
794 | } | |
795 | free(dev); | |
796 | free(mnt); | |
797 | } | |
798 | } | |
799 | ||
800 | done: | |
801 | if (dir) | |
802 | closedir(dir); | |
803 | ul_unref_path(pc); | |
804 | ||
805 | return count; | |
806 | } | |
807 | ||
808 | static int is_ejectable(const struct eject_control *ctl) | |
809 | { | |
810 | struct path_cxt *pc = NULL; | |
811 | dev_t devno; | |
812 | int rc = 0; | |
813 | ||
814 | devno = sysfs_devname_to_devno(ctl->device); | |
815 | if (devno) | |
816 | pc = ul_new_sysfs_path(devno, NULL, NULL); | |
817 | if (!pc) | |
818 | return 0; | |
819 | ||
820 | rc = sysfs_blkdev_is_hotpluggable(pc) || sysfs_blkdev_is_removable(pc); | |
821 | ul_unref_path(pc); | |
822 | return rc; | |
823 | } | |
824 | ||
825 | ||
826 | /* handle -x option */ | |
827 | static void set_device_speed(struct eject_control *ctl) | |
828 | { | |
829 | if (!ctl->x_option) | |
830 | return; | |
831 | ||
832 | if (ctl->x_arg == 0) | |
833 | verbose(ctl, _("setting CD-ROM speed to auto")); | |
834 | else | |
835 | verbose(ctl, _("setting CD-ROM speed to %ldX"), ctl->x_arg); | |
836 | ||
837 | open_device(ctl); | |
838 | select_speed(ctl); | |
839 | exit(EXIT_SUCCESS); | |
840 | } | |
841 | ||
842 | ||
843 | /* main program */ | |
844 | int main(int argc, char **argv) | |
845 | { | |
846 | char *disk = NULL; | |
847 | char *mountpoint = NULL; | |
848 | int worked = 0; /* set to 1 when successfully ejected */ | |
849 | struct eject_control ctl = { .fd = -1 }; | |
850 | ||
851 | setlocale(LC_ALL,""); | |
852 | bindtextdomain(PACKAGE, LOCALEDIR); | |
853 | textdomain(PACKAGE); | |
854 | close_stdout_atexit(); | |
855 | ||
856 | /* parse the command line arguments */ | |
857 | parse_args(&ctl, argc, argv); | |
858 | ||
859 | /* handle -d option */ | |
860 | if (ctl.d_option) { | |
861 | info(_("default device: `%s'"), EJECT_DEFAULT_DEVICE); | |
862 | return EXIT_SUCCESS; | |
863 | } | |
864 | ||
865 | if (!ctl.device) { | |
866 | ctl.device = mnt_resolve_path(EJECT_DEFAULT_DEVICE, NULL); | |
867 | verbose(&ctl, _("using default device `%s'"), ctl.device); | |
868 | } else { | |
869 | char *p; | |
870 | ||
871 | if (ctl.device[strlen(ctl.device) - 1] == '/') | |
872 | ctl.device[strlen(ctl.device) - 1] = '\0'; | |
873 | ||
874 | /* figure out full device or mount point name */ | |
875 | p = find_device(ctl.device); | |
876 | if (p) | |
877 | free(ctl.device); | |
878 | else | |
879 | p = ctl.device; | |
880 | ||
881 | ctl.device = mnt_resolve_spec(p, NULL); | |
882 | free(p); | |
883 | } | |
884 | ||
885 | if (!ctl.device) | |
886 | errx(EXIT_FAILURE, _("unable to find device")); | |
887 | ||
888 | verbose(&ctl, _("device name is `%s'"), ctl.device); | |
889 | ||
890 | device_get_mountpoint(&ctl, &ctl.device, &mountpoint); | |
891 | if (mountpoint) | |
892 | verbose(&ctl, _("%s: mounted on %s"), ctl.device, mountpoint); | |
893 | else | |
894 | verbose(&ctl, _("%s: not mounted"), ctl.device); | |
895 | ||
896 | disk = get_disk_devname(ctl.device); | |
897 | if (disk) { | |
898 | verbose(&ctl, _("%s: disc device: %s (disk device will be used for eject)"), ctl.device, disk); | |
899 | free(ctl.device); | |
900 | ctl.device = disk; | |
901 | disk = NULL; | |
902 | } else { | |
903 | struct stat st; | |
904 | ||
905 | if (stat(ctl.device, &st) != 0 || !S_ISBLK(st.st_mode)) | |
906 | errx(EXIT_FAILURE, _("%s: not found mountpoint or device " | |
907 | "with the given name"), ctl.device); | |
908 | ||
909 | verbose(&ctl, _("%s: is whole-disk device"), ctl.device); | |
910 | } | |
911 | ||
912 | if (ctl.F_option == 0 && is_ejectable(&ctl) == 0) | |
913 | errx(EXIT_FAILURE, _("%s: is not ejectable device"), ctl.device); | |
914 | ||
915 | /* handle -n option */ | |
916 | if (ctl.n_option) { | |
917 | info(_("device is `%s'"), ctl.device); | |
918 | verbose(&ctl, _("exiting due to -n/--noop option")); | |
919 | goto done; | |
920 | } | |
921 | ||
922 | /* handle -i option */ | |
923 | if (ctl.i_option) { | |
924 | open_device(&ctl); | |
925 | manual_eject(&ctl); | |
926 | goto done; | |
927 | } | |
928 | ||
929 | /* handle -a option */ | |
930 | if (ctl.a_option) { | |
931 | if (ctl.a_arg) | |
932 | verbose(&ctl, _("%s: enabling auto-eject mode"), ctl.device); | |
933 | else | |
934 | verbose(&ctl, _("%s: disabling auto-eject mode"), ctl.device); | |
935 | open_device(&ctl); | |
936 | auto_eject(&ctl); | |
937 | goto done; | |
938 | } | |
939 | ||
940 | /* handle -t option */ | |
941 | if (ctl.t_option) { | |
942 | verbose(&ctl, _("%s: closing tray"), ctl.device); | |
943 | open_device(&ctl); | |
944 | close_tray(ctl.fd); | |
945 | set_device_speed(&ctl); | |
946 | goto done; | |
947 | } | |
948 | ||
949 | /* handle -T option */ | |
950 | if (ctl.T_option) { | |
951 | verbose(&ctl, _("%s: toggling tray"), ctl.device); | |
952 | open_device(&ctl); | |
953 | toggle_tray(ctl.fd); | |
954 | set_device_speed(&ctl); | |
955 | goto done; | |
956 | } | |
957 | ||
958 | /* handle -X option */ | |
959 | if (ctl.X_option) { | |
960 | verbose(&ctl, _("%s: listing CD-ROM speed"), ctl.device); | |
961 | open_device(&ctl); | |
962 | list_speeds(&ctl); | |
963 | goto done; | |
964 | } | |
965 | ||
966 | /* handle -x option only */ | |
967 | if (!ctl.c_option) | |
968 | set_device_speed(&ctl); | |
969 | ||
970 | ||
971 | /* | |
972 | * Unmount all partitions if -m is not specified; or umount given | |
973 | * mountpoint if -M is specified, otherwise print error of another | |
974 | * partition is mounted. | |
975 | */ | |
976 | if (!ctl.m_option) { | |
977 | int ct = umount_partitions(&ctl); /* umount all, or count mounted on -M */ | |
978 | ||
979 | if (ct == 0 && mountpoint) | |
980 | umount_one(&ctl, mountpoint); /* probably whole-device */ | |
981 | ||
982 | if (ctl.M_option) { | |
983 | if (ct == 1 && mountpoint) | |
984 | umount_one(&ctl, mountpoint); | |
985 | else if (ct) | |
986 | errx(EXIT_FAILURE, _("error: %s: device in use"), ctl.device); | |
987 | } | |
988 | /* Now, we assume the device is no more used, use O_EXCL to be | |
989 | * resistant against our bugs and possible races (someone else | |
990 | * remounted the device). | |
991 | */ | |
992 | ctl.force_exclusive = 1; | |
993 | } | |
994 | ||
995 | /* handle -c option */ | |
996 | if (ctl.c_option) { | |
997 | verbose(&ctl, _("%s: selecting CD-ROM disc #%ld"), ctl.device, ctl.c_arg); | |
998 | open_device(&ctl); | |
999 | changer_select(&ctl); | |
1000 | set_device_speed(&ctl); | |
1001 | goto done; | |
1002 | } | |
1003 | ||
1004 | /* if user did not specify type of eject, try all four methods */ | |
1005 | if (ctl.r_option + ctl.s_option + ctl.f_option + ctl.q_option == 0) | |
1006 | ctl.r_option = ctl.s_option = ctl.f_option = ctl.q_option = 1; | |
1007 | ||
1008 | /* open device */ | |
1009 | open_device(&ctl); | |
1010 | ||
1011 | /* try various methods of ejecting until it works */ | |
1012 | if (ctl.r_option) { | |
1013 | verbose(&ctl, _("%s: trying to eject using CD-ROM eject command"), ctl.device); | |
1014 | worked = eject_cdrom(ctl.fd); | |
1015 | verbose(&ctl, worked ? _("CD-ROM eject command succeeded") : | |
1016 | _("CD-ROM eject command failed")); | |
1017 | } | |
1018 | ||
1019 | if (ctl.s_option && !worked) { | |
1020 | verbose(&ctl, _("%s: trying to eject using SCSI commands"), ctl.device); | |
1021 | worked = eject_scsi(&ctl); | |
1022 | verbose(&ctl, worked ? _("SCSI eject succeeded") : | |
1023 | _("SCSI eject failed")); | |
1024 | } | |
1025 | ||
1026 | if (ctl.f_option && !worked) { | |
1027 | verbose(&ctl, _("%s: trying to eject using floppy eject command"), ctl.device); | |
1028 | worked = eject_floppy(ctl.fd); | |
1029 | verbose(&ctl, worked ? _("floppy eject command succeeded") : | |
1030 | _("floppy eject command failed")); | |
1031 | } | |
1032 | ||
1033 | if (ctl.q_option && !worked) { | |
1034 | verbose(&ctl, _("%s: trying to eject using tape offline command"), ctl.device); | |
1035 | worked = eject_tape(ctl.fd); | |
1036 | verbose(&ctl, worked ? _("tape offline command succeeded") : | |
1037 | _("tape offline command failed")); | |
1038 | } | |
1039 | ||
1040 | if (!worked) | |
1041 | errx(EXIT_FAILURE, _("unable to eject")); | |
1042 | ||
1043 | done: | |
1044 | /* cleanup */ | |
1045 | if (ctl.fd >= 0) | |
1046 | close(ctl.fd); | |
1047 | ||
1048 | free(ctl.device); | |
1049 | free(mountpoint); | |
1050 | ||
1051 | mnt_unref_table(ctl.mtab); | |
1052 | ||
1053 | return EXIT_SUCCESS; | |
1054 | } |