From aa7029dcf66aaf4d5afdba65c613102e0d5a884e Mon Sep 17 00:00:00 2001 From: doron zarhi Date: Sun, 6 Mar 2022 14:06:53 +0200 Subject: [PATCH] fs: add xattr support --- src/rpcclient/rpcclient/darwin/fs.py | 44 ++++++++++++++++++++++++++++ src/rpcclient/tests/test_fs.py | 9 ++++++ 2 files changed, 53 insertions(+) diff --git a/src/rpcclient/rpcclient/darwin/fs.py b/src/rpcclient/rpcclient/darwin/fs.py index 65cbf51b..c6702156 100644 --- a/src/rpcclient/rpcclient/darwin/fs.py +++ b/src/rpcclient/rpcclient/darwin/fs.py @@ -1,3 +1,5 @@ +from typing import List, Mapping + from rpcclient.common import path_to_str from rpcclient.exceptions import BadReturnValueError from rpcclient.fs import Fs, DirEntry, ScandirIterator @@ -55,6 +57,48 @@ def scandir(self, path: str = '.'): raise BadReturnValueError(f'failed to opendir(): {path} ({self._client.last_error})') return DarwinScandirIterator(path, dp, self._client) + @path_to_str('path') + def setxattr(self, path: str, name: str, value: bytes): + """ set an extended attribute value """ + count = self._client.symbols.setxattr(path, name, value, len(value), 0, 0).c_int64 + if count == -1: + raise BadReturnValueError(f'failed to setxattr(): {path} ({self._client.last_error})') + + @path_to_str('path') + def removexattr(self, path: str, name: str): + """ remove an extended attribute value """ + count = self._client.symbols.removexattr(path, name, 0).c_int64 + if count == -1: + raise BadReturnValueError(f'failed to removexattr(): {path} ({self._client.last_error})') + + @path_to_str('path') + def listxattr(self, path: str) -> List[str]: + """ list extended attribute names """ + max_buf_len = 1024 + with self._client.safe_malloc(max_buf_len) as xattributes_names: + count = self._client.symbols.listxattr(path, xattributes_names, max_buf_len, 0).c_int64 + if count == -1: + raise BadReturnValueError(f'failed to listxattr(): {path} ({self._client.last_error})') + return [s.decode() for s in xattributes_names.peek(count).split(b'\x00')[:-1]] + + @path_to_str('path') + def getxattr(self, path: str, name: str) -> bytes: + """ get an extended attribute value """ + max_buf_len = 1024 + with self._client.safe_malloc(max_buf_len) as value: + count = self._client.symbols.getxattr(path, name, value, max_buf_len, 0, 0).c_int64 + if count == -1: + raise BadReturnValueError(f'failed to getxattr(): {path} ({self._client.last_error})') + return value.peek(count) + + @path_to_str('path') + def dictxattr(self, path: str) -> Mapping[str, bytes]: + """ get a dictionary of all extended attributes """ + result = {} + for k in self.listxattr(path): + result[k] = self.getxattr(path, k) + return result + @path_to_str('path') def statfs(self, path: str): with self._client.safe_malloc(statfs64.sizeof()) as buf: diff --git a/src/rpcclient/tests/test_fs.py b/src/rpcclient/tests/test_fs.py index df2e5eb8..44c2f194 100644 --- a/src/rpcclient/tests/test_fs.py +++ b/src/rpcclient/tests/test_fs.py @@ -118,3 +118,12 @@ def test_walk(client, tmp_path): (tmp_path / 'dir_b' / 'b1.txt').touch() (tmp_path / 'dir_b' / 'b2.txt').touch() assert list(os.walk(tmp_path)) == list(client.fs.walk(tmp_path)) + + +def test_xattr(client, tmp_path): + client.fs.setxattr(tmp_path, 'KEY', b'VALUE') + assert client.fs.getxattr(tmp_path, 'KEY') == b'VALUE' + assert client.fs.listxattr(tmp_path) == ['KEY'] + assert client.fs.dictxattr(tmp_path) == {'KEY': b'VALUE'} + client.fs.removexattr(tmp_path, 'KEY') + assert client.fs.listxattr(tmp_path) == []