1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2007 Nicholas Marriott <[email protected]> |
5 | * |
6 | * Permission to use, copy, modify, and distribute this software for any |
7 | * purpose with or without fee is hereby granted, provided that the above |
8 | * copyright notice and this permission notice appear in all copies. |
9 | * |
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | */ |
18 | |
19 | #include <sys/types.h> |
20 | |
21 | #include <stdlib.h> |
22 | #include <string.h> |
23 | #include <time.h> |
24 | |
25 | #include "tmux.h" |
26 | |
27 | /* |
28 | * Set of paste buffers. Note that paste buffer data is not necessarily a C |
29 | * string! |
30 | */ |
31 | |
32 | struct paste_buffer { |
33 | char *data; |
34 | size_t size; |
35 | |
36 | char *name; |
37 | time_t created; |
38 | int automatic; |
39 | u_int order; |
40 | |
41 | RB_ENTRY(paste_buffer) name_entry; |
42 | RB_ENTRY(paste_buffer) time_entry; |
43 | }; |
44 | |
45 | static u_int paste_next_index; |
46 | static u_int paste_next_order; |
47 | static u_int paste_num_automatic; |
48 | static RB_HEAD(paste_name_tree, paste_buffer) paste_by_name; |
49 | static RB_HEAD(paste_time_tree, paste_buffer) paste_by_time; |
50 | |
51 | static int paste_cmp_names(const struct paste_buffer *, |
52 | const struct paste_buffer *); |
53 | RB_GENERATE_STATIC(paste_name_tree, paste_buffer, name_entry, paste_cmp_names); |
54 | |
55 | static int paste_cmp_times(const struct paste_buffer *, |
56 | const struct paste_buffer *); |
57 | RB_GENERATE_STATIC(paste_time_tree, paste_buffer, time_entry, paste_cmp_times); |
58 | |
59 | static int |
60 | paste_cmp_names(const struct paste_buffer *a, const struct paste_buffer *b) |
61 | { |
62 | return (strcmp(a->name, b->name)); |
63 | } |
64 | |
65 | static int |
66 | paste_cmp_times(const struct paste_buffer *a, const struct paste_buffer *b) |
67 | { |
68 | if (a->order > b->order) |
69 | return (-1); |
70 | if (a->order < b->order) |
71 | return (1); |
72 | return (0); |
73 | } |
74 | |
75 | /* Get paste buffer name. */ |
76 | const char * |
77 | paste_buffer_name(struct paste_buffer *pb) |
78 | { |
79 | return (pb->name); |
80 | } |
81 | |
82 | /* Get paste buffer order. */ |
83 | u_int |
84 | paste_buffer_order(struct paste_buffer *pb) |
85 | { |
86 | return (pb->order); |
87 | } |
88 | |
89 | /* Get paste buffer created. */ |
90 | time_t |
91 | paste_buffer_created(struct paste_buffer *pb) |
92 | { |
93 | return (pb->created); |
94 | } |
95 | |
96 | /* Get paste buffer data. */ |
97 | const char * |
98 | paste_buffer_data(struct paste_buffer *pb, size_t *size) |
99 | { |
100 | if (size != NULL) |
101 | *size = pb->size; |
102 | return (pb->data); |
103 | } |
104 | |
105 | /* Walk paste buffers by time. */ |
106 | struct paste_buffer * |
107 | paste_walk(struct paste_buffer *pb) |
108 | { |
109 | if (pb == NULL) |
110 | return (RB_MIN(paste_time_tree, &paste_by_time)); |
111 | return (RB_NEXT(paste_time_tree, &paste_by_time, pb)); |
112 | } |
113 | |
114 | /* Get the most recent automatic buffer. */ |
115 | struct paste_buffer * |
116 | paste_get_top(const char **name) |
117 | { |
118 | struct paste_buffer *pb; |
119 | |
120 | pb = RB_MIN(paste_time_tree, &paste_by_time); |
121 | if (pb == NULL) |
122 | return (NULL); |
123 | if (name != NULL) |
124 | *name = pb->name; |
125 | return (pb); |
126 | } |
127 | |
128 | /* Get a paste buffer by name. */ |
129 | struct paste_buffer * |
130 | paste_get_name(const char *name) |
131 | { |
132 | struct paste_buffer pbfind; |
133 | |
134 | if (name == NULL || *name == '\0') |
135 | return (NULL); |
136 | |
137 | pbfind.name = (char *)name; |
138 | return (RB_FIND(paste_name_tree, &paste_by_name, &pbfind)); |
139 | } |
140 | |
141 | /* Free a paste buffer. */ |
142 | void |
143 | paste_free(struct paste_buffer *pb) |
144 | { |
145 | RB_REMOVE(paste_name_tree, &paste_by_name, pb); |
146 | RB_REMOVE(paste_time_tree, &paste_by_time, pb); |
147 | if (pb->automatic) |
148 | paste_num_automatic--; |
149 | |
150 | free(pb->data); |
151 | free(pb->name); |
152 | free(pb); |
153 | } |
154 | |
155 | /* |
156 | * Add an automatic buffer, freeing the oldest automatic item if at limit. Note |
157 | * that the caller is responsible for allocating data. |
158 | */ |
159 | void |
160 | paste_add(const char *prefix, char *data, size_t size) |
161 | { |
162 | struct paste_buffer *pb, *pb1; |
163 | u_int limit; |
164 | |
165 | if (prefix == NULL) |
166 | prefix = "buffer" ; |
167 | |
168 | if (size == 0) { |
169 | free(data); |
170 | return; |
171 | } |
172 | |
173 | limit = options_get_number(global_options, "buffer-limit" ); |
174 | RB_FOREACH_REVERSE_SAFE(pb, paste_time_tree, &paste_by_time, pb1) { |
175 | if (paste_num_automatic < limit) |
176 | break; |
177 | if (pb->automatic) |
178 | paste_free(pb); |
179 | } |
180 | |
181 | pb = xmalloc(sizeof *pb); |
182 | |
183 | pb->name = NULL; |
184 | do { |
185 | free(pb->name); |
186 | xasprintf(&pb->name, "%s%u" , prefix, paste_next_index); |
187 | paste_next_index++; |
188 | } while (paste_get_name(pb->name) != NULL); |
189 | |
190 | pb->data = data; |
191 | pb->size = size; |
192 | |
193 | pb->automatic = 1; |
194 | paste_num_automatic++; |
195 | |
196 | pb->created = time(NULL); |
197 | |
198 | pb->order = paste_next_order++; |
199 | RB_INSERT(paste_name_tree, &paste_by_name, pb); |
200 | RB_INSERT(paste_time_tree, &paste_by_time, pb); |
201 | } |
202 | |
203 | /* Rename a paste buffer. */ |
204 | int |
205 | paste_rename(const char *oldname, const char *newname, char **cause) |
206 | { |
207 | struct paste_buffer *pb, *pb_new; |
208 | |
209 | if (cause != NULL) |
210 | *cause = NULL; |
211 | |
212 | if (oldname == NULL || *oldname == '\0') { |
213 | if (cause != NULL) |
214 | *cause = xstrdup("no buffer" ); |
215 | return (-1); |
216 | } |
217 | if (newname == NULL || *newname == '\0') { |
218 | if (cause != NULL) |
219 | *cause = xstrdup("new name is empty" ); |
220 | return (-1); |
221 | } |
222 | |
223 | pb = paste_get_name(oldname); |
224 | if (pb == NULL) { |
225 | if (cause != NULL) |
226 | xasprintf(cause, "no buffer %s" , oldname); |
227 | return (-1); |
228 | } |
229 | |
230 | pb_new = paste_get_name(newname); |
231 | if (pb_new != NULL) { |
232 | if (cause != NULL) |
233 | xasprintf(cause, "buffer %s already exists" , newname); |
234 | return (-1); |
235 | } |
236 | |
237 | RB_REMOVE(paste_name_tree, &paste_by_name, pb); |
238 | |
239 | free(pb->name); |
240 | pb->name = xstrdup(newname); |
241 | |
242 | if (pb->automatic) |
243 | paste_num_automatic--; |
244 | pb->automatic = 0; |
245 | |
246 | RB_INSERT(paste_name_tree, &paste_by_name, pb); |
247 | |
248 | return (0); |
249 | } |
250 | |
251 | /* |
252 | * Add or replace an item in the store. Note that the caller is responsible for |
253 | * allocating data. |
254 | */ |
255 | int |
256 | paste_set(char *data, size_t size, const char *name, char **cause) |
257 | { |
258 | struct paste_buffer *pb, *old; |
259 | |
260 | if (cause != NULL) |
261 | *cause = NULL; |
262 | |
263 | if (size == 0) { |
264 | free(data); |
265 | return (0); |
266 | } |
267 | if (name == NULL) { |
268 | paste_add(NULL, data, size); |
269 | return (0); |
270 | } |
271 | |
272 | if (*name == '\0') { |
273 | if (cause != NULL) |
274 | *cause = xstrdup("empty buffer name" ); |
275 | return (-1); |
276 | } |
277 | |
278 | pb = xmalloc(sizeof *pb); |
279 | |
280 | pb->name = xstrdup(name); |
281 | |
282 | pb->data = data; |
283 | pb->size = size; |
284 | |
285 | pb->automatic = 0; |
286 | pb->order = paste_next_order++; |
287 | |
288 | pb->created = time(NULL); |
289 | |
290 | if ((old = paste_get_name(name)) != NULL) |
291 | paste_free(old); |
292 | |
293 | RB_INSERT(paste_name_tree, &paste_by_name, pb); |
294 | RB_INSERT(paste_time_tree, &paste_by_time, pb); |
295 | |
296 | return (0); |
297 | } |
298 | |
299 | /* Convert start of buffer into a nice string. */ |
300 | char * |
301 | paste_make_sample(struct paste_buffer *pb) |
302 | { |
303 | char *buf; |
304 | size_t len, used; |
305 | const int flags = VIS_OCTAL|VIS_TAB|VIS_NL; |
306 | const size_t width = 200; |
307 | |
308 | len = pb->size; |
309 | if (len > width) |
310 | len = width; |
311 | buf = xreallocarray(NULL, len, 4 + 4); |
312 | |
313 | used = utf8_strvis(buf, pb->data, len, flags); |
314 | if (pb->size > width || used > width) |
315 | strlcpy(buf + width, "..." , 4); |
316 | return (buf); |
317 | } |
318 | |