253 lines
6.9 KiB
C
253 lines
6.9 KiB
C
/*
|
|
* Copyright 2005 The Android Open Source Project
|
|
*
|
|
* Android "cp" replacement.
|
|
*
|
|
* The GNU/Linux "cp" uses O_LARGEFILE in its open() calls, utimes() instead
|
|
* of utime(), and getxattr()/setxattr() instead of chmod(). These are
|
|
* probably "better", but are non-portable, and not necessary for our
|
|
* purposes.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <getopt.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <utime.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <host/CopyFile.h>
|
|
|
|
/*#define DEBUG_MSGS*/
|
|
#ifdef DEBUG_MSGS
|
|
# define DBUG(x) printf x
|
|
#else
|
|
# define DBUG(x) ((void)0)
|
|
#endif
|
|
|
|
#define FSSEP '/' /* filename separator char */
|
|
|
|
|
|
/*
|
|
* Process the command-line file arguments.
|
|
*
|
|
* Returns 0 on success.
|
|
*/
|
|
int process(int argc, char* const argv[], unsigned int options)
|
|
{
|
|
int retVal = 0;
|
|
int i, cc;
|
|
char* stripDest = NULL;
|
|
int stripDestLen;
|
|
struct stat destStat;
|
|
bool destMustBeDir = false;
|
|
struct stat sb;
|
|
|
|
assert(argc >= 2);
|
|
|
|
/*
|
|
* Check for and trim a trailing slash on the last arg.
|
|
*
|
|
* It's useful to be able to say "cp foo bar/" when you want to copy
|
|
* a single file into a directory. If you say "cp foo bar", and "bar"
|
|
* does not exist, it will create "bar", when what you really wanted
|
|
* was for the cp command to fail with "directory does not exist".
|
|
*/
|
|
stripDestLen = strlen(argv[argc-1]);
|
|
stripDest = malloc(stripDestLen+1);
|
|
memcpy(stripDest, argv[argc-1], stripDestLen+1);
|
|
if (stripDest[stripDestLen-1] == FSSEP) {
|
|
stripDest[--stripDestLen] = '\0';
|
|
destMustBeDir = true;
|
|
}
|
|
|
|
if (argc > 2)
|
|
destMustBeDir = true;
|
|
|
|
/*
|
|
* Start with a quick check to ensure that, if we're expecting to copy
|
|
* to a directory, the target already exists and is actually a directory.
|
|
* It's okay if it's a symlink to a directory.
|
|
*
|
|
* If it turns out to be a directory, go ahead and raise the
|
|
* destMustBeDir flag so we do some path concatenation below.
|
|
*/
|
|
if (stat(stripDest, &sb) < 0) {
|
|
if (destMustBeDir) {
|
|
if (errno == ENOENT)
|
|
fprintf(stderr,
|
|
"acp: destination directory '%s' does not exist\n",
|
|
stripDest);
|
|
else
|
|
fprintf(stderr, "acp: unable to stat dest dir\n");
|
|
retVal = 1;
|
|
goto bail;
|
|
}
|
|
} else {
|
|
if (S_ISDIR(sb.st_mode)) {
|
|
DBUG(("--- dest exists and is a dir, setting flag\n"));
|
|
destMustBeDir = true;
|
|
} else if (destMustBeDir) {
|
|
fprintf(stderr,
|
|
"acp: destination '%s' is not a directory\n",
|
|
stripDest);
|
|
retVal = 1;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copying files.
|
|
*
|
|
* Strip trailing slashes off. They shouldn't be there, but
|
|
* sometimes file completion will put them in for directories.
|
|
*
|
|
* The observed behavior of GNU and BSD cp is that they print warnings
|
|
* if something fails, but continue on. If any part fails, the command
|
|
* exits with an error status.
|
|
*/
|
|
for (i = 0; i < argc-1; i++) {
|
|
const char* srcName;
|
|
char* src;
|
|
char* dst;
|
|
int copyResult;
|
|
int srcLen;
|
|
|
|
/* make a copy of the source name, and strip trailing '/' */
|
|
srcLen = strlen(argv[i]);
|
|
src = malloc(srcLen+1);
|
|
memcpy(src, argv[i], srcLen+1);
|
|
|
|
if (src[srcLen-1] == FSSEP)
|
|
src[--srcLen] = '\0';
|
|
|
|
/* find just the name part */
|
|
srcName = strrchr(src, FSSEP);
|
|
if (srcName == NULL) {
|
|
srcName = src;
|
|
} else {
|
|
srcName++;
|
|
assert(*srcName != '\0');
|
|
}
|
|
|
|
if (destMustBeDir) {
|
|
/* concatenate dest dir and src name */
|
|
int srcNameLen = strlen(srcName);
|
|
|
|
dst = malloc(stripDestLen +1 + srcNameLen +1);
|
|
memcpy(dst, stripDest, stripDestLen);
|
|
dst[stripDestLen] = FSSEP;
|
|
memcpy(dst + stripDestLen+1, srcName, srcNameLen+1);
|
|
} else {
|
|
/* simple */
|
|
dst = stripDest;
|
|
}
|
|
|
|
/*
|
|
* Copy the source to the destination.
|
|
*/
|
|
copyResult = copyFile(src, dst, options);
|
|
|
|
if (copyResult != 0)
|
|
retVal = 1;
|
|
|
|
free(src);
|
|
if (dst != stripDest)
|
|
free(dst);
|
|
}
|
|
|
|
bail:
|
|
free(stripDest);
|
|
return retVal;
|
|
}
|
|
|
|
/*
|
|
* Set up the options.
|
|
*/
|
|
int main(int argc, char* const argv[])
|
|
{
|
|
bool wantUsage;
|
|
int ic, retVal;
|
|
int verboseLevel;
|
|
unsigned int options;
|
|
|
|
verboseLevel = 0;
|
|
options = 0;
|
|
wantUsage = false;
|
|
|
|
while (1) {
|
|
ic = getopt(argc, argv, "defprtuv");
|
|
if (ic < 0)
|
|
break;
|
|
|
|
switch (ic) {
|
|
case 'd':
|
|
options |= COPY_NO_DEREFERENCE;
|
|
break;
|
|
case 'e':
|
|
options |= COPY_TRY_EXE;
|
|
break;
|
|
case 'f':
|
|
options |= COPY_FORCE;
|
|
break;
|
|
case 'p':
|
|
options |= COPY_PERMISSIONS;
|
|
break;
|
|
case 't':
|
|
options |= COPY_TIMESTAMPS;
|
|
break;
|
|
case 'r':
|
|
options |= COPY_RECURSIVE;
|
|
break;
|
|
case 'u':
|
|
options |= COPY_UPDATE_ONLY;
|
|
break;
|
|
case 'v':
|
|
verboseLevel++;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unexpected arg -%c\n", ic);
|
|
wantUsage = true;
|
|
break;
|
|
}
|
|
|
|
if (wantUsage)
|
|
break;
|
|
}
|
|
|
|
options |= verboseLevel & COPY_VERBOSE_MASK;
|
|
|
|
if (optind == argc-1) {
|
|
fprintf(stderr, "acp: missing destination file\n");
|
|
return 2;
|
|
} else if (optind+2 > argc)
|
|
wantUsage = true;
|
|
|
|
if (wantUsage) {
|
|
fprintf(stderr, "Usage: acp [OPTION]... SOURCE DEST\n");
|
|
fprintf(stderr, " or: acp [OPTION]... SOURCE... DIRECTORY\n");
|
|
fprintf(stderr, "\nOptions:\n");
|
|
fprintf(stderr, " -d never follow (dereference) symbolic links\n");
|
|
fprintf(stderr, " -e if source file doesn't exist, try adding "
|
|
"'.exe' [Win32 only]\n");
|
|
fprintf(stderr, " -f use force, removing existing file if it's "
|
|
"not writeable\n");
|
|
fprintf(stderr, " -p preserve mode, ownership\n");
|
|
fprintf(stderr, " -r recursive copy\n");
|
|
fprintf(stderr, " -t preserve timestamps\n");
|
|
fprintf(stderr, " -u update only: don't copy if dest is newer\n");
|
|
fprintf(stderr, " -v verbose output (-vv is more verbose)\n");
|
|
return 2;
|
|
}
|
|
|
|
retVal = process(argc-optind, argv+optind, options);
|
|
DBUG(("EXIT: %d\n", retVal));
|
|
return retVal;
|
|
}
|
|
|