-
Notifications
You must be signed in to change notification settings - Fork 208
/
hash_data_adapter_xattr.go
98 lines (75 loc) · 2.97 KB
/
hash_data_adapter_xattr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//go:build linux || darwin
// XAttr APIs are compatible across linux & darwin; no need to duplicate code.
package common
import (
"encoding/json"
"errors"
"fmt"
"golang.org/x/sys/unix"
"path/filepath"
"strings"
)
// ===== OS-Specific hash adapter changes =====
func (HashStorageMode) XAttr() HashStorageMode { return 11 } // It's OK if OS-specific options overlap, but we should leave some room for the agnostic options
func (e HashStorageMode) osDefault() HashStorageMode { return e.XAttr() }
func init() {
osAgnosticBehavior := NewHashDataAdapter
NewHashDataAdapter = func(hashPath, dataPath string, mode HashStorageMode) (adapter HashDataAdapter, err error) {
switch mode {
case EHashStorageMode.XAttr():
// Checking the root directory of the source isn't technically correct, as a filesystem could be mounted under the source.
lcm.Info("XAttr hash storage mode is selected. This assumes all files indexed on the source are on filesystem(s) that support user_xattr.")
return &XAttrHashDataAdapter{dataBasePath: dataPath}, nil
default: // fall back to OS-agnostic behaviors
return osAgnosticBehavior(hashPath, dataPath, mode)
}
}
}
// XAttrHashDataAdapter stores hash data in the Xattr fields
type XAttrHashDataAdapter struct {
dataBasePath string
}
func (a *XAttrHashDataAdapter) GetMode() HashStorageMode {
return EHashStorageMode.XAttr()
}
func (a *XAttrHashDataAdapter) getDataPath(relativePath string) string {
return filepath.Join(a.dataBasePath, relativePath)
}
func (a *XAttrHashDataAdapter) GetHashData(relativePath string) (*SyncHashData, error) {
metaFile := a.getDataPath(relativePath)
buf := make([]byte, 512) // 512 bytes should be plenty of space
retry:
sz, err := unix.Getxattr(metaFile, strings.TrimPrefix(AzCopyHashDataStream, "."), buf) // MacOS doesn't take well to the dot(?)
if err != nil {
if err == unix.ERANGE { // But just in case, let's safeguard against it and re-call with a larger buffer.
buf = make([]byte, len(buf) * 2)
goto retry
}
if err == unix.ENODATA { // There's no hash present; nothing to do.
return nil, errors.New("no hash present")
}
return nil, fmt.Errorf("failed to read xattr: %w; consider utilizing an OS-agnostic hash storage mode", err)
}
buf = buf[:sz] // trim the ending bytes off
var out SyncHashData
err = json.Unmarshal(buf, &out)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal xattr: %w; buffer contents: \"%s\"", err, buf)
}
return &out, nil
}
func (a *XAttrHashDataAdapter) SetHashData(relativePath string, data *SyncHashData) error {
if data == nil {
return nil
}
metaFile := a.getDataPath(relativePath)
buf, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("failed to marshal xattr: %w", err)
}
err = unix.Setxattr(metaFile, strings.TrimPrefix(AzCopyHashDataStream, "."), buf, 0) // Default flags == create or replace
if err != nil {
return fmt.Errorf("failed to write xattr: %w; consider utilizing an OS-agnostic hash storage mode", err)
}
return nil
}