Add metadata and special files restore options

Add options to control if xattrs and specials get restored. Add
file flag mask preference. It's a rough interface, but this is a niche
use case.

Refactoring of options passing since golang ergonomics are poor for long
parameter lists.
This commit is contained in:
2023-10-06 04:08:38 -05:00
parent 09637c69bc
commit 6d5cb4b7b9
8 changed files with 141 additions and 82 deletions

View File

@@ -858,6 +858,10 @@ func restoreRepository(context *cli.Context) {
SetOwner: !context.Bool("ignore-owner"), SetOwner: !context.Bool("ignore-owner"),
ShowStatistics: context.Bool("stats"), ShowStatistics: context.Bool("stats"),
AllowFailures: context.Bool("persist"), AllowFailures: context.Bool("persist"),
ExcludeXattrs: preference.ExcludeXattrs,
NormalizeXattrs: preference.NormalizeXattrs,
IncludeSpecials: preference.IncludeSpecials,
FileFlagsMask: uint32(preference.FileFlagsMask),
} }
var patterns []string var patterns []string

View File

@@ -45,15 +45,19 @@ type BackupOptions struct {
} }
type RestoreOptions struct { type RestoreOptions struct {
Threads int Threads int
Patterns []string Patterns []string
InPlace bool InPlace bool
QuickMode bool QuickMode bool
Overwrite bool Overwrite bool
DeleteMode bool DeleteMode bool
SetOwner bool SetOwner bool
ShowStatistics bool ShowStatistics bool
AllowFailures bool AllowFailures bool
ExcludeXattrs bool
NormalizeXattrs bool
IncludeSpecials bool
FileFlagsMask uint32
} }
func (manager *BackupManager) SetDryRun(dryRun bool) { func (manager *BackupManager) SetDryRun(dryRun bool) {
@@ -648,6 +652,13 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
overwrite := options.Overwrite overwrite := options.Overwrite
allowFailures := options.AllowFailures allowFailures := options.AllowFailures
metadataOptions := RestoreMetadataOptions{
SetOwner: options.SetOwner,
ExcludeXattrs: options.ExcludeXattrs,
NormalizeXattrs: options.NormalizeXattrs,
FileFlagsMask: options.FileFlagsMask,
}
startTime := time.Now().Unix() startTime := time.Now().Unix()
LOG_DEBUG("RESTORE_PARAMETERS", "top: %s, revision: %d, in-place: %t, quick: %t, delete: %t", LOG_DEBUG("RESTORE_PARAMETERS", "top: %s, revision: %d, in-place: %t, quick: %t, delete: %t",
@@ -800,7 +811,7 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
if stat.Mode()&os.ModeSymlink != 0 { if stat.Mode()&os.ModeSymlink != 0 {
isRegular, link, err := Readlink(fullPath) isRegular, link, err := Readlink(fullPath)
if err == nil && link == remoteEntry.Link && !isRegular { if err == nil && link == remoteEntry.Link && !isRegular {
remoteEntry.RestoreMetadata(fullPath, nil, options.SetOwner) remoteEntry.RestoreMetadata(fullPath, stat, metadataOptions)
if remoteEntry.IsHardLinkRoot() { if remoteEntry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true hardLinkTable[len(hardLinkTable)-1].willExist = true
} }
@@ -825,7 +836,7 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err) LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err)
return 0 return 0
} }
remoteEntry.RestoreMetadata(fullPath, nil, options.SetOwner) remoteEntry.RestoreMetadata(fullPath, nil, metadataOptions)
LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", remoteEntry.Path) LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", remoteEntry.Path)
} else if remoteEntry.IsDir() { } else if remoteEntry.IsDir() {
@@ -846,15 +857,15 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
return 0 return 0
} }
} }
err = remoteEntry.RestoreEarlyDirFlags(fullPath, 0) // TODO: mask err = remoteEntry.RestoreEarlyDirFlags(fullPath, options.FileFlagsMask)
if err != nil { if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err) LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
} }
directoryEntries = append(directoryEntries, remoteEntry) directoryEntries = append(directoryEntries, remoteEntry)
} else if remoteEntry.IsSpecial() { } else if remoteEntry.IsSpecial() && options.IncludeSpecials {
if stat, _ := os.Lstat(fullPath); stat != nil { if stat, _ := os.Lstat(fullPath); stat != nil {
if remoteEntry.IsSameSpecial(stat) { if remoteEntry.IsSameSpecial(stat) {
remoteEntry.RestoreMetadata(fullPath, nil, options.SetOwner) remoteEntry.RestoreMetadata(fullPath, nil, metadataOptions)
if remoteEntry.IsHardLinkRoot() { if remoteEntry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true hardLinkTable[len(hardLinkTable)-1].willExist = true
} }
@@ -876,7 +887,7 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
LOG_ERROR("RESTORE_SPECIAL", "Failed to restore special file %s: %v", fullPath, err) LOG_ERROR("RESTORE_SPECIAL", "Failed to restore special file %s: %v", fullPath, err)
return 0 return 0
} }
remoteEntry.RestoreMetadata(fullPath, nil, options.SetOwner) remoteEntry.RestoreMetadata(fullPath, nil, metadataOptions)
LOG_TRACE("DOWNLOAD_DONE", "Special %s %s restored", remoteEntry.Path, remoteEntry.FmtSpecial()) LOG_TRACE("DOWNLOAD_DONE", "Special %s %s restored", remoteEntry.Path, remoteEntry.FmtSpecial())
} else { } else {
@@ -982,7 +993,7 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
} }
newFile.Close() newFile.Close()
file.RestoreMetadata(fullPath, nil, options.SetOwner) file.RestoreMetadata(fullPath, nil, metadataOptions)
if !options.ShowStatistics { if !options.ShowStatistics {
LOG_INFO("DOWNLOAD_DONE", "Downloaded %s (0)", file.Path) LOG_INFO("DOWNLOAD_DONE", "Downloaded %s (0)", file.Path)
downloadedFileSize += file.Size downloadedFileSize += file.Size
@@ -993,7 +1004,8 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
} }
downloaded, err := manager.RestoreFile(chunkDownloader, chunkMaker, file, top, options.InPlace, overwrite, downloaded, err := manager.RestoreFile(chunkDownloader, chunkMaker, file, top, options.InPlace, overwrite,
options.ShowStatistics, totalFileSize, downloadedFileSize, startDownloadingTime, allowFailures) options.ShowStatistics, totalFileSize, downloadedFileSize, startDownloadingTime, allowFailures,
metadataOptions.FileFlagsMask)
if err != nil { if err != nil {
// RestoreFile returned an error; if allowFailures is false RestoerFile would error out and not return so here // RestoreFile returned an error; if allowFailures is false RestoerFile would error out and not return so here
// we just need to show a warning // we just need to show a warning
@@ -1012,7 +1024,7 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
skippedFileSize += file.Size skippedFileSize += file.Size
skippedFileCount++ skippedFileCount++
} }
file.RestoreMetadata(fullPath, nil, options.SetOwner) file.RestoreMetadata(fullPath, nil, metadataOptions)
} }
for _, linkEntry := range hardLinks { for _, linkEntry := range hardLinks {
@@ -1059,7 +1071,7 @@ func (manager *BackupManager) Restore(top string, revision int, options RestoreO
for _, entry := range directoryEntries { for _, entry := range directoryEntries {
dir := joinPath(top, entry.Path) dir := joinPath(top, entry.Path)
entry.RestoreMetadata(dir, nil, options.SetOwner) entry.RestoreMetadata(dir, nil, metadataOptions)
} }
if options.ShowStatistics { if options.ShowStatistics {
@@ -1273,7 +1285,8 @@ func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top s
// false, nil: Skipped file; // false, nil: Skipped file;
// false, error: Failure to restore file (only if allowFailures == true) // false, error: Failure to restore file (only if allowFailures == true)
func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chunkMaker *ChunkMaker, entry *Entry, top string, inPlace bool, overwrite bool, func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chunkMaker *ChunkMaker, entry *Entry, top string, inPlace bool, overwrite bool,
showStatistics bool, totalFileSize int64, downloadedFileSize int64, startTime int64, allowFailures bool) (bool, error) { showStatistics bool, totalFileSize int64, downloadedFileSize int64, startTime int64, allowFailures bool,
fileFlagsMask uint32) (bool, error) {
LOG_TRACE("DOWNLOAD_START", "Downloading %s", entry.Path) LOG_TRACE("DOWNLOAD_START", "Downloading %s", entry.Path)
@@ -1320,7 +1333,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
LOG_ERROR("DOWNLOAD_CREATE", "Failed to create the file %s for in-place writing: %v", fullPath, err) LOG_ERROR("DOWNLOAD_CREATE", "Failed to create the file %s for in-place writing: %v", fullPath, err)
return false, nil return false, nil
} }
err = entry.RestoreEarlyFileFlags(existingFile, 0) // TODO: implement mask err = entry.RestoreEarlyFileFlags(existingFile, fileFlagsMask)
if err != nil { if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err) LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
} }
@@ -1507,7 +1520,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
return false, nil return false, nil
} }
} }
err = entry.RestoreEarlyFileFlags(existingFile, 0) // TODO: implement mask err = entry.RestoreEarlyFileFlags(existingFile, fileFlagsMask)
if err != nil { if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err) LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
} }
@@ -1593,7 +1606,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
LOG_ERROR("DOWNLOAD_OPEN", "Failed to open file for writing: %v", err) LOG_ERROR("DOWNLOAD_OPEN", "Failed to open file for writing: %v", err)
return false, nil return false, nil
} }
err = entry.RestoreEarlyFileFlags(newFile, 0) // TODO: implement mask err = entry.RestoreEarlyFileFlags(newFile, fileFlagsMask)
if err != nil { if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err) LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
} }

View File

@@ -582,7 +582,15 @@ func (entry *Entry) String(maxSizeDigits int) string {
return fmt.Sprintf("%*d %s %64s %s", maxSizeDigits, entry.Size, modifiedTime, entry.Hash, entry.Path) return fmt.Sprintf("%*d %s %64s %s", maxSizeDigits, entry.Size, modifiedTime, entry.Hash, entry.Path)
} }
func (entry *Entry) RestoreMetadata(fullPath string, fileInfo os.FileInfo, setOwner bool) bool { type RestoreMetadataOptions struct {
SetOwner bool
ExcludeXattrs bool
NormalizeXattrs bool
FileFlagsMask uint32
}
func (entry *Entry) RestoreMetadata(fullPath string, fileInfo os.FileInfo,
options RestoreMetadataOptions) bool {
if fileInfo == nil { if fileInfo == nil {
var err error var err error
@@ -593,13 +601,15 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo os.FileInfo, setOw
} }
} }
err := entry.SetAttributesToFile(fullPath) if !options.ExcludeXattrs {
if err != nil { err := entry.SetAttributesToFile(fullPath, options.NormalizeXattrs)
LOG_WARN("RESTORE_ATTR", "Failed to set extended attributes on %s: %v", entry.Path, err) if err != nil {
LOG_WARN("RESTORE_ATTR", "Failed to set extended attributes on %s: %v", entry.Path, err)
}
} }
// Note that chown can remove setuid/setgid bits so should be called before chmod // Note that chown can remove setuid/setgid bits so should be called before chmod
if setOwner { if options.SetOwner {
if !SetOwner(fullPath, entry, fileInfo) { if !SetOwner(fullPath, entry, fileInfo) {
return false return false
} }
@@ -624,7 +634,7 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo os.FileInfo, setOw
} }
} }
err = entry.RestoreLateFileFlags(fullPath, fileInfo, 0) // TODO: implement mask err := entry.RestoreLateFileFlags(fullPath, fileInfo, options.FileFlagsMask)
if err != nil { if err != nil {
LOG_WARN("RESTORE_FLAGS", "Failed to set file flags on %s: %v", entry.Path, err) LOG_WARN("RESTORE_FLAGS", "Failed to set file flags on %s: %v", entry.Path, err)
} }

