/*++ Copyright (c) 2013 Minoca Corp. All Rights Reserved Module Name: ln.c Abstract: This module implements the ln (link) utility. Author: Evan Green 22-Oct-2013 Environment: POSIX --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include #include #include #include #include #include "swlib.h" // // ---------------------------------------------------------------- Definitions // #define LN_VERSION_MAJOR 1 #define LN_VERSION_MINOR 0 #define LN_USAGE \ "usage: ln [-fs] source_file target_file\n" \ " ln [-fs] source_file ... target_directory\n" \ "The ln utility creates a symbolic or hard link to the given file or \n" \ "within the given target directory. Options are:\n" \ " -f, --force -- Remove existing destination files.\n" \ " -s, --symbolic -- Create symbolic links instead of hard links.\n" \ " -v, --verbose -- Print files being linked.\n" \ " --help -- Show this help text and exit.\n" \ " --version -- Print the application version information and exit.\n" #define LN_OPTIONS_STRING "fsv" // // Define ln options. // // // Set this option to remove existing destination files. // #define LN_OPTION_FORCE 0x00000001 // // Set this option to create symbolic links instead of hard ones. // #define LN_OPTION_SYMBOLIC 0x00000002 // // Set this option to print links created. // #define LN_OPTION_VERBOSE 0x00000004 // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // INT LnLink ( ULONG Options, PSTR Source, PSTR Destination ); // // -------------------------------------------------------------------- Globals // struct option LnLongOptions[] = { {"force", no_argument, 0, 'f'}, {"symbolic", no_argument, 0, 's'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {"verbose", no_argument, 0, 'v'}, {NULL, 0, 0, 0}, }; // // ------------------------------------------------------------------ Functions // INT LnMain ( INT ArgumentCount, CHAR **Arguments ) /*++ Routine Description: This routine is the main entry point for the ln (link) 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; INT Option; ULONG Options; PSTR SourceBaseName; ULONG SourceCount; struct stat Stat; int Status; PSTR Target; BOOL TargetIsDirectory; int TotalStatus; SourceCount = 0; Target = NULL; Options = 0; TotalStatus = 0; // // Process the control arguments. // while (TRUE) { Option = getopt_long(ArgumentCount, Arguments, LN_OPTIONS_STRING, LnLongOptions, NULL); if (Option == -1) { break; } if ((Option == '?') || (Option == ':')) { Status = 1; goto MainEnd; } switch (Option) { case 'f': Options |= LN_OPTION_FORCE; break; case 's': Options |= LN_OPTION_SYMBOLIC; break; case 'v': Options |= LN_OPTION_VERBOSE; break; case 'V': SwPrintVersion(LN_VERSION_MAJOR, LN_VERSION_MINOR); return 1; case 'h': printf(LN_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. // 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; } // // It is an error to specify more than one operand and the final one is // not an existing directory. // if ((SourceCount > 1) && (TargetIsDirectory == FALSE)) { SwPrintError(0, Target, "Target is not a directory"); Status = EINVAL; goto MainEnd; } // // If there are only two operands and the target is not a directory, then // just link the source and target. // if ((SourceCount == 1) && (TargetIsDirectory == FALSE)) { Status = LnLink(Options, Arguments[ArgumentIndex], Target); goto MainEnd; } assert(TargetIsDirectory != FALSE); // // Loop through the arguments again and perform the links. // while (ArgumentIndex < ArgumentCount) { Argument = Arguments[ArgumentIndex]; ArgumentIndex += 1; // // Skip the target. // if (Argument == Target) { continue; } // // Get the final name component of the source, because // "ln 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. // Status = SwAppendPath(Target, strlen(Target) + 1, SourceBaseName, strlen(SourceBaseName) + 1, &AppendedPath, &AppendedPathSize); if (Status == FALSE) { TotalStatus = EINVAL; continue; } Status = LnLink(Options, Argument, AppendedPath); free(AppendedPath); if (Status != 0) { TotalStatus = Status; } } MainEnd: if ((TotalStatus == 0) && (Status != 0)) { TotalStatus = Status; } return TotalStatus; } // // --------------------------------------------------------- Internal Functions // INT LnLink ( ULONG Options, PSTR Source, PSTR Destination ) /*++ Routine Description: This routine creates a link (hard or symbolic) to the source path at the destination path. Arguments: Options - Supplies link options. See LN_OPTION_* definitions. Source - Supplies a pointer to the source of the link. Destination - Supplies a pointer to the destination of the link. Return Value: Returns an integer exit code. 0 for success, nonzero otherwise. --*/ { PSTR LinkDestination; struct stat Stat; INT Status; LinkDestination = NULL; // // Check to see if the destination exists. // Status = SwStat(Destination, FALSE, &Stat); if (Status == 0) { // // It exists. If force is on, then try to unlink it. // if ((Options & LN_OPTION_FORCE) != 0) { Status = SwUnlink(Destination); if (Status != 0) { Status = errno; SwPrintError(Status, Destination, "Unable to delete"); goto LinkEnd; } // // Force is off, so just complain and exit. // } else { Status = EEXIST; SwPrintError(Status, Destination, "Cannot create link at"); goto LinkEnd; } } // // Create a symbolic or hard link. // if ((Options & LN_OPTION_SYMBOLIC) != 0) { if (SwSymlinkSupported != 0) { Status = SwCreateSymbolicLink(Source, Destination); } else { Status = SwCopy(0, Source, Destination); } if (Status != 0) { SwPrintError(Status, Destination, "Unable to link"); goto LinkEnd; } // // Create a hard link. // } else { // // If the source is a symbolic link, use its destination. // Status = SwStat(Source, FALSE, &Stat); if (Status != 0) { SwPrintError(Status, Source, "Unable to stat"); goto LinkEnd; } if (S_ISLNK(Stat.st_mode)) { Status = SwReadLink(Source, &LinkDestination); if (Status != 0) { SwPrintError(Status, Source, "Unable to read symbolic link"); goto LinkEnd; } Source = LinkDestination; } Status = SwCreateHardLink(Source, Destination); goto LinkEnd; } if ((Options & LN_OPTION_VERBOSE) != 0) { printf("'%s' => '%s'\n", Destination, Source); } LinkEnd: if (LinkDestination != NULL) { free(LinkDestination); } return Status; }