mirror of
https://github.com/tmux/tmux.git
synced 2024-11-16 01:18:52 +00:00
eddcc3dfa9
files from server.c (merging server-msg.c into the client file) and rather than iterating over each set after poll(), allow a callback to be specified when the fd is added and just walk once over the returned pollfds calling each callback where needed. More to come, getting this in so it is tested.
718 lines
15 KiB
C
718 lines
15 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <paths.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/*
|
|
* Main server functions.
|
|
*/
|
|
|
|
/* Client list. */
|
|
struct clients clients;
|
|
struct clients dead_clients;
|
|
|
|
/* Mapping of a pollfd to an fd independent of its position in the array. */
|
|
struct poll_item {
|
|
int fd;
|
|
int events;
|
|
|
|
void (*fn)(int, int, void *);
|
|
void *data;
|
|
|
|
RB_ENTRY(poll_item) entry;
|
|
};
|
|
RB_HEAD(poll_items, poll_item) poll_items;
|
|
|
|
int server_poll_cmp(struct poll_item *, struct poll_item *);
|
|
struct poll_item*server_poll_lookup(int);
|
|
void server_poll_add(int, int, void (*)(int, int, void *), void *);
|
|
struct pollfd *server_poll_flatten(int *);
|
|
void server_poll_dispatch(struct pollfd *, int);
|
|
void server_poll_reset(void);
|
|
RB_PROTOTYPE(poll_items, poll_item, entry, server_poll_cmp);
|
|
RB_GENERATE(poll_items, poll_item, entry, server_poll_cmp);
|
|
|
|
int server_create_socket(void);
|
|
void server_callback(int, int, void *);
|
|
int server_main(int);
|
|
void server_shutdown(void);
|
|
int server_should_shutdown(void);
|
|
void server_child_signal(void);
|
|
void server_fill_windows(void);
|
|
void server_fill_clients(void);
|
|
void server_fill_jobs(void);
|
|
void server_clean_dead(void);
|
|
void server_second_timers(void);
|
|
void server_lock_server(void);
|
|
void server_lock_sessions(void);
|
|
int server_update_socket(void);
|
|
|
|
int
|
|
server_poll_cmp(struct poll_item *pitem1, struct poll_item *pitem2)
|
|
{
|
|
return (pitem1->fd - pitem2->fd);
|
|
}
|
|
|
|
void
|
|
server_poll_add(int fd, int events, void (*fn)(int, int, void *), void *data)
|
|
{
|
|
struct poll_item *pitem;
|
|
|
|
pitem = xmalloc(sizeof *pitem);
|
|
pitem->fd = fd;
|
|
pitem->events = events;
|
|
|
|
pitem->fn = fn;
|
|
pitem->data = data;
|
|
|
|
RB_INSERT(poll_items, &poll_items, pitem);
|
|
}
|
|
|
|
struct poll_item *
|
|
server_poll_lookup(int fd)
|
|
{
|
|
struct poll_item pitem;
|
|
|
|
pitem.fd = fd;
|
|
return (RB_FIND(poll_items, &poll_items, &pitem));
|
|
}
|
|
|
|
struct pollfd *
|
|
server_poll_flatten(int *nfds)
|
|
{
|
|
struct poll_item *pitem;
|
|
struct pollfd *pfds;
|
|
|
|
pfds = NULL;
|
|
*nfds = 0;
|
|
RB_FOREACH(pitem, poll_items, &poll_items) {
|
|
pfds = xrealloc(pfds, (*nfds) + 1, sizeof *pfds);
|
|
pfds[*nfds].fd = pitem->fd;
|
|
pfds[*nfds].events = pitem->events;
|
|
(*nfds)++;
|
|
}
|
|
return (pfds);
|
|
}
|
|
|
|
void
|
|
server_poll_dispatch(struct pollfd *pfds, int nfds)
|
|
{
|
|
struct poll_item *pitem;
|
|
struct pollfd *pfd;
|
|
|
|
while (nfds > 0) {
|
|
pfd = &pfds[--nfds];
|
|
if (pfd->revents != 0) {
|
|
pitem = server_poll_lookup(pfd->fd);
|
|
pitem->fn(pitem->fd, pfd->revents, pitem->data);
|
|
}
|
|
}
|
|
xfree(pfds);
|
|
}
|
|
|
|
void
|
|
server_poll_reset(void)
|
|
{
|
|
struct poll_item *pitem;
|
|
|
|
while (!RB_EMPTY(&poll_items)) {
|
|
pitem = RB_ROOT(&poll_items);
|
|
RB_REMOVE(poll_items, &poll_items, pitem);
|
|
xfree(pitem);
|
|
}
|
|
}
|
|
|
|
/* Create server socket. */
|
|
int
|
|
server_create_socket(void)
|
|
{
|
|
struct sockaddr_un sa;
|
|
size_t size;
|
|
mode_t mask;
|
|
int fd, mode;
|
|
|
|
memset(&sa, 0, sizeof sa);
|
|
sa.sun_family = AF_UNIX;
|
|
size = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path);
|
|
if (size >= sizeof sa.sun_path) {
|
|
errno = ENAMETOOLONG;
|
|
fatal("socket failed");
|
|
}
|
|
unlink(sa.sun_path);
|
|
|
|
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
|
|
fatal("socket failed");
|
|
|
|
mask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
|
|
if (bind(fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1)
|
|
fatal("bind failed");
|
|
umask(mask);
|
|
|
|
if (listen(fd, 16) == -1)
|
|
fatal("listen failed");
|
|
|
|
if ((mode = fcntl(fd, F_GETFL)) == -1)
|
|
fatal("fcntl failed");
|
|
if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1)
|
|
fatal("fcntl failed");
|
|
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
|
|
fatal("fcntl failed");
|
|
|
|
return (fd);
|
|
}
|
|
|
|
/* Callback for server socket. */
|
|
void
|
|
server_callback(int fd, int events, unused void *data)
|
|
{
|
|
struct sockaddr_storage sa;
|
|
socklen_t slen = sizeof sa;
|
|
int newfd;
|
|
|
|
if (events & (POLLERR|POLLNVAL|POLLHUP))
|
|
fatalx("lost server socket");
|
|
if (!(events & POLLIN))
|
|
return;
|
|
|
|
newfd = accept(fd, (struct sockaddr *) &sa, &slen);
|
|
if (newfd == -1) {
|
|
if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED)
|
|
return;
|
|
fatal("accept failed");
|
|
}
|
|
if (sigterm) {
|
|
close(newfd);
|
|
return;
|
|
}
|
|
server_client_create(newfd);
|
|
}
|
|
|
|
/* Fork new server. */
|
|
int
|
|
server_start(char *path)
|
|
{
|
|
struct client *c;
|
|
int pair[2], srv_fd;
|
|
char *cause;
|
|
char rpathbuf[MAXPATHLEN];
|
|
|
|
/* The first client is special and gets a socketpair; create it. */
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0)
|
|
fatal("socketpair failed");
|
|
|
|
switch (fork()) {
|
|
case -1:
|
|
fatal("fork failed");
|
|
case 0:
|
|
break;
|
|
default:
|
|
close(pair[1]);
|
|
return (pair[0]);
|
|
}
|
|
close(pair[0]);
|
|
|
|
/*
|
|
* Must daemonise before loading configuration as the PID changes so
|
|
* $TMUX would be wrong for sessions created in the config file.
|
|
*/
|
|
if (daemon(1, 0) != 0)
|
|
fatal("daemon failed");
|
|
|
|
logfile("server");
|
|
log_debug("server started, pid %ld", (long) getpid());
|
|
|
|
ARRAY_INIT(&windows);
|
|
ARRAY_INIT(&clients);
|
|
ARRAY_INIT(&dead_clients);
|
|
ARRAY_INIT(&sessions);
|
|
ARRAY_INIT(&dead_sessions);
|
|
TAILQ_INIT(&session_groups);
|
|
mode_key_init_trees();
|
|
key_bindings_init();
|
|
utf8_build();
|
|
|
|
start_time = time(NULL);
|
|
socket_path = path;
|
|
|
|
if (realpath(socket_path, rpathbuf) == NULL)
|
|
strlcpy(rpathbuf, socket_path, sizeof rpathbuf);
|
|
log_debug("socket path %s", socket_path);
|
|
setproctitle("server (%s)", rpathbuf);
|
|
|
|
srv_fd = server_create_socket();
|
|
server_client_create(pair[1]);
|
|
|
|
if (access(SYSTEM_CFG, R_OK) != 0) {
|
|
if (errno != ENOENT) {
|
|
xasprintf(
|
|
&cause, "%s: %s", strerror(errno), SYSTEM_CFG);
|
|
goto error;
|
|
}
|
|
} else if (load_cfg(SYSTEM_CFG, NULL, &cause) != 0)
|
|
goto error;
|
|
if (cfg_file != NULL && load_cfg(cfg_file, NULL, &cause) != 0)
|
|
goto error;
|
|
|
|
exit(server_main(srv_fd));
|
|
|
|
error:
|
|
/* Write the error and shutdown the server. */
|
|
c = ARRAY_FIRST(&clients);
|
|
|
|
server_write_error(c, cause);
|
|
xfree(cause);
|
|
|
|
sigterm = 1;
|
|
server_shutdown();
|
|
|
|
exit(server_main(srv_fd));
|
|
}
|
|
|
|
/* Main server loop. */
|
|
int
|
|
server_main(int srv_fd)
|
|
{
|
|
struct pollfd *pfds;
|
|
int nfds, xtimeout;
|
|
u_int i;
|
|
time_t now, last;
|
|
|
|
siginit();
|
|
log_debug("server socket is %d", srv_fd);
|
|
|
|
last = time(NULL);
|
|
|
|
pfds = NULL;
|
|
for (;;) {
|
|
/* If sigterm, kill all windows and clients. */
|
|
if (sigterm)
|
|
server_shutdown();
|
|
|
|
/* Stop if no sessions or clients left. */
|
|
if (server_should_shutdown())
|
|
break;
|
|
|
|
/* Handle child exit. */
|
|
if (sigchld) {
|
|
server_child_signal();
|
|
sigchld = 0;
|
|
}
|
|
|
|
/* Recreate socket on SIGUSR1. */
|
|
if (sigusr1) {
|
|
close(srv_fd);
|
|
srv_fd = server_create_socket();
|
|
sigusr1 = 0;
|
|
}
|
|
|
|
/* Initialise pollfd array and add server socket. */
|
|
server_poll_reset();
|
|
server_poll_add(srv_fd, POLLIN, server_callback, NULL);
|
|
|
|
/* Fill window and client sockets. */
|
|
server_fill_jobs();
|
|
server_fill_windows();
|
|
server_fill_clients();
|
|
|
|
/* Update socket permissions. */
|
|
xtimeout = INFTIM;
|
|
if (server_update_socket() != 0)
|
|
xtimeout = POLL_TIMEOUT;
|
|
|
|
/* Do the poll. */
|
|
pfds = server_poll_flatten(&nfds);
|
|
if (poll(pfds, nfds, xtimeout) == -1) {
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
fatal("poll failed");
|
|
}
|
|
server_poll_dispatch(pfds, nfds);
|
|
|
|
/* Call second-based timers. */
|
|
now = time(NULL);
|
|
if (now != last) {
|
|
last = now;
|
|
server_second_timers();
|
|
}
|
|
|
|
/* Run once-per-loop events. */
|
|
server_job_loop();
|
|
server_window_loop();
|
|
server_client_loop();
|
|
|
|
/* Collect any unset key bindings. */
|
|
key_bindings_clean();
|
|
|
|
/* Collect dead clients and sessions. */
|
|
server_clean_dead();
|
|
}
|
|
server_poll_reset();
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
|
|
if (ARRAY_ITEM(&sessions, i) != NULL)
|
|
session_destroy(ARRAY_ITEM(&sessions, i));
|
|
}
|
|
ARRAY_FREE(&sessions);
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
|
|
if (ARRAY_ITEM(&clients, i) != NULL)
|
|
server_client_lost(ARRAY_ITEM(&clients, i));
|
|
}
|
|
ARRAY_FREE(&clients);
|
|
|
|
mode_key_free_trees();
|
|
key_bindings_free();
|
|
|
|
close(srv_fd);
|
|
|
|
unlink(socket_path);
|
|
xfree(socket_path);
|
|
|
|
options_free(&global_s_options);
|
|
options_free(&global_w_options);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Kill all clients. */
|
|
void
|
|
server_shutdown(void)
|
|
{
|
|
struct session *s;
|
|
struct client *c;
|
|
u_int i, j;
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
|
|
c = ARRAY_ITEM(&clients, i);
|
|
if (c != NULL) {
|
|
if (c->flags & (CLIENT_BAD|CLIENT_SUSPENDED))
|
|
server_client_lost(c);
|
|
else
|
|
server_write_client(c, MSG_SHUTDOWN, NULL, 0);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
|
|
s = ARRAY_ITEM(&sessions, i);
|
|
for (j = 0; j < ARRAY_LENGTH(&clients); j++) {
|
|
c = ARRAY_ITEM(&clients, j);
|
|
if (c != NULL && c->session == s) {
|
|
s = NULL;
|
|
break;
|
|
}
|
|
}
|
|
if (s != NULL)
|
|
session_destroy(s);
|
|
}
|
|
}
|
|
|
|
/* Check if the server should be shutting down (no more clients or windows). */
|
|
int
|
|
server_should_shutdown(void)
|
|
{
|
|
u_int i;
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
|
|
if (ARRAY_ITEM(&sessions, i) != NULL)
|
|
return (0);
|
|
}
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
|
|
if (ARRAY_ITEM(&clients, i) != NULL)
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/* Handle SIGCHLD. */
|
|
void
|
|
server_child_signal(void)
|
|
{
|
|
struct window *w;
|
|
struct window_pane *wp;
|
|
struct job *job;
|
|
int status;
|
|
pid_t pid;
|
|
u_int i;
|
|
|
|
for (;;) {
|
|
switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) {
|
|
case -1:
|
|
if (errno == ECHILD)
|
|
return;
|
|
fatal("waitpid failed");
|
|
case 0:
|
|
return;
|
|
}
|
|
if (!WIFSTOPPED(status)) {
|
|
SLIST_FOREACH(job, &all_jobs, lentry) {
|
|
if (pid == job->pid) {
|
|
job->pid = -1;
|
|
job->status = status;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
|
|
continue;
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
|
|
w = ARRAY_ITEM(&windows, i);
|
|
if (w == NULL)
|
|
continue;
|
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
|
if (wp->pid == pid) {
|
|
if (killpg(pid, SIGCONT) != 0)
|
|
kill(pid, SIGCONT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fill window pollfds. */
|
|
void
|
|
server_fill_windows(void)
|
|
{
|
|
struct window *w;
|
|
struct window_pane *wp;
|
|
u_int i;
|
|
int events;
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
|
|
w = ARRAY_ITEM(&windows, i);
|
|
if (w == NULL)
|
|
continue;
|
|
|
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
|
if (wp->fd == -1)
|
|
continue;
|
|
events = POLLIN;
|
|
if (BUFFER_USED(wp->out) > 0)
|
|
events |= POLLOUT;
|
|
server_poll_add(
|
|
wp->fd, events, server_window_callback, wp);
|
|
|
|
if (wp->pipe_fd == -1)
|
|
continue;
|
|
events = 0;
|
|
if (BUFFER_USED(wp->pipe_buf) > 0)
|
|
events |= POLLOUT;
|
|
server_poll_add(
|
|
wp->pipe_fd, events, server_window_callback, wp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fill client pollfds. */
|
|
void
|
|
server_fill_clients(void)
|
|
{
|
|
struct client *c;
|
|
u_int i;
|
|
int events;
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
|
|
c = ARRAY_ITEM(&clients, i);
|
|
|
|
if (c != NULL) {
|
|
events = 0;
|
|
if (!(c->flags & CLIENT_BAD))
|
|
events |= POLLIN;
|
|
if (c->ibuf.w.queued > 0)
|
|
events |= POLLOUT;
|
|
server_poll_add(
|
|
c->ibuf.fd, events, server_client_callback, c);
|
|
}
|
|
|
|
if (c != NULL && !(c->flags & CLIENT_SUSPENDED) &&
|
|
c->tty.fd != -1 && c->session != NULL) {
|
|
events = POLLIN;
|
|
if (BUFFER_USED(c->tty.out) > 0)
|
|
events |= POLLOUT;
|
|
server_poll_add(
|
|
c->tty.fd, events, server_client_callback, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fill in job fds. */
|
|
void
|
|
server_fill_jobs(void)
|
|
{
|
|
struct job *job;
|
|
|
|
SLIST_FOREACH(job, &all_jobs, lentry) {
|
|
if (job->fd == -1)
|
|
continue;
|
|
server_poll_add(job->fd, POLLIN, server_job_callback, job);
|
|
}
|
|
}
|
|
|
|
/* Free dead, unreferenced clients and sessions. */
|
|
void
|
|
server_clean_dead(void)
|
|
{
|
|
struct session *s;
|
|
struct client *c;
|
|
u_int i;
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&dead_sessions); i++) {
|
|
s = ARRAY_ITEM(&dead_sessions, i);
|
|
if (s == NULL || s->references != 0)
|
|
continue;
|
|
ARRAY_SET(&dead_sessions, i, NULL);
|
|
xfree(s);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&dead_clients); i++) {
|
|
c = ARRAY_ITEM(&dead_clients, i);
|
|
if (c == NULL || c->references != 0)
|
|
continue;
|
|
ARRAY_SET(&dead_clients, i, NULL);
|
|
xfree(c);
|
|
}
|
|
}
|
|
|
|
/* Call any once-per-second timers. */
|
|
void
|
|
server_second_timers(void)
|
|
{
|
|
struct window *w;
|
|
struct window_pane *wp;
|
|
u_int i;
|
|
|
|
if (options_get_number(&global_s_options, "lock-server"))
|
|
server_lock_server();
|
|
else
|
|
server_lock_sessions();
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
|
|
w = ARRAY_ITEM(&windows, i);
|
|
if (w == NULL)
|
|
continue;
|
|
|
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
|
if (wp->mode != NULL && wp->mode->timer != NULL)
|
|
wp->mode->timer(wp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Lock the server if ALL sessions have hit the time limit. */
|
|
void
|
|
server_lock_server(void)
|
|
{
|
|
struct session *s;
|
|
u_int i;
|
|
int timeout;
|
|
time_t t;
|
|
|
|
t = time(NULL);
|
|
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
|
|
if ((s = ARRAY_ITEM(&sessions, i)) == NULL)
|
|
continue;
|
|
|
|
if (s->flags & SESSION_UNATTACHED) {
|
|
s->activity = time(NULL);
|
|
continue;
|
|
}
|
|
|
|
timeout = options_get_number(&s->options, "lock-after-time");
|
|
if (timeout <= 0 || t <= s->activity + timeout)
|
|
return; /* not timed out */
|
|
}
|
|
|
|
server_lock();
|
|
recalculate_sizes();
|
|
}
|
|
|
|
/* Lock any sessions which have timed out. */
|
|
void
|
|
server_lock_sessions(void)
|
|
{
|
|
struct session *s;
|
|
u_int i;
|
|
int timeout;
|
|
time_t t;
|
|
|
|
t = time(NULL);
|
|
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
|
|
if ((s = ARRAY_ITEM(&sessions, i)) == NULL)
|
|
continue;
|
|
|
|
if (s->flags & SESSION_UNATTACHED) {
|
|
s->activity = time(NULL);
|
|
continue;
|
|
}
|
|
|
|
timeout = options_get_number(&s->options, "lock-after-time");
|
|
if (timeout > 0 && t > s->activity + timeout) {
|
|
server_lock_session(s);
|
|
recalculate_sizes();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update socket execute permissions based on whether sessions are attached. */
|
|
int
|
|
server_update_socket(void)
|
|
{
|
|
struct session *s;
|
|
u_int i;
|
|
static int last = -1;
|
|
int n;
|
|
|
|
n = 0;
|
|
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
|
|
s = ARRAY_ITEM(&sessions, i);
|
|
if (s != NULL && !(s->flags & SESSION_UNATTACHED)) {
|
|
n++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (n != last) {
|
|
last = n;
|
|
if (n != 0)
|
|
chmod(socket_path, S_IRWXU);
|
|
else
|
|
chmod(socket_path, S_IRUSR|S_IWUSR);
|
|
}
|
|
|
|
return (n);
|
|
}
|