Questions about FileUtils.ln_sf and existing symlinks

Hello. I’m actually programming a software manager to install a Linux system from a partition.

Actually I have a function like that:

def makeSymbolicLink(path : String, targetPath : String)
            begin
                FileUtils.ln_sf(path, targetPath)
            rescue
                Ism.notifyOfMakeSymbolicLinkError(path, targetPath)
                exit 1
            end
        end

I just would like to know how can I avoid the rescue if the symlink already exist ?

Can you share the actual exception type/message you’re getting? The API docs seem to indicate this method should just override it if it already exists, not error.

It’s the result I have when I start to install Glibc-pass1 with my software ISM:

zohran@zohran-VirtualBox:~/ism$ crystal Main.cr -so -i libstdc++-pass1
ISM start to calculate depencies: Done !

	Glibc-Pass1 /2.34/  { Multilib }
	Libstdc++-Pass1 /11.2.0/  { }

2 new softwares will be install

Would you like to install these softwares ?[y/n]n
zohran@zohran-VirtualBox:~/ism$ crystal Main.cr -so -i libstdc++-pass1
ISM start to calculate depencies: Done !

	Glibc-Pass1 /2.34/  { Multilib }
	Libstdc++-Pass1 /11.2.0/  { }

2 new softwares will be install

Would you like to install these softwares ?[y/n]y

<< [1 / 2] Installing Glibc-Pass1

* Downloading Glibc-Pass1
--2022-06-24 16:54:57--  https://ftp.gnu.org/gnu/glibc/glibc-2.34.tar.xz
Resolving ftp.gnu.org (ftp.gnu.org)... 209.51.188.20, 2001:470:142:3::b
Connecting to ftp.gnu.org (ftp.gnu.org)|209.51.188.20|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17301232 (16M) [application/x-xz]
Saving to: ‘glibc-2.34.tar.xz’

glibc-2.34.tar.xz         100%[==================================>]  16.50M  7.17MB/s    in 2.3s    

2022-06-24 16:55:00 (7.17 MB/s) - ‘glibc-2.34.tar.xz’ saved [17301232/17301232]

* Checking Glibc-Pass1
* Extracting Glibc-Pass1
* Patching Glibc-Pass1
* Preparing Glibc-Pass1
[!] Failed to make symbolic link from /mnt/lib/ld-linux-x86-64.so.2 to /mnt/lib64

It use the function I posted you before. (it’s the result of a test under virtualbox, and it follow this LFS book: Linux From Scratch, under “Compiling a Cross-Toolchain” section )

Sorry but this doesn’t help. It’s going to say Failed to make symbolic link from /mnt/lib/ld-linux-x86-64.so.2 to /mnt/lib64 because that what you’re doing in your rescue logic. But your logic also will say that for any exception. So it’s entirely possible that its failing not because it already exists but for some other reason.

What’s the output if you update the method to be like:

def makeSymbolicLink(path : String, targetPath : String)
            begin
                FileUtils.ln_sf(path, targetPath)
            rescue ex
                pp ex
                exit 1
            end
        end

It returned this:

zohran@zohran-VirtualBox:~/ism$ crystal Main.cr -so -i libstdc++-pass1
ISM start to calculate depencies: Done !

	Glibc-Pass1 /2.34/  { Multilib }
	Libstdc++-Pass1 /11.2.0/  { }

2 new softwares will be install

Would you like to install these softwares ?[y/n]y

<< [1 / 2] Installing Glibc-Pass1

* Downloading Glibc-Pass1
--2022-06-24 16:54:57--  https://ftp.gnu.org/gnu/glibc/glibc-2.34.tar.xz
Resolving ftp.gnu.org (ftp.gnu.org)... 209.51.188.20, 2001:470:142:3::b
Connecting to ftp.gnu.org (ftp.gnu.org)|209.51.188.20|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17301232 (16M) [application/x-xz]
Saving to: ‘glibc-2.34.tar.xz’

glibc-2.34.tar.xz         100%[==================================>]  16.50M  7.17MB/s    in 2.3s    

2022-06-24 16:55:00 (7.17 MB/s) - ‘glibc-2.34.tar.xz’ saved [17301232/17301232]

* Checking Glibc-Pass1
* Extracting Glibc-Pass1
* Patching Glibc-Pass1
* Preparing Glibc-Pass1
[!] Failed to make symbolic link from /mnt/lib/ld-linux-x86-64.so.2 to /mnt/lib64
zohran@zohran-VirtualBox:~/ism$ crystal Main.cr -so -i libstdc++-pass1
ISM start to calculate depencies: Done !

	Glibc-Pass1 /2.34/  { Multilib }
	Libstdc++-Pass1 /11.2.0/  { }

2 new softwares will be install

Would you like to install these softwares ?[y/n]y

<< [1 / 2] Installing Glibc-Pass1

* Downloading Glibc-Pass1
--2022-06-24 17:42:04--  https://ftp.gnu.org/gnu/glibc/glibc-2.34.tar.xz
Resolving ftp.gnu.org (ftp.gnu.org)... 209.51.188.20, 2001:470:142:3::b
Connecting to ftp.gnu.org (ftp.gnu.org)|209.51.188.20|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17301232 (16M) [application/x-xz]
Saving to: ‘glibc-2.34.tar.xz’

glibc-2.34.tar.xz         100%[==================================>]  16.50M  6.99MB/s    in 2.4s    

2022-06-24 17:42:06 (6.99 MB/s) - ‘glibc-2.34.tar.xz’ saved [17301232/17301232]

* Checking Glibc-Pass1
* Extracting Glibc-Pass1
* Patching Glibc-Pass1
* Preparing Glibc-Pass1
#<File::AlreadyExistsError:Error creating symlink: '/mnt/lib/ld-linux-x86-64.so.2' -> '/mnt/lib64/ld-linux-x86-64.so.2': File exists>

So what I expected

:+1: perfect thanks. To solve this you could prob just add like:

which would handle deleting it if it already exists, and the method itself would handle targetPath.

EDIT: Think this actually a bug. See Questions about FileUtils.ln_sf and existing symlinks - #8 by Blacksmoke16.

Shouldn’t ln_sf override any existing links?

It does tho. Creating a file input.xml and running:

require "file_utils"

FileUtils.ln_s("input.xml", "data.xml")

Results in data.xml -> input.xml being created. Running it again produces Unhandled exception: Error creating symlink: 'input.xml' -> 'data.xml': File exists (File::AlreadyExistsError). But using ln_sf works fine and updates the modtime of the link.

BUT, I think what’s going on is if you run like:

require "file_utils"

FileUtils.ln_sf("data.txt", "i_dont_exist.txt")

It’ll create i_dont_exist.txt -> data.txt, but if you run it again, Unhandled exception: Error creating symlink: 'data.txt' -> 'i_dont_exist.txt': File exists (File::AlreadyExistsError). Which seems to be because File.file? within crystal/file_utils.cr at b7377c0419ae5c1ce91e8298f075b3ff15636c54 · crystal-lang/crystal · GitHub, is resolving the symlink and returning true because the destination of the link is a file. But if you create a symlink to a file that doesn’t exist, it returns false, and doesn’t delete it.

So in short, that probably just needs to be File.delete(dest_path) if File.symlink?(dest_path)

EDIT: Also the difference in ls -la and the exception message is super confusing.

the -f in ln -f is exactly just delete it before create a symlink.