/*++ Copyright (c) 2013 Minoca Corp. All Rights Reserved Module Name: mv.c Abstract: This module implements the mv (move) file utility. Author: Evan Green 2-Jul-2013 Environment: POSIX --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include #include #include #include #include #include #include #include "swlib.h" // // ---------------------------------------------------------------- Definitions // #define MV_VERSION_MAJOR 1 #define MV_VERSION_MINOR 0 #define MV_USAGE \ "usage: mv [-fiv] source... target\n\n" \ "The mv utility moves files and directories.\n\n" \ " -f, --force -- Skip all prompts.\n" \ " -i, --interactive -- Interactive mode. Prompt for each file.\n" \ " -v, --verbose -- Verbose, print each file being removed.\n" \ " --help -- Display this help text.\n" \ " --version -- Display version information and exit.\n\n" #define MV_OPTIONS_STRING ":fiv" // // Define rm options. // // // Set this option to disable all prompts. // #define MV_OPTION_FORCE 0x00000001 // // Set this option to set prompts for all files. // #define MV_OPTION_INTERACTIVE 0x00000002 // // Set this option to print each file that's deleted. // #define MV_OPTION_VERBOSE 0x00000004 // // This internal option is set if standard in is a terminal device. // #define MV_OPTION_STDIN_IS_TERMINAL 0x00000008 // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // INT MvMove ( INT Options, PSTR Argument, PSTR Target ); // // -------------------------------------------------------------------- Globals // struct option MvLongOptions[] = { {"force", no_argument, 0, 'f'}, {"interactive", no_argument, 0, 'i'}, {"verbose", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {NULL, 0, 0, 0} }; // // ------------------------------------------------------------------ Functions // INT MvMain ( INT ArgumentCount, CHAR **Arguments ) /*++ Routine Description: This routine is the main entry point for the mv 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 LinkDestination; INT Option; ULONG Options; PSTR Source; ULONG SourceCount; struct stat Stat; int Status; PSTR Target; BOOL TargetIsDirectory; int TotalStatus; LinkDestination = NULL; SourceCount = 0; Target = NULL; TotalStatus = 0; Options = 0; if (isatty(STDIN_FILENO)) { Options |= MV_OPTION_STDIN_IS_TERMINAL; } // // Process the control arguments. // while (TRUE) { Option = getopt_long(ArgumentCount, Arguments, MV_OPTIONS_STRING, MvLongOptions, NULL); if (Option == -1) { break; } if ((Option == '?') || (Option == ':')) { Status = 1; goto MainEnd; } switch (Option) { case 'f': Options |= MV_OPTION_FORCE; Options &= ~MV_OPTION_INTERACTIVE; break; case 'i': Options |= MV_OPTION_INTERACTIVE; Options &= ~MV_OPTION_FORCE; break; case 'v': Options |= MV_OPTION_VERBOSE; break; case 'V': SwPrintVersion(MV_VERSION_MAJOR, MV_VERSION_MINOR); return 1; case 'h': printf(MV_USAGE); return 1; default: assert(FALSE); Status = 1; goto MainEnd; } } ArgumentIndex = optind; if (ArgumentIndex > ArgumentCount) { ArgumentIndex = ArgumentCount; } if (ArgumentIndex < ArgumentCount) { 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, or a link to a directory. // TargetIsDirectory = FALSE; Status = SwStat(Target, FALSE, &Stat); if (Status == 0) { if (S_ISLNK(Stat.st_mode)) { Status = SwReadLink(Target, &LinkDestination); if (Status == 0) { Target = LinkDestination; Status = SwStat(Target, FALSE, &Stat); if (Status == 0) { if (S_ISDIR(Stat.st_mode)) { TargetIsDirectory = TRUE; } } } } else if (S_ISDIR(Stat.st_mode)) { TargetIsDirectory = TRUE; } } else if (errno != ENOENT) { TotalStatus = errno; SwPrintError(TotalStatus, Target, "Failed to stat 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 move 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; } // // Create an appended version of the path if the target is a directory. // if (TargetIsDirectory != FALSE) { Source = basename(Argument); if (Source == NULL) { SwPrintError(errno, Argument, "Unable to get base name of"); TotalStatus = 1; continue; } Status = SwAppendPath(Target, strlen(Target) + 1, Source, strlen(Source) + 1, &AppendedPath, &AppendedPathSize); if (Status == FALSE) { TotalStatus = EINVAL; continue; } Status = MvMove(Options, Argument, AppendedPath); free(AppendedPath); } else { Status = MvMove(Options, Argument, Target); } if (Status != 0) { TotalStatus = Status; } } MainEnd: if (LinkDestination != NULL) { free(LinkDestination); } return TotalStatus; } // // --------------------------------------------------------- Internal Functions // INT MvMove ( INT Options, PSTR Argument, PSTR Target ) /*++ Routine Description: This routine is the workhorse behind the mv application. It moves a source to a destination. Arguments: Options - Supplies the application options. Argument - Supplies the object to move. Target - Supplies the target to move it to. Return Value: Returns an integer exit code. 0 for success, nonzero otherwise. --*/ { BOOL Answer; ULONG CopyOptions; PSTR QuotedArgument; PSTR QuotedTarget; int Result; struct stat Stat; struct stat TargetStat; int TargetStatResult; TargetStatResult = SwStat(Target, FALSE, &TargetStat); if ((TargetStatResult != 0) && (errno != ENOENT)) { Result = errno; SwPrintError(Result, Target, "Could not stat"); goto MoveEnd; } // // If the destination exists, the force option is off, and either // 1) The permissions don't allow writing and stdin is a terminal, or // 2) The interactive option is enabled, // // then print a prompt. // if ((TargetStatResult == 0) && ((Options & MV_OPTION_FORCE) == 0) && ((((TargetStat.st_mode & S_IWUSR) == 0) && ((Options & MV_OPTION_STDIN_IS_TERMINAL) == 0)) || ((Options & MV_OPTION_INTERACTIVE) != 0))) { QuotedArgument = SwQuoteArgument(Argument); fprintf(stderr, "mv: Overwrite file '%s'? ", QuotedArgument); if (QuotedArgument != Argument) { free(QuotedArgument); } Result = SwGetYesNoAnswer(&Answer); if ((Result != 0) || (Answer == FALSE)) { goto MoveEnd; } } // // In verbose mode, print out what's going on. // if ((Options & MV_OPTION_VERBOSE) != 0) { QuotedArgument = SwQuoteArgument(Argument); QuotedTarget = SwQuoteArgument(Target); printf("'%s' -> '%s'\n", QuotedArgument, QuotedTarget); if (QuotedArgument != Argument) { free(QuotedArgument); } if (QuotedTarget != Target) { free(QuotedTarget); } } // // First try out a rename, and happily exit if it worked. // Result = rename(Argument, Target); if (Result == 0) { goto MoveEnd; } Result = errno; if ((Result != EXDEV) && ((Result != EEXIST) && ((Options & MV_OPTION_FORCE) == 0))) { SwPrintError(Result, Argument, "Could not move"); goto MoveEnd; } // // Stat the source. // Result = SwStat(Argument, FALSE, &Stat); if (Result != 0) { Result = errno; SwPrintError(Result, Argument, "Could not stat"); goto MoveEnd; } // // There's more work to be done if the destination exists. // if (TargetStatResult == 0) { // // If the destination is a directory and the source is not or vice // versa, fail. // if ((S_ISDIR(TargetStat.st_mode)) != (S_ISDIR(Stat.st_mode))) { Result = ENOTDIR; if (S_ISDIR(TargetStat.st_mode)) { Result = EISDIR; } SwPrintError(Result, Target, "Could not move to target"); goto MoveEnd; } // // Try to remove the destination. // if (S_ISDIR(TargetStat.st_mode)) { Result = SwRemoveDirectory(Target); } else { Result = SwUnlink(Target); } if (Result != 0) { Result = errno; SwPrintError(Result, Target, "Could not remove target"); goto MoveEnd; } // // Try rename one more time just for fun. // Result = rename(Argument, Target); if (Result == 0) { goto MoveEnd; } } // // Attempt to duplicate the file hierarchy, then delete the old file // hierarchy. // CopyOptions = COPY_OPTION_FOLLOW_OPERAND_LINKS | COPY_OPTION_PRESERVE_PERMISSIONS | COPY_OPTION_RECURSIVE; Result = SwCopy(CopyOptions, Argument, Target); if (Result != 0) { QuotedArgument = SwQuoteArgument(Argument); SwPrintError(Result, Target, "Failed to copy '%s' to", QuotedArgument); goto MoveEnd; } Result = SwDelete(DELETE_OPTION_FORCE | DELETE_OPTION_RECURSIVE, Argument); if (Result != 0) { SwPrintError(Result, Argument, "Failed to remove"); goto MoveEnd; } MoveEnd: return Result; }