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