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
18 # - listxattr: list too long
19 # - sidecar file permissions
20 # - corrupt metadata files
23 class TestXAttrs(unittest.TestCase):
25 self.sourceDir = "./source/"
26 self.mountDir = "./mount/"
27 self.randomFilename = "foo.txt"
29 self.randomFile = self.mountDir + self.randomFilename
30 self.randomFileSidecar = self.randomFile + ".xattr"
32 self.randomSourceFile = self.sourceDir + self.randomFilename
33 self.randomSourceFileSidecar = self.randomSourceFile + ".xattr"
35 if os.path.isfile(self.randomFile):
36 os.remove(self.randomFile)
38 if os.path.isfile(self.randomFileSidecar):
39 os.remove(self.randomFileSidecar)
41 Path(self.randomFile).touch()
42 self.assertTrue(os.path.isfile(self.randomFile))
43 self.assertFalse(os.path.isfile(self.randomFileSidecar))
46 if os.path.isfile(self.randomFile):
47 os.remove(self.randomFile)
49 if os.path.isfile(self.randomFileSidecar):
50 os.remove(self.randomFileSidecar)
52 def test_xattr_set(self):
53 xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
55 def test_xattr_set_name_max_length(self):
57 key = "user." + "x" * 250
59 self.assertEqual(len(key), 255)
60 xattr.setxattr(self.randomFile, key, bytes(value, enc))
62 key = "user." + "x" * 251
63 self.assertEqual(len(key), 256)
64 with self.assertRaises(OSError) as ex:
65 xattr.setxattr(self.randomFile, key, bytes(value, enc))
66 self.assertEqual(ex.exception.errno, 34)
67 self.assertEqual(ex.exception.strerror, "Numerical result out of range")
69 @unittest.expectedFailure
70 def test_xattr_set_value_max_size(self):
73 value = "x" * (64 * 1024 + 1) # we want max 64KiB of data
74 with self.assertRaises(OSError) as ex:
75 xattr.setxattr(self.randomFile, key, bytes(value, enc))
77 # on btrfs we get "no space left on device"
78 self.assertEqual(ex.exception.errno, 28)
79 self.assertEqual(ex.exception.strerror, "No space left on device")
81 # on fuse_xattr we get "Argument list too long"
82 # the error is thrown by fuse, not by fuse_xattr code
84 def test_xattr_set_namespaces(self):
85 with self.assertRaises(OSError) as ex:
86 xattr.setxattr(self.randomFile, "system.foo", bytes("bar", "utf-8"))
87 self.assertEqual(ex.exception.errno, 95)
88 self.assertEqual(ex.exception.strerror, "Operation not supported")
90 with self.assertRaises(OSError) as ex:
91 xattr.setxattr(self.randomFile, "trust.foo", bytes("bar", "utf-8"))
92 self.assertEqual(ex.exception.errno, 95)
93 self.assertEqual(ex.exception.strerror, "Operation not supported")
95 with self.assertRaises(OSError) as ex:
96 xattr.setxattr(self.randomFile, "foo.foo", bytes("bar", "utf-8"))
97 self.assertEqual(ex.exception.errno, 95)
98 self.assertEqual(ex.exception.strerror, "Operation not supported")
100 with self.assertRaises(PermissionError) as ex:
101 xattr.setxattr(self.randomFile, "security.foo", bytes("bar", "utf-8"))
102 self.assertEqual(ex.exception.errno, 1)
103 self.assertEqual(ex.exception.strerror, "Operation not permitted")
105 def test_xattr_get_non_existent(self):
107 with self.assertRaises(OSError) as ex:
108 xattr.getxattr(self.randomFile, key)
109 self.assertEqual(ex.exception.errno, 61)
110 self.assertEqual(ex.exception.strerror, "No data available")
112 def test_xattr_get(self):
116 xattr.setxattr(self.randomFile, key, bytes(value, enc))
117 read_value = xattr.getxattr(self.randomFile, key)
118 self.assertEqual(value, read_value.decode(enc))
120 def test_xattr_set_override(self):
125 xattr.setxattr(self.randomFile, key, bytes(value1, enc))
126 xattr.setxattr(self.randomFile, key, bytes(value2, enc))
127 read_value = xattr.getxattr(self.randomFile, key)
128 self.assertEqual(value2, read_value.decode(enc))
130 def test_xattr_set_create(self):
135 xattr.setxattr(self.randomFile, key, bytes(value1, enc), xattr.XATTR_CREATE)
136 with self.assertRaises(FileExistsError) as ex:
137 xattr.setxattr(self.randomFile, key, bytes(value2, enc), xattr.XATTR_CREATE)
138 self.assertEqual(ex.exception.errno, 17)
139 self.assertEqual(ex.exception.strerror, "File exists")
141 read_value = xattr.getxattr(self.randomFile, key)
142 self.assertEqual(value1, read_value.decode(enc))
144 def test_xattr_set_replace(self):
148 with self.assertRaises(OSError) as ex:
149 xattr.setxattr(self.randomFile, key, bytes(value, enc), xattr.XATTR_REPLACE)
150 self.assertEqual(ex.exception.errno, 61)
151 self.assertEqual(ex.exception.strerror, "No data available")
153 with self.assertRaises(OSError) as ex:
154 xattr.getxattr(self.randomFile, key)
155 self.assertEqual(ex.exception.errno, 61)
156 self.assertEqual(ex.exception.strerror, "No data available")
158 def test_xattr_list_empty(self):
159 attrs = xattr.listxattr(self.randomFile)
160 self.assertEqual(len(attrs), 0)
162 def test_xattr_list(self):
170 xattr.setxattr(self.randomFile, key1, bytes(value, enc))
171 xattr.setxattr(self.randomFile, key2, bytes(value, enc))
172 xattr.setxattr(self.randomFile, key3, bytes(value, enc))
174 # get and check the list
175 attrs = xattr.listxattr(self.randomFile)
177 self.assertEqual(len(attrs), 3)
178 self.assertTrue(key1 in attrs)
179 self.assertTrue(key2 in attrs)
180 self.assertTrue(key3 in attrs)
182 def test_xattr_unicode(self):
184 key = "user.fooシüßてЙĘ𝄠✠"
188 xattr.setxattr(self.randomFile, key, bytes(value, enc))
191 attrs = xattr.listxattr(self.randomFile)
192 self.assertEqual(len(attrs), 1)
193 self.assertTrue(key.encode() in attrs)
196 read_value = xattr.getxattr(self.randomFile, key)
197 self.assertEqual(value, read_value.decode(enc))
200 xattr.removexattr(self.randomFile, key)
202 def test_xattr_remove(self):
208 xattr.setxattr(self.randomFile, key, bytes(value, enc))
211 xattr.removexattr(self.randomFile, key)
213 # list should be empty
214 attrs = xattr.listxattr(self.randomFile)
215 self.assertEqual(len(attrs), 0)
217 # should fail when trying to read it
218 with self.assertRaises(OSError) as ex:
219 xattr.getxattr(self.randomFile, key)
220 self.assertEqual(ex.exception.errno, 61)
221 self.assertEqual(ex.exception.strerror, "No data available")
223 # removing twice should fail
224 with self.assertRaises(OSError) as ex:
225 xattr.getxattr(self.randomFile, key)
226 self.assertEqual(ex.exception.errno, 61)
227 self.assertEqual(ex.exception.strerror, "No data available")
229 def test_hide_sidecar(self):
230 xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
231 self.assertTrue(os.path.isfile(self.randomFile))
232 self.assertFalse(os.path.isfile(self.randomFileSidecar))
234 sidecarFilename = self.randomFilename + ".xattr"
235 files_mount = os.listdir(self.mountDir)
236 self.assertTrue(self.randomFilename in files_mount)
237 self.assertTrue(sidecarFilename not in files_mount)
239 files_source = os.listdir(self.sourceDir)
240 self.assertTrue(self.randomFilename in files_source)
241 self.assertTrue(sidecarFilename in files_source)
243 def test_create_new_file(self):
244 test_filename = "test_create_new_file"
245 self.assertFalse(os.path.isfile(self.sourceDir + test_filename))
246 self.assertFalse(os.path.isfile(self.mountDir + test_filename))
248 open(self.mountDir + test_filename, "a").close()
249 self.assertTrue(os.path.isfile(self.sourceDir + test_filename))
250 self.assertTrue(os.path.isfile(self.mountDir + test_filename))
251 # FIXME: if one assert fails, the file isn't going to be deleted
252 os.remove(self.mountDir + test_filename)
254 def test_remove_file_with_sidecar(self):
255 xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
256 self.assertTrue(os.path.isfile(self.randomFile))
257 self.assertTrue(os.path.isfile(self.randomSourceFile))
258 self.assertTrue(os.path.isfile(self.randomSourceFileSidecar))
260 os.remove(self.randomFile)
261 self.assertFalse(os.path.isfile(self.randomFile))
262 self.assertFalse(os.path.isfile(self.randomSourceFile))
263 self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
265 def test_remove_file_without_sidecar(self):
266 self.assertTrue(os.path.isfile(self.randomFile))
267 self.assertTrue(os.path.isfile(self.randomSourceFile))
268 self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
270 os.remove(self.randomFile)
271 self.assertFalse(os.path.isfile(self.randomFile))
272 self.assertFalse(os.path.isfile(self.randomSourceFile))
273 self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
275 if __name__ == '__main__':