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