Merge master into floating_panes.

This commit is contained in:
Dane Jensen
2026-06-09 20:33:15 +00:00
committed by GitHub
15 changed files with 567 additions and 227 deletions

View File

@@ -27,13 +27,17 @@
*/
static enum cmd_retval cmd_kill_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_pane_all(struct cmdq_item *, const char *);
static int cmd_kill_pane_filter(struct cmdq_item *,
struct session *, struct winlink *,
struct window_pane *, const char *);
const struct cmd_entry cmd_kill_pane_entry = {
.name = "kill-pane",
.alias = "killp",
.args = { "at:", 0, 0, NULL },
.usage = "[-a] " CMD_TARGET_PANE_USAGE,
.args = { "af:t:", 0, 0, NULL },
.usage = "[-a] [-f filter] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -46,22 +50,19 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s;
struct winlink *wl = target->wl;
struct window_pane *loopwp, *tmpwp, *wp = target->wp;
struct window_pane *wp = target->wp;
const char *filter = args_get(args, 'f');
if (args_has(args, 'a')) {
server_unzoom_window(wl->window);
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
if (loopwp == wp)
continue;
server_client_remove_pane(loopwp);
layout_close_pane(loopwp);
window_remove_pane(wl->window, loopwp);
}
server_redraw_window(wl->window);
return (CMD_RETURN_NORMAL);
if (filter != NULL && !args_has(args, 'a')) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'a'))
return (cmd_kill_pane_all(item, filter));
if (wp == NULL) {
cmdq_error(item, "no active pane to kill");
return (CMD_RETURN_ERROR);
@@ -69,3 +70,48 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
server_kill_pane(wp);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_kill_pane_all(struct cmdq_item *item, const char *filter)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s;
struct winlink *wl = target->wl;
struct window_pane *wp = target->wp;
struct window_pane *loopwp, *tmpwp;
server_unzoom_window(wl->window);
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
if (loopwp == wp)
continue;
if (!cmd_kill_pane_filter(item, s, wl, loopwp, filter))
continue;
server_client_remove_pane(loopwp);
layout_close_pane(loopwp);
window_remove_pane(wl->window, loopwp);
}
server_redraw_window(wl->window);
return (CMD_RETURN_NORMAL);
}
static int
cmd_kill_pane_filter(struct cmdq_item *item, struct session *s,
struct winlink *wl, struct window_pane *wp, const char *filter)
{
struct format_tree *ft;
char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, wl, wp);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
}

View File

@@ -18,6 +18,8 @@
#include <sys/types.h>
#include <stdlib.h>
#include "tmux.h"
/*
@@ -28,13 +30,16 @@
*/
static enum cmd_retval cmd_kill_session_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_session_all(struct cmdq_item *, const char *);
static int cmd_kill_session_filter(struct cmdq_item *,
struct session *, const char *);
const struct cmd_entry cmd_kill_session_entry = {
.name = "kill-session",
.alias = NULL,
.args = { "aCgt:", 0, 0, NULL },
.usage = "[-aCg] " CMD_TARGET_SESSION_USAGE,
.args = { "aCgf:t:", 0, 0, NULL },
.usage = "[-aCg] [-f filter] " CMD_TARGET_SESSION_USAGE,
.target = { 't', CMD_FIND_SESSION, 0 },
@@ -50,6 +55,12 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
struct session *s = target->s, *sloop, *stmp;
struct session_group *sg;
struct winlink *wl;
const char *filter = args_get(args, 'f');
if (filter != NULL && (!args_has(args, 'a') || args_has(args, 'C'))) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'C')) {
RB_FOREACH(wl, winlinks, &s->windows) {
@@ -57,14 +68,9 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
wl->flags &= ~WINLINK_ALERTFLAGS;
}
server_redraw_session(s);
} else if (args_has(args, 'a')) {
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) {
if (sloop != s) {
server_destroy_session(sloop);
session_destroy(sloop, 1, __func__);
}
}
} else if (args_has(args, 'g') &&
} else if (args_has(args, 'a'))
return (cmd_kill_session_all(item, filter));
else if (args_has(args, 'g') &&
(sg = session_group_contains(s)) != NULL) {
TAILQ_FOREACH_SAFE(sloop, &sg->sessions, gentry, stmp) {
server_destroy_session(sloop);
@@ -76,3 +82,42 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
}
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_kill_session_all(struct cmdq_item *item, const char *filter)
{
struct session *s = cmdq_get_target(item)->s;
struct session *sloop, *stmp;
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) {
if (sloop == s)
continue;
if (!cmd_kill_session_filter(item, sloop, filter))
continue;
server_destroy_session(sloop);
session_destroy(sloop, 1, __func__);
}
return (CMD_RETURN_NORMAL);
}
static int
cmd_kill_session_filter(struct cmdq_item *item, struct session *s,
const char *filter)
{
struct format_tree *ft;
char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, NULL, NULL);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
}

