libbtrfsutil: add subvolume iterator helpers
[platform/upstream/btrfs-progs.git] / libbtrfsutil / python / tests / test_subvolume.py
1 # Copyright (C) 2018 Facebook
2 #
3 # This file is part of libbtrfsutil.
4 #
5 # libbtrfsutil is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # libbtrfsutil is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with libbtrfsutil.  If not, see <http://www.gnu.org/licenses/>.
17
18 import fcntl
19 import errno
20 import os
21 import os.path
22 from pathlib import PurePath
23 import traceback
24
25 import btrfsutil
26 from tests import BtrfsTestCase, HAVE_PATH_LIKE
27
28
29 class TestSubvolume(BtrfsTestCase):
30     def test_is_subvolume(self):
31         dir = os.path.join(self.mountpoint, 'foo')
32         os.mkdir(dir)
33
34         for arg in self.path_or_fd(self.mountpoint):
35             with self.subTest(type=type(arg)):
36                 self.assertTrue(btrfsutil.is_subvolume(arg))
37         for arg in self.path_or_fd(dir):
38             with self.subTest(type=type(arg)):
39                 self.assertFalse(btrfsutil.is_subvolume(arg))
40
41         with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
42             btrfsutil.is_subvolume(os.path.join(self.mountpoint, 'bar'))
43         # This is a bit of an implementation detail, but really this is testing
44         # that the exception is initialized correctly.
45         self.assertEqual(e.exception.btrfsutilerror, btrfsutil.ERROR_STATFS_FAILED)
46         self.assertEqual(e.exception.errno, errno.ENOENT)
47
48     def test_subvolume_id(self):
49         dir = os.path.join(self.mountpoint, 'foo')
50         os.mkdir(dir)
51
52         for arg in self.path_or_fd(self.mountpoint):
53             with self.subTest(type=type(arg)):
54                 self.assertEqual(btrfsutil.subvolume_id(arg), 5)
55         for arg in self.path_or_fd(dir):
56             with self.subTest(type=type(arg)):
57                 self.assertEqual(btrfsutil.subvolume_id(arg), 5)
58
59     def test_subvolume_path(self):
60         btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'subvol1'))
61         os.mkdir(os.path.join(self.mountpoint, 'dir1'))
62         os.mkdir(os.path.join(self.mountpoint, 'dir1/dir2'))
63         btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'dir1/dir2/subvol2'))
64         btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'dir1/dir2/subvol2/subvol3'))
65         os.mkdir(os.path.join(self.mountpoint, 'subvol1/dir3'))
66         btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'subvol1/dir3/subvol4'))
67
68         for arg in self.path_or_fd(self.mountpoint):
69             with self.subTest(type=type(arg)):
70                 self.assertEqual(btrfsutil.subvolume_path(arg), '')
71                 self.assertEqual(btrfsutil.subvolume_path(arg, 5), '')
72                 self.assertEqual(btrfsutil.subvolume_path(arg, 256), 'subvol1')
73                 self.assertEqual(btrfsutil.subvolume_path(arg, 257), 'dir1/dir2/subvol2')
74                 self.assertEqual(btrfsutil.subvolume_path(arg, 258), 'dir1/dir2/subvol2/subvol3')
75                 self.assertEqual(btrfsutil.subvolume_path(arg, 259), 'subvol1/dir3/subvol4')
76
77         pwd = os.getcwd()
78         try:
79             os.chdir(self.mountpoint)
80             path = ''
81             for i in range(26):
82                 name = chr(ord('a') + i) * 255
83                 path = os.path.join(path, name)
84                 btrfsutil.create_subvolume(name)
85                 os.chdir(name)
86             self.assertEqual(btrfsutil.subvolume_path('.'), path)
87         finally:
88             os.chdir(pwd)
89
90     def test_subvolume_info(self):
91         for arg in self.path_or_fd(self.mountpoint):
92             with self.subTest(type=type(arg)):
93                 info = btrfsutil.subvolume_info(arg)
94                 self.assertEqual(info.id, 5)
95                 self.assertEqual(info.parent_id, 0)
96                 self.assertEqual(info.dir_id, 0)
97                 self.assertEqual(info.flags, 0)
98                 self.assertEqual(info.uuid, bytes(16))
99                 self.assertEqual(info.parent_uuid, bytes(16))
100                 self.assertEqual(info.received_uuid, bytes(16))
101                 self.assertNotEqual(info.generation, 0)
102                 self.assertEqual(info.ctransid, 0)
103                 self.assertEqual(info.otransid, 0)
104                 self.assertEqual(info.stransid, 0)
105                 self.assertEqual(info.rtransid, 0)
106                 self.assertEqual(info.ctime, 0)
107                 self.assertEqual(info.otime, 0)
108                 self.assertEqual(info.stime, 0)
109                 self.assertEqual(info.rtime, 0)
110
111         subvol = os.path.join(self.mountpoint, 'subvol')
112         btrfsutil.create_subvolume(subvol)
113
114         info = btrfsutil.subvolume_info(subvol)
115         self.assertEqual(info.id, 256)
116         self.assertEqual(info.parent_id, 5)
117         self.assertEqual(info.dir_id, 256)
118         self.assertEqual(info.flags, 0)
119         self.assertIsInstance(info.uuid, bytes)
120         self.assertEqual(info.parent_uuid, bytes(16))
121         self.assertEqual(info.received_uuid, bytes(16))
122         self.assertNotEqual(info.generation, 0)
123         self.assertNotEqual(info.ctransid, 0)
124         self.assertNotEqual(info.otransid, 0)
125         self.assertEqual(info.stransid, 0)
126         self.assertEqual(info.rtransid, 0)
127         self.assertNotEqual(info.ctime, 0)
128         self.assertNotEqual(info.otime, 0)
129         self.assertEqual(info.stime, 0)
130         self.assertEqual(info.rtime, 0)
131
132         # TODO: test received_uuid, stransid, rtransid, stime, and rtime
133
134         for arg in self.path_or_fd(self.mountpoint):
135             with self.subTest(type=type(arg)):
136                 with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
137                     # BTRFS_EXTENT_TREE_OBJECTID
138                     btrfsutil.subvolume_info(arg, 2)
139
140     def test_read_only(self):
141         for arg in self.path_or_fd(self.mountpoint):
142             with self.subTest(type=type(arg)):
143                 btrfsutil.set_subvolume_read_only(arg)
144                 self.assertTrue(btrfsutil.get_subvolume_read_only(arg))
145                 self.assertTrue(btrfsutil.subvolume_info(arg).flags & 1)
146
147                 btrfsutil.set_subvolume_read_only(arg, False)
148                 self.assertFalse(btrfsutil.get_subvolume_read_only(arg))
149                 self.assertFalse(btrfsutil.subvolume_info(arg).flags & 1)
150
151                 btrfsutil.set_subvolume_read_only(arg, True)
152                 self.assertTrue(btrfsutil.get_subvolume_read_only(arg))
153                 self.assertTrue(btrfsutil.subvolume_info(arg).flags & 1)
154
155                 btrfsutil.set_subvolume_read_only(arg, False)
156
157     def test_default_subvolume(self):
158         for arg in self.path_or_fd(self.mountpoint):
159             with self.subTest(type=type(arg)):
160                 self.assertEqual(btrfsutil.get_default_subvolume(arg), 5)
161
162         subvol = os.path.join(self.mountpoint, 'subvol')
163         btrfsutil.create_subvolume(subvol)
164         for arg in self.path_or_fd(subvol):
165             with self.subTest(type=type(arg)):
166                 btrfsutil.set_default_subvolume(arg)
167                 self.assertEqual(btrfsutil.get_default_subvolume(arg), 256)
168                 btrfsutil.set_default_subvolume(arg, 5)
169                 self.assertEqual(btrfsutil.get_default_subvolume(arg), 5)
170
171     def test_create_subvolume(self):
172         subvol = os.path.join(self.mountpoint, 'subvol')
173
174         btrfsutil.create_subvolume(subvol + '1')
175         self.assertTrue(btrfsutil.is_subvolume(subvol + '1'))
176         btrfsutil.create_subvolume((subvol + '2').encode())
177         self.assertTrue(btrfsutil.is_subvolume(subvol + '2'))
178         if HAVE_PATH_LIKE:
179             btrfsutil.create_subvolume(PurePath(subvol + '3'))
180             self.assertTrue(btrfsutil.is_subvolume(subvol + '3'))
181
182         pwd = os.getcwd()
183         try:
184             os.chdir(self.mountpoint)
185             btrfsutil.create_subvolume('subvol4')
186             self.assertTrue(btrfsutil.is_subvolume('subvol4'))
187         finally:
188             os.chdir(pwd)
189
190         btrfsutil.create_subvolume(subvol + '5/')
191         self.assertTrue(btrfsutil.is_subvolume(subvol + '5'))
192
193         btrfsutil.create_subvolume(subvol + '6//')
194         self.assertTrue(btrfsutil.is_subvolume(subvol + '6'))
195
196         transid = btrfsutil.create_subvolume(subvol + '7', async=True)
197         self.assertTrue(btrfsutil.is_subvolume(subvol + '7'))
198         self.assertGreater(transid, 0)
199
200         # Test creating subvolumes under '/' in a chroot.
201         pid = os.fork()
202         if pid == 0:
203             try:
204                 os.chroot(self.mountpoint)
205                 os.chdir('/')
206                 btrfsutil.create_subvolume('/subvol8')
207                 self.assertTrue(btrfsutil.is_subvolume('/subvol8'))
208                 with self.assertRaises(btrfsutil.BtrfsUtilError):
209                     btrfsutil.create_subvolume('/')
210                 os._exit(0)
211             except Exception:
212                 traceback.print_exc()
213                 os._exit(1)
214         wstatus = os.waitpid(pid, 0)[1]
215         self.assertTrue(os.WIFEXITED(wstatus))
216         self.assertEqual(os.WEXITSTATUS(wstatus), 0)
217
218     def test_subvolume_iterator(self):
219         pwd = os.getcwd()
220         try:
221             os.chdir(self.mountpoint)
222             btrfsutil.create_subvolume('foo')
223
224             path, subvol = next(btrfsutil.SubvolumeIterator('.', info=True))
225             self.assertEqual(path, 'foo')
226             self.assertIsInstance(subvol, btrfsutil.SubvolumeInfo)
227             self.assertEqual(subvol.id, 256)
228             self.assertEqual(subvol.parent_id, 5)
229
230             btrfsutil.create_subvolume('foo/bar')
231             btrfsutil.create_subvolume('foo/bar/baz')
232
233             subvols = [
234                 ('foo', 256),
235                 ('foo/bar', 257),
236                 ('foo/bar/baz', 258),
237             ]
238
239             for arg in self.path_or_fd('.'):
240                 with self.subTest(type=type(arg)):
241                     self.assertEqual(list(btrfsutil.SubvolumeIterator(arg)), subvols)
242             self.assertEqual(list(btrfsutil.SubvolumeIterator('.', top=0)), subvols)
243
244             self.assertEqual(list(btrfsutil.SubvolumeIterator('.', post_order=True)),
245                              [('foo/bar/baz', 258),
246                               ('foo/bar', 257),
247                               ('foo', 256)])
248
249             subvols = [
250                 ('bar', 257),
251                 ('bar/baz', 258),
252             ]
253
254             self.assertEqual(list(btrfsutil.SubvolumeIterator('.', top=256)), subvols)
255             self.assertEqual(list(btrfsutil.SubvolumeIterator('foo', top=0)), subvols)
256
257             os.rename('foo/bar/baz', 'baz')
258             self.assertEqual(sorted(btrfsutil.SubvolumeIterator('.')),
259                              [('baz', 258),
260                               ('foo', 256),
261                               ('foo/bar', 257)])
262
263             with btrfsutil.SubvolumeIterator('.') as it:
264                 self.assertGreaterEqual(it.fileno(), 0)
265                 it.close()
266                 with self.assertRaises(ValueError):
267                     next(iter(it))
268                 with self.assertRaises(ValueError):
269                     it.fileno()
270                 it.close()
271         finally:
272             os.chdir(pwd)