diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 665fb0937e..25f0266a67 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -74,6 +74,19 @@ func tempDir() string { return dir } +// Link creates newname as a hard link to the oldname file. +// If there is an error, it will be of type *LinkError. +func Link(oldname, newname string) error { + e := ignoringEINTR(func() error { + return syscall.Link(oldname, newname) + }) + + if e != nil { + return &LinkError{"link", oldname, newname, e} + } + return nil +} + // Symlink creates newname as a symbolic link to oldname. // On Windows, a symlink to a non-existent oldname creates a file symlink; // if oldname is later created as a directory the symlink will not work. diff --git a/src/os/os_hardlink_test.go b/src/os/os_hardlink_test.go new file mode 100644 index 0000000000..96f1c9bbb6 --- /dev/null +++ b/src/os/os_hardlink_test.go @@ -0,0 +1,53 @@ +//go:build !windows && !baremetal && !js && !wasi && !wasip1 && !wasm_unknown + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + . "os" + "syscall" + "testing" +) + +func TestHardlink(t *testing.T) { + defer chtmpdir(t)() + from, to := "hardlinktestfrom", "hardlinktestto" + + file, err := Create(to) + if err != nil { + t.Fatalf("Create(%q) failed: %v", to, err) + } + if err = file.Close(); err != nil { + t.Errorf("Close(%q) failed: %v", to, err) + } + err = Link(to, from) + if err != nil { + t.Fatalf("Link(%q, %q) failed: %v", to, from, err) + } + + tostat, err := Lstat(to) + if err != nil { + t.Fatalf("Lstat(%q) failed: %v", to, err) + } + fromstat, err := Stat(from) + if err != nil { + t.Fatalf("Stat(%q) failed: %v", from, err) + } + if !SameFile(tostat, fromstat) { + t.Errorf("Symlink(%q, %q) did not create symlink", to, from) + } + + fromstat, err = Lstat(from) + if err != nil { + t.Fatalf("Lstat(%q) failed: %v", from, err) + } + // if they have the same inode, they are hard links + if fromstat.Sys().(*syscall.Stat_t).Ino != tostat.Sys().(*syscall.Stat_t).Ino { + t.Fatalf("Lstat(%q).Sys().Ino = %v, Lstat(%q).Sys().Ino = %v, want the same", to, tostat.Sys().(*syscall.Stat_t).Ino, from, fromstat.Sys().(*syscall.Stat_t).Ino) + } + + file.Close() +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 7a13245b6d..fb2e23968e 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -134,6 +134,16 @@ func Rename(from, to string) (err error) { return } +func Link(oldname, newname string) (err error) { + fromdata := cstring(oldname) + todata := cstring(newname) + fail := int(libc_link(&fromdata[0], &todata[0])) + if fail < 0 { + err = getErrno() + } + return +} + func Symlink(from, to string) (err error) { fromdata := cstring(from) todata := cstring(to) @@ -416,6 +426,11 @@ func libc_rename(from, to *byte) int32 //export symlink func libc_symlink(from, to *byte) int32 +// int link(const char *oldname, *newname); +// +//export link +func libc_link(oldname, newname *byte) int32 + // int fsync(int fd); // //export fsync