Rewrite ln(1) to be standards compliant.
This commit is contained in:
parent
d3a2eb8a79
commit
ff1a90c331
260
utils/ln.c
260
utils/ln.c
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2013, 2018 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2013, 2018, 2020 Jonas 'Sortie' Termansen.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -19,8 +19,9 @@
|
|||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <error.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
@ -28,6 +29,179 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool lnat(const char* source,
|
||||
int source_dirfd,
|
||||
const char* source_basename,
|
||||
const char* target,
|
||||
int target_dirfd,
|
||||
const char* target_basename,
|
||||
bool force,
|
||||
bool symbolic,
|
||||
bool physical,
|
||||
bool no_dereference,
|
||||
bool no_target_directory,
|
||||
bool verbose)
|
||||
{
|
||||
for ( int attempt = 0; true; attempt++ )
|
||||
{
|
||||
if ( (symbolic ?
|
||||
symlinkat(source, target_dirfd, target_basename) :
|
||||
linkat(source_dirfd, source_basename, target_dirfd,
|
||||
target_basename, physical ? 0 : AT_SYMLINK_FOLLOW)) < 0 )
|
||||
{
|
||||
int error = errno;
|
||||
if ( !attempt && error == EEXIST && !no_target_directory )
|
||||
{
|
||||
struct stat target_st;
|
||||
if ( !fstatat(target_dirfd, target_basename, &target_st,
|
||||
no_dereference ? AT_SYMLINK_NOFOLLOW : 0) &&
|
||||
S_ISDIR(target_st.st_mode) )
|
||||
{
|
||||
int new_target_dirfd = openat(target_dirfd, target_basename,
|
||||
O_RDONLY | O_DIRECTORY);
|
||||
if ( new_target_dirfd < 0 )
|
||||
{
|
||||
warn("%s", target);
|
||||
return false;
|
||||
}
|
||||
char* new_target;
|
||||
if ( asprintf(&new_target, "%s/%s", target,
|
||||
source_basename) < 0 )
|
||||
err(1, "malloc");
|
||||
const char* new_target_basename = source_basename;
|
||||
bool result =
|
||||
lnat(source, source_dirfd, source_basename,
|
||||
new_target, new_target_dirfd, new_target_basename,
|
||||
force, symbolic, physical, no_dereference,
|
||||
no_target_directory, verbose);
|
||||
free(new_target);
|
||||
close(new_target_dirfd);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if ( !attempt && error == EEXIST && force )
|
||||
{
|
||||
if ( !symbolic && !strcmp(source_basename, target_basename) )
|
||||
{
|
||||
struct stat source_dirst, target_dirst;
|
||||
fstat(source_dirfd, &source_dirst);
|
||||
fstat(target_dirfd, &target_dirst);
|
||||
if ( source_dirst.st_dev == target_dirst.st_dev &&
|
||||
source_dirst.st_ino == target_dirst.st_ino )
|
||||
{
|
||||
warnx("'%s' and '%s' are the same file", source,
|
||||
target);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( unlinkat(target_dirfd, target_basename, 0) < 0 )
|
||||
{
|
||||
warn("unlink: %s", target);
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
errno = error;
|
||||
warn("%s: %s -> %s", symbolic ? "symlink" : "link", source, target);
|
||||
return false;
|
||||
}
|
||||
if ( verbose )
|
||||
printf("`%s' => `%s'\n", source, target);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Retains the trailing slashes unlike basename(3) so ln foo/ bar/ fails.
|
||||
static const char* basename_with_slashes(const char* path)
|
||||
{
|
||||
if ( !path || !*path )
|
||||
return ".";
|
||||
size_t offset = strlen(path);
|
||||
while ( offset && path[offset - 1] == '/' )
|
||||
offset--;
|
||||
while ( offset && path[offset - 1] != '/' )
|
||||
offset--;
|
||||
return path + offset;
|
||||
}
|
||||
|
||||
static bool ln(const char* source,
|
||||
const char* target,
|
||||
bool force,
|
||||
bool symbolic,
|
||||
bool physical,
|
||||
bool no_dereference,
|
||||
bool no_target_directory,
|
||||
bool verbose)
|
||||
{
|
||||
char* source_dup = strdup(source);
|
||||
if ( !source_dup )
|
||||
err(1, "malloc");
|
||||
const char* source_basename = basename_with_slashes(source);
|
||||
char* source_dirname = dirname(source_dup);
|
||||
int source_dirfd = symbolic ?
|
||||
AT_FDCWD :
|
||||
open(source_dirname, O_RDONLY | O_DIRECTORY);
|
||||
if ( !symbolic && source_dirfd < 0 )
|
||||
{
|
||||
warn("%s", source);
|
||||
free(source_dup);
|
||||
return false;
|
||||
}
|
||||
char* target_dup = strdup(target);
|
||||
if ( !target_dup )
|
||||
err(1, "malloc");
|
||||
const char* target_basename = basename_with_slashes(target);
|
||||
char* target_dirname = dirname(target_dup);
|
||||
int target_dirfd = open(target_dirname, O_RDONLY | O_DIRECTORY);
|
||||
if ( target_dirfd < 0 )
|
||||
{
|
||||
warn("%s", target);
|
||||
if ( symbolic )
|
||||
close(source_dirfd);
|
||||
free(source_dup);
|
||||
free(target_dup);
|
||||
return false;
|
||||
}
|
||||
bool result = lnat(source, source_dirfd, source_basename,
|
||||
target, target_dirfd, target_basename,
|
||||
force, symbolic, physical, no_dereference,
|
||||
no_target_directory, verbose);
|
||||
close(source_dirfd);
|
||||
close(target_dirfd);
|
||||
free(source_dup);
|
||||
free(target_dup);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool ln_into_directory(const char* source,
|
||||
const char* target,
|
||||
bool force,
|
||||
bool symbolic,
|
||||
bool physical,
|
||||
bool no_dereference,
|
||||
bool verbose)
|
||||
{
|
||||
char* source_copy = strdup(source);
|
||||
if ( !source_copy )
|
||||
err(1, "malloc");
|
||||
const char* base_name = basename(source_copy);
|
||||
size_t source_length = strlen(source);
|
||||
bool has_slash =
|
||||
source_length && source[source_length - 1] == '/';
|
||||
char* new_target;
|
||||
if ( asprintf(&new_target,
|
||||
"%s%s%s",
|
||||
target,
|
||||
has_slash ? "" : "/",
|
||||
base_name) < 0 )
|
||||
err(1, "malloc");
|
||||
free(source_copy);
|
||||
bool ret = ln(source, new_target, force, symbolic, physical, no_dereference,
|
||||
true, verbose);
|
||||
free(new_target);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void compact_arguments(int* argc, char*** argv)
|
||||
{
|
||||
for ( int i = 0; i < *argc; i++ )
|
||||
|
@ -41,24 +215,15 @@ static void compact_arguments(int* argc, char*** argv)
|
|||
}
|
||||
}
|
||||
|
||||
static void help(FILE* fp, const char* argv0)
|
||||
{
|
||||
fprintf(fp, "Usage: %s [OPTION]... TARGET LINK_NAME\n", argv0);
|
||||
fprintf(fp, "Create a hard or symbolic link.\n");
|
||||
}
|
||||
|
||||
static void version(FILE* fp, const char* argv0)
|
||||
{
|
||||
fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
const char* argv0 = argv[0];
|
||||
bool force = false;
|
||||
bool symbolic = false;
|
||||
bool physical = true;
|
||||
bool no_dereference = false;
|
||||
bool no_target_directory = false;
|
||||
bool verbose = false;
|
||||
|
||||
for ( int i = 1; i < argc; i++ )
|
||||
{
|
||||
const char* arg = argv[i];
|
||||
|
@ -73,66 +238,53 @@ int main(int argc, char* argv[])
|
|||
while ( (c = *++arg) ) switch ( c )
|
||||
{
|
||||
case 'f': force = true; break;
|
||||
case 'L': physical = false; break;
|
||||
case 'n': no_dereference = true; break;
|
||||
case 'P': physical = true; break;
|
||||
case 's': symbolic = true; break;
|
||||
case 'T': no_target_directory = true; break;
|
||||
case 'v': verbose = true; break;
|
||||
default:
|
||||
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
|
||||
help(stderr, argv0);
|
||||
exit(1);
|
||||
errx(1, "unknown option -- '%c'", c);
|
||||
}
|
||||
}
|
||||
else if ( !strcmp(arg, "--force") )
|
||||
force = true;
|
||||
else if ( !strcmp(arg, "--logical") )
|
||||
physical = false;
|
||||
else if ( !strcmp(arg, "--physical") )
|
||||
physical = true;
|
||||
else if ( !strcmp(arg, "--symbolic") )
|
||||
symbolic = true;
|
||||
else if ( !strcmp(arg, "--verbose") )
|
||||
verbose = true;
|
||||
else if ( !strcmp(arg, "--help") )
|
||||
help(stdout, argv0), exit(0);
|
||||
else if ( !strcmp(arg, "--version") )
|
||||
version(stdout, argv0), exit(0);
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: unknown option: %s\n", argv0, arg);
|
||||
help(stderr, argv0);
|
||||
exit(1);
|
||||
}
|
||||
errx(1, "unknown option: %s", arg);
|
||||
}
|
||||
|
||||
compact_arguments(&argc, &argv);
|
||||
|
||||
if ( argc != 3 )
|
||||
error(1, 0, "%s operand", argc < 3 ? "missing" : "extra");
|
||||
if ( no_target_directory && argc != 3 )
|
||||
errx(1, "unexpected extra operand");
|
||||
|
||||
const char* oldname = argv[1];
|
||||
const char* newname = argv[2];
|
||||
if ( argc == 2 )
|
||||
return ln_into_directory(argv[1], ".", force, symbolic, physical,
|
||||
no_dereference, verbose) ? 0 : 1;
|
||||
|
||||
bool done = false;
|
||||
again:
|
||||
if ( force )
|
||||
unlink(newname);
|
||||
if ( argc == 3 )
|
||||
return ln(argv[1], argv[2], force, symbolic, physical, no_dereference,
|
||||
no_target_directory, verbose) ? 0 : 1;
|
||||
|
||||
struct stat st;
|
||||
int ret = (symbolic ? symlink : link)(oldname, newname);
|
||||
if ( ret == 0 )
|
||||
const char* target = argv[argc - 1];
|
||||
bool success = true;
|
||||
|
||||
for ( int i = 1; i < argc - 1; i++ )
|
||||
{
|
||||
if ( verbose )
|
||||
printf("`%s' => `%s'\n", newname, oldname);
|
||||
const char* source = argv[i];
|
||||
if ( !ln_into_directory(source, target, force, symbolic, physical,
|
||||
no_dereference, verbose) )
|
||||
success = false;
|
||||
}
|
||||
else if ( !done && errno == EEXIST && !no_target_directory &&
|
||||
lstat(newname, &st) == 0 && S_ISDIR(st.st_mode) )
|
||||
{
|
||||
char* oldnamecopy = strdup(oldname);
|
||||
const char* name = basename(oldnamecopy);
|
||||
char* newnewname;
|
||||
asprintf(&newnewname, "%s/%s", newname, name);
|
||||
free(oldnamecopy);
|
||||
newname = newnewname;
|
||||
done = true;
|
||||
goto again;
|
||||
}
|
||||
else
|
||||
error(0, errno, "`%s' => `%s'", newname, oldname);
|
||||
return ret ? 1 : 0;
|
||||
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue