Repository: borgbackup/borg

Issue:
When extracting a macOS locked file, some metadata is lost with "Operation not permitted"
## Description

When restoring a file that is locked using the macOS HFS "Locked" Finder flag, the file should be locked _last_ to avoid causing other metadata operations to fail.

## Environment

* macOS 10.12.6
* Borg 1.1.1

## Steps to replicate

1. Prepare a locked file that also has an extended attribute:
```
mkdir test; cd test
mkdir src; cd src
echo "file contents" > locked-file
xattr -w extra.data "this is an xattr" locked-file
SetFile -a L locked-file
cd ..
```

2. Back up the file:
```
sudo borg init -e repokey sam@keep:/storage/backupdisk/borg/lock-test
sudo borg create sam@keep:/storage/backupdisk/borg/lock-test::test1 src
```

3. Restore the file:
```
mkdir dst; cd dst
sudo borg extract --strip-components=1 sam@keep:/storage/backupdisk/borg/lock-test::test1 src
cd -
```

## Expected result

* `borg extract` has no output.
* Resulting file has an extended attribute named `extra.data` with contents `this is an xattr`.
    * Verify this with `xattr -pl extra.data dst/locked-file`.

## Actual result

* `borg extract` says: `attrs: [Errno 1] Operation not permitted: '<FD 7>'`
* Resulting file has no extended attribute named `extra.data`.

## Notes

* No other Finder flags are restored properly. Except `Invisible` for some reason.


Discussion:
@smammy so, "locked" is a bsdflag, right? Is this the same as the "immutable" flag?

Borg first sets bsdflags, then xattrs. So, can we just reverse order or do we run into other issues then?
@smammy can you try changing the order and check whether it works as expected then? also concerning the "other" (which?) finder flags?

The code is in borg/archive.py, method restore_attrs. Just move the if-block dealing with bsdflags to the end of the method (after the xattrs block).

Resolving patch:
diff --git a/src/borg/archive.py b/src/borg/archive.py
--- a/src/borg/archive.py
+++ b/src/borg/archive.py
@@ -692,12 +692,6 @@ def restore_attrs(self, path, item, symlink=False, fd=None):
             # some systems don't support calling utime on a symlink
             pass
         acl_set(path, item, self.numeric_owner)
-
-        if not self.nobsdflags and 'bsdflags' in item:
-            try:
-                set_flags(path, item.bsdflags, fd=fd)
-            except OSError:
-                pass
         # chown removes Linux capabilities, so set the extended attributes at the end, after chown, since they include
         # the Linux capabilities in the "security.capability" attribute.
         xattrs = item.get('xattrs', {})
@@ -720,6 +714,12 @@ def restore_attrs(self, path, item, symlink=False, fd=None):
                     set_ec(EXIT_WARNING)
                 else:
                     raise
+        # bsdflags include the immutable flag and need to be set last:
+        if not self.nobsdflags and 'bsdflags' in item:
+            try:
+                set_flags(path, item.bsdflags, fd=fd)
+            except OSError:
+                pass
 
     def set_meta(self, key, value):
         metadata = self._load_meta(self.id)


Test patch: