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, ¶m, &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, ¶m, &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, ¶m, &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, ¶m, &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, ¶m, &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, ¶m, &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