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

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

cp improvements
(Thanks to Maurizio Lombardi)

  • Support for the -r flag (recursive directory copying) and the -f flag

(force copying even if the destination file already exists) has been
added.

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