How to overwrite a symlink in Go?
Asked Answered
S

3

5

I would like to overwrite a symlink using Go but I couldn't find how to do it.

If I try to create the symlink and it already exists an error is returned.

My code:

err := os.Symlink(filePath, symlinkPath)
if err != nil {
    fmt.Println(err)
}

I guess the symlink must be removed and then created again. Is that right? If so, how can I unlink the symlink?

Seriema answered 20/5, 2016 at 11:42 Comment(0)
T
6

Note that @Vadyus's answer hides actual filesystem errors while running lstat. For example, if your disk is broken and Lstat fails, you will still run os.Remove and ignore its error (DANGEROUS, unless you like to debug things for hours).

The following snippets checks for file existence and other errors correctly:

if _, err := os.Lstat(symlinkPath); err == nil {
  if err := os.Remove(symlinkPath); err != nil {
      return fmt.Errorf("failed to unlink: %+v", err)
  }
} else if os.IsNotExist(err) {
    return fmt.Errorf("failed to check symlink: %+v", err)
}
Those answered 26/9, 2018 at 3:8 Comment(0)
G
8

Just check that symlink exists and delete it before creating new one

if _, err := os.Lstat(symlinkPath); err == nil {
  os.Remove(symlinkPath)
}
Gipon answered 20/5, 2016 at 11:50 Comment(2)
mmh yes, I've a bug in my app, it returns always "no such file or directory", that's why it works for me with err != nil. The file is just always removed... sorry for the noiseBailsman
I understand the problem, you use Stat instead of Lstat, now I use if _, err := os.Lstat(symlinkPath); err == nil { and it does the trick for me. I need to check if the symlink exists, not the target.Bailsman
T
6

Note that @Vadyus's answer hides actual filesystem errors while running lstat. For example, if your disk is broken and Lstat fails, you will still run os.Remove and ignore its error (DANGEROUS, unless you like to debug things for hours).

The following snippets checks for file existence and other errors correctly:

if _, err := os.Lstat(symlinkPath); err == nil {
  if err := os.Remove(symlinkPath); err != nil {
      return fmt.Errorf("failed to unlink: %+v", err)
  }
} else if os.IsNotExist(err) {
    return fmt.Errorf("failed to check symlink: %+v", err)
}
Those answered 26/9, 2018 at 3:8 Comment(0)
B
6

The other answers here are correct...but there are two small issues:

  • There is a tiny data race where the new symlink will be created elsewhere before here but after the delete, leaving it in a potentially inconsistent state.
  • If the program were to die / crash before the symlink is created but after the previous one is deleted, it may again leave things in an inconsistent state.

The more atomic way to handle this is to create a temporary symlink and then rename it over the original:

symlinkPathTmp := symlinkPath + ".tmp"
if err := os.Remove(symlinkPathTmp); err != nil && !os.IsNotExist(err) {
  return err
}

if err := os.Symlink(filePath, symlinkPathTmp); err != nil {
  return err
}

if err := os.Rename(symlinkPathTmp, symlinkPath); err != nil {
  return err
}

There is still a small race between deleting the temporary link and re-creating it, but it won't risk leaving the primarily link in an inconsistent state. Ideally, we would be able to work around that by using a randomized name for the temporary link, but Go's TempFile always creates a new file, so it isn't quite as useful. (You could maybe call TempFile, then delete the file name and re-use the name, which would be riskier but still safer then just appending a constant .tmp suffix.)

Even with that race however, you still gain the atomic-ness where any interruptions won't result in a missing link.

Note that this is dependent on Posix behavior and may not work on Windows (why would you be using symlinks on Windows anyway?), but it's a technique shared by many macOS/Linux tools that need atomic symlink replacements.

Bryce answered 28/9, 2019 at 17:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.