acp.c revision 88b607994a148f4af5bffee163e39ce8296750c6
1/*
2 * Copyright 2005 The Android Open Source Project
3 *
4 * Android "cp" replacement.
5 *
6 * The GNU/Linux "cp" uses O_LARGEFILE in its open() calls, utimes() instead
7 * of utime(), and getxattr()/setxattr() instead of chmod().  These are
8 * probably "better", but are non-portable, and not necessary for our
9 * purposes.
10 */
11#include <stdlib.h>
12#include <stdio.h>
13#include <string.h>
14#include <unistd.h>
15#include <sys/types.h>
16#include <sys/stat.h>
17#include <getopt.h>
18#include <dirent.h>
19#include <fcntl.h>
20#include <utime.h>
21#include <limits.h>
22#include <errno.h>
23#include <assert.h>
24#include <host/CopyFile.h>
25
26/*#define DEBUG_MSGS*/
27#ifdef DEBUG_MSGS
28# define DBUG(x) printf x
29#else
30# define DBUG(x) ((void)0)
31#endif
32
33#define FSSEP '/'       /* filename separator char */
34
35
36/*
37 * Process the command-line file arguments.
38 *
39 * Returns 0 on success.
40 */
41int process(int argc, char* const argv[], unsigned int options)
42{
43    int retVal = 0;
44    int i, cc;
45    char* stripDest = NULL;
46    int stripDestLen;
47    struct stat destStat;
48    bool destMustBeDir = false;
49    struct stat sb;
50
51    assert(argc >= 2);
52
53    /*
54     * Check for and trim a trailing slash on the last arg.
55     *
56     * It's useful to be able to say "cp foo bar/" when you want to copy
57     * a single file into a directory.  If you say "cp foo bar", and "bar"
58     * does not exist, it will create "bar", when what you really wanted
59     * was for the cp command to fail with "directory does not exist".
60     */
61    stripDestLen = strlen(argv[argc-1]);
62    stripDest = malloc(stripDestLen+1);
63    memcpy(stripDest, argv[argc-1], stripDestLen+1);
64    if (stripDest[stripDestLen-1] == FSSEP) {
65        stripDest[--stripDestLen] = '\0';
66        destMustBeDir = true;
67    }
68
69    if (argc > 2)
70        destMustBeDir = true;
71
72    /*
73     * Start with a quick check to ensure that, if we're expecting to copy
74     * to a directory, the target already exists and is actually a directory.
75     * It's okay if it's a symlink to a directory.
76     *
77     * If it turns out to be a directory, go ahead and raise the
78     * destMustBeDir flag so we do some path concatenation below.
79     */
80    if (stat(stripDest, &sb) < 0) {
81        if (destMustBeDir) {
82            if (errno == ENOENT)
83                fprintf(stderr,
84                    "acp: destination directory '%s' does not exist\n",
85                    stripDest);
86            else
87                fprintf(stderr, "acp: unable to stat dest dir\n");
88            retVal = 1;
89            goto bail;
90        }
91    } else {
92        if (S_ISDIR(sb.st_mode)) {
93            DBUG(("--- dest exists and is a dir, setting flag\n"));
94            destMustBeDir = true;
95        } else if (destMustBeDir) {
96            fprintf(stderr,
97                "acp: destination '%s' is not a directory\n",
98                stripDest);
99            retVal = 1;
100            goto bail;
101        }
102    }
103
104    /*
105     * Copying files.
106     *
107     * Strip trailing slashes off.  They shouldn't be there, but
108     * sometimes file completion will put them in for directories.
109     *
110     * The observed behavior of GNU and BSD cp is that they print warnings
111     * if something fails, but continue on.  If any part fails, the command
112     * exits with an error status.
113     */
114    for (i = 0; i < argc-1; i++) {
115        const char* srcName;
116        char* src;
117        char* dst;
118        int copyResult;
119        int srcLen;
120
121        /* make a copy of the source name, and strip trailing '/' */
122        srcLen = strlen(argv[i]);
123        src = malloc(srcLen+1);
124        memcpy(src, argv[i], srcLen+1);
125
126        if (src[srcLen-1] == FSSEP)
127            src[--srcLen] = '\0';
128
129        /* find just the name part */
130        srcName = strrchr(src, FSSEP);
131        if (srcName == NULL) {
132            srcName = src;
133        } else {
134            srcName++;
135            assert(*srcName != '\0');
136        }
137
138        if (destMustBeDir) {
139            /* concatenate dest dir and src name */
140            int srcNameLen = strlen(srcName);
141
142            dst = malloc(stripDestLen +1 + srcNameLen +1);
143            memcpy(dst, stripDest, stripDestLen);
144            dst[stripDestLen] = FSSEP;
145            memcpy(dst + stripDestLen+1, srcName, srcNameLen+1);
146        } else {
147            /* simple */
148            dst = stripDest;
149        }
150
151        /*
152         * Copy the source to the destination.
153         */
154        copyResult = copyFile(src, dst, options);
155
156        if (copyResult != 0)
157            retVal = 1;
158
159        free(src);
160        if (dst != stripDest)
161            free(dst);
162    }
163
164bail:
165    free(stripDest);
166    return retVal;
167}
168
169/*
170 * Set up the options.
171 */
172int main(int argc, char* const argv[])
173{
174    bool wantUsage;
175    int ic, retVal;
176    int verboseLevel;
177    unsigned int options;
178
179    verboseLevel = 0;
180    options = 0;
181    wantUsage = false;
182
183    while (1) {
184        ic = getopt(argc, argv, "defprtuv");
185        if (ic < 0)
186            break;
187
188        switch (ic) {
189            case 'd':
190                options |= COPY_NO_DEREFERENCE;
191                break;
192            case 'e':
193                options |= COPY_TRY_EXE;
194                break;
195            case 'f':
196                options |= COPY_FORCE;
197                break;
198            case 'p':
199                options |= COPY_PERMISSIONS;
200                break;
201            case 't':
202                options |= COPY_TIMESTAMPS;
203                break;
204            case 'r':
205                options |= COPY_RECURSIVE;
206                break;
207            case 'u':
208                options |= COPY_UPDATE_ONLY;
209                break;
210            case 'v':
211                verboseLevel++;
212                break;
213            default:
214                fprintf(stderr, "Unexpected arg -%c\n", ic);
215                wantUsage = true;
216                break;
217        }
218
219        if (wantUsage)
220            break;
221    }
222
223    options |= verboseLevel & COPY_VERBOSE_MASK;
224
225    if (optind == argc-1) {
226        fprintf(stderr, "acp: missing destination file\n");
227        return 2;
228    } else if (optind+2 > argc)
229        wantUsage = true;
230
231    if (wantUsage) {
232        fprintf(stderr, "Usage: acp [OPTION]... SOURCE DEST\n");
233        fprintf(stderr, "  or:  acp [OPTION]... SOURCE... DIRECTORY\n");
234        fprintf(stderr, "\nOptions:\n");
235        fprintf(stderr, "  -d  never follow (dereference) symbolic links\n");
236        fprintf(stderr, "  -e  if source file doesn't exist, try adding "
237                        "'.exe' [Win32 only]\n");
238        fprintf(stderr, "  -f  use force, removing existing file if it's "
239                        "not writeable\n");
240        fprintf(stderr, "  -p  preserve mode, ownership\n");
241        fprintf(stderr, "  -r  recursive copy\n");
242        fprintf(stderr, "  -t  preserve timestamps\n");
243        fprintf(stderr, "  -u  update only: don't copy if dest is newer\n");
244        fprintf(stderr, "  -v  verbose output (-vv is more verbose)\n");
245        return 2;
246    }
247
248    retVal = process(argc-optind, argv+optind, options);
249    DBUG(("EXIT: %d\n", retVal));
250    return retVal;
251}
252
253