|  | /* | 
|  | * 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 <host/CopyFile.h> | 
|  |  | 
|  | #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> | 
|  |  | 
|  | #ifdef HAVE_MS_C_RUNTIME | 
|  | #  define  mkdir(path,mode)   _mkdir(path) | 
|  | #endif | 
|  |  | 
|  | #if defined(_WIN32) | 
|  | #  define S_ISLNK(s) 0 | 
|  | #  define lstat stat | 
|  | #  ifndef EACCESS   /* seems to be missing from the Mingw headers */ | 
|  | #    define  EACCESS            13 | 
|  | #  endif | 
|  | #endif | 
|  |  | 
|  | #ifndef O_BINARY | 
|  | #  define  O_BINARY  0 | 
|  | #endif | 
|  |  | 
|  | /*#define DEBUG_MSGS*/ | 
|  | #ifdef DEBUG_MSGS | 
|  | # define DBUG(x) printf x | 
|  | #else | 
|  | # define DBUG(x) ((void)0) | 
|  | #endif | 
|  |  | 
|  | #define FSSEP '/'       /* filename separator char */ | 
|  |  | 
|  | static int copyFileRecursive(const char* src, const char* dst, bool isCmdLine, unsigned int options); | 
|  |  | 
|  | /* | 
|  | * Returns true if the source file is newer than the destination file. | 
|  | * | 
|  | * The check is based on the modification date, whole seconds only.  This | 
|  | * also returns true if the file sizes don't match. | 
|  | */ | 
|  | static bool isSourceNewer(const struct stat* pSrcStat, const struct stat* pDstStat) | 
|  | { | 
|  | return (pSrcStat->st_mtime > pDstStat->st_mtime) || | 
|  | (pSrcStat->st_size != pDstStat->st_size); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Returns true if the source file has high resolution modification | 
|  | * date. Cygwin/Mingw doesn't support st_mtim and always returns false. | 
|  | */ | 
|  | static bool isHiresMtime(const struct stat* pSrcStat) | 
|  | { | 
|  | #if defined(__CYGWIN__) || defined(__MINGW32__) | 
|  | return 0; | 
|  | #elif defined(MACOSX_RSRC) | 
|  | return pSrcStat->st_mtimespec.tv_nsec > 0; | 
|  | #else | 
|  | return pSrcStat->st_mtim.tv_nsec > 0; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Returns true if the source and destination files are actually the | 
|  | * same thing.  We detect this by checking the inode numbers, which seems | 
|  | * to work on Cygwin. | 
|  | */ | 
|  | static bool isSameFile(const struct stat* pSrcStat, const struct stat* pDstStat) | 
|  | { | 
|  | #ifndef HAVE_VALID_STAT_ST_INO | 
|  | /* with MSVCRT.DLL, stat always sets st_ino to 0, and there is no simple way to */ | 
|  | /* get the equivalent information with Win32 (Cygwin does some weird stuff in   */ | 
|  | /* its winsup/cygwin/fhandler_disk_file.cc to emulate this, too complex for us) */ | 
|  | return 0; | 
|  | #else | 
|  | return (pSrcStat->st_ino == pDstStat->st_ino); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static void printCopyMsg(const char* src, const char* dst, unsigned int options) | 
|  | { | 
|  | if ((options & COPY_VERBOSE_MASK) > 0) | 
|  | printf("    '%s' --> '%s'\n", src, dst); | 
|  | } | 
|  |  | 
|  | static void printNotNewerMsg(const char* src, const char* dst, unsigned int options) | 
|  | { | 
|  | if ((options & COPY_VERBOSE_MASK) > 1) | 
|  | printf("    '%s' is up-to-date\n", dst); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Copy the contents of one file to another. | 
|  | * | 
|  | * The files are assumed to be seeked to the start. | 
|  | */ | 
|  | static int copyFileContents(const char* dst, int dstFd, const char* src, int srcFd) | 
|  | { | 
|  | unsigned char buf[8192]; | 
|  | ssize_t readCount, writeCount; | 
|  |  | 
|  | /* | 
|  | * Read a chunk, write it, and repeat. | 
|  | */ | 
|  | while (1) { | 
|  | readCount = read(srcFd, buf, sizeof(buf)); | 
|  | if (readCount < 0) { | 
|  | fprintf(stderr, | 
|  | "acp: failed reading '%s': %s\n", src, strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (readCount > 0) { | 
|  | writeCount = write(dstFd, buf, readCount); | 
|  | if (writeCount < 0) { | 
|  | fprintf(stderr, | 
|  | "acp: failed writing '%s': %s\n", dst, strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  | if (writeCount != readCount) { | 
|  | fprintf(stderr, "acp: partial write to '%s' (%zd of %zd)\n", | 
|  | dst, writeCount, readCount); | 
|  | return -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (readCount < (ssize_t) sizeof(buf)) | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Set the permissions, owner, and timestamps on the destination file | 
|  | * equal to those of the source file. | 
|  | * | 
|  | * Failures here are "soft"; they don't produce warning messages and don't | 
|  | * cause the cp command to report a failure. | 
|  | */ | 
|  | static int setPermissions(const char* dst, const struct stat* pSrcStat, unsigned int options) | 
|  | { | 
|  | struct utimbuf ut; | 
|  |  | 
|  | if (options & COPY_TIMESTAMPS) { | 
|  | /* | 
|  | * Start with timestamps.  The access and mod dates are not affected | 
|  | * by the next operations. | 
|  | */ | 
|  | ut.actime = pSrcStat->st_atime; | 
|  | ut.modtime = pSrcStat->st_mtime; | 
|  | if (isHiresMtime(pSrcStat)) | 
|  | ut.modtime += 1; | 
|  | if (utime(dst, &ut) != 0) { | 
|  | DBUG(("---   unable to set timestamps on '%s': %s\n", | 
|  | dst, strerror(errno))); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (options & COPY_PERMISSIONS) { | 
|  | /* | 
|  | * Set the permissions. | 
|  | */ | 
|  | if (chmod(dst, pSrcStat->st_mode & ~(S_IFMT)) != 0) { | 
|  | DBUG(("---   unable to set perms on '%s' to 0%o: %s\n", | 
|  | dst, pSrcStat->st_mode & ~(S_IFMT), strerror(errno))); | 
|  | } | 
|  | #ifndef HAVE_MS_C_RUNTIME | 
|  | /* | 
|  | * Set the owner. | 
|  | */ | 
|  | if (chown(dst, pSrcStat->st_uid, pSrcStat->st_gid) != 0) { | 
|  | DBUG(("---   unable to set owner of '%s' to %d/%d: %s\n", | 
|  | dst, pSrcStat->st_uid, pSrcStat->st_gid, strerror(errno))); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Copy a regular file.  If the destination file exists and is not a | 
|  | * regular file, we fail.  However, we use stat() rather than lstat(), | 
|  | * because it's okay to write through a symlink (the noDereference stuff | 
|  | * only applies to the source file). | 
|  | * | 
|  | * If the file doesn't exist, create it.  If it does exist, truncate it. | 
|  | */ | 
|  | static int copyRegular(const char* src, const char* dst, const struct stat* pSrcStat, unsigned int options) | 
|  | { | 
|  | struct stat dstStat; | 
|  | int srcFd, dstFd, statResult, copyResult; | 
|  |  | 
|  | DBUG(("--- copying regular '%s' to '%s'\n", src, dst)); | 
|  |  | 
|  | statResult = stat(dst, &dstStat); | 
|  | if (statResult == 0 && !S_ISREG(dstStat.st_mode)) { | 
|  | fprintf(stderr, | 
|  | "acp: destination '%s' exists and is not regular file\n", | 
|  | dst); | 
|  | return -1; | 
|  | } else if (statResult != 0 && errno != ENOENT) { | 
|  | fprintf(stderr, "acp: unable to stat destination '%s'\n", dst); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (statResult == 0) { | 
|  | if (isSameFile(pSrcStat, &dstStat)) { | 
|  | fprintf(stderr, "acp: '%s' and '%s' are the same file\n", | 
|  | src, dst); | 
|  | return -1; | 
|  | } | 
|  | if (options & COPY_UPDATE_ONLY) { | 
|  | if (!isSourceNewer(pSrcStat, &dstStat)) { | 
|  | DBUG(("---  source is not newer: '%s'\n", src)); | 
|  | printNotNewerMsg(src, dst, options); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* open src */ | 
|  | srcFd = open(src, O_RDONLY | O_BINARY, 0); | 
|  | if (srcFd < 0) { | 
|  | fprintf(stderr, "acp: unable to open '%s': %s\n", src, strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* open dest with O_CREAT | O_TRUNC */ | 
|  | DBUG(("---  opening '%s'\n", dst)); | 
|  | dstFd = open(dst, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0644); | 
|  |  | 
|  | if (dstFd < 0) { | 
|  | if (errno == ENOENT) { | 
|  | /* this happens if the target directory doesn't exist */ | 
|  | fprintf(stderr, | 
|  | "acp: cannot create '%s': %s\n", dst, strerror(errno)); | 
|  | (void) close(srcFd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* if "force" is set, try removing the destination file and retry */ | 
|  | if (options & COPY_FORCE) { | 
|  | if (unlink(dst) != 0) { | 
|  | #ifdef HAVE_MS_C_RUNTIME | 
|  | /* MSVCRT.DLL unlink will fail with EACCESS if the file is set read-only */ | 
|  | /* so try to change its mode, and unlink again                           */ | 
|  | if (errno == EACCESS) { | 
|  | if (chmod(dst, S_IWRITE|S_IREAD) == 0 && unlink(dst) == 0) | 
|  | goto Open_File; | 
|  | } | 
|  | #endif | 
|  | fprintf(stderr, "acp: unable to remove '%s': %s\n", | 
|  | dst, strerror(errno)); | 
|  | (void) close(srcFd); | 
|  | return -1; | 
|  | } | 
|  | #ifdef HAVE_MS_C_RUNTIME | 
|  | Open_File: | 
|  | #endif | 
|  | dstFd = open(dst, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0644); | 
|  | } | 
|  | } | 
|  | if (dstFd < 0) { | 
|  | fprintf(stderr, "acp: unable to open '%s': %s\n", | 
|  | dst, strerror(errno)); | 
|  | (void) close(srcFd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | copyResult = copyFileContents(dst, dstFd, src, srcFd); | 
|  |  | 
|  | (void) close(srcFd); | 
|  | (void) close(dstFd); | 
|  | if (copyResult != 0) | 
|  | return -1; | 
|  |  | 
|  | #ifdef MACOSX_RSRC | 
|  | { | 
|  | char* srcRsrcName = NULL; | 
|  | char* dstRsrcName = NULL; | 
|  | struct stat rsrcStat; | 
|  |  | 
|  | srcRsrcName = malloc(strlen(src) + 5 + 1); | 
|  | strcpy(srcRsrcName, src); | 
|  | strcat(srcRsrcName, "/rsrc"); | 
|  |  | 
|  | dstRsrcName = malloc(strlen(dst) + 5 + 1); | 
|  | strcpy(dstRsrcName, dst); | 
|  | strcat(dstRsrcName, "/rsrc"); | 
|  |  | 
|  | if (stat(srcRsrcName, &rsrcStat) == 0 && rsrcStat.st_size > 0) { | 
|  | DBUG(("---  RSRC: %s --> %s\n", srcRsrcName, dstRsrcName)); | 
|  |  | 
|  | srcFd = open(srcRsrcName, O_RDONLY); | 
|  | dstFd = open(dstRsrcName, O_TRUNC | O_WRONLY, 0); | 
|  | copyResult = -1; | 
|  | if (srcFd >= 0 && dstFd >= 0) { | 
|  | copyResult = copyFileContents(dstRsrcName, dstFd, | 
|  | srcRsrcName, srcFd); | 
|  | (void) close(srcFd); | 
|  | (void) close(dstFd); | 
|  | } | 
|  |  | 
|  | if (copyResult != 0) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | free(srcRsrcName); | 
|  | free(dstRsrcName); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | setPermissions(dst, pSrcStat, options); | 
|  |  | 
|  | printCopyMsg(src, dst, options); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Copy a symlink.  This only happens if we're in "no derefence" mode, | 
|  | * in which we copy the links rather than the files that are pointed at. | 
|  | * | 
|  | * We always discard the destination file.  If it's a symlink already, | 
|  | * we want to throw it out and replace it.  If it's not a symlink, we | 
|  | * need to trash it so we can create one. | 
|  | */ | 
|  | #if defined(_WIN32) | 
|  | extern int copySymlink(const char* src, const char* dst, const struct stat* pSrcStat, unsigned int options) __attribute__((error("no symlinks on Windows"))); | 
|  | #else | 
|  | static int copySymlink(const char* src, const char* dst, const struct stat* pSrcStat, unsigned int options) | 
|  | { | 
|  | struct stat dstStat; | 
|  | char linkBuf[PATH_MAX+1]; | 
|  | int statResult, nameLen; | 
|  |  | 
|  | assert(options & COPY_NO_DEREFERENCE); | 
|  | DBUG(("--- copying symlink '%s' to '%s'\n", src, dst)); | 
|  |  | 
|  | /* NOTE: we use lstat() here */ | 
|  | statResult = lstat(dst, &dstStat); | 
|  | if (statResult == 0 && !S_ISREG(dstStat.st_mode) | 
|  | && !S_ISLNK(dstStat.st_mode) | 
|  | ) | 
|  | { | 
|  | fprintf(stderr, | 
|  | "acp: destination '%s' exists and is not regular or symlink\n", | 
|  | dst); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (statResult == 0) { | 
|  | if (isSameFile(pSrcStat, &dstStat)) { | 
|  | fprintf(stderr, "acp: '%s' and '%s' are the same file\n", | 
|  | src, dst); | 
|  | return -1; | 
|  | } | 
|  | if (options & COPY_UPDATE_ONLY) { | 
|  | if (!isSourceNewer(pSrcStat, &dstStat)) { | 
|  | DBUG(("---  source is not newer: '%s'\n", src)); | 
|  | printNotNewerMsg(src, dst, options); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* extract the symlink contents */ | 
|  | nameLen = readlink(src, linkBuf, sizeof(linkBuf)-1); | 
|  | if (nameLen <= 0) { | 
|  | fprintf(stderr, "acp: unable to read symlink '%s': %s\n", | 
|  | src, strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  | linkBuf[nameLen] = '\0'; | 
|  | DBUG(("--- creating symlink file '%s' (--> %s)\n", dst, linkBuf)); | 
|  |  | 
|  | if (statResult == 0) { | 
|  | DBUG(("---  removing '%s'\n", dst)); | 
|  | if (unlink(dst) != 0) { | 
|  | fprintf(stderr, "acp: unable to remove '%s': %s\n", | 
|  | dst, strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (symlink(linkBuf, dst) != 0) { | 
|  | fprintf(stderr, "acp: unable to create symlink '%s' [%s]: %s\n", | 
|  | dst, linkBuf, strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * There's no way to set the file date or access permissions, but | 
|  | * it is possible to set the owner. | 
|  | */ | 
|  | if (options & COPY_PERMISSIONS) { | 
|  | if (lchown(dst, pSrcStat->st_uid, pSrcStat->st_gid) != 0) | 
|  | DBUG(("---  lchown failed: %s\n", strerror(errno))); | 
|  | } | 
|  |  | 
|  | printCopyMsg(src, dst, options); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * Copy the contents of one directory to another.  Both "src" and "dst" | 
|  | * must be directories.  We will create "dst" if it does not exist. | 
|  | */ | 
|  | int copyDirectory(const char* src, const char* dst, const struct stat* pSrcStat, unsigned int options) | 
|  | { | 
|  | int retVal = 0; | 
|  | struct stat dstStat; | 
|  | DIR* dir; | 
|  | int cc, statResult; | 
|  |  | 
|  | DBUG(("--- copy dir '%s' to '%s'\n", src, dst)); | 
|  |  | 
|  | statResult = stat(dst, &dstStat); | 
|  | if (statResult == 0 && !S_ISDIR(dstStat.st_mode)) { | 
|  | fprintf(stderr, | 
|  | "acp: destination '%s' exists and is not a directory\n", dst); | 
|  | return -1; | 
|  | } else if (statResult != 0 && errno != ENOENT) { | 
|  | fprintf(stderr, "acp: unable to stat destination '%s'\n", dst); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (statResult == 0) { | 
|  | if (isSameFile(pSrcStat, &dstStat)) { | 
|  | fprintf(stderr, | 
|  | "acp: cannot copy directory into itself ('%s' and '%s')\n", | 
|  | src, dst); | 
|  | return -1; | 
|  | } | 
|  | } else { | 
|  | DBUG(("---  creating dir '%s'\n", dst)); | 
|  | cc = mkdir(dst, 0755); | 
|  | if (cc != 0) { | 
|  | fprintf(stderr, "acp: unable to create directory '%s': %s\n", | 
|  | dst, strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* only print on mkdir */ | 
|  | printCopyMsg(src, dst, options); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Open the directory, and plow through its contents. | 
|  | */ | 
|  | dir = opendir(src); | 
|  | if (dir == NULL) { | 
|  | fprintf(stderr, "acp: unable to open directory '%s': %s\n", | 
|  | src, strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | while (1) { | 
|  | struct dirent* ent; | 
|  | char* srcFile; | 
|  | char* dstFile; | 
|  | int srcLen, dstLen, nameLen; | 
|  |  | 
|  | ent = readdir(dir); | 
|  | if (ent == NULL) | 
|  | break; | 
|  |  | 
|  | if (strcmp(ent->d_name, ".") == 0 || | 
|  | strcmp(ent->d_name, "..") == 0) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | nameLen = strlen(ent->d_name); | 
|  | srcLen = strlen(src); | 
|  | dstLen = strlen(dst); | 
|  |  | 
|  | srcFile = malloc(srcLen +1 + nameLen +1); | 
|  | memcpy(srcFile, src, srcLen); | 
|  | srcFile[srcLen] = FSSEP; | 
|  | memcpy(srcFile + srcLen+1, ent->d_name, nameLen +1); | 
|  |  | 
|  | dstFile = malloc(dstLen +1 + nameLen +1); | 
|  | memcpy(dstFile, dst, dstLen); | 
|  | dstFile[dstLen] = FSSEP; | 
|  | memcpy(dstFile + dstLen+1, ent->d_name, nameLen +1); | 
|  |  | 
|  | if (copyFileRecursive(srcFile, dstFile, false, options) != 0) | 
|  | retVal = -1;        /* note failure and keep going */ | 
|  |  | 
|  | free(srcFile); | 
|  | free(dstFile); | 
|  | } | 
|  | closedir(dir); | 
|  |  | 
|  | setPermissions(dst, pSrcStat, options); | 
|  |  | 
|  | return retVal; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Do the actual copy.  This is called recursively from copyDirectory(). | 
|  | * | 
|  | * "dst" should only be a directory if "src" is also a directory. | 
|  | * | 
|  | * Returns 0 on success. | 
|  | */ | 
|  | static int copyFileRecursive(const char* src, const char* dst, bool isCmdLine, unsigned int options) | 
|  | { | 
|  | char* srcExe = NULL; | 
|  | char* dstExe = NULL; | 
|  | char* dstDir = NULL; | 
|  | struct stat srcStat; | 
|  | int retVal = 0; | 
|  | int statResult, statErrno; | 
|  |  | 
|  | /* | 
|  | * Stat the source file.  If it doesn't exist, fail. | 
|  | */ | 
|  | if (options & COPY_NO_DEREFERENCE) | 
|  | statResult = lstat(src, &srcStat); | 
|  | else | 
|  | statResult = stat(src, &srcStat); | 
|  | statErrno = errno;        /* preserve across .exe attempt */ | 
|  |  | 
|  | #ifdef WIN32_EXE | 
|  | /* | 
|  | * Here's the interesting part.  Under Cygwin, if you have a file | 
|  | * called "foo.exe", stat("foo", ...) will succeed, but open("foo", ...) | 
|  | * will fail.  We need to figure out what its name is supposed to be | 
|  | * so we can create the correct destination file. | 
|  | * | 
|  | * If we don't have the "-e" flag set, we want "acp foo bar" to fail, | 
|  | * not automatically find "foo.exe".  That way, if we really were | 
|  | * trying to copy "foo", it doesn't grab something we don't want. | 
|  | */ | 
|  | if (isCmdLine && statResult == 0) { | 
|  | int tmpFd; | 
|  | tmpFd = open(src, O_RDONLY | O_BINARY, 0); | 
|  | if (tmpFd < 0) { | 
|  | statResult = -1; | 
|  | statErrno = ENOENT; | 
|  | } else { | 
|  | (void) close(tmpFd); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If we didn't find the file, try it again with ".exe". | 
|  | */ | 
|  | if (isCmdLine && statResult < 0 && statErrno == ENOENT && (options & COPY_TRY_EXE)) { | 
|  | srcExe = malloc(strlen(src) + 4 +1); | 
|  | strcpy(srcExe, src); | 
|  | strcat(srcExe, ".exe"); | 
|  |  | 
|  | if (options & COPY_NO_DEREFERENCE) | 
|  | statResult = lstat(srcExe, &srcStat); | 
|  | else | 
|  | statResult = stat(srcExe, &srcStat); | 
|  |  | 
|  | if (statResult == 0 && !S_ISREG(srcStat.st_mode)) | 
|  | statResult = -1;        /* fail, use original statErrno below */ | 
|  |  | 
|  | if (statResult == 0) { | 
|  | /* found a .exe, copy that instead */ | 
|  | dstExe = malloc(strlen(dst) + 4 +1); | 
|  | strcpy(dstExe, dst); | 
|  | strcat(dstExe, ".exe"); | 
|  |  | 
|  | src = srcExe; | 
|  | dst = dstExe; | 
|  | } else { | 
|  | DBUG(("---  couldn't find '%s' either\n", srcExe)); | 
|  | } | 
|  | } | 
|  | #endif | 
|  | if (statResult < 0) { | 
|  | if (statErrno == ENOENT) | 
|  | fprintf(stderr, "acp: file '%s' does not exist\n", src); | 
|  | else | 
|  | fprintf(stderr, "acp: unable to stat '%s': %s\n", | 
|  | src, strerror(statErrno)); | 
|  | retVal = -1; | 
|  | goto bail; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If "src" is a directory, ignore it if "recursive" isn't set. | 
|  | * | 
|  | * We want to create "dst" as a directory (or verify that it already | 
|  | * exists as a directory), and then copy its contents. | 
|  | */ | 
|  | if (S_ISDIR(srcStat.st_mode)) { | 
|  | if (!(options & COPY_RECURSIVE)) { | 
|  | fprintf(stderr, "acp: omitting directory '%s'\n", src); | 
|  | } else { | 
|  | retVal = copyDirectory(src, dst, &srcStat, options); | 
|  | } | 
|  | } else if (S_ISLNK(srcStat.st_mode)) { | 
|  | retVal = copySymlink(src, dst, &srcStat, options); | 
|  | } else if (S_ISREG(srcStat.st_mode)) { | 
|  | retVal = copyRegular(src, dst, &srcStat, options); | 
|  | } else { | 
|  | fprintf(stderr, "acp: skipping unusual file '%s' (mode=0%o)\n", | 
|  | src, srcStat.st_mode); | 
|  | retVal = -1; | 
|  | } | 
|  |  | 
|  | bail: | 
|  | free(srcExe); | 
|  | free(dstExe); | 
|  | free(dstDir); | 
|  | return retVal; | 
|  | } | 
|  |  | 
|  | int copyFile(const char* src, const char* dst, unsigned int options) | 
|  | { | 
|  | return copyFileRecursive(src, dst, true, options); | 
|  | } | 
|  |  | 
|  |  |