"Initial commit to Gerrit"
[profile/ivi/xorg-utils.git] / luit / luit.c
1 /*
2 Copyright (c) 2001 by Juliusz Chroboczek
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21 */
22
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <locale.h>
31 #include <sys/types.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <assert.h>
36 #include <stdarg.h>
37 #include <sys/ioctl.h>
38 #include <signal.h>
39
40 #include "luit.h"
41 #include "sys.h"
42 #include "other.h"
43 #include "parser.h"
44 #include "iso2022.h"
45
46 static int pipe_option = 0;
47 static int p2c_waitpipe[2];
48 static int c2p_waitpipe[2];
49
50 static Iso2022Ptr inputState = NULL, outputState = NULL;
51
52 static char *child_argv0 = NULL;
53 static const char *locale_name = NULL;
54 static int exitOnChild = 0;
55 static int converter = 0;
56
57 const char *locale_alias = LOCALE_ALIAS_FILE;
58
59 int ilog = -1;
60 int olog = -1;
61 int verbose = 0;
62
63 static volatile int sigwinch_queued = 0;
64 static volatile int sigchld_queued = 0;
65
66 static int convert(int, int);
67 static int condom(int, char **);
68
69 static void
70 ErrorF(const char *f,...)
71 {
72     va_list args;
73     va_start(args, f);
74     vfprintf(stderr, f, args);
75     va_end(args);
76 }
77
78 static void
79 FatalError(const char *f,...)
80 {
81     va_list args;
82     va_start(args, f);
83     vfprintf(stderr, f, args);
84     va_end(args);
85     ExitProgram(1);
86 }
87
88 static void
89 help(void)
90 {
91     fprintf(stderr,
92             "luit\n"
93             "  [ -V ] [ -h ] [ -list ] [ -v ] [ -argv0 name ]\n"
94             "  [ -gl gn ] [-gr gk] "
95             "[ -g0 set ] [ -g1 set ] "
96             "[ -g2 set ] [ -g3 set ]\n"
97             "  [ -encoding encoding ] "
98             "[ +oss ] [ +ols ] [ +osl ] [ +ot ]\n"
99             "  [ -kgl gn ] [-kgr gk] "
100             "[ -kg0 set ] [ -kg1 set ] "
101             "[ -kg2 set ] [ -kg3 set ]\n"
102             "  [ -k7 ] [ +kss ] [ +kssgr ] [ -kls ]\n"
103             "  [ -c ] "
104             "[ -p ] "
105             "[ -x ] "
106             "[ -ilog filename ] "
107             "[ -olog filename ] "
108             "[ -alias filename ] "
109             "[ -- ]\n"
110             "  [ program [ args ] ]\n");
111 }
112
113 static int
114 parseOptions(int argc, char **argv)
115 {
116     int i = 1;
117     while (i < argc) {
118         if (argv[i][0] != '-' && argv[i][0] != '+') {
119             break;
120         } else if (!strcmp(argv[i], "--")) {
121             i++;
122             break;
123         } else if (!strcmp(argv[i], "-v")) {
124             verbose++;
125             i++;
126         } else if (!strcmp(argv[i], "-V")) {
127             printf("%s - %s\n", argv[0], VERSION);
128             ExitProgram(0);
129         } else if (!strcmp(argv[i], "-h")) {
130             help();
131             ExitProgram(0);
132         } else if (!strcmp(argv[i], "-list")) {
133             reportCharsets();
134             ExitProgram(0);
135         } else if (!strcmp(argv[i], "+oss")) {
136             outputState->outputFlags &= ~OF_SS;
137             i++;
138         } else if (!strcmp(argv[i], "+ols")) {
139             outputState->outputFlags &= ~OF_LS;
140             i++;
141         } else if (!strcmp(argv[i], "+osl")) {
142             outputState->outputFlags &= ~OF_SELECT;
143             i++;
144         } else if (!strcmp(argv[i], "+ot")) {
145             outputState->outputFlags = OF_PASSTHRU;
146             i++;
147         } else if (!strcmp(argv[i], "-k7")) {
148             inputState->inputFlags &= ~IF_EIGHTBIT;
149             i++;
150         } else if (!strcmp(argv[i], "+kss")) {
151             inputState->inputFlags &= ~IF_SS;
152             i++;
153         } else if (!strcmp(argv[1], "+kssgr")) {
154             inputState->inputFlags &= ~IF_SSGR;
155             i++;
156         } else if (!strcmp(argv[i], "-kls")) {
157             inputState->inputFlags |= IF_LS;
158             i++;
159         } else if (!strcmp(argv[i], "-g0")) {
160             if (i + 1 >= argc)
161                 FatalError("-g0 requires an argument\n");
162             G0(outputState) = getCharsetByName(argv[i + 1]);
163             i += 2;
164         } else if (!strcmp(argv[i], "-g1")) {
165             if (i + 1 >= argc)
166                 FatalError("-g1 requires an argument\n");
167             G1(outputState) = getCharsetByName(argv[i + 1]);
168             i += 2;
169         } else if (!strcmp(argv[i], "-g2")) {
170             if (i + 1 >= argc)
171                 FatalError("-g2 requires an argument\n");
172             G2(outputState) = getCharsetByName(argv[i + 1]);
173             i += 2;
174         } else if (!strcmp(argv[i], "-g3")) {
175             if (i + 1 >= argc)
176                 FatalError("-g3 requires an argument\n");
177             G3(outputState) = getCharsetByName(argv[i + 1]);
178
179             i += 2;
180         } else if (!strcmp(argv[i], "-gl")) {
181             int j;
182             if (i + 1 >= argc)
183                 FatalError("-gl requires an argument\n");
184             if (strlen(argv[i + 1]) != 2 ||
185                 argv[i + 1][0] != 'g')
186                 j = -1;
187             else
188                 j = argv[i + 1][1] - '0';
189             if (j < 0 || j > 3)
190                 FatalError("The argument of -gl "
191                            "should be one of g0 through g3,\n"
192                            "not %s\n", argv[i + 1]);
193             else
194                 outputState->glp = &outputState->g[j];
195             i += 2;
196         } else if (!strcmp(argv[i], "-gr")) {
197             int j;
198             if (i + 1 >= argc)
199                 FatalError("-gr requires an argument\n");
200             if (strlen(argv[i + 1]) != 2 ||
201                 argv[i + 1][0] != 'g')
202                 j = -1;
203             else
204                 j = argv[i + 1][1] - '0';
205             if (j < 0 || j > 3)
206                 FatalError("The argument of -gl "
207                            "should be one of g0 through g3,\n"
208                            "not %s\n", argv[i + 1]);
209             else
210                 outputState->grp = &outputState->g[j];
211             i += 2;
212         } else if (!strcmp(argv[i], "-kg0")) {
213             if (i + 1 >= argc)
214                 FatalError("-kg0 requires an argument\n");
215             G0(inputState) = getCharsetByName(argv[i + 1]);
216             i += 2;
217         } else if (!strcmp(argv[i], "-kg1")) {
218             if (i + 1 >= argc)
219                 FatalError("-kg1 requires an argument\n");
220             G1(inputState) = getCharsetByName(argv[i + 1]);
221             i += 2;
222         } else if (!strcmp(argv[i], "-kg2")) {
223             if (i + 1 >= argc)
224                 FatalError("-kg2 requires an argument\n");
225             G2(inputState) = getCharsetByName(argv[i + 1]);
226             i += 2;
227         } else if (!strcmp(argv[i], "-kg3")) {
228             if (i + 1 >= argc)
229                 FatalError("-kg3 requires an argument\n");
230             G3(inputState) = getCharsetByName(argv[i + 1]);
231
232             i += 2;
233         } else if (!strcmp(argv[i], "-kgl")) {
234             int j;
235             if (i + 1 >= argc)
236                 FatalError("-kgl requires an argument\n");
237             if (strlen(argv[i + 1]) != 2 ||
238                 argv[i + 1][0] != 'g')
239                 j = -1;
240             else
241                 j = argv[i + 1][1] - '0';
242             if (j < 0 || j > 3)
243                 FatalError("The argument of -kgl "
244                            "should be one of g0 through g3,\n"
245                            "not %s\n", argv[i + 1]);
246             else
247                 inputState->glp = &inputState->g[j];
248             i += 2;
249         } else if (!strcmp(argv[i], "-kgr")) {
250             int j;
251             if (i + 1 >= argc)
252                 FatalError("-kgl requires an argument\n");
253             if (strlen(argv[i + 1]) != 2 ||
254                 argv[i + 1][0] != 'g')
255                 j = -1;
256             else
257                 j = argv[i + 1][1] - '0';
258             if (j < 0 || j > 3)
259                 FatalError("The argument of -kgl "
260                            "should be one of g0 through g3,\n"
261                            "not %s\n", argv[i + 1]);
262             else
263                 inputState->grp = &inputState->g[j];
264             i += 2;
265         } else if (!strcmp(argv[i], "-argv0")) {
266             if (i + 1 >= argc)
267                 FatalError("-argv0 requires an argument\n");
268             child_argv0 = argv[i + 1];
269             i += 2;
270         } else if (!strcmp(argv[i], "-x")) {
271             exitOnChild = 1;
272             i++;
273         } else if (!strcmp(argv[i], "-c")) {
274             converter = 1;
275             i++;
276         } else if (!strcmp(argv[i], "-ilog")) {
277             if (i + 1 >= argc)
278                 FatalError("-ilog requires an argument\n");
279             ilog = open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0777);
280             if (ilog < 0) {
281                 perror("Couldn't open input log");
282                 ExitProgram(1);
283             }
284             i += 2;
285         } else if (!strcmp(argv[i], "-olog")) {
286             if (i + 1 >= argc)
287                 FatalError("-olog requires an argument\n");
288             olog = open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0777);
289             if (olog < 0) {
290                 perror("Couldn't open output log");
291                 ExitProgram(1);
292             }
293             i += 2;
294         } else if (!strcmp(argv[i], "-alias")) {
295             if (i + 1 >= argc)
296                 FatalError("-alias requires an argument\n");
297             locale_alias = argv[i + 1];
298             i += 2;
299         } else if (!strcmp(argv[i], "-encoding")) {
300             if (i + 1 >= argc)
301                 FatalError("-encoding requires an argument\n");
302             locale_name = argv[i + 1];
303             i += 2;
304         } else if (!strcmp(argv[i], "-p")) {
305             pipe_option = 1;
306             i += 1;
307         } else {
308             FatalError("Unknown option %s\n", argv[i]);
309         }
310     }
311     return i;
312 }
313
314 static int
315 parseArgs(int argc, char **argv,
316           char *argv0,
317           char **path_return,
318           char ***argv_return)
319 {
320     char *path = NULL;
321     char **child_argv = NULL;
322
323     if (argc <= 0) {
324         char *shell;
325         shell = getenv("SHELL");
326         if (shell) {
327             path = strmalloc(shell);
328             if (!path)
329                 goto bail;
330         } else {
331             path = strmalloc("/bin/sh");
332             if (!path)
333                 goto bail;
334         }
335         child_argv = malloc(2 * sizeof(char *));
336         if (!child_argv)
337             goto bail;
338         if (argv0)
339             child_argv[0] = argv0;
340         else
341             child_argv[0] = my_basename(path);
342         child_argv[1] = NULL;
343     } else {
344         path = strmalloc(argv[0]);
345         if (!path)
346             goto bail;
347         child_argv = malloc((unsigned) (argc + 1) * sizeof(char *));
348         if (!child_argv) {
349             goto bail;
350         }
351         if (child_argv0)
352             child_argv[0] = argv0;
353         else
354             child_argv[0] = my_basename(argv[0]);
355         memcpy(child_argv + 1, argv + 1, (unsigned) (argc - 1) * sizeof(char *));
356         child_argv[argc] = NULL;
357     }
358
359     *path_return = path;
360     *argv_return = child_argv;
361     return 0;
362
363   bail:
364     if (path)
365         free(path);
366     if (argv)
367         free(argv);
368     return -1;
369 }
370
371 int
372 main(int argc, char **argv)
373 {
374     int rc;
375     int i;
376     char *l;
377
378 #ifdef HAVE_PUTENV
379     if ((l = strmalloc("NCURSES_NO_UTF8_ACS=1")) != 0)
380         putenv(l);
381 #endif
382
383     l = setlocale(LC_ALL, "");
384     if (!l)
385         ErrorF("Warning: couldn't set locale.\n");
386
387     inputState = allocIso2022();
388     if (!inputState)
389         FatalError("Couldn't create input state\n");
390
391     outputState = allocIso2022();
392     if (!outputState)
393         FatalError("Couldn't create output state\n");
394
395     if (l) {
396         locale_name = setlocale(LC_CTYPE, NULL);
397     } else {
398         locale_name = getenv("LC_ALL");
399         if (locale_name == NULL) {
400             locale_name = getenv("LC_CTYPE");
401             if (locale_name == NULL) {
402                 locale_name = getenv("LANG");
403             }
404         }
405     }
406
407     if (locale_name == NULL) {
408         ErrorF("Couldn't get locale name -- using C\n");
409         locale_name = "C";
410     }
411
412     i = parseOptions(argc, argv);
413     if (i < 0)
414         FatalError("Couldn't parse options\n");
415
416     rc = initIso2022(locale_name, NULL, outputState);
417     if (rc < 0)
418         FatalError("Couldn't init output state\n");
419
420     rc = mergeIso2022(inputState, outputState);
421     if (rc < 0)
422         FatalError("Couldn't init input state\n");
423
424     if (converter)
425         rc = convert(0, 1);
426     else
427         rc = condom(argc - i, argv + i);
428
429 #ifdef NO_LEAKS
430     ExitProgram(rc);
431 #endif
432     return rc;
433 }
434
435 static int
436 convert(int ifd, int ofd)
437 {
438     int rc, i;
439     unsigned char buf[BUFFER_SIZE];
440
441     rc = droppriv();
442     if (rc < 0) {
443         perror("Couldn't drop privileges");
444         ExitProgram(1);
445     }
446
447     while (1) {
448         i = (int) read(ifd, buf, (size_t) BUFFER_SIZE);
449         if (i <= 0) {
450             if (i < 0) {
451                 perror("Read error");
452                 ExitProgram(1);
453             }
454             break;
455         }
456         copyOut(outputState, ofd, buf, (unsigned) i);
457     }
458     return 0;
459 }
460
461 #ifdef SIGWINCH
462 static void
463 sigwinchHandler(int sig GCC_UNUSED)
464 {
465     sigwinch_queued = 1;
466 }
467 #endif
468
469 static void
470 sigchldHandler(int sig GCC_UNUSED)
471 {
472     sigchld_queued = 1;
473 }
474
475 static int
476 setup_io(int pty)
477 {
478     int rc;
479     int val;
480
481 #ifdef SIGWINCH
482     installHandler(SIGWINCH, sigwinchHandler);
483 #endif
484     installHandler(SIGCHLD, sigchldHandler);
485
486     rc = copyTermios(0, pty);
487     if (rc < 0)
488         FatalError("Couldn't copy terminal settings\n");
489
490     rc = setRawTermios();
491     if (rc < 0)
492         FatalError("Couldn't set terminal to raw\n");
493
494     val = fcntl(0, F_GETFL, 0);
495     if (val >= 0) {
496         fcntl(0, F_SETFL, val | O_NONBLOCK);
497     }
498     val = fcntl(pty, F_GETFL, 0);
499     if (val >= 0) {
500         fcntl(pty, F_SETFL, val | O_NONBLOCK);
501     }
502
503     setWindowSize(0, pty);
504
505     return rc;
506 }
507
508 static void
509 cleanup_io(int pty)
510 {
511     int val;
512
513 #ifdef SIGWINCH
514     installHandler(SIGWINCH, SIG_DFL);
515 #endif
516     installHandler(SIGCHLD, SIG_DFL);
517
518     val = fcntl(0, F_GETFL, 0);
519     if (val >= 0) {
520         fcntl(0, F_SETFL, val & ~O_NONBLOCK);
521     }
522     val = fcntl(pty, F_GETFL, 0);
523     if (val >= 0) {
524         fcntl(pty, F_SETFL, val & ~O_NONBLOCK);
525     }
526 }
527
528 static void
529 close_waitpipe(int which)
530 {
531     close(p2c_waitpipe[which]);
532     close(c2p_waitpipe[!which]);
533 }
534
535 static void
536 write_waitpipe(int fds[2])
537 {
538     IGNORE_RC(write(fds[1], "1", (size_t) 1));
539 }
540
541 static void
542 read_waitpipe(int fds[2])
543 {
544     char tmp[10];
545     IGNORE_RC(read(fds[0], tmp, (size_t) 1));
546 }
547
548 static int
549 condom(int argc, char **argv)
550 {
551     int pty;
552     int pid;
553     char *line;
554     char *path = 0;
555     char **child_argv = 0;
556     int rc;
557
558     rc = parseArgs(argc, argv, child_argv0,
559                    &path, &child_argv);
560     if (rc < 0)
561         FatalError("Couldn't parse arguments\n");
562
563     rc = allocatePty(&pty, &line);
564     if (rc < 0) {
565         perror("Couldn't allocate pty");
566         ExitProgram(1);
567     }
568
569     rc = droppriv();
570     if (rc < 0) {
571         perror("Couldn't drop privileges");
572         ExitProgram(1);
573     }
574
575     if (pipe_option) {
576         IGNORE_RC(pipe(p2c_waitpipe));
577         IGNORE_RC(pipe(c2p_waitpipe));
578     }
579
580     pid = fork();
581     if (pid < 0) {
582         perror("Couldn't fork");
583         ExitProgram(1);
584     }
585
586     if (pid == 0) {
587         close(pty);
588         if (pipe_option) {
589             close_waitpipe(1);
590         }
591         child(line, path, child_argv);
592     } else {
593         if (pipe_option) {
594             close_waitpipe(0);
595         }
596         free(child_argv);
597         free(path);
598         free(line);
599         parent(pid, pty);
600     }
601
602     return 0;
603 }
604
605 void
606 child(char *line, char *path, char *const argv[])
607 {
608     int tty;
609     int pgrp;
610
611     close(0);
612     close(1);
613     close(2);
614
615     pgrp = setsid();
616     if (pgrp < 0) {
617         kill(getppid(), SIGABRT);
618         ExitProgram(1);
619     }
620
621     tty = openTty(line);
622     if (tty < 0) {
623         kill(getppid(), SIGABRT);
624         ExitProgram(1);
625     }
626     if (pipe_option) {
627         write_waitpipe(c2p_waitpipe);
628     }
629
630     if (tty != 0)
631         dup2(tty, 0);
632     if (tty != 1)
633         dup2(tty, 1);
634     if (tty != 2)
635         dup2(tty, 2);
636
637     if (tty > 2)
638         close(tty);
639
640     if (pipe_option) {
641         read_waitpipe(p2c_waitpipe);
642         close_waitpipe(0);
643     }
644
645     execvp(path, argv);
646     perror("Couldn't exec");
647     ExitProgram(1);
648 }
649
650 void
651 parent(int pid GCC_UNUSED, int pty)
652 {
653     unsigned char buf[BUFFER_SIZE];
654     int i;
655     int rc;
656
657     if (pipe_option) {
658         read_waitpipe(c2p_waitpipe);
659     }
660
661     if (verbose) {
662         reportIso2022(outputState);
663     }
664     setup_io(pty);
665
666     if (pipe_option) {
667         write_waitpipe(p2c_waitpipe);
668         close_waitpipe(1);
669     }
670
671     for (;;) {
672         rc = waitForInput(0, pty);
673
674         if (sigwinch_queued) {
675             sigwinch_queued = 0;
676             setWindowSize(0, pty);
677         }
678
679         if (sigchld_queued && exitOnChild)
680             break;
681
682         if (rc > 0) {
683             if (rc & 2) {
684                 i = (int) read(pty, buf, (size_t) BUFFER_SIZE);
685                 if ((i == 0) || ((i < 0) && (errno != EAGAIN)))
686                     break;
687                 if (i > 0)
688                     copyOut(outputState, 0, buf, (unsigned) i);
689             }
690             if (rc & 1) {
691                 i = (int) read(0, buf, (size_t) BUFFER_SIZE);
692                 if ((i == 0) || ((i < 0) && (errno != EAGAIN)))
693                     break;
694                 if (i > 0)
695                     copyIn(inputState, pty, buf, i);
696             }
697         }
698     }
699
700     restoreTermios();
701     cleanup_io(pty);
702 }
703
704 #ifdef NO_LEAKS
705 void
706 luit_leaks(void)
707 {
708     destroyIso2022(inputState);
709     destroyIso2022(outputState);
710 }
711 #endif