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

Last change on this file was 87822ce, checked in by Jiri Svoboda <jiri@…>, 4 years ago

Avoid infinite loop when console communication is broken

Need to make sure callers of console_get_event_timeout() can distinguish
between timeout and I/O error. Fix all callers of console_get_event()
and console_get_event_timeout() not to enter infinite loop when console
connection is broken. Also avoid setting of errno variable.

  • Property mode set to 100644
File size: 12.7 KB
RevLine 
[36ab7c7]1/*
2 * Copyright (c) 2008 Tim Post
[39463ff]3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
[36ab7c7]6 * modification, are permitted provided that the following conditions
7 * are met:
[39463ff]8 *
[36ab7c7]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.
[39463ff]16 *
[36ab7c7]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.
[39463ff]27 */
28
[6afc9d7]29#include <errno.h>
[dd8ab1c]30#include <str_error.h>
[39463ff]31#include <stdio.h>
32#include <stdlib.h>
[e367f5b8]33#include <io/console.h>
34#include <io/keycode.h>
[2640b6c]35#include <getopt.h>
[19f857a]36#include <str.h>
[23a0368]37#include <vfs/vfs.h>
[1737bfb]38#include <dirent.h>
[39463ff]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
[2640b6c]46#define CP_VERSION "0.0.1"
47#define CP_DEFAULT_BUFLEN 1024
48
49static const char *cmdname = "cp";
[e367f5b8]50static console_ctrl_t *con;
[2640b6c]51
52static struct option const long_options[] = {
53 { "buffer", required_argument, 0, 'b' },
54 { "force", no_argument, 0, 'f' },
[1433ecda]55 { "interactive", no_argument, 0, 'i' },
[2640b6c]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
[1737bfb]63typedef enum {
64 TYPE_NONE,
65 TYPE_FILE,
66 TYPE_DIR
67} dentry_type_t;
68
[8e3498b]69static int copy_file(const char *src, const char *dest,
[1737bfb]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{
[39330200]82 vfs_stat_t s;
[1737bfb]83
[b7fd2a0]84 errno_t r = vfs_stat_path(path, &s);
[1737bfb]85
[23a0368]86 if (r != EOK)
[1737bfb]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
[2640b6c]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
[1737bfb]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
[e367f5b8]145static bool get_user_decision(bool bdefault, const char *message, ...)
146{
147 va_list args;
[87822ce]148 errno_t rc;
[e367f5b8]149
150 va_start(args, message);
151 vprintf(message, args);
152 va_end(args);
153
154 while (true) {
[07b7c48]155 cons_event_t ev;
[e367f5b8]156 console_flush(con);
[87822ce]157 rc = console_get_event(con, &ev);
158 if (rc != EOK)
159 exit(1);
[07b7c48]160 if (ev.type != CEV_KEY || ev.ev.key.type != KEY_PRESS ||
161 (ev.ev.key.mods & (KM_CTRL | KM_ALT)) != 0) {
[e367f5b8]162 continue;
163 }
164
[1433ecda]165 switch (ev.ev.key.key) {
[e367f5b8]166 case KC_Y:
167 printf("y\n");
168 return true;
169 case KC_N:
170 printf("n\n");
171 return false;
172 case KC_ENTER:
173 printf("%c\n", bdefault ? 'Y' : 'N');
174 return bdefault;
175 default:
176 break;
177 }
178 }
179}
180
[b7fd2a0]181static errno_t do_copy(const char *src, const char *dest,
[e367f5b8]182 size_t blen, int vb, int recursive, int force, int interactive)
[1737bfb]183{
[b7fd2a0]184 errno_t rc = EOK;
[1737bfb]185 char dest_path[PATH_MAX];
186 char src_path[PATH_MAX];
187 DIR *dir = NULL;
188 struct dirent *dp;
189
190 dentry_type_t src_type = get_type(src);
191 dentry_type_t dest_type = get_type(dest);
192
193 const size_t src_len = str_size(src);
194
195 if (src_type == TYPE_FILE) {
196 char *src_fname;
197
198 /* Initialize the src_path with the src argument */
199 str_cpy(src_path, src_len + 1, src);
200 str_rtrim(src_path, '/');
[a35b458]201
[1737bfb]202 /* Get the last component name from the src path */
203 src_fname = get_last_path_component(src_path);
[a35b458]204
[1737bfb]205 /* Initialize dest_path with the dest argument */
206 str_cpy(dest_path, PATH_MAX, dest);
207
208 if (dest_type == TYPE_DIR) {
209 /* e.g. cp file_name /data */
210 /* e.g. cp file_name /data/ */
[a35b458]211
[7c3fb9b]212 /*
213 * dest is a directory,
[1737bfb]214 * append the src filename to it.
215 */
216 merge_paths(dest_path, PATH_MAX, src_fname);
217 dest_type = get_type(dest_path);
218 } else if (dest_type == TYPE_NONE) {
219 if (dest_path[str_size(dest_path) - 1] == '/') {
220 /* e.g. cp /textdemo /data/dirnotexists/ */
221
222 printf("The dest directory %s does not exists",
223 dest_path);
[8e3498b]224 rc = ENOENT;
[1737bfb]225 goto exit;
226 }
227 }
228
229 if (dest_type == TYPE_DIR) {
230 printf("Cannot overwrite existing directory %s\n",
231 dest_path);
[8e3498b]232 rc = EEXIST;
[1737bfb]233 goto exit;
234 } else if (dest_type == TYPE_FILE) {
235 /* e.g. cp file_name existing_file */
236
[7c3fb9b]237 /*
238 * dest already exists,
[e367f5b8]239 * if force is set we will try to remove it.
240 * if interactive is set user input is required.
[1737bfb]241 */
[e367f5b8]242 if (force && !interactive) {
[8e3498b]243 rc = vfs_unlink_path(dest_path);
244 if (rc != EOK) {
[1737bfb]245 printf("Unable to remove %s\n",
246 dest_path);
247 goto exit;
248 }
[e367f5b8]249 } else if (!force && interactive) {
250 bool overwrite = get_user_decision(false,
251 "File already exists: %s. Overwrite? [y/N]: ",
252 dest_path);
253 if (overwrite) {
254 printf("Overwriting file: %s\n", dest_path);
[8e3498b]255 rc = vfs_unlink_path(dest_path);
256 if (rc != EOK) {
[e367f5b8]257 printf("Unable to remove %s\n", dest_path);
258 goto exit;
259 }
260 } else {
261 printf("Not overwriting file: %s\n", dest_path);
[8e3498b]262 rc = EOK;
[e367f5b8]263 goto exit;
264 }
[1737bfb]265 } else {
[e367f5b8]266 printf("File already exists: %s\n", dest_path);
[8e3498b]267 rc = EEXIST;
[1737bfb]268 goto exit;
269 }
270 }
271
272 /* call copy_file and exit */
[1569a9b]273 if (copy_file(src, dest_path, blen, vb) < 0) {
274 rc = EIO;
275 }
[1737bfb]276
277 } else if (src_type == TYPE_DIR) {
278 /* e.g. cp -r /x/srcdir /y/destdir/ */
279
280 if (!recursive) {
281 printf("Cannot copy the %s directory without the "
282 "-r option\n", src);
[8e3498b]283 rc = EINVAL;
[1737bfb]284 goto exit;
285 } else if (dest_type == TYPE_FILE) {
286 printf("Cannot overwrite a file with a directory\n");
[8e3498b]287 rc = EEXIST;
[1737bfb]288 goto exit;
289 }
290
291 char *src_dirname;
292
293 /* Initialize src_path with the content of src */
294 str_cpy(src_path, src_len + 1, src);
295 str_rtrim(src_path, '/');
296
297 src_dirname = get_last_path_component(src_path);
298
299 str_cpy(dest_path, PATH_MAX, dest);
300
301 switch (dest_type) {
302 case TYPE_DIR:
303 if (str_cmp(src_dirname, "..") &&
304 str_cmp(src_dirname, ".")) {
[7c3fb9b]305 /*
306 * The last component of src_path is
[1737bfb]307 * not '.' or '..'
308 */
309 merge_paths(dest_path, PATH_MAX, src_dirname);
310
[8e3498b]311 rc = vfs_link_path(dest_path, KIND_DIRECTORY,
312 NULL);
313 if (rc != EOK) {
[1737bfb]314 printf("Unable to create "
315 "dest directory %s\n", dest_path);
316 goto exit;
317 }
318 }
319 break;
320 default:
321 case TYPE_NONE:
[7c3fb9b]322 /*
323 * dest does not exists, this means the user wants
[1737bfb]324 * to specify the name of the destination directory
325 *
326 * e.g. cp -r /src /data/new_dir_src
327 */
[8e3498b]328 rc = vfs_link_path(dest_path, KIND_DIRECTORY, NULL);
329 if (rc != EOK) {
[1737bfb]330 printf("Unable to create "
331 "dest directory %s\n", dest_path);
332 goto exit;
333 }
334 break;
335 }
336
337 dir = opendir(src);
338 if (!dir) {
339 /* Something strange is happening... */
340 printf("Unable to open src %s directory\n", src);
[8e3498b]341 rc = ENOENT;
[1737bfb]342 goto exit;
343 }
344
[7c3fb9b]345 /*
346 * Copy every single directory entry of src into the
[1737bfb]347 * destination directory.
348 */
349 while ((dp = readdir(dir))) {
[39330200]350 vfs_stat_t src_s;
351 vfs_stat_t dest_s;
[1737bfb]352
353 char src_dent[PATH_MAX];
354 char dest_dent[PATH_MAX];
355
356 str_cpy(src_dent, PATH_MAX, src);
357 merge_paths(src_dent, PATH_MAX, dp->d_name);
358
359 str_cpy(dest_dent, PATH_MAX, dest_path);
360 merge_paths(dest_dent, PATH_MAX, dp->d_name);
361
362 /* Check if we are copying a directory into itself */
[23a0368]363 vfs_stat_path(src_dent, &src_s);
364 vfs_stat_path(dest_path, &dest_s);
[1737bfb]365
366 if (dest_s.index == src_s.index &&
367 dest_s.fs_handle == src_s.fs_handle) {
368 printf("Cannot copy a directory "
369 "into itself\n");
[8e3498b]370 rc = EEXIST;
[1737bfb]371 goto exit;
372 }
373
374 if (vb)
375 printf("copy %s %s\n", src_dent, dest_dent);
376
377 /* Recursively call do_copy() */
[8e3498b]378 rc = do_copy(src_dent, dest_dent, blen, vb, recursive,
[e367f5b8]379 force, interactive);
[8e3498b]380 if (rc != EOK)
[1737bfb]381 goto exit;
382
383 }
384 } else
385 printf("Unable to open source file %s\n", src);
386
387exit:
388 if (dir)
389 closedir(dir);
[8e3498b]390 return rc;
[1737bfb]391}
392
[8e3498b]393static int copy_file(const char *src, const char *dest,
[1433ecda]394 size_t blen, int vb)
[2640b6c]395{
[8e3498b]396 int fd1, fd2;
397 size_t rbytes, wbytes;
[b7fd2a0]398 errno_t rc;
[6348376]399 off64_t total;
[2640b6c]400 char *buff = NULL;
[58898d1d]401 aoff64_t posr = 0, posw = 0;
[39330200]402 vfs_stat_t st;
[2640b6c]403
404 if (vb)
405 printf("Copying %s to %s\n", src, dest);
406
[f77c1c9]407 rc = vfs_lookup_open(src, WALK_REGULAR, MODE_READ, &fd1);
408 if (rc != EOK) {
[2640b6c]409 printf("Unable to open source file %s\n", src);
[890793b]410 return -1;
[2640b6c]411 }
412
[f77c1c9]413 rc = vfs_lookup_open(dest, WALK_REGULAR | WALK_MAY_CREATE, MODE_WRITE, &fd2);
414 if (rc != EOK) {
[2640b6c]415 printf("Unable to open destination file %s\n", dest);
[9c4cf0d]416 vfs_put(fd1);
[890793b]417 return -1;
[2640b6c]418 }
419
[23a0368]420 if (vfs_stat(fd1, &st) != EOK) {
[58898d1d]421 printf("Unable to fstat %d\n", fd1);
[9c4cf0d]422 vfs_put(fd1);
423 vfs_put(fd2);
[1b20da0]424 return -1;
[58898d1d]425 }
[2640b6c]426
[58898d1d]427 total = st.size;
[2640b6c]428 if (vb)
[7e752b2]429 printf("%" PRIu64 " bytes to copy\n", total);
[2640b6c]430
[8df7a1c]431 if (NULL == (buff = (char *) malloc(blen))) {
[2640b6c]432 printf("Unable to allocate enough memory to read %s\n",
[8df7a1c]433 src);
[8e3498b]434 rc = ENOMEM;
[2640b6c]435 goto out;
436 }
437
[8e3498b]438 while ((rc = vfs_read(fd1, &posr, buff, blen, &rbytes)) == EOK &&
439 rbytes > 0) {
440 if ((rc = vfs_write(fd2, &posw, buff, rbytes, &wbytes)) != EOK)
[4c4ddbe9]441 break;
[8df7a1c]442 }
[2640b6c]443
[8e3498b]444 if (rc != EOK) {
[dd8ab1c]445 printf("\nError copying %s: %s\n", src, str_error(rc));
[1569a9b]446 return -1;
[2640b6c]447 }
448
449out:
[9c4cf0d]450 vfs_put(fd1);
451 vfs_put(fd2);
[2640b6c]452 if (buff)
453 free(buff);
[1569a9b]454 if (rc != EOK) {
455 return -1;
456 } else {
457 return 0;
458 }
[2640b6c]459}
[39463ff]460
461void help_cmd_cp(unsigned int level)
462{
[8df7a1c]463 static char helpfmt[] =
464 "Usage: %s [options] <source> <dest>\n"
[1737bfb]465 "Options:\n"
[8df7a1c]466 " -h, --help A short option summary\n"
467 " -v, --version Print version information and exit\n"
[1737bfb]468 " -V, --verbose Be annoyingly noisy about what's being done\n"
[e367f5b8]469 " -f, --force Do not complain when <dest> exists (overrides a previous -i)\n"
470 " -i, --interactive Ask what to do when <dest> exists (overrides a previous -f)\n"
[1737bfb]471 " -r, --recursive Copy entire directories\n"
472 " -b, --buffer ## Set the read buffer size to ##\n";
[2640b6c]473 if (level == HELP_SHORT) {
474 printf("`%s' copies files and directories\n", cmdname);
475 } else {
476 help_cmd_cp(HELP_SHORT);
[1737bfb]477 printf(helpfmt, cmdname);
[2640b6c]478 }
479
[39463ff]480 return;
481}
482
483int cmd_cp(char **argv)
484{
[5b3cf90]485 unsigned int argc, verbose = 0;
[1737bfb]486 int buffer = 0, recursive = 0;
[e367f5b8]487 int force = 0, interactive = 0;
[3b10e07b]488 int c, opt_ind;
[b7fd2a0]489 errno_t ret;
[2640b6c]490
[e367f5b8]491 con = console_init(stdin, stdout);
[2640b6c]492 argc = cli_count_args(argv);
[39463ff]493
[948222e4]494 c = 0;
495 optreset = 1;
496 optind = 0;
497 opt_ind = 0;
498
499 while (c != -1) {
[e367f5b8]500 c = getopt_long(argc, argv, "hvVfirb:", long_options, &opt_ind);
[1b20da0]501 switch (c) {
[2640b6c]502 case 'h':
503 help_cmd_cp(1);
504 return CMD_SUCCESS;
505 case 'v':
[7e752b2]506 printf("%s\n", CP_VERSION);
[2640b6c]507 return CMD_SUCCESS;
508 case 'V':
509 verbose = 1;
510 break;
511 case 'f':
[e367f5b8]512 interactive = 0;
[1737bfb]513 force = 1;
[2640b6c]514 break;
[e367f5b8]515 case 'i':
516 force = 0;
517 interactive = 1;
518 break;
[2640b6c]519 case 'r':
[1737bfb]520 recursive = 1;
[2640b6c]521 break;
522 case 'b':
523 if (-1 == (buffer = strtoint(optarg))) {
524 printf("%s: Invalid buffer specification, "
[8df7a1c]525 "(should be a number greater than zero)\n",
526 cmdname);
[e367f5b8]527 console_done(con);
[2640b6c]528 return CMD_FAILURE;
529 }
[4c4ddbe9]530 if (verbose)
531 printf("Buffer = %d\n", buffer);
[2640b6c]532 break;
533 }
534 }
[39463ff]535
[4c4ddbe9]536 if (buffer == 0)
537 buffer = CP_DEFAULT_BUFLEN;
538
[2640b6c]539 argc -= optind;
[39463ff]540
[2640b6c]541 if (argc != 2) {
542 printf("%s: invalid number of arguments. Try %s --help\n",
[8df7a1c]543 cmdname, cmdname);
[e367f5b8]544 console_done(con);
[2640b6c]545 return CMD_FAILURE;
[39463ff]546 }
547
[1737bfb]548 ret = do_copy(argv[optind], argv[optind + 1], buffer, verbose,
[e367f5b8]549 recursive, force, interactive);
550
551 console_done(con);
[2640b6c]552
[1737bfb]553 if (ret == 0)
[2640b6c]554 return CMD_SUCCESS;
555 else
556 return CMD_FAILURE;
[39463ff]557}
Note: See TracBrowser for help on using the repository browser.