View File

@@ -18,6 +18,8 @@
#include <sys/types.h>
#include <stdlib.h>
#include "tmux.h"
/*
@@ -25,13 +27,16 @@
*/
static enum cmd_retval cmd_kill_window_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_window_all(struct cmdq_item *, const char *);
static int cmd_kill_window_filter(struct cmdq_item *,
struct session *, struct winlink *, const char *);
const struct cmd_entry cmd_kill_window_entry = {
.name = "kill-window",
.alias = "killw",
.args = { "at:", 0, 0, NULL },
.usage = "[-a] " CMD_TARGET_WINDOW_USAGE,
.args = { "af:t:", 0, 0, NULL },
.usage = "[-a] [-f filter] " CMD_TARGET_WINDOW_USAGE,
.target = { 't', CMD_FIND_WINDOW, 0 },
@@ -57,10 +62,15 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct winlink *wl = target->wl, *loop;
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct session *s = target->s;
u_int found;
const char *filter = args_get(args, 'f');
if (filter != NULL && !args_has(args, 'a')) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (cmd_get_entry(self) == &cmd_unlink_window_entry) {
if (!args_has(args, 'k') && !session_is_linked(s, w)) {
@@ -72,39 +82,76 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'a')) {
if (RB_PREV(winlinks, &s->windows, wl) == NULL &&
RB_NEXT(winlinks, &s->windows, wl) == NULL)
return (CMD_RETURN_NORMAL);
/* Kill all windows except the current one. */
do {
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window != wl->window) {
server_kill_window(loop->window, 0);
found++;
break;
}
}
} while (found != 0);
/*
* If the current window appears in the session more than once,
* kill it as well.
*/
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window == wl->window)
found++;
}
if (found > 1)
server_kill_window(wl->window, 0);
server_renumber_all();
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'a'))
return (cmd_kill_window_all(item, filter));
server_kill_window(wl->window, 1);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_kill_window_all(struct cmdq_item *item, const char *filter)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s;
struct winlink *wl = target->wl;
struct winlink *loop;
u_int found, kill_current;
if (RB_PREV(winlinks, &s->windows, wl) == NULL &&
RB_NEXT(winlinks, &s->windows, wl) == NULL)
return (CMD_RETURN_NORMAL);
/* Kill all windows except the current one. */
do {
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window != wl->window &&
cmd_kill_window_filter(item, s, loop, filter)) {
server_kill_window(loop->window, 0);
found++;
break;
}
}
} while (found != 0);
/*
* If the current window appears in the session more than once, kill it
* as well if it matches the filter.
*/
found = kill_current = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window == wl->window) {
found++;
if (cmd_kill_window_filter(item, s, loop, filter))
kill_current = 1;
}
}
if (kill_current && found > 1)
server_kill_window(wl->window, 0);
server_renumber_all();
return (CMD_RETURN_NORMAL);
}
static int
cmd_kill_window_filter(struct cmdq_item *item, struct session *s,
struct winlink *wl, const char *filter)
{
struct format_tree *ft;
char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, wl, NULL);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
}

View File

