Recursively deleting symbolic link and what it points to
Recently I looked for a solution to this little problem. how do you, programmatically, delete a symbolic link and a file that it points to?
One problem that you should take care of when tackling this problem, is that symbolic link can point to a symbolic link. Then symbolic link should also point to symbolic link. And once again, and again and again…
So what can we do about it? First, lets start with lstat() system all. It will tell us is the file we’re interested in, is actually a symbolic link. To be more precise, st_mode field in struct stat will have flag S_IFLNK if specified file is a symbolic link. Note that it should be lstat() and not stat() – the later will return information about file the link points to and not about the link.
Next step is to call readlink(). This system call returns name of the file pointed to by specified symbolic link.
Finally, you should repeat the process recursively, for the pointed to file. Here is the code that does it:
int recursive_deleter(const char* filename) { struct stat st; char buffer[1024]; if (lstat(filename, &st)) { perror("stat"); return -1; } if (st.st_mode & S_IFLNK == S_IFLNK) { memset(buffer, 0, sizeof(buffer)); if (readlink(filename, buffer, sizeof(buffer)) < 0) { perror("readlink"); return -1; } printf("File %s is a symbolic link to %s\n", filename, buffer); if (recursive_deleter(buffer)) return 0; } printf("Deleting %s\n", filename); if (unlink(filename)) { perror("unlink"); return -1; } return 0; }
Have fun!
An interesting problem. Assuming I wasn’t writing a C program to start with I’d have initially tackled it at the shell.
seq 10 98 | while read f; do ln -s $((f+1)) $f; done; >99
f=10; while [[ -L $f || -e $f ]]; do n=$(readlink “$f”); rm “$f”; f=”$n”; done
Originally I had “while [[ $f ]]” but this causes rm to attempt to remove a non-existent file if a symlink is broken. Adding -e alone doesn’t work because [[ -e 10 ]] is false as bash gives up after following a few of the symlinks so we need to continue whilst it’s either a symlink or it really does exist.
Wow. I am speechless.
What if somebody has some sense of humour and will prepare circular-symlink structure for you to stumble upon?
No problems. Linux has code that checks for circular symbolic links.
@Ralph Corderoy – I guess You meant ln -s 99 10 to complete the circle.
Your code is OK, as it firstly removes link, and then descends to linked file. If it’s linking back to previously-visited, it’s already dead-link when reached, so infinite loop is avoided.
My concern was however about Alexander’s C-code, which does it in reverese order, so infinite loops are possible. Firstly unlinking and then calling recursive_deleter would solve the problem.
Hi Jaroslaw, no, ln -s 10 99, as I said, to create 99 -> 10, completing the circle. ln -s a b creates b, and points it to a; think of ln -s as cp(1) with b being the destination/the thing to create.
@Alexander_Sandler
“No problems. Linux has code that checks for circular symbolic links.”
And does what exactly when circular structures found? If you try to open file, that turns out to be circular symlink, linux will handle it correctly – return error code. But here, you take over entire symlink navigation, as you are using syscalls (‘fstat’, ‘readlink’, ‘unlink’) that don’t really care what this symlink points to, so they cannot know if your structure is circular. Which of those functions should return error (or anyhow else break infinite recursion) in case of circular-symlinks?
change: if (st.st_mode & S_IFLNK == S_IFLNK)
to: if (st.st_mode & S_S_IFMT == S_IFLNK)
S_IFMT, sorry