/* $OpenBSD: paste.c,v 1.47 2024/10/12 08:13:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * 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 #include #include #include #include #include "tmux.h" /* * Set of paste buffers. Note that paste buffer data is not necessarily a C * string! */ struct paste_buffer { char *data; size_t size; char *name; time_t created; int automatic; u_int order; RB_ENTRY(paste_buffer) name_entry; RB_ENTRY(paste_buffer) time_entry; }; static u_int paste_next_index; static u_int paste_next_order; static u_int paste_num_automatic; static RB_HEAD(paste_name_tree, paste_buffer) paste_by_name; static RB_HEAD(paste_time_tree, paste_buffer) paste_by_time; static int paste_cmp_names(const struct paste_buffer *, const struct paste_buffer *); RB_GENERATE_STATIC(paste_name_tree, paste_buffer, name_entry, paste_cmp_names); static int paste_cmp_times(const struct paste_buffer *, const struct paste_buffer *); RB_GENERATE_STATIC(paste_time_tree, paste_buffer, time_entry, paste_cmp_times); static int paste_cmp_names(const struct paste_buffer *a, const struct paste_buffer *b) { return (strcmp(a->name, b->name)); } static int paste_cmp_times(const struct paste_buffer *a, const struct paste_buffer *b) { if (a->order > b->order) return (-1); if (a->order < b->order) return (1); return (0); } /* Get paste buffer name. */ const char * paste_buffer_name(struct paste_buffer *pb) { return (pb->name); } /* Get paste buffer order. */ u_int paste_buffer_order(struct paste_buffer *pb) { return (pb->order); } /* Get paste buffer created. */ time_t paste_buffer_created(struct paste_buffer *pb) { return (pb->created); } /* Get paste buffer data. */ const char * paste_buffer_data(struct paste_buffer *pb, size_t *size) { if (size != NULL) *size = pb->size; return (pb->data); } /* Walk paste buffers by time. */ struct paste_buffer * paste_walk(struct paste_buffer *pb) { if (pb == NULL) return (RB_MIN(paste_time_tree, &paste_by_time)); return (RB_NEXT(paste_time_tree, &paste_by_time, pb)); } int paste_is_empty(void) { return RB_ROOT(&paste_by_time) == NULL; } /* Get the most recent automatic buffer. */ struct paste_buffer * paste_get_top(const char **name) { struct paste_buffer *pb; pb = RB_MIN(paste_time_tree, &paste_by_time); while (pb != NULL && !pb->automatic) pb = RB_NEXT(paste_time_tree, &paste_by_time, pb); if (pb == NULL) return (NULL); if (name != NULL) *name = pb->name; return (pb); } /* Get a paste buffer by name. */ struct paste_buffer * paste_get_name(const char *name) { struct paste_buffer pbfind; if (name == NULL || *name == '\0') return (NULL); pbfind.name = (char *)name; return (RB_FIND(paste_name_tree, &paste_by_name, &pbfind)); } /* Free a paste buffer. */ void paste_free(struct paste_buffer *pb) { notify_paste_buffer(pb->name, 1); RB_REMOVE(paste_name_tree, &paste_by_name, pb); RB_REMOVE(paste_time_tree, &paste_by_time, pb); if (pb->automatic) paste_num_automatic--; free(pb->data); free(pb->name); free(pb); } /* * Add an automatic buffer, freeing the oldest automatic item if at limit. Note * that the caller is responsible for allocating data. */ void paste_add(const char *prefix, char *data, size_t size) { struct paste_buffer *pb, *pb1; u_int limit; if (prefix == NULL) prefix = "buffer"; if (size == 0) { free(data); return; } limit = options_get_number(global_options, "buffer-limit"); RB_FOREACH_REVERSE_SAFE(pb, paste_time_tree, &paste_by_time, pb1) { if (paste_num_automatic < limit) break; if (pb->automatic) paste_free(pb); } pb = xmalloc(sizeof *pb); pb->name = NULL; do { free(pb->name); xasprintf(&pb->name, "%s%u", prefix, paste_next_index); paste_next_index++; } while (paste_get_name(pb->name) != NULL); pb->data = data; pb->size = size; pb->automatic = 1; paste_num_automatic++; pb->created = time(NULL); pb->order = paste_next_order++; RB_INSERT(paste_name_tree, &paste_by_name, pb); RB_INSERT(paste_time_tree, &paste_by_time, pb); notify_paste_buffer(pb->name, 0); } /* Rename a paste buffer. */ int paste_rename(const char *oldname, const char *newname, char **cause) { struct paste_buffer *pb, *pb_new; if (cause != NULL) *cause = NULL; if (oldname == NULL || *oldname == '\0') { if (cause != NULL) *cause = xstrdup("no buffer"); return (-1); } if (newname == NULL || *newname == '\0') { if (cause != NULL) *cause = xstrdup("new name is empty"); return (-1); } pb = paste_get_name(oldname); if (pb == NULL) { if (cause != NULL) xasprintf(cause, "no buffer %s", oldname); return (-1); } pb_new = paste_get_name(newname); if (pb_new == pb) return (0); if (pb_new != NULL) paste_free(pb_new); RB_REMOVE(paste_name_tree, &paste_by_name, pb); free(pb->name); pb->name = xstrdup(newname); if (pb->automatic) paste_num_automatic--; pb->automatic = 0; RB_INSERT(paste_name_tree, &paste_by_name, pb); notify_paste_buffer(oldname, 1); notify_paste_buffer(newname, 0); return (0); } /* * Add or replace an item in the store. Note that the caller is responsible for * allocating data. */ int paste_set(char *data, size_t size, const char *name, char **cause) { struct paste_buffer *pb, *old; if (cause != NULL) *cause = NULL; if (size == 0) { free(data); return (0); } if (name == NULL) { paste_add(NULL, data, size); return (0); } if (*name == '\0') { if (cause != NULL) *cause = xstrdup("empty buffer name"); return (-1); } pb = xmalloc(sizeof *pb); pb->name = xstrdup(name); pb->data = data; pb->size = size; pb->automatic = 0; pb->order = paste_next_order++; pb->created = time(NULL); if ((old = paste_get_name(name)) != NULL) paste_free(old); RB_INSERT(paste_name_tree, &paste_by_name, pb); RB_INSERT(paste_time_tree, &paste_by_time, pb); notify_paste_buffer(name, 0); return (0); } /* Set paste data without otherwise changing it. */ void paste_replace(struct paste_buffer *pb, char *data, size_t size) { free(pb->data); pb->data = data; pb->size = size; notify_paste_buffer(pb->name, 0); } /* Convert start of buffer into a nice string. */ char * paste_make_sample(struct paste_buffer *pb) { char *buf; size_t len, used; const int flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL; const size_t width = 200; len = pb->size; if (len > width) len = width; buf = xreallocarray(NULL, len, 4 + 4); used = utf8_strvis(buf, pb->data, len, flags); if (pb->size > width || used > width) strlcpy(buf + width, "...", 4); return (buf); }