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