1 /*
2 	Copyright (C) 2004, 2005 Stephen Bach
3 	This file is part of the Viewglob package.
4 
5 	Viewglob is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation; either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	Viewglob is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with Viewglob; if not, write to the Free Software
17 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19 
20 
21 #include <string.h>
22 #include <stdio.h>
23 
24 /* For open() */
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 
29 /* Sockets */
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <sys/un.h>
33 #include <arpa/inet.h>
34 
35 #include "common.h"
36 #include "hardened-io.h"
37 #include "param-io.h"
38 #include "child.h"
39 #include "x11-stuff.h"
40 #include "shell.h"
41 #include "tcp-listen.h"
42 #include "logging.h"
43 #include "syslogging.h"
44 #include "fgetopt.h"
45 #include "conf-to-args.h"
46 
47 #define DEFAULT_VGEXPAND_OPTS  "-d"
48 #define CONF_FILE              ".viewglob/vgd.conf"
49 
50 #define X_FAILURE        3
51 #define SOCKET_FAILURE   2
52 #define GENERAL_FAILURE  1
53 
54 struct state {
55 	GList*                clients;
56 	struct vgseer_client* current;
57 	gboolean              current_is_active;
58 
59 	struct child          display;
60 	Window                display_win;
61 
62 	Display*              Xdisplay;
63 	gboolean              persistent;
64 	gboolean              daemon;
65 	GString*              vgexpand_opts;
66 
67 	gchar*                port;
68 	gchar*                unix_sock_name;
69 	gint                  port_fd;
70 	gint                  unix_fd;
71 };
72 
73 
74 struct vgseer_client {
75 	/* Static properties (set once) */
76 	Window            win;
77 	gint              fd;
78 
79 	/* Dynamic properties (change often) */
80 	enum shell_status status;
81 	GString*          cli;
82 	GString*          pwd;
83 	GString*          developing_mask;
84 	GString*          mask;
85 	GString*          expanded;
86 };
87 
88 
89 void state_init(struct state* s);
90 void vgseer_client_init(struct vgseer_client* v);
91 static void poll_loop(struct state* s);
92 static void die(struct state* s, gint result);
93 static gint setup_polling(struct state* s, fd_set* set);
94 static void new_client(struct state* s, gint accept_fd);
95 static void new_ping_client(gint ping_fd);
96 static void new_vgseer_client(struct state* s, gint client_fd);
97 static void process_client(struct state* s, struct vgseer_client* v);
98 static void process_display(struct state* s);
99 static void drop_client(struct state* s, struct vgseer_client* v);
100 static void context_switch(struct state* s, struct vgseer_client* v);
101 static void check_active_window(struct state* s);
102 static void update_display(struct state* s, struct vgseer_client* v,
103 		enum parameter param, gchar* value);
104 static gint unix_listen(struct state* s);
105 static int daemonize(void);
106 static void parse_args(gint argc, gchar** argv, struct state* s);
107 static void report_version(void);
108 static void usage(void);
109 
110 
main(gint argc,gchar ** argv)111 gint main(gint argc, gchar** argv) {
112 
113 	struct state s;
114 
115 	/* Set the program name. */
116 	gchar* basename = g_path_get_basename(argv[0]);
117 	g_set_prgname(basename);
118 	g_free(basename);
119 
120 	g_log_set_handler(NULL,
121 			G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_MESSAGE |
122 			G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, logging, NULL);
123 
124 	state_init(&s);
125 
126 	/* Get program execution options. */
127 	gint conf_argc;
128 	gchar** conf_argv;
129 	if (conf_to_args(&conf_argc, &conf_argv, CONF_FILE)) {
130 		parse_args(conf_argc, conf_argv, &s);
131 		g_strfreev(conf_argv);
132 	}
133 	parse_args(argc, argv, &s);
134 
135 	/* Get a connection to the X display. */
136 	if ( (s.Xdisplay = XOpenDisplay(NULL)) == NULL) {
137 		g_critical("Could not connect to X server");
138 		exit(X_FAILURE);
139 	}
140 
141 	/* Setup listening sockets. */
142 	if ( (s.port_fd = tcp_listen(NULL, s.port)) == -1)
143 		exit(SOCKET_FAILURE);
144 	if ( (s.unix_fd = unix_listen(&s)) == -1)
145 		exit(SOCKET_FAILURE);
146 
147 	(void) chdir("/");
148 
149 	/* Turn into a daemon. */
150 	if (s.daemon)
151 		daemonize();
152 
153 	poll_loop(&s);
154 
155 	if (s.unix_sock_name)
156 		(void) unlink(s.unix_sock_name);
157 	return EXIT_SUCCESS;
158 }
159 
160 
unix_listen(struct state * s)161 static gint unix_listen(struct state* s) {
162 
163 	gint listenfd;
164 
165 	gchar* name;
166 	gchar* home;
167 	gchar* vgdir;
168 
169 	struct stat vgdir_stat;
170 
171 	struct sockaddr_un sun;
172 
173 	listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
174 	if (listenfd < 0) {
175 		g_critical("Could not create unix socket: %s", g_strerror(errno));
176 		return -1;
177 	}
178 
179 	if ((home = getenv("HOME")) == NULL) {
180 		g_critical("User does not have a home!");
181 		return -1;
182 	}
183 
184 	/* Create the ~/.viewglob/ directory. */
185 	vgdir = g_strconcat(home, "/.viewglob", NULL);
186 	if (stat(vgdir, &vgdir_stat) == -1) {
187 		if (mkdir(vgdir, 0700) == -1) {
188 			g_critical("Could not create ~/.viewglob directory: %s",
189 					g_strerror(errno));
190 			return -1;
191 		}
192 	}
193 	else if (!S_ISDIR(vgdir_stat.st_mode)) {
194 		g_critical("~/.viewglob exists but is not a directory");
195 		return -1;
196 	}
197 
198 	name = g_strconcat(vgdir, "/.", s->port, NULL);
199 	g_free(vgdir);
200 
201 	if (strlen(name) + 1 > sizeof(sun.sun_path)) {
202 		g_critical("Path is too long for unix socket");
203 		return -1;
204 	}
205 
206 	(void) unlink(name);
207 	(void) memset(&sun, 0, sizeof(sun));
208 	sun.sun_family = AF_LOCAL;
209 	strcpy(sun.sun_path, name);
210 
211 	if (bind(listenfd, (struct sockaddr*) &sun, sizeof(sun)) == -1) {
212 		g_critical("Could not bind socket: %s", g_strerror(errno));
213 		return -1;
214 	}
215 
216 	if (listen(listenfd, SOMAXCONN) == -1) {
217 		g_critical("Could not listen on socket: %s", g_strerror(errno));
218 		return -1;
219 	}
220 
221 	s->unix_sock_name = name;
222 	return listenfd;
223 }
224 
225 
parse_args(gint argc,gchar ** argv,struct state * s)226 static void parse_args(gint argc, gchar** argv, struct state* s) {
227 	g_return_if_fail(argv != NULL);
228 	g_return_if_fail(s != NULL);
229 
230 	gboolean in_loop = TRUE;
231 
232 	struct option long_options[] = {
233 		{ "port", 1, NULL, 'p' },
234 		{ "display", 1, NULL, 'd' },
235 		{ "persistent", 2, NULL, 'P' },
236 		{ "daemon", 2, NULL, 'D' },
237 		{ "sort-style", 1, NULL, 's' },
238 		{ "dir-order", 1, NULL, 'r' },
239 		{ "font-size-modifier", 1, NULL, 'z' },
240 		{ "black", 1, NULL, '1' },
241 		{ "red", 1, NULL, '2' },
242 		{ "green", 1, NULL, '3' },
243 		{ "yellow", 1, NULL, '4' },
244 		{ "blue", 1, NULL, '5' },
245 		{ "magenta", 1, NULL, '6' },
246 		{ "cyan", 1, NULL, '7' },
247 		{ "white", 1, NULL, '8' },
248 		{ "file-icons", 2, NULL, 'i' },
249 		{ "jump-resize", 2, NULL, 'j' },
250 		{ "help", 0, NULL, 'H' },
251 		{ "version", 0, NULL, 'V' },
252 		{ 0, 0, 0, 0},
253 	};
254 
255 	optind = 0;
256 	while (in_loop) {
257 		switch (fgetopt_long(argc, argv,
258 					"p:d:D::P::s:r:z:i::j::HV", long_options, NULL)) {
259 			case -1:
260 				in_loop = FALSE;
261 				break;
262 
263 			/* Port */
264 			case 'p':
265 				g_free(s->port);
266 				s->port = g_strdup(optarg);
267 				break;
268 
269 			/* Display */
270 			case 'd':
271 				/* vgmini and vgclassic can be accepted without providing a
272 				   path. */
273 				g_free(s->display.exec_name);
274 				if (STREQ(optarg, "vgmini") || STREQ(optarg, "vgclassic")) {
275 					s->display.exec_name = g_strconcat(
276 							VG_LIB_DIR, "/", optarg, NULL);
277 				}
278 				else
279 					s->display.exec_name = g_strdup(optarg);
280 				break;
281 
282 			/* Persistence */
283 			case 'P':
284 				if (!optarg || STREQ(optarg, "on"))
285 					s->persistent = TRUE;
286 				else if (STREQ(optarg, "off"))
287 					s->persistent = FALSE;
288 				break;
289 
290 			/* Daemon */
291 			case 'D':
292 				if (!optarg || STREQ(optarg, "on"))
293 					s->daemon = TRUE;
294 				else if (STREQ(optarg, "off"))
295 					s->daemon = FALSE;
296 				break;
297 
298 			/* Sort style */
299 			case 's':
300 				if (STREQ(optarg, "ls"))
301 					optarg = "-l";
302 				else if (STREQ(optarg, "windows") || STREQ(optarg, "win"))
303 					optarg = "-w";
304 				else
305 					optarg = "";
306 				s->vgexpand_opts = g_string_append(
307 						s->vgexpand_opts, optarg);
308 				break;
309 
310 			/* Dir order */
311 			case 'r':
312 				if (STREQ(optarg, "descending"))
313 					optarg = "-d";
314 				else if (STREQ(optarg, "ascending"))
315 					optarg = "-a";
316 				else if (STREQ(optarg, "ascending-pwd-first"))
317 					optarg = "-p";
318 				else
319 					optarg = "";
320 				s->vgexpand_opts = g_string_append(
321 						s->vgexpand_opts, optarg);
322 				break;
323 
324 			/* Font size modifier */
325 			case 'z':
326 				args_add(&s->display.args, "-z");
327 				args_add(&s->display.args, optarg);
328 				break;
329 
330 			/* Colours */
331 			case '1':
332 				args_add(&s->display.args, "--black");
333 				args_add(&s->display.args, optarg);
334 				break;
335 			case '2':
336 				args_add(&s->display.args, "--red");
337 				args_add(&s->display.args, optarg);
338 				break;
339 			case '3':
340 				args_add(&s->display.args, "--green");
341 				args_add(&s->display.args, optarg);
342 				break;
343 			case '4':
344 				args_add(&s->display.args, "--yellow");
345 				args_add(&s->display.args, optarg);
346 				break;
347 			case '5':
348 				args_add(&s->display.args, "--blue");
349 				args_add(&s->display.args, optarg);
350 				break;
351 			case '6':
352 				args_add(&s->display.args, "--magenta");
353 				args_add(&s->display.args, optarg);
354 				break;
355 			case '7':
356 				args_add(&s->display.args, "--cyan");
357 				args_add(&s->display.args, optarg);
358 				break;
359 			case '8':
360 				args_add(&s->display.args, "--white");
361 				args_add(&s->display.args, optarg);
362 				break;
363 
364 			/* File type icons */
365 			case 'i':
366 				args_add(&s->display.args, "--file-icons");
367 				if (optarg)
368 					args_add(&s->display.args, optarg);
369 				break;
370 
371 			/* Enable or disable jump-resize */
372 			case 'j':
373 				args_add(&s->display.args, "--jump-resize");
374 				if (optarg)
375 					args_add(&s->display.args, optarg);
376 				break;
377 
378 			case 'H':
379 				usage();
380 				break;
381 
382 			case 'v':
383 			case 'V':
384 				report_version();
385 				break;
386 
387 			case ':':
388 				g_critical("Option missing argument");
389 				exit(GENERAL_FAILURE);
390 				break;
391 
392 			case '?':
393 			default:
394 				g_critical("Unknown option provided");
395 				exit(GENERAL_FAILURE);
396 				break;
397 		}
398 	}
399 }
400 
401 
usage(void)402 static void usage(void) {
403 	g_print(
404 #		include "vgd-usage.h"
405 	);
406 	exit(EXIT_SUCCESS);
407 }
408 
409 
report_version(void)410 static void report_version(void) {
411 	printf("%s %s\n", g_get_prgname(), VERSION);
412 	printf("Released %s\n", VG_RELEASE_DATE);
413 	exit(EXIT_SUCCESS);
414 }
415 
416 
poll_loop(struct state * s)417 static void poll_loop(struct state* s) {
418 	g_return_if_fail(s != NULL);
419 
420 	GList* iter;
421 	struct vgseer_client* v;
422 
423 	fd_set rset;
424 	gint max_fd;
425 	gint nready;
426 
427 	while (TRUE) {
428 
429 		max_fd = setup_polling(s, &rset);
430 
431 		/* Wait for input for a half second. */
432 		nready = hardened_select(max_fd + 1, &rset, 500);
433 		if (nready == -1) {
434 			g_critical("Problem while waiting for data: %s",
435 					g_strerror(errno));
436 			die(s, GENERAL_FAILURE);
437 		}
438 		else if (nready > 0) {
439 
440 			if (FD_ISSET(s->port_fd, &rset)) {
441 				new_client(s, s->port_fd);
442 				nready--;
443 			}
444 
445 			if (nready && FD_ISSET(s->unix_fd, &rset)) {
446 				new_client(s, s->unix_fd);
447 				nready--;
448 			}
449 
450 			if (nready && child_running(&s->display) &&
451 					FD_ISSET(s->display.fd_in, &rset)) {
452 				process_display(s);
453 				nready--;
454 			}
455 
456 			if (nready) {
457 
458 				for (iter = s->clients; iter; iter = g_list_next(iter)) {
459 					v = iter->data;
460 					if (FD_ISSET(v->fd, &rset)) {
461 						process_client(s, v);
462 						/* It's unsafe to process more than one client, as
463 						   the list may have changed. */
464 						break;
465 					}
466 				}
467 			}
468 		}
469 
470 		if (s->clients)
471 			check_active_window(s);
472 	}
473 }
474 
475 
check_active_window(struct state * s)476 static void check_active_window(struct state* s) {
477 	g_return_if_fail(s != NULL);
478 
479 	Window new_active_win = get_active_window(s->Xdisplay);
480 
481 	/* If the currently active window has changed and is one of
482 	   our vgseer client terminals, make a context switch. */
483 	if (new_active_win == s->current->win) {
484 		if (!s->current_is_active) {
485 			/* Reraise the display. */
486 			s->current_is_active = TRUE;
487 			update_display(s, s->current, P_WIN_ID,
488 					win_to_str(s->current->win));
489 		}
490 	}
491 	else {
492 		s->current_is_active = FALSE;
493 		GList* iter;
494 		struct vgseer_client* v;
495 		for (iter = s->clients; iter; iter = g_list_next(iter)) {
496 			v = iter->data;
497 			if (new_active_win == v->win) {
498 				s->current = v;
499 				s->current_is_active = TRUE;
500 				context_switch(s, v);
501 				break;
502 			}
503 		}
504 	}
505 }
506 
507 
context_switch(struct state * s,struct vgseer_client * v)508 static void context_switch(struct state* s, struct vgseer_client* v) {
509 	g_return_if_fail(s != NULL);
510 	g_return_if_fail(v != NULL);
511 
512 	if (!child_running(&s->display))
513 		return;
514 
515 	gint fd = s->display.fd_out;
516 
517 	/* Send a bunch of data to the display. */
518 	if (	!put_param(fd, P_STATUS, shell_status_to_string(v->status)) ||
519 			!put_param(fd, P_CMD, v->cli->str) ||
520 			!put_param(fd, P_DEVELOPING_MASK, v->developing_mask->str) ||
521 			!put_param(fd, P_MASK, v->mask->str) ||
522 			!put_param(fd, P_WIN_ID, win_to_str(v->win)) ||
523 			!put_param(fd, P_VGEXPAND_DATA, v->expanded->str)) {
524 		g_critical("(disp) Couldn't make context switch");
525 		// FIXME restart display, try again
526 	}
527 }
528 
529 
process_display(struct state * s)530 static void process_display(struct state* s) {
531 	g_return_if_fail(s != NULL);
532 
533 	enum parameter param;
534 	gchar* value;
535 
536 	/* Try to recover from display read errors instead of just dying. */
537 	if (!get_param(s->display.fd_in, &param, &value)) {
538 		(void) child_terminate(&s->display);
539 		if (!child_fork(&s->display)) {
540 			g_critical("The display had issues and I couldn't restart it");
541 			die(s, GENERAL_FAILURE);
542 		}
543 		else {
544 			s->display_win = 0;
545 			return;
546 		}
547 	}
548 
549 	switch (param) {
550 
551 		case P_FILE:
552 			/* Pass the file on to the client. */
553 			break;
554 
555 		case P_KEY:
556 			/* Pass the key on to the client. */
557 			break;
558 
559 		case P_WIN_ID:
560 			/* Store the new window id. */
561 			if ((s->display_win = strtoul(value, NULL, 10)) == ULONG_MAX) {
562 				g_warning("(disp) window ID is out of bounds: %s", value);
563 				s->display_win = 0;
564 			}
565 			param = P_NONE;
566 			break;
567 
568 		case P_EOF:
569 			g_message("(disp) EOF from display");
570 			(void) child_terminate(&s->display);
571 			param = P_NONE;
572 			break;
573 
574 		default:
575 			g_warning("(disp) Unexpected parameter: %d = %s", param, value);
576 			// TODO: restart display (wrap into function)
577 			param = P_NONE;
578 			break;
579 	}
580 
581 	if (param != P_NONE) {
582 		/* Pass the message right along. */
583 		if (!put_param(s->current->fd, param, value)) {
584 			g_warning("Couldn't pass message to current client");
585 			drop_client(s, s->current);
586 		}
587 	}
588 }
589 
590 
process_client(struct state * s,struct vgseer_client * v)591 static void process_client(struct state* s, struct vgseer_client* v) {
592 	g_return_if_fail(s != NULL);
593 	g_return_if_fail(v != NULL);
594 
595 	enum parameter param;
596 	gchar* value;
597 
598 	if (!get_param(v->fd, &param, &value)) {
599 		drop_client(s, v);
600 		return;
601 	}
602 
603 	enum shell_status new_status;
604 
605 	switch (param) {
606 
607 		case P_STATUS:
608 			if ( (new_status = string_to_shell_status(value)) == SS_ERROR) {
609 				g_warning("(%d) Invalid shell status from client: %s",
610 						v->fd, value);
611 				drop_client(s, v);
612 			}
613 
614 			if (new_status != v->status) {
615 				v->status = new_status;
616 				update_display(s, v, param, value);
617 			}
618 			break;
619 
620 		case P_PWD:
621 			// FIXME PWD required?
622 			v->pwd = g_string_assign(v->pwd, value);
623 			break;
624 
625 		case P_CMD:
626 			v->cli = g_string_assign(v->cli, value);
627 			update_display(s, v, param, value);
628 			break;
629 
630 		case P_MASK:
631 			/* Clear the developing mask, if any. */
632 			v->developing_mask = g_string_truncate(v->developing_mask, 0);
633 			v->mask = g_string_assign(v->mask, value);
634 
635 			update_display(s, v, P_DEVELOPING_MASK, "");
636 			update_display(s, v, P_MASK, value);
637 			break;
638 
639 		case P_DEVELOPING_MASK:
640 			v->developing_mask = g_string_assign(v->developing_mask, value);
641 
642 			update_display(s, v, param, value);
643 			break;
644 
645 		case P_ORDER:
646 			if (STREQ(value, "refocus")) {
647 				/* If vgd doesn't recognize that the current terminal has
648 				   changed, you can force it to take notice by refocusing. */
649 				if (s->current != v) {
650 					s->current = v;
651 					context_switch(s, v);
652 				}
653 				else
654 					update_display(s, v, param, value);
655 			}
656 			else if (STREQ(value, "toggle")) {
657 				if (child_running(&s->display))
658 					child_terminate(&s->display);
659 				else {
660 					if (!child_fork(&s->display)) {
661 						g_critical("Couldn't fork the display");
662 						die(s, GENERAL_FAILURE);
663 					}
664 					context_switch(s, s->current);
665 				}
666 			}
667 			else {
668 				/* The rest of the orders go to the display. */
669 				update_display(s, v, param, value);
670 			}
671 			break;
672 
673 		case P_VGEXPAND_DATA:
674 			v->expanded = g_string_assign(v->expanded, value);
675 			update_display(s, v, param, value);
676 			break;
677 
678 		case P_EOF:
679 			g_message("(%d) EOF from client", v->fd);
680 			drop_client(s, v);
681 			break;
682 
683 		default:
684 			g_warning("(%d) Unexpected parameter: %d = %s", v->fd, param,
685 					value);
686 			drop_client(s, v);
687 			break;
688 	}
689 }
690 
691 
update_display(struct state * s,struct vgseer_client * v,enum parameter param,gchar * value)692 static void update_display(struct state* s, struct vgseer_client* v,
693 		enum parameter param, gchar* value) {
694 	g_return_if_fail(s != NULL);
695 	g_return_if_fail(v != NULL);
696 	g_return_if_fail(value != NULL);
697 
698 	if (s->current != v || !child_running(&s->display))
699 		return;
700 
701 	if (!put_param(s->display.fd_out, param, value)) {
702 		g_critical("Couldn't send parameter to display");
703 		//FIXME restart display
704 	}
705 }
706 
707 
708 /* Disconnect the client and free its resources. */
drop_client(struct state * s,struct vgseer_client * v)709 static void drop_client(struct state* s, struct vgseer_client* v) {
710 	g_return_if_fail(s != NULL);
711 	g_return_if_fail(v != NULL);
712 
713 	s->clients = g_list_remove(s->clients, v);
714 
715 	(void) close(v->fd);
716 	g_message("(%d) Dropped client", v->fd);
717 	g_string_free(v->cli, TRUE);
718 	g_string_free(v->pwd, TRUE);
719 	g_string_free(v->developing_mask, TRUE);
720 	g_string_free(v->mask, TRUE);
721 	g_string_free(v->expanded, TRUE);
722 	g_free(v);
723 
724 	/* Kill the display if all the clients are gone. */
725 	if (child_running(&s->display) && !s->clients) {
726 		(void) child_terminate(&s->display);
727 		g_message("(disp) Killed display");
728 	}
729 
730 	/* If this was the current client, switch to another one. */
731 	if (s->current == v) {
732 		if (s->clients) {
733 			s->current = s->clients->data;
734 			context_switch(s, s->current);
735 		}
736 		else
737 			s->current = NULL;
738 	}
739 
740 	/* If we're not running in persistent mode, and this was the last client,
741 	   exit. */
742 	if (!s->persistent && !s->clients)
743 		die(s, EXIT_SUCCESS);
744 }
745 
746 
747 /* Accept a new client. */
new_client(struct state * s,gint accept_fd)748 static void new_client(struct state* s, gint accept_fd) {
749 	g_return_if_fail(s != NULL);
750 	g_return_if_fail(accept_fd >= 0);
751 
752 	gint new_fd;
753 	struct sockaddr_in sa;
754 	socklen_t sa_len;
755 
756 	enum parameter param;
757 	gchar* value;
758 
759 	/* Accept the new client. */
760 	again:
761 	sa_len = sizeof(sa);
762 	if ( (new_fd = accept(accept_fd, (struct sockaddr*) &sa,
763 					&sa_len)) == -1) {
764 		if (errno == EINTR)
765 			goto again;
766 		else {
767 			g_warning("Error while accepting new client: %s",
768 					g_strerror(errno));
769 			return;
770 		}
771 	}
772 
773 	g_message("(%d) New client accepted", new_fd);
774 
775 	// TODO add time limits to get_param
776 	/* Receive client's purpose. */
777 	if (get_param(new_fd, &param, &value) && param == P_PURPOSE) {
778 		if (STREQ(value, "vgping"))
779 			new_ping_client(new_fd);
780 		else if (STREQ(value, "vgseer"))
781 			new_vgseer_client(s, new_fd);
782 		else {
783 			g_warning("(%d) Unexpected purpose: \"%s\"", new_fd, value);
784 			(void) close(new_fd);
785 		}
786 	}
787 	else {
788 		g_warning("(%d) Did not receive purpose from client", new_fd);
789 		(void) close(new_fd);
790 	}
791 }
792 
793 
new_ping_client(gint ping_fd)794 static void new_ping_client(gint ping_fd) {
795 	g_return_if_fail(ping_fd >= 0);
796 
797 	g_message("(%d) Client is pinging", ping_fd);
798 	if (!put_param(ping_fd, P_STATUS, "yo"))
799 		g_warning("(%d) Couldn't ping back", ping_fd);
800 
801 	(void) close(ping_fd);
802 }
803 
804 
new_vgseer_client(struct state * s,gint client_fd)805 static void new_vgseer_client(struct state* s, gint client_fd) {
806 	g_return_if_fail(s != NULL);
807 	g_return_if_fail(client_fd >= 0);
808 
809 	enum parameter param;
810 	gchar* value;
811 
812 	struct vgseer_client* v;
813 	gchar* term_title = NULL;
814 
815 	g_message("(%d) Client is a vgseer", client_fd);
816 
817 	v = g_new(struct vgseer_client, 1);
818 	vgseer_client_init(v);
819 
820 	/* Version */
821 	if (!get_param(client_fd, &param, &value) || param != P_VERSION)
822 		goto out_of_sync;
823 	// TODO: check_version()
824 	if (STREQ(VERSION, value)) {
825 		value = "OK";
826 		if (!put_param(client_fd, P_STATUS, value))
827 			goto reject;
828 	}
829 	else {
830 		/* Versions differ */
831 		gchar* warning = g_strconcat("vgd is v", VERSION,
832 				", vgseer is v", value, NULL);
833 		value = "WARNING";
834 		if (!put_param(client_fd, P_STATUS, value))
835 			goto reject;
836 		if (!put_param(client_fd, P_REASON, warning))
837 			goto reject;
838 		g_free(warning);
839 	}
840 
841 	/* Title */
842 	if (!get_param(client_fd, &param, &value) || param != P_TERM_TITLE)
843 		goto out_of_sync;
844 	term_title = g_strdup(value);
845 
846 	/* Tell vgseer client to set a title on its terminal. */
847 	if (!put_param(client_fd, P_ORDER, "set-title"))
848 		goto reject;
849 	if (!get_param(client_fd, &param, &value) || param != P_STATUS ||
850 			!STREQ(value, "title-set"))
851 		goto out_of_sync;
852 
853 	/* Find the client's terminal window. */
854 	if ( (v->win = get_xid_from_title(s->Xdisplay, term_title)) == 0)
855 		g_warning("(%d) Couldn't locate client's window", client_fd);
856 
857 	/* Finally, send over the vgexpand options. */
858 	if (!put_param(client_fd, P_VGEXPAND_OPTS, s->vgexpand_opts->str))
859 		goto reject;
860 
861 	/* We've got a new client. */
862 	v->fd = client_fd;
863 
864 	/* This is our only client, so it's current by default. */
865 	if (!s->clients)
866 		s->current = v;
867 
868 	s->clients = g_list_prepend(s->clients, v);
869 
870 	/* Startup the display if it's not around. */
871 	if (!child_running(&s->display)) {
872 		if (!child_fork(&s->display)) {
873 			g_critical("Couldn't fork the display");
874 			die(s, GENERAL_FAILURE);
875 		}
876 		/* Send the window ID, just in case this is the current window.
877 		   Otherwise the window ID is only sent on a context switch. */
878 		update_display(s, v, P_WIN_ID, win_to_str(v->win));
879 	}
880 
881 	return;
882 
883 out_of_sync:
884 	g_warning("(%d) Client sent unexpected data", client_fd);
885 reject:
886 	g_warning("(%d) Client rejected", client_fd);
887 	g_free(term_title);
888 	g_free(v);
889 	(void) close(client_fd);
890 }
891 
892 
893 /* Set the fd_set for all clients, the display, and the listen fd. */
setup_polling(struct state * s,fd_set * set)894 static gint setup_polling(struct state* s, fd_set* set) {
895 	g_return_val_if_fail(s != NULL, 0);
896 	g_return_val_if_fail(set != NULL, 0);
897 
898 	gint max_fd;
899 	GList* iter;
900 	struct vgseer_client* v;
901 
902 	/* Setup polling for the accept sockets. */
903 	FD_ZERO(set);
904 	FD_SET(s->port_fd, set);
905 	FD_SET(s->unix_fd, set);
906 	max_fd = MAX(s->port_fd, s->unix_fd);
907 
908 	/* Setup polling for each vgseer client. */
909 	for (iter = s->clients; iter; iter = g_list_next(iter)) {
910 		v = iter->data;
911 		g_return_val_if_fail(v->fd >= 0, 0);
912 		FD_SET(v->fd, set);
913 		max_fd = MAX(max_fd, v->fd);
914 	}
915 
916 	/* And poll the display. */
917 	if (child_running(&s->display)) {
918 		FD_SET(s->display.fd_in, set);
919 		max_fd = MAX(max_fd, s->display.fd_in);
920 	}
921 
922 	return max_fd;
923 }
924 
925 
926 /* Tell vgseer clients to disable, kill the display, and exit. */
die(struct state * s,gint result)927 static void die(struct state* s, gint result) {
928 
929 	GList* iter;
930 	struct vgseer_client* v;
931 
932 	/* Drop all the clients. */
933 	for (iter = s->clients; iter; iter = s->clients) {
934 		v = iter->data;
935 		(void) put_param(v->fd, P_STATUS, "dead");
936 		(void) drop_client(s, v);
937 	}
938 
939 	/* Kill display. */
940 	if (child_running(&s->display))
941 		(void) child_terminate(&s->display);
942 
943 	if (s->unix_sock_name)
944 		(void) unlink(s->unix_sock_name);
945 	exit(result);
946 }
947 
948 
949 /* This function taken from UNIX Network Programming, Volume I 3rd Ed.
950    by W. Richard Stevens, Bill Fenner, and Andrew M. Rudoff. */
daemonize(void)951 static gboolean daemonize(void) {
952 
953 	pid_t pid;
954 
955 	if ((pid = fork()) < 0)
956 		return FALSE;
957 	else if (pid)
958 		_exit(EXIT_SUCCESS);  /* Parent terminates. */
959 
960 	/* Child 1 continues... */
961 
962 	if (setsid() < 0)
963 		return FALSE;
964 
965 //	if (signal(SIGHUP, SIG_IGN) == SIG_ERR)
966 //		return FALSE;
967 
968 	if ((pid = fork()) < 0)
969 		return FALSE;
970 	else if (pid)
971 		_exit(EXIT_SUCCESS);  /* Child 1 terminates. */
972 
973 	/* Child 2 continues... */
974 
975 	/* Redirect stdin, stdout, and stderr to /dev/null. */
976 	(void) close(STDIN_FILENO);
977 	(void) close(STDOUT_FILENO);
978 	(void) close(STDERR_FILENO);
979 	open("/dev/null", O_RDONLY);
980 	open("/dev/null", O_RDWR);
981 	open("/dev/null", O_RDWR);
982 
983 	/* Use syslog for warnings and errors now that we're a daemon. */
984 	g_log_set_handler(NULL,
985 			G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_MESSAGE |
986 			G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, syslogging, NULL);
987 	openlog_wrapped(g_get_prgname());
988 
989 	return TRUE;
990 }
991 
992 
state_init(struct state * s)993 void state_init(struct state* s) {
994 	s->clients = NULL;
995 	s->persistent = FALSE;
996 	s->daemon = TRUE;
997 
998 	child_init(&s->display);
999 	s->display.exec_name = g_strdup(VG_LIB_DIR "/vgmini");
1000 	s->display_win = 0;
1001 
1002 	s->vgexpand_opts = g_string_new(DEFAULT_VGEXPAND_OPTS);
1003 	s->Xdisplay = NULL;
1004 	s->current = NULL;
1005 
1006 	s->port = g_strdup("16108");
1007 	s->port_fd = -1;
1008 
1009 	s->unix_sock_name = NULL;
1010 	s->unix_fd = -1;
1011 }
1012 
1013 
vgseer_client_init(struct vgseer_client * v)1014 void vgseer_client_init(struct vgseer_client* v) {
1015 
1016 	v->win = 0;
1017 	v->fd = -1;
1018 
1019 	v->status = SS_LOST;
1020 	v->cli = g_string_new(NULL);
1021 	v->pwd = g_string_new(NULL);
1022 	v->developing_mask = g_string_new(NULL);
1023 	v->mask = g_string_new(NULL);
1024 	v->expanded = g_string_new(NULL);
1025 }
1026 
1027