@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
@@ -37,67 +38,71 @@ const struct cmd_entry cmd_server_access_entry = {
.name = "server-access",
.alias = NULL,
.args = { "adlrw", 0, 1, NULL },
.usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]",
.args = { "adglrw", 0, 1, NULL },
.usage = "[-adglrw] " CMD_TARGET_PANE_USAGE " [user|group]",
.flags = CMD_CLIENT_CANFAIL,
.exec = cmd_server_access_exec
};
static enum cmd_retval
cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw)
cmd_server_access_deny(struct cmdq_item *item, id_t id, int flags,
const char *type, const char *name)
{
struct client *loop;
struct server_acl_user *user;
uid_t uid;
if ((user = server_acl_user_find(pw->pw_uid)) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
if (!server_acl_find(id, flags)) {
cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR);
}
TAILQ_FOREACH(loop, &clients, entry) {
uid = proc_get_peer_uid(loop->peer);
if (uid == server_acl_get_uid(user)) {
loop->exit_message = xstrdup("access not allowed");
loop->flags |= CLIENT_EXIT;
}
}
server_acl_user_deny(pw->pw_uid);
server_acl_deny(id, flags);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_target_client(item);
char *name;
struct passwd *pw = NULL;
char *arg;
const char *name = NULL, *type;
struct passwd *pw;
struct group *gr;
id_t id;
int flags = 0;
if (args_has(args, 'l')) {
server_acl_display(item);
return (CMD_RETURN_NORMAL);
}
if (args_count(args) == 0) {
cmdq_error(item, "missing user argument");
cmdq_error(item, "missing user or group argument");
return (CMD_RETURN_ERROR);
}
name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
if (*name != '\0')
pw = getpwnam(name);
if (pw == NULL) {
cmdq_error(item, "unknown user: %s", name);
free(name);
arg = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
if (args_has(args, 'g')) {
type = "group";
if ((gr = getgrnam(arg)) != NULL) {
id = gr->gr_gid;
name = gr->gr_name;
flags |= SERVER_ACL_IS_GROUP;
}
} else {
type = "user";
if ((pw = getpwnam(arg)) != NULL) {
id = pw->pw_uid;
name = pw->pw_name;
}
}
if (name == NULL) {
cmdq_error(item, "unknown %s: %s", type, arg);
free(arg);
return (CMD_RETURN_ERROR);
}
free(name);
free(arg);
if (pw->pw_uid == 0 || pw->pw_uid == getuid()) {
if ((~flags & SERVER_ACL_IS_GROUP) && (id == 0 || id == getuid())) {
cmdq_error(item, "%s owns the server, can't change access",
pw->pw_name);
name);
return (CMD_RETURN_ERROR);
}
@@ -111,36 +116,35 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
}
if (args_has(args, 'd'))
return (cmd_server_access_deny(item, pw));
return (cmd_server_access_deny(item, id, flags, type, name));
if (args_has(args, 'a')) {
if (server_acl_user_find(pw->pw_uid) != NULL) {
cmdq_error(item, "user %s is already added",
pw->pw_name);
if (server_acl_find(id, flags)) {
cmdq_error(item, "%s %s is already added", type, name);
return (CMD_RETURN_ERROR);
}
server_acl_user_allow(pw->pw_uid);
server_acl_allow(id, flags);
/* Do not return - allow -r or -w with -a. */
} else if (args_has(args, 'r') || args_has(args, 'w')) {
/* -r or -w implies -a if user does not exist. */
if (server_acl_user_find(pw->pw_uid) == NULL)
server_acl_user_allow(pw->pw_uid);
/* -r or -w implies -a if the entry does not exist. */
if (!server_acl_find(id, flags))
server_acl_allow(id, flags);
}
if (args_has(args, 'w')) {
if (server_acl_user_find(pw->pw_uid) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
if (!server_acl_find(id, flags)) {
cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR);
}
server_acl_user_allow_write(pw->pw_uid);
server_acl_allow_write(id, flags);
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'r')) {
if (server_acl_user_find(pw->pw_uid) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
if (!server_acl_find(id, flags)) {
cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR);
}
server_acl_user_deny_write(pw->pw_uid);
server_acl_deny_write(id, flags);
return (CMD_RETURN_NORMAL);
}

View File

@@ -38,7 +38,7 @@ const struct cmd_entry cmd_new_pane_entry = {
.name = "new-pane",
.alias = "newp",
.args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vx:X:y:Y:Z", 0, -1, NULL },
.args = { "bBc:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] "
"[-s style] [-S active-border-style] "
@@ -56,7 +56,7 @@ const struct cmd_entry cmd_split_window_entry = {
.name = "split-window",
.alias = "splitw",
.args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:T:vZ", 0, -1, NULL },
.args = { "bBc:de:EfF:hIkl:m:p:PR:s:S:t:T:vZ", 0, -1, NULL },
.usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] "
"[-s style] [-S active-border-style] "
@@ -244,5 +244,15 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
environ_free(sc.environ);
if (input)
return (CMD_RETURN_WAIT);
if (args_has(args, 'B')) {
/*
* With -B, block this command queue item until the pane's
* command exits; window_pane_block_finish will be called to
* continue it.
*/
new_wp->block_item = item;
return (CMD_RETURN_WAIT);
}
return (CMD_RETURN_NORMAL);
}

12
proc.c
View File

@@ -57,6 +57,7 @@ struct tmuxpeer {
struct imsgbuf ibuf;
struct event event;
uid_t uid;
gid_t gid;
int flags;
#define PEER_BAD 0x1
@@ -301,7 +302,6 @@ proc_add_peer(struct tmuxproc *tp, int fd,
void (*dispatchcb)(struct imsg *, void *), void *arg)
{
struct tmuxpeer *peer;
gid_t gid;
peer = xcalloc(1, sizeof *peer);
peer->parent = tp;
@@ -314,8 +314,10 @@ proc_add_peer(struct tmuxproc *tp, int fd,
imsgbuf_allow_fdpass(&peer->ibuf);
event_set(&peer->event, fd, EV_READ, proc_event_cb, peer);
if (getpeereid(fd, &peer->uid, &gid) != 0)
if (getpeereid(fd, &peer->uid, &peer->gid) != 0) {
peer->uid = (uid_t)-1;
peer->gid = (gid_t)-1;
}
log_debug("add peer %p: %d (%p)", peer, fd, arg);
TAILQ_INSERT_TAIL(&tp->peers, peer, entry);
@@ -384,3 +386,9 @@ proc_get_peer_uid(struct tmuxpeer *peer)
{
return (peer->uid);
}
gid_t
proc_get_peer_gid(struct tmuxpeer *peer)
{
return (peer->gid);
}

View File

@@ -22,6 +22,7 @@
#include <sys/socket.h>
#include <ctype.h>
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
@@ -29,158 +30,203 @@
#include "tmux.h"
struct server_acl_user {
uid_t uid;
struct server_acl_entry {
id_t id;
int flags;
#define SERVER_ACL_READONLY 0x1
int flags;
RB_ENTRY(server_acl_user) entry;
RB_ENTRY(server_acl_entry) entry;
};
static int
server_acl_cmp(struct server_acl_user *user1, struct server_acl_user *user2)
server_acl_cmp(struct server_acl_entry *entry1,
struct server_acl_entry *entry2)
{
if (user1->uid < user2->uid)
if ((entry1->flags ^ entry2->flags) & SERVER_ACL_IS_GROUP) {
if (entry1->flags & SERVER_ACL_IS_GROUP)
return (1);
return (-1);
return (user1->uid > user2->uid);
}
if (entry1->id < entry2->id)
return (-1);
return (entry1->id > entry2->id);
}
RB_HEAD(server_acl_entries, server_acl_user) server_acl_entries;
RB_GENERATE_STATIC(server_acl_entries, server_acl_user, entry, server_acl_cmp);
RB_HEAD(server_acl_entries, server_acl_entry) server_acl_entries;
RB_GENERATE_STATIC(server_acl_entries, server_acl_entry, entry, server_acl_cmp);
/* Initialize server_acl tree. */
static struct server_acl_entry *
server_acl_entry_find(id_t id, int flags)
{
struct server_acl_entry find = {
.id = id,
.flags = flags & SERVER_ACL_IS_GROUP
};
return (RB_FIND(server_acl_entries, &server_acl_entries, &find));
}
static struct server_acl_entry *
server_acl_check(struct client *c)
{
struct server_acl_entry *entry;
uid_t uid;
gid_t gid;
uid = proc_get_peer_uid(c->peer);
if (uid == (uid_t)-1)
return (NULL);
entry = server_acl_entry_find(uid, 0);
if (entry != NULL)
return (entry);
gid = proc_get_peer_gid(c->peer);
if (gid == (gid_t)-1)
return (NULL);
return (server_acl_entry_find(gid, SERVER_ACL_IS_GROUP));
}
static void
server_acl_update(void)
{
struct server_acl_entry *entry;
struct client *c;
TAILQ_FOREACH(c, &clients, entry) {
entry = server_acl_check(c);
if (entry == NULL) {
c->exit_message = xstrdup("access not allowed");
c->flags |= CLIENT_EXIT;
} else if (entry->flags & SERVER_ACL_READONLY)
c->flags |= CLIENT_READONLY;
else
c->flags &= ~CLIENT_READONLY;
}
}
/* Initialize ACL tree. */
void
server_acl_init(void)
{
RB_INIT(&server_acl_entries);
if (getuid() != 0)
server_acl_user_allow(0);
server_acl_user_allow(getuid());
server_acl_allow(0, 0);
server_acl_allow(getuid(), 0);
}
/* Find user entry. */
struct server_acl_user*
server_acl_user_find(uid_t uid)
/* Check if an ACL entry exists. */
int
server_acl_find(id_t id, int flags)
{
struct server_acl_user find = { .uid = uid };
return (RB_FIND(server_acl_entries, &server_acl_entries, &find));
return (server_acl_entry_find(id, flags) != NULL);
}
/* Display the tree. */
void
server_acl_display(struct cmdq_item *item)
{
struct server_acl_user *loop;
struct server_acl_entry *loop;
struct passwd *pw;
struct group *gr;
const char *name;
char type;
RB_FOREACH(loop, server_acl_entries, &server_acl_entries) {
if (loop->uid == 0)
continue;
if ((pw = getpwuid(loop->uid)) != NULL)
name = pw->pw_name;
if (~loop->flags & SERVER_ACL_IS_GROUP) {
if (loop->id == 0)
continue;
if ((pw = getpwuid(loop->id)) != NULL)
name = pw->pw_name;
else
name = "unknown";
type = 'U';
} else {
if ((gr = getgrgid(loop->id)) != NULL)
name = gr->gr_name;
else
name = "unknown";
type = 'G';
}
if (loop->flags & SERVER_ACL_READONLY)
cmdq_print(item, "%s (%c,R)", name, type);
else
name = "unknown";
if (loop->flags == SERVER_ACL_READONLY)
cmdq_print(item, "%s (R)", name);
else
cmdq_print(item, "%s (W)", name);
cmdq_print(item, "%s (%c,W)", name, type);
}
}
/* Allow a user. */
/* Allow an ACL entry. */
void
server_acl_user_allow(uid_t uid)
server_acl_allow(id_t id, int flags)
{
struct server_acl_user *user;
struct server_acl_entry *entry;
user = server_acl_user_find(uid);
if (user == NULL) {
user = xcalloc(1, sizeof *user);
user->uid = uid;
RB_INSERT(server_acl_entries, &server_acl_entries, user);
entry = server_acl_entry_find(id, flags);
if (entry == NULL) {
entry = xcalloc(1, sizeof *entry);
entry->id = id;
entry->flags = flags & SERVER_ACL_IS_GROUP;
RB_INSERT(server_acl_entries, &server_acl_entries, entry);
}
}
/* Deny a user (remove from the tree). */
/* Deny an ACL entry (remove it from the tree). */
void
server_acl_user_deny(uid_t uid)
server_acl_deny(id_t id, int flags)
{
struct server_acl_user *user;
struct server_acl_entry *entry;
user = server_acl_user_find(uid);
if (user != NULL) {
RB_REMOVE(server_acl_entries, &server_acl_entries, user);
free(user);
entry = server_acl_entry_find(id, flags);
if (entry != NULL) {
RB_REMOVE(server_acl_entries, &server_acl_entries, entry);
free(entry);
server_acl_update();
}
}
/* Allow this user write access. */
/* Allow this ACL entry write access. */
void
server_acl_user_allow_write(uid_t uid)
server_acl_allow_write(id_t id, int flags)
{
struct server_acl_user *user;
struct client *c;
struct server_acl_entry *entry;
user = server_acl_user_find(uid);
if (user == NULL)
entry = server_acl_entry_find(id, flags);
if (entry == NULL)
return;
user->flags &= ~SERVER_ACL_READONLY;
TAILQ_FOREACH(c, &clients, entry) {
uid = proc_get_peer_uid(c->peer);
if (uid != (uid_t)-1 && uid == user->uid)
c->flags &= ~CLIENT_READONLY;
}
entry->flags &= ~SERVER_ACL_READONLY;
server_acl_update();
}
/* Deny this user write access. */
/* Deny this ACL entry write access. */
void
server_acl_user_deny_write(uid_t uid)
server_acl_deny_write(id_t id, int flags)
{
struct server_acl_user *user;
struct client *c;
struct server_acl_entry *entry;
user = server_acl_user_find(uid);
if (user == NULL)
entry = server_acl_entry_find(id, flags);
if (entry == NULL)
return;
user->flags |= SERVER_ACL_READONLY;
TAILQ_FOREACH(c, &clients, entry) {
uid = proc_get_peer_uid(c->peer);
if (uid != (uid_t)-1 && uid == user->uid)
c->flags |= CLIENT_READONLY;
}
entry->flags |= SERVER_ACL_READONLY;
server_acl_update();
}
/*
* Check if the client's UID exists in the ACL list and if so, set as read only
* if needed. Return false if the user does not exist.
* Check if the client's UID or GID exists in the ACL list and if so, set as
* read only if needed. UID entries take precedence over GID entries. Return
* false if no entry exists.
*/
int
server_acl_join(struct client *c)
{
struct server_acl_user *user;
uid_t uid;
struct server_acl_entry *entry;
uid = proc_get_peer_uid(c->peer);
if (uid == (uid_t)-1)
entry = server_acl_check(c);
if (entry == NULL)
return (0);
user = server_acl_user_find(uid);
if (user == NULL)
return (0);
if (user->flags & SERVER_ACL_READONLY)
if (entry->flags & SERVER_ACL_READONLY)
c->flags |= CLIENT_READONLY;
return (1);
}
/* Get UID for user entry. */
uid_t
server_acl_get_uid(struct server_acl_user *user)
{
return (user->uid);
}

View File

@@ -695,12 +695,8 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py,
bdr_bottom = fwp->yoff + fwp->sy;
if (py == bdr_bottom)
break;
if (window_pane_is_floating(wp)) {
/* Floating pane, check top border. */
bdr_top = fwp->yoff - 1;
if (py == bdr_top)
break;
}
if (py == bdr_top)
break;
}
}
if (fwp != NULL)

View File

@@ -497,6 +497,8 @@ server_child_exited(pid_t pid, int status)
log_debug("%%%u exited", wp->id);
wp->flags |= PANE_EXITED;
window_pane_block_finish(wp);
if (window_pane_destroy_ready(wp))
server_destroy_pane(wp, 1);
break;

76
tmux.1
View File

@@ -1207,6 +1207,7 @@ Kill the
server and clients and destroy all sessions.
.It Xo Ic kill\-session
.Op Fl aCg
.Op Fl f Ar filter
.Op Fl t Ar target\-session
.Xc
Destroy the given session, closing any windows linked to it and no other
@@ -1214,6 +1215,12 @@ sessions, and detaching all clients attached to it.
If
.Fl a
is given, all sessions but the specified one is killed.
When
.Fl a
is given,
.Fl f
specifies a filter.
Only sessions for which the filter is true are killed.
If
.Fl g
is given and the session is in a session group, all sessions in the group are
@@ -1578,35 +1585,57 @@ option.
Rename the session to
.Ar new\-name .
.It Xo Ic server\-access
.Op Fl adlrw
.Op Ar user
.Op Fl adglrw
.Op Ar user | group
.Xc
Change the access or read/write permission of
.Ar user .
.Ar user
or
.Ar group .
The user running the
.Nm
server (its owner) and the root user cannot be changed and are always
permitted access.
.Fl g
changes a group rather than a user.
.Pp
.Fl a
and
.Fl d
are used to give or revoke access for the specified user.
If the user is already attached, the
are used to give or revoke access for the specified user or group.
If a client is already attached, the
.Fl d
flag causes their clients to be detached.
flag causes it to be detached unless it is still permitted by another entry.
.Pp
.Fl r
and
.Fl w
change the permissions for
.Ar user :
.Ar user
or
.Ar group :
.Fl r
makes their clients read-only and
makes matching clients read-only and
.Fl w
writable.
.Fl l
lists current access permissions.
User entries are shown with
.Ql U ,
group entries with
.Ql G ,
and read-only or writable entries with
.Ql R
or
.Ql W ,
for example
.Ql user1 (U,W)
or
.Ql testgroup (G,R) .
If both a user and group entry match a client, the user entry takes
precedence.
Only the effective group ID of the client is used, not its supplementary
groups.
.Pp
By default, the access list is empty and
.Nm
@@ -3132,6 +3161,7 @@ the marked pane is used rather than the current pane.
.Tg killp
.It Xo Ic kill\-pane
.Op Fl a
.Op Fl f Ar filter
.Op Fl t Ar target\-pane
.Xc
.D1 Pq alias: Ic killp
@@ -3139,11 +3169,18 @@ Destroy the given pane.
If no panes remain in the containing window, it is also destroyed.
The
.Fl a
option kills all but the pane given with
option kills all panes in the window except the pane given with
.Fl t .
When
.Fl a
is given,
.Fl f
specifies a filter.
Only panes for which the filter is true are killed.
.Tg killw
.It Xo Ic kill\-window
.Op Fl a
.Op Fl f Ar filter
.Op Fl t Ar target\-window
.Xc
.D1 Pq alias: Ic killw
@@ -3152,8 +3189,14 @@ Kill the current window or the window at
removing it from any sessions to which it is linked.
The
.Fl a
option kills all but the window given with
option kills all windows in the session except the window given with
.Fl t .
When
.Fl a
is given,
.Fl f
specifies a filter.
Only windows for which the filter is true are killed.
.Tg lastp
.It Xo Ic last\-pane
.Op Fl deZ
@@ -3414,7 +3457,7 @@ but a different format may be specified with
.Fl F .
.Tg newp
.It Xo Ic new\-pane
.Op Fl bdefhIkPvZ
.Op Fl bBdefhIkPvZ
.Op Fl c Ar start\-directory
.Op Fl e Ar environment
.Op Fl F Ar format
@@ -3490,6 +3533,17 @@ but also sets the
option for this pane to
.Ar message .
.Pp
.Fl B
blocks until
.Ar shell\-command
exits, then returns its exit status.
For example:
.Bd -literal -offset indent
$ tmux new-pane -B 'vi afile'
$ echo $?
0
.Ed
.Pp
.Fl E ,
or an empty
.Ar shell\-command ,

16
tmux.h
View File

@@ -1305,6 +1305,7 @@ struct window_pane {
char tty[TTY_NAME_MAX];
int status;
struct timeval dead_time;
struct cmdq_item *block_item; /* new-pane -B: waiting for pane exit */
int fd;
struct bufferevent *event;
@@ -2436,6 +2437,7 @@ void proc_flush_peer(struct tmuxpeer *);
void proc_toggle_log(struct tmuxproc *);
pid_t proc_fork_and_daemon(int *);
uid_t proc_get_peer_uid(struct tmuxpeer *);
gid_t proc_get_peer_gid(struct tmuxpeer *);
/* cfg.c */
extern int cfg_finished;
@@ -3433,6 +3435,7 @@ struct window *window_find_by_id(u_int);
void window_update_activity(struct window *);
struct window *window_create(u_int, u_int, u_int, u_int);
void window_pane_set_event(struct window_pane *);
void window_pane_block_finish(struct window_pane *);
struct window_pane *window_get_active_at(struct window *, u_int, u_int);
struct window_pane *window_find_string(struct window *, const char *);
int window_has_floating_panes(struct window *);
@@ -3875,15 +3878,16 @@ struct screen *sixel_to_screen(struct sixel_image *);
#endif
/* server-acl.c */
#define SERVER_ACL_READONLY 0x1
#define SERVER_ACL_IS_GROUP 0x2
void server_acl_init(void);
struct server_acl_user *server_acl_user_find(uid_t);
int server_acl_find(id_t, int);
void server_acl_display(struct cmdq_item *);
void server_acl_user_allow(uid_t);
void server_acl_user_deny(uid_t);
void server_acl_user_allow_write(uid_t);
void server_acl_user_deny_write(uid_t);
void server_acl_allow(id_t, int);
void server_acl_deny(id_t, int);
void server_acl_allow_write(id_t, int);
void server_acl_deny_write(id_t, int);
int server_acl_join(struct client *);
uid_t server_acl_get_uid(struct server_acl_user *);
/* hyperlink.c */
u_int hyperlinks_put(struct hyperlinks *, const char *,

2
tty.c
View File

@@ -2051,6 +2051,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
tty_margin_off(tty);
if (ctx->flags & TTY_CTX_CELL_INVALIDATE)
tty_invalidate(tty);
tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy);
tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette,

View File

@@ -82,6 +82,23 @@ utf8_is_hangul_filler(const struct utf8_data *ud)
return (memcmp(ud->data, "\343\205\244", 3) == 0);
}
/* Count regional indicator characters. */
static u_int
utf8_regional_count(const struct utf8_data *ud)
{
u_int count = 0, i;
for (i = 0; i + 4 <= ud->size; i++) {
if (ud->data[i] == 0xf0 &&
ud->data[i + 1] == 0x9f &&
ud->data[i + 2] == 0x87 &&
ud->data[i + 3] >= 0xa6 &&
ud->data[i + 3] <= 0xbf)
count++;
}
return (count);
}
/* Should these two characters combine? */
int
utf8_should_combine(const struct utf8_data *with, const struct utf8_data *add)
@@ -94,8 +111,13 @@ utf8_should_combine(const struct utf8_data *with, const struct utf8_data *add)
return (0);
/* Regional indicators. */
if ((a >= 0x1F1E6 && a <= 0x1F1FF) && (w >= 0x1F1E6 && w <= 0x1F1FF))
if ((a >= 0x1F1E6 && a <= 0x1F1FF) && (w >= 0x1F1E6 && w <= 0x1F1FF)) {
if (utf8_regional_count(with) != 1)
return (0);
if (utf8_regional_count(add) != 1)
return (0);
return (1);
}
/* Emoji skin tone modifiers. */
switch (a) {

View File

@@ -355,6 +355,7 @@ window_tree_build(void *modedata, struct sort_criteria *sort_crit,
uint64_t *tag, const char *filter)
{
struct window_tree_modedata *data = modedata;
int squash_groups = data->squash_groups;
struct session *s, **l;
struct session_group *sg, *current;
u_int n, i;
@@ -370,15 +371,14 @@ window_tree_build(void *modedata, struct sort_criteria *sort_crit,
l = sort_get_sessions(&n, sort_crit);
if (n == 0)
return;
s = l[n - 1];
for (i = 0; i < n; i++) {
if (data->squash_groups &&
(sg = session_group_contains(s)) != NULL) {
s = l[i];
if (squash_groups && (sg = session_group_contains(s)) != NULL) {
if ((sg == current && s != data->fs.s) ||
(sg != current && s != TAILQ_FIRST(&sg->sessions)))
continue;
}
window_tree_build_session(l[i], modedata, sort_crit, filter);
window_tree_build_session(s, modedata, sort_crit, filter);
}
switch (data->type) {

View File

@@ -18,6 +18,7 @@
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
@@ -380,6 +381,13 @@ window_pane_destroy_ready(struct window_pane *wp)
if (~wp->flags & PANE_EXITED)
return (0);
/*
* If a command queue item is blocked on this pane, wait for the
* child's exit status before destroying it.
*/
if (wp->block_item != NULL && (~wp->flags & PANE_STATUSREADY))
return (0);
return (1);
}
@@ -642,12 +650,32 @@ window_get_active_at(struct window *w, u_int x, u_int y)
pane_status = options_get_number(w->options, "pane-border-status");
if (pane_status == PANE_STATUS_TOP) {
/*
* Prefer a pane's top border status line over the pane above's
* bottom border.
*/
TAILQ_FOREACH(wp, &w->z_index, zentry) {
if (!window_pane_visible(wp) || window_pane_is_floating(wp))
continue;
window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy);
if ((int)x < xoff || x > xoff + sx)
continue;
if ((int)y == yoff - 1)
return (wp);
}
}
TAILQ_FOREACH(wp, &w->z_index, zentry) {
if (!window_pane_visible(wp))
continue;
window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy);
if (!window_pane_is_floating(wp)) {
/* Tiled - to and including bottom or right border. */
/*
* Tiled - to and including the right border, excluding
* the bottom border.
*/
if ((int)x < xoff || x > xoff + sx)
continue;
if (pane_status == PANE_STATUS_TOP) {
@@ -1120,12 +1148,38 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit)
return (wp);
}
void
window_pane_block_finish(struct window_pane *wp)
{
struct cmdq_item *item = wp->block_item;
struct client *c;
int retval = 0;
if (item == NULL)
return;
wp->block_item = NULL;
if (wp->flags & PANE_STATUSREADY) {
if (WIFEXITED(wp->status))
retval = WEXITSTATUS(wp->status);
else if (WIFSIGNALED(wp->status))
retval = WTERMSIG(wp->status) + 128;
}
c = cmdq_get_client(item);
if (c != NULL && c->session == NULL)
c->retval = retval;
cmdq_continue(item);
}
static void
window_pane_destroy(struct window_pane *wp)
{
struct window_pane_resize *r;
struct window_pane_resize *r1;
window_pane_block_finish(wp);
window_pane_reset_mode_all(wp);
free(wp->searchstr);
@@ -1280,7 +1334,7 @@ window_pane_set_mode(struct window_pane *wp, struct window_pane *swp,
TAILQ_INSERT_HEAD(&wp->modes, wme, entry);
wme->screen = wme->mode->init(wme, fs, args);
}
wme->kill = args_has(args, 'k');
wme->kill = args != NULL ? args_has(args, 'k') : 0;
wp->screen = wme->screen;
wp->flags |= (PANE_REDRAW|PANE_REDRAWSCROLLBAR|PANE_CHANGED);