1 /* vifm
2 * Copyright (C) 2011 xaizek.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #include "undo.h"
20
21 #include <assert.h> /* assert() */
22 #include <stddef.h> /* size_t */
23 #include <stdio.h>
24 #include <stdlib.h> /* free() malloc() */
25 #include <string.h> /* strcpy() strdup() */
26
27 #include "compat/fs_limits.h"
28 #include "compat/reallocarray.h"
29 #include "utils/fs.h"
30 #include "utils/macros.h"
31 #include "utils/path.h"
32 #include "utils/str.h"
33 #include "utils/utils.h"
34 #include "ops.h"
35 #include "registers.h"
36 #include "trash.h"
37
38 /* XXX: the unit isn't clever enough to handle operations on the same file
39 * within group of changes, hence fake OP_MOVETMP* operations. Hard to
40 * tell if this can be done without such workarounds, but worth a try. */
41
42 typedef struct
43 {
44 char *msg;
45 int error;
46 int balance;
47 int can_undone;
48 int incomplete;
49 }
50 group_t;
51
52 typedef struct
53 {
54 OPS op;
55 const char *src; /* NULL, buf1 or buf2 */
56 const char *dst; /* NULL, buf1 or buf2 */
57 void *data; /* for uid_t, gid_t and mode_t */
58 const char *exists; /* NULL, buf1 or buf2 */
59 const char *dont_exist; /* NULL, buf1 or buf2 */
60 }
61 op_t;
62
63 typedef struct cmd_t
64 {
65 char *buf1;
66 char *buf2;
67 op_t do_op;
68 op_t undo_op;
69
70 group_t *group;
71 struct cmd_t *prev;
72 struct cmd_t *next;
73 }
74 cmd_t;
75
76 static OPS undo_op[] = {
77 OP_NONE, /* OP_NONE */
78 OP_NONE, /* OP_USR */
79 OP_NONE, /* OP_REMOVE */
80 OP_SYMLINK, /* OP_REMOVESL */
81 OP_REMOVE, /* OP_COPY */
82 OP_REMOVE, /* OP_COPYF */
83 OP_REMOVE, /* OP_COPYA */
84 OP_MOVE, /* OP_MOVE */
85 OP_MOVE, /* OP_MOVEF */
86 OP_MOVE, /* OP_MOVEA */
87 OP_MOVETMP1, /* OP_MOVETMP1 */
88 OP_MOVETMP2, /* OP_MOVETMP2 */
89 OP_MOVETMP3, /* OP_MOVETMP3 */
90 OP_MOVETMP4, /* OP_MOVETMP4 */
91 OP_CHOWN, /* OP_CHOWN */
92 OP_CHGRP, /* OP_CHGRP */
93 #ifndef _WIN32
94 OP_CHMOD, /* OP_CHMOD */
95 OP_CHMODR, /* OP_CHMODR */
96 #else
97 OP_SUBATTR, /* OP_ADDATTR */
98 OP_ADDATTR, /* OP_SUBATTR */
99 #endif
100 OP_REMOVE, /* OP_SYMLINK */
101 OP_REMOVESL, /* OP_SYMLINK2 */
102 OP_RMDIR, /* OP_MKDIR */
103 OP_MKDIR, /* OP_RMDIR */
104 OP_REMOVE, /* OP_MKFILE */
105 };
106 ARRAY_GUARD(undo_op, OP_COUNT);
107
108 static enum
109 {
110 OPER_1ST,
111 OPER_2ND,
112 OPER_NON,
113 } opers[][8] = {
114 /* 1st arg 2nd arg exists absent */
115 { OPER_NON, OPER_NON, OPER_NON, OPER_NON, /* redo OP_NONE */
116 OPER_NON, OPER_NON, OPER_NON, OPER_NON, }, /* undo OP_NONE */
117 { OPER_NON, OPER_NON, OPER_NON, OPER_NON, /* redo OP_USR */
118 OPER_NON, OPER_NON, OPER_NON, OPER_NON, }, /* undo OP_NONE */
119 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_REMOVE */
120 OPER_NON, OPER_NON, OPER_NON, OPER_NON, }, /* undo OP_NONE */
121 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_REMOVESL */
122 OPER_2ND, OPER_1ST, OPER_NON, OPER_NON, }, /* undo OP_SYMLINK2 */
123 { OPER_1ST, OPER_2ND, OPER_1ST, OPER_2ND, /* redo OP_COPY */
124 OPER_2ND, OPER_NON, OPER_2ND, OPER_NON, }, /* undo OP_REMOVE */
125 { OPER_1ST, OPER_2ND, OPER_1ST, OPER_NON, /* redo OP_COPYF */
126 OPER_2ND, OPER_NON, OPER_2ND, OPER_NON, }, /* undo OP_REMOVE */
127 { OPER_1ST, OPER_2ND, OPER_1ST, OPER_2ND, /* redo OP_COPYA */
128 OPER_2ND, OPER_NON, OPER_2ND, OPER_NON, }, /* undo OP_REMOVE */
129 { OPER_1ST, OPER_2ND, OPER_1ST, OPER_2ND, /* redo OP_MOVE */
130 OPER_2ND, OPER_1ST, OPER_2ND, OPER_1ST, }, /* undo OP_MOVE */
131 { OPER_1ST, OPER_2ND, OPER_1ST, OPER_NON, /* redo OP_MOVEF */
132 OPER_2ND, OPER_1ST, OPER_2ND, OPER_1ST, }, /* undo OP_MOVE */
133 { OPER_1ST, OPER_2ND, OPER_1ST, OPER_2ND, /* redo OP_MOVEA */
134 OPER_2ND, OPER_1ST, OPER_2ND, OPER_1ST, }, /* undo OP_MOVE */
135 { OPER_1ST, OPER_2ND, OPER_2ND, OPER_NON, /* redo OP_MOVETMP1 */
136 OPER_2ND, OPER_1ST, OPER_2ND, OPER_NON, }, /* undo OP_MOVETMP1 */
137 { OPER_1ST, OPER_2ND, OPER_1ST, OPER_NON, /* redo OP_MOVETMP2 */
138 OPER_2ND, OPER_1ST, OPER_1ST, OPER_NON, }, /* undo OP_MOVETMP2 */
139 { OPER_1ST, OPER_2ND, OPER_NON, OPER_2ND, /* redo OP_MOVETMP3 */
140 OPER_2ND, OPER_1ST, OPER_2ND, OPER_1ST, }, /* undo OP_MOVETMP3 */
141 { OPER_1ST, OPER_2ND, OPER_1ST, OPER_2ND, /* redo OP_MOVETMP4 */
142 OPER_2ND, OPER_1ST, OPER_NON, OPER_NON, }, /* undo OP_MOVETMP4 */
143 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_CHOWN */
144 OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, }, /* undo OP_CHOWN */
145 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_CHGRP */
146 OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, }, /* undo OP_CHGRP */
147 #ifndef _WIN32
148 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_CHMOD */
149 OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, }, /* undo OP_CHMOD */
150 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_CHMODR */
151 OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, }, /* undo OP_CHMODR */
152 #else
153 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_ADDATTR */
154 OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, }, /* undo OP_SUBATTR */
155 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_SUBATTR */
156 OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, }, /* undo OP_ADDATTR */
157 #endif
158 { OPER_1ST, OPER_2ND, OPER_NON, OPER_2ND, /* redo OP_SYMLINK */
159 OPER_2ND, OPER_NON, OPER_2ND, OPER_NON, }, /* undo OP_REMOVE */
160 { OPER_1ST, OPER_2ND, OPER_NON, OPER_NON, /* redo OP_SYMLINK2 */
161 OPER_2ND, OPER_NON, OPER_2ND, OPER_NON, }, /* undo OP_REMOVESL */
162 { OPER_1ST, OPER_NON, OPER_NON, OPER_1ST, /* redo OP_MKDIR */
163 OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, }, /* undo OP_RMDIR */
164 { OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, /* redo OP_RMDIR */
165 OPER_1ST, OPER_NON, OPER_NON, OPER_1ST, }, /* undo OP_MKDIR */
166 { OPER_1ST, OPER_NON, OPER_NON, OPER_1ST, /* redo OP_MKFILE */
167 OPER_1ST, OPER_NON, OPER_1ST, OPER_NON, }, /* undo OP_REMOVE */
168 };
169 ARRAY_GUARD(opers, OP_COUNT);
170
171 /* Flags that indicate which operations have data that must be free()'d. */
172 static const char data_is_ptr[] = {
173 0, /* OP_NONE */
174 1, /* OP_USR */
175 0, /* OP_REMOVE */
176 0, /* OP_REMOVESL */
177 0, /* OP_COPY */
178 0, /* OP_COPYF */
179 0, /* OP_COPYA */
180 0, /* OP_MOVE */
181 0, /* OP_MOVEF */
182 0, /* OP_MOVEA */
183 0, /* OP_MOVETMP1 */
184 0, /* OP_MOVETMP2 */
185 0, /* OP_MOVETMP3 */
186 0, /* OP_MOVETMP4 */
187 0, /* OP_CHOWN */
188 0, /* OP_CHGRP */
189 #ifndef _WIN32
190 1, /* OP_CHMOD */
191 1, /* OP_CHMODR */
192 #else
193 0, /* OP_ADDATTR */
194 0, /* OP_SUBATTR */
195 #endif
196 0, /* OP_SYMLINK */
197 0, /* OP_SYMLINK2 */
198 0, /* OP_MKDIR */
199 0, /* OP_RMDIR */
200 0, /* OP_MKFILE */
201 };
202 ARRAY_GUARD(data_is_ptr, OP_COUNT);
203
204 /* Operation handler function. Performs all undo and redo operations. */
205 static un_perform_func do_func;
206 /* External function, which corrects operation availability and influence on
207 * operation checks. */
208 static un_op_available_func op_avail_func;
209 /* External optional callback to abort execution of compound operations. */
210 static un_cancel_requested_func cancel_func;
211 /* Number of undo levels, which are not groups but operations. */
212 static const int *undo_levels;
213
214 /* List head thanks to which current is never NULL. cmds.prev points to the
215 * actual head of the list or back to cmds when the list is empty. */
216 static cmd_t cmds = {
217 .prev = &cmds,
218 };
219 /* Points at the current item of the list of operations. Matches cmds.prev
220 * unless some operations were undone. */
221 static cmd_t *current = &cmds;
222
223 static int group_opened;
224 static long long next_group;
225 static group_t *last_group;
226 static char *group_msg;
227
228 static int command_count;
229
230 static int no_function(void);
231 static void init_cmd(cmd_t *cmd, OPS op, void *do_data, void *undo_data);
232 static void init_entry(cmd_t *cmd, const char **e, int type);
233 static void remove_cmd(cmd_t *cmd);
234 static int is_undo_group_possible(void);
235 static int is_redo_group_possible(void);
236 static int is_op_possible(const op_t *op);
237 static void change_filename_in_trash(cmd_t *cmd, const char filename[]);
238 static void update_entry(const char **e, const char old[], const char new[]);
239 static char ** fill_undolist_detail(char **list);
240 static const char * get_op_desc(op_t op);
241 static char ** fill_undolist_nondetail(char **list);
242
243 void
un_init(un_perform_func exec_func,un_op_available_func op_avail,un_cancel_requested_func cancel,const int * max_levels)244 un_init(un_perform_func exec_func, un_op_available_func op_avail,
245 un_cancel_requested_func cancel, const int *max_levels)
246 {
247 assert(exec_func != NULL);
248
249 do_func = exec_func;
250 op_avail_func = op_avail;
251 cancel_func = (cancel != NULL) ? cancel : &no_function;
252 undo_levels = max_levels;
253 }
254
255 /* Always says no. Returns zero. */
256 static int
no_function(void)257 no_function(void)
258 {
259 return 0;
260 }
261
262 void
un_reset(void)263 un_reset(void)
264 {
265 assert(!group_opened);
266
267 while(cmds.next != NULL)
268 remove_cmd(cmds.next);
269 cmds.prev = &cmds;
270
271 current = &cmds;
272 next_group = 0;
273 last_group = NULL;
274 }
275
276 void
un_group_open(const char msg[])277 un_group_open(const char msg[])
278 {
279 assert(!group_opened);
280
281 group_opened = 1;
282
283 (void)replace_string(&group_msg, msg);
284 last_group = NULL;
285 }
286
287 void
un_group_reopen_last(void)288 un_group_reopen_last(void)
289 {
290 assert(!group_opened);
291 assert(next_group != 0);
292
293 group_opened = 1;
294 next_group--;
295 }
296
297 char *
un_replace_group_msg(const char msg[])298 un_replace_group_msg(const char msg[])
299 {
300 char *result;
301
302 result = group_msg;
303 group_msg = (msg != NULL) ? strdup(msg) : NULL;
304 if(last_group != NULL && group_msg != NULL)
305 {
306 (void)replace_string(&last_group->msg, group_msg);
307 }
308 return result;
309 }
310
311 int
un_group_add_op(OPS op,void * do_data,void * undo_data,const char buf1[],const char buf2[])312 un_group_add_op(OPS op, void *do_data, void *undo_data, const char buf1[],
313 const char buf2[])
314 {
315 int mem_error;
316 cmd_t *cmd;
317
318 assert(group_opened);
319 assert(buf1 != NULL);
320 assert(buf2 != NULL);
321
322 /* free list tail */
323 while(current->next != NULL)
324 remove_cmd(current->next);
325
326 while(command_count > 0 && command_count >= *undo_levels)
327 remove_cmd(cmds.next);
328
329 if(*undo_levels <= 0)
330 {
331 if(data_is_ptr[op])
332 {
333 free(do_data);
334 }
335 if(data_is_ptr[undo_op[op]])
336 {
337 free(undo_data);
338 }
339 return 0;
340 }
341
342 command_count++;
343
344 /* add operation to the list */
345 cmd = calloc(1, sizeof(*cmd));
346 if(cmd == NULL)
347 return -1;
348
349 cmd->buf1 = strdup(buf1);
350 cmd->buf2 = strdup(buf2);
351 cmd->prev = current;
352 init_cmd(cmd, op, do_data, undo_data);
353 if(last_group != NULL)
354 {
355 cmd->group = last_group;
356 }
357 else if((cmd->group = malloc(sizeof(group_t))) != NULL)
358 {
359 cmd->group->msg = strdup(group_msg);
360 cmd->group->error = 0;
361 cmd->group->balance = 0;
362 cmd->group->can_undone = 1;
363 cmd->group->incomplete = 0;
364 }
365 mem_error = cmd->group == NULL || cmd->buf1 == NULL || cmd->buf2 == NULL;
366 if(mem_error)
367 {
368 remove_cmd(cmd);
369 return -1;
370 }
371 last_group = cmd->group;
372
373 if(undo_op[op] == OP_NONE)
374 cmd->group->can_undone = 0;
375
376 current->next = cmd;
377 current = cmd;
378 cmds.prev = cmd;
379
380 return 0;
381 }
382
383 static void
init_cmd(cmd_t * cmd,OPS op,void * do_data,void * undo_data)384 init_cmd(cmd_t *cmd, OPS op, void *do_data, void *undo_data)
385 {
386 cmd->do_op.op = op;
387 cmd->do_op.data = do_data;
388 cmd->undo_op.op = undo_op[op];
389 cmd->undo_op.data = undo_data;
390 init_entry(cmd, &cmd->do_op.src, opers[op][0]);
391 init_entry(cmd, &cmd->do_op.dst, opers[op][1]);
392 init_entry(cmd, &cmd->do_op.exists, opers[op][2]);
393 init_entry(cmd, &cmd->do_op.dont_exist, opers[op][3]);
394 init_entry(cmd, &cmd->undo_op.src, opers[op][4]);
395 init_entry(cmd, &cmd->undo_op.dst, opers[op][5]);
396 init_entry(cmd, &cmd->undo_op.exists, opers[op][6]);
397 init_entry(cmd, &cmd->undo_op.dont_exist, opers[op][7]);
398 }
399
400 static void
init_entry(cmd_t * cmd,const char ** e,int type)401 init_entry(cmd_t *cmd, const char **e, int type)
402 {
403 if(type == OPER_NON)
404 *e = NULL;
405 else if(type == OPER_1ST)
406 *e = cmd->buf1;
407 else
408 *e = cmd->buf2;
409 }
410
411 static void
remove_cmd(cmd_t * cmd)412 remove_cmd(cmd_t *cmd)
413 {
414 int last_cmd_in_group = 1;
415
416 if(cmd == current)
417 current = cmd->prev;
418
419 if(cmd->prev != NULL)
420 {
421 cmd->prev->next = cmd->next;
422 if(cmd->group == cmd->prev->group)
423 last_cmd_in_group = 0;
424 }
425 if(cmd->next != NULL)
426 {
427 cmd->next->prev = cmd->prev;
428 if(cmd->group == cmd->next->group)
429 last_cmd_in_group = 0;
430 }
431 else /* this is the last command in the list */
432 {
433 cmds.prev = cmd->prev;
434 }
435
436 if(last_cmd_in_group)
437 {
438 free(cmd->group->msg);
439 free(cmd->group);
440 if(last_group == cmd->group)
441 last_group = NULL;
442 }
443 else
444 {
445 cmd->group->incomplete = 1;
446 }
447 free(cmd->buf1);
448 free(cmd->buf2);
449 if(data_is_ptr[cmd->do_op.op])
450 free(cmd->do_op.data);
451 if(data_is_ptr[cmd->undo_op.op])
452 free(cmd->undo_op.data);
453
454 free(cmd);
455
456 command_count--;
457 }
458
459 void
un_group_close(void)460 un_group_close(void)
461 {
462 assert(group_opened);
463
464 group_opened = 0;
465 next_group++;
466
467 while(cmds.next != NULL && cmds.next->group->incomplete)
468 remove_cmd(cmds.next);
469 }
470
471 int
un_last_group_empty(void)472 un_last_group_empty(void)
473 {
474 return (last_group == NULL);
475 }
476
477 UnErrCode
un_group_undo(void)478 un_group_undo(void)
479 {
480 assert(!group_opened);
481
482 if(current == &cmds)
483 return UN_ERR_NONE;
484
485 int errors = (current->group->error != 0);
486 const int disbalance = (current->group->balance != 0);
487 const int cant_undo = !current->group->can_undone;
488 if(errors || disbalance || cant_undo || !is_undo_group_possible())
489 {
490 do
491 current = current->prev;
492 while(current != &cmds && current->group == current->next->group);
493
494 if(errors) return UN_ERR_ERRORS;
495 if(disbalance) return UN_ERR_BALANCE;
496 if(cant_undo) return UN_ERR_NOUNDO;
497 return UN_ERR_BROKEN;
498 }
499
500 regs_sync_from_shared_memory();
501 current->group->balance--;
502
503 int skip = 0;
504 int cancelled;
505 do
506 {
507 if(!skip)
508 {
509 int err = do_func(current->undo_op.op, current->undo_op.data,
510 current->undo_op.src, current->undo_op.dst);
511 if(err == SKIP_UNDO_REDO_OPERATION)
512 {
513 skip = 1;
514 current->group->balance++;
515 }
516 else if(err != 0)
517 {
518 current->group->error = 1;
519 errors = 1;
520 }
521 }
522 current = current->prev;
523 }
524 while(!(cancelled = cancel_func()) && current != &cmds &&
525 current->group == current->next->group);
526
527 regs_sync_to_shared_memory();
528
529 if(cancelled) return UN_ERR_CANCELLED;
530 if(skip) return UN_ERR_SKIPPED;
531 if(errors) return UN_ERR_FAIL;
532 return UN_ERR_SUCCESS;
533 }
534
535 static int
is_undo_group_possible(void)536 is_undo_group_possible(void)
537 {
538 cmd_t *cmd = current;
539 do
540 {
541 int ret;
542 ret = is_op_possible(&cmd->undo_op);
543 if(ret == 0)
544 return 0;
545 else if(ret < 0)
546 change_filename_in_trash(cmd, cmd->undo_op.dst);
547 cmd = cmd->prev;
548 }
549 while(cmd != &cmds && cmd->group == cmd->next->group);
550 return 1;
551 }
552
553 UnErrCode
un_group_redo(void)554 un_group_redo(void)
555 {
556 assert(!group_opened);
557
558 if(current->next == NULL)
559 return UN_ERR_NONE;
560
561 int errors = (current->next->group->error != 0);
562 const int disbalance = (current->next->group->balance == 0);
563 if(errors || disbalance || !is_redo_group_possible())
564 {
565 do
566 current = current->next;
567 while(current->next != NULL && current->group == current->next->group);
568
569 if(errors) return UN_ERR_ERRORS;
570 if(disbalance) return UN_ERR_BALANCE;
571 return UN_ERR_BROKEN;
572 }
573
574 regs_sync_from_shared_memory();
575 current->next->group->balance++;
576
577 int skip = 0;
578 int cancelled;
579 do
580 {
581 current = current->next;
582 if(!skip)
583 {
584 int err = do_func(current->do_op.op, current->do_op.data,
585 current->do_op.src, current->do_op.dst);
586 if(err == SKIP_UNDO_REDO_OPERATION)
587 {
588 current->next->group->balance--;
589 skip = 1;
590 }
591 else if(err != 0)
592 {
593 current->group->error = 1;
594 errors = 1;
595 }
596 }
597 }
598 while(!(cancelled = cancel_func()) && current->next != NULL &&
599 current->group == current->next->group);
600
601 regs_sync_to_shared_memory();
602
603 if(cancelled) return UN_ERR_CANCELLED;
604 if(skip) return UN_ERR_SKIPPED;
605 if(errors) return UN_ERR_FAIL;
606 return UN_ERR_SUCCESS;
607 }
608
609 static int
is_redo_group_possible(void)610 is_redo_group_possible(void)
611 {
612 cmd_t *cmd = current;
613 do
614 {
615 int ret;
616 cmd = cmd->next;
617 ret = is_op_possible(&cmd->do_op);
618 if(ret == 0)
619 return 0;
620 else if(ret < 0)
621 change_filename_in_trash(cmd, cmd->do_op.dst);
622 }
623 while(cmd->next != NULL && cmd->group == cmd->next->group);
624 return 1;
625 }
626
627 /*
628 * Return value:
629 * 0 - impossible
630 * < 0 - possible with renaming file in trash
631 * > 0 - possible
632 */
633 static int
is_op_possible(const op_t * op)634 is_op_possible(const op_t *op)
635 {
636 if(op_avail_func != NULL)
637 {
638 const int avail = op_avail_func(op->op);
639 if(avail != 0)
640 {
641 return (avail > 0);
642 }
643 }
644
645 if(op->exists != NULL && !path_exists(op->exists, NODEREF))
646 {
647 return 0;
648 }
649 if(op->dont_exist != NULL && path_exists(op->dont_exist, NODEREF) &&
650 !is_case_change(op->src, op->dst))
651 {
652 return (trash_has_path(op->dst) ? -1 : 0);
653 }
654 return 1;
655 }
656
657 static void
change_filename_in_trash(cmd_t * cmd,const char filename[])658 change_filename_in_trash(cmd_t *cmd, const char filename[])
659 {
660 const char *name_tail;
661 char *new;
662 char *old;
663 char *const base_dir = strdup(filename);
664
665 remove_last_path_component(base_dir);
666
667 name_tail = trash_get_real_name_of(filename);
668 new = trash_gen_path(base_dir, name_tail);
669 assert(new != NULL && "Should always get trash name here.");
670
671 free(base_dir);
672
673 old = cmd->buf2;
674 cmd->buf2 = new;
675
676 regs_rename_contents(filename, new);
677
678 update_entry(&cmd->do_op.src, old, cmd->buf2);
679 update_entry(&cmd->do_op.dst, old, cmd->buf2);
680 update_entry(&cmd->do_op.exists, old, cmd->buf2);
681 update_entry(&cmd->do_op.dont_exist, old, cmd->buf2);
682 update_entry(&cmd->undo_op.src, old, cmd->buf2);
683 update_entry(&cmd->undo_op.dst, old, cmd->buf2);
684 update_entry(&cmd->undo_op.exists, old, cmd->buf2);
685 update_entry(&cmd->undo_op.dont_exist, old, cmd->buf2);
686
687 free(old);
688 }
689
690 /* Checks whether *e equals old and updates it to new if so. */
691 static void
update_entry(const char ** e,const char old[],const char new[])692 update_entry(const char **e, const char old[], const char new[])
693 {
694 if(*e == old)
695 {
696 *e = new;
697 }
698 }
699
700 char **
un_get_list(int detail)701 un_get_list(int detail)
702 {
703 char **list, **p;
704 int group_count;
705 cmd_t *cmd;
706
707 assert(!group_opened);
708
709 group_count = 1;
710 cmd = cmds.prev;
711 while(cmd != &cmds)
712 {
713 if(cmd->group != cmd->prev->group)
714 group_count++;
715 cmd = cmd->prev;
716 }
717
718 if(detail)
719 list = reallocarray(NULL, group_count + command_count*2 + 1,
720 sizeof(char *));
721 else
722 list = reallocarray(NULL, group_count, sizeof(char *));
723
724 if(list == NULL)
725 return NULL;
726
727 if(detail)
728 p = fill_undolist_detail(list);
729 else
730 p = fill_undolist_nondetail(list);
731 *p = NULL;
732
733 return list;
734 }
735
736 static char **
fill_undolist_detail(char ** list)737 fill_undolist_detail(char **list)
738 {
739 int left;
740 cmd_t *cmd;
741
742 left = *undo_levels;
743 cmd = cmds.prev;
744 while(cmd != &cmds && left > 0)
745 {
746 if((*list = format_str(" %s", cmd->group->msg)) == NULL)
747 break;
748
749 list++;
750 do
751 {
752 const char *p;
753
754 p = get_op_desc(cmd->do_op);
755 if((*list = format_str(" do: %s", p)) == NULL)
756 {
757 return list;
758 }
759 ++list;
760
761 p = get_op_desc(cmd->undo_op);
762 if((*list = format_str(" undo: %s", p)) == NULL)
763 {
764 return list;
765 }
766 ++list;
767
768 cmd = cmd->prev;
769 --left;
770 }
771 while(cmd != &cmds && cmd->group == cmd->next->group && left > 0);
772 }
773
774 return list;
775 }
776
777 static const char *
get_op_desc(op_t op)778 get_op_desc(op_t op)
779 {
780 static char buf[64 + 2*PATH_MAX] = "";
781 switch(op.op)
782 {
783 case OP_NONE:
784 strcpy(buf, "<no operation>");
785 break;
786 case OP_USR:
787 copy_str(buf, sizeof(buf), (const char *)op.data);
788 break;
789 case OP_REMOVE:
790 case OP_REMOVESL:
791 snprintf(buf, sizeof(buf), "rm %s", op.src);
792 break;
793 case OP_COPY:
794 case OP_COPYA:
795 snprintf(buf, sizeof(buf), "cp %s to %s", op.src, op.dst);
796 break;
797 case OP_COPYF:
798 snprintf(buf, sizeof(buf), "cp -f %s to %s", op.src, op.dst);
799 break;
800 case OP_MOVE:
801 case OP_MOVEA:
802 case OP_MOVETMP1:
803 case OP_MOVETMP2:
804 case OP_MOVETMP3:
805 case OP_MOVETMP4:
806 snprintf(buf, sizeof(buf), "mv %s to %s", op.src, op.dst);
807 break;
808 case OP_MOVEF:
809 snprintf(buf, sizeof(buf), "mv -f %s to %s", op.src, op.dst);
810 break;
811 case OP_CHOWN:
812 snprintf(buf, sizeof(buf), "chown %" PRINTF_ULL " %s",
813 (unsigned long long)(size_t)op.data, op.src);
814 break;
815 case OP_CHGRP:
816 snprintf(buf, sizeof(buf), "chown :%" PRINTF_ULL " %s",
817 (unsigned long long)(size_t)op.data, op.src);
818 break;
819 #ifndef _WIN32
820 case OP_CHMOD:
821 case OP_CHMODR:
822 snprintf(buf, sizeof(buf), "chmod %s %s", (char *)op.data, op.src);
823 break;
824 #else
825 case OP_ADDATTR:
826 snprintf(buf, sizeof(buf), "attrib +%s", attr_str((size_t)op.data));
827 break;
828 case OP_SUBATTR:
829 snprintf(buf, sizeof(buf), "attrib -%s", attr_str((size_t)op.data));
830 break;
831 #endif
832 case OP_SYMLINK:
833 case OP_SYMLINK2:
834 snprintf(buf, sizeof(buf), "ln -s %s to %s", op.src, op.dst);
835 break;
836 case OP_MKDIR:
837 snprintf(buf, sizeof(buf), "mkdir %s%s", (op.data == NULL) ? "" : "-p ",
838 op.src);
839 break;
840 case OP_RMDIR:
841 snprintf(buf, sizeof(buf), "rmdir %s", op.src);
842 break;
843 case OP_MKFILE:
844 snprintf(buf, sizeof(buf), "touch %s", op.src);
845 break;
846
847 case OP_COUNT:
848 strcpy(buf, "ERROR, not a valid operation kind");
849 break;
850 }
851
852 return buf;
853 }
854
855 static char **
fill_undolist_nondetail(char ** list)856 fill_undolist_nondetail(char **list)
857 {
858 int left;
859 cmd_t *cmd;
860
861 left = *undo_levels;
862 cmd = cmds.prev;
863 while(cmd != &cmds && left-- > 0)
864 {
865 if((*list = format_str(" %s", cmd->group->msg)) == NULL)
866 break;
867
868 do
869 cmd = cmd->prev;
870 while(cmd != &cmds && cmd->group == cmd->next->group);
871 list++;
872 }
873
874 return list;
875 }
876
877 int
un_get_list_pos(int detail)878 un_get_list_pos(int detail)
879 {
880 cmd_t *cur = cmds.prev;
881 int result_group = 0;
882 int result_cmd = 0;
883
884 assert(!group_opened);
885
886 if(cur == &cmds)
887 result_group++;
888 while(cur != current)
889 {
890 if(cur->group != cur->prev->group)
891 result_group++;
892 result_cmd += 2;
893 cur = cur->prev;
894 }
895 return detail ? (result_group + result_cmd) : result_group;
896 }
897
898 void
un_set_pos(int pos,int detail)899 un_set_pos(int pos, int detail)
900 {
901 assert(!group_opened);
902
903 cmd_t *cur = cmds.prev;
904 cmd_t *last = cur;
905
906 --pos;
907 while(pos >= 0 && cur != &cmds)
908 {
909 if(cur->group != last->group)
910 {
911 last = cur;
912 --pos;
913 }
914 if(detail)
915 {
916 pos -= 2;
917 }
918 cur = cur->prev;
919 }
920
921 current = (pos == 0 ? cur : last);
922 }
923
924 void
un_clear_cmds_with_trash(const char trash_dir[])925 un_clear_cmds_with_trash(const char trash_dir[])
926 {
927 cmd_t *cur = cmds.prev;
928
929 assert(!group_opened);
930
931 while(cur != &cmds)
932 {
933 cmd_t *prev = cur->prev;
934
935 if(cur->group->balance < 0)
936 {
937 if(cur->do_op.exists != NULL &&
938 trash_has_path_at(trash_dir, cur->do_op.exists))
939 {
940 remove_cmd(cur);
941 }
942 }
943 else
944 {
945 if(cur->undo_op.exists != NULL &&
946 trash_has_path_at(trash_dir, cur->undo_op.exists))
947 {
948 remove_cmd(cur);
949 }
950 }
951 cur = prev;
952 }
953 }
954
955 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
956 /* vim: set cinoptions+=t0 filetype=c : */
957