Shaman: handle duplicate symlinks gracefully

Instead of erroring out when a symlink already exists, investigate it. If
the linked file is the one that's intended, just use it.

For some reason, BAT and/or the Flamenco add-on include some files twice
in the checkout request to Shaman. This is now handled gracefully.
This commit is contained in:
Sybren A. Stüvel 2022-08-04 16:29:15 +02:00
parent 7102a3def8
commit 44ccc6c3ca
2 changed files with 41 additions and 17 deletions

View File

@ -200,27 +200,48 @@ func (m *Manager) SymlinkToCheckout(blobPath, checkoutPath, symlinkRelativePath
// This is expected to fail sometimes, because we don't create parent directories yet.
// We only create those when we get a failure from symlinking.
err = os.Symlink(blobPath, symlinkPath)
if err == nil {
return err
}
if !errors.Is(err, fs.ErrNotExist) {
switch {
case err == nil:
return nil
case errors.Is(err, fs.ErrExist):
// The symlink already exists, which is weird. Investigate so we can log a
// more detailed warning.
linkTarget, readErr := os.Readlink(symlinkPath)
if readErr != nil {
logger.Error().
AnErr("symlinkError", err).
AnErr("symlinkReadError", readErr).
Msg("shaman: unable to create symlink as it already exists, but also it cannot be read")
return err
}
if linkTarget != blobPath {
logger.Error().
AnErr("symlinkError", err).
Str("alreadyLinkedFrom", linkTarget).
Msg("shaman: unable to create simlink, as it already exists and links a different blob")
return err
}
// The right file is linked, so let's warn about the situation and otherwise ignore it.
logger.Warn().
AnErr("symlinkError", err).
Msg("shaman: symlink unexpectedly already exists, but it is linking the right path so let's just use it")
case errors.Is(err, fs.ErrNotExist):
// The directory doesn't exist yet.
logger.Debug().Msg("shaman: creating parent directory")
dir := filepath.Dir(symlinkPath)
if err := os.MkdirAll(dir, 0777); err != nil {
logger.Error().Err(err).Msg("shaman: unable to create parent directory")
return err
}
if err := os.Symlink(blobPath, symlinkPath); err != nil {
logger.Error().Err(err).Msg("shaman: unable to create symlink, after creating parent directory")
return err
}
default:
logger.Error().Err(err).Msg("shaman: unable to create symlink")
return err
}
logger.Debug().Msg("shaman: creating parent directory")
dir := filepath.Dir(symlinkPath)
if err := os.MkdirAll(dir, 0777); err != nil {
logger.Error().Err(err).Msg("shaman: unable to create parent directory")
return err
}
if err := os.Symlink(blobPath, symlinkPath); err != nil {
logger.Error().Err(err).Msg("shaman: unable to create symlink, after creating parent directory")
return err
}
// Change the modification time of the blob to mark it as 'referenced' just now.
m.wg.Add(1)
go func() {

View File

@ -58,6 +58,9 @@ func TestSymlinkToCheckout(t *testing.T) {
err = manager.SymlinkToCheckout(blobPath, manager.checkoutBasePath, symlinkRelativePath)
assert.NoError(t, err)
err = manager.SymlinkToCheckout(blobPath, manager.checkoutBasePath, symlinkRelativePath)
assert.NoError(t, err, "symlinking a file twice should not be an issue")
// Wait for touch() calls to be done.
manager.wg.Wait()