4 # fuse_xattrs - Add xattrs support using sidecar files
6 # Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
8 # This program can be distributed under the terms of the GNU GPL.
9 # See the file COPYING.
14 from pathlib import Path
17 if xattr.__version__ != '0.9.1':
18 print("WARNING, only tested with xattr version 0.9.1")
21 # - listxattr: list too long
22 # - sidecar file permissions
23 # - corrupt metadata files
26 class TestXAttrs(unittest.TestCase):
28 self.sourceDir = "./source/"
29 self.mountDir = "./mount/"
30 self.randomFilename = "foo.txt"
32 self.randomFile = self.mountDir + self.randomFilename
33 self.randomFileSidecar = self.randomFile + ".xattr"
35 self.randomSourceFile = self.sourceDir + self.randomFilename
36 self.randomSourceFileSidecar = self.randomSourceFile + ".xattr"
38 if os.path.isfile(self.randomFile):
39 os.remove(self.randomFile)
41 if os.path.isfile(self.randomFileSidecar):
42 os.remove(self.randomFileSidecar)
44 Path(self.randomFile).touch()
45 self.assertTrue(os.path.isfile(self.randomFile))
46 self.assertFalse(os.path.isfile(self.randomFileSidecar))
49 if os.path.isfile(self.randomFile):
50 os.remove(self.randomFile)
52 if os.path.isfile(self.randomFileSidecar):
53 os.remove(self.randomFileSidecar)
55 def test_xattr_set(self):
56 xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
58 def test_xattr_set_name_max_length(self):
60 key = "user." + "x" * 250
62 self.assertEqual(len(key), 255)
63 xattr.setxattr(self.randomFile, key, bytes(value, enc))
65 key = "user." + "x" * 251
66 self.assertEqual(len(key), 256)
67 with self.assertRaises(OSError) as ex:
68 xattr.setxattr(self.randomFile, key, bytes(value, enc))
69 self.assertEqual(ex.exception.errno, 34)
70 self.assertEqual(ex.exception.strerror, "Numerical result out of range")
72 @unittest.expectedFailure
73 def test_xattr_set_value_max_size(self):
76 value = "x" * (64 * 1024 + 1) # we want max 64KiB of data
77 with self.assertRaises(OSError) as ex:
78 xattr.setxattr(self.randomFile, key, bytes(value, enc))
80 # on btrfs we get "no space left on device"
81 self.assertEqual(ex.exception.errno, 28)
82 self.assertEqual(ex.exception.strerror, "No space left on device")
84 # on fuse_xattr we get "Argument list too long"
85 # the error is thrown by fuse, not by fuse_xattr code
87 def test_xattr_set_namespaces(self):
88 with self.assertRaises(OSError) as ex:
89 xattr.setxattr(self.randomFile, "system.foo", bytes("bar", "utf-8"))
90 self.assertEqual(ex.exception.errno, 95)
91 self.assertEqual(ex.exception.strerror, "Operation not supported")
93 with self.assertRaises(OSError) as ex:
94 xattr.setxattr(self.randomFile, "trust.foo", bytes("bar", "utf-8"))
95 self.assertEqual(ex.exception.errno, 95)
96 self.assertEqual(ex.exception.strerror, "Operation not supported")
98 with self.assertRaises(OSError) as ex:
99 xattr.setxattr(self.randomFile, "foo.foo", bytes("bar", "utf-8"))
100 self.assertEqual(ex.exception.errno, 95)
101 self.assertEqual(ex.exception.strerror, "Operation not supported")
103 with self.assertRaises(PermissionError) as ex:
104 xattr.setxattr(self.randomFile, "security.foo", bytes("bar", "utf-8"))
105 self.assertEqual(ex.exception.errno, 1)
106 self.assertEqual(ex.exception.strerror, "Operation not permitted")
108 def test_xattr_get_non_existent(self):
110 with self.assertRaises(OSError) as ex:
111 xattr.getxattr(self.randomFile, key)
112 self.assertEqual(ex.exception.errno, 61)
113 self.assertEqual(ex.exception.strerror, "No data available")
115 def test_xattr_get(self):
119 xattr.setxattr(self.randomFile, key, bytes(value, enc))
120 read_value = xattr.getxattr(self.randomFile, key)
121 self.assertEqual(value, read_value.decode(enc))
123 def test_xattr_set_empty(self):
127 xattr.setxattr(self.randomFile, key, bytes(value, enc))
128 read_value = xattr.getxattr(self.randomFile, key)
129 self.assertEqual(value, read_value.decode(enc))
131 def test_xattr_set_override(self):
136 xattr.setxattr(self.randomFile, key, bytes(value1, enc))
137 xattr.setxattr(self.randomFile, key, bytes(value2, enc))
138 read_value = xattr.getxattr(self.randomFile, key)
139 self.assertEqual(value2, read_value.decode(enc))
141 def test_xattr_set_create(self):
146 xattr.setxattr(self.randomFile, key, bytes(value1, enc), xattr.XATTR_CREATE)
147 with self.assertRaises(FileExistsError) as ex:
148 xattr.setxattr(self.randomFile, key, bytes(value2, enc), xattr.XATTR_CREATE)
149 self.assertEqual(ex.exception.errno, 17)
150 self.assertEqual(ex.exception.strerror, "File exists")
152 read_value = xattr.getxattr(self.randomFile, key)
153 self.assertEqual(value1, read_value.decode(enc))
155 def test_xattr_set_replace(self):
159 with self.assertRaises(OSError) as ex:
160 xattr.setxattr(self.randomFile, key, bytes(value, enc), xattr.XATTR_REPLACE)
161 self.assertEqual(ex.exception.errno, 61)
162 self.assertEqual(ex.exception.strerror, "No data available")
164 with self.assertRaises(OSError) as ex:
165 xattr.getxattr(self.randomFile, key)
166 self.assertEqual(ex.exception.errno, 61)
167 self.assertEqual(ex.exception.strerror, "No data available")
169 def test_xattr_list_empty(self):
170 attrs = xattr.listxattr(self.randomFile)
171 self.assertEqual(len(attrs), 0)
173 def test_xattr_list(self):
181 xattr.setxattr(self.randomFile, key1, bytes(value, enc))
182 xattr.setxattr(self.randomFile, key2, bytes(value, enc))
183 xattr.setxattr(self.randomFile, key3, bytes(value, enc))
185 # get and check the list
186 attrs = xattr.listxattr(self.randomFile)
188 self.assertEqual(len(attrs), 3)
189 self.assertTrue(key1 in attrs)
190 self.assertTrue(key2 in attrs)
191 self.assertTrue(key3 in attrs)
193 def test_xattr_unicode(self):
195 key = "user.fooシüßてЙĘ𝄠✠"
199 xattr.setxattr(self.randomFile, key, bytes(value, enc))
202 attrs = xattr.listxattr(self.randomFile)
203 self.assertEqual(len(attrs), 1)
204 self.assertEqual(attrs[0], key)
207 read_value = xattr.getxattr(self.randomFile, key)
208 self.assertEqual(value, read_value.decode(enc))
211 xattr.removexattr(self.randomFile, key)
213 def test_xattr_remove(self):
219 xattr.setxattr(self.randomFile, key, bytes(value, enc))
222 xattr.removexattr(self.randomFile, key)
224 # list should be empty
225 attrs = xattr.listxattr(self.randomFile)
226 self.assertEqual(len(attrs), 0)
228 # should fail when trying to read it
229 with self.assertRaises(OSError) as ex:
230 xattr.getxattr(self.randomFile, key)
231 self.assertEqual(ex.exception.errno, 61)
232 self.assertEqual(ex.exception.strerror, "No data available")
234 # removing twice should fail
235 with self.assertRaises(OSError) as ex:
236 xattr.getxattr(self.randomFile, key)
237 self.assertEqual(ex.exception.errno, 61)
238 self.assertEqual(ex.exception.strerror, "No data available")
240 def test_hide_sidecar(self):
241 xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
242 self.assertTrue(os.path.isfile(self.randomFile))
243 self.assertFalse(os.path.isfile(self.randomFileSidecar))
245 sidecarFilename = self.randomFilename + ".xattr"
246 files_mount = os.listdir(self.mountDir)
247 self.assertTrue(self.randomFilename in files_mount)
248 self.assertTrue(sidecarFilename not in files_mount)
250 files_source = os.listdir(self.sourceDir)
251 self.assertTrue(self.randomFilename in files_source)
252 self.assertTrue(sidecarFilename in files_source)
254 def test_create_new_file(self):
255 test_filename = "test_create_new_file"
256 self.assertFalse(os.path.isfile(self.sourceDir + test_filename))
257 self.assertFalse(os.path.isfile(self.mountDir + test_filename))
259 open(self.mountDir + test_filename, "a").close()
260 self.assertTrue(os.path.isfile(self.sourceDir + test_filename))
261 self.assertTrue(os.path.isfile(self.mountDir + test_filename))
262 # FIXME: if one assert fails, the file isn't going to be deleted
263 os.remove(self.mountDir + test_filename)
265 def test_remove_file_with_sidecar(self):
266 xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
267 self.assertTrue(os.path.isfile(self.randomFile))
268 self.assertTrue(os.path.isfile(self.randomSourceFile))
269 self.assertTrue(os.path.isfile(self.randomSourceFileSidecar))
271 os.remove(self.randomFile)
272 self.assertFalse(os.path.isfile(self.randomFile))
273 self.assertFalse(os.path.isfile(self.randomSourceFile))
274 self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
276 def test_remove_file_without_sidecar(self):
277 self.assertTrue(os.path.isfile(self.randomFile))
278 self.assertTrue(os.path.isfile(self.randomSourceFile))
279 self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
281 os.remove(self.randomFile)
282 self.assertFalse(os.path.isfile(self.randomFile))
283 self.assertFalse(os.path.isfile(self.randomSourceFile))
284 self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
286 if __name__ == '__main__':