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