1 /*
2  *   xmcd - Motif(R) CD Audio Player/Ripper
3  *
4  *   Copyright (C) 1993-2004  Ti Kan
5  *   E-mail: xmcd@amb.org
6  *
7  *   This program is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU General Public License as published by
9  *   the Free Software Foundation; either version 2 of the License, or
10  *   (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with this program; if not, write to the Free Software
19  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  *
21  */
22 #ifndef lint
23 static char *_main_c_ident_ = "@(#)main.c	7.36 04/03/17";
24 #endif
25 
26 #include "common_d/appenv.h"
27 #include "common_d/version.h"
28 #include "common_d/util.h"
29 #include "xmcd_d/xmcd.h"
30 #include "xmcd_d/resource.h"
31 #include "xmcd_d/widget.h"
32 #include "xmcd_d/callback.h"
33 #include "xmcd_d/cdfunc.h"
34 #include "xmcd_d/command.h"
35 #include "libdi_d/libdi.h"
36 #include "cdda_d/cdda.h"
37 
38 
39 #define STARTUP_CMD_DELAY	1500	/* Delay interval before running
40 					 * startup command
41 					 */
42 
43 /* Global data */
44 char			*progname,	/* The path name we are invoked with */
45 			*cdisplay;	/* Command-line specified display */
46 appdata_t		app_data;	/* Options data */
47 widgets_t		widgets;	/* Holder of all widgets */
48 pixmaps_t		pixmaps;	/* Holder of all pixmaps */
49 FILE			*errfp;		/* Error message stream */
50 
51 /* Data global to this module only */
52 STATIC curstat_t	status;		/* Current CD player status */
53 STATIC XtAppContext	app_context;	/* Application context */
54 STATIC Colormap		localcmap;	/* local colormap */
55 STATIC char		*cmd;		/* Command string */
56 
57 
58 /***********************
59  *   public routines   *
60  ***********************/
61 
62 /*
63  * curstat_addr
64  *	Return the address of the curstat_t structure.
65  *
66  * Args:
67  *	Nothing.
68  *
69  * Return:
70  *	Nothing.
71  */
72 curstat_t *
73 curstat_addr(void)
74 {
75 	return (&status);
76 }
77 
78 
79 /*
80  * event_loop
81  *	Used to handle X events while waiting on I/O.
82  *
83  * Args:
84  *	flag - Currently unused
85  *
86  * Return:
87  *	Nothing.
88  */
89 /* ARGSUSED */
90 void
91 event_loop(int flag)
92 {
93 	while (XtAppPending(app_context))
94 		XtAppProcessEvent(app_context, XtIMAll);
95 }
96 
97 
98 /*
99  * shutdown_gui
100  *	Perform window system shutdown cleanup.
101  *
102  * Args:
103  *	None.
104  *
105  * Return:
106  *	Nothing.
107  */
108 void
109 shutdown_gui(void)
110 {
111 	if (app_data.instcmap)
112 		XFreeColormap(XtDisplay(widgets.toplevel), localcmap);
113 }
114 
115 
116 /***********************
117  *  internal routines  *
118  ***********************/
119 
120 
121 /*
122  * usage
123  *	Display command line usage syntax
124  *
125  * Args:
126  *	arg - The command line arg string that is invalid
127  *
128  * Return:
129  *	Nothing.
130  */
131 STATIC void
132 usage(char *arg)
133 {
134 	(void) fprintf(errfp,
135 		"%s %s.%s.%s Motif(R) CD player/ripper\n%s\n%s\n\n",
136 		PROGNAME, VERSION_MAJ, VERSION_MIN, VERSION_TEENY,
137 		COPYRIGHT, XMCD_URL
138 	);
139 
140 	if (strcmp(arg, "-help") != 0)
141 		(void) fprintf(errfp, "%s: %s\n\n", app_data.str_badopts, arg);
142 
143 	(void) fprintf(errfp, "Usage: %s %s %s %s\n            %s %s %s",
144 		PROGNAME,
145 		"[-dev device]",
146 		"[-instcmap]",
147 		"[-remote]",
148 		"[-rmthost hostname]",
149 		"[-debug level#]",
150 		"[-help]"
151 	);
152 
153 #ifdef _SOLARIS
154 	/* Solaris volume manager auto-start support */
155 	(void) fprintf(errfp, "\n            [-c device] [-X] [-o]");
156 #endif
157 
158  	(void) fprintf(errfp, "\n            [command [arg ...]]\n\n");
159 
160 	/* Display command usage */
161 	cmd_usage();
162 
163  	(void) fprintf(errfp,
164 	    "\nStandard Xt Intrinsics toolkit options are also supported.\n");
165 }
166 
167 
168 /*
169  * chk_resource_ver
170  *	Look for XMcd.version in the user's private X resource files
171  *	and check if there is a mismatch to this version of xmcd.
172  *	Display error message if mismatch found.
173  *
174  * Args:
175  *	dpy - The X display.
176  *
177  * Return:
178  *	FALSE - Mismatch found, or other error.
179  *	TRUE  - No mismatch found.
180  */
181 STATIC bool_t
182 chk_resource_ver(Display *dpy)
183 {
184 #ifdef __VMS
185 	return TRUE;
186 #else
187 	int		pfd[2],
188 			ret;
189 	unsigned int	ver,
190 			vmaj,
191 			vmin;
192 	pid_t		cpid;
193 	waitret_t	wstat;
194 	bool_t		chkok = FALSE;
195 	FILE		*fp;
196 	char		*cp,
197 			*path,
198 			buf[STR_BUF_SZ * 4];
199 
200 	if (PIPE(pfd) < 0) {
201 		perror("chk_resource_ver: pipe failed");
202 		return FALSE;
203 	}
204 
205 	if ((cp = getenv("XUSERFILESEARCHPATH")) == NULL)
206 		return TRUE;	/* Cannot check, just hope it's ok */
207 
208 	path = XtResolvePathname(dpy, NULL, "XMcd", NULL, cp, NULL, 0, NULL);
209 	if (path == NULL || path[0] == '\0')
210 		return TRUE;	/* Cannot check, just hope it's ok */
211 
212 	switch (cpid = FORK()) {
213 	case -1:
214 		/* Fork failed */
215 		perror("chk_resource_ver: fork failed");
216 		return FALSE;
217 	case 0:
218 		/* Child */
219 		(void) util_signal(SIGPIPE, SIG_IGN);
220 		(void) close(pfd[0]);
221 		break;
222 	default:
223 		/* Parent */
224 		(void) close(pfd[1]);
225 
226 		(void) read(pfd[0], &ver, sizeof(ver));
227 		(void) close(pfd[0]);
228 
229 		ret = util_waitchild(cpid, 5, NULL, 0, FALSE, &wstat);
230 
231 		if (ret < 0)
232 			chkok = FALSE;
233 		else if (WIFEXITED(wstat))
234 			chkok = (bool_t) (WEXITSTATUS(wstat) == 0);
235 		else if (WTERMSIG(wstat))
236 			chkok = FALSE;
237 
238 		if (!chkok)
239 			return TRUE; /* Cannot check, just hope it's ok */
240 
241 		vmaj = (ver & 0xff00) >> 8;
242 		vmin = (ver & 0x00ff);
243 
244 		if (vmaj != atoi(VERSION_MAJ) || vmin != atoi(VERSION_MIN)) {
245 			(void) fprintf(errfp, "%s Fatal Error:\n"
246 				"The XMcd.version (%u.%u) in the %s file\n"
247 				"is not compatible with this version of "
248 				"%s (%s.%s).\n",
249 				PROGNAME, vmaj, vmin, path,
250 				PROGNAME, VERSION_MAJ, VERSION_MIN
251 			);
252 			return FALSE;
253 		}
254 
255 		return TRUE;
256 	}
257 
258 	/* Set defaults */
259 	vmaj = atoi(VERSION_MAJ);
260 	vmin = atoi(VERSION_MIN);
261 
262 	/* Force uid and gid to original setting */
263 	if (!util_set_ougid())
264 		_exit(errno);
265 
266 	/* Read resource file and look for XMcd.version */
267 	if ((fp = fopen(path, "r")) == NULL)
268 		_exit(0);
269 
270 	while (fgets(buf, sizeof(buf), fp) != NULL) {
271 		if (buf[0] == '!' || buf[0] == '#' ||
272 		    buf[0] == '\n' || buf[0] == '\0')
273 			continue;
274 
275 		if (sscanf(buf, "XMcd.version: %u.%u", &vmaj, &vmin) == 2)
276 			break;	/* Found it */
277 	}
278 
279 	(void) fclose(fp);
280 
281 	ver = ((vmaj << 8) | vmin);
282 	(void) write(pfd[1], &ver, sizeof(ver));
283 	(void) close(pfd[1]);
284 
285 	_exit(0);
286 	/*NOTREACHED*/
287 #endif	/* __VMS */
288 }
289 
290 
291 /*
292  * x_error
293  *	X error handler - Used when user interface debugging is enabled
294  *
295  * Args:
296  *	dpy - The X display
297  *	ev - The X event
298  *
299  * Return:
300  *	Does not return.
301  */
302 STATIC int
303 x_error(Display *dpy, XErrorEvent *ev)
304 {
305 	char	str[ERR_BUF_SZ],
306 		msg[ERR_BUF_SZ],
307 		num[32];
308 	char	*mtyp = "XlibMessage";
309 
310 	(void) fprintf(errfp, "x_error: X error on \"%s\"\n",
311 		DisplayString(dpy)
312 	);
313 	XGetErrorText(dpy, ev->error_code, str, sizeof(str));
314 	XGetErrorDatabaseText(
315 		dpy, mtyp, "XError", "X Error", msg, sizeof(msg)
316 	);
317 	(void) fprintf(errfp, "%s: %s\n  ", msg, str);
318 	XGetErrorDatabaseText(
319 		dpy, mtyp, "MajorCode", "Request Major code %d",
320 		msg, sizeof(msg)
321 	);
322 	(void) fprintf(errfp, msg, ev->request_code);
323 	if (ev->request_code < 128) {
324 		(void) sprintf(num, "%d", ev->request_code);
325 		XGetErrorDatabaseText(
326 			dpy, "XRequest", num, "", str, sizeof(str)
327 		);
328 		(void) fprintf(errfp, " (%s)\n", str);
329 	}
330 	else {
331 		XGetErrorDatabaseText(
332 			dpy, mtyp, "MinorCode", "Request Minor code %d",
333 			msg, sizeof(msg)
334 		);
335 		(void) fputs("  ", errfp);
336 		(void) fprintf(errfp, msg, ev->minor_code);
337 		(void) fputs("\n", errfp);
338 	}
339 	if (ev->error_code >= 128) {
340 		(void) fprintf(errfp, "  Error code 0x%x\n", ev->error_code);
341 	}
342 	else {
343 		switch (ev->error_code) {
344 		case BadValue:
345 			XGetErrorDatabaseText(
346 				dpy, mtyp, "Value", "Value 0x%x",
347 				msg, sizeof(msg)
348 			);
349 			(void) fputs("  ", errfp);
350 			(void) fprintf(errfp, msg, ev->resourceid);
351 			(void) fputs("\n", errfp);
352 			break;
353 		case BadAtom:
354 			XGetErrorDatabaseText(
355 				dpy, mtyp, "AtomID", "AtomID 0x%x",
356 				msg, sizeof(msg)
357 			);
358 			(void) fputs("  ", errfp);
359 			(void) fprintf(errfp, msg, ev->resourceid);
360 			(void) fputs("\n", errfp);
361 			break;
362 		case BadWindow:
363 		case BadPixmap:
364 		case BadCursor:
365 		case BadFont:
366 		case BadDrawable:
367 		case BadColor:
368 		case BadGC:
369 		case BadIDChoice:
370 			XGetErrorDatabaseText(
371 				dpy, mtyp, "ResourceID", "ResourceID 0x%x",
372 				msg, sizeof(msg)
373 			);
374 			(void) fputs("  ", errfp);
375 			(void) fprintf(errfp, msg, ev->resourceid);
376 			(void) fputs("\n", errfp);
377 
378 			break;
379 		default:
380 			(void) fprintf(errfp, "  Error code: 0x%x\n",
381 				ev->error_code
382 			);
383 			break;
384 		}
385 	}
386 	XGetErrorDatabaseText(
387 		dpy, mtyp, "ErrorSerial", "Error Serial #%d",
388 		msg, sizeof(msg)
389 	);
390 	(void) fputs("  ", errfp);
391 	(void) fprintf(errfp, msg, ev->serial);
392 	(void) fputs("\n", errfp);
393 	XGetErrorDatabaseText(
394 		dpy, mtyp, "CurrentSerial", "Current Serial #%d",
395 		msg, sizeof(msg)
396 	);
397 	(void) fputs("\n", errfp);
398 
399 	if (ev->error_code == BadImplementation)
400 		return 0;
401 
402 	/* Cause a core dump if possible, and exit immediately */
403 	abort();
404 	return 0;	/* Just to silence some picky compilers */
405 }
406 
407 
408 /*
409  * x_ioerror
410  *	X IO error handler - Used when user interface debugging is enabled
411  *
412  * Args:
413  *	dpy - The X display
414  *	ev - The X event
415  *
416  * Return:
417  *	Does not return.
418  */
419 STATIC int
420 x_ioerror(Display *dpy)
421 {
422 	if (errno == EPIPE) {
423 		(void) fprintf(errfp,
424 			"x_ioerror: X connection to \"%s\" broken:\n"
425 			"explicit kill or server shutdown.\n",
426 			DisplayString(dpy)
427 		);
428 	}
429 	else {
430 		(void) fprintf(errfp,
431 			"x_ioerror: fatal IO error (errno %d) on \"%s\"\n",
432 			errno, DisplayString(dpy)
433 		);
434 	}
435 
436 	/* Cause a core dump if possible, and exit immediately */
437 	abort();
438 	return 0;	/* Just to silence some picky compilers */
439 }
440 
441 
442 /*
443  * main
444  *	The main function
445  */
446 int
447 main(int argc, char **argv)
448 {
449 	int	i;
450 	Display	*display;
451 #ifdef HAS_EUID
452 	uid_t	euid,
453 		ruid;
454 	gid_t	egid,
455 		rgid;
456 #endif
457 
458 	/* Program startup multi-threading initializations */
459 	cdda_preinit();
460 
461 	/* Error message stream */
462 	errfp = stderr;
463 
464 	/* Initialize variables */
465 	progname = argv[0];
466 	cmd = NULL;
467 	localcmap = (Colormap) 0;
468 	(void) memset(&status, 0, sizeof(curstat_t));
469 
470 	/* Handle some signals */
471 	if (util_signal(SIGINT, onsig) == SIG_IGN)
472 		(void) util_signal(SIGINT, SIG_IGN);
473 	if (util_signal(SIGHUP, onsig) == SIG_IGN)
474 		(void) util_signal(SIGHUP, SIG_IGN);
475 	if (util_signal(SIGTERM, onsig) == SIG_IGN)
476 		(void) util_signal(SIGTERM, SIG_IGN);
477 	if (util_signal(SIGQUIT, onsig) == SIG_IGN)
478 		(void) util_signal(SIGQUIT, SIG_IGN);
479 
480 	/* Set SIGCHLD handler to default */
481 #ifndef __VMS_VER
482 	(void) util_signal(SIGCHLD, SIG_DFL);
483 #else
484 #if (__VMS_VER >= 70000000) && (__DECC_VER > 50230003)
485 	/* OpenVMS v7.0 and DECCV5.2 have these defined */
486 	(void) util_signal(SIGCHLD, SIG_DFL);
487 #endif
488 #endif
489 
490 	/* Ignore SIGALRMs until we need them */
491 	(void) util_signal(SIGALRM, SIG_IGN);
492 
493 	/* Hack: Aside from stdin, stdout, stderr, there shouldn't
494 	 * be any other files open, so force-close them.  This is
495 	 * necessary in case xmcd was inheriting any open file
496 	 * descriptors from a parent process which is for the
497 	 * CD-ROM device, and violating exclusive-open requirements
498 	 * on some platforms.
499 	 */
500 	for (i = 3; i < 10; i++)
501 		(void) close(i);
502 
503 #if (XtSpecificationRelease >= 5)
504 	/* Set locale */
505 	XtSetLanguageProc(NULL, NULL, NULL);
506 #endif
507 
508 	/* Snoop command line for -display before XtVaAppInitialize
509 	 * consumes it  Also, handle -help here before we attempt
510 	 * to open the X display.
511 	 */
512 	for (i = 0; i < argc; i++) {
513 		if (strcmp(argv[i], "-display") == 0)
514 			cdisplay = argv[++i];
515 		else if (strcmp(argv[i], "-help") == 0) {
516 			usage(argv[i]);
517 			exit(1);
518 		}
519 	}
520 
521 	/* Initialize libutil */
522 	util_init();
523 
524 #ifdef HAS_EUID
525 	/* Get real IDs */
526 	ruid = getuid();
527 	rgid = getgid();
528 
529 	/* Save effective IDs */
530 	euid = geteuid();
531 	egid = getegid();
532 
533 	/* Give up root until we have a connection to the X server, so
534 	 * that X authentication will work correctly.
535 	 */
536 	(void) util_seteuid(ruid);
537 	(void) util_setegid(rgid);
538 #endif
539 
540 	/* Initialize X toolkit */
541 	widgets.toplevel = XtVaAppInitialize(
542 		&app_context,
543 		"XMcd",
544 		options, XtNumber(options),
545 		&argc, argv,
546 		NULL,
547 		XmNmappedWhenManaged, False,
548 		NULL
549 	);
550 
551 	display = XtDisplay(widgets.toplevel);
552 
553 	/* Check for old xmcd X resources */
554 	if (!chk_resource_ver(display))
555 		exit(1);
556 
557 	/* Get application options */
558 	XtVaGetApplicationResources(
559 		widgets.toplevel,
560 		(XtPointer) &app_data,
561 		resources,
562 		XtNumber(resources),
563 		NULL
564 	);
565 
566 	/* Build command */
567 	if (argc > 1) {
568 		for (i = 1;  i < argc; i++) {
569 			if (argv[i][0] == '-') {
570 				usage(argv[i]);
571 				exit(1);
572 			}
573 
574 			if (i == 1) {
575 				cmd = (char *) MEM_ALLOC(
576 					"cmd",
577 					strlen(argv[i]) + 1
578 				);
579 				if (cmd == NULL) {
580 					CD_FATAL(app_data.str_nomemory);
581 					exit(1);
582 				}
583 				cmd[0] = '\0';
584 			}
585 			else {
586 				cmd = (char *) MEM_REALLOC(
587 					"cmd",
588 					cmd,
589 					strlen(cmd) + strlen(argv[i]) + 2
590 				);
591 				if (cmd == NULL) {
592 					CD_FATAL(app_data.str_nomemory);
593 					exit(1);
594 				}
595 				(void) strcat(cmd, " ");
596 			}
597 			(void) strcat(cmd, argv[i]);
598 		}
599 	}
600 
601 	/* Set X error handlers if user interface debugging is enabled */
602 	if ((app_data.debug & DBG_UI) != 0) {
603 		(void) XSetErrorHandler(x_error);
604 		(void) XSetIOErrorHandler(x_ioerror);
605 	}
606 
607 	/* Remote control specified - handle that here */
608 	if (app_data.remotemode) {
609 		cmd_init(&status, display, TRUE);
610 		exit(cmd_sendrmt(display, cmd));
611 	}
612 
613 	/* Install colormap if specified */
614 	if (app_data.instcmap) {
615 		localcmap = XCopyColormapAndFree(
616 			display,
617 			DefaultColormap(display, DefaultScreen(display))
618 		);
619 		XtVaSetValues(widgets.toplevel, XmNcolormap, localcmap, NULL);
620 	}
621 
622 	/* Create all widgets */
623 	create_widgets(&widgets);
624 
625 	/* Configure resources before realizing widgets */
626 	pre_realize_config(&widgets);
627 
628 	/* Display widgets */
629 	XtRealizeWidget(widgets.toplevel);
630 
631 	/* Configure resources after realizing widgets */
632 	post_realize_config(&widgets, &pixmaps);
633 
634 	/* Register callback routines */
635 	register_callbacks(&widgets, &status);
636 
637 #ifdef HAS_EUID
638 	/* Ok, back to root */
639 	(void) util_seteuid(euid);
640 	(void) util_setegid(egid);
641 #endif
642 
643 	/* Initialize various subsystems */
644 	cd_init(&status);
645 
646 	/* Start various subsystems */
647 	cd_start(&status);
648 
649 	/* Make main window appear */
650 	XtMapWidget(widgets.toplevel);
651 
652 	/* Start remote control */
653 	cmd_init(&status, display, FALSE);
654 
655 	if (cmd != NULL) {
656 		/* Change to watch cursor */
657 		cd_busycurs(TRUE, CURS_ALL);
658 
659 		/* Schedule to perform start-up command */
660 		(void) cd_timeout(
661 			STARTUP_CMD_DELAY,
662 			cmd_startup,
663 			(byte_t *) cmd
664 		);
665 	}
666 
667 	/* Main event processing loop */
668 	XtAppMainLoop(app_context);
669 
670 	shutdown_gui();
671 	exit(0);
672 
673 	/*NOTREACHED*/
674 }
675 
676