mirror of
https://github.com/samuelncui/acp.git
synced 2026-08-16 02:46:01 +00:00
feat: copy xattrs
This commit is contained in:
+2
-2
@@ -14,8 +14,8 @@ func (c *Copyer) cleanupJob(ctx context.Context, copyed <-chan *baseJob) {
|
||||
}
|
||||
|
||||
for _, name := range job.successTargets {
|
||||
if err := copyAttrs(name, job); err != nil {
|
||||
c.reportError(job.path, name, fmt.Errorf("change info, copy attrs fail, %w", err))
|
||||
if err := writeSysStat(name, job); err != nil {
|
||||
c.reportError(job.path, name, fmt.Errorf("change info, write sys stat fail, %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,12 @@ func (c *Copyer) walk(ctx context.Context) ([]*baseJob, error) {
|
||||
targets = append(targets, src.dst(d))
|
||||
}
|
||||
|
||||
sysStat, err := readSysStat(path, stat)
|
||||
if err != nil {
|
||||
c.reportError(path, "", fmt.Errorf("read sys stat, %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
appendJob(&baseJob{
|
||||
copyer: c,
|
||||
src: src,
|
||||
@@ -98,7 +104,7 @@ func (c *Copyer) walk(ctx context.Context) ([]*baseJob, error) {
|
||||
size: stat.Size(),
|
||||
mode: stat.Mode(),
|
||||
modTime: stat.ModTime(),
|
||||
sys: stat.Sys(),
|
||||
sys: sysStat,
|
||||
|
||||
targets: targets,
|
||||
})
|
||||
@@ -147,6 +153,12 @@ func (c *Copyer) walk(ctx context.Context) ([]*baseJob, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
sysStat, err := readSysStat(j.src, stat)
|
||||
if err != nil {
|
||||
c.reportError(j.src, "", fmt.Errorf("read sys stat, %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
appendJob(&baseJob{
|
||||
copyer: c,
|
||||
src: &source{base: "/", path: lo.Filter(strings.Split(j.src, "/"), func(s string, _ int) bool { return s != "" })},
|
||||
@@ -155,7 +167,7 @@ func (c *Copyer) walk(ctx context.Context) ([]*baseJob, error) {
|
||||
size: stat.Size(),
|
||||
mode: stat.Mode(),
|
||||
modTime: stat.ModTime(),
|
||||
sys: stat.Sys(),
|
||||
sys: sysStat,
|
||||
|
||||
targets: j.dsts,
|
||||
})
|
||||
|
||||
@@ -42,7 +42,7 @@ type baseJob struct {
|
||||
size int64 // length in bytes for regular files; system-dependent for others
|
||||
mode fs.FileMode // file mode bits
|
||||
modTime time.Time // modification time
|
||||
sys any
|
||||
sys *sysStat
|
||||
|
||||
lock sync.Mutex
|
||||
writeTime time.Time
|
||||
|
||||
+10
-17
@@ -4,9 +4,11 @@
|
||||
package acp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func truncate(file *os.File, size int64) error {
|
||||
@@ -16,19 +18,10 @@ func truncate(file *os.File, size int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyAttrs(name string, j *baseJob) error {
|
||||
if err := os.Chmod(name, j.mode); err != nil {
|
||||
return fmt.Errorf("chmod fail, %w", err)
|
||||
}
|
||||
if os.Geteuid() == 0 {
|
||||
if stat, ok := j.sys.(*syscall.Stat_t); ok {
|
||||
if err := os.Chown(name, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||
return fmt.Errorf("chown fail, %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := os.Chtimes(name, j.modTime, j.modTime); err != nil {
|
||||
return fmt.Errorf("chtimes fail, %w", err)
|
||||
}
|
||||
return nil
|
||||
func isNoAttrErr(err error) bool {
|
||||
return errors.Is(err, unix.ENOATTR) || errors.Is(err, unix.ENODATA)
|
||||
}
|
||||
|
||||
func checkXattrKey(key string) bool {
|
||||
return !strings.HasPrefix(key, "system.")
|
||||
}
|
||||
|
||||
+9
-16
@@ -4,9 +4,11 @@
|
||||
package acp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func truncate(file *os.File, size int64) error {
|
||||
@@ -16,19 +18,10 @@ func truncate(file *os.File, size int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyAttrs(name string, j *baseJob) error {
|
||||
if err := os.Chmod(name, j.mode); err != nil {
|
||||
return fmt.Errorf("chmod fail, %w", err)
|
||||
}
|
||||
if os.Geteuid() == 0 {
|
||||
if stat, ok := j.sys.(*syscall.Stat_t); ok {
|
||||
if err := os.Chown(name, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||
return fmt.Errorf("chown fail, %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := os.Chtimes(name, j.modTime, j.modTime); err != nil {
|
||||
return fmt.Errorf("chtimes fail, %w", err)
|
||||
}
|
||||
return nil
|
||||
func isNoAttrErr(err error) bool {
|
||||
return errors.Is(err, unix.ENODATA)
|
||||
}
|
||||
|
||||
func checkXattrKey(key string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
+12
-2
@@ -5,14 +5,24 @@ package acp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
func truncate(_ *os.File, _ int64) error {
|
||||
type sysStat struct{}
|
||||
|
||||
func readSysStat(path string, stat fs.FileInfo) (*sysStat, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func truncate(file *os.File, size int64) error {
|
||||
if err := file.Truncate(size); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyAttrs(name string, j *baseJob) error {
|
||||
func writeSysStat(name string, j *baseJob) error {
|
||||
if err := os.Chmod(name, j.mode); err != nil {
|
||||
return fmt.Errorf("chmod fail, %w", err)
|
||||
}
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
//go:build darwin || linux
|
||||
// +build darwin linux
|
||||
|
||||
package acp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type xattr struct {
|
||||
key string
|
||||
value []byte
|
||||
}
|
||||
|
||||
type sysStat struct {
|
||||
*syscall.Stat_t
|
||||
xattrs []xattr
|
||||
}
|
||||
|
||||
func readSysStat(path string, stat fs.FileInfo) (*sysStat, error) {
|
||||
sysstat, ok := stat.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("stat sys failed, %T", stat.Sys())
|
||||
}
|
||||
|
||||
xattrs, err := readXattrs(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read xattrs failed, %w", err)
|
||||
}
|
||||
|
||||
return &sysStat{Stat_t: sysstat, xattrs: xattrs}, nil
|
||||
}
|
||||
|
||||
func writeSysStat(name string, j *baseJob) error {
|
||||
if err := writeXattrs(name, j.sys.xattrs); err != nil {
|
||||
return fmt.Errorf("write xattr fail, %w", err)
|
||||
}
|
||||
if err := os.Chmod(name, j.mode); err != nil {
|
||||
return fmt.Errorf("chmod fail, %w", err)
|
||||
}
|
||||
if os.Geteuid() == 0 {
|
||||
if err := os.Chown(name, int(j.sys.Uid), int(j.sys.Gid)); err != nil {
|
||||
return fmt.Errorf("chown fail, %w", err)
|
||||
}
|
||||
}
|
||||
if err := os.Chtimes(name, j.modTime, j.modTime); err != nil {
|
||||
return fmt.Errorf("chtimes fail, %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readXattrs(path string) ([]xattr, error) {
|
||||
size, err := unix.Listxattr(path, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.ENOTSUP) || isNoAttrErr(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("count xattrs failed, %w", err)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
keyBuf := make([]byte, size)
|
||||
n, err := unix.Listxattr(path, keyBuf)
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.ENOTSUP) || isNoAttrErr(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("list xattrs failed, %w", err)
|
||||
}
|
||||
keyBuf = keyBuf[:n]
|
||||
|
||||
start := 0
|
||||
xattrs := make([]xattr, 0)
|
||||
for i, b := range keyBuf {
|
||||
if b != 0 {
|
||||
continue
|
||||
}
|
||||
if i <= start {
|
||||
start = i + 1
|
||||
continue
|
||||
}
|
||||
|
||||
name := string(keyBuf[start:i])
|
||||
start = i + 1
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if !checkXattrKey(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
valSize, err := unix.Getxattr(path, name, nil)
|
||||
if err != nil {
|
||||
if isNoAttrErr(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if valSize == 0 {
|
||||
xattrs = append(xattrs, xattr{key: name, value: []byte{}})
|
||||
continue
|
||||
}
|
||||
|
||||
val := make([]byte, valSize)
|
||||
n, err := unix.Getxattr(path, name, val)
|
||||
if err != nil {
|
||||
if isNoAttrErr(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xattrs = append(xattrs, xattr{key: name, value: val[:n]})
|
||||
}
|
||||
|
||||
return xattrs, nil
|
||||
}
|
||||
|
||||
func writeXattrs(path string, xattrs []xattr) error {
|
||||
for _, xattr := range xattrs {
|
||||
if err := unix.Setxattr(path, xattr.key, xattr.value, 0); err != nil {
|
||||
if errors.Is(err, unix.ENOTSUP) ||
|
||||
errors.Is(err, unix.EPERM) ||
|
||||
errors.Is(err, unix.EROFS) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+8
-1
@@ -5,9 +5,16 @@ package acp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
type sysStat struct{}
|
||||
|
||||
func readSysStat(path string, stat fs.FileInfo) (*sysStat, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func truncate(file *os.File, size int64) error {
|
||||
if err := file.Truncate(size); err != nil {
|
||||
return err
|
||||
@@ -15,7 +22,7 @@ func truncate(file *os.File, size int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyAttrs(name string, j *baseJob) error {
|
||||
func writeSysStat(name string, j *baseJob) error {
|
||||
if err := os.Chmod(name, j.mode); err != nil {
|
||||
return fmt.Errorf("chmod fail, %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user