chiark / gitweb /
*** empty log message ***
[sympathy.git] / apps / sympathy.c
1 /*
2  * sympathy.c:
3  *
4  * Copyright (c) 2008 James McKenzie <james@fishsoup.dhs.org>,
5  * All rights reserved.
6  *
7  */
8
9 static char rcsid[] =
10   "$Id$";
11
12 /*
13  * $Log$
14  * Revision 1.28  2008/03/02 10:27:24  james
15  * *** empty log message ***
16  *
17  * Revision 1.27  2008/03/02 09:55:37  james
18  * *** empty log message ***
19  *
20  * Revision 1.26  2008/02/29 14:55:09  james
21  * *** empty log message ***
22  *
23  * Revision 1.25  2008/02/28 22:43:25  james
24  * *** empty log message ***
25  *
26  * Revision 1.24  2008/02/28 22:00:42  james
27  * *** empty log message ***
28  *
29  * Revision 1.23  2008/02/28 16:57:51  james
30  * *** empty log message ***
31  *
32  * Revision 1.22  2008/02/28 01:47:44  james
33  * *** empty log message ***
34  *
35  * Revision 1.21  2008/02/27 16:01:24  james
36  * *** empty log message ***
37  *
38  * Revision 1.20  2008/02/27 10:00:34  james
39  * *** empty log message ***
40  *
41  * Revision 1.19  2008/02/27 09:47:05  james
42  * *** empty log message ***
43  *
44  * Revision 1.18  2008/02/27 09:42:53  james
45  * *** empty log message ***
46  *
47  * Revision 1.17  2008/02/27 09:42:21  james
48  * *** empty log message ***
49  *
50  * Revision 1.16  2008/02/27 01:31:38  james
51  * *** empty log message ***
52  *
53  * Revision 1.15  2008/02/27 01:31:14  james
54  * *** empty log message ***
55  *
56  * Revision 1.14  2008/02/24 00:43:55  james
57  * *** empty log message ***
58  *
59  * Revision 1.13  2008/02/24 00:42:53  james
60  * *** empty log message ***
61  *
62  * Revision 1.12  2008/02/23 11:48:52  james
63  * *** empty log message ***
64  *
65  * Revision 1.11  2008/02/20 20:16:07  james
66  * *** empty log message ***
67  *
68  * Revision 1.10  2008/02/20 19:44:37  james
69  * @@
70  *
71  * Revision 1.9  2008/02/20 18:49:11  staffcvs
72  * *** empty log message ***
73  *
74  * Revision 1.8  2008/02/20 18:33:37  james
75  * *** empty log message ***
76  *
77  * Revision 1.7  2008/02/20 18:31:44  james
78  * *** empty log message ***
79  *
80  * Revision 1.6  2008/02/20 17:18:33  james
81  * *** empty log message ***
82  *
83  * Revision 1.5  2008/02/20 15:50:14  james
84  * *** empty log message ***
85  *
86  * Revision 1.4  2008/02/20 02:11:35  james
87  * *** empty log message ***
88  *
89  * Revision 1.3  2008/02/14 02:46:44  james
90  * *** empty log message ***
91  *
92  * Revision 1.2  2008/02/14 00:57:58  james
93  * *** empty log message ***
94  *
95  * Revision 1.1  2008/02/05 14:25:49  james
96  * *** empty log message ***
97  *
98  */
99
100 #include <sys/types.h>
101 #include <stdarg.h>
102 #include <sympathy.h>
103 #include <stdlib.h>
104 #include <sys/utsname.h>
105 #include <sys/stat.h>
106 #include <dirent.h>
107 #include <string.h>
108 #include <strings.h>
109 #include <malloc.h>
110 #include <fcntl.h>
111
112 #include "mainloop.h"
113
114 extern void usage (void);
115 static char hostname[1024];
116
117 char *socket_dirs[] =
118   { "~/.sympathy", "~/sympathy", "/etc/sympathy", "/var/sympathy", NULL };
119
120 int
121 safe_atoi (char *a)
122 {
123   char *end;
124   int ret;
125
126   if (!a)
127     return -1;
128
129   ret = (int) strtol (a, &end, 0);
130
131   if (end == a)
132     return -1;
133
134   return ret;
135 }
136
137 char *
138 fatal_moan (char *fmt, ...)
139 {
140   va_list ap;
141
142
143   va_start (ap, fmt);
144   vfprintf (stderr, fmt, ap);
145   va_end (ap);
146
147   putc ('\n', stderr);
148   exit (1);
149 }
150
151
152 char *
153 teedious_snprint (char *fmt, va_alist * ap)
154 {
155   va_alist *aq;
156   int size = 1024;
157   char *buf = malloc (size);
158
159   if (!buf)
160     fatal_moan ("malloc failed");
161
162   while (1) {
163
164     va_copy (aq, ap);
165     n = vsnprintf (buf, size, fmt, aq);
166     va_end (aq);
167
168     if (n > -1 && n < (size))
169       return buf;
170
171     if (n > -1)                 /* glibc 2.1 */
172       size = n + 1;
173     else                        /* glibc 2.0 */
174       size *= 2;                /* twice the old size */
175
176     buf = realloc (buf, size);
177
178     if (!buf)
179       fatal_moan ("malloc failed");
180   }
181
182 }
183
184 char *
185 gloo_paths (char *dir, char *leaf)
186 {
187   int i;
188   char *ret, *ptr;
189   if (!dir)
190     dir = "";
191   if (!leaf)
192     leaf = "";
193   ret = ptr = malloc (strlen (dir) + strlen (leaf) + 2);
194
195   while (*dir)
196     *(ptr++) = *(dir++);
197   *(ptr++) = '/';
198   while (*leaf)
199     *(ptr++) = *(leaf++);
200   *ptr = 0;
201
202   return ret;
203 }
204
205
206 /*make the path in fmt from home (hence the name) */
207 char *
208 mome (char *fmt, ...)
209 {
210
211   char *rt, *home, *leaf;
212   va_list ap;
213
214
215   home = getenv ("HOME");
216   if (!home)
217     return NULL;
218
219   if (fmt) {
220     va_start (ap, fmt);
221     leaf = teedious_snprintf (fmt, ap);
222     va_end (ap);
223   } else {
224     leaf = NULL;
225   }
226
227   ret = gloo_paths (home, leaf);
228   if (leaf)
229     free (leaf);
230
231   return ret;
232 }
233
234
235 char *
236 find_socket (char *fmt, ...)
237 {
238   Socket *ret;
239   char *path, *leaf, *h, **ptr;
240   va_list ap;
241
242
243   if (fmt) {
244     va_start (ap, fmt);
245     leaf = teedious_snprintf (fmt, ap);
246     va_end (ap);
247   } else {
248     leaf = NULL;
249   }
250
251
252   for (ptr = socket_dirs; *ptr; ptr++) {
253     if (**ptr == '~') {
254       h = mome (*ptr + 1);
255     } else {
256       h = *ptr;
257     }
258
259     if (!h)
260       continue;
261
262
263     path = gloo_paths (h, leaf);
264     if (**ptr == '~')
265       free (h);
266
267     ret = socket_connect (path);
268
269     free (path);
270
271     if (ret) {
272       free (leaf);
273       return ret;
274     }
275
276   }
277
278   free (leaf);
279   return NULL;
280
281 }
282
283
284
285 int
286 list_sockets_in_dir (char *sockdir)
287 {
288   struct dirent *ent;
289   struct stat buf;
290   char *sn = NULL;
291   Socket *s;
292   DIR *dir = opendir (sockdir);
293
294
295
296   int hostname_len = strlen (hostname);
297
298   if (!dir)
299     fatal_moan ("can't examine %s for sockets", sockdir);
300
301   rewinddir (dir);
302
303
304   while ((ent = readdir (dir))) {
305     sn = gloo_paths (sockdir, ent->d_name);
306     if (stat (sn, &buf) || (!S_ISSOCK (buf.st_mode))) {
307       free (sn);
308       continue;
309     }
310
311     s = socket_connect (sn);
312
313     if (s) {
314       printf ("\t%s     (Active)\n", sn);
315       socket_free (s);
316     } else {
317       if (strncmp (ent->d_name, hostname, hostname_len)) {
318         printf ("\t%s   (Unknown - not this host)\n", sn);
319       } else {
320         printf ("\t%s   (Dead, wiped)\n", sn);
321         unlink (sn);
322       }
323     }
324
325     free (sn);
326   }
327
328   closedir (dir);
329
330   return 0;
331 }
332
333 int
334 list_sockets (void)
335 {
336   char **ptr, *h;
337
338
339   for (ptr = socket_dirs; *ptr; ptr++) {
340     if (*ptr == '~') {
341       h = mome (ptr + 1);
342     } else {
343       h = strdup (*ptr);
344     }
345
346     if (!h)
347       continue;
348     list_sockets_in_dir (h);
349     free (h);
350
351   }
352
353   return 0;
354 }
355
356
357 void
358 get_hostname (void)
359 {
360   struct utsname name;
361
362   if (uname (&name)) {
363     strcpy (hostname, "unknown.");
364     return;
365   }
366
367
368   strcpy (hostname, name.nodename);
369   strcat (hostname, ".");
370 }
371
372
373 int
374 main (int argc, char *argv[])
375 {
376   int c;
377   extern char *optarg;
378   extern int optind, opterr, optopt;
379   CRT_Pos size = { VT102_COLS_80, VT102_ROWS_24 };
380
381   int csnok_pipe[2] = { 0 };
382   int csnok = 0;
383
384   int oflags[128];
385   char *oargs[128];
386
387   Socket *server_socket = NULL, *client_socket = NULL;
388   ANSI *ansi = NULL;
389   TTY *tty = NULL;
390   Log *log = NULL;
391
392   int history = 200;
393   int pid;
394
395   get_hostname ();
396
397   memset (oflags, 0, sizeof (oflags));
398   memset (oargs, 0, sizeof (oargs));
399   while ((c = getopt (argc, argv, "vw:utscr:lKHd:pb:fL:Fk:n:")) != EOF) {
400     switch (c) {
401     case ':':
402     case 'h':
403     case '?':
404       usage ();
405     default:
406       if ((c > 64) && (c < 91)) {
407         oflags[c]++;
408         oargs[c] = optarg;
409       } else if ((c > 96) && (c < 123)) {
410         oflags[c]++;
411         oargs[c] = optarg;
412       } else {
413         fprintf (stderr, "unknown option %c\n", c);
414         usage ();
415       }
416     }
417
418   }
419
420   /*Compatability for screen's ls */
421   if (oflags['l'])
422     oflags['s'] = 0;
423
424
425   {
426     int sum = 0;
427     sum += oflags['t'];
428     sum += (oflags['s'] || oflags['c']) ? 1 : 0;
429     sum += oflags['r'];
430     sum += oflags['l'];
431     sum += oflags['v'];
432
433     if (!sum) {
434       /*If no mode is specified behave like screen */
435       oflags['s']++;
436       oflags['c']++;
437       sum++;
438     }
439
440     if (sum != 1)
441       fatal_moan
442         ("specifiy exactly one of ( -c and or -s ), -t, -r, -l and -v");
443   }
444
445   if (oflags['v']) {
446     fprintf (stderr, "Version: %s\n", libsympathy_version ());
447     fprintf (stderr, "Version: %s\n", rcsid);
448     return 0;
449   }
450
451   if (oflags['l'])
452     return list_sockets ();
453
454
455   if (oflags['r'] && oflags['k'])
456     fatal_moan ("-k is incompatible with -r");
457
458
459   if (oflags['n']) {
460     history = safe_atoi (oargs['n']);
461     if (history < 0)
462       fatal_moan ("cannot parse -n %s as an integer", oargs['n']);
463
464     if (!history)
465       fatal_moan ("agrument to -n must be greater than zero");
466   }
467
468   /*Fold -r implies -c */
469   if (oflags['r'])
470     oflags['c']++;
471
472   if (oflags['p'] && oflags['d'])
473     fatal_moan ("-p incompatible with -d");
474
475
476   if (oflags['c'] && oflags['s'] && oflags['F'])
477     fatal_moan ("-F is incompatible with -c -s");
478
479   /*implement server and client: this process forks. The parent */
480   /*becomes the client and the child forks again to become the */
481   /*server. If there's no -k argument the client (parent) needs */
482   /*to find out the pid of the server, we use a pipe */
483
484   if (oflags['s'] && oflags['c']) {
485     if (!oflags['k']) {
486       csnok++;
487       pipe (csnok_pipe);
488     }
489
490     switch (pid = fork ()) {
491     case 0:                    /*child becomes the server */
492       oflags['c'] = 0;
493       oflags['H'] = 0;
494
495       if (csnok)
496         close (csnok_pipe[0]);
497       break;
498     case -1:
499       fatal_moan ("fork failed");
500     default:                   /*parent becomes client */
501       oflags['s'] = 0;
502       oflags['K'] = 0;
503       oflags['d'] = 0;
504       oflags['p'] = 0;
505       oflags['b'] = 0;
506       oflags['f'] = 0;
507       oflags['L'] = 0;
508       oflags['n'] = 0;
509       oflags['w'] = 0;
510
511       /*Collect the child */
512       waitpid (pid, NULL, 0);
513
514       /*if there was no k argument we need to find the */
515       /*pid of the server process so that we can work out */
516       /*what the socket is called. The server tells us on */
517       /*a pipe */
518
519       if (csnok) {
520         close (csnok_pipe[1]);
521
522         if (read (csnok_pipe[0], &pid, sizeof (pid)) != sizeof (pid))
523           fatal_moan ("Failed to receive pid of server process");
524
525         close (csnok_pipe[0]);
526
527         oargs['k'] = mome ("/.sympathy/%s%d", hostname, pid);
528         oflags['k']++;
529       }
530     }
531   }
532
533
534   if (oflags['c'] && !oflags['k'] && !oflags['r'])
535     fatal_moan ("-c requires a socket to be specified with -s or -k or -r");
536
537   if ((oflags['p'] || oflags['d'] || oflags['K'] || oflags['b'] || oflags['f']
538        || oflags['L']) && oflags['c'])
539     fatal_moan ("-c or -r are incompatible with -p, -d, -K, -b, -f or -L");
540
541   if (oflags['t'] || oflags['s']) {
542     if (!oflags['p'] && !oflags['d'])
543       oflags['p']++;
544   }
545
546   if (oflags['w']) {
547     char buf[128], *ptr;
548     strcpy (buf, oargs['w']);
549     ptr = index (buf, 'x');
550     if (ptr) {
551       *ptr = 0;
552       ptr++;
553       size.y = safe_atoi (ptr);
554     }
555     size.x = safe_atoi (buf);
556
557     if ((size.x > VT102_MAX_COLS) || (size.x < 1))
558       fatal_moan ("-w requires a width between 1 and %d\n", VT102_MAX_COLS);
559
560     if ((size.y > VT102_MAX_ROWS) || (size.y < 1))
561       fatal_moan ("-w requires a height between 1 and %d\n", VT102_MAX_ROWS);
562
563   }
564
565   if (oflags['s'] && !oflags['F']) {
566     /*nochdir incase socket is relative path, unlink then will fail */
567     daemon (1, 0);
568
569   }
570
571
572   if (oflags['s']) {
573     char *path;
574     path = mome ("/.sympathy");
575     mkdir (path, 0700);
576     free (path);
577
578
579     if (!oflags['k']) {
580       pid = getpid ();
581
582       oargs['k'] = mome ("/.sympathy/%s%d", hostname, pid);
583       oflags['k']++;
584     }
585
586     server_socket = socket_listen (oargs['k']);
587
588     /*Tell our parent's parent what our pid is */
589     if (csnok) {
590       pid = getpid ();
591
592       write (csnok_pipe[1], &pid, sizeof (pid));
593       close (csnok_pipe[1]);
594     }
595
596     if (!server_socket)
597       fatal_moan ("failed to create socket %s for listening", oargs['k']);
598
599   }
600
601   if (oflags['s'] || oflags['t']) {
602
603     if (oflags['L']) {
604       log = file_log_new (oargs['L']);
605       if (!log)
606         fatal_moan ("unable to access log file %s", oargs['L']);
607     }
608
609     if (oflags['p']) {
610       tty = ptty_open (NULL, NULL, &size);
611       if (!tty)
612         fatal_moan ("unable to open a ptty");
613     } else {
614       /*HACK-- check that console=device does not occur in */
615       /*/proc/cmdline */
616       if (!oargs['d'])
617         fatal_moan ("no argument to -d");
618
619       {
620         char kernel_cmdline[4096] = { 0 };
621         char search_string[1024] = "console=";
622         char *ptr = oargs['d'];
623         int fd;
624
625         if (!strncmp ("/dev/", ptr, 5))
626           ptr += 5;
627
628         strcat (search_string, ptr);
629
630         fd = open ("/proc/cmdline", O_RDONLY);
631         read (fd, kernel_cmdline, sizeof (kernel_cmdline));
632         close (fd);
633
634         kernel_cmdline[sizeof (kernel_cmdline) - 1] = 0;
635
636         if (strstr (kernel_cmdline, search_string))
637           fatal_moan ("/proc/cmdline contains %s", search_string);
638       }
639
640       tty =
641         serial_open (oargs['d'],
642                      oflags['K'] ? SERIAL_LOCK_ACTIVE : SERIAL_LOCK_PASSIVE);
643       if (!tty)
644         fatal_moan ("unable to open serial port %s", oargs['d']);
645     }
646
647     if (oflags['b']) {
648       int baud = safe_atoi (oargs['b']);
649
650       if (baud < 0)
651         fatal_moan ("Unable to parse baudrate %s", oargs['b']);
652
653       tty_set_baud (tty, baud);
654
655     }
656
657     tty_set_flow (tty, oflags['f'] ? 1 : 0);
658   }
659
660
661   if (oflags['r']) {
662     char *id = oargs['r'];
663
664     if (!id)
665       fatal_moan ("-r requires an argument");
666
667     if (safe_atoi (id) > 0) {
668       client_socket = find_socket ("%s%d", hostname, safe_atoi (id));
669     } else {
670       client_socket = find_socket ("%s", id);
671     }
672
673     if (!client_socket)
674       fatal_moan ("failed to find socket %s", oargs['r']);
675
676   } else if (oflags['c']) {
677     client_socket = socket_connect (oargs['k']);
678
679     if (!client_socket)
680       fatal_moan ("failed to connect to socket %s", oargs['k']);
681
682   }
683
684
685   if (oflags['c'] || oflags['t']) {
686
687     if (oflags['H']) {
688       ansi = ansi_new_html (stdout);
689     } else {
690       terminal_register_handlers ();
691       ansi =
692         ansi_new_from_terminal (terminal_open (0, 1), oflags['u'] ? 0 : 1);
693       ansi->reset (ansi, NULL);
694     }
695   }
696
697   mainloop (tty, server_socket, client_socket, ansi, log, history, &size);
698
699   if (ansi) {
700     ansi->close (ansi);
701     terminal_atexit ();
702   }
703
704   if (tty)
705     tty->close (tty);
706
707   if (log)
708     log->close (log);
709   if (server_socket)
710     socket_free (server_socket);
711   if (client_socket)
712     socket_free (client_socket);
713
714   if (!oflags['H'])
715     printf ("you have now exited sympathy\n");
716   return 0;
717 }