1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #include <linux/fs.h> |
4 | #include <linux/types.h> |
5 | #include "ctree.h" |
6 | #include "disk-io.h" |
7 | #include "btrfs_inode.h" |
8 | #include "export.h" |
9 | #include "accessors.h" |
10 | #include "super.h" |
11 | |
12 | #define BTRFS_FID_SIZE_NON_CONNECTABLE (offsetof(struct btrfs_fid, \ |
13 | parent_objectid) / 4) |
14 | #define BTRFS_FID_SIZE_CONNECTABLE (offsetof(struct btrfs_fid, \ |
15 | parent_root_objectid) / 4) |
16 | #define BTRFS_FID_SIZE_CONNECTABLE_ROOT (sizeof(struct btrfs_fid) / 4) |
17 | |
18 | static int btrfs_encode_fh(struct inode *inode, u32 *fh, int *max_len, |
19 | struct inode *parent) |
20 | { |
21 | struct btrfs_fid *fid = (struct btrfs_fid *)fh; |
22 | int len = *max_len; |
23 | int type; |
24 | |
25 | if (parent && (len < BTRFS_FID_SIZE_CONNECTABLE)) { |
26 | *max_len = BTRFS_FID_SIZE_CONNECTABLE; |
27 | return FILEID_INVALID; |
28 | } else if (len < BTRFS_FID_SIZE_NON_CONNECTABLE) { |
29 | *max_len = BTRFS_FID_SIZE_NON_CONNECTABLE; |
30 | return FILEID_INVALID; |
31 | } |
32 | |
33 | len = BTRFS_FID_SIZE_NON_CONNECTABLE; |
34 | type = FILEID_BTRFS_WITHOUT_PARENT; |
35 | |
36 | fid->objectid = btrfs_ino(inode: BTRFS_I(inode)); |
37 | fid->root_objectid = BTRFS_I(inode)->root->root_key.objectid; |
38 | fid->gen = inode->i_generation; |
39 | |
40 | if (parent) { |
41 | u64 parent_root_id; |
42 | |
43 | fid->parent_objectid = BTRFS_I(inode: parent)->location.objectid; |
44 | fid->parent_gen = parent->i_generation; |
45 | parent_root_id = BTRFS_I(inode: parent)->root->root_key.objectid; |
46 | |
47 | if (parent_root_id != fid->root_objectid) { |
48 | fid->parent_root_objectid = parent_root_id; |
49 | len = BTRFS_FID_SIZE_CONNECTABLE_ROOT; |
50 | type = FILEID_BTRFS_WITH_PARENT_ROOT; |
51 | } else { |
52 | len = BTRFS_FID_SIZE_CONNECTABLE; |
53 | type = FILEID_BTRFS_WITH_PARENT; |
54 | } |
55 | } |
56 | |
57 | *max_len = len; |
58 | return type; |
59 | } |
60 | |
61 | /* |
62 | * Read dentry of inode with @objectid from filesystem root @root_objectid. |
63 | * |
64 | * @sb: the filesystem super block |
65 | * @objectid: inode objectid |
66 | * @root_objectid: object id of the subvolume root where to look up the inode |
67 | * @generation: optional, if not zero, verify that the found inode |
68 | * generation matches |
69 | * |
70 | * Return dentry alias for the inode, otherwise an error. In case the |
71 | * generation does not match return ESTALE. |
72 | */ |
73 | struct dentry *btrfs_get_dentry(struct super_block *sb, u64 objectid, |
74 | u64 root_objectid, u64 generation) |
75 | { |
76 | struct btrfs_fs_info *fs_info = btrfs_sb(sb); |
77 | struct btrfs_root *root; |
78 | struct inode *inode; |
79 | |
80 | if (objectid < BTRFS_FIRST_FREE_OBJECTID) |
81 | return ERR_PTR(error: -ESTALE); |
82 | |
83 | root = btrfs_get_fs_root(fs_info, objectid: root_objectid, check_ref: true); |
84 | if (IS_ERR(ptr: root)) |
85 | return ERR_CAST(ptr: root); |
86 | |
87 | inode = btrfs_iget(s: sb, ino: objectid, root); |
88 | btrfs_put_root(root); |
89 | if (IS_ERR(ptr: inode)) |
90 | return ERR_CAST(ptr: inode); |
91 | |
92 | if (generation != 0 && generation != inode->i_generation) { |
93 | iput(inode); |
94 | return ERR_PTR(error: -ESTALE); |
95 | } |
96 | |
97 | return d_obtain_alias(inode); |
98 | } |
99 | |
100 | static struct dentry *btrfs_fh_to_parent(struct super_block *sb, struct fid *fh, |
101 | int fh_len, int fh_type) |
102 | { |
103 | struct btrfs_fid *fid = (struct btrfs_fid *) fh; |
104 | u64 objectid, root_objectid; |
105 | u32 generation; |
106 | |
107 | if (fh_type == FILEID_BTRFS_WITH_PARENT) { |
108 | if (fh_len < BTRFS_FID_SIZE_CONNECTABLE) |
109 | return NULL; |
110 | root_objectid = fid->root_objectid; |
111 | } else if (fh_type == FILEID_BTRFS_WITH_PARENT_ROOT) { |
112 | if (fh_len < BTRFS_FID_SIZE_CONNECTABLE_ROOT) |
113 | return NULL; |
114 | root_objectid = fid->parent_root_objectid; |
115 | } else |
116 | return NULL; |
117 | |
118 | objectid = fid->parent_objectid; |
119 | generation = fid->parent_gen; |
120 | |
121 | return btrfs_get_dentry(sb, objectid, root_objectid, generation); |
122 | } |
123 | |
124 | static struct dentry *btrfs_fh_to_dentry(struct super_block *sb, struct fid *fh, |
125 | int fh_len, int fh_type) |
126 | { |
127 | struct btrfs_fid *fid = (struct btrfs_fid *) fh; |
128 | u64 objectid, root_objectid; |
129 | u32 generation; |
130 | |
131 | if ((fh_type != FILEID_BTRFS_WITH_PARENT || |
132 | fh_len < BTRFS_FID_SIZE_CONNECTABLE) && |
133 | (fh_type != FILEID_BTRFS_WITH_PARENT_ROOT || |
134 | fh_len < BTRFS_FID_SIZE_CONNECTABLE_ROOT) && |
135 | (fh_type != FILEID_BTRFS_WITHOUT_PARENT || |
136 | fh_len < BTRFS_FID_SIZE_NON_CONNECTABLE)) |
137 | return NULL; |
138 | |
139 | objectid = fid->objectid; |
140 | root_objectid = fid->root_objectid; |
141 | generation = fid->gen; |
142 | |
143 | return btrfs_get_dentry(sb, objectid, root_objectid, generation); |
144 | } |
145 | |
146 | struct dentry *btrfs_get_parent(struct dentry *child) |
147 | { |
148 | struct inode *dir = d_inode(dentry: child); |
149 | struct btrfs_fs_info *fs_info = btrfs_sb(sb: dir->i_sb); |
150 | struct btrfs_root *root = BTRFS_I(inode: dir)->root; |
151 | struct btrfs_path *path; |
152 | struct extent_buffer *leaf; |
153 | struct btrfs_root_ref *ref; |
154 | struct btrfs_key key; |
155 | struct btrfs_key found_key; |
156 | int ret; |
157 | |
158 | path = btrfs_alloc_path(); |
159 | if (!path) |
160 | return ERR_PTR(error: -ENOMEM); |
161 | |
162 | if (btrfs_ino(inode: BTRFS_I(inode: dir)) == BTRFS_FIRST_FREE_OBJECTID) { |
163 | key.objectid = root->root_key.objectid; |
164 | key.type = BTRFS_ROOT_BACKREF_KEY; |
165 | key.offset = (u64)-1; |
166 | root = fs_info->tree_root; |
167 | } else { |
168 | key.objectid = btrfs_ino(inode: BTRFS_I(inode: dir)); |
169 | key.type = BTRFS_INODE_REF_KEY; |
170 | key.offset = (u64)-1; |
171 | } |
172 | |
173 | ret = btrfs_search_slot(NULL, root, key: &key, p: path, ins_len: 0, cow: 0); |
174 | if (ret < 0) |
175 | goto fail; |
176 | if (ret == 0) { |
177 | /* |
178 | * Key with offset of -1 found, there would have to exist an |
179 | * inode with such number or a root with such id. |
180 | */ |
181 | ret = -EUCLEAN; |
182 | goto fail; |
183 | } |
184 | |
185 | if (path->slots[0] == 0) { |
186 | ret = -ENOENT; |
187 | goto fail; |
188 | } |
189 | |
190 | path->slots[0]--; |
191 | leaf = path->nodes[0]; |
192 | |
193 | btrfs_item_key_to_cpu(eb: leaf, cpu_key: &found_key, nr: path->slots[0]); |
194 | if (found_key.objectid != key.objectid || found_key.type != key.type) { |
195 | ret = -ENOENT; |
196 | goto fail; |
197 | } |
198 | |
199 | if (found_key.type == BTRFS_ROOT_BACKREF_KEY) { |
200 | ref = btrfs_item_ptr(leaf, path->slots[0], |
201 | struct btrfs_root_ref); |
202 | key.objectid = btrfs_root_ref_dirid(eb: leaf, s: ref); |
203 | } else { |
204 | key.objectid = found_key.offset; |
205 | } |
206 | btrfs_free_path(p: path); |
207 | |
208 | if (found_key.type == BTRFS_ROOT_BACKREF_KEY) { |
209 | return btrfs_get_dentry(sb: fs_info->sb, objectid: key.objectid, |
210 | root_objectid: found_key.offset, generation: 0); |
211 | } |
212 | |
213 | return d_obtain_alias(btrfs_iget(s: fs_info->sb, ino: key.objectid, root)); |
214 | fail: |
215 | btrfs_free_path(p: path); |
216 | return ERR_PTR(error: ret); |
217 | } |
218 | |
219 | static int btrfs_get_name(struct dentry *parent, char *name, |
220 | struct dentry *child) |
221 | { |
222 | struct inode *inode = d_inode(dentry: child); |
223 | struct inode *dir = d_inode(dentry: parent); |
224 | struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); |
225 | struct btrfs_path *path; |
226 | struct btrfs_root *root = BTRFS_I(inode: dir)->root; |
227 | struct btrfs_inode_ref *iref; |
228 | struct btrfs_root_ref *rref; |
229 | struct extent_buffer *leaf; |
230 | unsigned long name_ptr; |
231 | struct btrfs_key key; |
232 | int name_len; |
233 | int ret; |
234 | u64 ino; |
235 | |
236 | if (!S_ISDIR(dir->i_mode)) |
237 | return -EINVAL; |
238 | |
239 | ino = btrfs_ino(inode: BTRFS_I(inode)); |
240 | |
241 | path = btrfs_alloc_path(); |
242 | if (!path) |
243 | return -ENOMEM; |
244 | |
245 | if (ino == BTRFS_FIRST_FREE_OBJECTID) { |
246 | key.objectid = BTRFS_I(inode)->root->root_key.objectid; |
247 | key.type = BTRFS_ROOT_BACKREF_KEY; |
248 | key.offset = (u64)-1; |
249 | root = fs_info->tree_root; |
250 | } else { |
251 | key.objectid = ino; |
252 | key.offset = btrfs_ino(inode: BTRFS_I(inode: dir)); |
253 | key.type = BTRFS_INODE_REF_KEY; |
254 | } |
255 | |
256 | ret = btrfs_search_slot(NULL, root, key: &key, p: path, ins_len: 0, cow: 0); |
257 | if (ret < 0) { |
258 | btrfs_free_path(p: path); |
259 | return ret; |
260 | } else if (ret > 0) { |
261 | if (ino == BTRFS_FIRST_FREE_OBJECTID) { |
262 | path->slots[0]--; |
263 | } else { |
264 | btrfs_free_path(p: path); |
265 | return -ENOENT; |
266 | } |
267 | } |
268 | leaf = path->nodes[0]; |
269 | |
270 | if (ino == BTRFS_FIRST_FREE_OBJECTID) { |
271 | rref = btrfs_item_ptr(leaf, path->slots[0], |
272 | struct btrfs_root_ref); |
273 | name_ptr = (unsigned long)(rref + 1); |
274 | name_len = btrfs_root_ref_name_len(eb: leaf, s: rref); |
275 | } else { |
276 | iref = btrfs_item_ptr(leaf, path->slots[0], |
277 | struct btrfs_inode_ref); |
278 | name_ptr = (unsigned long)(iref + 1); |
279 | name_len = btrfs_inode_ref_name_len(eb: leaf, s: iref); |
280 | } |
281 | |
282 | read_extent_buffer(eb: leaf, dst: name, start: name_ptr, len: name_len); |
283 | btrfs_free_path(p: path); |
284 | |
285 | /* |
286 | * have to add the null termination to make sure that reconnect_path |
287 | * gets the right len for strlen |
288 | */ |
289 | name[name_len] = '\0'; |
290 | |
291 | return 0; |
292 | } |
293 | |
294 | const struct export_operations btrfs_export_ops = { |
295 | .encode_fh = btrfs_encode_fh, |
296 | .fh_to_dentry = btrfs_fh_to_dentry, |
297 | .fh_to_parent = btrfs_fh_to_parent, |
298 | .get_parent = btrfs_get_parent, |
299 | .get_name = btrfs_get_name, |
300 | }; |
301 | |