-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathdvd_eject.c
More file actions
521 lines (414 loc) · 12.6 KB
/
Copy pathdvd_eject.c
File metadata and controls
521 lines (414 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <getopt.h>
#include <mntent.h>
#include <dvdread/dvd_reader.h>
#include "dvd_device.h"
/**
* _ _ _ _
* __| |_ ____| | ___ (_) ___ ___| |_
* / _` \ \ / / _` | / _ \| |/ _ \/ __| __|
* | (_| |\ V / (_| | | __/| | __/ (__| |_
* \__,_| \_/ \__,_|___\___|/ |\___|\___|\__|
* |_____| |__/
*
*
* Opens / closes a disc tray, and decrypts the CSS on the drive to avoid
* any spatial anomalies (and hardware devices whining to the kernel about
* reading sectors without authentication). Requires libdvdread as a
* dependency library.
*
* Three return values: 0 on success, 1 on error, 2 on success and drive has
* a DVD inside of it.
*
* To build, one of the two:
* $ gcc -o dvd_eject dvd_eject.c -ldvdread
* $ gcc -o dvd_eject dvd_eject.c `pkg-config --libs --cflags dvdread`
*
* Usage:
* $ dvd_eject -h
*
* BUGS
*
* I found one race condition where if you close the tray (using dvd_eject -t),
* have a trigger mount it when udev fires one, and immediately try to eject
* it again, it will fail. Unlocking the tray seems to be a requirement before
* it can send a command to open it. So, if you have a sequence like this, it
* will try, but fail: 1) close tray 2) mount disc 3) immediately eject. I
* believe the reason is that the drive status will report whether its ready or
* not, regardless of it being in the process of mounted. The is_mounted check
* will pass, and unmounting it passes fine as well, but unlocking the door
* will continue to fail. I haven't been able to find a fix for this yet.
* However, you shouldn't run into this bug unless you are debugging like me.
*
* Story mode:
*
* I've spent a lot of time trying to track down why the hardware will send
* errors to the systemlog like so:
*
* "Sense Key : Illegal Request [current]"
* "Add. Sense: Read of scrambled sector without authentication"
*
* I'm debugging them not because I care about the messages, but becaue if it
* happens enough, the drives will eventually lock up and be unable to process
* any more discs (until next reboot).
*
* I've narrowed down two things that help a lot to prevent them as best as I
* can:
*
* 1. Make as *few* calls as possible to the device to check on its status
* (this program does that -- it keeps it to a bare minimum)
* 2. Use libdvdcss to authenticate access to the DVD (through libdvdread)
*
* Those two combined seem to help in large amounts, but I've never found
* anything that is a sure fire solution.
*
* The basic logic of this program and reason for it is this: Opening and
* closing a disc tray is trivial. The issue this one works around, however,
* is that just because a tray is *closed*, does not mean that it is *ready* to
* acess the media in it. This can cause issues when you do something like
* "dvd_info /dev/dvd" and the tray is open. The tray will close by the call made
* through libdvdread / libdvdcss to access the device, but will fail because
* the device is not yet in a ready mode. So there's a gap between "closing" and
* "closed and ready". Fortunately, the Linux kernel allows for checking those
* states, and that's the core of the logic here.
*
* With that in mind, all this does is close or open the tray, wait until it is
* a "ready" state again, and then exits.
*
* You can optionally open the DVD with libdvdread as a final check.
*
* If anecdotal evidence has any value ... it works for me. :)
*
* Good luck, and here's to all the other DVD collectors / multimedia geeks out
* there! :D
*
*/
int drive_status(int dvd_fd);
bool has_media(int dvd_fd);
bool is_open(int dvd_fd);
bool is_ready(int dvd_fd);
int open_tray(int dvd_fd);
int close_tray(int dvd_fd);
int lock_door(int dvd_fd);
int unlock_door(int dvd_fd);
int8_t is_mounted(char *device_filename);
int main(int argc, char **argv) {
// Program name
uint8_t p_dvd_eject = 1;
uint8_t p_dvd_close = 0;
uint8_t open_close = 0;
bool d_help = false;
bool dvdread_scan = false;
// Device hardware
char device_filename[PATH_MAX];
int dvd_fd = -1;
char eject_str[PATH_MAX];
char umount_str[PATH_MAX];
// Drive status
bool dvd_drive_opened = false;
bool dvd_drive_has_media = false;
// Waiting
uint32_t sleepy_time = 1000000;
bool opt_wait = false;
int arg_wait_times = 15;
int times_waited = 0;
int retval = -1;
// getopt
int long_index = 0;
int opt = 0;
opterr = 1;
// Start dvd_eject
// Get options
struct option long_options[] = {
{ "close", no_argument, 0, 't' },
{ "dvdread", no_argument, 0, 'd' },
{ "system", no_argument, 0, 's' },
{ "wait", no_argument, 0, 'w' },
{ "help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
while((opt = getopt_long(argc, argv, "dtsjmwh", long_options, &long_index )) != -1) {
switch(opt) {
case 'd':
dvdread_scan = true;
break;
case 't':
open_close = 1;
break;
case 's':
p_dvd_eject = 2;
break;
case 'j':
open_close = 0;
p_dvd_eject = 1;
p_dvd_close = 0;
break;
case 'm':
p_dvd_close = 2;
break;
case 'w':
opt_wait = true;
break;
case 'h':
case '?':
d_help = true;
break;
case 0:
default:
break;
}
}
if(d_help) {
printf("dvd_eject - eject or close an optical drive\n");
printf("\n");
printf("Usage: dvd_eject [options] [device]\n");
printf("\n");
printf(" -j, --eject Eject tray (default)\n");
printf(" -t, --close Close tray\n");
printf(" -w, --wait Wait for device to be ready after closing tray\n");
printf(" -d, --dvdread Try to open with libdvdread after closing tray\n");
printf(" -s Use local 'eject' to open tray\n");
printf(" -m Use local 'eject' to close tray\n");
printf(" -h, --help Display this help output\n");
printf("\n");
printf("\nDefault device is %s\n", DEFAULT_DVD_DEVICE);
return 0;
}
// Start dvd_eject
memset(device_filename, '\0', PATH_MAX);
if(argv[optind])
strncpy(device_filename, argv[optind], PATH_MAX - 1);
else
strncpy(device_filename, DEFAULT_DVD_DEVICE, PATH_MAX - 1);
if(p_dvd_eject == 2 || p_dvd_close == 2) {
memset(eject_str, '\0', PATH_MAX);
if(p_dvd_eject)
snprintf(eject_str, PATH_MAX - 1, "eject %s", device_filename);
else if(p_dvd_close)
snprintf(eject_str, PATH_MAX - 1, "eject -t %s", device_filename);
retval = system(eject_str);
return retval;
}
dvd_fd = open(device_filename, O_RDONLY | O_NONBLOCK);
if(dvd_fd < 0) {
printf("Could not open device %s\n", device_filename);
return 1;
}
// Fetch status
if(drive_status(dvd_fd) < 0) {
printf("%s is not a DVD drive\n", device_filename);
close(dvd_fd);
return 1;
}
if(open_close == 0)
printf("[Open Drive Tray]\n");
else if(open_close == 1)
printf("[Close Drive Tray]\n");
printf("* Tracking %s status\n", device_filename);
printf("* Prepping crew, sir ...");
// Wait for the device to be ready before performing any actions
while(is_ready(dvd_fd) == false) {
printf(".");
usleep(sleepy_time);
times_waited += 1;
if(times_waited == arg_wait_times) {
printf("\n");
printf("* Waited %i, tired of waiting, trying workarounds\n", arg_wait_times);
printf("* Closing and reopening device\n");
if(close(dvd_fd) == 0)
printf("* Closing file descriptor worked\n");
else
printf("* Closing file descriptor failed, continuing anyway\n");
printf("* Reopening file descriptor\n");
dvd_fd = open(device_filename, O_RDONLY);
if(dvd_fd == 0) {
printf("* Closing and reopening file descriptor worked, checking if device is ready now\n");
if(is_ready(dvd_fd) == false) {
printf("* Drive still not marked as ready, quitting\n");
close(dvd_fd);
return 1;
}
printf("* Opening file descriptor failed, exiting\n");
return 1;
}
}
}
printf(" at your command!\n");
dvd_drive_opened = is_open(dvd_fd);
dvd_drive_has_media = has_media(dvd_fd);
if(dvd_drive_opened) {
printf("* Docking port status: open\n");
} else {
printf("* Docking port status: closed\n");
if(!dvd_drive_has_media)
printf("* Shuttle bay empty\n");
}
// Check for silly users
if(open_close && dvd_drive_opened) {
printf("* Are you sure you belong on the bridge, sir?\n");
close(dvd_fd);
return 0;
}
if(p_dvd_close && !dvd_drive_opened) {
if(has_media(dvd_fd) && dvdread_scan) {
printf("* Scanning Deck C section 55 for anomalies ... ");
dvd_reader_t *dvdread_dvd = NULL;
dvdread_dvd = DVDOpen(device_filename);
if(dvdread_dvd == NULL)
printf("red alert!!\n");
else
printf("all systems go!\n");
}
close(dvd_fd);
return 0;
}
// Open the tray as requested, if it is closed
if(p_dvd_eject && !dvd_drive_opened) {
bool device_mounted = is_mounted(device_filename);
printf("* Releasing docking clamps ...\n");
if(device_mounted) {
// We may be superuser, so try this first
retval = umount(device_filename);
if(retval != 0 && errno == EBUSY) {
printf("* The cargo bay is being accessed ... mission delayed.\n");
device_mounted = false;
}
// Try unmounting it using a system call
if(device_mounted) {
memset(umount_str, '\0', PATH_MAX);
snprintf(umount_str, PATH_MAX - 1, "umount %s", device_filename);
retval = system(umount_str);
// Ignore the system retval, check ourselves if it passed
if(is_mounted(device_filename)) {
printf("* Junior crew may have scraped the sides .. check for faulty mounts!\n");
}
}
}
// Ideally, opening the tray will unlock it as well. If not, try again
// after unlocking it directly.
printf("* Leaving space dock ...\n");
retval = open_tray(dvd_fd);
if(retval) {
retval = unlock_door(dvd_fd);
if(retval) {
printf("* Docking clamps could not be released!\n");
printf("* Taking off too soon? Disabling engines, try again in a few seconds.\n");
close(dvd_fd);
return 1;
}
retval = open_tray(dvd_fd);
if(retval) {
printf("* Door seems to be stuck ... \n");
close(dvd_fd);
printf("* One last try!\n");
memset(eject_str, '\0', PATH_MAX);
snprintf(eject_str, PATH_MAX - 1, "eject %s", device_filename);
retval = system(eject_str);
return retval;
}
}
printf("* Awaiting clearance for warp speed, captain ...");
if(opt_wait) {
while(!is_ready(dvd_fd)) {
printf(".");
usleep(sleepy_time);
}
}
// Do one last nap
usleep(sleepy_time);
printf(" engaging at warp 5.1 sir!\n");
close(dvd_fd);
return 0;
}
// Close the tray as requested, if it is open
if(p_dvd_close && dvd_drive_opened) {
printf("* Shuttle cleared for docking!\n");
retval = close_tray(dvd_fd);
if(retval != 0) {
printf("* Emergency cleanup on deck twelve! :(\n");
close(dvd_fd);
return 1;
}
if(opt_wait) {
while(!is_ready(dvd_fd)) {
printf("* Steady as she goes ...\n");
usleep(sleepy_time);
}
}
// Open DVD device
if(has_media(dvd_fd) && dvdread_scan) {
printf("* Scanning Deck C section 55 for anomalies ... ");
dvd_reader_t *dvdread_dvd = NULL;
dvdread_dvd = DVDOpen(device_filename);
if(dvdread_dvd == NULL)
printf("red alert!!\n");
else
printf("all systems go!\n");
}
printf("* Welcome to the station! Enjoy your stay.\n");
close(dvd_fd);
return 0;
}
close(dvd_fd);
return 0;
}
int drive_status(int dvd_fd) {
return ioctl(dvd_fd, CDROM_DRIVE_STATUS);
}
bool has_media(int dvd_fd) {
if(drive_status(dvd_fd) == CDS_DISC_OK)
return true;
else
return false;
}
bool is_open(int dvd_fd) {
if(drive_status(dvd_fd) == CDS_TRAY_OPEN)
return true;
else
return false;
}
bool is_ready(int dvd_fd) {
if(drive_status(dvd_fd) != CDS_DRIVE_NOT_READY)
return true;
else
return false;
}
int open_tray(int dvd_fd) {
return ioctl(dvd_fd, CDROMEJECT);
}
int close_tray(int dvd_fd) {
return ioctl(dvd_fd, CDROMCLOSETRAY);
}
int lock_door(int dvd_fd) {
return ioctl(dvd_fd, CDROM_LOCKDOOR, 1);
}
int unlock_door(int dvd_fd) {
return ioctl(dvd_fd, CDROM_LOCKDOOR, 0);
}
/**
* Check if a device is mounted or not.
* Returns 1 if true; 0 if false; -1 if it can't tell
* Used to unmount a drive before ejecting it
*/
int8_t is_mounted(char *device_filename) {
FILE *mtab = setmntent("/proc/mounts", "r");
if(mtab == NULL)
return -1;
struct mntent *mnt = getmntent(mtab);
while(mnt != NULL) {
if(strncmp(device_filename, mnt->mnt_fsname, strlen(device_filename)) == 0)
return 1;
mnt = getmntent(mtab);
}
return 0;
}