source: mainline/uspace/app/bdsh/cmds/modules/cp/cp.c@ 9af1c61

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 9af1c61 was 8e3498b, checked in by Jiri Svoboda <jiri@…>, 8 years ago

vfs_read/write() should return error code separately from number of bytes transferred.

  • Property mode set to 100644
File size: 12.5 KB
Line 
1/*
2 * Copyright (c) 2008 Tim Post
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <errno.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <io/console.h>
33#include <io/keycode.h>
34#include <getopt.h>
35#include <str.h>
36#include <vfs/vfs.h>
37#include <dirent.h>
38#include "config.h"
39#include "util.h"
40#include "errors.h"
41#include "entry.h"
42#include "cp.h"
43#include "cmds.h"
44
45#define CP_VERSION "0.0.1"
46#define CP_DEFAULT_BUFLEN 1024
47
48static const char *cmdname = "cp";
49static console_ctrl_t *con;
50
51static struct option const long_options[] = {
52 { "buffer", required_argument, 0, 'b' },
53 { "force", no_argument, 0, 'f' },
54 { "interactive", no_argument, 0, 'i'},
55 { "recursive", no_argument, 0, 'r' },
56 { "help", no_argument, 0, 'h' },
57 { "version", no_argument, 0, 'v' },
58 { "verbose", no_argument, 0, 'V' },
59 { 0, 0, 0, 0 }
60};
61
62typedef enum {
63 TYPE_NONE,
64 TYPE_FILE,
65 TYPE_DIR
66} dentry_type_t;
67
68static int copy_file(const char *src, const char *dest,
69 size_t blen, int vb);
70
71/** Get the type of a directory entry.
72 *
73 * @param path Path of the directory entry.
74 *
75 * @return TYPE_DIR if the dentry is a directory.
76 * @return TYPE_FILE if the dentry is a file.
77 * @return TYPE_NONE if the dentry does not exists.
78 */
79static dentry_type_t get_type(const char *path)
80{
81 struct stat s;
82
83 int r = vfs_stat_path(path, &s);
84
85 if (r != EOK)
86 return TYPE_NONE;
87 else if (s.is_directory)
88 return TYPE_DIR;
89 else if (s.is_file)
90 return TYPE_FILE;
91
92 return TYPE_NONE;
93}
94
95static int strtoint(const char *s1)
96{
97 long t1;
98
99 if (-1 == (t1 = strtol(s1, (char **) NULL, 10)))
100 return -1;
101
102 if (t1 <= 0)
103 return -1;
104
105 return (int) t1;
106}
107
108/** Get the last component of a path.
109 *
110 * e.g. /data/a ---> a
111 *
112 * @param path Pointer to the path.
113 *
114 * @return Pointer to the last component or to the path itself.
115 */
116static char *get_last_path_component(char *path)
117{
118 char *ptr;
119
120 ptr = str_rchr(path, '/');
121 if (!ptr)
122 return path;
123 else
124 return ptr + 1;
125}
126
127/** Merge two paths together.
128 *
129 * e.g. (path1 = /data/dir, path2 = a/b) --> /data/dir/a/b
130 *
131 * @param path1 Path to which path2 will be appended.
132 * @param path1_size Size of the path1 buffer.
133 * @param path2 Path that will be appended to path1.
134 */
135static void merge_paths(char *path1, size_t path1_size, char *path2)
136{
137 const char *delim = "/";
138
139 str_rtrim(path1, '/');
140 str_append(path1, path1_size, delim);
141 str_append(path1, path1_size, path2);
142}
143
144static bool get_user_decision(bool bdefault, const char *message, ...)
145{
146 va_list args;
147
148 va_start(args, message);
149 vprintf(message, args);
150 va_end(args);
151
152 while (true) {
153 cons_event_t ev;
154 console_flush(con);
155 console_get_event(con, &ev);
156 if (ev.type != CEV_KEY || ev.ev.key.type != KEY_PRESS ||
157 (ev.ev.key.mods & (KM_CTRL | KM_ALT)) != 0) {
158 continue;
159 }
160
161 switch(ev.ev.key.key) {
162 case KC_Y:
163 printf("y\n");
164 return true;
165 case KC_N:
166 printf("n\n");
167 return false;
168 case KC_ENTER:
169 printf("%c\n", bdefault ? 'Y' : 'N');
170 return bdefault;
171 default:
172 break;
173 }
174 }
175}
176
177static int do_copy(const char *src, const char *dest,
178 size_t blen, int vb, int recursive, int force, int interactive)
179{
180 int rc = EOK;
181 char dest_path[PATH_MAX];
182 char src_path[PATH_MAX];
183 DIR *dir = NULL;
184 struct dirent *dp;
185
186 dentry_type_t src_type = get_type(src);
187 dentry_type_t dest_type = get_type(dest);
188
189 const size_t src_len = str_size(src);
190
191 if (src_type == TYPE_FILE) {
192 char *src_fname;
193
194 /* Initialize the src_path with the src argument */
195 str_cpy(src_path, src_len + 1, src);
196 str_rtrim(src_path, '/');
197
198 /* Get the last component name from the src path */
199 src_fname = get_last_path_component(src_path);
200
201 /* Initialize dest_path with the dest argument */
202 str_cpy(dest_path, PATH_MAX, dest);
203
204 if (dest_type == TYPE_DIR) {
205 /* e.g. cp file_name /data */
206 /* e.g. cp file_name /data/ */
207
208 /* dest is a directory,
209 * append the src filename to it.
210 */
211 merge_paths(dest_path, PATH_MAX, src_fname);
212 dest_type = get_type(dest_path);
213 } else if (dest_type == TYPE_NONE) {
214 if (dest_path[str_size(dest_path) - 1] == '/') {
215 /* e.g. cp /textdemo /data/dirnotexists/ */
216
217 printf("The dest directory %s does not exists",
218 dest_path);
219 rc = ENOENT;
220 goto exit;
221 }
222 }
223
224 if (dest_type == TYPE_DIR) {
225 printf("Cannot overwrite existing directory %s\n",
226 dest_path);
227 rc = EEXIST;
228 goto exit;
229 } else if (dest_type == TYPE_FILE) {
230 /* e.g. cp file_name existing_file */
231
232 /* dest already exists,
233 * if force is set we will try to remove it.
234 * if interactive is set user input is required.
235 */
236 if (force && !interactive) {
237 rc = vfs_unlink_path(dest_path);
238 if (rc != EOK) {
239 printf("Unable to remove %s\n",
240 dest_path);
241 goto exit;
242 }
243 } else if (!force && interactive) {
244 bool overwrite = get_user_decision(false,
245 "File already exists: %s. Overwrite? [y/N]: ",
246 dest_path);
247 if (overwrite) {
248 printf("Overwriting file: %s\n", dest_path);
249 rc = vfs_unlink_path(dest_path);
250 if (rc != EOK) {
251 printf("Unable to remove %s\n", dest_path);
252 goto exit;
253 }
254 } else {
255 printf("Not overwriting file: %s\n", dest_path);
256 rc = EOK;
257 goto exit;
258 }
259 } else {
260 printf("File already exists: %s\n", dest_path);
261 rc = EEXIST;
262 goto exit;
263 }
264 }
265
266 /* call copy_file and exit */
267 rc = (copy_file(src, dest_path, blen, vb) < 0);
268
269 } else if (src_type == TYPE_DIR) {
270 /* e.g. cp -r /x/srcdir /y/destdir/ */
271
272 if (!recursive) {
273 printf("Cannot copy the %s directory without the "
274 "-r option\n", src);
275 rc = EINVAL;
276 goto exit;
277 } else if (dest_type == TYPE_FILE) {
278 printf("Cannot overwrite a file with a directory\n");
279 rc = EEXIST;
280 goto exit;
281 }
282
283 char *src_dirname;
284
285 /* Initialize src_path with the content of src */
286 str_cpy(src_path, src_len + 1, src);
287 str_rtrim(src_path, '/');
288
289 src_dirname = get_last_path_component(src_path);
290
291 str_cpy(dest_path, PATH_MAX, dest);
292
293 switch (dest_type) {
294 case TYPE_DIR:
295 if (str_cmp(src_dirname, "..") &&
296 str_cmp(src_dirname, ".")) {
297 /* The last component of src_path is
298 * not '.' or '..'
299 */
300 merge_paths(dest_path, PATH_MAX, src_dirname);
301
302 rc = vfs_link_path(dest_path, KIND_DIRECTORY,
303 NULL);
304 if (rc != EOK) {
305 printf("Unable to create "
306 "dest directory %s\n", dest_path);
307 goto exit;
308 }
309 }
310 break;
311 default:
312 case TYPE_NONE:
313 /* dest does not exists, this means the user wants
314 * to specify the name of the destination directory
315 *
316 * e.g. cp -r /src /data/new_dir_src
317 */
318 rc = vfs_link_path(dest_path, KIND_DIRECTORY, NULL);
319 if (rc != EOK) {
320 printf("Unable to create "
321 "dest directory %s\n", dest_path);
322 goto exit;
323 }
324 break;
325 }
326
327 dir = opendir(src);
328 if (!dir) {
329 /* Something strange is happening... */
330 printf("Unable to open src %s directory\n", src);
331 rc = ENOENT;
332 goto exit;
333 }
334
335 /* Copy every single directory entry of src into the
336 * destination directory.
337 */
338 while ((dp = readdir(dir))) {
339 struct stat src_s;
340 struct stat dest_s;
341
342 char src_dent[PATH_MAX];
343 char dest_dent[PATH_MAX];
344
345 str_cpy(src_dent, PATH_MAX, src);
346 merge_paths(src_dent, PATH_MAX, dp->d_name);
347
348 str_cpy(dest_dent, PATH_MAX, dest_path);
349 merge_paths(dest_dent, PATH_MAX, dp->d_name);
350
351 /* Check if we are copying a directory into itself */
352 vfs_stat_path(src_dent, &src_s);
353 vfs_stat_path(dest_path, &dest_s);
354
355 if (dest_s.index == src_s.index &&
356 dest_s.fs_handle == src_s.fs_handle) {
357 printf("Cannot copy a directory "
358 "into itself\n");
359 rc = EEXIST;
360 goto exit;
361 }
362
363 if (vb)
364 printf("copy %s %s\n", src_dent, dest_dent);
365
366 /* Recursively call do_copy() */
367 rc = do_copy(src_dent, dest_dent, blen, vb, recursive,
368 force, interactive);
369 if (rc != EOK)
370 goto exit;
371
372 }
373 } else
374 printf("Unable to open source file %s\n", src);
375
376exit:
377 if (dir)
378 closedir(dir);
379 return rc;
380}
381
382static int copy_file(const char *src, const char *dest,
383 size_t blen, int vb)
384{
385 int fd1, fd2;
386 size_t rbytes, wbytes;
387 int rc;
388 off64_t total;
389 char *buff = NULL;
390 aoff64_t posr = 0, posw = 0;
391 struct stat st;
392
393 if (vb)
394 printf("Copying %s to %s\n", src, dest);
395
396 fd1 = vfs_lookup_open(src, WALK_REGULAR, MODE_READ);
397 if (fd1 < 0) {
398 printf("Unable to open source file %s\n", src);
399 return -1;
400 }
401
402 fd2 = vfs_lookup_open(dest, WALK_REGULAR | WALK_MAY_CREATE, MODE_WRITE);
403 if (fd2 < 0) {
404 printf("Unable to open destination file %s\n", dest);
405 vfs_put(fd1);
406 return -1;
407 }
408
409 if (vfs_stat(fd1, &st) != EOK) {
410 printf("Unable to fstat %d\n", fd1);
411 vfs_put(fd1);
412 vfs_put(fd2);
413 return -1;
414 }
415
416 total = st.size;
417 if (vb)
418 printf("%" PRIu64 " bytes to copy\n", total);
419
420 if (NULL == (buff = (char *) malloc(blen))) {
421 printf("Unable to allocate enough memory to read %s\n",
422 src);
423 rc = ENOMEM;
424 goto out;
425 }
426
427 while ((rc = vfs_read(fd1, &posr, buff, blen, &rbytes)) == EOK &&
428 rbytes > 0) {
429 if ((rc = vfs_write(fd2, &posw, buff, rbytes, &wbytes)) != EOK)
430 break;
431 }
432
433 if (rc != EOK) {
434 printf("\nError copying %s, (%d)\n", src, rc);
435 return rc;
436 }
437
438out:
439 vfs_put(fd1);
440 vfs_put(fd2);
441 if (buff)
442 free(buff);
443 return rc;
444}
445
446void help_cmd_cp(unsigned int level)
447{
448 static char helpfmt[] =
449 "Usage: %s [options] <source> <dest>\n"
450 "Options:\n"
451 " -h, --help A short option summary\n"
452 " -v, --version Print version information and exit\n"
453 " -V, --verbose Be annoyingly noisy about what's being done\n"
454 " -f, --force Do not complain when <dest> exists (overrides a previous -i)\n"
455 " -i, --interactive Ask what to do when <dest> exists (overrides a previous -f)\n"
456 " -r, --recursive Copy entire directories\n"
457 " -b, --buffer ## Set the read buffer size to ##\n";
458 if (level == HELP_SHORT) {
459 printf("`%s' copies files and directories\n", cmdname);
460 } else {
461 help_cmd_cp(HELP_SHORT);
462 printf(helpfmt, cmdname);
463 }
464
465 return;
466}
467
468int cmd_cp(char **argv)
469{
470 unsigned int argc, verbose = 0;
471 int buffer = 0, recursive = 0;
472 int force = 0, interactive = 0;
473 int c, opt_ind;
474 int64_t ret;
475
476 con = console_init(stdin, stdout);
477 argc = cli_count_args(argv);
478
479 for (c = 0, optreset = 1, optind = 0, opt_ind = 0; c != -1;) {
480 c = getopt_long(argc, argv, "hvVfirb:", long_options, &opt_ind);
481 switch (c) {
482 case 'h':
483 help_cmd_cp(1);
484 return CMD_SUCCESS;
485 case 'v':
486 printf("%s\n", CP_VERSION);
487 return CMD_SUCCESS;
488 case 'V':
489 verbose = 1;
490 break;
491 case 'f':
492 interactive = 0;
493 force = 1;
494 break;
495 case 'i':
496 force = 0;
497 interactive = 1;
498 break;
499 case 'r':
500 recursive = 1;
501 break;
502 case 'b':
503 if (-1 == (buffer = strtoint(optarg))) {
504 printf("%s: Invalid buffer specification, "
505 "(should be a number greater than zero)\n",
506 cmdname);
507 console_done(con);
508 return CMD_FAILURE;
509 }
510 if (verbose)
511 printf("Buffer = %d\n", buffer);
512 break;
513 }
514 }
515
516 if (buffer == 0)
517 buffer = CP_DEFAULT_BUFLEN;
518
519 argc -= optind;
520
521 if (argc != 2) {
522 printf("%s: invalid number of arguments. Try %s --help\n",
523 cmdname, cmdname);
524 console_done(con);
525 return CMD_FAILURE;
526 }
527
528 ret = do_copy(argv[optind], argv[optind + 1], buffer, verbose,
529 recursive, force, interactive);
530
531 console_done(con);
532
533 if (ret == 0)
534 return CMD_SUCCESS;
535 else
536 return CMD_FAILURE;
537}
538
Note: See TracBrowser for help on using the repository browser.