How do you check whether a file is a hard link in Go? os.FileMode
only has a mode for symlinks, not hard links.
I had an idea that unfortunately doesn't work:
package main
func main() {
filename := os.Args[1]
var hardlink bool
link, _ := os.Readlink(filename)
fi, _ := os.Stat(filename)
mode := string(fi.Mode().String()[0])
if link != "" && mode != "L" {
hardlink = true
}
fmt.Printf("%v is hard link? %v\n", filename, hardlink)
}
This^ doesn't work because os.Readlink
reads only symlinks, not hard links.
I found a somewhat related answer:
Counting hard links to a file in Go
But this answer shows how to find the number of hard links to a file, not whether a file itself is a hard link.
I'm guessing that the syscall package used in that answer or, better yet, the sys package has a way to test whether a file's a hard link. Does anyone know to do this? (I have trouble understanding those packages because they're so low-level.)
I should add the reason why I'd like to check this. I'm trying to make a function to create a tar archive of a directory [using filepath.Walk()
]. In this function, when I create the *tar.Header
for a file, I set a value to *tar.Header.Typeflag
.
For example, if fi
is a file's *os.FileInfo
variable and hdr
is the *tar.Header
variable for that file's place in a new tar archive, it looks like this:
if fi.Mode().IsDir() {
hdr.Typeflag = tar.TypeDir
}
In the tar package, the modes for hard links and regular files are distinct, TypeLink
and TypeReg
, but this isn't the case in the os package. So running this won't set the correct Typeflag
:
hdr.Mode = int64(fi.Mode())
Figured it out from an example in Docker's source code:
https://github.com/docker/docker/blob/master/pkg/archive/archive.go
https://github.com/docker/docker/blob/master/pkg/archive/archive_unix.go
package main
import (
"errors"
"fmt"
"log"
"os"
"syscall"
)
func main() {
filename := os.Args[1]
// 'os.Lstat()' reads the link itself.
// 'os.Stat()' would read the link's target.
fi, err := os.Lstat(filename)
if err != nil {
log.Fatal(err)
}
// https://github.com/docker/docker/blob/master/pkg/archive/archive_unix.go
// in 'func setHeaderForSpecialDevice()'
s, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
err = errors.New("cannot convert stat value to syscall.Stat_t")
log.Fatal(err)
}
// The index number of this file's inode:
inode := uint64(s.Ino)
// Total number of files/hardlinks connected to this file's inode:
nlink := uint32(s.Nlink)
// True if the file is a symlink.
if fi.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(fi.Name())
if err != nil {
log.Fatal(err)
}
fmt.Printf("%v is a symlink to %v on inode %v.\n", filename, link, inode)
os.Exit(0)
}
// Otherwise, for hardlinks:
fmt.Printf("The inode for %v, %v, has %v hardlinks.\n", filename, inode, nlink)
if nlink > 1 {
fmt.Printf("Inode %v has %v other hardlinks besides %v.\n", inode, nlink, filename)
} else {
fmt.Printf("%v is the only hardlink to inode %v.\n", filename, inode)
}
}