简体   繁体   中英

How to create a hard link to an existing file or directory in C?

I am looking to create a hard link to a file or directory that already exists. For instance a user chooses to create a hard link to a file named "pathname" and the user can choose the name of the link file.

On most modern versions of Unix (or variants of Unix), you cannot create a hard link to a directory. POSIX allows it if you have sufficient privileges and the system supports it, but some (I believe most) systems do not allow it.

To create a hard link, you need to use the link() function (system call):

if (link(existing_file, new_name) != 0)
    …link failed…

Note that the new name must be complete, unlike the ln command. You cannot specify a directory as the new name; you must specify the file name within the directory.


Can you be a bit more specific about how the link() function works?

If invoked as link(source, target) , then a file of some sort must exist with the name in source (it can't be a directory unless perhaps you have 'appropriate privileges' — which means superuser or root privileges), and there must not be a file with the name in target , but all the directories leading to the file named by target must exist. Assuming the preconditions are met, after the system call is successful, then you can refer to the same file contents by either the name in source or the name in target .

For instance if I ask a user to choose an existing path and then choose a name for the hard link being created, how would I go about this?

FWIW, don't bother with prompting — use command line arguments, like the ln command does.

I also read that I have to unlink the original file.

You probably don't, but that depends on the semantics of what you want to achieve, and what you mean by 'original file'. It would be possible to write code that removes the target file if it already exists (like ln -f source target would remove the target file if it already exists). It would be possible to write code that removes the source file name after the link is successful (like mv source target — note the different command name). It would be possible to write code that attempts to ensure that all the directories leading to the target are created if they don't already exist (like mkdir -p $(dirname target) ). Etc. You could decide to allow a target directory to be specified by a command line option, instead of just using the last argument as the target directory. Etc. There are lots of possibilities — you just have to decide what semantics you want and implement them. Note that the rules for symbolic (soft) links and the symlink() function are different from the rules for hard links.

Here's some code (source file link37.c ):

#include "stderr.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    err_setarg0(argv[0]);

    if (argc != 3)
        err_usage("source target");
    char *source = argv[1];
    char *target = argv[2];
    struct stat sb;
    if (stat(source, &sb) != 0)
        err_syserr("cannot access source file '%s': ", source);
    if (stat(target, &sb) == 0)
    {
        if (!S_ISDIR(sb.st_mode))
            err_error("name '%s' exists and is not a directory\n", target);
        else
        {
            char *slash = strrchr(source, '/');
            if (slash == 0)
                slash = source;
            else
                slash++;
            if (*slash == '\0')
                err_error("name '%s' cannot end with a slash\n", source);
            size_t baselen = strlen(target);
            size_t filelen = strlen(slash);
            size_t namelen = baselen + filelen + 2;
            char *name = malloc(namelen);
            if (name == 0)
                err_syserr("failed to allocate %zu bytes memory: ", namelen);
            memmove(name, target, baselen);
            memmove(name + baselen, "/", 1);
            memmove(name + baselen + 1, slash, filelen + 1);
            target = name;
        }
    }
    if (link(source, target) != 0)
        err_syserr("failed to link '%s' to '%s': ", source, target);
    if (target != argv[2])
        free(target);
    return 0;
}

The free() at the end is not really necessary. The functions starting err_ are available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory. They're how I report errors in programs. Some systems have a header <err.h> and a motley assortment of functions that do some of what my package does — have fun with them (I don't like them, but there's a strong case of NIH syndrome too). There are other ways to concatenate the file name components; one would be to use sprintf(name, "%s/%s", target, slash) instead of 3 memmove() operations.

Sample run:

$ link37 link37.c chameleon
$ mkdir -p doc
$ link37 link37.c doc
$ link37 /Users/jonathanleffler/soq/ src
link37: name '/Users/jonathanleffler/soq/' cannot end with a slash
$ link37 /Users/jonathanleffler/soq src
link37: failed to link '/Users/jonathanleffler/soq' to 'src/soq': error (1) Operation not permitted
$ link37 link37.c chameleon
link37: name 'chameleon' exists and is not a directory
$ link37 /no/where/file.c /some/where/
link37: cannot access source file '/no/where/file.c': error (2) No such file or directory
$ link37 link37.c /some/where/
link37: failed to link 'link37.c' to '/some/where/': error (2) No such file or directory
$ rm -f doc/link37.c chameleon
$ rmdir doc 2>/dev/null
$

I had a directory doc already in existence (so the mkdir -p doc didn't do anything), but the rmdir doc at the end did no damage either. That's why I didn't use rm -fr doc though — I have information I want to keep in doc .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM