]> git.ipfire.org Git - thirdparty/util-linux.git/blame - term-utils/agetty.c
Merge branch 'tests-mount' of https://github.com/rudimeier/util-linux
[thirdparty/util-linux.git] / term-utils / agetty.c
CommitLineData
b75c8134 1/*
53d55042
SK
2 * Alternate Getty (agetty) 'agetty' is a versatile, portable, easy to use
3 * replacement for getty on SunOS 4.1.x or the SAC ttymon/ttyadm/sacadm/pmadm
4 * suite on Solaris and other SVR4 systems. 'agetty' was written by Wietse
5 * Venema, enhanced by John DiMarco, and further enhanced by Dennis Cronin.
b75c8134
KZ
6 *
7 * Ported to Linux by Peter Orbaek <poe@daimi.aau.dk>
1683f6bf
DWF
8 * Adopt the mingetty features for a better support
9 * of virtual consoles by Werner Fink <werner@suse.de>
b75c8134 10 *
53d55042 11 * This program is freely distributable.
b75c8134 12 */
6dbe3af9
KZ
13#include <stdio.h>
14#include <unistd.h>
15#include <stdlib.h>
2b6fc908 16#include <string.h>
1961482a 17#include <termios.h>
6dbe3af9
KZ
18#include <signal.h>
19#include <errno.h>
3aa6b68f 20#include <sys/ioctl.h>
6dbe3af9
KZ
21#include <sys/types.h>
22#include <sys/stat.h>
bb280f79 23#include <sys/wait.h>
6dbe3af9 24#include <fcntl.h>
22853e4a 25#include <stdarg.h>
6dbe3af9
KZ
26#include <ctype.h>
27#include <utmp.h>
28#include <getopt.h>
22853e4a 29#include <time.h>
6dbe3af9 30#include <sys/file.h>
e61e66bd 31#include <sys/socket.h>
729def03 32#include <langinfo.h>
3aa6b68f 33#include <grp.h>
2b945eda
KZ
34#include <arpa/inet.h>
35#include <netdb.h>
36#include <ifaddrs.h>
0f283438 37#include <net/if.h>
df73ad46 38
8abcf290 39#include "strutils.h"
e12c9866 40#include "all-io.h"
7eda085c 41#include "nls.h"
e6590f06 42#include "pathnames.h"
e5b17b31 43#include "c.h"
1683f6bf 44#include "widechar.h"
879a7ab4 45#include "ttyutils.h"
6dbe3af9 46
fd6b7a7f 47#ifdef __linux__
0da025da 48# include <sys/kd.h>
53d55042
SK
49# include <sys/param.h>
50# define USE_SYSLOG
729def03
WF
51# ifndef DEFAULT_VCTERM
52# define DEFAULT_VCTERM "linux"
53# endif
fba8a535 54# if defined (__s390__) || defined (__s390x__)
f2bcda51
WF
55# define DEFAULT_TTYS0 "dumb"
56# define DEFAULT_TTY32 "ibm327x"
fba8a535
WF
57# define DEFAULT_TTYS1 "vt220"
58# endif
729def03
WF
59# ifndef DEFAULT_STERM
60# define DEFAULT_STERM "vt102"
61# endif
4585ec0b
ST
62#elif defined(__GNU__)
63# define USE_SYSLOG
64# ifndef DEFAULT_VCTERM
65# define DEFAULT_VCTERM "hurd"
66# endif
67# ifndef DEFAULT_STERM
68# define DEFAULT_STERM "vt102"
69# endif
729def03
WF
70#else
71# ifndef DEFAULT_VCTERM
72# define DEFAULT_VCTERM "vt100"
73# endif
74# ifndef DEFAULT_STERM
75# define DEFAULT_STERM "vt100"
76# endif
6dbe3af9
KZ
77#endif
78
53d55042 79/* If USE_SYSLOG is undefined all diagnostics go to /dev/console. */
6dbe3af9 80#ifdef USE_SYSLOG
53d55042 81# include <syslog.h>
6dbe3af9
KZ
82#endif
83
b75c8134
KZ
84/*
85 * Some heuristics to find out what environment we are in: if it is not
53d55042
SK
86 * System V, assume it is SunOS 4. The LOGIN_PROCESS is defined in System V
87 * utmp.h, which will select System V style getty.
b75c8134 88 */
53d55042
SK
89#ifdef LOGIN_PROCESS
90# define SYSV_STYLE
6dbe3af9
KZ
91#endif
92
b75c8134
KZ
93/*
94 * Things you may want to modify.
95 *
96 * If ISSUE is not defined, agetty will never display the contents of the
97 * /etc/issue file. You will not want to spit out large "issue" files at the
98 * wrong baud rate. Relevant for System V only.
99 *
100 * You may disagree with the default line-editing etc. characters defined
101 * below. Note, however, that DEL cannot be used for interrupt generation
102 * and for line editing at the same time.
103 */
6dbe3af9 104
53d55042 105/* Displayed before the login prompt. */
6dbe3af9 106#ifdef SYSV_STYLE
96cc7b0b 107# define ISSUE _PATH_ISSUE
53d55042 108# include <sys/utsname.h>
6dbe3af9
KZ
109#endif
110
53d55042 111/* Login prompt. */
e85281a8 112#define LOGIN "login: "
9aeb66dc 113#define LOGIN_ARGV_MAX 16 /* Numbers of args for login */
6dbe3af9 114
b75c8134
KZ
115/*
116 * When multiple baud rates are specified on the command line, the first one
117 * we will try is the first one specified.
118 */
6dbe3af9
KZ
119#define FIRST_SPEED 0
120
121/* Storage for command-line options. */
53d55042 122#define MAX_SPEED 10 /* max. nr. of baud rates */
6dbe3af9
KZ
123
124struct options {
bde20e1b
WF
125 int flags; /* toggle switches, see below */
126 int timeout; /* time-out period */
eb8e1f9f 127 char *autolog; /* login the user automatically */
e85281a8
DWF
128 char *chdir; /* Chdir before the login */
129 char *chroot; /* Chroot before the login */
bde20e1b 130 char *login; /* login program */
eb8e1f9f 131 char *logopt; /* options for login program */
bde20e1b 132 char *tty; /* name of tty */
729def03
WF
133 char *vcline; /* line of virtual console */
134 char *term; /* terminal type */
bde20e1b
WF
135 char *initstring; /* modem init string */
136 char *issue; /* alternative issue file */
cb872ac9
KZ
137 char *erasechars; /* string with erase chars */
138 char *killchars; /* string with kill chars */
b34f097e 139 char *osrelease; /* /etc/os-release data */
e85281a8
DWF
140 int delay; /* Sleep seconds before prompt */
141 int nice; /* Run login with this priority */
bde20e1b 142 int numspeed; /* number of baud rates to try */
ef264c83 143 int clocal; /* CLOCAL_MODE_* */
b9c73909 144 int kbmode; /* Keyboard mode if virtual console */
bde20e1b 145 speed_t speeds[MAX_SPEED]; /* baud rates to be tried */
6dbe3af9
KZ
146};
147
ef264c83
KZ
148enum {
149 CLOCAL_MODE_AUTO = 0,
150 CLOCAL_MODE_ALWAYS,
151 CLOCAL_MODE_NEVER
152};
153
53d55042
SK
154#define F_PARSE (1<<0) /* process modem status messages */
155#define F_ISSUE (1<<1) /* display /etc/issue */
156#define F_RTSCTS (1<<2) /* enable RTS/CTS flow control */
ef264c83 157
53d55042
SK
158#define F_INITSTRING (1<<4) /* initstring is set */
159#define F_WAITCRLF (1<<5) /* wait for CR or LF */
160#define F_CUSTISSUE (1<<6) /* give alternative issue file */
161#define F_NOPROMPT (1<<7) /* do not ask for login name! */
162#define F_LCUC (1<<8) /* support for *LCUC stty modes */
163#define F_KEEPSPEED (1<<9) /* follow baud rate from kernel */
164#define F_KEEPCFLAGS (1<<10) /* reuse c_cflags setup from kernel */
bde20e1b 165#define F_EIGHTBITS (1<<11) /* Assume 8bit-clean tty */
3aa6b68f
WF
166#define F_VCONSOLE (1<<12) /* This is a virtual console */
167#define F_HANGUP (1<<13) /* Do call vhangup(2) */
0da025da 168#define F_UTF8 (1<<14) /* We can do UTF8 */
eb8e1f9f 169#define F_LOGINPAUSE (1<<15) /* Wait for any key before dropping login prompt */
e85281a8
DWF
170#define F_NOCLEAR (1<<16) /* Do not clear the screen before prompting */
171#define F_NONL (1<<17) /* No newline before issue */
172#define F_NOHOSTNAME (1<<18) /* Do not show the hostname */
173#define F_LONGHNAME (1<<19) /* Show Full qualified hostname */
36601b23 174#define F_NOHINTS (1<<20) /* Don't print hints */
01095ae3 175#define F_REMOTE (1<<21) /* Add '-h fakehost' to login(1) command line */
0da025da
WF
176
177#define serial_tty_option(opt, flag) \
178 (((opt)->flags & (F_VCONSOLE|(flag))) == (flag))
6dbe3af9 179
fd6b7a7f 180struct Speedtab {
53d55042 181 long speed;
bde20e1b 182 speed_t code;
fd6b7a7f
KZ
183};
184
1a204cd2 185static const struct Speedtab speedtab[] = {
53d55042
SK
186 {50, B50},
187 {75, B75},
188 {110, B110},
189 {134, B134},
190 {150, B150},
191 {200, B200},
192 {300, B300},
193 {600, B600},
194 {1200, B1200},
195 {1800, B1800},
196 {2400, B2400},
197 {4800, B4800},
198 {9600, B9600},
09625c2e 199#ifdef B19200
53d55042 200 {19200, B19200},
09625c2e 201#elif defined(EXTA)
53d55042 202 {19200, EXTA},
fd6b7a7f 203#endif
09625c2e
JB
204#ifdef B38400
205 {38400, B38400},
206#elif defined(EXTB)
53d55042 207 {38400, EXTB},
fd6b7a7f
KZ
208#endif
209#ifdef B57600
53d55042 210 {57600, B57600},
fd6b7a7f
KZ
211#endif
212#ifdef B115200
53d55042 213 {115200, B115200},
fd6b7a7f
KZ
214#endif
215#ifdef B230400
53d55042 216 {230400, B230400},
f28dde8e
JB
217#endif
218#ifdef B460800
219 {460800, B460800},
220#endif
221#ifdef B500000
222 {500000, B500000},
223#endif
224#ifdef B576000
225 {576000, B576000},
226#endif
227#ifdef B921600
228 {921600, B921600},
229#endif
230#ifdef B1000000
231 {1000000, B1000000},
232#endif
233#ifdef B1152000
234 {1152000, B1152000},
235#endif
236#ifdef B1500000
237 {1500000, B1500000},
238#endif
239#ifdef B2000000
240 {2000000, B2000000},
241#endif
242#ifdef B2500000
243 {2500000, B2500000},
244#endif
245#ifdef B3000000
246 {3000000, B3000000},
247#endif
248#ifdef B3500000
249 {3500000, B3500000},
250#endif
251#ifdef B4000000
252 {4000000, B4000000},
fd6b7a7f 253#endif
53d55042 254 {0, 0},
6dbe3af9
KZ
255};
256
729def03 257static void init_special_char(char* arg, struct options *op);
53d55042
SK
258static void parse_args(int argc, char **argv, struct options *op);
259static void parse_speeds(struct options *op, char *arg);
729def03
WF
260static void update_utmp(struct options *op);
261static void open_tty(char *tty, struct termios *tp, struct options *op);
53d55042 262static void termio_init(struct options *op, struct termios *tp);
0da025da 263static void reset_vc (const struct options *op, struct termios *tp);
53d55042 264static void auto_baud(struct termios *tp);
2b945eda
KZ
265static void output_special_char (unsigned char c, struct options *op,
266 struct termios *tp, FILE *fp);
53d55042
SK
267static void do_prompt(struct options *op, struct termios *tp);
268static void next_speed(struct options *op, struct termios *tp);
269static char *get_logname(struct options *op,
270 struct termios *tp, struct chardata *cp);
271static void termio_final(struct options *op,
272 struct termios *tp, struct chardata *cp);
273static int caps_lock(char *s);
bde20e1b
WF
274static speed_t bcode(char *s);
275static void usage(FILE * out) __attribute__((__noreturn__));
763d7a87
KZ
276static void log_err(const char *, ...) __attribute__((__noreturn__))
277 __attribute__((__format__(printf, 1, 2)));
278static void log_warn (const char *, ...)
279 __attribute__((__format__(printf, 1, 2)));
a694957a 280static ssize_t append(char *dest, size_t len, const char *sep, const char *src);
9aeb66dc
KZ
281static void check_username (const char* nm);
282static void login_options_to_argv(char *argv[], int *argc, char *str, char *username);
bb280f79 283static int plymouth_command(const char* arg);
6dbe3af9 284
eb63b9b8 285/* Fake hostname for ut_host specified on command line. */
bde20e1b 286static char *fakehost;
eb63b9b8 287
6dbe3af9 288#ifdef DEBUGGING
bb280f79 289# include "closestream.h"
46568b00 290# ifndef DEBUG_OUTPUT
bb280f79 291# define DEBUG_OUTPUT "/dev/tty10"
46568b00
KZ
292# endif
293# define debug(s) do { fprintf(dbf,s); fflush(dbf); } while (0)
6dbe3af9
KZ
294FILE *dbf;
295#else
46568b00 296# define debug(s) do { ; } while (0)
efcf26f4 297#endif
6dbe3af9 298
53d55042 299int main(int argc, char **argv)
6dbe3af9 300{
9aeb66dc 301 char *username = NULL; /* login name, given to /bin/login */
eb8e1f9f
WF
302 struct chardata chardata; /* will be set by get_logname() */
303 struct termios termios; /* terminal mode bits */
1a204cd2 304 struct options options = {
eb8e1f9f
WF
305 .flags = F_ISSUE, /* show /etc/issue (SYSV_STYLE) */
306 .login = _PATH_LOGIN, /* default login program */
eb8e1f9f 307 .tty = "tty1", /* default tty line */
5676f365 308 .issue = ISSUE /* default issue file */
70bedfa1 309 };
9aeb66dc
KZ
310 char *login_argv[LOGIN_ARGV_MAX + 1];
311 int login_argc = 0;
3aa6b68f
WF
312 struct sigaction sa, sa_hup, sa_quit, sa_int;
313 sigset_t set;
70bedfa1
KZ
314
315 setlocale(LC_ALL, "");
316 bindtextdomain(PACKAGE, LOCALEDIR);
317 textdomain(PACKAGE);
b75c8134 318
3aa6b68f
WF
319 /* In case vhangup(2) has to called */
320 sa.sa_handler = SIG_IGN;
321 sa.sa_flags = SA_RESTART;
322 sigemptyset (&sa.sa_mask);
323 sigaction(SIGHUP, &sa, &sa_hup);
324 sigaction(SIGQUIT, &sa, &sa_quit);
325 sigaction(SIGINT, &sa, &sa_int);
326
6dbe3af9 327#ifdef DEBUGGING
1aba8336 328 dbf = fopen(DEBUG_OUTPUT, "w");
bb280f79
WF
329 for (int i = 1; i < argc; i++) {
330 if (i > 1)
331 debug(" ");
53d55042 332 debug(argv[i]);
bb280f79
WF
333 }
334 debug("\n");
53d55042 335#endif /* DEBUGGING */
6dbe3af9 336
70bedfa1 337 /* Parse command-line arguments. */
70bedfa1 338 parse_args(argc, argv, &options);
6dbe3af9 339
9aeb66dc
KZ
340 login_argv[login_argc++] = options.login; /* set login program name */
341
70bedfa1 342 /* Update the utmp file. */
6dbe3af9 343#ifdef SYSV_STYLE
729def03 344 update_utmp(&options);
6dbe3af9 345#endif
e85281a8
DWF
346 if (options.delay)
347 sleep(options.delay);
348
70bedfa1 349 debug("calling open_tty\n");
53d55042 350
70bedfa1 351 /* Open the tty as standard { input, output, error }. */
729def03 352 open_tty(options.tty, &termios, &options);
70bedfa1 353
3aa6b68f
WF
354 /* Unmask SIGHUP if inherited */
355 sigemptyset(&set);
356 sigaddset(&set, SIGHUP);
357 sigprocmask(SIG_UNBLOCK, &set, NULL);
358 sigaction(SIGHUP, &sa_hup, NULL);
359
70bedfa1
KZ
360 tcsetpgrp(STDIN_FILENO, getpid());
361 /* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */
362 debug("calling termio_init\n");
53d55042 363 termio_init(&options, &termios);
70bedfa1 364
53d55042 365 /* Write the modem init string and DO NOT flush the buffers. */
eb8e1f9f
WF
366 if (serial_tty_option(&options, F_INITSTRING) &&
367 options.initstring && *options.initstring != '\0') {
70bedfa1 368 debug("writing init string\n");
729def03 369 write_all(STDOUT_FILENO, options.initstring,
3aa6b68f 370 strlen(options.initstring));
70bedfa1
KZ
371 }
372
714cff30
KZ
373 if (options.flags & F_VCONSOLE || options.clocal != CLOCAL_MODE_ALWAYS)
374 /* Go to blocking mode unless -L is specified, this change
375 * affects stdout, stdin and stderr as all the file descriptors
376 * are created by dup(). */
70bedfa1 377 fcntl(STDOUT_FILENO, F_SETFL,
53d55042 378 fcntl(STDOUT_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
70bedfa1
KZ
379
380 /* Optionally detect the baud rate from the modem status message. */
381 debug("before autobaud\n");
0da025da 382 if (serial_tty_option(&options, F_PARSE))
70bedfa1
KZ
383 auto_baud(&termios);
384
385 /* Set the optional timer. */
386 if (options.timeout)
763d7a87 387 alarm((unsigned) options.timeout);
70bedfa1 388
53d55042 389 /* Optionally wait for CR or LF before writing /etc/issue */
0da025da 390 if (serial_tty_option(&options, F_WAITCRLF)) {
70bedfa1
KZ
391 char ch;
392
393 debug("waiting for cr-lf\n");
53d55042
SK
394 while (read(STDIN_FILENO, &ch, 1) == 1) {
395 /* Strip "parity bit". */
396 ch &= 0x7f;
fd6b7a7f 397#ifdef DEBUGGING
70bedfa1 398 fprintf(dbf, "read %c\n", ch);
fd6b7a7f 399#endif
53d55042
SK
400 if (ch == '\n' || ch == '\r')
401 break;
70bedfa1 402 }
726f69e2 403 }
726f69e2 404
f5664477 405 INIT_CHARDATA(&chardata);
933956cb
KZ
406
407 if (options.autolog) {
408 debug("doing auto login\n");
409 username = options.autolog;
410 }
411
1683f6bf 412 if ((options.flags & F_NOPROMPT) == 0) {
eb8e1f9f 413 if (options.autolog) {
933956cb 414 /* Autologin prompt */
eb8e1f9f 415 do_prompt(&options, &termios);
8c219bf4 416 printf(_("%s%s (automatic login)\n"), LOGIN, options.autolog);
eb8e1f9f
WF
417 } else {
418 /* Read the login name. */
419 debug("reading login name\n");
9aeb66dc 420 while ((username =
f8bd089b 421 get_logname(&options, &termios, &chardata)) == NULL)
eb8e1f9f
WF
422 if ((options.flags & F_VCONSOLE) == 0)
423 next_speed(&options, &termios);
424 }
70bedfa1 425 }
6dbe3af9 426
70bedfa1 427 /* Disable timer. */
70bedfa1 428 if (options.timeout)
53d55042 429 alarm(0);
6dbe3af9 430
0da025da
WF
431 if ((options.flags & F_VCONSOLE) == 0) {
432 /* Finalize the termios settings. */
433 termio_final(&options, &termios, &chardata);
6dbe3af9 434
0da025da
WF
435 /* Now the newline character should be properly written. */
436 write_all(STDOUT_FILENO, "\r\n", 2);
437 }
6dbe3af9 438
763d7a87
KZ
439 sigaction(SIGQUIT, &sa_quit, NULL);
440 sigaction(SIGINT, &sa_int, NULL);
3aa6b68f 441
9aeb66dc
KZ
442 if (username)
443 check_username(username);
444
445 if (options.logopt) {
446 /*
447 * The --login-options completely overwrites the default
448 * way how agetty composes login(1) command line.
449 */
450 login_options_to_argv(login_argv, &login_argc,
451 options.logopt, username);
01095ae3
KZ
452 } else {
453 if (fakehost && (options.flags & F_REMOTE)) {
454 login_argv[login_argc++] = "-h";
455 login_argv[login_argc++] = fakehost;
456 }
457 if (username) {
458 if (options.autolog)
459 login_argv[login_argc++] = "-f";
460 else
461 login_argv[login_argc++] = "--";
462 login_argv[login_argc++] = username;
463 }
9aeb66dc 464 }
eb8e1f9f 465
9aeb66dc 466 login_argv[login_argc] = NULL; /* last login argv */
eb8e1f9f 467
e85281a8
DWF
468 if (options.chroot) {
469 if (chroot(options.chroot) < 0)
470 log_err(_("%s: can't change root directory %s: %m"),
471 options.tty, options.chroot);
472 }
473 if (options.chdir) {
474 if (chdir(options.chdir) < 0)
475 log_err(_("%s: can't change working directory %s: %m"),
476 options.tty, options.chdir);
477 }
478 if (options.nice) {
479 if (nice(options.nice) < 0)
480 log_warn(_("%s: can't change process priority: %m"),
481 options.tty);
482 }
f614b73c 483 free(options.osrelease);
1aba8336 484#ifdef DEBUGGING
1aba8336
SK
485 if (close_stream(dbf) != 0)
486 log_err("write failed: %s", DEBUG_OUTPUT);
487#endif
488
70bedfa1 489 /* Let the login program take care of password validation. */
9aeb66dc
KZ
490 execv(options.login, login_argv);
491 log_err(_("%s: can't exec %s: %m"), options.tty, login_argv[0]);
492}
493
494/*
495 * Returns : @str if \u not found
496 * : @username if @str equal to "\u"
497 * : newly allocated string if \u mixed with something other
498 */
499static char *replace_u(char *str, char *username)
500{
501 char *entry = NULL, *p = str;
502 size_t usz = username ? strlen(username) : 0;
503
504 while (*p) {
505 size_t sz;
506 char *tp, *old = entry;
507
508 if (memcmp(p, "\\u", 2)) {
509 p++;
510 continue; /* no \u */
511 }
512 sz = strlen(str);
513
3797cf25 514 if (p == str && sz == 2) {
9aeb66dc 515 /* 'str' contains only '\u' */
3797cf25 516 free(old);
9aeb66dc 517 return username;
3797cf25 518 }
9aeb66dc
KZ
519
520 tp = entry = malloc(sz + usz);
521 if (!tp)
522 log_err(_("failed to allocate memory: %m"));
523
524 if (p != str) {
525 /* copy chars befor \u */
526 memcpy(tp, str, p - str);
527 tp += p - str;
528 }
529 if (usz) {
530 /* copy username */
531 memcpy(tp, username, usz);
532 tp += usz;
533 }
534 if (*(p + 2))
535 /* copy chars after \u + \0 */
536 memcpy(tp, p + 2, sz - (p - str) - 1);
537 else
538 *tp = '\0';
539
540 p = tp;
541 str = entry;
542 free(old);
543 }
544
545 return entry ? entry : str;
546}
547
548static void login_options_to_argv(char *argv[], int *argc,
549 char *str, char *username)
550{
551 char *p;
552 int i = *argc;
553
554 while (str && isspace(*str))
555 str++;
556 p = str;
557
558 while (p && *p && i < LOGIN_ARGV_MAX) {
559 if (isspace(*p)) {
560 *p = '\0';
561 while (isspace(*++p))
562 ;
563 if (*p) {
564 argv[i++] = replace_u(str, username);
565 str = p;
566 }
567 } else
568 p++;
569 }
570 if (str && *str && i < LOGIN_ARGV_MAX)
571 argv[i++] = replace_u(str, username);
572 *argc = i;
6dbe3af9
KZ
573}
574
f2bcda51
WF
575#define is_speed(str) (strlen((str)) == strspn((str), "0123456789,"))
576
53d55042 577/* Parse command-line arguments. */
bde20e1b 578static void parse_args(int argc, char **argv, struct options *op)
6dbe3af9 579{
53d55042 580 int c;
70bedfa1
KZ
581
582 enum {
583 VERSION_OPTION = CHAR_MAX + 1,
36601b23 584 NOHINTS_OPTION,
e85281a8
DWF
585 NOHOSTNAME_OPTION,
586 LONGHOSTNAME_OPTION,
cb872ac9
KZ
587 HELP_OPTION,
588 ERASE_CHARS_OPTION,
589 KILL_CHARS_OPTION,
70bedfa1 590 };
3aa6b68f 591 const struct option longopts[] = {
70bedfa1 592 { "8bits", no_argument, 0, '8' },
eb8e1f9f 593 { "autologin", required_argument, 0, 'a' },
70bedfa1 594 { "noreset", no_argument, 0, 'c' },
e85281a8
DWF
595 { "chdir", required_argument, 0, 'C' },
596 { "delay", required_argument, 0, 'd' },
01095ae3 597 { "remote", no_argument, 0, 'E' },
70bedfa1
KZ
598 { "issue-file", required_argument, 0, 'f' },
599 { "flow-control", no_argument, 0, 'h' },
600 { "host", required_argument, 0, 'H' },
601 { "noissue", no_argument, 0, 'i' },
602 { "init-string", required_argument, 0, 'I' },
e85281a8 603 { "noclear", no_argument, 0, 'J' },
70bedfa1 604 { "login-program", required_argument, 0, 'l' },
ef264c83 605 { "local-line", optional_argument, 0, 'L' },
70bedfa1
KZ
606 { "extract-baud", no_argument, 0, 'm' },
607 { "skip-login", no_argument, 0, 'n' },
e85281a8 608 { "nonewline", no_argument, 0, 'N' },
eb8e1f9f 609 { "login-options", required_argument, 0, 'o' },
3fc62fd3 610 { "login-pause", no_argument, 0, 'p' },
e85281a8
DWF
611 { "nice", required_argument, 0, 'P' },
612 { "chroot", required_argument, 0, 'r' },
3aa6b68f 613 { "hangup", no_argument, 0, 'R' },
70bedfa1
KZ
614 { "keep-baud", no_argument, 0, 's' },
615 { "timeout", required_argument, 0, 't' },
616 { "detect-case", no_argument, 0, 'U' },
617 { "wait-cr", no_argument, 0, 'w' },
36601b23 618 { "nohints", no_argument, 0, NOHINTS_OPTION },
3fc62fd3 619 { "nohostname", no_argument, 0, NOHOSTNAME_OPTION },
e85281a8 620 { "long-hostname", no_argument, 0, LONGHOSTNAME_OPTION },
70bedfa1
KZ
621 { "version", no_argument, 0, VERSION_OPTION },
622 { "help", no_argument, 0, HELP_OPTION },
cb872ac9
KZ
623 { "erase-chars", required_argument, 0, ERASE_CHARS_OPTION },
624 { "kill-chars", required_argument, 0, KILL_CHARS_OPTION },
70bedfa1
KZ
625 { NULL, 0, 0, 0 }
626 };
627
763d7a87 628 while ((c = getopt_long(argc, argv,
6f964206 629 "8a:cC:d:Ef:hH:iI:Jl:L::mnNo:pP:r:Rst:Uw", longopts,
53d55042 630 NULL)) != -1) {
70bedfa1
KZ
631 switch (c) {
632 case '8':
bde20e1b 633 op->flags |= F_EIGHTBITS;
70bedfa1 634 break;
eb8e1f9f
WF
635 case 'a':
636 op->autolog = optarg;
637 break;
70bedfa1
KZ
638 case 'c':
639 op->flags |= F_KEEPCFLAGS;
640 break;
e85281a8
DWF
641 case 'C':
642 op->chdir = optarg;
643 break;
644 case 'd':
645 op->delay = atoi(optarg);
646 break;
01095ae3
KZ
647 case 'E':
648 op->flags |= F_REMOTE;
649 break;
53d55042 650 case 'f':
70bedfa1
KZ
651 op->flags |= F_CUSTISSUE;
652 op->issue = optarg;
653 break;
53d55042 654 case 'h':
70bedfa1
KZ
655 op->flags |= F_RTSCTS;
656 break;
53d55042 657 case 'H':
70bedfa1
KZ
658 fakehost = optarg;
659 break;
53d55042 660 case 'i':
70bedfa1
KZ
661 op->flags &= ~F_ISSUE;
662 break;
663 case 'I':
729def03
WF
664 init_special_char(optarg, op);
665 op->flags |= F_INITSTRING;
666 break;
e85281a8
DWF
667 case 'J':
668 op->flags |= F_NOCLEAR;
669 break;
70bedfa1 670 case 'l':
53d55042 671 op->login = optarg;
70bedfa1 672 break;
53d55042 673 case 'L':
ef264c83
KZ
674 /* -L and -L=always have the same meaning */
675 op->clocal = CLOCAL_MODE_ALWAYS;
676 if (optarg) {
677 if (strcmp(optarg, "=always") == 0)
678 op->clocal = CLOCAL_MODE_ALWAYS;
679 else if (strcmp(optarg, "=never") == 0)
680 op->clocal = CLOCAL_MODE_NEVER;
681 else if (strcmp(optarg, "=auto") == 0)
682 op->clocal = CLOCAL_MODE_AUTO;
683 else
38ae77d7 684 log_err(_("invalid argument of --local-line"));
ef264c83 685 }
70bedfa1 686 break;
53d55042 687 case 'm':
70bedfa1
KZ
688 op->flags |= F_PARSE;
689 break;
690 case 'n':
691 op->flags |= F_NOPROMPT;
692 break;
eb8e1f9f
WF
693 case 'o':
694 op->logopt = optarg;
695 break;
696 case 'p':
697 op->flags |= F_LOGINPAUSE;
698 break;
e85281a8
DWF
699 case 'P':
700 op->nice = atoi(optarg);
701 break;
702 case 'r':
703 op->chroot = optarg;
704 break;
3aa6b68f
WF
705 case 'R':
706 op->flags |= F_HANGUP;
707 break;
70bedfa1 708 case 's':
53d55042 709 op->flags |= F_KEEPSPEED;
70bedfa1 710 break;
53d55042 711 case 't':
70bedfa1 712 if ((op->timeout = atoi(optarg)) <= 0)
bde20e1b 713 log_err(_("bad timeout value: %s"), optarg);
70bedfa1
KZ
714 break;
715 case 'U':
716 op->flags |= F_LCUC;
717 break;
718 case 'w':
719 op->flags |= F_WAITCRLF;
720 break;
36601b23
KZ
721 case NOHINTS_OPTION:
722 op->flags |= F_NOHINTS;
723 break;
e85281a8
DWF
724 case NOHOSTNAME_OPTION:
725 op->flags |= F_NOHOSTNAME;
726 break;
727 case LONGHOSTNAME_OPTION:
728 op->flags |= F_LONGHNAME;
729 break;
cb872ac9
KZ
730 case ERASE_CHARS_OPTION:
731 op->erasechars = optarg;
732 break;
733 case KILL_CHARS_OPTION:
734 op->killchars = optarg;
735 break;
70bedfa1
KZ
736 case VERSION_OPTION:
737 printf(_("%s from %s\n"), program_invocation_short_name,
53d55042 738 PACKAGE_STRING);
70bedfa1
KZ
739 exit(EXIT_SUCCESS);
740 case HELP_OPTION:
741 usage(stdout);
742 default:
743 usage(stderr);
726f69e2 744 }
6dbe3af9 745 }
70bedfa1
KZ
746
747 debug("after getopt loop\n");
748
729def03 749 if (argc < optind + 1) {
bde20e1b 750 log_warn(_("not enough arguments"));
70bedfa1
KZ
751 usage(stderr);
752 }
753
729def03 754 /* Accept "tty", "baudrate tty", and "tty baudrate". */
f2bcda51 755 if (is_speed(argv[optind])) {
53d55042
SK
756 /* Assume BSD style speed. */
757 parse_speeds(op, argv[optind++]);
729def03
WF
758 if (argc < optind + 1) {
759 warn(_("not enough arguments"));
760 usage(stderr);
761 }
762 op->tty = argv[optind++];
70bedfa1 763 } else {
53d55042 764 op->tty = argv[optind++];
729def03
WF
765 if (argc > optind) {
766 char *v = argv[optind++];
f2bcda51 767 if (is_speed(v))
729def03
WF
768 parse_speeds(op, v);
769 else
770 op->speeds[op->numspeed++] = bcode("9600");
771 }
70bedfa1
KZ
772 }
773
729def03
WF
774 /* On virtual console remember the line which is used for */
775 if (strncmp(op->tty, "tty", 3) == 0 &&
776 strspn(op->tty + 3, "0123456789") == strlen(op->tty+3))
777 op->vcline = op->tty+3;
778
70bedfa1 779 if (argc > optind && argv[optind])
729def03 780 op->term = argv[optind];
6dbe3af9 781
66ee8158 782#ifdef DO_DEVFS_FIDDLING
70bedfa1 783 /*
53d55042 784 * Some devfs junk, following Goswin Brederlow:
70bedfa1
KZ
785 * turn ttyS<n> into tts/<n>
786 * turn tty<n> into vc/<n>
53d55042 787 * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=72241
70bedfa1
KZ
788 */
789 if (op->tty && strlen(op->tty) < 90) {
790 char dev_name[100];
791 struct stat st;
792
793 if (strncmp(op->tty, "ttyS", 4) == 0) {
794 strcpy(dev_name, "/dev/");
795 strcat(dev_name, op->tty);
796 if (stat(dev_name, &st) < 0) {
797 strcpy(dev_name, "/dev/tts/");
798 strcat(dev_name, op->tty + 4);
ca8e91a4
KZ
799 if (stat(dev_name, &st) == 0) {
800 op->tty = strdup(dev_name + 5);
801 if (!op->tty)
802 log_err(_("failed to allocate memory: %m"));
803 }
70bedfa1
KZ
804 }
805 } else if (strncmp(op->tty, "tty", 3) == 0) {
806 strcpy(dev_name, "/dev/");
807 strncat(dev_name, op->tty, 90);
808 if (stat(dev_name, &st) < 0) {
809 strcpy(dev_name, "/dev/vc/");
810 strcat(dev_name, op->tty + 3);
ca8e91a4
KZ
811 if (stat(dev_name, &st) == 0) {
812 op->tty = strdup(dev_name + 5);
813 if (!op->tty)
814 log_err(_("failed to allocate memory: %m"));
815 }
70bedfa1
KZ
816 }
817 }
818 }
53d55042 819#endif /* DO_DEVFS_FIDDLING */
66ee8158 820
70bedfa1 821 debug("exiting parseargs\n");
6dbe3af9
KZ
822}
823
53d55042 824/* Parse alternate baud rates. */
bde20e1b 825static void parse_speeds(struct options *op, char *arg)
6dbe3af9 826{
53d55042 827 char *cp;
6dbe3af9 828
f021834e 829 debug("entered parse_speeds\n");
f8bd089b 830 for (cp = strtok(arg, ","); cp != NULL; cp = strtok((char *)0, ",")) {
70bedfa1 831 if ((op->speeds[op->numspeed++] = bcode(cp)) <= 0)
bde20e1b 832 log_err(_("bad speed: %s"), cp);
70bedfa1 833 if (op->numspeed >= MAX_SPEED)
bde20e1b 834 log_err(_("too many alternate speeds"));
70bedfa1
KZ
835 }
836 debug("exiting parsespeeds\n");
6dbe3af9
KZ
837}
838
839#ifdef SYSV_STYLE
840
53d55042 841/* Update our utmp entry. */
729def03 842static void update_utmp(struct options *op)
6dbe3af9 843{
53d55042
SK
844 struct utmp ut;
845 time_t t;
729def03
WF
846 pid_t pid = getpid();
847 pid_t sid = getsid(0);
848 char *vcline = op->vcline;
849 char *line = op->tty;
53d55042 850 struct utmp *utp;
70bedfa1
KZ
851
852 /*
853 * The utmp file holds miscellaneous information about things started by
854 * /sbin/init and other system-related events. Our purpose is to update
855 * the utmp entry for the current process, in particular the process type
856 * and the tty line we are listening to. Return successfully only if the
857 * utmp file can be opened for update, and if we are able to find our
858 * entry in the utmp file.
859 */
70bedfa1
KZ
860 utmpname(_PATH_UTMP);
861 setutent();
862
53d55042 863 /*
729def03 864 * Find my pid in utmp.
53d55042
SK
865 *
866 * FIXME: Earlier (when was that?) code here tested only utp->ut_type !=
867 * INIT_PROCESS, so maybe the >= here should be >.
868 *
869 * FIXME: The present code is taken from login.c, so if this is changed,
870 * maybe login has to be changed as well (is this true?).
871 */
70bedfa1 872 while ((utp = getutent()))
729def03 873 if (utp->ut_pid == pid
70bedfa1
KZ
874 && utp->ut_type >= INIT_PROCESS
875 && utp->ut_type <= DEAD_PROCESS)
876 break;
877
878 if (utp) {
879 memcpy(&ut, utp, sizeof(ut));
880 } else {
53d55042 881 /* Some inits do not initialize utmp. */
70bedfa1 882 memset(&ut, 0, sizeof(ut));
729def03
WF
883 if (vcline && *vcline)
884 /* Standard virtual console devices */
763d7a87 885 strncpy(ut.ut_id, vcline, sizeof(ut.ut_id));
729def03
WF
886 else {
887 size_t len = strlen(line);
888 char * ptr;
763d7a87
KZ
889 if (len >= sizeof(ut.ut_id))
890 ptr = line + len - sizeof(ut.ut_id);
729def03
WF
891 else
892 ptr = line;
763d7a87 893 strncpy(ut.ut_id, ptr, sizeof(ut.ut_id));
729def03 894 }
70bedfa1
KZ
895 }
896
897 strncpy(ut.ut_user, "LOGIN", sizeof(ut.ut_user));
898 strncpy(ut.ut_line, line, sizeof(ut.ut_line));
899 if (fakehost)
900 strncpy(ut.ut_host, fakehost, sizeof(ut.ut_host));
901 time(&t);
cfa7fe89
SK
902#if defined(_HAVE_UT_TV)
903 ut.ut_tv.tv_sec = t;
904#else
70bedfa1 905 ut.ut_time = t;
cfa7fe89 906#endif
70bedfa1 907 ut.ut_type = LOGIN_PROCESS;
729def03
WF
908 ut.ut_pid = pid;
909 ut.ut_session = sid;
70bedfa1
KZ
910
911 pututline(&ut);
912 endutent();
913
914 {
48d7b13a 915#ifdef HAVE_UPDWTMP
70bedfa1 916 updwtmp(_PATH_WTMP, &ut);
5c36a0eb 917#else
70bedfa1
KZ
918 int ut_fd;
919 int lf;
920
53d55042 921 if ((lf = open(_PATH_WTMPLOCK, O_CREAT | O_WRONLY, 0660)) >= 0) {
70bedfa1 922 flock(lf, LOCK_EX);
53d55042
SK
923 if ((ut_fd =
924 open(_PATH_WTMP, O_APPEND | O_WRONLY)) >= 0) {
729def03 925 write_all(ut_fd, &ut, sizeof(ut));
70bedfa1
KZ
926 close(ut_fd);
927 }
928 flock(lf, LOCK_UN);
929 close(lf);
930 }
53d55042 931#endif /* HAVE_UPDWTMP */
70bedfa1 932 }
6dbe3af9
KZ
933}
934
53d55042 935#endif /* SYSV_STYLE */
6dbe3af9 936
53d55042 937/* Set up tty as stdin, stdout & stderr. */
729def03 938static void open_tty(char *tty, struct termios *tp, struct options *op)
6dbe3af9 939{
3aa6b68f 940 const pid_t pid = getpid();
b9c73909 941 int closed = 0;
6dbe3af9 942
3aa6b68f
WF
943 /* Set up new standard input, unless we are given an already opened port. */
944
945 if (strcmp(tty, "-") != 0) {
946 char buf[PATH_MAX+1];
947 struct group *gr = NULL;
70bedfa1 948 struct stat st;
3aa6b68f
WF
949 int fd, len;
950 pid_t tid;
951 gid_t gid = 0;
952
953 /* Use tty group if available */
954 if ((gr = getgrnam("tty")))
955 gid = gr->gr_gid;
956
763d7a87
KZ
957 if (((len = snprintf(buf, sizeof(buf), "/dev/%s", tty)) >=
958 (int)sizeof(buf)) || (len < 0))
3aa6b68f
WF
959 log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
960
961 /*
962 * There is always a race between this reset and the call to
963 * vhangup() that s.o. can use to get access to your tty.
964 * Linux login(1) will change tty permissions. Use root owner and group
965 * with permission -rw------- for the period between getty and login.
966 */
91984e93 967 if (chown(buf, 0, gid) || chmod(buf, (gid ? 0620 : 0600))) {
3aa6b68f
WF
968 if (errno == EROFS)
969 log_warn("%s: %m", buf);
970 else
971 log_err("%s: %m", buf);
972 }
6dbe3af9 973
3aa6b68f
WF
974 /* Open the tty as standard input. */
975 if ((fd = open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0)) < 0)
976 log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
977
978 /* Sanity checks... */
3aa6b68f 979 if (fstat(fd, &st) < 0)
763d7a87 980 log_err("%s: %m", buf);
70bedfa1 981 if ((st.st_mode & S_IFMT) != S_IFCHR)
bde20e1b 982 log_err(_("/dev/%s: not a character device"), tty);
c6b35139
KZ
983 if (!isatty(fd))
984 log_err(_("/dev/%s: not a tty"), tty);
6dbe3af9 985
3aa6b68f 986 if (((tid = tcgetsid(fd)) < 0) || (pid != tid)) {
763d7a87 987 if (ioctl(fd, TIOCSCTTY, 1) == -1)
8c219bf4 988 log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty);
3aa6b68f
WF
989 }
990
783b08fc
KZ
991 close(STDIN_FILENO);
992 errno = 0;
993
3aa6b68f 994 if (op->flags & F_HANGUP) {
783b08fc
KZ
995
996 if (ioctl(fd, TIOCNOTTY))
997 debug("TIOCNOTTY ioctl failed\n");
998
3aa6b68f 999 /*
783b08fc
KZ
1000 * Let's close all file decriptors before vhangup
1001 * https://lkml.org/lkml/2012/6/5/145
3aa6b68f 1002 */
783b08fc
KZ
1003 close(fd);
1004 close(STDOUT_FILENO);
1005 close(STDERR_FILENO);
1006 errno = 0;
1007 closed = 1;
1008
3aa6b68f 1009 if (vhangup())
8c219bf4 1010 log_err(_("/dev/%s: vhangup() failed: %m"), tty);
783b08fc
KZ
1011 } else
1012 close(fd);
6dbe3af9 1013
70bedfa1 1014 debug("open(2)\n");
3aa6b68f
WF
1015 if (open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0) != 0)
1016 log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
783b08fc 1017
3aa6b68f 1018 if (((tid = tcgetsid(STDIN_FILENO)) < 0) || (pid != tid)) {
763d7a87 1019 if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) == -1)
8c219bf4 1020 log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty);
3aa6b68f
WF
1021 }
1022
70bedfa1 1023 } else {
3aa6b68f 1024
70bedfa1
KZ
1025 /*
1026 * Standard input should already be connected to an open port. Make
1027 * sure it is open for read/write.
1028 */
3aa6b68f 1029
70bedfa1 1030 if ((fcntl(STDIN_FILENO, F_GETFL, 0) & O_RDWR) != O_RDWR)
bde20e1b 1031 log_err(_("%s: not open for read/write"), tty);
3aa6b68f 1032
70bedfa1
KZ
1033 }
1034
3aa6b68f 1035 if (tcsetpgrp(STDIN_FILENO, pid))
8c219bf4 1036 log_warn(_("/dev/%s: cannot set process group: %m"), tty);
3aa6b68f
WF
1037
1038 /* Get rid of the present outputs. */
783b08fc
KZ
1039 if (!closed) {
1040 close(STDOUT_FILENO);
1041 close(STDERR_FILENO);
1042 errno = 0;
1043 }
3aa6b68f 1044
70bedfa1
KZ
1045 /* Set up standard output and standard error file descriptors. */
1046 debug("duping\n");
53d55042 1047
70bedfa1
KZ
1048 /* set up stdout and stderr */
1049 if (dup(STDIN_FILENO) != 1 || dup(STDIN_FILENO) != 2)
bde20e1b 1050 log_err(_("%s: dup problem: %m"), tty);
6dbe3af9 1051
3aa6b68f
WF
1052 /* make stdio unbuffered for slow modem lines */
1053 setvbuf(stdout, NULL, _IONBF, 0);
1054
6dbe3af9 1055 /*
70bedfa1
KZ
1056 * The following ioctl will fail if stdin is not a tty, but also when
1057 * there is noise on the modem control lines. In the latter case, the
53d55042
SK
1058 * common course of action is (1) fix your cables (2) give the modem
1059 * more time to properly reset after hanging up.
1060 *
1061 * SunOS users can achieve (2) by patching the SunOS kernel variable
1062 * "zsadtrlow" to a larger value; 5 seconds seems to be a good value.
1063 * http://www.sunmanagers.org/archives/1993/0574.html
6dbe3af9 1064 */
bde20e1b 1065 memset(tp, 0, sizeof(struct termios));
70bedfa1 1066 if (tcgetattr(STDIN_FILENO, tp) < 0)
8c219bf4 1067 log_err(_("%s: failed to get terminal attributes: %m"), tty);
70bedfa1 1068
fba8a535
WF
1069#if defined (__s390__) || defined (__s390x__)
1070 if (!op->term) {
1071 /*
1072 * Special terminal on first serial line on a S/390(x) which
1073 * is due legacy reasons a block terminal of type 3270 or
1074 * higher. Whereas the second serial line on a S/390(x) is
1075 * a real character terminal which is compatible with VT220.
1076 */
f2bcda51 1077 if (strcmp(op->tty, "ttyS0") == 0) /* linux/drivers/s390/char/con3215.c */
fba8a535 1078 op->term = DEFAULT_TTYS0;
f2bcda51
WF
1079 else if (strncmp(op->tty, "3270/tty", 8) == 0) /* linux/drivers/s390/char/con3270.c */
1080 op->term = DEFAULT_TTY32;
1081 else if (strcmp(op->tty, "ttyS1") == 0) /* linux/drivers/s390/char/sclp_vt220.c */
fba8a535
WF
1082 op->term = DEFAULT_TTYS1;
1083 }
1084#endif
70bedfa1 1085 /*
3aa6b68f 1086 * Detect if this is a virtual console or serial/modem line.
b9c73909
WF
1087 * In case of a virtual console the ioctl KDGKBMODE succeeds
1088 * whereas on other lines it will fails.
70bedfa1 1089 */
b9c73909 1090 if (ioctl(STDIN_FILENO, KDGKBMODE, &op->kbmode) == 0) {
3aa6b68f
WF
1091 op->flags |= F_VCONSOLE;
1092 if (!op->term)
1093 op->term = DEFAULT_VCTERM;
b9c73909
WF
1094 } else {
1095 op->kbmode = K_RAW;
1096 if (!op->term)
1097 op->term = DEFAULT_STERM;
1098 }
729def03
WF
1099
1100 setenv("TERM", op->term, 1);
6dbe3af9
KZ
1101}
1102
53d55042 1103/* Initialize termios settings. */
bde20e1b 1104static void termio_init(struct options *op, struct termios *tp)
6dbe3af9 1105{
70bedfa1 1106 speed_t ispeed, ospeed;
1683f6bf 1107 struct winsize ws;
bb280f79
WF
1108 struct termios lock;
1109 int i = (plymouth_command("--ping") == 0) ? 30 : 0;
1110
1111 while (i-- > 0) {
1112 /*
1113 * Even with TTYReset=no it seems with systemd or plymouth
1114 * the termios flags become changed from under the first
1115 * agetty on a serial system console as the flags are locked.
1116 */
1117 memset(&lock, 0, sizeof(struct termios));
1118 if (ioctl(STDIN_FILENO, TIOCGLCKTRMIOS, &lock) < 0)
1119 break;
1120 if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag)
1121 break;
1122 debug("termios locked\n");
1123 if (i == 15 && plymouth_command("quit") != 0)
1124 break;
1125 sleep(1);
1126 }
1127 memset(&lock, 0, sizeof(struct termios));
1128 ioctl(STDIN_FILENO, TIOCSLCKTRMIOS, &lock);
70bedfa1 1129
0da025da
WF
1130 if (op->flags & F_VCONSOLE) {
1131#if defined(IUTF8) && defined(KDGKBMODE)
b9c73909 1132 switch(op->kbmode) {
0da025da 1133 case K_UNICODE:
b0198a11 1134 setlocale(LC_CTYPE, "C.UTF-8");
0da025da
WF
1135 op->flags |= F_UTF8;
1136 break;
1137 case K_RAW:
1138 case K_MEDIUMRAW:
1139 case K_XLATE:
1140 default:
1141 setlocale(LC_CTYPE, "POSIX");
1142 op->flags &= ~F_UTF8;
1143 break;
1144 }
1145#else
1146 setlocale(LC_CTYPE, "POSIX");
1147 op->flags &= ~F_UTF8;
1148#endif
1149 reset_vc(op, tp);
1150
1151 if ((tp->c_cflag & (CS8|PARODD|PARENB)) == CS8)
1152 op->flags |= F_EIGHTBITS;
1153
e85281a8
DWF
1154 if ((op->flags & F_NOCLEAR) == 0) {
1155 /*
1156 * Do not write a full reset (ESC c) because this destroys
1157 * the unicode mode again if the terminal was in unicode
1158 * mode. Also it clears the CONSOLE_MAGIC features which
1159 * are required for some languages/console-fonts.
1160 * Just put the cursor to the home position (ESC [ H),
1161 * erase everything below the cursor (ESC [ J), and set the
1162 * scrolling region to the full window (ESC [ r)
1163 */
1164 write_all(STDOUT_FILENO, "\033[r\033[H\033[J", 9);
1165 }
0da025da
WF
1166 return;
1167 }
1168
70bedfa1 1169 if (op->flags & F_KEEPSPEED) {
53d55042
SK
1170 /* Save the original setting. */
1171 ispeed = cfgetispeed(tp);
70bedfa1 1172 ospeed = cfgetospeed(tp);
1683f6bf
DWF
1173
1174 if (!ispeed) ispeed = TTYDEF_SPEED;
1175 if (!ospeed) ospeed = TTYDEF_SPEED;
1176
53d55042 1177 } else {
70bedfa1 1178 ospeed = ispeed = op->speeds[FIRST_SPEED];
53d55042 1179 }
70bedfa1
KZ
1180
1181 /*
1182 * Initial termios settings: 8-bit characters, raw-mode, blocking i/o.
1183 * Special characters are set after we have read the login name; all
1184 * reads will be done in raw mode anyway. Errors will be dealt with
53d55042 1185 * later on.
70bedfa1 1186 */
53d55042 1187
8408647d
WF
1188#ifdef IUTF8
1189 tp->c_iflag = tp->c_iflag & IUTF8;
7478fce0
ST
1190 if (tp->c_iflag & IUTF8)
1191 op->flags |= F_UTF8;
8408647d
WF
1192#else
1193 tp->c_iflag = 0;
1194#endif
9c62a232
DJ
1195 tp->c_lflag = 0;
1196 tp->c_oflag &= OPOST | ONLCR;
70bedfa1 1197
1683f6bf 1198 if ((op->flags & F_KEEPCFLAGS) == 0)
70bedfa1
KZ
1199 tp->c_cflag = CS8 | HUPCL | CREAD | (tp->c_cflag & CLOCAL);
1200
53d55042
SK
1201 /*
1202 * Note that the speed is stored in the c_cflag termios field, so we have
70bedfa1
KZ
1203 * set the speed always when the cflag se reseted.
1204 */
1205 cfsetispeed(tp, ispeed);
1206 cfsetospeed(tp, ospeed);
1207
ef264c83
KZ
1208 /* The default is to follow setting from kernel, but it's possible
1209 * to explicitly remove/add CLOCAL flag by -L[=<mode>]*/
1210 switch (op->clocal) {
1211 case CLOCAL_MODE_ALWAYS:
1212 tp->c_cflag |= CLOCAL; /* -L or -L=always */
1213 break;
1214 case CLOCAL_MODE_NEVER:
1215 tp->c_cflag &= ~CLOCAL; /* -L=never */
1216 break;
1217 case CLOCAL_MODE_AUTO: /* -L=auto */
1218 break;
1219 }
1220
30e8b186 1221#ifdef HAVE_STRUCT_TERMIOS_C_LINE
70bedfa1 1222 tp->c_line = 0;
1961482a 1223#endif
70bedfa1
KZ
1224 tp->c_cc[VMIN] = 1;
1225 tp->c_cc[VTIME] = 0;
7eda085c 1226
1683f6bf
DWF
1227 /* Check for terminal size and if not found set default */
1228 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) {
1229 int set = 0;
1230 if (ws.ws_row == 0) {
1231 ws.ws_row = 24;
1232 set++;
1233 }
1234 if (ws.ws_col == 0) {
1235 ws.ws_col = 80;
1236 set++;
1237 }
efcf26f4
KZ
1238 if (ioctl(STDIN_FILENO, TIOCSWINSZ, &ws))
1239 debug("TIOCSWINSZ ioctl failed\n");
1683f6bf
DWF
1240 }
1241
53d55042 1242 /* Optionally enable hardware flow control. */
7eda085c 1243#ifdef CRTSCTS
70bedfa1
KZ
1244 if (op->flags & F_RTSCTS)
1245 tp->c_cflag |= CRTSCTS;
7eda085c 1246#endif
bb280f79
WF
1247 /* Flush input and output queues, important for modems! */
1248 tcflush(STDIN_FILENO, TCIOFLUSH);
7eda085c 1249
bb280f79
WF
1250 if (tcsetattr(STDIN_FILENO, TCSANOW, tp))
1251 log_warn(_("setting terminal attributes failed: %m"));
fd6b7a7f 1252
53d55042
SK
1253 /* Go to blocking input even in local mode. */
1254 fcntl(STDIN_FILENO, F_SETFL,
1255 fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
fd6b7a7f 1256
70bedfa1 1257 debug("term_io 2\n");
6dbe3af9
KZ
1258}
1259
0da025da
WF
1260/* Reset virtual console on stdin to its defaults */
1261static void reset_vc(const struct options *op, struct termios *tp)
1262{
879a7ab4
KZ
1263 int fl = 0;
1264
1265 fl |= (op->flags & F_KEEPCFLAGS) == 0 ? 0 : UL_TTY_KEEPCFLAGS;
1266 fl |= (op->flags & F_UTF8) == 0 ? 0 : UL_TTY_UTF8;
1267
1268 reset_virtual_console(tp, fl);
0da025da 1269
0da025da 1270 if (tcsetattr(STDIN_FILENO, TCSADRAIN, tp))
8c219bf4 1271 log_warn(_("setting terminal attributes failed: %m"));
bb280f79
WF
1272
1273 /* Go to blocking input even in local mode. */
1274 fcntl(STDIN_FILENO, F_SETFL,
1275 fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
0da025da
WF
1276}
1277
53d55042 1278/* Extract baud rate from modem status message. */
bde20e1b 1279static void auto_baud(struct termios *tp)
6dbe3af9 1280{
bde20e1b 1281 speed_t speed;
53d55042 1282 int vmin;
70bedfa1 1283 unsigned iflag;
53d55042
SK
1284 char buf[BUFSIZ];
1285 char *bp;
1286 int nread;
70bedfa1
KZ
1287
1288 /*
1289 * This works only if the modem produces its status code AFTER raising
1290 * the DCD line, and if the computer is fast enough to set the proper
1291 * baud rate before the message has gone by. We expect a message of the
1292 * following format:
1293 *
1294 * <junk><number><junk>
1295 *
1296 * The number is interpreted as the baud rate of the incoming call. If the
1297 * modem does not tell us the baud rate within one second, we will keep
1298 * using the current baud rate. It is advisable to enable BREAK
1299 * processing (comma-separated list of baud rates) if the processing of
1300 * modem status messages is enabled.
1301 */
1302
1303 /*
1304 * Use 7-bit characters, don't block if input queue is empty. Errors will
53d55042 1305 * be dealt with later on.
70bedfa1 1306 */
70bedfa1 1307 iflag = tp->c_iflag;
53d55042
SK
1308 /* Enable 8th-bit stripping. */
1309 tp->c_iflag |= ISTRIP;
70bedfa1 1310 vmin = tp->c_cc[VMIN];
53d55042
SK
1311 /* Do not block when queue is empty. */
1312 tp->c_cc[VMIN] = 0;
70bedfa1
KZ
1313 tcsetattr(STDIN_FILENO, TCSANOW, tp);
1314
1315 /*
1316 * Wait for a while, then read everything the modem has said so far and
1317 * try to extract the speed of the dial-in call.
1318 */
53d55042 1319 sleep(1);
70bedfa1
KZ
1320 if ((nread = read(STDIN_FILENO, buf, sizeof(buf) - 1)) > 0) {
1321 buf[nread] = '\0';
53d55042 1322 for (bp = buf; bp < buf + nread; bp++)
70bedfa1
KZ
1323 if (isascii(*bp) && isdigit(*bp)) {
1324 if ((speed = bcode(bp))) {
1325 cfsetispeed(tp, speed);
1326 cfsetospeed(tp, speed);
1327 }
1328 break;
1329 }
6dbe3af9 1330 }
6dbe3af9 1331
53d55042 1332 /* Restore terminal settings. Errors will be dealt with later on. */
70bedfa1
KZ
1333 tp->c_iflag = iflag;
1334 tp->c_cc[VMIN] = vmin;
53d55042 1335 tcsetattr(STDIN_FILENO, TCSANOW, tp);
6dbe3af9
KZ
1336}
1337
2448f336
KZ
1338static char *xgethostname(void)
1339{
1340 char *name;
1341 size_t sz = get_hostname_max() + 1;
1342
1343 name = malloc(sizeof(char) * sz);
1344 if (!name)
1345 log_err(_("failed to allocate memory: %m"));
1346
8362545b
KZ
1347 if (gethostname(name, sz) != 0) {
1348 free(name);
2448f336 1349 return NULL;
8362545b 1350 }
2448f336
KZ
1351 name[sz - 1] = '\0';
1352 return name;
1353}
1354
1355static char *xgetdomainname(void)
1356{
1357#ifdef HAVE_GETDOMAINNAME
1358 char *name;
1359 size_t sz = get_hostname_max() + 1;
1360
1361 name = malloc(sizeof(char) * sz);
1362 if (!name)
1363 log_err(_("failed to allocate memory: %m"));
1364
8362545b
KZ
1365 if (getdomainname(name, sz) != 0) {
1366 free(name);
2448f336 1367 return NULL;
8362545b 1368 }
2448f336
KZ
1369 name[sz - 1] = '\0';
1370 return name;
1371#endif
1372 return NULL;
1373}
1374
b34f097e
KZ
1375static char *read_os_release(struct options *op, const char *varname)
1376{
1377 int fd = -1;
1378 struct stat st;
1379 size_t varsz = strlen(varname);
1380 char *p, *buf = NULL, *ret = NULL;
1381
1382 /* read the file only once */
1383 if (!op->osrelease) {
1384 fd = open(_PATH_OS_RELEASE, O_RDONLY);
1385 if (fd == -1) {
1386 log_warn(_("cannot open: %s: %m"), _PATH_OS_RELEASE);
1387 return NULL;
1388 }
1389
1390 if (fstat(fd, &st) < 0 || st.st_size > 4 * 1024 * 1024)
1391 goto done;
1392
1393 op->osrelease = malloc(st.st_size + 1);
1394 if (!op->osrelease)
1395 log_err(_("failed to allocate memory: %m"));
1396 if (read_all(fd, op->osrelease, st.st_size) != (ssize_t) st.st_size) {
1397 free(op->osrelease);
1398 op->osrelease = NULL;
1399 goto done;
1400 }
1401 op->osrelease[st.st_size] = 0;
1402 }
1403 buf = strdup(op->osrelease);
1404 if (!buf)
1405 log_err(_("failed to allocate memory: %m"));
1406 p = buf;
1407
1408 for (;;) {
1409 char *eol, *eon;
1410
1411 p += strspn(p, "\n\r");
1412 p += strspn(p, " \t\n\r");
1413 if (!*p)
1414 break;
1415 if (strspn(p, "#;\n") != 0) {
1416 p += strcspn(p, "\n\r");
1417 continue;
1418 }
1419 if (strncmp(p, varname, varsz) != 0) {
1420 p += strcspn(p, "\n\r");
1421 continue;
1422 }
1423 p += varsz;
1424 p += strspn(p, " \t\n\r=\"");
1425 eol = p + strcspn(p, "\n\r");
1426 *eol = '\0';
1427 eon = eol-1;
1428 while (eon > p) {
1429 if (*eon == '\t' || *eon == ' ') {
1430 eon--;
1431 continue;
1432 }
1433 if (*eon == '"') {
1434 *eon = '\0';
1435 break;
1436 }
1437 break;
1438 }
f614b73c 1439 free(ret);
b34f097e
KZ
1440 ret = strdup(p);
1441 if (!ret)
1442 log_err(_("failed to allocate memory: %m"));
1443 p = eol + 1;
1444 }
1445done:
1446 free(buf);
1447 if (fd >= 0)
1448 close(fd);
1449 return ret;
1450}
1451
53d55042 1452/* Show login prompt, optionally preceded by /etc/issue contents. */
bde20e1b 1453static void do_prompt(struct options *op, struct termios *tp)
6dbe3af9
KZ
1454{
1455#ifdef ISSUE
53d55042 1456 FILE *fd;
53d55042 1457#endif /* ISSUE */
6dbe3af9 1458
e85281a8
DWF
1459 if ((op->flags & F_NONL) == 0) {
1460 /* Issue not in use, start with a new line. */
1461 write_all(STDOUT_FILENO, "\r\n", 2);
1462 }
3aa6b68f 1463
53d55042 1464#ifdef ISSUE
70bedfa1 1465 if ((op->flags & F_ISSUE) && (fd = fopen(op->issue, "r"))) {
1683f6bf 1466 int c, oflag = tp->c_oflag; /* Save current setting. */
3aa6b68f 1467
1683f6bf
DWF
1468 if ((op->flags & F_VCONSOLE) == 0) {
1469 /* Map new line in output to carriage return & new line. */
1470 tp->c_oflag |= (ONLCR | OPOST);
1471 tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
1472 }
70bedfa1
KZ
1473
1474 while ((c = getc(fd)) != EOF) {
729def03 1475 if (c == '\\')
2b945eda 1476 output_special_char(getc(fd), op, tp, fd);
729def03 1477 else
763d7a87 1478 putchar(c);
70bedfa1
KZ
1479 }
1480 fflush(stdout);
6dbe3af9 1481
1683f6bf
DWF
1482 if ((op->flags & F_VCONSOLE) == 0) {
1483 /* Restore settings. */
1484 tp->c_oflag = oflag;
1485 /* Wait till output is gone. */
1486 tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
1487 }
53d55042 1488 fclose(fd);
6dbe3af9 1489 }
eb8e1f9f
WF
1490#endif /* ISSUE */
1491 if (op->flags & F_LOGINPAUSE) {
8c219bf4 1492 puts(_("[press ENTER to login]"));
eb8e1f9f
WF
1493 getc(stdin);
1494 }
1495#ifdef KDGKBLED
36601b23
KZ
1496 if (!(op->flags & F_NOHINTS) && !op->autolog &&
1497 (op->flags & F_VCONSOLE)) {
a694957a
KZ
1498 int kb = 0;
1499
eb8e1f9f 1500 if (ioctl(STDIN_FILENO, KDGKBLED, &kb) == 0) {
a694957a
KZ
1501 char hint[256] = { '\0' };
1502 int nl = 0;
a694957a 1503
01c5b787 1504 if (access(_PATH_NUMLOCK_ON, F_OK) == 0)
a694957a
KZ
1505 nl = 1;
1506
1507 if (nl && (kb & 0x02) == 0)
1508 append(hint, sizeof(hint), NULL, _("Num Lock off"));
1509
1510 else if (nl == 0 && (kb & 2) && (kb & 0x20) == 0)
1511 append(hint, sizeof(hint), NULL, _("Num Lock on"));
1512
1513 if ((kb & 0x04) && (kb & 0x40) == 0)
1514 append(hint, sizeof(hint), ", ", _("Caps Lock on"));
1515
1516 if ((kb & 0x01) && (kb & 0x10) == 0)
1517 append(hint, sizeof(hint), ", ", _("Scroll Lock on"));
1518
1519 if (*hint)
1520 printf(_("Hint: %s\n\n"), hint);
eb8e1f9f
WF
1521 }
1522 }
1523#endif /* KDGKBLED */
e85281a8 1524 if ((op->flags & F_NOHOSTNAME) == 0) {
2448f336
KZ
1525 char *hn = xgethostname();
1526
1527 if (hn) {
e85281a8 1528 char *dot = strchr(hn, '.');
74b3df85
SK
1529 char *cn = hn;
1530 struct addrinfo *res = NULL;
e85281a8 1531
e85281a8
DWF
1532 if ((op->flags & F_LONGHNAME) == 0) {
1533 if (dot)
1534 *dot = '\0';
74b3df85
SK
1535
1536 } else if (dot == NULL) {
1537 struct addrinfo hints;
1538
1539 memset(&hints, 0, sizeof(hints));
1540 hints.ai_flags = AI_CANONNAME;
1541
1542 if (!getaddrinfo(hn, NULL, &hints, &res)
1543 && res && res->ai_canonname)
1544 cn = res->ai_canonname;
1545 }
1546
1547 write_all(STDOUT_FILENO, cn, strlen(cn));
e85281a8 1548 write_all(STDOUT_FILENO, " ", 1);
74b3df85
SK
1549
1550 if (res)
1551 freeaddrinfo(res);
2448f336 1552 free(hn);
e85281a8 1553 }
70bedfa1 1554 }
933956cb 1555 if (!op->autolog) {
eb8e1f9f
WF
1556 /* Always show login prompt. */
1557 write_all(STDOUT_FILENO, LOGIN, sizeof(LOGIN) - 1);
1558 }
6dbe3af9
KZ
1559}
1560
53d55042 1561/* Select next baud rate. */
bde20e1b 1562static void next_speed(struct options *op, struct termios *tp)
6dbe3af9 1563{
70bedfa1
KZ
1564 static int baud_index = -1;
1565
1566 if (baud_index == -1)
1567 /*
53d55042 1568 * If the F_KEEPSPEED flags is set then the FIRST_SPEED is not
70bedfa1
KZ
1569 * tested yet (see termio_init()).
1570 */
53d55042
SK
1571 baud_index =
1572 (op->flags & F_KEEPSPEED) ? FIRST_SPEED : 1 % op->numspeed;
70bedfa1
KZ
1573 else
1574 baud_index = (baud_index + 1) % op->numspeed;
1575
1576 cfsetispeed(tp, op->speeds[baud_index]);
1577 cfsetospeed(tp, op->speeds[baud_index]);
53d55042 1578 tcsetattr(STDIN_FILENO, TCSANOW, tp);
6dbe3af9
KZ
1579}
1580
53d55042 1581/* Get user name, establish parity, speed, erase, kill & eol. */
bde20e1b 1582static char *get_logname(struct options *op, struct termios *tp, struct chardata *cp)
6dbe3af9 1583{
70bedfa1 1584 static char logname[BUFSIZ];
53d55042
SK
1585 char *bp;
1586 char c; /* input character, full eight bits */
1587 char ascval; /* low 7 bits of input character */
1683f6bf 1588 int eightbit;
53d55042
SK
1589 static char *erase[] = { /* backspace-space-backspace */
1590 "\010\040\010", /* space parity */
1591 "\010\040\010", /* odd parity */
1592 "\210\240\210", /* even parity */
1593 "\210\240\210", /* no parity */
70bedfa1
KZ
1594 };
1595
1596 /* Initialize kill, erase, parity etc. (also after switching speeds). */
f5664477 1597 INIT_CHARDATA(cp);
70bedfa1 1598
53d55042
SK
1599 /*
1600 * Flush pending input (especially important after parsing or switching
1601 * the baud rate).
1602 */
1683f6bf
DWF
1603 if ((op->flags & F_VCONSOLE) == 0)
1604 sleep(1);
53d55042 1605 tcflush(STDIN_FILENO, TCIFLUSH);
70bedfa1 1606
1683f6bf
DWF
1607 eightbit = (op->flags & F_EIGHTBITS);
1608 bp = logname;
1609 *bp = '\0';
1610
1611 while (*logname == '\0') {
1612
1613 /* Write issue file and prompt */
70bedfa1
KZ
1614 do_prompt(op, tp);
1615
1683f6bf
DWF
1616 cp->eol = '\0';
1617
1618 /* Read name, watch for break and end-of-line. */
1619 while (cp->eol == '\0') {
1620
cb872ac9
KZ
1621 char key;
1622
763d7a87 1623 if (read(STDIN_FILENO, &c, 1) < 1) {
1683f6bf 1624
714cff30
KZ
1625 /* The terminal could be open with O_NONBLOCK when
1626 * -L (force CLOCAL) is specified... */
1683f6bf 1627 if (errno == EINTR || errno == EAGAIN) {
a5bd7939 1628 xusleep(250000);
1683f6bf
DWF
1629 continue;
1630 }
1631 switch (errno) {
1632 case 0:
1633 case EIO:
1634 case ESRCH:
1635 case EINVAL:
1636 case ENOENT:
1637 break;
1638 default:
1639 log_err(_("%s: read: %m"), op->tty);
1640 }
70bedfa1 1641 }
1683f6bf 1642
70bedfa1 1643 /* Do parity bit handling. */
1683f6bf 1644 if (eightbit)
70bedfa1 1645 ascval = c;
1683f6bf
DWF
1646 else if (c != (ascval = (c & 0177))) {
1647 uint32_t bits; /* # of "1" bits per character */
1648 uint32_t mask; /* mask with 1 bit up */
1649 for (bits = 1, mask = 1; mask & 0177; mask <<= 1) {
70bedfa1 1650 if (mask & ascval)
53d55042 1651 bits++;
1683f6bf 1652 }
70bedfa1
KZ
1653 cp->parity |= ((bits & 1) ? 1 : 2);
1654 }
1683f6bf 1655
cb872ac9
KZ
1656 if (op->killchars && strchr(op->killchars, ascval))
1657 key = CTL('U');
1658 else if (op->erasechars && strchr(op->erasechars, ascval))
1659 key = DEL;
1660 else
1661 key = ascval;
1662
70bedfa1 1663 /* Do erase, kill and end-of-line processing. */
cb872ac9 1664 switch (key) {
1683f6bf
DWF
1665 case 0:
1666 *bp = 0;
1667 if (op->numspeed > 1)
4a9b7543 1668 return NULL;
1683f6bf 1669 break;
70bedfa1
KZ
1670 case CR:
1671 case NL:
1683f6bf
DWF
1672 *bp = 0; /* terminate logname */
1673 cp->eol = ascval; /* set end-of-line char */
70bedfa1
KZ
1674 break;
1675 case BS:
1676 case DEL:
1683f6bf 1677 cp->erase = ascval; /* set erase character */
70bedfa1 1678 if (bp > logname) {
b9261127 1679 if ((tp->c_lflag & ECHO) == 0)
1683f6bf 1680 write_all(1, erase[cp->parity], 3);
70bedfa1
KZ
1681 bp--;
1682 }
1683 break;
1684 case CTL('U'):
1683f6bf 1685 cp->kill = ascval; /* set kill character */
70bedfa1 1686 while (bp > logname) {
b9261127 1687 if ((tp->c_lflag & ECHO) == 0)
1683f6bf 1688 write_all(1, erase[cp->parity], 3);
70bedfa1
KZ
1689 bp--;
1690 }
1691 break;
1692 case CTL('D'):
1693 exit(EXIT_SUCCESS);
1694 default:
1683f6bf
DWF
1695 if (!isascii(ascval) || !isprint(ascval))
1696 break;
1697 if ((size_t)(bp - logname) >= sizeof(logname) - 1)
bde20e1b 1698 log_err(_("%s: input overrun"), op->tty);
b9261127 1699 if ((tp->c_lflag & ECHO) == 0)
1683f6bf
DWF
1700 write_all(1, &c, 1); /* echo the character */
1701 *bp++ = ascval; /* and store it */
70bedfa1
KZ
1702 break;
1703 }
6dbe3af9 1704 }
6dbe3af9 1705 }
1683f6bf
DWF
1706#ifdef HAVE_WIDECHAR
1707 if ((op->flags & (F_EIGHTBITS|F_UTF8)) == (F_EIGHTBITS|F_UTF8)) {
1708 /* Check out UTF-8 multibyte characters */
1709 ssize_t len;
1710 wchar_t *wcs, *wcp;
1711
1712 len = mbstowcs((wchar_t *)0, logname, 0);
1713 if (len < 0)
8c219bf4 1714 log_err(_("%s: invalid character conversion for login name"), op->tty);
1683f6bf 1715
ca8e91a4
KZ
1716 wcs = (wchar_t *) malloc((len + 1) * sizeof(wchar_t));
1717 if (!wcs)
1718 log_err(_("failed to allocate memory: %m"));
1683f6bf
DWF
1719
1720 len = mbstowcs(wcs, logname, len + 1);
1721 if (len < 0)
8c219bf4 1722 log_err(_("%s: invalid character conversion for login name"), op->tty);
1683f6bf
DWF
1723
1724 wcp = wcs;
1725 while (*wcp) {
1726 const wint_t wc = *wcp++;
1727 if (!iswprint(wc))
8c219bf4 1728 log_err(_("%s: invalid character 0x%x in login name"), op->tty, wc);
1683f6bf
DWF
1729 }
1730 free(wcs);
1731 } else
1732#endif
1733 if ((op->flags & F_LCUC) && (cp->capslock = caps_lock(logname))) {
1734
1735 /* Handle names with upper case and no lower case. */
70bedfa1
KZ
1736 for (bp = logname; *bp; bp++)
1737 if (isupper(*bp))
1683f6bf
DWF
1738 *bp = tolower(*bp); /* map name to lower case */
1739 }
1740
70bedfa1 1741 return logname;
6dbe3af9
KZ
1742}
1743
53d55042 1744/* Set the final tty mode bits. */
bde20e1b 1745static void termio_final(struct options *op, struct termios *tp, struct chardata *cp)
6dbe3af9 1746{
70bedfa1 1747 /* General terminal-independent stuff. */
53d55042
SK
1748
1749 /* 2-way flow control */
1750 tp->c_iflag |= IXON | IXOFF;
1751 tp->c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE;
1752 /* no longer| ECHOCTL | ECHOPRT */
70bedfa1
KZ
1753 tp->c_oflag |= OPOST;
1754 /* tp->c_cflag = 0; */
53d55042
SK
1755 tp->c_cc[VINTR] = DEF_INTR;
1756 tp->c_cc[VQUIT] = DEF_QUIT;
1757 tp->c_cc[VEOF] = DEF_EOF;
70bedfa1 1758 tp->c_cc[VEOL] = DEF_EOL;
fd6b7a7f 1759#ifdef __linux__
53d55042 1760 tp->c_cc[VSWTC] = DEF_SWITCH;
1961482a 1761#elif defined(VSWTCH)
53d55042
SK
1762 tp->c_cc[VSWTCH] = DEF_SWITCH;
1763#endif /* __linux__ */
6dbe3af9 1764
70bedfa1
KZ
1765 /* Account for special characters seen in input. */
1766 if (cp->eol == CR) {
53d55042
SK
1767 tp->c_iflag |= ICRNL;
1768 tp->c_oflag |= ONLCR;
70bedfa1 1769 }
53d55042
SK
1770 tp->c_cc[VERASE] = cp->erase;
1771 tp->c_cc[VKILL] = cp->kill;
70bedfa1
KZ
1772
1773 /* Account for the presence or absence of parity bits in input. */
1774 switch (cp->parity) {
53d55042
SK
1775 case 0:
1776 /* space (always 0) parity */
1777 break;
1778 case 1:
1779 /* odd parity */
1780 tp->c_cflag |= PARODD;
1781 /* do not break */
1782 case 2:
1783 /* even parity */
1784 tp->c_cflag |= PARENB;
1785 tp->c_iflag |= INPCK | ISTRIP;
1786 /* do not break */
1787 case (1 | 2):
1788 /* no parity bit */
1789 tp->c_cflag &= ~CSIZE;
1790 tp->c_cflag |= CS7;
1791 break;
70bedfa1
KZ
1792 }
1793 /* Account for upper case without lower case. */
1794 if (cp->capslock) {
ee519041 1795#ifdef IUCLC
70bedfa1 1796 tp->c_iflag |= IUCLC;
ee519041 1797#endif
b75c8134 1798#ifdef XCASE
70bedfa1 1799 tp->c_lflag |= XCASE;
1961482a 1800#endif
ee519041 1801#ifdef OLCUC
70bedfa1 1802 tp->c_oflag |= OLCUC;
ee519041 1803#endif
70bedfa1 1804 }
53d55042 1805 /* Optionally enable hardware flow control. */
6dbe3af9 1806#ifdef CRTSCTS
70bedfa1
KZ
1807 if (op->flags & F_RTSCTS)
1808 tp->c_cflag |= CRTSCTS;
6dbe3af9
KZ
1809#endif
1810
53d55042 1811 /* Finally, make the new settings effective. */
70bedfa1 1812 if (tcsetattr(STDIN_FILENO, TCSANOW, tp) < 0)
8c219bf4 1813 log_err(_("%s: failed to set terminal attributes: %m"), op->tty);
6dbe3af9
KZ
1814}
1815
53d55042
SK
1816/*
1817 * String contains upper case without lower case.
1818 * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=52940
1819 * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=156242
1820 */
bde20e1b 1821static int caps_lock(char *s)
6dbe3af9 1822{
53d55042 1823 int capslock;
70bedfa1
KZ
1824
1825 for (capslock = 0; *s; s++) {
1826 if (islower(*s))
1827 return EXIT_SUCCESS;
1828 if (capslock == 0)
1829 capslock = isupper(*s);
1830 }
1831 return capslock;
6dbe3af9
KZ
1832}
1833
53d55042 1834/* Convert speed string to speed code; return 0 on failure. */
bde20e1b 1835static speed_t bcode(char *s)
6dbe3af9 1836{
1a204cd2 1837 const struct Speedtab *sp;
53d55042 1838 long speed = atol(s);
6dbe3af9 1839
70bedfa1
KZ
1840 for (sp = speedtab; sp->speed; sp++)
1841 if (sp->speed == speed)
1842 return sp->code;
1843 return 0;
6dbe3af9
KZ
1844}
1845
7d368556 1846static void __attribute__ ((__noreturn__)) usage(FILE *out)
6dbe3af9 1847{
7d368556 1848 fputs(USAGE_HEADER, out);
3755d48b
BS
1849 fprintf(out, _(" %1$s [options] <line> [<baud_rate>,...] [<termtype>]\n"
1850 " %1$s [options] <baud_rate>,... <line> [<termtype>]\n"), program_invocation_short_name);
7d368556
SK
1851 fputs(USAGE_OPTIONS, out);
1852 fputs(_(" -8, --8bits assume 8-bit tty\n"), out);
1853 fputs(_(" -a, --autologin <user> login the specified user automatically\n"), out);
1854 fputs(_(" -c, --noreset do not reset control mode\n"), out);
1505a0b2 1855 fputs(_(" -E, --remote use -r <hostname> for login(1)\n"), out);
7d368556
SK
1856 fputs(_(" -f, --issue-file <file> display issue file\n"), out);
1857 fputs(_(" -h, --flow-control enable hardware flow control\n"), out);
1858 fputs(_(" -H, --host <hostname> specify login host\n"), out);
1859 fputs(_(" -i, --noissue do not display issue file\n"), out);
1860 fputs(_(" -I, --init-string <string> set init string\n"), out);
1861 fputs(_(" -l, --login-program <file> specify login program\n"), out);
38ae77d7 1862 fputs(_(" -L, --local-line[=<mode>] control the local line flag\n"), out);
7d368556
SK
1863 fputs(_(" -m, --extract-baud extract baud rate during connect\n"), out);
1864 fputs(_(" -n, --skip-login do not prompt for login\n"), out);
1865 fputs(_(" -o, --login-options <opts> options that are passed to login\n"), out);
a056cfe2 1866 fputs(_(" -p, --login-pause wait for any key before the login\n"), out);
4b856ee4 1867 fputs(_(" -r, --chroot <dir> change root to the directory\n"), out);
7d368556
SK
1868 fputs(_(" -R, --hangup do virtually hangup on the tty\n"), out);
1869 fputs(_(" -s, --keep-baud try to keep baud rate after break\n"), out);
1870 fputs(_(" -t, --timeout <number> login process timeout\n"), out);
1871 fputs(_(" -U, --detect-case detect uppercase terminal\n"), out);
1872 fputs(_(" -w, --wait-cr wait carriage-return\n"), out);
1873 fputs(_(" --noclear do not clear the screen before prompt\n"), out);
1874 fputs(_(" --nohints do not print hints\n"), out);
1875 fputs(_(" --nonewline do not print a newline before issue\n"), out);
a056cfe2 1876 fputs(_(" --nohostname no hostname at all will be shown\n"), out);
7d368556
SK
1877 fputs(_(" --long-hostname show full qualified hostname\n"), out);
1878 fputs(_(" --erase-chars <string> additional backspace chars\n"), out);
1879 fputs(_(" --kill-chars <string> additional kill chars\n"), out);
86ad1432
SK
1880 fputs(_(" --help display this help and exit\n"), out);
1881 fputs(_(" --version output version information and exit\n"), out);
7d368556 1882 fprintf(out, USAGE_MAN_TAIL("agetty(8)"));
70bedfa1
KZ
1883
1884 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
6dbe3af9
KZ
1885}
1886
bde20e1b
WF
1887/*
1888 * Helper function reports errors to console or syslog.
1889 * Will be used by log_err() and log_warn() therefore
1890 * it takes a format as well as va_list.
1891 */
6dbe3af9
KZ
1892#define str2cpy(b,s1,s2) strcat(strcpy(b,s1),s2)
1893
bde20e1b 1894static void dolog(int priority, const char *fmt, va_list ap)
53d55042 1895{
6dbe3af9 1896#ifndef USE_SYSLOG
53d55042 1897 int fd;
6dbe3af9 1898#endif
53d55042
SK
1899 char buf[BUFSIZ];
1900 char *bp;
6dbe3af9 1901
70bedfa1
KZ
1902 /*
1903 * If the diagnostic is reported via syslog(3), the process name is
1904 * automatically prepended to the message. If we write directly to
1905 * /dev/console, we must prepend the process name ourselves.
1906 */
6dbe3af9 1907#ifdef USE_SYSLOG
70bedfa1
KZ
1908 buf[0] = '\0';
1909 bp = buf;
6dbe3af9 1910#else
53d55042 1911 str2cpy(buf, program_invocation_short_name, ": ");
70bedfa1 1912 bp = buf + strlen(buf);
53d55042 1913#endif /* USE_SYSLOG */
763d7a87 1914 vsnprintf(bp, sizeof(buf)-strlen(buf), fmt, ap);
6dbe3af9 1915
70bedfa1
KZ
1916 /*
1917 * Write the diagnostic directly to /dev/console if we do not use the
1918 * syslog(3) facility.
1919 */
6dbe3af9 1920#ifdef USE_SYSLOG
53d55042 1921 openlog(program_invocation_short_name, LOG_PID, LOG_AUTHPRIV);
bde20e1b 1922 syslog(priority, "%s", buf);
70bedfa1 1923 closelog();
6dbe3af9 1924#else
70bedfa1 1925 /* Terminate with CR-LF since the console mode is unknown. */
53d55042 1926 strcat(bp, "\r\n");
70bedfa1 1927 if ((fd = open("/dev/console", 1)) >= 0) {
729def03 1928 write_all(fd, buf, strlen(buf));
53d55042 1929 close(fd);
70bedfa1 1930 }
53d55042 1931#endif /* USE_SYSLOG */
bde20e1b
WF
1932}
1933
1934static void log_err(const char *fmt, ...)
1935{
1936 va_list ap;
1937
1938 va_start(ap, fmt);
1939 dolog(LOG_ERR, fmt, ap);
1940 va_end(ap);
1941
53d55042
SK
1942 /* Be kind to init(8). */
1943 sleep(10);
70bedfa1 1944 exit(EXIT_FAILURE);
6dbe3af9 1945}
bde20e1b
WF
1946
1947static void log_warn(const char *fmt, ...)
1948{
1949 va_list ap;
1950
1951 va_start(ap, fmt);
1952 dolog(LOG_WARNING, fmt, ap);
1953 va_end(ap);
1954}
729def03 1955
0f283438 1956static void print_addr(sa_family_t family, void *addr)
2b945eda 1957{
0f283438
KZ
1958 char buff[INET6_ADDRSTRLEN + 1];
1959
1960 inet_ntop(family, addr, buff, sizeof(buff));
1961 printf("%s", buff);
1962}
1963
1964/*
1965 * Prints IP for the specified interface (@iface), if the interface is not
1966 * specified then prints the "best" one (UP, RUNNING, non-LOOPBACK). If not
1967 * found the "best" interface then prints at least host IP.
1968 */
1969static void output_iface_ip(struct ifaddrs *addrs,
1970 const char *iface,
1971 sa_family_t family)
1972{
1973 struct ifaddrs *p;
1974 struct addrinfo hints, *info = NULL;
1975 char *host = NULL;
1976 void *addr = NULL;
1977
1978 if (!addrs)
2b945eda
KZ
1979 return;
1980
0f283438 1981 for (p = addrs; p; p = p->ifa_next) {
2b945eda 1982
0f283438
KZ
1983 if (!p->ifa_name ||
1984 !p->ifa_addr ||
1985 p->ifa_addr->sa_family != family)
1986 continue;
1987
1988 if (iface) {
1989 /* Filter out by interface name */
1990 if (strcmp(p->ifa_name, iface) != 0)
1991 continue;
1992 } else {
1993 /* Select the "best" interface */
1994 if ((p->ifa_flags & IFF_LOOPBACK) ||
1995 !(p->ifa_flags & IFF_UP) ||
1996 !(p->ifa_flags & IFF_RUNNING))
1997 continue;
1998 }
2b945eda 1999
0f283438
KZ
2000 addr = NULL;
2001 switch (p->ifa_addr->sa_family) {
2b945eda 2002 case AF_INET:
0f283438 2003 addr = &((struct sockaddr_in *) p->ifa_addr)->sin_addr;
2b945eda
KZ
2004 break;
2005 case AF_INET6:
0f283438 2006 addr = &((struct sockaddr_in6 *) p->ifa_addr)->sin6_addr;
2b945eda
KZ
2007 break;
2008 }
0f283438 2009
2b945eda 2010 if (addr) {
0f283438
KZ
2011 print_addr(family, addr);
2012 return;
2b945eda 2013 }
0f283438 2014 }
2b945eda 2015
0f283438
KZ
2016 if (iface)
2017 return;
2b945eda 2018
0f283438 2019 /* Hmm.. not found the best interface, print host IP at least */
2b945eda
KZ
2020 memset(&hints, 0, sizeof(hints));
2021 hints.ai_family = family;
2022 if (family == AF_INET6)
2023 hints.ai_flags = AI_V4MAPPED;
2024
2448f336
KZ
2025 host = xgethostname();
2026 if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) {
2b945eda
KZ
2027 switch (info->ai_family) {
2028 case AF_INET:
2029 addr = &((struct sockaddr_in *) info->ai_addr)->sin_addr;
2030 break;
2031 case AF_INET6:
2032 addr = &((struct sockaddr_in6 *) info->ai_addr)->sin6_addr;
2033 break;
2034 }
0f283438
KZ
2035 if (addr)
2036 print_addr(family, addr);
2b945eda
KZ
2037
2038 freeaddrinfo(info);
2039 }
2448f336 2040 free(host);
2b945eda
KZ
2041}
2042
2043/*
2044 * parses \x{argument}, if not argument specified then returns NULL, the @fd
2045 * has to point to one char after the sequence (it means '{').
2046 */
2047static char *get_escape_argument(FILE *fd, char *buf, size_t bufsz)
2048{
2049 size_t i = 0;
2050 int c = fgetc(fd);
2051
2052 if (c == EOF || (unsigned char) c != '{') {
2053 ungetc(c, fd);
2054 return NULL;
2055 }
2056
2057 do {
2058 c = fgetc(fd);
2059 if (c == EOF)
2060 return NULL;
2061 if ((unsigned char) c != '}' && i < bufsz - 1)
2062 buf[i++] = (unsigned char) c;
2063
2064 } while ((unsigned char) c != '}');
2065
2066 buf[i] = '\0';
2067 return buf;
2068}
2069
729def03 2070static void output_special_char(unsigned char c, struct options *op,
2b945eda 2071 struct termios *tp, FILE *fp)
729def03
WF
2072{
2073 struct utsname uts;
763d7a87
KZ
2074
2075 uname(&uts);
3aa6b68f 2076
729def03
WF
2077 switch (c) {
2078 case 's':
763d7a87 2079 printf("%s", uts.sysname);
729def03
WF
2080 break;
2081 case 'n':
763d7a87 2082 printf("%s", uts.nodename);
729def03
WF
2083 break;
2084 case 'r':
763d7a87 2085 printf("%s", uts.release);
729def03
WF
2086 break;
2087 case 'v':
763d7a87 2088 printf("%s", uts.version);
729def03
WF
2089 break;
2090 case 'm':
763d7a87 2091 printf("%s", uts.machine);
729def03
WF
2092 break;
2093 case 'o':
2094 {
2448f336
KZ
2095 char *dom = xgetdomainname();
2096
2097 fputs(dom ? dom : "unknown_domain", stdout);
2098 free(dom);
729def03
WF
2099 break;
2100 }
2101 case 'O':
2102 {
2448f336
KZ
2103 char *dom = NULL;
2104 char *host = xgethostname();
729def03
WF
2105 struct addrinfo hints, *info = NULL;
2106
2107 memset(&hints, 0, sizeof(hints));
2108 hints.ai_flags = AI_CANONNAME;
2109
2448f336 2110 if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) {
729def03 2111 char *canon;
2448f336 2112
729def03
WF
2113 if (info->ai_canonname &&
2114 (canon = strchr(info->ai_canonname, '.')))
2115 dom = canon + 1;
729def03 2116 }
2448f336
KZ
2117 fputs(dom ? dom : "unknown_domain", stdout);
2118 if (info)
2119 freeaddrinfo(info);
2120 free(host);
729def03
WF
2121 break;
2122 }
2123 case 'd':
2124 case 't':
2125 {
2126 time_t now;
2127 struct tm *tm;
2128
763d7a87 2129 time(&now);
729def03
WF
2130 tm = localtime(&now);
2131
d9201203
KZ
2132 if (!tm)
2133 break;
2134
729def03 2135 if (c == 'd') /* ISO 8601 */
763d7a87 2136 printf("%s %s %d %d",
729def03
WF
2137 nl_langinfo(ABDAY_1 + tm->tm_wday),
2138 nl_langinfo(ABMON_1 + tm->tm_mon),
2139 tm->tm_mday,
2140 tm->tm_year < 70 ? tm->tm_year + 2000 :
2141 tm->tm_year + 1900);
2142 else
763d7a87 2143 printf("%02d:%02d:%02d",
729def03
WF
2144 tm->tm_hour, tm->tm_min, tm->tm_sec);
2145 break;
2146 }
2147 case 'l':
763d7a87 2148 printf ("%s", op->tty);
729def03
WF
2149 break;
2150 case 'b':
2151 {
2152 const speed_t speed = cfgetispeed(tp);
2153 int i;
2154
2155 for (i = 0; speedtab[i].speed; i++) {
2156 if (speedtab[i].code == speed) {
2157 printf("%ld", speedtab[i].speed);
2158 break;
2159 }
2160 }
2161 break;
2162 }
b34f097e
KZ
2163 case 'S':
2164 {
2165 char *var = NULL, varname[64];
2166
2167 if (get_escape_argument(fp, varname, sizeof(varname)))
2168 var = read_os_release(op, varname);
2169 else if (!(var = read_os_release(op, "PRETTY_NAME")))
2170 var = uts.sysname;
2171 if (var) {
2172 if (strcmp(varname, "ANSI_COLOR") == 0)
2173 printf("\033[%sm", var);
2174 else
2175 printf("%s", var);
2176 if (var != uts.sysname)
2177 free(var);
2178 }
2179 break;
2180 }
729def03
WF
2181 case 'u':
2182 case 'U':
2183 {
2184 int users = 0;
2185 struct utmp *ut;
2186 setutent();
2187 while ((ut = getutent()))
2188 if (ut->ut_type == USER_PROCESS)
2189 users++;
2190 endutent();
729def03 2191 if (c == 'U')
9e531400
BS
2192 printf(P_("%d user", "%d users", users), users);
2193 else
2194 printf ("%d ", users);
729def03
WF
2195 break;
2196 }
2b945eda
KZ
2197 case '4':
2198 case '6':
2199 {
2200 sa_family_t family = c == '4' ? AF_INET : AF_INET6;
0f283438 2201 struct ifaddrs *addrs = NULL;
2b945eda
KZ
2202 char iface[128];
2203
0f283438
KZ
2204 if (getifaddrs(&addrs))
2205 break;
2206
2207 if (get_escape_argument(fp, iface, sizeof(iface)))
2b945eda 2208 output_iface_ip(addrs, iface, family);
0f283438
KZ
2209 else
2210 output_iface_ip(addrs, NULL, family);
2211
2212 freeifaddrs(addrs);
2b945eda
KZ
2213 break;
2214 }
729def03 2215 default:
763d7a87 2216 putchar(c);
729def03
WF
2217 break;
2218 }
2219}
2220
2221static void init_special_char(char* arg, struct options *op)
2222{
2223 char ch, *p, *q;
2224 int i;
2225
ca8e91a4
KZ
2226 op->initstring = malloc(strlen(arg) + 1);
2227 if (!op->initstring)
2228 log_err(_("failed to allocate memory: %m"));
729def03
WF
2229
2230 /*
2231 * Copy optarg into op->initstring decoding \ddd octal
2232 * codes into chars.
2233 */
2234 q = op->initstring;
2235 p = arg;
2236 while (*p) {
2237 /* The \\ is converted to \ */
2238 if (*p == '\\') {
2239 p++;
2240 if (*p == '\\') {
2241 ch = '\\';
2242 p++;
2243 } else {
2244 /* Handle \000 - \177. */
2245 ch = 0;
2246 for (i = 1; i <= 3; i++) {
2247 if (*p >= '0' && *p <= '7') {
2248 ch <<= 3;
2249 ch += *p - '0';
2250 p++;
2251 } else {
2252 break;
2253 }
2254 }
2255 }
2256 *q++ = ch;
2257 } else
2258 *q++ = *p++;
2259 }
2260 *q = '\0';
2261}
eb8e1f9f 2262
a694957a
KZ
2263/*
2264 * Appends @str to @dest and if @dest is not empty then use use @sep as a
2265 * separator. The maximal final length of the @dest is @len.
2266 *
2267 * Returns the final @dest length or -1 in case of error.
2268 */
2269static ssize_t append(char *dest, size_t len, const char *sep, const char *src)
2270{
2271 size_t dsz = 0, ssz = 0, sz;
2272 char *p;
2273
2274 if (!dest || !len || !src)
2275 return -1;
2276
2277 if (*dest)
2278 dsz = strlen(dest);
2279 if (dsz && sep)
2280 ssz = strlen(sep);
2281 sz = strlen(src);
2282
2283 if (dsz + ssz + sz + 1 > len)
2284 return -1;
2285
2286 p = dest + dsz;
2287 if (ssz) {
2288 memcpy(p, sep, ssz);
2289 p += ssz;
2290 }
2291 memcpy(p, src, sz);
2292 *(p + sz) = '\0';
2293
2294 return dsz + ssz + sz;
2295}
9aeb66dc 2296
eb8e1f9f
WF
2297/*
2298 * Do not allow the user to pass an option as a user name
2299 * To be more safe: Use `--' to make sure the rest is
2300 * interpreted as non-options by the program, if it supports it.
2301 */
9aeb66dc 2302static void check_username(const char* nm)
eb8e1f9f
WF
2303{
2304 const char *p = nm;
2305 if (!nm)
2306 goto err;
763d7a87 2307 if (strlen(nm) > 42)
eb8e1f9f 2308 goto err;
763d7a87 2309 while (isspace(*p))
eb8e1f9f
WF
2310 p++;
2311 if (*p == '-')
2312 goto err;
2313 return;
2314err:
2315 errno = EPERM;
8c219bf4 2316 log_err(_("checkname failed: %m"));
eb8e1f9f
WF
2317}
2318
bb280f79
WF
2319/*
2320 * For the case plymouth is found on this system
2321 */
2322static int plymouth_command(const char* arg)
2323{
2324 const char *cmd = "/usr/bin/plymouth";
2325 static int has_plymouth = 1;
2326 pid_t pid;
2327
2328 if (!has_plymouth)
2329 return 127;
2330
2331 pid = fork();
2332 if (!pid) {
2333 int fd = open("/dev/null", O_RDWR);
2334 dup2(fd, 0);
2335 dup2(fd, 1);
2336 dup2(fd, 2);
2337 close(fd);
2338 execl(cmd, cmd, arg, (char *) NULL);
2339 exit(127);
2340 } else if (pid > 0) {
2341 int status;
2342 waitpid(pid, &status, 0);
2343 if (status == 127)
2344 has_plymouth = 0;
2345 return status;
2346 }
2347 return 1;
2348}