Release v.5.0.0
[screen.git] / src / screen.c
bloba79c3b1b0ccfb3ccdf09a0a27352f7835961fd52
1 /* Copyright (c) 2010
2 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
3 * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net)
4 * Copyright (c) 2008, 2009
5 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
6 * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
7 * Micah Cowan (micah@cowan.name)
8 * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net)
9 * Copyright (c) 1993-2002, 2003, 2005, 2006, 2007
10 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
11 * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
12 * Copyright (c) 1987 Oliver Laumann
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3, or (at your option)
16 * any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program (see the file COPYING); if not, see
25 * https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 ****************************************************************
31 #include "config.h"
33 #include "screen.h"
35 #include <ctype.h>
36 #include <fcntl.h>
37 #include <poll.h>
38 #include <pwd.h>
39 #include <signal.h>
40 #include <stdint.h>
41 #include <stdbool.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <sys/ioctl.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #include <limits.h>
50 #include <locale.h>
51 #if defined(HAVE_LANGINFO_H)
52 #include <langinfo.h>
53 #endif
55 #include "logfile.h" /* islogfile, logfflush, logfopen/logfclose */
56 #include "fileio.h"
57 #include "list_generic.h"
58 #include "mark.h"
59 #include "utmp.h"
60 #include "winmsg.h"
62 extern char **environ;
64 int force_vt = 1;
65 int VBellWait;
66 int MsgWait;
67 int MsgMinWait;
68 int SilenceWait;
70 char *ShellProg;
71 char *ShellArgs[2];
73 struct backtick;
75 static struct passwd *getpwbyname(char *, struct passwd *);
76 static void SigChldHandler(void);
77 static void SigChld(int);
78 static void SigInt(int);
79 static void CoreDump(int);
80 static void FinitHandler(int);
81 static void DoWait(void);
82 static void serv_read_fn(Event *, void *);
83 static void serv_select_fn(Event *, void *);
84 static void logflush_fn(Event *, void *);
85 static int IsSymbol(char *, char *);
86 static char *ParseChar(char *, char *);
87 static int ParseEscape(char *);
88 static void SetTtyname(bool fatal, struct stat *st);
90 int nversion; /* numerical version, used for secondary DA */
92 /* the attacher */
93 bool do_auth = false;
94 struct passwd *ppp;
95 char *attach_tty;
96 int attach_fd = -1;
97 char *attach_term;
98 char *LoginName;
99 struct mode attach_Mode;
101 /* Indicator whether the current tty exists in another namespace. */
102 bool attach_tty_is_in_new_ns = false;
104 /* Content of the tty symlink when attach_tty_is_in_new_ns == true. */
105 char attach_tty_name_in_ns[MAXPATHLEN];
107 char SocketPath[MAXPATHLEN];
108 char *SocketName; /* SocketName is pointer in SocketPath */
109 char *SocketMatch = NULL; /* session id command line argument */
110 int ServerSocket = -1;
111 Event serv_read;
112 Event serv_select;
113 Event logflushev;
115 char **NewEnv = NULL;
116 char *RcFileName = NULL;
117 char *home;
119 char *screenlogfile; /* filename layout */
120 int log_flush = 10; /* flush interval in seconds */
121 bool logtstamp_on = false; /* tstamp disabled */
122 char *logtstamp_string; /* stamp layout */
123 int logtstamp_after = 120; /* first tstamp after 120s */
124 char *hardcopydir = NULL;
125 char *BellString;
126 char *VisualBellString;
127 char *ActivityString;
128 char *BufferFile;
129 char *PowDetachString;
130 char *hstatusstring;
131 char *captionstring;
132 char *wliststr;
133 char *wlisttit;
134 bool auto_detach = true;
135 bool adaptflag;
136 bool iflag;
137 bool lsflag;
138 bool quietflag;
139 bool wipeflag;
140 bool xflag;
141 int rflag;
142 int dflag;
143 int queryflag = -1;
144 bool hastruecolor = false;
146 char *multi;
147 int multiattach;
148 int tty_mode;
149 int tty_oldmode = -1;
151 char HostName[MAXSTR];
152 pid_t MasterPid;
153 pid_t PanicPid;
154 uid_t real_uid;
155 uid_t eff_uid;
156 uid_t multi_uid;
157 uid_t own_uid;
158 gid_t real_gid;
159 gid_t eff_gid;
160 bool default_startup;
161 int ZombieKey_destroy;
162 int ZombieKey_resurrect;
163 int ZombieKey_onerror;
164 char *preselect = NULL; /* only used in Attach() */
166 char *screenencodings;
168 bool cjkwidth;
170 Layer *flayer;
171 Window *fore;
172 Window *mru_window;
173 Window *first_window;
174 Window *last_window;
175 Window *console_window;
177 #ifdef ENABLE_TELNET
178 int af;
179 #endif
182 * Do this last
184 #include "attacher.h"
185 #include "encoding.h"
186 #include "help.h"
187 #include "misc.h"
188 #include "process.h"
189 #include "socket.h"
190 #include "termcap.h"
191 #include "tty.h"
193 char strnomem[] = "Out of memory.";
195 static int InterruptPlease;
196 static int GotSigChld;
198 /********************************************************************/
199 /********************************************************************/
200 /********************************************************************/
202 static struct passwd *getpwbyname(char *name, struct passwd *ppp)
204 int n;
206 if (!ppp && !(ppp = getpwnam(name)))
207 return NULL;
209 /* Do password sanity check..., allow ##user for SUN_C2 security */
210 n = 0;
211 if (ppp->pw_passwd[0] == '#' && ppp->pw_passwd[1] == '#' && strcmp(ppp->pw_passwd + 2, ppp->pw_name) == 0)
212 n = 13;
213 for (; n < 13; n++) {
214 char c = ppp->pw_passwd[n];
215 if (!(c == '.' || c == '/' || c == '$' ||
216 (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')))
217 break;
220 if (n < 13)
221 ppp->pw_passwd = NULL;
222 if (ppp->pw_passwd && strlen(ppp->pw_passwd) == 13 + 11)
223 ppp->pw_passwd[13] = 0; /* beware of linux's long passwords */
225 return ppp;
228 static char *locale_name(void)
230 static char *s;
232 s = getenv("LC_ALL");
233 if (s == NULL)
234 s = getenv("LC_CTYPE");
235 if (s == NULL)
236 s = getenv("LANG");
237 if (s == NULL)
238 s = "C";
239 return s;
242 static void exit_with_usage(char *myname, char *message, char *arg)
244 printf("Use: %s [-opts] [cmd [args]]\n", myname);
245 printf(" or: %s -r [host.tty]\n\nOptions:\n", myname);
246 #ifdef ENABLE_TELNET
247 printf("-4 Resolve hostnames only to IPv4 addresses.\n");
248 printf("-6 Resolve hostnames only to IPv6 addresses.\n");
249 #endif
250 printf("-a Force all capabilities into each window's termcap.\n");
251 printf("-A -[r|R] Adapt all windows to the new display width & height.\n");
252 printf("-c file Read configuration file instead of '.screenrc'.\n");
253 printf("-d (-r) Detach the elsewhere running screen (and reattach here).\n");
254 printf("-dmS name Start as daemon: Screen session in detached mode.\n");
255 printf("-D (-r) Detach and logout remote (and reattach here).\n");
256 printf("-D -RR Do whatever is needed to get a screen session.\n");
257 printf("-e xy Change command characters.\n");
258 printf("-f Flow control on, -fn = off, -fa = auto.\n");
259 printf("-h lines Set the size of the scrollback history buffer.\n");
260 printf("-i Interrupt output sooner when flow control is on.\n");
261 #if defined(ENABLE_UTMP)
262 printf("-l Login mode on (update %s), -ln = off.\n", UTMPXFILE);
263 #endif
264 printf("-ls [match] or\n");
265 printf("-list Do nothing, just list our SocketDir [on possible matches].\n");
266 printf("-L Turn on output logging.\n");
267 printf("-Logfile file Set logfile name.\n");
268 printf("-m ignore $STY variable, do create a new screen session.\n");
269 printf("-O Choose optimal output rather than exact vt100 emulation.\n");
270 printf("-p window Preselect the named window if it exists.\n");
271 printf("-P Tell screen to enable authentication.\n");
272 printf("-q Quiet startup. Exits with non-zero return code if unsuccessful.\n");
273 printf("-Q Commands will send the response to the stdout of the querying process.\n");
274 printf("-r [session] Reattach to a detached screen process.\n");
275 printf("-R Reattach if possible, otherwise start a new session.\n");
276 printf("-s shell Shell to execute rather than $SHELL.\n");
277 printf("-S sockname Name this session <pid>.sockname instead of <pid>.<tty>.<host>.\n");
278 printf("-t title Set title. (window's name).\n");
279 printf("-T term Use term as $TERM for windows, rather than \"screen\".\n");
280 printf("-U Tell screen to use UTF-8 encoding.\n");
281 printf("-v Print \"Screen version %s\".\n", version);
282 printf("-wipe [match] Do nothing, just clean up SocketDir [on possible matches].\n");
283 printf("-x Attach to a not detached screen. (Multi display mode).\n");
284 printf("-X Execute <cmd> as a screen command in the specified session.\n");
285 if (message && *message) {
286 printf("\nError: ");
287 printf(message, arg);
288 printf("\n");
289 exit(1);
291 exit(0);
294 int main(int argc, char **argv)
296 int n;
297 char *ap;
298 char *av0;
299 char socknamebuf[FILENAME_MAX + 1];
300 int mflag = 0;
301 char *myname = (argc == 0) ? "screen" : argv[0];
302 char *SocketDir;
303 struct stat st;
304 mode_t oumask;
305 struct NewWindow nwin;
306 bool detached = false; /* start up detached */
307 char *sockp;
308 char *sty = NULL;
309 char *multi_home = NULL;
310 bool cmdflag = 0;
313 * First, close all unused descriptors
314 * (otherwise, we might have problems with the select() call)
316 closeallfiles(0);
317 snprintf(version, 59, "%d.%d.%d (build on %s) ", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, BUILD_DATE);
318 nversion = VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_REVISION;
320 BellString = SaveStr("Bell in window %n");
321 VisualBellString = SaveStr(" Wuff, Wuff!! ");
322 ActivityString = SaveStr("Activity in window %n");
323 screenlogfile = SaveStr("screenlog.%n");
324 logtstamp_string = SaveStr("-- %n:%t -- time-stamp -- %M/%d/%y %c:%s --\n");
325 hstatusstring = SaveStr("%h");
326 captionstring = SaveStr("%4n %t");
327 wlisttit = SaveStr(" Num Name%=Flags");
328 wliststr = SaveStr("%4n %t%=%f");
329 BufferFile = SaveStr(DEFAULT_BUFFERFILE);
330 ShellProg = NULL;
331 PowDetachString = NULL;
332 default_startup = (argc > 1) ? false : true;
333 adaptflag = false;
334 VBellWait = VBELLWAIT * 1000;
335 MsgWait = MSGWAIT * 1000;
336 MsgMinWait = MSGMINWAIT * 1000;
337 SilenceWait = SILENCEWAIT;
338 zmodem_sendcmd = SaveStr("!!! sz -vv -b ");
339 zmodem_recvcmd = SaveStr("!!! rz -vv -b -E");
341 CompileKeys(NULL, 0, mark_key_tab);
342 InitBuiltinTabs();
343 screenencodings = SaveStr(SCREENENCODINGS);
344 cjkwidth = 0;
345 nwin = nwin_undef;
346 nwin_options = nwin_undef;
347 strncpy(screenterm, "screen", MAXTERMLEN);
348 screenterm[MAXTERMLEN] = '\0';
349 #ifdef ENABLE_TELNET
350 af = AF_UNSPEC;
351 #endif
353 real_uid = getuid();
354 real_gid = getgid();
355 eff_uid = geteuid();
356 eff_gid = getegid();
358 av0 = *argv;
359 /* if this is a login screen, assume -RR */
360 if (*av0 == '-') {
361 rflag = 4;
362 xflag = true;
363 ShellProg = SaveStr(DefaultShell); /* to prevent nasty circles */
366 while (argc > 0) {
367 ap = *++argv;
368 if (--argc > 0 && *ap == '-') {
369 if (ap[1] == '-' && ap[2] == 0) {
370 argv++;
371 argc--;
372 break;
374 if (ap[1] == '-' && !strncmp(ap, "--version", 9)) {
375 printf("Screen version %s\n", version);
376 exit(0);
378 if (ap[1] == '-' && !strncmp(ap, "--help", 6))
379 exit_with_usage(myname, NULL, NULL);
380 while (ap && *ap && *++ap) {
381 switch (*ap) {
382 #ifdef ENABLE_TELNET
383 case '4':
384 af = AF_INET;
385 break;
386 case '6':
387 af = AF_INET6;
388 break;
389 #endif
390 case 'a':
391 nwin_options.aflag = true;
392 break;
393 case 'A':
394 adaptflag = true;
395 break;
396 case 'p': /* preselect */
397 if (*++ap)
398 preselect = ap;
399 else {
400 if (!--argc)
401 exit_with_usage(myname, "Specify a window to preselect with -p",
402 NULL);
403 preselect = *++argv;
405 ap = NULL;
406 break;
407 case 'P':
408 do_auth = true;
409 break;
410 case 'c':
411 if (*++ap)
412 RcFileName = ap;
413 else {
414 if (--argc == 0)
415 exit_with_usage(myname,
416 "Specify an alternate rc-filename with -c",
417 NULL);
418 RcFileName = *++argv;
420 ap = NULL;
421 break;
422 case 'e':
423 if (!*++ap) {
424 if (--argc == 0)
425 exit_with_usage(myname, "Specify command characters with -e",
426 NULL);
427 ap = *++argv;
429 if (ParseEscape(ap))
430 Panic(0, "Two characters are required with -e option, not '%s'.", ap);
431 ap = NULL;
432 break;
433 case 'f':
434 ap++;
435 switch (*ap++) {
436 case 'n':
437 case '0':
438 nwin_options.flowflag = FLOW_ON;
439 break;
440 case '\0':
441 ap--;
442 /* FALLTHROUGH */
443 case 'y':
444 case '1':
445 nwin_options.flowflag = FLOW_OFF;
446 break;
447 case 'a':
448 nwin_options.flowflag = FLOW_AUTOFLAG;
449 break;
450 default:
451 exit_with_usage(myname, "Unknown flow option -%s", --ap);
453 break;
454 case 'h':
455 if (--argc == 0)
456 exit_with_usage(myname, NULL, NULL);
457 nwin_options.histheight = atoi(*++argv);
458 if (nwin_options.histheight < 0)
459 exit_with_usage(myname, "-h: %s: negative scrollback size?", *argv);
460 break;
461 case 'i':
462 iflag = true;
463 break;
464 case 't': /* title, the former AkA == -k */
465 if (--argc == 0)
466 exit_with_usage(myname, "Specify a new window-name with -t", NULL);
467 nwin_options.aka = *++argv;
468 break;
469 case 'l':
470 ap++;
471 switch (*ap++) {
472 case 'n':
473 case '0':
474 nwin_options.lflag = 0;
475 break;
476 case '\0':
477 ap--;
478 /* FALLTHROUGH */
479 case 'y':
480 case '1':
481 nwin_options.lflag = 1;
482 break;
483 case 'a':
484 nwin_options.lflag = 3;
485 break;
486 case 's': /* -ls */
487 case 'i': /* -list */
488 lsflag = true;
489 if (argc > 1 && !SocketMatch) {
490 SocketMatch = *++argv;
491 argc--;
493 ap = NULL;
494 break;
495 default:
496 exit_with_usage(myname, "%s: Unknown suboption to -l", --ap);
498 break;
499 case 'w':
500 if (strcmp(ap + 1, "ipe"))
501 exit_with_usage(myname, "Unknown option %s", --ap);
502 lsflag = true;
503 wipeflag = true;
504 if (argc > 1 && !SocketMatch) {
505 SocketMatch = *++argv;
506 argc--;
508 break;
509 case 'L':
510 if (!strcmp(ap + 1, "ogfile")) {
511 if (--argc == 0)
512 exit_with_usage(myname, "Specify logfile path with -Logfile", NULL);
514 if (strlen(*++argv) > PATH_MAX)
515 Panic(1, "-Logfile name too long. (max. %d char)", PATH_MAX);
517 free(screenlogfile); /* we already set it up while starting */
518 screenlogfile = SaveStr(*argv);
520 ap = NULL;
521 } else if (!strcmp(ap, "L"))
522 nwin_options.Lflag = 1;
524 break;
525 case 'm':
526 mflag = 1;
527 break;
528 case 'O': /* to be (or not to be?) deleted. jw. */
529 force_vt = 0;
530 break;
531 case 'T':
532 if (--argc == 0)
533 exit_with_usage(myname, "Specify terminal-type with -T", NULL);
534 if (strlen(*++argv) < MAXTERMLEN) {
535 strncpy(screenterm, *argv, MAXTERMLEN);
536 screenterm[MAXTERMLEN] = '\0';
537 } else
538 Panic(0, "-T: terminal name too long. (max. %d char)", MAXTERMLEN);
539 nwin_options.term = screenterm;
540 break;
541 case 'q':
542 quietflag = true;
543 break;
544 case 'Q':
545 queryflag = 1;
546 cmdflag = true;
547 break;
548 case 'r':
549 case 'R':
550 case 'x':
551 if (argc > 1 && *argv[1] != '-' && !SocketMatch) {
552 SocketMatch = *++argv;
553 argc--;
555 if (*ap == 'x')
556 xflag = true;
557 if (rflag)
558 rflag = 2;
559 rflag += (*ap == 'R') ? 2 : 1;
560 break;
561 case 'd':
562 dflag = 1;
563 /* FALLTHROUGH */
564 case 'D':
565 if (!dflag)
566 dflag = 2;
567 if (argc == 2) {
568 if (*argv[1] != '-' && !SocketMatch) {
569 SocketMatch = *++argv;
570 argc--;
573 break;
574 case 's':
575 if (--argc == 0)
576 exit_with_usage(myname, "Specify shell with -s", NULL);
577 if (ShellProg)
578 free(ShellProg);
579 ShellProg = SaveStr(*++argv);
580 break;
581 case 'S':
582 if (!SocketMatch) {
583 if (--argc == 0)
584 exit_with_usage(myname, "Specify session-name with -S", NULL);
585 SocketMatch = *++argv;
587 if (!*SocketMatch)
588 exit_with_usage(myname, "Empty session-name?", NULL);
589 if (strlen(SocketMatch) > 80)
590 exit_with_usage(myname, "Session-name is too long (max length is 80 symbols)", NULL);
591 break;
592 case 'X':
593 cmdflag = true;
594 break;
595 case 'v':
596 printf("Screen version %s\n", version);
597 exit(0);
598 case 'U':
599 nwin_options.encoding = UTF8;
600 break;
601 default:
602 exit_with_usage(myname, "Unknown option %s", --ap);
605 } else
606 break;
609 xsignal(SIGSEGV, CoreDump);
611 setlocale(LC_ALL, "");
612 if (nwin_options.encoding == -1) {
613 /* ask locale if we should start in UTF-8 mode */
614 #ifdef HAVE_LANGINFO_H
615 nwin_options.encoding = FindEncoding(nl_langinfo(CODESET));
616 #else
617 char *s;
618 if ((s = locale_name()) && strstr(s, "UTF-8"))
619 nwin_options.encoding = UTF8;
620 #endif
623 char *s;
624 if ((s = locale_name())) {
625 if (!strncmp(s, "zh_", 3) || !strncmp(s, "ja_", 3) || !strncmp(s, "ko_", 3)) {
626 cjkwidth = 1;
630 if (nwin_options.aka) {
631 if (nwin_options.encoding > 0) {
632 size_t len = strlen(nwin_options.aka);
633 size_t newsz;
634 char *newbuf = malloc(3 * len);
635 if (!newbuf)
636 Panic(0, "%s", strnomem);
637 newsz = RecodeBuf((unsigned char *)nwin_options.aka, len,
638 nwin_options.encoding, 0, (unsigned char *)newbuf);
639 newbuf[newsz] = '\0';
640 nwin_options.aka = newbuf;
641 } else {
642 /* If we just use the original value from av,
643 subsequent shelltitle invocations will attempt to free
644 space we don't own... */
645 nwin_options.aka = SaveStr(nwin_options.aka);
649 if (SocketMatch && strlen(SocketMatch) >= MAXSTR)
650 Panic(0, "Ridiculously long socketname - try again.");
651 if (cmdflag && !rflag && !dflag && !xflag)
652 xflag = true;
653 if (!cmdflag && dflag && mflag && !(rflag || xflag))
654 detached = true;
655 nwin = nwin_options;
656 nwin.encoding = nwin_undef.encoding; /* let screenrc overwrite it */
657 if (argc)
658 nwin.args = argv;
661 if (!ShellProg) {
662 char *sh;
664 sh = getenv("SHELL");
665 ShellProg = SaveStr(sh ? sh : DefaultShell);
667 ShellArgs[0] = ShellProg;
668 home = getenv("HOME");
669 if (!mflag && !SocketMatch) {
670 sty = getenv("STY");
671 if (sty && *sty == 0)
672 sty = NULL;
675 own_uid = multi_uid = real_uid;
676 if (SocketMatch && (sockp = strchr(SocketMatch, '/'))) {
677 *sockp = 0;
678 multi = SocketMatch;
679 SocketMatch = sockp + 1;
680 if (*multi) {
681 struct passwd *mppp;
682 if ((mppp = getpwnam(multi)) == NULL)
683 Panic(0, "Cannot identify account '%s'.", multi);
684 multi_uid = mppp->pw_uid;
685 multi_home = SaveStr(mppp->pw_dir);
686 if (strlen(multi_home) > MAXPATHLEN - 10)
687 Panic(0, "home directory path too long");
688 /* always fake multi attach mode */
689 if (rflag || lsflag)
690 xflag = 1;
691 detached = false;
692 multiattach = 1;
694 /* Special case: effective user is multiuser. */
695 if (eff_uid && (multi_uid != eff_uid))
696 Panic(0, "Must run suid root for multiuser support.");
698 if (SocketMatch && *SocketMatch == 0)
699 SocketMatch = NULL;
701 if ((LoginName = getlogin()) != NULL) {
702 if ((ppp = getpwnam(LoginName)) != NULL)
703 if (ppp->pw_uid != real_uid)
704 ppp = NULL;
706 if (ppp == NULL) {
707 if ((ppp = getpwuid(real_uid)) == NULL) {
708 Panic(0, "getpwuid() can't identify your account!");
709 exit(1);
711 LoginName = ppp->pw_name;
713 LoginName = SaveStr(LoginName);
715 ppp = getpwbyname(LoginName, ppp);
717 #if !defined(SOCKET_DIR)
718 if (multi && !multiattach) {
719 if (home && strcmp(home, ppp->pw_dir))
720 Panic(0, "$HOME must match passwd entry for multiuser screens.");
722 #endif
724 #define SET_GUID() do \
726 if (setgid(real_gid)) \
727 Panic(0, "setgid"); \
728 if (setuid(real_uid)) \
729 Panic(0, "setuid"); \
730 eff_uid = real_uid; \
731 eff_gid = real_gid; \
732 } while (0)
734 if (home == NULL || *home == '\0')
735 home = ppp->pw_dir;
736 if (strlen(LoginName) > MAXLOGINLEN)
737 Panic(0, "LoginName too long - sorry.");
738 if (multi && strlen(multi) > MAXLOGINLEN)
739 Panic(0, "Screen owner name too long - sorry.");
740 if (strlen(home) > MAXPATHLEN)
741 Panic(0, "$HOME too long - sorry.");
743 attach_tty = "";
744 if (!detached && !lsflag && !cmdflag && !(dflag && !mflag && !rflag && !xflag)
745 && !(sty && !SocketMatch && !mflag && !rflag && !xflag)) {
746 int fl;
748 /* ttyname implies isatty */
749 SetTtyname(true, &st);
750 tty_mode = (int)st.st_mode & 0777;
752 fl = fcntl(0, F_GETFL, 0);
753 if (fl != -1 && (fl & (O_RDWR | O_RDONLY | O_WRONLY)) == O_RDWR)
754 attach_fd = 0;
756 if (attach_fd == -1) {
757 if ((n = secopen(attach_tty, O_RDWR | O_NONBLOCK, 0)) < 0)
758 Panic(0, "Cannot open your terminal '%s' - please check.", attach_tty);
759 /* In case the pts device exists in another namespace we directly operate
760 * on the symbolic link itself. However, this means that we need to keep
761 * the fd open since we have no direct way of identifying the associated
762 * pts device accross namespaces. This is ok though since keeping fds open
763 * is done in the codebase already.
765 if (attach_tty_is_in_new_ns)
766 attach_fd = n;
767 else
768 close(n);
771 if ((attach_term = getenv("TERM")) == NULL || *attach_term == 0)
772 Panic(0, "Please set a terminal type.");
773 if (strlen(attach_term) > MAXTERMLEN)
774 Panic(0, "$TERM too long - sorry.");
775 GetTTY(0, &attach_Mode);
778 oumask = umask(0); /* well, unsigned never fails? jw. */
780 SocketDir = getenv("SCREENDIR");
781 if (SocketDir) {
782 if (strlen(SocketDir) >= MAXPATHLEN - 1)
783 Panic(0, "Ridiculously long $SCREENDIR - try again.");
784 if (multi)
785 Panic(0, "No $SCREENDIR with multi screens, please.");
787 if (multiattach) {
788 #ifndef SOCKET_DIR
789 sprintf(SocketPath, "%s/.screen", multi_home);
790 #else
791 SocketDir = SOCKET_DIR;
792 sprintf(SocketPath, "%s/S-%s", SocketDir, multi);
793 #endif
794 } else {
795 #ifndef SOCKET_DIR
796 if (SocketDir == NULL) {
797 if (strlen(home) > MAXPATHLEN - 8 - 1)
798 Panic(0, "$HOME too long - sorry.");
799 sprintf(SocketPath, "%s/.screen", home);
800 SocketDir = SocketPath;
802 #endif
803 if (SocketDir) {
804 if (access(SocketDir, F_OK)) {
805 if (UserContext() > 0) {
806 if (mkdir(SocketDir, 0700))
807 UserReturn(0);
808 UserReturn(1);
810 if (UserStatus() <= 0)
811 Panic(0, "Cannot make directory '%s'.", SocketDir);
813 if (SocketDir != SocketPath)
814 strncpy(SocketPath, SocketDir, ARRAY_SIZE(SocketPath));
816 #ifdef SOCKET_DIR
817 else {
818 SocketDir = SOCKET_DIR;
819 if (stat(SocketDir, &st)) {
820 n = (eff_uid == 0 && (real_uid || eff_gid == real_gid)) ? 0755 :
821 (eff_gid != real_gid) ? 0775 :
822 #ifdef S_ISVTX
823 0777 | S_ISVTX;
824 #else
825 0777;
826 #endif
827 if (mkdir(SocketDir, n) == -1)
828 Panic(errno, "Cannot make directory '%s'", SocketDir);
829 } else {
830 if (!S_ISDIR(st.st_mode))
831 Panic(0, "'%s' must be a directory.", SocketDir);
832 if (eff_uid == 0 && real_uid && st.st_uid != eff_uid)
833 Panic(0, "Directory '%s' must be owned by root.", SocketDir);
834 n = (eff_uid == 0 && (real_uid || (st.st_mode & 0775) != 0775)) ? 0755 :
835 (eff_gid == st.st_gid && eff_gid != real_gid) ? 0775 : 0777;
836 if (((int)st.st_mode & 0777) != n)
837 Panic(0, "Directory '%s' must have mode %03o.", SocketDir, n);
839 sprintf(SocketPath, "%s/S-%s", SocketDir, LoginName);
840 if (access(SocketPath, F_OK)) {
841 if (mkdir(SocketPath, 0700) == -1 && errno != EEXIST)
842 Panic(errno, "Cannot make directory '%s'", SocketPath);
843 (void)chown(SocketPath, real_uid, real_gid);
846 #endif
849 if (stat(SocketPath, &st) == -1)
850 Panic(errno, "Cannot access %s", SocketPath);
851 else if (!S_ISDIR(st.st_mode))
852 Panic(0, "%s is not a directory.", SocketPath);
853 if (multi) {
854 if (st.st_uid != multi_uid)
855 Panic(0, "%s is not the owner of %s.", multi, SocketPath);
856 } else {
857 #ifdef SOCKET_DIR /* if SOCKETDIR is not defined, the socket is in $HOME.
858 in that case it does not make sense to compare uids. */
859 if (st.st_uid != real_uid)
860 Panic(0, "You are not the owner of %s.", SocketPath);
861 #endif
863 if ((st.st_mode & 0777) != 0700)
864 Panic(0, "Directory %s must have mode 700.", SocketPath);
865 if (SocketMatch && strchr(SocketMatch, '/'))
866 Panic(0, "Bad session name '%s'", SocketMatch);
867 SocketName = SocketPath + strlen(SocketPath) + 1;
868 *SocketName = 0;
869 (void)umask(oumask);
871 (void)gethostname(HostName, MAXSTR);
872 HostName[MAXSTR - 1] = '\0';
873 if ((ap = strchr(HostName, '.')) != NULL)
874 *ap = '\0';
876 if (lsflag) {
877 int i, fo, oth;
879 if (multi)
880 real_uid = multi_uid;
881 SET_GUID();
882 i = FindSocket((int *)NULL, &fo, &oth, SocketMatch);
883 if (quietflag) {
884 if (rflag)
885 exit(10 + i);
886 else
887 exit(9 + (fo || oth ? 1 : 0) + fo);
889 if (fo == 0)
890 Panic(0, "No Sockets found in %s.\n", SocketPath);
891 Msg(0, "%d Socket%s in %s.", fo, fo > 1 ? "s" : "", SocketPath);
892 eexit(0);
894 xsignal(SIG_BYE, AttacherFinit); /* prevent races */
895 if (cmdflag) {
896 /* attach_tty is not mandatory */
898 if (multi)
899 real_uid = multi_uid;
901 SetTtyname(false, &st);
902 if (!*argv)
903 Panic(0, "Please specify a command.");
904 SET_GUID();
905 SendCmdMessage(sty, SocketMatch, argv, queryflag >= 0);
906 exit(0);
907 } else if (rflag || xflag) {
908 if (Attach(MSG_ATTACH)) {
909 Attacher();
910 /* NOTREACHED */
912 if (multiattach)
913 Panic(0, "Can't create sessions of other users.");
914 } else if (dflag && !mflag) {
915 SetTtyname(false, &st);
916 Attach(MSG_DETACH);
917 Msg(0, "[%s %sdetached.]\n", SocketName, (dflag > 1 ? "power " : ""));
918 eexit(0);
919 /* NOTREACHED */
921 if (!SocketMatch && !mflag && sty) {
922 /* attach_tty is not mandatory */
923 SetTtyname(false, &st);
924 SET_GUID();
925 nwin_options.args = argv;
926 SendCreateMsg(sty, &nwin);
927 exit(0);
928 /* NOTREACHED */
930 nwin_compose(&nwin_default, &nwin_options, &nwin_default);
932 if (!detached || dflag != 2)
933 MasterPid = fork();
934 else
935 MasterPid = 0;
937 switch (MasterPid) {
938 case -1:
939 Panic(errno, "fork");
940 /* NOTREACHED */
941 case 0:
942 break;
943 default:
944 if (detached)
945 exit(0);
946 if (SocketMatch)
947 sprintf(socknamebuf, "%d.%s", MasterPid, SocketMatch);
948 else
949 sprintf(socknamebuf, "%d.%s.%s", MasterPid, stripdev(attach_tty), HostName);
950 for (ap = socknamebuf; *ap; ap++)
951 if (*ap == '/')
952 *ap = '-';
953 if (strlen(socknamebuf) > FILENAME_MAX)
954 socknamebuf[FILENAME_MAX - 1] = 0;
955 snprintf(SocketPath + strlen(SocketPath), sizeof(SocketPath) - strlen(SocketPath), "/%s", socknamebuf);
956 SET_GUID();
957 Attacher();
958 /* NOTREACHED */
961 if (!detached)
962 PanicPid = getppid();
964 if (DefaultEsc == -1)
965 DefaultEsc = Ctrl('a');
966 if (DefaultMetaEsc == -1)
967 DefaultMetaEsc = 'a';
969 ap = av0 + strlen(av0) - 1;
970 while (ap >= av0) {
971 if (!strncmp("screen", ap, 6)) {
972 memcpy(ap, "SCREEN", 6); /* name this process "SCREEN-BACKEND" */
973 break;
975 ap--;
977 if (ap < av0)
978 *av0 = 'S';
980 if (!detached) {
981 if (attach_fd == -1) {
982 if ((n = secopen(attach_tty, O_RDWR | O_NONBLOCK, 0)) < 0)
983 Panic(0, "Cannot reopen '%s' - please check.", attach_tty);
984 } else
985 n = dup(attach_fd);
986 } else
987 n = -1;
990 * This guarantees that the session owner is listed, even when we
991 * start detached. From now on we should not refer to 'LoginName'
992 * any more, use users->u_name instead.
994 if (UserAdd(LoginName, NULL) < 0)
995 Panic(0, "Could not create user info");
996 if (!detached) {
997 if (MakeDisplay(LoginName, attach_tty, attach_term, n, getppid(), &attach_Mode) == NULL)
998 Panic(0, "Could not alloc display");
999 PanicPid = 0;
1000 D_encoding = nwin_options.encoding > 0 ? nwin_options.encoding : 0;
1003 if (!freopen("/dev/null", "r", stdin) ||
1004 !freopen("/dev/null", "w", stdout) ||
1005 !freopen("/dev/null", "w", stderr))
1006 Panic(0, "Cannot reassociate std streams");
1008 if (SocketMatch) {
1009 /* user started us with -S option */
1010 sprintf(socknamebuf, "%d.%s", (int)getpid(), SocketMatch);
1011 } else {
1012 sprintf(socknamebuf, "%d.%s.%s", (int)getpid(), stripdev(attach_tty), HostName);
1014 for (ap = socknamebuf; *ap; ap++)
1015 if (*ap == '/')
1016 *ap = '-';
1017 if (strlen(socknamebuf) > FILENAME_MAX) {
1018 socknamebuf[FILENAME_MAX] = 0;
1020 snprintf(SocketPath + strlen(SocketPath), sizeof(SocketPath) - strlen(SocketPath), "/%s", socknamebuf);
1022 ServerSocket = MakeServerSocket();
1023 #ifdef SYSTEM_SCREENRC
1024 (void)StartRc(SYSTEM_SCREENRC, 0);
1025 #endif
1026 (void)StartRc(RcFileName, 0);
1027 #ifdef ENABLE_UTMP
1028 InitUtmp();
1029 #endif /* ENABLE_UTMP */
1030 if (display) {
1031 if (InitTermcap(0, 0)) {
1032 fcntl(D_userfd, F_SETFL, 0); /* Flush sets FNBLOCK */
1033 freetty();
1034 if (D_userpid)
1035 Kill(D_userpid, SIG_BYE);
1036 eexit(1);
1038 MakeDefaultCanvas();
1039 InitTerm(0);
1040 #ifdef ENABLE_UTMP
1041 RemoveLoginSlot();
1042 #endif
1043 } else
1044 MakeTermcap(1);
1045 InitKeytab();
1046 MakeNewEnv();
1047 xsignal(SIGHUP, SigHup);
1048 xsignal(SIGINT, FinitHandler);
1049 xsignal(SIGQUIT, FinitHandler);
1050 xsignal(SIGTERM, FinitHandler);
1051 xsignal(SIGTTIN, SIG_IGN);
1052 xsignal(SIGTTOU, SIG_IGN);
1054 if (display) {
1055 brktty(D_userfd);
1056 SetMode(&D_OldMode, &D_NewMode, D_flow, iflag);
1057 /* Note: SetMode must be called _before_ FinishRc. */
1058 SetTTY(D_userfd, &D_NewMode);
1059 if (fcntl(D_userfd, F_SETFL, FNBLOCK))
1060 Msg(errno, "Warning: NBLOCK fcntl failed");
1061 } else
1062 brktty(-1); /* just try */
1063 xsignal(SIGCHLD, SigChld);
1064 #ifdef SYSTEM_SCREENRC
1065 FinishRc(SYSTEM_SCREENRC);
1066 #endif
1067 FinishRc(RcFileName);
1069 if (mru_window == NULL) {
1070 if (MakeWindow(&nwin) == -1) {
1071 struct pollfd pfd[1];
1073 pfd[0].fd = 0;
1074 pfd[0].events = POLLIN;
1076 Msg(0, "Sorry, could not find a PTY or TTY.");
1077 /* allow user to exit early by pressing any key. */
1078 poll(pfd, ARRAY_SIZE(pfd), MsgWait);
1079 Finit(0);
1080 /* NOTREACHED */
1082 } else if (argc) { /* Screen was invoked with a command */
1083 MakeWindow(&nwin);
1086 if (display && default_startup)
1087 display_license();
1088 xsignal(SIGINT, SigInt);
1089 if (rflag && (rflag & 1) == 0 && !quietflag) {
1090 Msg(0, "New screen...");
1091 rflag = 0;
1094 serv_read.type = EV_READ;
1095 serv_read.fd = ServerSocket;
1096 serv_read.handler = serv_read_fn;
1097 evenq(&serv_read);
1099 serv_select.priority = -10;
1100 serv_select.type = EV_ALWAYS;
1101 serv_select.handler = serv_select_fn;
1102 evenq(&serv_select);
1104 logflushev.type = EV_TIMEOUT;
1105 logflushev.handler = logflush_fn;
1107 sched();
1108 /* NOTREACHED */
1109 return 0;
1112 static void SigChldHandler(void)
1114 struct stat st;
1115 while (GotSigChld) {
1116 GotSigChld = 0;
1117 DoWait();
1119 if (stat(SocketPath, &st) == -1) {
1120 if (!RecoverSocket()) {
1121 Finit(1);
1126 static void SigChld(int sigsig)
1128 (void)sigsig; /* unused */
1129 GotSigChld = 1;
1132 void SigHup(int sigsig)
1134 (void)sigsig; /* unused */
1135 /* Hangup all displays */
1136 while ((display = displays))
1137 Hangup();
1141 * the backend's Interrupt handler
1142 * we cannot insert the intrc directly, as we never know
1143 * if fore is valid.
1145 static void SigInt(int sigsig)
1147 (void)sigsig; /* unused */
1149 xsignal(SIGINT, SigInt);
1151 InterruptPlease = 1;
1154 static void CoreDump(int sigsig)
1156 /* if running with s-bit, we must reset the s-bit, so that we get a
1157 * core file anyway.
1160 Display *disp;
1161 char buf[80];
1163 (void)sigsig; /* unused */
1165 if (setgid(getgid()))
1166 Panic(0, "setgid");
1167 if (setuid(getuid()))
1168 Panic(0, "setuid");
1169 unlink("core");
1171 sprintf(buf, "\r\n[screen caught a fatal signal. (core dumped)]\r\n");
1173 for (disp = displays; disp; disp = disp->d_next) {
1174 if (disp->d_nonblock < -1 || disp->d_nonblock > 1000000)
1175 continue;
1176 fcntl(disp->d_userfd, F_SETFL, 0);
1177 SetTTY(disp->d_userfd, &D_OldMode);
1178 write(disp->d_userfd, buf, strlen(buf));
1179 Kill(disp->d_userpid, SIG_BYE);
1182 abort();
1185 static void DoWait(void)
1187 pid_t pid;
1188 Window *win;
1189 int wstat;
1191 while ((pid = waitpid(-1, &wstat, WNOHANG | WUNTRACED)) > 0)
1193 for (win = mru_window; win; win = win->w_prev_mru) {
1194 if ((win->w_pid && pid == win->w_pid) || (win->w_deadpid && pid == win->w_deadpid)) {
1195 /* child has ceased to exist */
1196 win->w_pid = 0;
1198 if (WIFSTOPPED(wstat)) {
1199 #ifdef SIGTTIN
1200 if (WSTOPSIG(wstat) == SIGTTIN) {
1201 Msg(0, "Suspended (tty input)");
1202 continue;
1204 #endif
1205 #ifdef SIGTTOU
1206 if (WSTOPSIG(wstat) == SIGTTOU) {
1207 Msg(0, "Suspended (tty output)");
1208 continue;
1210 #endif
1211 /* Try to restart process */
1212 Msg(0, "Child has been stopped, restarting.");
1213 if (killpg(pid, SIGCONT))
1214 kill(pid, SIGCONT);
1215 } else {
1216 /* Screen will detect the window has died when the window's
1217 * file descriptor signals EOF (which it will do when the process in
1218 * the window terminates). So do this in a timeout of 10 seconds.
1219 * (not doing this at all might also work)
1220 * See #27061 for more details.
1222 win->w_destroyev.data = (char *)win;
1223 win->w_exitstatus = wstat;
1224 SetTimeout(&win->w_destroyev, 10 * 1000);
1225 evenq(&win->w_destroyev);
1227 break;
1229 if (win->w_pwin && pid == win->w_pwin->p_pid) {
1230 FreePseudowin(win);
1231 break;
1237 static void FinitHandler(int sigsig)
1239 (void)sigsig; /* unused */
1241 Finit(1);
1244 void Finit(int i)
1246 xsignal(SIGCHLD, SIG_DFL);
1247 xsignal(SIGHUP, SIG_IGN);
1249 while (mru_window) {
1250 Window *p = mru_window;
1251 mru_window = mru_window->w_prev_mru;
1252 FreeWindow(p);
1254 if (ServerSocket != -1) {
1255 xseteuid(real_uid);
1256 xsetegid(real_gid);
1257 (void)unlink(SocketPath);
1258 xseteuid(eff_uid);
1259 xsetegid(eff_gid);
1261 for (display = displays; display; display = display->d_next) {
1262 if (D_status)
1263 RemoveStatus();
1264 FinitTerm();
1265 #ifdef ENABLE_UTMP
1266 RestoreLoginSlot();
1267 #endif
1268 AddStr("[screen is terminating]\r\n");
1269 Flush(3);
1270 SetTTY(D_userfd, &D_OldMode);
1271 fcntl(D_userfd, F_SETFL, 0);
1272 freetty();
1273 Kill(D_userpid, SIG_BYE);
1276 * we _cannot_ call eexit(i) here,
1277 * instead of playing with the Socket above. Sigh.
1279 exit(i);
1282 void eexit(int e)
1284 if (ServerSocket != -1) {
1285 if (setgid(real_gid))
1286 AddStr("Failed to set gid\r\n");
1287 if (setuid(real_uid))
1288 AddStr("Failed to set uid\r\n");
1289 if (unlink(SocketPath))
1290 AddStr("Failed to remove socket\r\n");
1292 exit(e);
1295 void Hangup(void)
1297 if (display == NULL)
1298 return;
1299 if (D_userfd >= 0) {
1300 close(D_userfd);
1301 D_userfd = -1;
1303 if (auto_detach || displays->d_next)
1304 Detach(D_HANGUP);
1305 else
1306 Finit(0);
1310 * Detach now has the following modes:
1311 *D_DETACH SIG_BYE detach backend and exit attacher
1312 *D_HANGUP SIG_BYE detach backend and exit attacher
1313 *D_STOP SIG_STOP stop attacher (and detach backend)
1314 *D_REMOTE SIG_BYE remote detach -- reattach to new attacher
1315 *D_POWER SIG_POWER_BYE power detach -- attacher kills his parent
1316 *D_REMOTE_POWER SIG_POWER_BYE remote power detach -- both
1317 *D_LOCK SIG_LOCK lock the attacher
1318 * (jw)
1319 * we always remove our utmp slots. (even when "lock" or "stop")
1320 * Note: Take extra care here, we may be called by interrupt!
1322 void Detach(int mode)
1324 int sign = 0;
1325 pid_t pid;
1326 Canvas *cv;
1327 Window *p;
1329 if (display == NULL)
1330 return;
1332 #define AddStrSocket(msg) do { \
1333 if (SocketName) \
1335 AddStr("[" msg " from "); \
1336 AddStr(SocketName); \
1337 AddStr("]\r\n"); \
1339 else \
1340 AddStr("[" msg "]\r\n"); \
1341 } while (0)
1343 xsignal(SIGHUP, SIG_IGN);
1344 if (D_status)
1345 RemoveStatus();
1346 FinitTerm();
1347 if (!display)
1348 return;
1349 switch (mode) {
1350 case D_HANGUP:
1351 sign = SIG_BYE;
1352 break;
1353 case D_DETACH:
1354 AddStrSocket("detached");
1355 sign = SIG_BYE;
1356 break;
1357 case D_STOP:
1358 sign = SIG_STOP;
1359 break;
1360 case D_REMOTE:
1361 AddStrSocket("remote detached");
1362 sign = SIG_BYE;
1363 break;
1364 case D_POWER:
1365 AddStrSocket("power detached");
1366 if (PowDetachString) {
1367 AddStr(PowDetachString);
1368 AddStr("\r\n");
1370 sign = SIG_POWER_BYE;
1371 break;
1372 case D_REMOTE_POWER:
1373 AddStrSocket("remote power detached");
1374 if (PowDetachString) {
1375 AddStr(PowDetachString);
1376 AddStr("\r\n");
1378 sign = SIG_POWER_BYE;
1379 break;
1380 case D_LOCK:
1381 ClearAll();
1382 ClearScrollbackBuffer();
1383 sign = SIG_LOCK;
1384 /* tell attacher to lock terminal with a lockprg. */
1385 break;
1387 #ifdef ENABLE_UTMP
1388 if (displays->d_next == NULL) {
1389 for (p = mru_window; p; p = p->w_prev_mru) {
1390 if (p->w_slot != (slot_t) - 1 && !(p->w_lflag & 2)) {
1391 RemoveUtmp(p);
1393 * Set the slot to 0 to get the window
1394 * logged in again.
1396 p->w_slot = (slot_t) 0;
1400 if (mode != D_HANGUP)
1401 RestoreLoginSlot();
1402 #endif
1403 if (displays->d_next == NULL && console_window) {
1404 if (TtyGrabConsole(console_window->w_ptyfd, false, "detach")) {
1405 KillWindow(console_window);
1406 display = displays; /* restore display */
1409 if (D_fore) {
1410 ReleaseAutoWritelock(display, D_fore);
1411 D_user->u_detachwin = D_fore->w_number;
1412 D_user->u_detachotherwin = D_other ? D_other->w_number : D_fore->w_number;
1414 AutosaveLayout(D_layout);
1415 layout_last = D_layout;
1416 for (cv = D_cvlist; cv; cv = cv->c_next) {
1417 p = Layer2Window(cv->c_layer);
1418 SetCanvasWindow(cv, NULL);
1419 if (p)
1420 WindowChanged(p, 'u');
1423 pid = D_userpid;
1424 FreeDisplay();
1425 if (displays == NULL)
1426 /* Flag detached-ness */
1427 (void)chsock();
1429 * tell father what to do. We do that after we
1430 * freed the tty, thus getty feels more comfortable on hpux
1431 * if it was a power detach.
1433 Kill(pid, sign);
1434 xsignal(SIGHUP, SigHup);
1435 #undef AddStrSocket
1438 static int IsSymbol(char *e, char *s)
1440 int l;
1442 l = strlen(s);
1443 return strncmp(e, s, l) == 0 && e[l] == '=';
1446 void MakeNewEnv(void)
1448 char **op, **np;
1449 static char stybuf[MAXSTR];
1451 for (op = environ; *op; ++op) ;
1452 if (NewEnv)
1453 free((char *)NewEnv);
1454 NewEnv = np = malloc((unsigned)(op - environ + 7 + 1) * sizeof(char *));
1455 if (!NewEnv)
1456 Panic(0, "%s", strnomem);
1457 sprintf(stybuf, "STY=%s", strlen(SocketName) <= MAXSTR - 5 ? SocketName : "?");
1458 *np++ = stybuf; /* NewEnv[0] */
1459 *np++ = Term; /* NewEnv[1] */
1460 np++; /* room for SHELL */
1461 np += 2; /* room for TERMCAP and WINDOW */
1463 for (op = environ; *op; ++op) {
1464 if (!IsSymbol(*op, "TERM") && !IsSymbol(*op, "TERMCAP")
1465 && !IsSymbol(*op, "STY") && !IsSymbol(*op, "WINDOW")
1466 && !IsSymbol(*op, "SCREENCAP") && !IsSymbol(*op, "SHELL")
1467 && !IsSymbol(*op, "LINES") && !IsSymbol(*op, "COLUMNS")
1469 *np++ = *op;
1471 *np = NULL;
1474 #define PROCESS_MESSAGE(B) do { \
1475 char *p = B; \
1476 va_list ap; \
1477 va_start(ap, fmt); \
1478 (void)vsnprintf(p, sizeof(B) - 100, fmt, ap); \
1479 va_end(ap); \
1480 if (err) \
1482 p += strlen(p); \
1483 *p++ = ':'; \
1484 *p++ = ' '; \
1485 strncpy(p, strerror(err), B + sizeof(B) - p - 1); \
1486 B[sizeof(B) - 1] = 0; \
1488 } while (0)
1490 void Msg(int err, const char *fmt, ...)
1492 char buf[MAXPATHLEN * 2];
1493 PROCESS_MESSAGE(buf);
1495 if (display && displays)
1496 MakeStatus(buf);
1497 else if (displays) {
1498 for (display = displays; display; display = display->d_next)
1499 MakeStatus(buf);
1500 } else if (display) {
1501 /* no displays but a display - must have forked.
1502 * send message to backend!
1504 char *tty = D_usertty;
1505 Display *olddisplay = display;
1506 display = NULL; /* only send once */
1507 SendErrorMsg(tty, buf);
1508 display = olddisplay;
1509 } else
1510 printf("%s\r\n", buf);
1512 if (queryflag >= 0)
1513 write(queryflag, buf, strlen(buf));
1517 * Call FinitTerm for all displays, write a message to each and call eexit();
1519 void Panic(int err, const char *fmt, ...)
1521 char buf[MAXPATHLEN * 2];
1522 PROCESS_MESSAGE(buf);
1524 if (displays == NULL && display == NULL) {
1525 printf("%s\r\n", buf);
1526 if (PanicPid)
1527 Kill(PanicPid, SIG_BYE);
1528 } else if (displays == NULL) {
1529 /* no displays but a display - must have forked.
1530 * send message to backend!
1532 char *tty = D_usertty;
1533 display = NULL;
1534 SendErrorMsg(tty, buf);
1535 sleep(2);
1536 _exit(1);
1537 } else
1538 for (display = displays; display; display = display->d_next) {
1539 if (D_status)
1540 RemoveStatus();
1541 FinitTerm();
1542 Flush(3);
1543 #ifdef ENABLE_UTMP
1544 RestoreLoginSlot();
1545 #endif
1546 SetTTY(D_userfd, &D_OldMode);
1547 fcntl(D_userfd, F_SETFL, 0);
1548 write(D_userfd, buf, strlen(buf));
1549 write(D_userfd, "\n", 1);
1550 freetty();
1551 if (D_userpid)
1552 Kill(D_userpid, SIG_BYE);
1554 if (tty_oldmode >= 0) {
1555 #if defined(HAVE_SETEUID)
1556 if (setuid(own_uid))
1557 xseteuid(own_uid); /* may be a loop. sigh. */
1558 #else
1559 setuid(own_uid);
1560 #endif
1561 chmod(attach_tty, tty_oldmode);
1563 eexit(1);
1566 void QueryMsg(int err, const char *fmt, ...)
1568 char buf[MAXPATHLEN * 2];
1570 if (queryflag < 0)
1571 return;
1573 PROCESS_MESSAGE(buf);
1574 write(queryflag, buf, strlen(buf));
1577 void Dummy(int err, const char *fmt, ...)
1579 (void)err; /* unused */
1580 (void)fmt; /* unused */
1583 #undef PROCESS_MESSAGE
1586 * '^' is allowed as an escape mechanism for control characters. jw.
1588 * Added time insertion using ideas/code from /\ndy Jones
1589 * (andy@lingua.cltr.uq.OZ.AU) - thanks a lot!
1593 void PutWinMsg(char *s, int start, int max)
1595 int i, p, l, n;
1596 uint64_t r;
1597 struct mchar rend;
1598 struct mchar rendstack[MAX_WINMSG_REND];
1599 int rendstackn = 0;
1601 if (s != g_winmsg->buf) {
1602 /* sorry, no fancy coloring available */
1603 l = strlen(s);
1604 if (l > max)
1605 l = max;
1606 l -= start;
1607 s += start;
1608 while (l-- > 0)
1609 PUTCHARLP(*s++);
1610 return;
1612 rend = D_rend;
1613 p = 0;
1614 l = strlen(s);
1615 for (i = 0; i < g_winmsg->numrend && max > 0; i++) {
1616 if (p > g_winmsg->rendpos[i] || g_winmsg->rendpos[i] > l)
1617 break;
1618 if (p < g_winmsg->rendpos[i]) {
1619 n = g_winmsg->rendpos[i] - p;
1620 if (n > max)
1621 n = max;
1622 max -= n;
1623 p += n;
1624 while (n-- > 0) {
1625 if (start-- > 0)
1626 s++;
1627 else
1628 PUTCHARLP(*s++);
1631 r = g_winmsg->rend[i];
1632 if (r == 0) {
1633 if (rendstackn > 0)
1634 rend = rendstack[--rendstackn];
1635 } else {
1636 rendstack[rendstackn++] = rend;
1637 ApplyAttrColor(r, &rend);
1639 SetRendition(&rend);
1641 if (p < l) {
1642 n = l - p;
1643 if (n > max)
1644 n = max;
1645 while (n-- > 0) {
1646 if (start-- > 0)
1647 s++;
1648 else
1649 PUTCHARLP(*s++);
1654 static void serv_read_fn(Event *event, void *data)
1656 (void)event; /* unused */
1657 (void)data; /* unused */
1659 ReceiveMsg();
1662 static void serv_select_fn(Event *event, void *data)
1664 Window *p;
1666 (void)event; /* unused */
1667 (void)data; /* unused */
1669 /* XXX: messages?? */
1670 if (GotSigChld) {
1671 SigChldHandler();
1673 if (InterruptPlease) {
1674 /* This approach is rather questionable in a multi-display
1675 * environment */
1676 if (fore && displays) {
1677 char ibuf = displays->d_OldMode.tio.c_cc[VINTR];
1678 write(W_UWP(fore) ? fore->w_pwin->p_ptyfd : fore->w_ptyfd, &ibuf, 1);
1680 InterruptPlease = 0;
1683 for (p = mru_window; p; p = p->w_prev_mru) {
1684 if (p->w_bell == BELL_FOUND || p->w_bell == BELL_VISUAL) {
1685 Canvas *cv;
1686 int visual = p->w_bell == BELL_VISUAL || visual_bell;
1687 p->w_bell = BELL_ON;
1688 for (display = displays; display; display = display->d_next) {
1689 for (cv = D_cvlist; cv; cv = cv->c_next)
1690 if (cv->c_layer->l_bottom == &p->w_layer)
1691 break;
1692 if (cv == NULL) {
1693 p->w_bell = BELL_DONE;
1694 Msg(0, "%s", MakeWinMsg(BellString, p, '%'));
1695 } else if (visual && !D_VB && (!D_status || !D_status_bell)) {
1696 Msg(0, "%s", VisualBellString);
1697 if (D_status) {
1698 D_status_bell = 1;
1699 SetTimeout(&D_statusev, VBellWait);
1703 /* don't annoy the user with two messages */
1704 if (p->w_monitor == MON_FOUND)
1705 p->w_monitor = MON_DONE;
1706 WindowChanged(p, WINESC_WFLAGS);
1708 if (p->w_monitor == MON_FOUND) {
1709 Canvas *cv;
1710 p->w_monitor = MON_ON;
1711 for (display = displays; display; display = display->d_next) {
1712 for (cv = D_cvlist; cv; cv = cv->c_next)
1713 if (cv->c_layer->l_bottom == &p->w_layer)
1714 break;
1715 if (cv)
1716 continue; /* user already sees window */
1717 if (!(ACLBYTE(p->w_mon_notify, D_user->u_id) & ACLBIT(D_user->u_id)))
1718 continue; /* user doesn't care */
1719 Msg(0, "%s", MakeWinMsg(ActivityString, p, '%'));
1720 p->w_monitor = MON_DONE;
1722 WindowChanged(p, WINESC_WFLAGS);
1724 if (p->w_silence == SILENCE_FOUND) {
1725 /* Unset the flag if the user switched to this window. */
1726 if (p->w_layer.l_cvlist) {
1727 p->w_silence = SILENCE_ON;
1728 WindowChanged(p, WINESC_WFLAGS);
1733 for (display = displays; display; display = display->d_next) {
1734 Canvas *cv;
1735 if (D_status == STATUS_ON_WIN)
1736 continue;
1737 /* XXX: should use display functions! */
1738 for (cv = D_cvlist; cv; cv = cv->c_next) {
1739 int lx, ly;
1741 /* normalize window, see resize.c */
1742 lx = cv->c_layer->l_x;
1743 ly = cv->c_layer->l_y;
1744 if (lx == cv->c_layer->l_width)
1745 lx--;
1746 if (ly + cv->c_yoff < cv->c_ys) {
1747 int i, n = cv->c_ys - (ly + cv->c_yoff);
1748 cv->c_yoff = cv->c_ys - ly;
1749 RethinkViewportOffsets(cv);
1750 if (n > cv->c_layer->l_height)
1751 n = cv->c_layer->l_height;
1752 CV_CALL(cv,
1753 LScrollV(flayer, -n, 0, flayer->l_height - 1, 0);
1754 LayRedisplayLine(-1, -1, -1, 1); for (i = 0; i < n; i++)
1755 LayRedisplayLine(i, 0, flayer->l_width - 1, 1);
1756 if (cv == cv->c_display->d_forecv)
1757 LaySetCursor();) ;
1758 } else if (ly + cv->c_yoff > cv->c_ye) {
1759 int i, n = ly + cv->c_yoff - cv->c_ye;
1760 cv->c_yoff = cv->c_ye - ly;
1761 RethinkViewportOffsets(cv);
1762 if (n > cv->c_layer->l_height)
1763 n = cv->c_layer->l_height;
1764 CV_CALL(cv,
1765 LScrollV(flayer, n, 0, cv->c_layer->l_height - 1, 0);
1766 LayRedisplayLine(-1, -1, -1, 1); for (i = 0; i < n; i++)
1767 LayRedisplayLine(i + flayer->l_height - n, 0, flayer->l_width - 1, 1);
1768 if (cv == cv->c_display->d_forecv)
1769 LaySetCursor();) ;
1771 if (lx + cv->c_xoff < cv->c_xs) {
1772 int i, n = cv->c_xs - (lx + cv->c_xoff);
1773 if (n < (cv->c_xe - cv->c_xs + 1) / 2)
1774 n = (cv->c_xe - cv->c_xs + 1) / 2;
1775 if (cv->c_xoff + n > cv->c_xs)
1776 n = cv->c_xs - cv->c_xoff;
1777 cv->c_xoff += n;
1778 RethinkViewportOffsets(cv);
1779 if (n > cv->c_layer->l_width)
1780 n = cv->c_layer->l_width;
1781 CV_CALL(cv, LayRedisplayLine(-1, -1, -1, 1); for (i = 0; i < flayer->l_height; i++) {
1782 LScrollH(flayer, -n, i, 0, flayer->l_width - 1, 0, NULL);
1783 LayRedisplayLine(i, 0, n - 1, 1);}
1784 if (cv == cv->c_display->d_forecv)
1785 LaySetCursor();) ;
1786 } else if (lx + cv->c_xoff > cv->c_xe) {
1787 int i, n = lx + cv->c_xoff - cv->c_xe;
1788 if (n < (cv->c_xe - cv->c_xs + 1) / 2)
1789 n = (cv->c_xe - cv->c_xs + 1) / 2;
1790 if (cv->c_xoff - n + cv->c_layer->l_width - 1 < cv->c_xe)
1791 n = cv->c_xoff + cv->c_layer->l_width - 1 - cv->c_xe;
1792 cv->c_xoff -= n;
1793 RethinkViewportOffsets(cv);
1794 if (n > cv->c_layer->l_width)
1795 n = cv->c_layer->l_width;
1796 CV_CALL(cv, LayRedisplayLine(-1, -1, -1, 1); for (i = 0; i < flayer->l_height; i++) {
1797 LScrollH(flayer, n, i, 0, flayer->l_width - 1, 0, NULL);
1798 LayRedisplayLine(i, flayer->l_width - n, flayer->l_width - 1, 1);}
1799 if (cv == cv->c_display->d_forecv)
1800 LaySetCursor();) ;
1805 for (display = displays; display; display = display->d_next) {
1806 if (D_status == STATUS_ON_WIN || D_cvlist == NULL || D_cvlist->c_next == NULL)
1807 continue;
1808 CV_CALL(D_forecv, LayRestore();
1809 LaySetCursor());
1813 static void logflush_fn(Event *event, void *data)
1815 Window *p;
1816 char *buf;
1817 int n;
1819 (void)data; /* unused */
1821 if (!islogfile(NULL))
1822 return; /* no more logfiles */
1823 logfflush(NULL);
1824 n = log_flush ? log_flush : (logtstamp_after + 4) / 5;
1825 if (n) {
1826 SetTimeout(event, n * 1000);
1827 evenq(event); /* re-enqueue ourself */
1829 if (!logtstamp_on)
1830 return;
1831 /* write fancy time-stamp */
1832 for (p = mru_window; p; p = p->w_prev_mru) {
1833 if (!p->w_log)
1834 continue;
1835 p->w_logsilence += n;
1836 if (p->w_logsilence < logtstamp_after)
1837 continue;
1838 if (p->w_logsilence - n >= logtstamp_after)
1839 continue;
1840 buf = MakeWinMsg(logtstamp_string, p, '%');
1841 logfwrite(p->w_log, buf, strlen(buf));
1846 * Interprets ^?, ^@ and other ^-control-char notation.
1847 * Interprets \ddd octal notation
1849 * The result is placed in *cp, p is advanced behind the parsed expression and
1850 * returned.
1852 static char *ParseChar(char *p, char *cp)
1854 if (*p == 0)
1855 return NULL;
1856 if (*p == '^' && p[1]) {
1857 if (*++p == '?')
1858 *cp = '\177';
1859 else if (*p >= '@')
1860 *cp = Ctrl(*p);
1861 else
1862 return NULL;
1863 ++p;
1864 } else if (*p == '\\' && *++p <= '7' && *p >= '0') {
1865 *cp = 0;
1867 *cp = *cp * 8 + *p - '0';
1868 while (*++p <= '7' && *p >= '0');
1869 } else
1870 *cp = *p++;
1871 return p;
1874 static int ParseEscape(char *p)
1876 unsigned char buf[2];
1878 if (*p == 0)
1879 SetEscape(NULL, -1, -1);
1880 else {
1881 if ((p = ParseChar(p, (char *)buf)) == NULL || (p = ParseChar(p, (char *)buf + 1)) == NULL || *p)
1882 return -1;
1883 SetEscape(NULL, buf[0], buf[1]);
1885 return 0;
1888 static void SetTtyname(bool fatal, struct stat *st)
1890 int ret;
1891 int saved_errno = 0;
1893 attach_tty_is_in_new_ns = false;
1894 memset(&attach_tty_name_in_ns, 0, ARRAY_SIZE(attach_tty_name_in_ns));
1896 errno = 0;
1897 attach_tty = ttyname(0);
1898 if (!attach_tty) {
1899 if (errno == ENODEV) {
1900 saved_errno = errno;
1901 attach_tty = "/proc/self/fd/0";
1902 attach_tty_is_in_new_ns = true;
1903 ret = readlink(attach_tty, attach_tty_name_in_ns, ARRAY_SIZE(attach_tty_name_in_ns));
1904 if (ret < 0 || (size_t)ret >= ARRAY_SIZE(attach_tty_name_in_ns))
1905 Panic(0, "Bad tty '%s'", attach_tty);
1906 } else if (fatal) {
1907 Panic(0, "Must be connected to a terminal.");
1908 } else {
1909 attach_tty = "";
1913 if (attach_tty && strcmp(attach_tty, "")) {
1914 if (stat(attach_tty, st))
1915 Panic(errno, "Cannot access '%s'", attach_tty);
1917 if (strlen(attach_tty) >= MAXPATHLEN)
1918 Panic(0, "TtyName too long - sorry.");
1920 /* Only call CheckTtyname() if the device does not exist in
1921 * another namespace.
1923 if (saved_errno != ENODEV && CheckTtyname(attach_tty))
1924 Panic(0, "Bad tty '%s'", attach_tty);