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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since b14d9f9 was a6fc88a, checked in by Jakub Jermar <jakub@…>, 8 years ago

Let vfs_link() and vfs_link_path() return the linked file handle

Add an output argument to both vfs_link() and vfs_link_path() so that the
client may receive the file handle of the linked file or directory. This
makes it easy to work with the new file or directory as it is ready to be
opened or in case of a directory to use it as a parent for further
operations without the need for additional vfs_lookup().

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