//go:build linux package host import ( "errors" "os" "os/user" "strconv" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/safefile" "golang.org/x/sys/unix" ) func claimStateOwner() (int, int, error) { u, err := user.Lookup(serviceUser) if err != nil { return 0, 0, err } uid, err := strconv.Atoi(u.Uid) if err != nil || uid <= 0 { return 0, 0, hostwrite.ErrUnavailable } gid, err := strconv.Atoi(u.Gid) return uid, gid, err } func claimPrivatePathOwner(path string, uid, gid int, directory bool) error { var f *os.File var err error if directory { f, err = safefile.OpenDir(path) } else { f, err = safefile.OpenFile(path, os.O_RDONLY, 0) } if err != nil { return err } defer f.Close() fd := int(f.Fd()) var stat unix.Stat_t if err = unix.Fstat(fd, &stat); err != nil { return err } kind := uint32(unix.S_IFREG) mode := uint32(0600) if directory { kind = unix.S_IFDIR mode = 0700 } if stat.Mode&unix.S_IFMT != kind || (!directory && stat.Nlink != 1) { return hostwrite.ErrUnavailable } if os.Geteuid() == 0 { if err = unix.Fchown(fd, uid, gid); err != nil { return err } if err = unix.Fchmod(fd, mode); err != nil { return err } } else { if int(stat.Uid) != uid { return hostwrite.ErrUnavailable } if err = unix.Fchmod(fd, mode); err != nil { return err } } return nil } func prepareClaimStateDirectory(path string) error { uid, gid, err := claimStateOwner() if err != nil { return err } if err = safefile.MkdirAll(path, 0700); err != nil { return err } // Retirement journals are root's own and live in /var/lib/carlos-claims. return claimPrivatePathOwner(path, uid, gid, true) } func claimStateDatabaseOwner(path string) error { uid, gid, err := claimStateOwner() if err != nil { return err } for _, suffix := range []string{"", "-wal", "-shm"} { err = claimPrivatePathOwner(path+suffix, uid, gid, false) if suffix != "" && errors.Is(err, os.ErrNotExist) { continue } if err != nil { return err } } return nil }