version 1.91, 2010/10/16 08:42:35 |
version 1.92, 2010/10/18 20:00:03 |
|
|
|
|
#include <sys/types.h> |
#include <sys/types.h> |
#include <sys/stat.h> |
#include <sys/stat.h> |
#include <sys/wait.h> |
|
|
|
#include <errno.h> |
#include <errno.h> |
#include <event.h> |
#include <event.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <paths.h> |
#include <paths.h> |
#include <pwd.h> |
#include <pwd.h> |
#include <signal.h> |
|
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
#include <syslog.h> |
|
#include <unistd.h> |
#include <unistd.h> |
|
|
#include "tmux.h" |
#include "tmux.h" |
|
|
extern char *malloc_options; |
extern char *malloc_options; |
#endif |
#endif |
|
|
char *cfg_file; |
|
struct options global_options; /* server options */ |
struct options global_options; /* server options */ |
struct options global_s_options; /* session options */ |
struct options global_s_options; /* session options */ |
struct options global_w_options; /* window options */ |
struct options global_w_options; /* window options */ |
|
|
|
|
struct event_base *ev_base; |
struct event_base *ev_base; |
|
|
|
char *cfg_file; |
|
char *shell_cmd; |
int debug_level; |
int debug_level; |
time_t start_time; |
time_t start_time; |
char *socket_path; |
char socket_path[MAXPATHLEN]; |
int login_shell; |
int login_shell; |
|
char *environ_path; |
|
pid_t environ_pid; |
|
u_int environ_idx; |
|
|
struct env_data { |
|
char *path; |
|
pid_t pid; |
|
u_int idx; |
|
}; |
|
|
|
__dead void usage(void); |
__dead void usage(void); |
void parse_env(struct env_data *); |
void parseenvironment(void); |
char *makesockpath(const char *); |
char *makesocketpath(const char *); |
__dead void shell_exec(const char *, const char *); |
|
|
|
struct imsgbuf *main_ibuf; |
|
struct event main_event; |
|
|
|
void main_signal(int, short, unused void *); |
|
void main_callback(int, short, void *); |
|
void main_dispatch(const char *); |
|
|
|
__dead void |
__dead void |
usage(void) |
usage(void) |
{ |
{ |
|
|
} |
} |
|
|
void |
void |
parse_env(struct env_data *data) |
parseenvironment(void) |
{ |
{ |
char *env, *path_pid, *pid_idx, buf[256]; |
char *env, *path_pid, *pid_idx, buf[256]; |
size_t len; |
size_t len; |
const char *errstr; |
const char *errstr; |
long long ll; |
long long ll; |
|
|
data->pid = -1; |
environ_pid = -1; |
if ((env = getenv("TMUX")) == NULL) |
if ((env = getenv("TMUX")) == NULL) |
return; |
return; |
|
|
|
|
|
|
/* path */ |
/* path */ |
len = path_pid - env; |
len = path_pid - env; |
data->path = xmalloc (len + 1); |
environ_path = xmalloc(len + 1); |
memcpy(data->path, env, len); |
memcpy(environ_path, env, len); |
data->path[len] = '\0'; |
environ_path[len] = '\0'; |
|
|
/* pid */ |
/* pid */ |
len = pid_idx - path_pid - 1; |
len = pid_idx - path_pid - 1; |
|
|
ll = strtonum(buf, 0, LONG_MAX, &errstr); |
ll = strtonum(buf, 0, LONG_MAX, &errstr); |
if (errstr != NULL) |
if (errstr != NULL) |
return; |
return; |
data->pid = ll; |
environ_pid = ll; |
|
|
/* idx */ |
/* idx */ |
ll = strtonum(pid_idx+1, 0, UINT_MAX, &errstr); |
ll = strtonum(pid_idx + 1, 0, UINT_MAX, &errstr); |
if (errstr != NULL) |
if (errstr != NULL) |
return; |
return; |
data->idx = ll; |
environ_idx = ll; |
} |
} |
|
|
char * |
char * |
makesockpath(const char *label) |
makesocketpath(const char *label) |
{ |
{ |
char base[MAXPATHLEN], *path; |
char base[MAXPATHLEN], *path; |
struct stat sb; |
struct stat sb; |
|
|
int |
int |
main(int argc, char **argv) |
main(int argc, char **argv) |
{ |
{ |
struct cmd_list *cmdlist; |
struct passwd *pw; |
struct cmd *cmd; |
struct options *oo, *so, *wo; |
enum msgtype msg; |
struct keylist *keylist; |
struct passwd *pw; |
char *s, *path, *label, *home, **var; |
struct options *oo, *so, *wo; |
int opt, flags, quiet = 0; |
struct keylist *keylist; |
|
struct env_data envdata; |
|
struct msg_command_data cmddata; |
|
char *s, *shellcmd, *path, *label, *home, *cause; |
|
char **var; |
|
void *buf; |
|
size_t len; |
|
int opt, flags, quiet = 0, cmdflags = 0; |
|
short events; |
|
|
|
#ifdef DEBUG |
#ifdef DEBUG |
malloc_options = (char *) "AFGJPX"; |
malloc_options = (char *) "AFGJPX"; |
#endif |
#endif |
|
|
flags = 0; |
flags = 0; |
shellcmd = label = path = NULL; |
label = path = NULL; |
envdata.path = NULL; |
|
login_shell = (**argv == '-'); |
login_shell = (**argv == '-'); |
while ((opt = getopt(argc, argv, "28c:df:lL:qS:uUv")) != -1) { |
while ((opt = getopt(argc, argv, "28c:df:lL:qS:uUv")) != -1) { |
switch (opt) { |
switch (opt) { |
|
|
flags &= ~IDENTIFY_256COLOURS; |
flags &= ~IDENTIFY_256COLOURS; |
break; |
break; |
case 'c': |
case 'c': |
if (shellcmd != NULL) |
if (shell_cmd != NULL) |
xfree(shellcmd); |
xfree(shell_cmd); |
shellcmd = xstrdup(optarg); |
shell_cmd = xstrdup(optarg); |
break; |
break; |
case 'f': |
case 'f': |
if (cfg_file != NULL) |
if (cfg_file != NULL) |
|
|
argc -= optind; |
argc -= optind; |
argv += optind; |
argv += optind; |
|
|
if (shellcmd != NULL && argc != 0) |
if (shell_cmd != NULL && argc != 0) |
usage(); |
usage(); |
|
|
log_open_tty(debug_level); |
log_open_tty(debug_level); |
|
|
options_set_number(wo, "utf8", 0); |
options_set_number(wo, "utf8", 0); |
} |
} |
|
|
|
/* Locate the configuration file. */ |
if (cfg_file == NULL) { |
if (cfg_file == NULL) { |
home = getenv("HOME"); |
home = getenv("HOME"); |
if (home == NULL || *home == '\0') { |
if (home == NULL || *home == '\0') { |
|
|
} |
} |
|
|
/* |
/* |
* Figure out the socket path. If specified on the command-line with |
* Figure out the socket path. If specified on the command-line with -S |
* -S or -L, use it, otherwise try $TMUX or assume -L default. |
* or -L, use it, otherwise try $TMUX or assume -L default. |
*/ |
*/ |
parse_env(&envdata); |
parseenvironment(); |
if (path == NULL) { |
if (path == NULL) { |
/* No -L. Try $TMUX, or default. */ |
/* If no -L, use the environment. */ |
if (label == NULL) { |
if (label == NULL) { |
path = envdata.path; |
if (environ_path != NULL) |
if (path == NULL) |
path = xstrdup(environ_path); |
|
else |
label = xstrdup("default"); |
label = xstrdup("default"); |
} |
} |
|
|
/* -L or default set. */ |
/* -L or default set. */ |
if (label != NULL) { |
if (label != NULL) { |
if ((path = makesockpath(label)) == NULL) { |
if ((path = makesocketpath(label)) == NULL) { |
log_warn("can't create socket"); |
log_warn("can't create socket"); |
exit(1); |
exit(1); |
} |
} |
|
|
} |
} |
if (label != NULL) |
if (label != NULL) |
xfree(label); |
xfree(label); |
|
if (realpath(path, socket_path) == NULL) |
if (shellcmd != NULL) { |
strlcpy(socket_path, path, sizeof socket_path); |
msg = MSG_SHELL; |
|
buf = NULL; |
|
len = 0; |
|
} else { |
|
cmddata.pid = envdata.pid; |
|
cmddata.idx = envdata.idx; |
|
|
|
/* Prepare command for server. */ |
|
cmddata.argc = argc; |
|
if (cmd_pack_argv( |
|
argc, argv, cmddata.argv, sizeof cmddata.argv) != 0) { |
|
log_warnx("command too long"); |
|
exit(1); |
|
} |
|
|
|
msg = MSG_COMMAND; |
|
buf = &cmddata; |
|
len = sizeof cmddata; |
|
} |
|
|
|
if (shellcmd != NULL) |
|
cmdflags |= CMD_STARTSERVER; |
|
else if (argc == 0) /* new-session is the default */ |
|
cmdflags |= CMD_STARTSERVER|CMD_SENDENVIRON|CMD_CANTNEST; |
|
else { |
|
/* |
|
* It sucks parsing the command string twice (in client and |
|
* later in server) but it is necessary to get the start server |
|
* flag. |
|
*/ |
|
if ((cmdlist = cmd_list_parse(argc, argv, &cause)) == NULL) { |
|
log_warnx("%s", cause); |
|
exit(1); |
|
} |
|
cmdflags &= ~CMD_STARTSERVER; |
|
TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { |
|
if (cmd->entry->flags & CMD_STARTSERVER) |
|
cmdflags |= CMD_STARTSERVER; |
|
if (cmd->entry->flags & CMD_SENDENVIRON) |
|
cmdflags |= CMD_SENDENVIRON; |
|
if (cmd->entry->flags & CMD_CANTNEST) |
|
cmdflags |= CMD_CANTNEST; |
|
} |
|
cmd_list_free(cmdlist); |
|
} |
|
|
|
/* |
|
* Check if this could be a nested session, if the command can't nest: |
|
* if the socket path matches $TMUX, this is probably the same server. |
|
*/ |
|
if (shellcmd == NULL && envdata.path != NULL && |
|
cmdflags & CMD_CANTNEST && |
|
(path == envdata.path || strcmp(path, envdata.path) == 0)) { |
|
log_warnx("sessions should be nested with care. " |
|
"unset $TMUX to force."); |
|
exit(1); |
|
} |
|
|
|
ev_base = event_init(); |
|
set_signals(main_signal); |
|
|
|
/* Initialise the client socket/start the server. */ |
|
if ((main_ibuf = client_init(path, cmdflags, flags)) == NULL) |
|
exit(1); |
|
xfree(path); |
xfree(path); |
|
|
imsg_compose(main_ibuf, msg, PROTOCOL_VERSION, -1, -1, buf, len); |
/* Set process title. */ |
|
setproctitle("%s (%s)", __progname, socket_path); |
|
|
events = EV_READ; |
/* Pass control to the client. */ |
if (main_ibuf->w.queued > 0) |
ev_base = event_init(); |
events |= EV_WRITE; |
exit(client_main(argc, argv, flags)); |
event_set(&main_event, main_ibuf->fd, events, main_callback, shellcmd); |
|
event_add(&main_event, NULL); |
|
|
|
event_dispatch(); |
|
|
|
event_del(&main_event); |
|
|
|
clear_signals(0); |
|
client_main(); /* doesn't return */ |
|
} |
|
|
|
/* ARGSUSED */ |
|
void |
|
main_signal(int sig, unused short events, unused void *data) |
|
{ |
|
int status; |
|
|
|
switch (sig) { |
|
case SIGTERM: |
|
exit(1); |
|
case SIGCHLD: |
|
waitpid(WAIT_ANY, &status, WNOHANG); |
|
break; |
|
} |
|
} |
|
|
|
/* ARGSUSED */ |
|
void |
|
main_callback(unused int fd, short events, void *data) |
|
{ |
|
char *shellcmd = data; |
|
|
|
if (events & EV_READ) |
|
main_dispatch(shellcmd); |
|
|
|
if (events & EV_WRITE) { |
|
if (msgbuf_write(&main_ibuf->w) < 0) |
|
fatalx("msgbuf_write failed"); |
|
} |
|
|
|
event_del(&main_event); |
|
events = EV_READ; |
|
if (main_ibuf->w.queued > 0) |
|
events |= EV_WRITE; |
|
event_set(&main_event, main_ibuf->fd, events, main_callback, shellcmd); |
|
event_add(&main_event, NULL); |
|
} |
|
|
|
void |
|
main_dispatch(const char *shellcmd) |
|
{ |
|
struct imsg imsg; |
|
ssize_t n, datalen; |
|
struct msg_shell_data shelldata; |
|
struct msg_exit_data exitdata; |
|
|
|
if ((n = imsg_read(main_ibuf)) == -1 || n == 0) |
|
fatalx("imsg_read failed"); |
|
|
|
for (;;) { |
|
if ((n = imsg_get(main_ibuf, &imsg)) == -1) |
|
fatalx("imsg_get failed"); |
|
if (n == 0) |
|
return; |
|
datalen = imsg.hdr.len - IMSG_HEADER_SIZE; |
|
|
|
switch (imsg.hdr.type) { |
|
case MSG_EXIT: |
|
case MSG_SHUTDOWN: |
|
if (datalen != sizeof exitdata) { |
|
if (datalen != 0) |
|
fatalx("bad MSG_EXIT size"); |
|
exit(0); |
|
} |
|
memcpy(&exitdata, imsg.data, sizeof exitdata); |
|
exit(exitdata.retcode); |
|
case MSG_READY: |
|
if (datalen != 0) |
|
fatalx("bad MSG_READY size"); |
|
|
|
event_loopexit(NULL); /* move to client_main() */ |
|
break; |
|
case MSG_VERSION: |
|
if (datalen != 0) |
|
fatalx("bad MSG_VERSION size"); |
|
|
|
log_warnx("protocol version mismatch (client %u, " |
|
"server %u)", PROTOCOL_VERSION, imsg.hdr.peerid); |
|
exit(1); |
|
case MSG_SHELL: |
|
if (datalen != sizeof shelldata) |
|
fatalx("bad MSG_SHELL size"); |
|
memcpy(&shelldata, imsg.data, sizeof shelldata); |
|
shelldata.shell[(sizeof shelldata.shell) - 1] = '\0'; |
|
|
|
clear_signals(0); |
|
|
|
shell_exec(shelldata.shell, shellcmd); |
|
default: |
|
fatalx("unexpected message"); |
|
} |
|
|
|
imsg_free(&imsg); |
|
} |
|
} |
} |