/* Copyright (c) 2010 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net) * Copyright (c) 2008, 2009 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) * Micah Cowan (micah@cowan.name) * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net) * Copyright (c) 1993-2002, 2003, 2005, 2006, 2007 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) * Copyright (c) 1987 Oliver Laumann * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING); if not, see * https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * **************************************************************** */ #include "config.h" #include "screen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_LANGINFO_H) #include #endif #include "logfile.h" /* islogfile, logfflush, logfopen/logfclose */ #include "fileio.h" #include "list_generic.h" #include "mark.h" #include "utmp.h" #include "winmsg.h" extern char **environ; int force_vt = 1; int VBellWait, MsgWait, MsgMinWait, SilenceWait; char *ShellProg; char *ShellArgs[2]; struct backtick; static struct passwd *getpwbyname(char *, struct passwd *); static void SigChldHandler(void); static void SigChld(int); static void SigInt(int); static void CoreDump(int); static void FinitHandler(int); static void DoWait(void); static void serv_read_fn(Event *, void *); static void serv_select_fn(Event *, void *); static void logflush_fn(Event *, void *); static int IsSymbol(char *, char *); static char *ParseChar(char *, char *); static int ParseEscape(char *); static void SetTtyname(bool fatal, struct stat *st); int nversion; /* numerical version, used for secondary DA */ /* the attacher */ struct passwd *ppp; char *attach_tty; int attach_fd = -1; char *attach_term; char *LoginName; struct mode attach_Mode; /* Indicator whether the current tty exists in another namespace. */ bool attach_tty_is_in_new_ns = false; /* Content of the tty symlink when attach_tty_is_in_new_ns == true. */ char attach_tty_name_in_ns[MAXPATHLEN]; char SocketPath[MAXPATHLEN]; char *SocketName; /* SocketName is pointer in SocketPath */ char *SocketMatch = NULL; /* session id command line argument */ int ServerSocket = -1; Event serv_read; Event serv_select; Event logflushev; char **NewEnv = NULL; char *RcFileName = NULL; char *home; char *screenlogfile; /* filename layout */ int log_flush = 10; /* flush interval in seconds */ bool logtstamp_on = false; /* tstamp disabled */ char *logtstamp_string; /* stamp layout */ int logtstamp_after = 120; /* first tstamp after 120s */ char *hardcopydir = NULL; char *BellString; char *VisualBellString; char *ActivityString; char *BufferFile; char *PowDetachString; char *hstatusstring; char *captionstring; char *wliststr; char *wlisttit; bool auto_detach = true; bool adaptflag, iflag, lsflag, quietflag, wipeflag, xflag; int rflag, dflag; int queryflag = -1; bool hastruecolor = false; char *multi; int multiattach; int tty_mode; int tty_oldmode = -1; char HostName[MAXSTR]; pid_t MasterPid, PanicPid; uid_t real_uid, eff_uid; uid_t multi_uid; uid_t own_uid; gid_t real_gid, eff_gid; bool default_startup; int ZombieKey_destroy, ZombieKey_resurrect, ZombieKey_onerror; char *preselect = NULL; /* only used in Attach() */ char *screenencodings; bool cjkwidth; Layer *flayer; Window *fore; Window *mru_window; Window *first_window; Window *last_window; Window *console_window; #ifdef ENABLE_TELNET int af; #endif /* * Do this last */ #include "attacher.h" #include "encoding.h" #include "help.h" #include "misc.h" #include "process.h" #include "socket.h" #include "termcap.h" #include "tty.h" char strnomem[] = "Out of memory."; static int InterruptPlease; static int GotSigChld; /********************************************************************/ /********************************************************************/ /********************************************************************/ static struct passwd *getpwbyname(char *name, struct passwd *ppp) { int n; if (!ppp && !(ppp = getpwnam(name))) return NULL; /* Do password sanity check..., allow ##user for SUN_C2 security */ n = 0; if (ppp->pw_passwd[0] == '#' && ppp->pw_passwd[1] == '#' && strcmp(ppp->pw_passwd + 2, ppp->pw_name) == 0) n = 13; for (; n < 13; n++) { char c = ppp->pw_passwd[n]; if (!(c == '.' || c == '/' || c == '$' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) break; } if (n < 13) ppp->pw_passwd = NULL; if (ppp->pw_passwd && strlen(ppp->pw_passwd) == 13 + 11) ppp->pw_passwd[13] = 0; /* beware of linux's long passwords */ return ppp; } static char *locale_name(void) { static char *s; s = getenv("LC_ALL"); if (s == NULL) s = getenv("LC_CTYPE"); if (s == NULL) s = getenv("LANG"); if (s == NULL) s = "C"; return s; } static void exit_with_usage(char *myname, char *message, char *arg) { printf("Use: %s [-opts] [cmd [args]]\n", myname); printf(" or: %s -r [host.tty]\n\nOptions:\n", myname); #ifdef ENABLE_TELNET printf("-4 Resolve hostnames only to IPv4 addresses.\n"); printf("-6 Resolve hostnames only to IPv6 addresses.\n"); #endif printf("-a Force all capabilities into each window's termcap.\n"); printf("-A -[r|R] Adapt all windows to the new display width & height.\n"); printf("-c file Read configuration file instead of '.screenrc'.\n"); printf("-d (-r) Detach the elsewhere running screen (and reattach here).\n"); printf("-dmS name Start as daemon: Screen session in detached mode.\n"); printf("-D (-r) Detach and logout remote (and reattach here).\n"); printf("-D -RR Do whatever is needed to get a screen session.\n"); printf("-e xy Change command characters.\n"); printf("-f Flow control on, -fn = off, -fa = auto.\n"); printf("-h lines Set the size of the scrollback history buffer.\n"); printf("-i Interrupt output sooner when flow control is on.\n"); #if defined(ENABLE_UTMP) printf("-l Login mode on (update %s), -ln = off.\n", UTMPXFILE); #endif printf("-ls [match] or\n"); printf("-list Do nothing, just list our SocketDir [on possible matches].\n"); printf("-L Turn on output logging.\n"); printf("-Logfile file Set logfile name.\n"); printf("-m ignore $STY variable, do create a new screen session.\n"); printf("-O Choose optimal output rather than exact vt100 emulation.\n"); printf("-p window Preselect the named window if it exists.\n"); printf("-q Quiet startup. Exits with non-zero return code if unsuccessful.\n"); printf("-Q Commands will send the response to the stdout of the querying process.\n"); printf("-r [session] Reattach to a detached screen process.\n"); printf("-R Reattach if possible, otherwise start a new session.\n"); printf("-s shell Shell to execute rather than $SHELL.\n"); printf("-S sockname Name this session .sockname instead of ...\n"); printf("-t title Set title. (window's name).\n"); printf("-T term Use term as $TERM for windows, rather than \"screen\".\n"); printf("-U Tell screen to use UTF-8 encoding.\n"); printf("-v Print \"Screen version %s\".\n", version); printf("-wipe [match] Do nothing, just clean up SocketDir [on possible matches].\n"); printf("-x Attach to a not detached screen. (Multi display mode).\n"); printf("-X Execute as a screen command in the specified session.\n"); if (message && *message) { printf("\nError: "); printf(message, arg); printf("\n"); } exit(1); } int main(int argc, char **argv) { int n; char *ap; char *av0; char socknamebuf[FILENAME_MAX + 1]; int mflag = 0; char *myname = (argc == 0) ? "screen" : argv[0]; char *SocketDir; struct stat st; mode_t oumask; struct NewWindow nwin; bool detached = false; /* start up detached */ char *sockp; char *sty = NULL; char *multi_home = NULL; bool cmdflag = 0; /* * First, close all unused descriptors * (otherwise, we might have problems with the select() call) */ closeallfiles(0); snprintf(version, 59, "%d.%d.%d (build on %s) ", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, BUILD_DATE); nversion = VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_REVISION; BellString = SaveStr("Bell in window %n"); VisualBellString = SaveStr(" Wuff, Wuff!! "); ActivityString = SaveStr("Activity in window %n"); screenlogfile = SaveStr("screenlog.%n"); logtstamp_string = SaveStr("-- %n:%t -- time-stamp -- %M/%d/%y %c:%s --\n"); hstatusstring = SaveStr("%h"); captionstring = SaveStr("%4n %t"); wlisttit = SaveStr(" Num Name%=Flags"); wliststr = SaveStr("%4n %t%=%f"); BufferFile = SaveStr(DEFAULT_BUFFERFILE); ShellProg = NULL; PowDetachString = NULL; default_startup = (argc > 1) ? false : true; adaptflag = false; VBellWait = VBELLWAIT * 1000; MsgWait = MSGWAIT * 1000; MsgMinWait = MSGMINWAIT * 1000; SilenceWait = SILENCEWAIT; zmodem_sendcmd = SaveStr("!!! sz -vv -b "); zmodem_recvcmd = SaveStr("!!! rz -vv -b -E"); CompileKeys(NULL, 0, mark_key_tab); InitBuiltinTabs(); screenencodings = SaveStr(SCREENENCODINGS); cjkwidth = 0; nwin = nwin_undef; nwin_options = nwin_undef; strncpy(screenterm, "screen", MAXTERMLEN); screenterm[MAXTERMLEN] = '\0'; #ifdef ENABLE_TELNET af = AF_UNSPEC; #endif real_uid = getuid(); real_gid = getgid(); eff_uid = geteuid(); eff_gid = getegid(); av0 = *argv; /* if this is a login screen, assume -RR */ if (*av0 == '-') { rflag = 4; xflag = true; ShellProg = SaveStr(DefaultShell); /* to prevent nasty circles */ } while (argc > 0) { ap = *++argv; if (--argc > 0 && *ap == '-') { if (ap[1] == '-' && ap[2] == 0) { argv++; argc--; break; } if (ap[1] == '-' && !strncmp(ap, "--version", 9)) { printf("Screen version %s\n", version); exit(0); } if (ap[1] == '-' && !strncmp(ap, "--help", 6)) exit_with_usage(myname, NULL, NULL); while (ap && *ap && *++ap) { switch (*ap) { #ifdef ENABLE_TELNET case '4': af = AF_INET; break; case '6': af = AF_INET6; break; #endif case 'a': nwin_options.aflag = true; break; case 'A': adaptflag = true; break; case 'p': /* preselect */ if (*++ap) preselect = ap; else { if (!--argc) exit_with_usage(myname, "Specify a window to preselect with -p", NULL); preselect = *++argv; } ap = NULL; break; case 'c': if (*++ap) RcFileName = ap; else { if (--argc == 0) exit_with_usage(myname, "Specify an alternate rc-filename with -c", NULL); RcFileName = *++argv; } ap = NULL; break; case 'e': if (!*++ap) { if (--argc == 0) exit_with_usage(myname, "Specify command characters with -e", NULL); ap = *++argv; } if (ParseEscape(ap)) Panic(0, "Two characters are required with -e option, not '%s'.", ap); ap = NULL; break; case 'f': ap++; switch (*ap++) { case 'n': case '0': nwin_options.flowflag = FLOW_ON; break; case '\0': ap--; /* FALLTHROUGH */ case 'y': case '1': nwin_options.flowflag = FLOW_OFF; break; case 'a': nwin_options.flowflag = FLOW_AUTOFLAG; break; default: exit_with_usage(myname, "Unknown flow option -%s", --ap); } break; case 'h': if (--argc == 0) exit_with_usage(myname, NULL, NULL); nwin_options.histheight = atoi(*++argv); if (nwin_options.histheight < 0) exit_with_usage(myname, "-h: %s: negative scrollback size?", *argv); break; case 'i': iflag = true; break; case 't': /* title, the former AkA == -k */ if (--argc == 0) exit_with_usage(myname, "Specify a new window-name with -t", NULL); nwin_options.aka = *++argv; break; case 'l': ap++; switch (*ap++) { case 'n': case '0': nwin_options.lflag = 0; break; case '\0': ap--; /* FALLTHROUGH */ case 'y': case '1': nwin_options.lflag = 1; break; case 'a': nwin_options.lflag = 3; break; case 's': /* -ls */ case 'i': /* -list */ lsflag = true; if (argc > 1 && !SocketMatch) { SocketMatch = *++argv; argc--; } ap = NULL; break; default: exit_with_usage(myname, "%s: Unknown suboption to -l", --ap); } break; case 'w': if (strcmp(ap + 1, "ipe")) exit_with_usage(myname, "Unknown option %s", --ap); lsflag = true; wipeflag = true; if (argc > 1 && !SocketMatch) { SocketMatch = *++argv; argc--; } break; case 'L': if (!strcmp(ap + 1, "ogfile")) { if (--argc == 0) exit_with_usage(myname, "Specify logfile path with -Logfile", NULL); if (strlen(*++argv) > PATH_MAX) Panic(1, "-Logfile name too long. (max. %d char)", PATH_MAX); free(screenlogfile); /* we already set it up while starting */ screenlogfile = SaveStr(*argv); ap = NULL; } else if (!strcmp(ap, "L")) nwin_options.Lflag = 1; break; case 'm': mflag = 1; break; case 'O': /* to be (or not to be?) deleted. jw. */ force_vt = 0; break; case 'T': if (--argc == 0) exit_with_usage(myname, "Specify terminal-type with -T", NULL); if (strlen(*++argv) < MAXTERMLEN) { strncpy(screenterm, *argv, MAXTERMLEN); screenterm[MAXTERMLEN] = '\0'; } else Panic(0, "-T: terminal name too long. (max. %d char)", MAXTERMLEN); nwin_options.term = screenterm; break; case 'q': quietflag = true; break; case 'Q': queryflag = 1; cmdflag = true; break; case 'r': case 'R': case 'x': if (argc > 1 && *argv[1] != '-' && !SocketMatch) { SocketMatch = *++argv; argc--; } if (*ap == 'x') xflag = true; if (rflag) rflag = 2; rflag += (*ap == 'R') ? 2 : 1; break; case 'd': dflag = 1; /* FALLTHROUGH */ case 'D': if (!dflag) dflag = 2; if (argc == 2) { if (*argv[1] != '-' && !SocketMatch) { SocketMatch = *++argv; argc--; } } break; case 's': if (--argc == 0) exit_with_usage(myname, "Specify shell with -s", NULL); if (ShellProg) free(ShellProg); ShellProg = SaveStr(*++argv); break; case 'S': if (!SocketMatch) { if (--argc == 0) exit_with_usage(myname, "Specify session-name with -S", NULL); SocketMatch = *++argv; } if (!*SocketMatch) exit_with_usage(myname, "Empty session-name?", NULL); break; case 'X': cmdflag = true; break; case 'v': printf("Screen version %s\n", version); exit(0); case 'U': nwin_options.encoding = nwin_options.encoding == -1 ? UTF8 : 0; break; default: exit_with_usage(myname, "Unknown option %s", --ap); } } } else break; } xsignal(SIGSEGV, CoreDump); setlocale(LC_ALL, ""); if (nwin_options.encoding == -1) { /* ask locale if we should start in UTF-8 mode */ #ifdef HAVE_LANGINFO_H nwin_options.encoding = FindEncoding(nl_langinfo(CODESET)); #else char *s; if ((s = locale_name()) && strstr(s, "UTF-8")) nwin_options.encoding = UTF8; #endif } { char *s; if ((s = locale_name())) { if (!strncmp(s, "zh_", 3) || !strncmp(s, "ja_", 3) || !strncmp(s, "ko_", 3)) { cjkwidth = 1; } } } if (nwin_options.aka) { if (nwin_options.encoding > 0) { size_t len = strlen(nwin_options.aka); size_t newsz; char *newbuf = malloc(3 * len); if (!newbuf) Panic(0, "%s", strnomem); newsz = RecodeBuf((unsigned char *)nwin_options.aka, len, nwin_options.encoding, 0, (unsigned char *)newbuf); newbuf[newsz] = '\0'; nwin_options.aka = newbuf; } else { /* If we just use the original value from av, subsequent shelltitle invocations will attempt to free space we don't own... */ nwin_options.aka = SaveStr(nwin_options.aka); } } if (SocketMatch && strlen(SocketMatch) >= MAXSTR) Panic(0, "Ridiculously long socketname - try again."); if (cmdflag && !rflag && !dflag && !xflag) xflag = true; if (!cmdflag && dflag && mflag && !(rflag || xflag)) detached = true; nwin = nwin_options; nwin.encoding = nwin_undef.encoding; /* let screenrc overwrite it */ if (argc) nwin.args = argv; if (!ShellProg) { char *sh; sh = getenv("SHELL"); ShellProg = SaveStr(sh ? sh : DefaultShell); } ShellArgs[0] = ShellProg; home = getenv("HOME"); if (!mflag && !SocketMatch) { sty = getenv("STY"); if (sty && *sty == 0) sty = NULL; } own_uid = multi_uid = real_uid; if (SocketMatch && (sockp = strchr(SocketMatch, '/'))) { *sockp = 0; multi = SocketMatch; SocketMatch = sockp + 1; if (*multi) { struct passwd *mppp; if ((mppp = getpwnam(multi)) == NULL) Panic(0, "Cannot identify account '%s'.", multi); multi_uid = mppp->pw_uid; multi_home = SaveStr(mppp->pw_dir); if (strlen(multi_home) > MAXPATHLEN - 10) Panic(0, "home directory path too long"); /* always fake multi attach mode */ if (rflag || lsflag) xflag = 1; detached = false; multiattach = 1; } /* Special case: effective user is multiuser. */ if (eff_uid && (multi_uid != eff_uid)) Panic(0, "Must run suid root for multiuser support."); } if (SocketMatch && *SocketMatch == 0) SocketMatch = NULL; if ((LoginName = getlogin()) != NULL) { if ((ppp = getpwnam(LoginName)) != NULL) if (ppp->pw_uid != real_uid) ppp = NULL; } if (ppp == NULL) { if ((ppp = getpwuid(real_uid)) == NULL) { Panic(0, "getpwuid() can't identify your account!"); exit(1); } LoginName = ppp->pw_name; } LoginName = SaveStr(LoginName); ppp = getpwbyname(LoginName, ppp); #if !defined(SOCKET_DIR) if (multi && !multiattach) { if (home && strcmp(home, ppp->pw_dir)) Panic(0, "$HOME must match passwd entry for multiuser screens."); } #endif #define SET_GUID() do \ { \ if (setgid(real_gid)) \ Panic(0, "setgid"); \ if (setuid(real_uid)) \ Panic(0, "setuid"); \ eff_uid = real_uid; \ eff_gid = real_gid; \ } while (0) if (home == NULL || *home == '\0') home = ppp->pw_dir; if (strlen(LoginName) > MAXLOGINLEN) Panic(0, "LoginName too long - sorry."); if (multi && strlen(multi) > MAXLOGINLEN) Panic(0, "Screen owner name too long - sorry."); if (strlen(home) > MAXPATHLEN) Panic(0, "$HOME too long - sorry."); attach_tty = ""; if (!detached && !lsflag && !cmdflag && !(dflag && !mflag && !rflag && !xflag) && !(sty && !SocketMatch && !mflag && !rflag && !xflag)) { int fl; /* ttyname implies isatty */ SetTtyname(true, &st); tty_mode = (int)st.st_mode & 0777; fl = fcntl(0, F_GETFL, 0); if (fl != -1 && (fl & (O_RDWR | O_RDONLY | O_WRONLY)) == O_RDWR) attach_fd = 0; if (attach_fd == -1) { if ((n = secopen(attach_tty, O_RDWR | O_NONBLOCK, 0)) < 0) Panic(0, "Cannot open your terminal '%s' - please check.", attach_tty); /* In case the pts device exists in another namespace we directly operate * on the symbolic link itself. However, this means that we need to keep * the fd open since we have no direct way of identifying the associated * pts device accross namespaces. This is ok though since keeping fds open * is done in the codebase already. */ if (attach_tty_is_in_new_ns) attach_fd = n; else close(n); } if ((attach_term = getenv("TERM")) == NULL || *attach_term == 0) Panic(0, "Please set a terminal type."); if (strlen(attach_term) > MAXTERMLEN) Panic(0, "$TERM too long - sorry."); GetTTY(0, &attach_Mode); } oumask = umask(0); /* well, unsigned never fails? jw. */ SocketDir = getenv("SCREENDIR"); if (SocketDir) { if (strlen(SocketDir) >= MAXPATHLEN - 1) Panic(0, "Ridiculously long $SCREENDIR - try again."); if (multi) Panic(0, "No $SCREENDIR with multi screens, please."); } if (multiattach) { #ifndef SOCKET_DIR sprintf(SocketPath, "%s/.screen", multi_home); #else SocketDir = SOCKET_DIR; sprintf(SocketPath, "%s/S-%s", SocketDir, multi); #endif } else { #ifndef SOCKET_DIR if (SocketDir == NULL) { sprintf(SocketPath, "%s/.screen", home); SocketDir = SocketPath; } #endif if (SocketDir) { if (access(SocketDir, F_OK)) { if (UserContext() > 0) { if (mkdir(SocketDir, 0700)) UserReturn(0); UserReturn(1); } if (UserStatus() <= 0) Panic(0, "Cannot make directory '%s'.", SocketDir); } if (SocketDir != SocketPath) strncpy(SocketPath, SocketDir, ARRAY_SIZE(SocketPath)); } #ifdef SOCKET_DIR else { SocketDir = SOCKET_DIR; if (stat(SocketDir, &st)) { n = (eff_uid == 0 && (real_uid || eff_gid == real_gid)) ? 0755 : (eff_gid != real_gid) ? 0775 : #ifdef S_ISVTX 0777 | S_ISVTX; #else 0777; #endif if (mkdir(SocketDir, n) == -1) Panic(errno, "Cannot make directory '%s'", SocketDir); } else { if (!S_ISDIR(st.st_mode)) Panic(0, "'%s' must be a directory.", SocketDir); if (eff_uid == 0 && real_uid && st.st_uid != eff_uid) Panic(0, "Directory '%s' must be owned by root.", SocketDir); n = (eff_uid == 0 && (real_uid || (st.st_mode & 0775) != 0775)) ? 0755 : (eff_gid == st.st_gid && eff_gid != real_gid) ? 0775 : 0777; if (((int)st.st_mode & 0777) != n) Panic(0, "Directory '%s' must have mode %03o.", SocketDir, n); } sprintf(SocketPath, "%s/S-%s", SocketDir, LoginName); if (access(SocketPath, F_OK)) { if (mkdir(SocketPath, 0700) == -1 && errno != EEXIST) Panic(errno, "Cannot make directory '%s'", SocketPath); (void)chown(SocketPath, real_uid, real_gid); } } #endif } if (stat(SocketPath, &st) == -1) Panic(errno, "Cannot access %s", SocketPath); else if (!S_ISDIR(st.st_mode)) Panic(0, "%s is not a directory.", SocketPath); if (multi) { if (st.st_uid != multi_uid) Panic(0, "%s is not the owner of %s.", multi, SocketPath); } else { #ifdef SOCKET_DIR /* if SOCKETDIR is not defined, the socket is in $HOME. in that case it does not make sense to compare uids. */ if (st.st_uid != real_uid) Panic(0, "You are not the owner of %s.", SocketPath); #endif } if ((st.st_mode & 0777) != 0700) Panic(0, "Directory %s must have mode 700.", SocketPath); if (SocketMatch && strchr(SocketMatch, '/')) Panic(0, "Bad session name '%s'", SocketMatch); SocketName = SocketPath + strlen(SocketPath) + 1; *SocketName = 0; (void)umask(oumask); (void)gethostname(HostName, MAXSTR); HostName[MAXSTR - 1] = '\0'; if ((ap = strchr(HostName, '.')) != NULL) *ap = '\0'; if (lsflag) { int i, fo, oth; if (multi) real_uid = multi_uid; SET_GUID(); i = FindSocket((int *)NULL, &fo, &oth, SocketMatch); if (quietflag) { if (rflag) exit(10 + i); else exit(9 + (fo || oth ? 1 : 0) + fo); } if (fo == 0) Panic(0, "No Sockets found in %s.\n", SocketPath); Msg(0, "%d Socket%s in %s.", fo, fo > 1 ? "s" : "", SocketPath); eexit(0); } xsignal(SIG_BYE, AttacherFinit); /* prevent races */ if (cmdflag) { /* attach_tty is not mandatory */ SetTtyname(false, &st); if (!*argv) Panic(0, "Please specify a command."); SET_GUID(); SendCmdMessage(sty, SocketMatch, argv, queryflag >= 0); exit(0); } else if (rflag || xflag) { if (Attach(MSG_ATTACH)) { Attacher(); /* NOTREACHED */ } if (multiattach) Panic(0, "Can't create sessions of other users."); } else if (dflag && !mflag) { SetTtyname(false, &st); Attach(MSG_DETACH); Msg(0, "[%s %sdetached.]\n", SocketName, (dflag > 1 ? "power " : "")); eexit(0); /* NOTREACHED */ } if (!SocketMatch && !mflag && sty) { /* attach_tty is not mandatory */ SetTtyname(false, &st); SET_GUID(); nwin_options.args = argv; SendCreateMsg(sty, &nwin); exit(0); /* NOTREACHED */ } nwin_compose(&nwin_default, &nwin_options, &nwin_default); if (!detached || dflag != 2) MasterPid = fork(); else MasterPid = 0; switch (MasterPid) { case -1: Panic(errno, "fork"); /* NOTREACHED */ case 0: break; default: if (detached) exit(0); if (SocketMatch) sprintf(socknamebuf, "%d.%s", MasterPid, SocketMatch); else sprintf(socknamebuf, "%d.%s.%s", MasterPid, stripdev(attach_tty), HostName); for (ap = socknamebuf; *ap; ap++) if (*ap == '/') *ap = '-'; if (strlen(socknamebuf) > FILENAME_MAX) socknamebuf[FILENAME_MAX - 1] = 0; sprintf(SocketPath + strlen(SocketPath), "/%s", socknamebuf); SET_GUID(); Attacher(); /* NOTREACHED */ } if (!detached) PanicPid = getppid(); if (DefaultEsc == -1) DefaultEsc = Ctrl('a'); if (DefaultMetaEsc == -1) DefaultMetaEsc = 'a'; ap = av0 + strlen(av0) - 1; while (ap >= av0) { if (!strncmp("screen", ap, 6)) { memcpy(ap, "SCREEN", 6); /* name this process "SCREEN-BACKEND" */ break; } ap--; } if (ap < av0) *av0 = 'S'; if (!detached) { if (attach_fd == -1) { if ((n = secopen(attach_tty, O_RDWR | O_NONBLOCK, 0)) < 0) Panic(0, "Cannot reopen '%s' - please check.", attach_tty); } else n = dup(attach_fd); } else n = -1; /* * This guarantees that the session owner is listed, even when we * start detached. From now on we should not refer to 'LoginName' * any more, use users->u_name instead. */ if (UserAdd(LoginName, NULL) < 0) Panic(0, "Could not create user info"); if (!detached) { if (MakeDisplay(LoginName, attach_tty, attach_term, n, getppid(), &attach_Mode) == NULL) Panic(0, "Could not alloc display"); PanicPid = 0; D_encoding = nwin_options.encoding > 0 ? nwin_options.encoding : 0; } if (!freopen("/dev/null", "r", stdin) || !freopen("/dev/null", "w", stdout) || !freopen("/dev/null", "w", stderr)) Panic(0, "Cannot reassociate std streams"); if (SocketMatch) { /* user started us with -S option */ sprintf(socknamebuf, "%d.%s", (int)getpid(), SocketMatch); } else { sprintf(socknamebuf, "%d.%s.%s", (int)getpid(), stripdev(attach_tty), HostName); } for (ap = socknamebuf; *ap; ap++) if (*ap == '/') *ap = '-'; if (strlen(socknamebuf) > FILENAME_MAX) { socknamebuf[FILENAME_MAX] = 0; } sprintf(SocketPath + strlen(SocketPath), "/%s", socknamebuf); ServerSocket = MakeServerSocket(); #ifdef SYSTEM_SCREENRC (void)StartRc(SYSTEM_SCREENRC, 0); #endif (void)StartRc(RcFileName, 0); #ifdef ENABLE_UTMP InitUtmp(); #endif /* ENABLE_UTMP */ if (display) { if (InitTermcap(0, 0)) { fcntl(D_userfd, F_SETFL, 0); /* Flush sets FNBLOCK */ freetty(); if (D_userpid) Kill(D_userpid, SIG_BYE); eexit(1); } MakeDefaultCanvas(); InitTerm(0); #ifdef ENABLE_UTMP RemoveLoginSlot(); #endif } else MakeTermcap(1); InitKeytab(); MakeNewEnv(); xsignal(SIGHUP, SigHup); xsignal(SIGINT, FinitHandler); xsignal(SIGQUIT, FinitHandler); xsignal(SIGTERM, FinitHandler); xsignal(SIGTTIN, SIG_IGN); xsignal(SIGTTOU, SIG_IGN); if (display) { brktty(D_userfd); SetMode(&D_OldMode, &D_NewMode, D_flow, iflag); /* Note: SetMode must be called _before_ FinishRc. */ SetTTY(D_userfd, &D_NewMode); if (fcntl(D_userfd, F_SETFL, FNBLOCK)) Msg(errno, "Warning: NBLOCK fcntl failed"); } else brktty(-1); /* just try */ xsignal(SIGCHLD, SigChld); #ifdef SYSTEM_SCREENRC FinishRc(SYSTEM_SCREENRC); #endif FinishRc(RcFileName); if (mru_window == NULL) { if (MakeWindow(&nwin) == -1) { struct pollfd pfd[1]; pfd[0].fd = 0; pfd[0].events = POLLIN; Msg(0, "Sorry, could not find a PTY or TTY."); /* allow user to exit early by pressing any key. */ poll(pfd, ARRAY_SIZE(pfd), MsgWait); Finit(0); /* NOTREACHED */ } } else if (argc) { /* Screen was invoked with a command */ MakeWindow(&nwin); } if (display && default_startup) display_license(); xsignal(SIGINT, SigInt); if (rflag && (rflag & 1) == 0 && !quietflag) { Msg(0, "New screen..."); rflag = 0; } serv_read.type = EV_READ; serv_read.fd = ServerSocket; serv_read.handler = serv_read_fn; evenq(&serv_read); serv_select.priority = -10; serv_select.type = EV_ALWAYS; serv_select.handler = serv_select_fn; evenq(&serv_select); logflushev.type = EV_TIMEOUT; logflushev.handler = logflush_fn; sched(); /* NOTREACHED */ return 0; } static void SigChldHandler(void) { struct stat st; while (GotSigChld) { GotSigChld = 0; DoWait(); } if (stat(SocketPath, &st) == -1) { if (!RecoverSocket()) { Finit(1); } } } static void SigChld(int sigsig) { (void)sigsig; /* unused */ GotSigChld = 1; } void SigHup(int sigsig) { (void)sigsig; /* unused */ /* Hangup all displays */ while ((display = displays)) Hangup(); } /* * the backend's Interrupt handler * we cannot insert the intrc directly, as we never know * if fore is valid. */ static void SigInt(int sigsig) { (void)sigsig; /* unused */ xsignal(SIGINT, SigInt); InterruptPlease = 1; } static void CoreDump(int sigsig) { /* if running with s-bit, we must reset the s-bit, so that we get a * core file anyway. */ Display *disp; char buf[80]; (void)sigsig; /* unused */ if (setgid(getgid())) Panic(0, "setgid"); if (setuid(getuid())) Panic(0, "setuid"); unlink("core"); sprintf(buf, "\r\n[screen caught a fatal signal. (core dumped)]\r\n"); for (disp = displays; disp; disp = disp->d_next) { if (disp->d_nonblock < -1 || disp->d_nonblock > 1000000) continue; fcntl(disp->d_userfd, F_SETFL, 0); SetTTY(disp->d_userfd, &D_OldMode); write(disp->d_userfd, buf, strlen(buf)); Kill(disp->d_userpid, SIG_BYE); } abort(); } static void DoWait(void) { pid_t pid; Window *win; int wstat; while ((pid = waitpid(-1, &wstat, WNOHANG | WUNTRACED)) > 0) { for (win = mru_window; win; win = win->w_prev_mru) { if ((win->w_pid && pid == win->w_pid) || (win->w_deadpid && pid == win->w_deadpid)) { /* child has ceased to exist */ win->w_pid = 0; if (WIFSTOPPED(wstat)) { #ifdef SIGTTIN if (WSTOPSIG(wstat) == SIGTTIN) { Msg(0, "Suspended (tty input)"); continue; } #endif #ifdef SIGTTOU if (WSTOPSIG(wstat) == SIGTTOU) { Msg(0, "Suspended (tty output)"); continue; } #endif /* Try to restart process */ Msg(0, "Child has been stopped, restarting."); if (killpg(pid, SIGCONT)) kill(pid, SIGCONT); } else { /* Screen will detect the window has died when the window's * file descriptor signals EOF (which it will do when the process in * the window terminates). So do this in a timeout of 10 seconds. * (not doing this at all might also work) * See #27061 for more details. */ win->w_destroyev.data = (char *)win; win->w_exitstatus = wstat; SetTimeout(&win->w_destroyev, 10 * 1000); evenq(&win->w_destroyev); } break; } if (win->w_pwin && pid == win->w_pwin->p_pid) { FreePseudowin(win); break; } } } } static void FinitHandler(int sigsig) { (void)sigsig; /* unused */ Finit(1); } void Finit(int i) { xsignal(SIGCHLD, SIG_DFL); xsignal(SIGHUP, SIG_IGN); while (mru_window) { Window *p = mru_window; mru_window = mru_window->w_prev_mru; FreeWindow(p); } if (ServerSocket != -1) { xseteuid(real_uid); xsetegid(real_gid); (void)unlink(SocketPath); xseteuid(eff_uid); xsetegid(eff_gid); } for (display = displays; display; display = display->d_next) { if (D_status) RemoveStatus(); FinitTerm(); #ifdef ENABLE_UTMP RestoreLoginSlot(); #endif AddStr("[screen is terminating]\r\n"); Flush(3); SetTTY(D_userfd, &D_OldMode); fcntl(D_userfd, F_SETFL, 0); freetty(); Kill(D_userpid, SIG_BYE); } /* * we _cannot_ call eexit(i) here, * instead of playing with the Socket above. Sigh. */ exit(i); } void eexit(int e) { if (ServerSocket != -1) { if (setgid(real_gid)) AddStr("Failed to set gid\r\n"); if (setuid(real_uid)) AddStr("Failed to set uid\r\n"); if (unlink(SocketPath)) AddStr("Failed to remove socket\r\n"); } exit(e); } void Hangup(void) { if (display == NULL) return; if (D_userfd >= 0) { close(D_userfd); D_userfd = -1; } if (auto_detach || displays->d_next) Detach(D_HANGUP); else Finit(0); } /* * Detach now has the following modes: *D_DETACH SIG_BYE detach backend and exit attacher *D_HANGUP SIG_BYE detach backend and exit attacher *D_STOP SIG_STOP stop attacher (and detach backend) *D_REMOTE SIG_BYE remote detach -- reattach to new attacher *D_POWER SIG_POWER_BYE power detach -- attacher kills his parent *D_REMOTE_POWER SIG_POWER_BYE remote power detach -- both *D_LOCK SIG_LOCK lock the attacher * (jw) * we always remove our utmp slots. (even when "lock" or "stop") * Note: Take extra care here, we may be called by interrupt! */ void Detach(int mode) { int sign = 0; pid_t pid; Canvas *cv; Window *p; if (display == NULL) return; #define AddStrSocket(msg) do { \ if (SocketName) \ { \ AddStr("[" msg " from "); \ AddStr(SocketName); \ AddStr("]\r\n"); \ } \ else \ AddStr("[" msg "]\r\n"); \ } while (0) xsignal(SIGHUP, SIG_IGN); if (D_status) RemoveStatus(); FinitTerm(); if (!display) return; switch (mode) { case D_HANGUP: sign = SIG_BYE; break; case D_DETACH: AddStrSocket("detached"); sign = SIG_BYE; break; case D_STOP: sign = SIG_STOP; break; case D_REMOTE: AddStrSocket("remote detached"); sign = SIG_BYE; break; case D_POWER: AddStrSocket("power detached"); if (PowDetachString) { AddStr(PowDetachString); AddStr("\r\n"); } sign = SIG_POWER_BYE; break; case D_REMOTE_POWER: AddStrSocket("remote power detached"); if (PowDetachString) { AddStr(PowDetachString); AddStr("\r\n"); } sign = SIG_POWER_BYE; break; case D_LOCK: ClearAll(); ClearScrollbackBuffer(); sign = SIG_LOCK; /* tell attacher to lock terminal with a lockprg. */ break; } #ifdef ENABLE_UTMP if (displays->d_next == NULL) { for (p = mru_window; p; p = p->w_prev_mru) { if (p->w_slot != (slot_t) - 1 && !(p->w_lflag & 2)) { RemoveUtmp(p); /* * Set the slot to 0 to get the window * logged in again. */ p->w_slot = (slot_t) 0; } } } if (mode != D_HANGUP) RestoreLoginSlot(); #endif if (displays->d_next == NULL && console_window) { if (TtyGrabConsole(console_window->w_ptyfd, false, "detach")) { KillWindow(console_window); display = displays; /* restore display */ } } if (D_fore) { ReleaseAutoWritelock(display, D_fore); D_user->u_detachwin = D_fore->w_number; D_user->u_detachotherwin = D_other ? D_other->w_number : D_fore->w_number; } AutosaveLayout(D_layout); layout_last = D_layout; for (cv = D_cvlist; cv; cv = cv->c_next) { p = Layer2Window(cv->c_layer); SetCanvasWindow(cv, NULL); if (p) WindowChanged(p, 'u'); } pid = D_userpid; FreeDisplay(); if (displays == NULL) /* Flag detached-ness */ (void)chsock(); /* * tell father what to do. We do that after we * freed the tty, thus getty feels more comfortable on hpux * if it was a power detach. */ Kill(pid, sign); xsignal(SIGHUP, SigHup); #undef AddStrSocket } static int IsSymbol(char *e, char *s) { int l; l = strlen(s); return strncmp(e, s, l) == 0 && e[l] == '='; } void MakeNewEnv(void) { char **op, **np; static char stybuf[MAXSTR]; for (op = environ; *op; ++op) ; if (NewEnv) free((char *)NewEnv); NewEnv = np = malloc((unsigned)(op - environ + 7 + 1) * sizeof(char *)); if (!NewEnv) Panic(0, "%s", strnomem); sprintf(stybuf, "STY=%s", strlen(SocketName) <= MAXSTR - 5 ? SocketName : "?"); *np++ = stybuf; /* NewEnv[0] */ *np++ = Term; /* NewEnv[1] */ np++; /* room for SHELL */ np += 2; /* room for TERMCAP and WINDOW */ for (op = environ; *op; ++op) { if (!IsSymbol(*op, "TERM") && !IsSymbol(*op, "TERMCAP") && !IsSymbol(*op, "STY") && !IsSymbol(*op, "WINDOW") && !IsSymbol(*op, "SCREENCAP") && !IsSymbol(*op, "SHELL") && !IsSymbol(*op, "LINES") && !IsSymbol(*op, "COLUMNS") ) *np++ = *op; } *np = NULL; } #define PROCESS_MESSAGE(B) do { \ char *p = B; \ va_list ap; \ va_start(ap, fmt); \ (void)vsnprintf(p, sizeof(B) - 100, fmt, ap); \ va_end(ap); \ if (err) \ { \ p += strlen(p); \ *p++ = ':'; \ *p++ = ' '; \ strncpy(p, strerror(err), B + sizeof(B) - p - 1); \ B[sizeof(B) - 1] = 0; \ } \ } while (0) void Msg(int err, const char *fmt, ...) { char buf[MAXPATHLEN * 2]; PROCESS_MESSAGE(buf); if (display && displays) MakeStatus(buf); else if (displays) { for (display = displays; display; display = display->d_next) MakeStatus(buf); } else if (display) { /* no displays but a display - must have forked. * send message to backend! */ char *tty = D_usertty; Display *olddisplay = display; display = NULL; /* only send once */ SendErrorMsg(tty, buf); display = olddisplay; } else printf("%s\r\n", buf); if (queryflag >= 0) write(queryflag, buf, strlen(buf)); } /* * Call FinitTerm for all displays, write a message to each and call eexit(); */ void Panic(int err, const char *fmt, ...) { char buf[MAXPATHLEN * 2]; PROCESS_MESSAGE(buf); if (displays == NULL && display == NULL) { printf("%s\r\n", buf); if (PanicPid) Kill(PanicPid, SIG_BYE); } else if (displays == NULL) { /* no displays but a display - must have forked. * send message to backend! */ char *tty = D_usertty; display = NULL; SendErrorMsg(tty, buf); sleep(2); _exit(1); } else for (display = displays; display; display = display->d_next) { if (D_status) RemoveStatus(); FinitTerm(); Flush(3); #ifdef ENABLE_UTMP RestoreLoginSlot(); #endif SetTTY(D_userfd, &D_OldMode); fcntl(D_userfd, F_SETFL, 0); write(D_userfd, buf, strlen(buf)); write(D_userfd, "\n", 1); freetty(); if (D_userpid) Kill(D_userpid, SIG_BYE); } if (tty_oldmode >= 0) { #if defined(HAVE_SETEUID) if (setuid(own_uid)) xseteuid(own_uid); /* may be a loop. sigh. */ #else setuid(own_uid); #endif chmod(attach_tty, tty_oldmode); } eexit(1); } void QueryMsg(int err, const char *fmt, ...) { char buf[MAXPATHLEN * 2]; if (queryflag < 0) return; PROCESS_MESSAGE(buf); write(queryflag, buf, strlen(buf)); } void Dummy(int err, const char *fmt, ...) { (void)err; /* unused */ (void)fmt; /* unused */ } #undef PROCESS_MESSAGE /* * '^' is allowed as an escape mechanism for control characters. jw. * * Added time insertion using ideas/code from /\ndy Jones * (andy@lingua.cltr.uq.OZ.AU) - thanks a lot! * */ void PutWinMsg(char *s, int start, int max) { int i, p, l, n; uint64_t r; struct mchar rend; struct mchar rendstack[MAX_WINMSG_REND]; int rendstackn = 0; if (s != g_winmsg->buf) { /* sorry, no fancy coloring available */ l = strlen(s); if (l > max) l = max; l -= start; s += start; while (l-- > 0) PUTCHARLP(*s++); return; } rend = D_rend; p = 0; l = strlen(s); for (i = 0; i < g_winmsg->numrend && max > 0; i++) { if (p > g_winmsg->rendpos[i] || g_winmsg->rendpos[i] > l) break; if (p < g_winmsg->rendpos[i]) { n = g_winmsg->rendpos[i] - p; if (n > max) n = max; max -= n; p += n; while (n-- > 0) { if (start-- > 0) s++; else PUTCHARLP(*s++); } } r = g_winmsg->rend[i]; if (r == 0) { if (rendstackn > 0) rend = rendstack[--rendstackn]; } else { rendstack[rendstackn++] = rend; ApplyAttrColor(r, &rend); } SetRendition(&rend); } if (p < l) { n = l - p; if (n > max) n = max; while (n-- > 0) { if (start-- > 0) s++; else PUTCHARLP(*s++); } } } static void serv_read_fn(Event *event, void *data) { (void)event; /* unused */ (void)data; /* unused */ ReceiveMsg(); } static void serv_select_fn(Event *event, void *data) { Window *p; (void)event; /* unused */ (void)data; /* unused */ /* XXX: messages?? */ if (GotSigChld) { SigChldHandler(); } if (InterruptPlease) { /* This approach is rather questionable in a multi-display * environment */ if (fore && displays) { char ibuf = displays->d_OldMode.tio.c_cc[VINTR]; write(W_UWP(fore) ? fore->w_pwin->p_ptyfd : fore->w_ptyfd, &ibuf, 1); } InterruptPlease = 0; } for (p = mru_window; p; p = p->w_prev_mru) { if (p->w_bell == BELL_FOUND || p->w_bell == BELL_VISUAL) { Canvas *cv; int visual = p->w_bell == BELL_VISUAL || visual_bell; p->w_bell = BELL_ON; for (display = displays; display; display = display->d_next) { for (cv = D_cvlist; cv; cv = cv->c_next) if (cv->c_layer->l_bottom == &p->w_layer) break; if (cv == NULL) { p->w_bell = BELL_DONE; Msg(0, "%s", MakeWinMsg(BellString, p, '%')); } else if (visual && !D_VB && (!D_status || !D_status_bell)) { Msg(0, "%s", VisualBellString); if (D_status) { D_status_bell = 1; SetTimeout(&D_statusev, VBellWait); } } } /* don't annoy the user with two messages */ if (p->w_monitor == MON_FOUND) p->w_monitor = MON_DONE; WindowChanged(p, WINESC_WFLAGS); } if (p->w_monitor == MON_FOUND) { Canvas *cv; p->w_monitor = MON_ON; for (display = displays; display; display = display->d_next) { for (cv = D_cvlist; cv; cv = cv->c_next) if (cv->c_layer->l_bottom == &p->w_layer) break; if (cv) continue; /* user already sees window */ if (!(ACLBYTE(p->w_mon_notify, D_user->u_id) & ACLBIT(D_user->u_id))) continue; /* user doesn't care */ Msg(0, "%s", MakeWinMsg(ActivityString, p, '%')); p->w_monitor = MON_DONE; } WindowChanged(p, WINESC_WFLAGS); } if (p->w_silence == SILENCE_FOUND) { /* Unset the flag if the user switched to this window. */ if (p->w_layer.l_cvlist) { p->w_silence = SILENCE_ON; WindowChanged(p, WINESC_WFLAGS); } } } for (display = displays; display; display = display->d_next) { Canvas *cv; if (D_status == STATUS_ON_WIN) continue; /* XXX: should use display functions! */ for (cv = D_cvlist; cv; cv = cv->c_next) { int lx, ly; /* normalize window, see resize.c */ lx = cv->c_layer->l_x; ly = cv->c_layer->l_y; if (lx == cv->c_layer->l_width) lx--; if (ly + cv->c_yoff < cv->c_ys) { int i, n = cv->c_ys - (ly + cv->c_yoff); cv->c_yoff = cv->c_ys - ly; RethinkViewportOffsets(cv); if (n > cv->c_layer->l_height) n = cv->c_layer->l_height; CV_CALL(cv, LScrollV(flayer, -n, 0, flayer->l_height - 1, 0); LayRedisplayLine(-1, -1, -1, 1); for (i = 0; i < n; i++) LayRedisplayLine(i, 0, flayer->l_width - 1, 1); if (cv == cv->c_display->d_forecv) LaySetCursor();) ; } else if (ly + cv->c_yoff > cv->c_ye) { int i, n = ly + cv->c_yoff - cv->c_ye; cv->c_yoff = cv->c_ye - ly; RethinkViewportOffsets(cv); if (n > cv->c_layer->l_height) n = cv->c_layer->l_height; CV_CALL(cv, LScrollV(flayer, n, 0, cv->c_layer->l_height - 1, 0); LayRedisplayLine(-1, -1, -1, 1); for (i = 0; i < n; i++) LayRedisplayLine(i + flayer->l_height - n, 0, flayer->l_width - 1, 1); if (cv == cv->c_display->d_forecv) LaySetCursor();) ; } if (lx + cv->c_xoff < cv->c_xs) { int i, n = cv->c_xs - (lx + cv->c_xoff); if (n < (cv->c_xe - cv->c_xs + 1) / 2) n = (cv->c_xe - cv->c_xs + 1) / 2; if (cv->c_xoff + n > cv->c_xs) n = cv->c_xs - cv->c_xoff; cv->c_xoff += n; RethinkViewportOffsets(cv); if (n > cv->c_layer->l_width) n = cv->c_layer->l_width; CV_CALL(cv, LayRedisplayLine(-1, -1, -1, 1); for (i = 0; i < flayer->l_height; i++) { LScrollH(flayer, -n, i, 0, flayer->l_width - 1, 0, NULL); LayRedisplayLine(i, 0, n - 1, 1);} if (cv == cv->c_display->d_forecv) LaySetCursor();) ; } else if (lx + cv->c_xoff > cv->c_xe) { int i, n = lx + cv->c_xoff - cv->c_xe; if (n < (cv->c_xe - cv->c_xs + 1) / 2) n = (cv->c_xe - cv->c_xs + 1) / 2; if (cv->c_xoff - n + cv->c_layer->l_width - 1 < cv->c_xe) n = cv->c_xoff + cv->c_layer->l_width - 1 - cv->c_xe; cv->c_xoff -= n; RethinkViewportOffsets(cv); if (n > cv->c_layer->l_width) n = cv->c_layer->l_width; CV_CALL(cv, LayRedisplayLine(-1, -1, -1, 1); for (i = 0; i < flayer->l_height; i++) { LScrollH(flayer, n, i, 0, flayer->l_width - 1, 0, NULL); LayRedisplayLine(i, flayer->l_width - n, flayer->l_width - 1, 1);} if (cv == cv->c_display->d_forecv) LaySetCursor();) ; } } } for (display = displays; display; display = display->d_next) { if (D_status == STATUS_ON_WIN || D_cvlist == NULL || D_cvlist->c_next == NULL) continue; CV_CALL(D_forecv, LayRestore(); LaySetCursor()); } } static void logflush_fn(Event *event, void *data) { Window *p; char *buf; int n; (void)data; /* unused */ if (!islogfile(NULL)) return; /* no more logfiles */ logfflush(NULL); n = log_flush ? log_flush : (logtstamp_after + 4) / 5; if (n) { SetTimeout(event, n * 1000); evenq(event); /* re-enqueue ourself */ } if (!logtstamp_on) return; /* write fancy time-stamp */ for (p = mru_window; p; p = p->w_prev_mru) { if (!p->w_log) continue; p->w_logsilence += n; if (p->w_logsilence < logtstamp_after) continue; if (p->w_logsilence - n >= logtstamp_after) continue; buf = MakeWinMsg(logtstamp_string, p, '%'); logfwrite(p->w_log, buf, strlen(buf)); } } /* * Interprets ^?, ^@ and other ^-control-char notation. * Interprets \ddd octal notation * * The result is placed in *cp, p is advanced behind the parsed expression and * returned. */ static char *ParseChar(char *p, char *cp) { if (*p == 0) return NULL; if (*p == '^' && p[1]) { if (*++p == '?') *cp = '\177'; else if (*p >= '@') *cp = Ctrl(*p); else return NULL; ++p; } else if (*p == '\\' && *++p <= '7' && *p >= '0') { *cp = 0; do *cp = *cp * 8 + *p - '0'; while (*++p <= '7' && *p >= '0'); } else *cp = *p++; return p; } static int ParseEscape(char *p) { unsigned char buf[2]; if (*p == 0) SetEscape(NULL, -1, -1); else { if ((p = ParseChar(p, (char *)buf)) == NULL || (p = ParseChar(p, (char *)buf + 1)) == NULL || *p) return -1; SetEscape(NULL, buf[0], buf[1]); } return 0; } static void SetTtyname(bool fatal, struct stat *st) { int ret; int saved_errno = 0; attach_tty_is_in_new_ns = false; memset(&attach_tty_name_in_ns, 0, ARRAY_SIZE(attach_tty_name_in_ns)); errno = 0; attach_tty = ttyname(0); if (!attach_tty) { if (errno == ENODEV) { saved_errno = errno; attach_tty = "/proc/self/fd/0"; attach_tty_is_in_new_ns = true; ret = readlink(attach_tty, attach_tty_name_in_ns, ARRAY_SIZE(attach_tty_name_in_ns)); if (ret < 0 || (size_t)ret >= ARRAY_SIZE(attach_tty_name_in_ns)) Panic(0, "Bad tty '%s'", attach_tty); } else if (fatal) { Panic(0, "Must be connected to a terminal."); } else { attach_tty = ""; } } if (attach_tty && strcmp(attach_tty, "")) { if (stat(attach_tty, st)) Panic(errno, "Cannot access '%s'", attach_tty); if (strlen(attach_tty) >= MAXPATHLEN) Panic(0, "TtyName too long - sorry."); /* Only call CheckTtyname() if the device does not exist in * another namespace. */ if (saved_errno != ENODEV && CheckTtyname(attach_tty)) Panic(0, "Bad tty '%s'", attach_tty); } }