8

I'm writing an (un)archiving tool and the way it is designed it first creates a regular file from the archive before it examines the special attributes and may decide that this item is a symlink, in fact.

Note: Before more people misunderstand me for wanting to make a symlink of a file. No, I write the symlink data, i.e. its path, into the file, and then I want to tell the file system that this is a symlink

I've been developing this on OS X, where it's possible to turn a regular file into a symlink by simply setting its Type and Creator codes accordingly.

Now I like to get this code working on Linux as well. So I like to find a similar way there.

I am aware that the normal way to create a symlink is to call the symlink() function, but I wonder if there is also a way to change a regular file into a symlink, just like it's possible in OSX's BSD system, so that I do not have to refactor my working code too much?

There is lstat(), which returns the file type in st_mode's upmost bits. Now I wonder if there's also an analogous setter function for this mode field.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
  • Checking if I understand: you want to transform a regular file into a symlink that points to what the file contained? – Ciro Santilli OurBigBook.com Sep 09 '14 at 12:36
  • Ralated to the newer question ["What is there behind a symbolic link?"](https://stackoverflow.com/q/16912997). From the accepted answer i concluded that the difference between an ordinary file and a symlink is in some inode flag, so i would imagine that flipping that flag should be enough, but i guess it is not that easy. – Alexey Feb 03 '19 at 11:30

3 Answers3

4

I don't believe there is a way in Linux to do this as you describe. IIRC, the filesystem stores symlink information in the inode table and not in a regular file so there's no direct way of turning a file into a link.

If the symlink's path is stored inside the file, why not read out the path, delete the file, and create a symlink in its place?

bta
  • 43,959
  • 6
  • 69
  • 99
  • I accept this answer not for its rather obvious work-around suggestion, but for the explanation for why there is not a way to do what I wanted to do. I hope your explanation is correct :) – Thomas Tempelmann Feb 25 '10 at 10:18
  • Maybe I am confused of have not read carefully enough, but according to [this answer](https://stackoverflow.com/a/16914134), i concluded that a symlink is just a file with a special flag in the inode. Can't this flag just be flipped then? – Alexey Feb 03 '19 at 11:27
  • @Alexy It's more complicated than that because you have kernel-level inodes plus a whole bunch of implementation-specific stuff that varies from filesystem to filesystem. If you find yourself needing to manipulate symlinks at this low of a level, then you're probably doing something the hard way. – bta Feb 04 '19 at 22:12
3

Demonstrating what I wrote as a comment to bmarguiles's answer,

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv) {
    char *buffer = 0, *name = 0;
    int i;
    for (i = 1; i < argc; i++) {
        struct stat st;
        int fd = open(argv[i], O_RDONLY);
        fstat(fd, &st);
        buffer = realloc(buffer, st.st_size + 1);
        read(fd, buffer, st.st_size);
        close(fd);
        buffer[st.st_size] = '\0';
        name = realloc(name, strlen(argv[i]) + 2);
        sprintf(name, "%s~", argv[i]);
        symlink(buffer, name);
        rename(name, argv[i]);
    }
    free(buffer);
    free(name);
    return 0;
}
$ vi f2s.c
...
$ cc -o f2s f2s.c
$ echo -n / > test
$ ./f2s test
$ ls -l test
lrwxrwxrwx 1 me me 1 Feb 24 23:17 test -> /
$ echo -n / > test2
$ strace ./f2s test2
open("test2", O_RDONLY)                 = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=1, ...}) = 0
read(3, "/", 1)                         = 1
close(3)                                = 0
symlink("/", "test2~")                  = 0
rename("test2~", "test2")               = 0

This is just a demonstration; it really needs more error-handling and maybe a better temporary filename.

ephemient
  • 198,619
  • 38
  • 280
  • 391
  • I'm curious why the use of `realloc()` over `malloc()` ? – SiegeX Feb 25 '10 at 05:03
  • Mostly because I'm lazy and only felt like typing a single `free` at the end. This isn't production-quality code. – ephemient Feb 25 '10 at 05:06
  • You're not as lazy as you think, you already have two `free()`'s =) – SiegeX Feb 25 '10 at 05:20
  • Haha yeah, and in retrospect, moving to a `malloc+free` inside the loop would actually be exactly as much work for me to type it out. Still, I wrote this quickly in the Stack Overflow answer form -- you're lucky I even bothered to copy, compile, and test it :-) – ephemient Feb 25 '10 at 05:37
  • Thanks for making the effort to demonstrate this. This was not really necessary but I appreciate the effort. I'm torn between accepting your answer for the extra effort vs bta's for its spot-on reply to my question. I'll give you another up and him the checkmark, as you already have two ups. I hope that feels not too unfair. – Thomas Tempelmann Feb 25 '10 at 10:15
1

No, you can't turn one into the other. You have to unlink to kill the file and then symlink to create a symlink as a replacement.

bmargulies
  • 97,814
  • 39
  • 186
  • 310
  • 1
    It would be better to create a symlink with a different name, then rename it over the original file. This is an atomic replacement, and there won't be a gap where the filename is missing (as happens following your unlink). – ephemient Feb 25 '10 at 03:48