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

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

Extend console library API to support different event types.

  • Property mode set to 100644
File size: 12.0 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 <stdio.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <io/console.h>
33#include <io/keycode.h>
34#include <getopt.h>
35#include <str.h>
36#include <fcntl.h>
37#include <sys/stat.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 = stat(path, &s);
85
86 if (r)
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 (unlink(dest_path)) {
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 (unlink(dest_path)) {
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 (mkdir(dest_path, 0) == -1) {
297 printf("Unable to create "
298 "dest directory %s\n", dest_path);
299 goto exit;
300 }
301 }
302 break;
303 default:
304 case TYPE_NONE:
305 /* dest does not exists, this means the user wants
306 * to specify the name of the destination directory
307 *
308 * e.g. cp -r /src /data/new_dir_src
309 */
310 if (mkdir(dest_path, 0)) {
311 printf("Unable to create "
312 "dest directory %s\n", dest_path);
313 goto exit;
314 }
315 break;
316 }
317
318 dir = opendir(src);
319 if (!dir) {
320 /* Something strange is happening... */
321 printf("Unable to open src %s directory\n", src);
322 goto exit;
323 }
324
325 /* Copy every single directory entry of src into the
326 * destination directory.
327 */
328 while ((dp = readdir(dir))) {
329 struct stat src_s;
330 struct stat dest_s;
331
332 char src_dent[PATH_MAX];
333 char dest_dent[PATH_MAX];
334
335 str_cpy(src_dent, PATH_MAX, src);
336 merge_paths(src_dent, PATH_MAX, dp->d_name);
337
338 str_cpy(dest_dent, PATH_MAX, dest_path);
339 merge_paths(dest_dent, PATH_MAX, dp->d_name);
340
341 /* Check if we are copying a directory into itself */
342 stat(src_dent, &src_s);
343 stat(dest_path, &dest_s);
344
345 if (dest_s.index == src_s.index &&
346 dest_s.fs_handle == src_s.fs_handle) {
347 printf("Cannot copy a directory "
348 "into itself\n");
349 goto exit;
350 }
351
352 if (vb)
353 printf("copy %s %s\n", src_dent, dest_dent);
354
355 /* Recursively call do_copy() */
356 r = do_copy(src_dent, dest_dent, blen, vb, recursive,
357 force, interactive);
358 if (r)
359 goto exit;
360
361 }
362 } else
363 printf("Unable to open source file %s\n", src);
364
365exit:
366 if (dir)
367 closedir(dir);
368 return r;
369}
370
371static int64_t copy_file(const char *src, const char *dest,
372 size_t blen, int vb)
373{
374 int fd1, fd2, bytes;
375 off64_t total;
376 int64_t copied = 0;
377 char *buff = NULL;
378
379 if (vb)
380 printf("Copying %s to %s\n", src, dest);
381
382 if (-1 == (fd1 = open(src, O_RDONLY))) {
383 printf("Unable to open source file %s\n", src);
384 return -1;
385 }
386
387 if (-1 == (fd2 = open(dest, O_CREAT))) {
388 printf("Unable to open destination file %s\n", dest);
389 close(fd1);
390 return -1;
391 }
392
393 total = lseek(fd1, 0, SEEK_END);
394
395 if (vb)
396 printf("%" PRIu64 " bytes to copy\n", total);
397
398 lseek(fd1, 0, SEEK_SET);
399
400 if (NULL == (buff = (char *) malloc(blen))) {
401 printf("Unable to allocate enough memory to read %s\n",
402 src);
403 copied = -1;
404 goto out;
405 }
406
407 while ((bytes = read_all(fd1, buff, blen)) > 0) {
408 if ((bytes = write_all(fd2, buff, bytes)) < 0)
409 break;
410 copied += bytes;
411 }
412
413 if (bytes < 0) {
414 printf("\nError copying %s, (%d)\n", src, bytes);
415 copied = bytes;
416 }
417
418out:
419 close(fd1);
420 close(fd2);
421 if (buff)
422 free(buff);
423 return copied;
424}
425
426void help_cmd_cp(unsigned int level)
427{
428 static char helpfmt[] =
429 "Usage: %s [options] <source> <dest>\n"
430 "Options:\n"
431 " -h, --help A short option summary\n"
432 " -v, --version Print version information and exit\n"
433 " -V, --verbose Be annoyingly noisy about what's being done\n"
434 " -f, --force Do not complain when <dest> exists (overrides a previous -i)\n"
435 " -i, --interactive Ask what to do when <dest> exists (overrides a previous -f)\n"
436 " -r, --recursive Copy entire directories\n"
437 " -b, --buffer ## Set the read buffer size to ##\n";
438 if (level == HELP_SHORT) {
439 printf("`%s' copies files and directories\n", cmdname);
440 } else {
441 help_cmd_cp(HELP_SHORT);
442 printf(helpfmt, cmdname);
443 }
444
445 return;
446}
447
448int cmd_cp(char **argv)
449{
450 unsigned int argc, verbose = 0;
451 int buffer = 0, recursive = 0;
452 int force = 0, interactive = 0;
453 int c, opt_ind;
454 int64_t ret;
455
456 con = console_init(stdin, stdout);
457 argc = cli_count_args(argv);
458
459 for (c = 0, optind = 0, opt_ind = 0; c != -1;) {
460 c = getopt_long(argc, argv, "hvVfirb:", long_options, &opt_ind);
461 switch (c) {
462 case 'h':
463 help_cmd_cp(1);
464 return CMD_SUCCESS;
465 case 'v':
466 printf("%s\n", CP_VERSION);
467 return CMD_SUCCESS;
468 case 'V':
469 verbose = 1;
470 break;
471 case 'f':
472 interactive = 0;
473 force = 1;
474 break;
475 case 'i':
476 force = 0;
477 interactive = 1;
478 break;
479 case 'r':
480 recursive = 1;
481 break;
482 case 'b':
483 if (-1 == (buffer = strtoint(optarg))) {
484 printf("%s: Invalid buffer specification, "
485 "(should be a number greater than zero)\n",
486 cmdname);
487 console_done(con);
488 return CMD_FAILURE;
489 }
490 if (verbose)
491 printf("Buffer = %d\n", buffer);
492 break;
493 }
494 }
495
496 if (buffer == 0)
497 buffer = CP_DEFAULT_BUFLEN;
498
499 argc -= optind;
500
501 if (argc != 2) {
502 printf("%s: invalid number of arguments. Try %s --help\n",
503 cmdname, cmdname);
504 console_done(con);
505 return CMD_FAILURE;
506 }
507
508 ret = do_copy(argv[optind], argv[optind + 1], buffer, verbose,
509 recursive, force, interactive);
510
511 console_done(con);
512
513 if (ret == 0)
514 return CMD_SUCCESS;
515 else
516 return CMD_FAILURE;
517}
518
Note: See TracBrowser for help on using the repository browser.