/*++ Copyright (c) 2013 Minoca Corp. All Rights Reserved Module Name: cp.c Abstract: This module implements the cp (copy) utility. Author: Evan Green 3-Jul-2013 Environment: POSIX --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include #include #include #include #include #include "swlib.h" // // ---------------------------------------------------------------- Definitions // #define CP_VERSION_MAJOR 1 #define CP_VERSION_MINOR 0 #define CP_USAGE \ "usage: cp [-fip] source_file target_file\n" \ " cp [-fip] source_file... target\n" \ " cp -R [-H|-L|-P] [-fip] source_file... target\n\n" \ "The cp utility copies one or more files or directories. Options are:\n" \ " -f, --force -- If the file exists and cannot be truncated, attempt \n" \ " to unlink it.\n" \ " -i, --interactive -- Prompt before overwriting any existing file.\n" \ " -p, --preserve -- Preserve file permissions, owners, and access " \ "times.\n" \ " -R, --recursive -- Recursively copy subdirectories of each operand.\n" \ " -r -- Recursive, same as -R.\n" \ " -H -- Follow symbolic links specified in operands only.\n" \ " -L, --dereference -- Follow all symbolic links.\n" \ " -P, --no-dereference -- Do not follow symbolic links.\n" \ " -v, --verbose -- Print files being copied.\n" \ " --help -- Show this help text and exit.\n" \ " --version -- Print the application version information and exit.\n" #define CP_OPTIONS_STRING "fipRrHLPv" // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // // // -------------------------------------------------------------------- Globals // struct option CpLongOptions[] = { {"force", no_argument, 0, 'f'}, {"interactive", no_argument, 0, 'i'}, {"preserve", no_argument, 0, 'p'}, {"recursive", no_argument, 0, 'R'}, {"dereference", no_argument, 0, 'L'}, {"no-dereference", no_argument, 0, 'P'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {"verbose", no_argument, 0, 'v'}, {NULL, 0, 0, 0}, }; // // ------------------------------------------------------------------ Functions // INT CpMain ( INT ArgumentCount, CHAR **Arguments ) /*++ Routine Description: This routine is the main entry point for the cp utility. Arguments: ArgumentCount - Supplies the number of command line arguments the program was invoked with. Arguments - Supplies a tokenized array of command line arguments. Return Value: Returns an integer exit code. 0 for success, nonzero otherwise. --*/ { PSTR AppendedPath; ULONG AppendedPathSize; PSTR Argument; ULONG ArgumentIndex; PSTR FirstSource; INT Option; ULONG Options; PSTR SourceBaseName; ULONG SourceCount; struct stat Stat; int Status; PSTR Target; BOOL TargetIsDirectory; int TotalStatus; FirstSource = NULL; SourceCount = 0; Target = NULL; Options = COPY_OPTION_FOLLOW_OPERAND_LINKS; TotalStatus = 0; // // Process the control arguments. // while (TRUE) { Option = getopt_long(ArgumentCount, Arguments, CP_OPTIONS_STRING, CpLongOptions, NULL); if (Option == -1) { break; } if ((Option == '?') || (Option == ':')) { Status = 1; goto MainEnd; } switch (Option) { case 'f': Options |= COPY_OPTION_UNLINK; Options &= ~COPY_OPTION_INTERACTIVE; break; case 'i': Options |= COPY_OPTION_INTERACTIVE; break; case 'p': Options |= COPY_OPTION_PRESERVE_PERMISSIONS; break; case 'R': case 'r': Options |= COPY_OPTION_RECURSIVE; break; case 'H': Options |= COPY_OPTION_FOLLOW_OPERAND_LINKS; Options &= ~COPY_OPTION_FOLLOW_LINKS; break; case 'L': Options |= COPY_OPTION_FOLLOW_LINKS; Options &= ~COPY_OPTION_FOLLOW_OPERAND_LINKS; break; case 'P': Options &= ~(COPY_OPTION_FOLLOW_OPERAND_LINKS | COPY_OPTION_FOLLOW_LINKS); break; case 'v': Options |= COPY_OPTION_VERBOSE; break; case 'V': SwPrintVersion(CP_VERSION_MAJOR, CP_VERSION_MINOR); return 1; case 'h': printf(CP_USAGE); return 1; default: assert(FALSE); Status = 1; goto MainEnd; } } ArgumentIndex = optind; if (ArgumentIndex > ArgumentCount) { ArgumentIndex = ArgumentCount; } if (ArgumentIndex < ArgumentCount) { FirstSource = Arguments[ArgumentIndex]; Target = Arguments[ArgumentCount - 1]; } SourceCount = ArgumentCount - ArgumentIndex; // // Fail if there were not enough arguments. // if ((Target == NULL) || (SourceCount <= 1)) { SwPrintError(0, NULL, "Argument expected. Try --help for usage"); return 1; } SourceCount -= 1; // // Figure out if the target is a directory. // TargetIsDirectory = FALSE; Status = SwStat(Target, TRUE, &Stat); if (Status == 0) { if (S_ISDIR(Stat.st_mode)) { TargetIsDirectory = TRUE; } } else if (Status != ENOENT) { TotalStatus = Status; SwPrintError(TotalStatus, Target, "Failed to stat target"); goto MainEnd; } // // If there are only two operands and the target is not a directory and the // source is not a directory, then just copy the source to the destination // directly. // if ((SourceCount == 1) && (TargetIsDirectory == FALSE)) { Status = SwStat(FirstSource, TRUE, &Stat); if (Status != 0) { SwPrintError(Status, FirstSource, "Cannot stat"); goto MainEnd; } if (!S_ISDIR(Stat.st_mode)) { Status = SwCopy(Options, FirstSource, Target); goto MainEnd; } } // // If there's more than one source and the target is not a directory, that's // a problem. // if ((SourceCount > 1) && (TargetIsDirectory == FALSE)) { TotalStatus = ENOTDIR; SwPrintError(TotalStatus, Target, "Cannot copy to"); goto MainEnd; } // // Loop through the arguments again and perform the moves. // while (ArgumentIndex < ArgumentCount) { Argument = Arguments[ArgumentIndex]; ArgumentIndex += 1; // // Skip the target. // if (Argument == Target) { continue; } // // Get the final name component of the source, because // "cp mydir/myfile mydir2" results in a destination file of // "mydir2/myfile", not "mydir2/mydir/myfile". // SourceBaseName = basename(Argument); if (SourceBaseName == NULL) { TotalStatus = ENOMEM; continue; } // // Create an appended version of the path if the target is a directory. // if (TargetIsDirectory != FALSE) { Status = SwAppendPath(Target, strlen(Target) + 1, SourceBaseName, strlen(SourceBaseName) + 1, &AppendedPath, &AppendedPathSize); if (Status == FALSE) { TotalStatus = EINVAL; continue; } Status = SwCopy(Options, Argument, AppendedPath); free(AppendedPath); } else { Status = SwCopy(Options, Argument, Target); } if (Status != 0) { TotalStatus = Status; } } MainEnd: if ((TotalStatus == 0) && (Status != 0)) { TotalStatus = Status; } return TotalStatus; } // // --------------------------------------------------------- Internal Functions //