diff options
Diffstat (limited to 'internal/resource/link/hardlink.go')
| -rw-r--r-- | internal/resource/link/hardlink.go | 59 |
1 files changed, 59 insertions, 0 deletions
diff --git a/internal/resource/link/hardlink.go b/internal/resource/link/hardlink.go new file mode 100644 index 0000000..7307344 --- /dev/null +++ b/internal/resource/link/hardlink.go @@ -0,0 +1,59 @@ +package link + +import ( + "fmt" + "log" + "os" + "syscall" +) + +// ensureHardlink ensures l.path is a hard link to l.target. +// +// Idempotency and clobber policy: +// - already the same inode as the target: nothing to do. +// - a different file/link is in the way: it is renamed to "<path>.old" +// before the link is created (matching the symlink resource's behavior). +func ensureHardlink(l *Link) error { + log.Printf("processing hardlink: %s -> %s", l.path, l.target) + + if l.target == "" { + return fmt.Errorf("hardlink %s has no target", l.path) + } + + targetInfo, err := os.Stat(l.target) + if err != nil { + return fmt.Errorf("failed to stat hardlink target %s: %w", l.target, err) + } + + if info, err := os.Lstat(l.path); err == nil { + if sameInode(info, targetInfo) { + log.Printf("hardlink %s already links to %s", l.path, l.target) + return nil + } + old := l.path + ".old" + log.Printf("%s already exists, renaming to %s", l.path, old) + if err := os.Rename(l.path, old); err != nil { + return fmt.Errorf("failed to move existing %s aside: %w", l.path, err) + } + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to stat %s: %w", l.path, err) + } + + if err := os.Link(l.target, l.path); err != nil { + return fmt.Errorf("failed to create hardlink %s -> %s: %w", l.path, l.target, err) + } + + log.Printf("created hardlink %s -> %s", l.path, l.target) + return nil +} + +// sameInode reports whether two FileInfos refer to the same underlying inode +// (same device and inode number), i.e. they are already hard-linked. +func sameInode(a, b os.FileInfo) bool { + as, aok := a.Sys().(*syscall.Stat_t) + bs, bok := b.Sys().(*syscall.Stat_t) + if !aok || !bok { + return false + } + return as.Dev == bs.Dev && as.Ino == bs.Ino +} |
