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