View File

@@ -6,27 +6,55 @@ package duplicacy
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "fmt"
"os" "os"
"path" "path"
"reflect" "reflect"
"strconv"
"strings" "strings"
) )
type flagsMask uint32
func (f flagsMask) MarshalJSON() ([]byte, error) {
return json.Marshal(fmt.Sprintf("0x%.8x", f))
}
func (f *flagsMask) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
if str[0] == '0' && (str[1] == 'x' || str[1] == 'X') {
str = str[2:]
}
v, err := strconv.ParseUint(string(str), 16, 32)
if err != nil {
return err
}
*f = flagsMask(v)
return nil
}
// Preference stores options for each storage. // Preference stores options for each storage.
type Preference struct { type Preference struct {
Name string `json:"name"` Name string `json:"name"`
SnapshotID string `json:"id"` SnapshotID string `json:"id"`
RepositoryPath string `json:"repository"` RepositoryPath string `json:"repository"`
StorageURL string `json:"storage"` StorageURL string `json:"storage"`
Encrypted bool `json:"encrypted"` Encrypted bool `json:"encrypted"`
BackupProhibited bool `json:"no_backup"` BackupProhibited bool `json:"no_backup"`
RestoreProhibited bool `json:"no_restore"` RestoreProhibited bool `json:"no_restore"`
DoNotSavePassword bool `json:"no_save_password"` DoNotSavePassword bool `json:"no_save_password"`
NobackupFile string `json:"nobackup_file"` NobackupFile string `json:"nobackup_file"`
Keys map[string]string `json:"keys"` Keys map[string]string `json:"keys"`
FiltersFile string `json:"filters"` FiltersFile string `json:"filters"`
ExcludeByAttribute bool `json:"exclude_by_attribute"` ExcludeByAttribute bool `json:"exclude_by_attribute"`
ExcludeXattrs bool `json:"exclude_xattrs"`
NormalizeXattrs bool `json:"normalize_xattrs"`
IncludeSpecials bool `json:"include_specials"`
FileFlagsMask flagsMask `json:"file_flags_mask"`
} }
var preferencePath string var preferencePath string
@@ -43,7 +71,7 @@ func LoadPreferences(repository string) bool {
} }
if !stat.IsDir() { if !stat.IsDir() {
content, err := ioutil.ReadFile(preferencePath) content, err := os.ReadFile(preferencePath)
if err != nil { if err != nil {
LOG_ERROR("DOT_DUPLICACY_PATH", "Failed to locate the preference path: %v", err) LOG_ERROR("DOT_DUPLICACY_PATH", "Failed to locate the preference path: %v", err)
return false return false
@@ -61,7 +89,7 @@ func LoadPreferences(repository string) bool {
preferencePath = realPreferencePath preferencePath = realPreferencePath
} }
description, err := ioutil.ReadFile(path.Join(preferencePath, "preferences")) description, err := os.ReadFile(path.Join(preferencePath, "preferences"))
if err != nil { if err != nil {
LOG_ERROR("PREFERENCE_OPEN", "Failed to read the preference file from repository %s: %v", repository, err) LOG_ERROR("PREFERENCE_OPEN", "Failed to read the preference file from repository %s: %v", repository, err)
return false return false
@@ -110,7 +138,7 @@ func SavePreferences() bool {
} }
preferenceFile := path.Join(GetDuplicacyPreferencePath(), "preferences") preferenceFile := path.Join(GetDuplicacyPreferencePath(), "preferences")
err = ioutil.WriteFile(preferenceFile, description, 0600) err = os.WriteFile(preferenceFile, description, 0600)
if err != nil { if err != nil {
LOG_ERROR("PREFERENCE_WRITE", "Failed to save the preference file %s: %v", preferenceFile, err) LOG_ERROR("PREFERENCE_WRITE", "Failed to save the preference file %s: %v", preferenceFile, err)
return false return false

View File

@@ -114,6 +114,29 @@ func SetOwner(fullPath string, entry *Entry, fileInfo os.FileInfo) bool {
return true return true
} }
func MakeHardlink(source string, target string) error {
return os.Link(source, target)
}
func joinPath(components ...string) string {
combinedPath := `\\?\` + filepath.Join(components...)
// If the path is on a samba drive we must use the UNC format
if strings.HasPrefix(combinedPath, `\\?\\\`) {
combinedPath = `\\?\UNC\` + combinedPath[6:]
}
return combinedPath
}
func SplitDir(fullPath string) (dir string, file string) {
i := strings.LastIndex(fullPath, "\\")
return fullPath[:i+1], fullPath[i+1:]
}
func excludedByAttribute(attributes map[string][]byte) bool {
return false
}
type listEntryLinkKey struct{} type listEntryLinkKey struct{}
func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked bool) { func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked bool) {
@@ -128,7 +151,7 @@ func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
return nil return nil
} }
func (entry *Entry) SetAttributesToFile(fullPath string) error { func (entry *Entry) SetAttributesToFile(fullPath string, normalize bool) error {
return nil return nil
} }
@@ -159,26 +182,3 @@ func (entry *Entry) RestoreSpecial(fullPath string) error {
func (entry *Entry) FmtSpecial() string { func (entry *Entry) FmtSpecial() string {
return "" return ""
} }
func MakeHardlink(source string, target string) error {
return os.Link(source, target)
}
func joinPath(components ...string) string {
combinedPath := `\\?\` + filepath.Join(components...)
// If the path is on a samba drive we must use the UNC format
if strings.HasPrefix(combinedPath, `\\?\\\`) {
combinedPath = `\\?\UNC\` + combinedPath[6:]
}
return combinedPath
}
func SplitDir(fullPath string) (dir string, file string) {
i := strings.LastIndex(fullPath, "\\")
return fullPath[:i+1], fullPath[i+1:]
}
func excludedByAttribute(attributes map[string][]byte) bool {
return false
}

View File

@@ -64,7 +64,7 @@ func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
return nil return nil
} }
func (entry *Entry) SetAttributesToFile(fullPath string) error { func (entry *Entry) SetAttributesToFile(fullPath string, normalize bool) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() { if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() {
return nil return nil
} }
@@ -109,7 +109,7 @@ func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error {
} }
func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error { func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
if entry.Attributes == nil { if mask == 0xffffffff {
return nil return nil
} }
@@ -121,8 +121,10 @@ func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo,
var flags uint32 var flags uint32
if v, have := (*entry.Attributes)[darwinFileFlagsKey]; have { if entry.Attributes != nil {
flags = binary.LittleEndian.Uint32(v) if v, have := (*entry.Attributes)[darwinFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v)
}
} }
stat := fileInfo.Sys().(*syscall.Stat_t) stat := fileInfo.Sys().(*syscall.Stat_t)

View File

@@ -124,7 +124,7 @@ func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
return nil return nil
} }
func (entry *Entry) SetAttributesToFile(fullPath string) error { func (entry *Entry) SetAttributesToFile(fullPath string, normalize bool) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 { if entry.Attributes == nil || len(*entry.Attributes) == 0 {
return nil return nil
} }
@@ -161,7 +161,7 @@ func (entry *Entry) SetAttributesToFile(fullPath string) error {
} }
func (entry *Entry) RestoreEarlyDirFlags(fullPath string, mask uint32) error { func (entry *Entry) RestoreEarlyDirFlags(fullPath string, mask uint32) error {
if entry.Attributes == nil { if entry.Attributes == nil || mask == 0xffffffff {
return nil return nil
} }
var flags uint32 var flags uint32
@@ -185,7 +185,7 @@ func (entry *Entry) RestoreEarlyDirFlags(fullPath string, mask uint32) error {
} }
func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error { func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error {
if entry.Attributes == nil { if entry.Attributes == nil || mask == 0xffffffff {
return nil return nil
} }
var flags uint32 var flags uint32
@@ -204,7 +204,7 @@ func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error {
} }
func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error { func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
if entry.IsLink() || entry.Attributes == nil { if entry.IsLink() || entry.Attributes == nil || mask == 0xffffffff {
return nil return nil
} }
var flags uint32 var flags uint32
@@ -218,7 +218,7 @@ func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo,
if err != nil { if err != nil {
return err return err
} }
err = ioctl(f, linux_FS_IOC_SETFLAGS, &flags) err = ioctl(f, unix.FS_IOC_SETFLAGS, &flags)
f.Close() f.Close()
if err != nil { if err != nil {
return fmt.Errorf("Set flags 0x%.8x failed: %w", flags, err) return fmt.Errorf("Set flags 0x%.8x failed: %w", flags, err)

View File

@@ -71,7 +71,7 @@ func (entry *Entry) ReadFileFlags(fullPath string, fileInfo os.FileInfo) error {
return nil return nil
} }
func (entry *Entry) SetAttributesToFile(fullPath string) error { func (entry *Entry) SetAttributesToFile(fullPath string, normalize bool) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() { if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() {
return nil return nil
} }
@@ -116,7 +116,7 @@ func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error {
} }
func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error { func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
if entry.Attributes == nil { if mask == 0xffffffff {
return nil return nil
} }
@@ -128,8 +128,10 @@ func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo,
var flags uint32 var flags uint32
if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have { if entry.Attributes != nil {
flags = binary.LittleEndian.Uint32(v) if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v)
}
} }
stat := fileInfo.Sys().(*syscall.Stat_t) stat := fileInfo.Sys().(*syscall.Stat_t)