summaryrefslogtreecommitdiff
path: root/apps/fileop.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/fileop.c')
-rw-r--r--apps/fileop.c606
1 files changed, 606 insertions, 0 deletions
diff --git a/apps/fileop.c b/apps/fileop.c
new file mode 100644
index 0000000000..0d2dc774b9
--- /dev/null
+++ b/apps/fileop.c
@@ -0,0 +1,606 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Björn Stenberg
11 * Copyright (C) 2024 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22#include <stdbool.h>
23#include <errno.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include "string-extra.h"
28#include "debug.h"
29
30#include "misc.h"
31#include "plugin.h"
32#include "dir.h"
33#include "tree.h"
34#include "fileop.h"
35#include "pathfuncs.h"
36
37#include "settings.h"
38#include "lang.h"
39#include "yesno.h"
40#include "splash.h"
41#include "keyboard.h"
42
43/* Used for directory move, copy and delete */
44struct file_op_params
45{
46 char path[MAX_PATH]; /* Buffer for full path */
47 bool is_dir;
48 int objects; /* how many files and subdirectories*/
49 int processed;
50 size_t append; /* Append position in 'path' for stack push */
51};
52
53static bool poll_cancel_action(const char *path, int operation, int current, int total)
54{
55 const char *op_str = "";
56
57 clear_screen_buffer(false);
58
59 if (operation == FOC_COPY)
60 op_str = str(LANG_COPYING);
61 else if (operation == FOC_MOVE)
62 op_str = str(LANG_MOVING);
63 else if (operation == FOC_COUNT)
64 op_str = str(LANG_SCANNING_DISK);
65 else if (operation == FOC_DELETE)
66 op_str = str(LANG_DELETING);
67
68 path_basename(path, &path);
69
70 if (total <= 0)
71 splashf(0, "%s (%d) %s", op_str, current, path);
72 else
73 splash_progress(current, total, "%s %s", op_str, path);
74
75 return ACTION_STD_CANCEL == get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
76}
77
78static struct file_op_params* init_file_op(struct file_op_params *param,
79 const char *basename,
80 const char *selected_file)
81{
82 /* Assumes: basename will never be NULL */
83 if (selected_file == NULL)
84 {
85 param->append = strlcpy(param->path, basename, sizeof (param->path));
86 }
87 else
88 param->append = path_append(param->path, basename,
89 selected_file, sizeof (param->path));
90 param->is_dir = dir_exists(param->path);
91 param->objects = 0; /* how many files and subdirectories*/
92 param->processed = 0;
93
94 return param;
95}
96
97/* counts file objects, deletes file objects */
98static int directory_fileop(struct file_op_params *param, enum file_op_current fileop)
99{
100 errno = 0;
101 DIR *dir = opendir(param->path);
102 if (!dir) {
103 if (errno == EMFILE) {
104 return FORC_TOO_MANY_SUBDIRS;
105 }
106 return FORC_PATH_NOT_EXIST; /* open error */
107 }
108 int rc = FORC_SUCCESS;
109 size_t append = param->append;
110
111 /* walk through the directory content */
112 while (rc == FORC_SUCCESS) {
113 errno = 0; /* distinguish failure from eod */
114 struct dirent *entry = readdir(dir);
115 if (!entry) {
116 if (errno) {
117 rc = FORC_PATH_NOT_EXIST;
118 }
119 break;
120 }
121
122 struct dirinfo info = dir_get_info(dir, entry);
123 if ((info.attribute & ATTR_DIRECTORY) &&
124 is_dotdir_name(entry->d_name)) {
125 continue; /* skip these */
126 }
127
128 /* append name to current directory */
129 param->append = append + path_append(&param->path[append],
130 PA_SEP_HARD, entry->d_name,
131 sizeof (param->path) - append);
132
133 if (fileop == FOC_DELETE)
134 {
135 param->processed++;
136 /* at this point we've already counted and never had a path too long
137 in the other branch so we 'should not' encounter one here either */
138
139 if (param->processed > param->objects)
140 {
141 rc = FORC_UNKNOWN_FAILURE;
142 break;
143 }
144
145 if (info.attribute & ATTR_DIRECTORY) {
146 /* remove a subdirectory */
147 rc = directory_fileop(param, fileop);
148 } else {
149 /* remove a file */
150 if (poll_cancel_action(param->path, FOC_DELETE, param->processed, param->objects))
151 {
152 rc = FORC_CANCELLED;
153 break;
154 }
155 rc = remove(param->path);
156 }
157 }
158 else /* count objects */
159 {
160 param->objects++;
161
162 if (param->append >= sizeof (param->path)) {
163 rc = FORC_PATH_TOO_LONG;
164 break; /* no space left in buffer */
165 }
166
167 if (info.attribute & ATTR_DIRECTORY) {
168 /* remove a subdirectory */
169 rc = directory_fileop(param, FOC_COUNT);
170 } else {
171 if (poll_cancel_action(param->path, FOC_COUNT, param->objects, 0))
172 {
173 rc = FORC_CANCELLED;
174 break;
175 }
176 }
177 }
178 param->append = append; /* other functions may use param, reset append */
179 /* Remove basename we added above */
180 param->path[append] = '\0';
181 }
182
183 closedir(dir);
184
185 if (fileop == FOC_DELETE && rc == FORC_SUCCESS) {
186 /* remove the now empty directory */
187 if (poll_cancel_action(param->path, FOC_DELETE, param->processed, param->objects))
188 {
189 rc = FORC_CANCELLED;
190 } else {
191 rc = rmdir(param->path);
192 }
193 }
194
195 return rc;
196}
197
198static int check_count_fileobjects(struct file_op_params *param)
199{
200 int rc = directory_fileop(param, FOC_COUNT);
201 DEBUGF("%s res:(%d) objects %d \n", __func__, rc, param->objects);
202 return rc;
203}
204
205static bool check_new_name(const char *basename)
206{
207 /* at least prevent escapes out of the base directory from keyboard-
208 entered filenames; the file code should reject other invalidities */
209 return *basename != '\0' && !strchr(basename, PATH_SEPCH) &&
210 !is_dotdir_name(basename);
211}
212
213int create_dir(void)
214{
215 int rc = FORC_UNKNOWN_FAILURE;
216 char dirname[MAX_PATH];
217 size_t pathlen = path_append(dirname, getcwd(NULL, 0), PA_SEP_HARD,
218 sizeof (dirname));
219 char *basename = dirname + pathlen;
220
221 if (pathlen >= sizeof (dirname)) {
222 /* Too long */
223 } else if (kbd_input(basename, sizeof (dirname) - pathlen, NULL) < 0) {
224 rc = FORC_CANCELLED;
225 } else if (check_new_name(basename)) {
226 rc = mkdir(dirname);
227 }
228
229 return rc;
230}
231
232/************************************************************************************/
233/* share code for file and directory deletion, saves space */
234static int delete_file_dir(struct file_op_params *param)
235{
236 /* Note: delete_file_dir() will happily delete whatever
237 * path is passed (after confirmation) */
238 if (confirm_delete_yesno(param->path) != YESNO_YES) {
239 return FORC_CANCELLED;
240 }
241
242 clear_screen_buffer(true);
243 poll_cancel_action(param->path, FOC_DELETE, param->processed, param->objects);
244
245 int rc = FORC_UNKNOWN_FAILURE;
246
247 if (param->is_dir) { /* if directory */
248 cpu_boost(true);
249 rc = directory_fileop(param, FOC_DELETE);
250 cpu_boost(false);
251 } else {
252 rc = remove(param->path);
253 }
254
255 return rc;
256}
257
258int delete_fileobject(const char *selected_file)
259{
260 struct file_op_params param;
261 if (init_file_op(&param, selected_file, NULL)->is_dir == true)
262 {
263 int rc = check_count_fileobjects(&param);
264 DEBUGF("%s res: %d, ct: %d, %s", __func__, rc, param.objects, param.path);
265 if (rc != FORC_SUCCESS)
266 return rc;
267 }
268
269 return delete_file_dir(&param);
270}
271
272int rename_file(const char *selected_file)
273{
274 int rc = FORC_UNKNOWN_FAILURE;
275 char newname[MAX_PATH];
276 const char *oldbase, *selection = selected_file;
277
278 path_basename(selection, &oldbase);
279 size_t pathlen = oldbase - selection;
280 char *newbase = newname + pathlen;
281
282 if (strmemccpy(newname, selection, sizeof (newname)) == NULL) {
283 /* Too long */
284 } else if (kbd_input(newbase, sizeof (newname) - pathlen, NULL) < 0) {
285 rc = FORC_CANCELLED;
286 } else if (!strcmp(oldbase, newbase)) {
287 rc = FORC_NOOP; /* No change at all */
288 } else if (check_new_name(newbase)) {
289 switch (relate(selection, newname))
290 {
291 case RELATE_DIFFERENT:
292 if (file_exists(newname)) {
293 break; /* don't overwrite */
294 }
295 /* Fall-through */
296 case RELATE_SAME:
297 rc = rename(selection, newname);
298 break;
299 case RELATE_PREFIX:
300 default:
301 break;
302 }
303 }
304
305 return rc;
306}
307
308static int move_by_rename(const char *src_path,
309 const char *dst_path,
310 unsigned int *pflags)
311{
312 unsigned int flags = *pflags;
313 int rc = FORC_UNKNOWN_FAILURE;
314 while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
315 if ((flags & PASTE_OVERWRITE) || !file_exists(dst_path)) {
316 /* Just try to move the directory / file */
317 if (poll_cancel_action(src_path, FOC_MOVE, 0 , 0)) {
318 rc = FORC_CANCELLED;
319 } else {
320 rc = rename(src_path, dst_path);
321 }
322
323 if (rc < 0) {
324 int errnum = errno;
325 if (errnum == ENOTEMPTY && (flags & PASTE_OVERWRITE)) {
326 /* Directory is not empty thus rename() will not do a quick
327 overwrite */
328 break;
329 }
330 #ifdef HAVE_MULTIVOLUME
331 else if (errnum == EXDEV) {
332 /* Failed because cross volume rename doesn't work; force
333 a move instead */
334 *pflags |= PASTE_EXDEV;
335 break;
336 }
337 #endif /* HAVE_MULTIVOLUME */
338 }
339 }
340
341 break;
342 }
343 return rc;
344}
345
346/* Paste a file */
347static int copy_move_file(const char *src_path, const char *dst_path, unsigned int flags)
348{
349 /* Try renaming first */
350 int rc = move_by_rename(src_path, dst_path, &flags);
351 if (rc == FORC_SUCCESS)
352 return rc;
353
354 /* See if we can get the plugin buffer for the file copy buffer */
355 size_t buffersize;
356 char *buffer = (char *) plugin_get_buffer(&buffersize);
357 if (buffer == NULL || buffersize < 512) {
358 /* Not large enough, try for a disk sector worth of stack
359 instead */
360 buffersize = 512;
361 buffer = (char *)alloca(buffersize);
362 }
363
364 if (buffer == NULL) {
365 return FORC_NO_BUFFER_AVAIL;
366 }
367
368 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
369 size */
370
371 int src_fd = open(src_path, O_RDONLY);
372 if (src_fd >= 0) {
373 off_t src_sz = lseek(src_fd, 0, SEEK_END);
374 lseek(src_fd, 0, SEEK_SET);
375
376 int oflag = O_WRONLY|O_CREAT;
377
378 if (!(flags & PASTE_OVERWRITE)) {
379 oflag |= O_EXCL;
380 }
381
382 int dst_fd = open(dst_path, oflag, 0666);
383 if (dst_fd >= 0) {
384 off_t total_size = 0;
385 off_t next_cancel_test = 0; /* No excessive button polling */
386
387 rc = FORC_SUCCESS;
388
389 while (rc == FORC_SUCCESS) {
390 if (total_size >= next_cancel_test) {
391 next_cancel_test = total_size + 0x10000;
392 if (poll_cancel_action(src_path,
393 !(flags & PASTE_COPY) ? FOC_MOVE : FOC_COPY,
394 total_size, src_sz))
395 {
396 rc = FORC_CANCELLED;
397 break;
398 }
399 }
400
401 ssize_t bytesread = read(src_fd, buffer, buffersize);
402 if (bytesread <= 0) {
403 if (bytesread < 0) {
404 rc = FORC_READ_FAILURE;
405 }
406 /* else eof on buffer boundary; nothing to write */
407 break;
408 }
409
410 ssize_t byteswritten = write(dst_fd, buffer, bytesread);
411 if (byteswritten < bytesread) {
412 /* Some I/O error */
413 rc = FORC_WRITE_FAILURE;
414 break;
415 }
416
417 total_size += byteswritten;
418
419 if (bytesread < (ssize_t)buffersize) {
420 /* EOF with trailing bytes */
421 break;
422 }
423 }
424
425 if (rc == FORC_SUCCESS) {
426 /* If overwriting, set the correct length if original was
427 longer */
428 rc = ftruncate(dst_fd, total_size) * 10;
429 }
430
431 close(dst_fd);
432
433 if (rc != FORC_SUCCESS) {
434 /* Copy failed. Cleanup. */
435 remove(dst_path);
436 }
437 }
438
439 close(src_fd);
440 }
441
442 if (rc == FORC_SUCCESS && !(flags & PASTE_COPY)) {
443 /* Remove the source file */
444 rc = remove(src_path) * 10;
445 }
446
447 return rc;
448}
449
450/* Paste a directory */
451static int copy_move_directory(struct file_op_params *src,
452 struct file_op_params *dst,
453 unsigned int flags)
454{
455 int rc = FORC_UNKNOWN_FAILURE;
456
457 DIR *srcdir = opendir(src->path);
458
459 if (srcdir) {
460 /* Make a directory to copy things to */
461 rc = mkdir(dst->path) * 10;
462 if (rc < 0 && errno == EEXIST && (flags & PASTE_OVERWRITE)) {
463 /* Exists and overwrite was approved */
464 rc = FORC_SUCCESS;
465 }
466 }
467
468 size_t srcap = src->append, dstap = dst->append;
469
470 /* Walk through the directory content; this loop will exit as soon as
471 there's a problem */
472 while (rc == FORC_SUCCESS) {
473 errno = 0; /* Distinguish failure from eod */
474 struct dirent *entry = readdir(srcdir);
475 if (!entry) {
476 if (errno) {
477 rc = FORC_PATH_NOT_EXIST;
478 }
479 break;
480 }
481
482 struct dirinfo info = dir_get_info(srcdir, entry);
483 if ((info.attribute & ATTR_DIRECTORY) &&
484 is_dotdir_name(entry->d_name)) {
485 continue; /* Skip these */
486 }
487
488 /* Append names to current directories */
489 src->append = srcap +
490 path_append(&src->path[srcap], PA_SEP_HARD, entry->d_name,
491 sizeof(src->path) - srcap);
492
493 dst->append = dstap +
494 path_append(&dst->path[dstap], PA_SEP_HARD, entry->d_name,
495 sizeof (dst->path) - dstap);
496 /* src length was already checked by check_count_fileobjects() */
497 if (dst->append >= sizeof (dst->path)) {
498 rc = FORC_PATH_TOO_LONG; /* No space left in buffer */
499 break;
500 }
501
502 src->processed++;
503 if (src->processed > src->objects)
504 {
505 rc = FORC_UNKNOWN_FAILURE;
506 break;
507 }
508
509 if (poll_cancel_action(src->path,
510 !(flags & PASTE_COPY) ? FOC_MOVE : FOC_COPY,
511 src->processed, src->objects))
512 {
513 rc = FORC_CANCELLED;
514 break;
515 }
516
517 DEBUGF("Copy %s to %s\n", src->path, dst->path);
518
519 if (info.attribute & ATTR_DIRECTORY) {
520 /* Copy/move a subdirectory */
521 rc = copy_move_directory(src, dst, flags); /* recursion */;
522 } else {
523 /* Copy/move a file */
524 rc = copy_move_file(src->path, dst->path, flags);
525 }
526
527 /* Remove basenames we added above */
528 src->path[srcap] = '\0';
529 dst->path[dstap] = '\0';
530 }
531
532 if (rc == FORC_SUCCESS && !(flags & PASTE_COPY)) {
533 /* Remove the now empty directory */
534 rc = rmdir(src->path) * 10;
535 }
536
537 closedir(srcdir);
538 return rc;
539}
540
541int copy_move_fileobject(const char *src_path, const char *dst_path, unsigned int flags)
542{
543 if (!src_path[0])
544 return FORC_NOOP;
545
546 int rc = FORC_UNKNOWN_FAILURE;
547
548 struct file_op_params src, dst;
549
550 /* Figure out the name of the selection */
551 const char *nameptr;
552 path_basename(src_path, &nameptr);
553
554 /* Final target is current directory plus name of selection */
555 init_file_op(&dst, dst_path, nameptr);
556
557 switch (dst.append < sizeof (dst.path) ?
558 relate(src_path, dst.path) : FORC_PATH_TOO_LONG)
559 {
560 case RELATE_SAME:
561 rc = FORC_NOOP;
562 break;
563
564 case RELATE_DIFFERENT:
565 if (file_exists(dst.path)) {
566 /* If user chooses not to overwrite, cancel */
567 if (confirm_overwrite_yesno() == YESNO_NO) {
568 rc = FORC_NOOVERWRT;
569 break;
570 }
571
572 flags |= PASTE_OVERWRITE;
573 }
574
575 /* Now figure out what we're doing */
576 cpu_boost(true);
577
578 init_file_op(&src, src_path, NULL);
579
580 if (src.is_dir) {
581 /* Copy or move a subdirectory */
582
583 if (src.append < sizeof (src.path)) {
584 /* Try renaming first */
585 rc = move_by_rename(src.path, dst.path, &flags);
586 if (rc != FORC_SUCCESS && rc != FORC_CANCELLED) {
587 if (check_count_fileobjects(&src) == FORC_SUCCESS) {
588 rc = copy_move_directory(&src, &dst, flags);
589 }
590 }
591 }
592 } else {
593 /* Copy or move a file */
594 rc = copy_move_file(src_path, dst.path, flags);
595 }
596
597 cpu_boost(false);
598 break;
599
600 case RELATE_PREFIX:
601 default: /* Some other relation / failure */
602 break;
603 }
604
605 return rc;
606}