1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/ceph/ceph_debug.h> |
3 | |
4 | #include <linux/exportfs.h> |
5 | #include <linux/slab.h> |
6 | #include <asm/unaligned.h> |
7 | |
8 | #include "super.h" |
9 | #include "mds_client.h" |
10 | #include "crypto.h" |
11 | |
12 | /* |
13 | * Basic fh |
14 | */ |
15 | struct ceph_nfs_fh { |
16 | u64 ino; |
17 | } __attribute__ ((packed)); |
18 | |
19 | /* |
20 | * Larger fh that includes parent ino. |
21 | */ |
22 | struct ceph_nfs_confh { |
23 | u64 ino, parent_ino; |
24 | } __attribute__ ((packed)); |
25 | |
26 | /* |
27 | * fh for snapped inode |
28 | */ |
29 | struct ceph_nfs_snapfh { |
30 | u64 ino; |
31 | u64 snapid; |
32 | u64 parent_ino; |
33 | u32 hash; |
34 | } __attribute__ ((packed)); |
35 | |
36 | static int ceph_encode_snapfh(struct inode *inode, u32 *rawfh, int *max_len, |
37 | struct inode *parent_inode) |
38 | { |
39 | struct ceph_client *cl = ceph_inode_to_client(inode); |
40 | static const int snap_handle_length = |
41 | sizeof(struct ceph_nfs_snapfh) >> 2; |
42 | struct ceph_nfs_snapfh *sfh = (void *)rawfh; |
43 | u64 snapid = ceph_snap(inode); |
44 | int ret; |
45 | bool no_parent = true; |
46 | |
47 | if (*max_len < snap_handle_length) { |
48 | *max_len = snap_handle_length; |
49 | ret = FILEID_INVALID; |
50 | goto out; |
51 | } |
52 | |
53 | ret = -EINVAL; |
54 | if (snapid != CEPH_SNAPDIR) { |
55 | struct inode *dir; |
56 | struct dentry *dentry = d_find_alias(inode); |
57 | if (!dentry) |
58 | goto out; |
59 | |
60 | rcu_read_lock(); |
61 | dir = d_inode_rcu(dentry: dentry->d_parent); |
62 | if (ceph_snap(inode: dir) != CEPH_SNAPDIR) { |
63 | sfh->parent_ino = ceph_ino(inode: dir); |
64 | sfh->hash = ceph_dentry_hash(dir, dn: dentry); |
65 | no_parent = false; |
66 | } |
67 | rcu_read_unlock(); |
68 | dput(dentry); |
69 | } |
70 | |
71 | if (no_parent) { |
72 | if (!S_ISDIR(inode->i_mode)) |
73 | goto out; |
74 | sfh->parent_ino = sfh->ino; |
75 | sfh->hash = 0; |
76 | } |
77 | sfh->ino = ceph_ino(inode); |
78 | sfh->snapid = snapid; |
79 | |
80 | *max_len = snap_handle_length; |
81 | ret = FILEID_BTRFS_WITH_PARENT; |
82 | out: |
83 | doutc(cl, "%p %llx.%llx ret=%d\n" , inode, ceph_vinop(inode), ret); |
84 | return ret; |
85 | } |
86 | |
87 | static int ceph_encode_fh(struct inode *inode, u32 *rawfh, int *max_len, |
88 | struct inode *parent_inode) |
89 | { |
90 | struct ceph_client *cl = ceph_inode_to_client(inode); |
91 | static const int handle_length = |
92 | sizeof(struct ceph_nfs_fh) >> 2; |
93 | static const int connected_handle_length = |
94 | sizeof(struct ceph_nfs_confh) >> 2; |
95 | int type; |
96 | |
97 | if (ceph_snap(inode) != CEPH_NOSNAP) |
98 | return ceph_encode_snapfh(inode, rawfh, max_len, parent_inode); |
99 | |
100 | if (parent_inode && (*max_len < connected_handle_length)) { |
101 | *max_len = connected_handle_length; |
102 | return FILEID_INVALID; |
103 | } else if (*max_len < handle_length) { |
104 | *max_len = handle_length; |
105 | return FILEID_INVALID; |
106 | } |
107 | |
108 | if (parent_inode) { |
109 | struct ceph_nfs_confh *cfh = (void *)rawfh; |
110 | doutc(cl, "%p %llx.%llx with parent %p %llx.%llx\n" , inode, |
111 | ceph_vinop(inode), parent_inode, ceph_vinop(parent_inode)); |
112 | cfh->ino = ceph_ino(inode); |
113 | cfh->parent_ino = ceph_ino(inode: parent_inode); |
114 | *max_len = connected_handle_length; |
115 | type = FILEID_INO32_GEN_PARENT; |
116 | } else { |
117 | struct ceph_nfs_fh *fh = (void *)rawfh; |
118 | doutc(cl, "%p %llx.%llx\n" , inode, ceph_vinop(inode)); |
119 | fh->ino = ceph_ino(inode); |
120 | *max_len = handle_length; |
121 | type = FILEID_INO32_GEN; |
122 | } |
123 | return type; |
124 | } |
125 | |
126 | static struct inode *__lookup_inode(struct super_block *sb, u64 ino) |
127 | { |
128 | struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc; |
129 | struct inode *inode; |
130 | struct ceph_vino vino; |
131 | int err; |
132 | |
133 | vino.ino = ino; |
134 | vino.snap = CEPH_NOSNAP; |
135 | |
136 | if (ceph_vino_is_reserved(vino)) |
137 | return ERR_PTR(error: -ESTALE); |
138 | |
139 | inode = ceph_find_inode(sb, vino); |
140 | if (!inode) { |
141 | struct ceph_mds_request *req; |
142 | int mask; |
143 | |
144 | req = ceph_mdsc_create_request(mdsc, op: CEPH_MDS_OP_LOOKUPINO, |
145 | mode: USE_ANY_MDS); |
146 | if (IS_ERR(ptr: req)) |
147 | return ERR_CAST(ptr: req); |
148 | |
149 | mask = CEPH_STAT_CAP_INODE; |
150 | if (ceph_security_xattr_wanted(in: d_inode(dentry: sb->s_root))) |
151 | mask |= CEPH_CAP_XATTR_SHARED; |
152 | req->r_args.lookupino.mask = cpu_to_le32(mask); |
153 | |
154 | req->r_ino1 = vino; |
155 | req->r_num_caps = 1; |
156 | err = ceph_mdsc_do_request(mdsc, NULL, req); |
157 | inode = req->r_target_inode; |
158 | if (inode) |
159 | ihold(inode); |
160 | ceph_mdsc_put_request(req); |
161 | if (!inode) |
162 | return err < 0 ? ERR_PTR(error: err) : ERR_PTR(error: -ESTALE); |
163 | } else { |
164 | if (ceph_inode_is_shutdown(inode)) { |
165 | iput(inode); |
166 | return ERR_PTR(error: -ESTALE); |
167 | } |
168 | } |
169 | return inode; |
170 | } |
171 | |
172 | struct inode *ceph_lookup_inode(struct super_block *sb, u64 ino) |
173 | { |
174 | struct inode *inode = __lookup_inode(sb, ino); |
175 | if (IS_ERR(ptr: inode)) |
176 | return inode; |
177 | if (inode->i_nlink == 0) { |
178 | iput(inode); |
179 | return ERR_PTR(error: -ESTALE); |
180 | } |
181 | return inode; |
182 | } |
183 | |
184 | static struct dentry *__fh_to_dentry(struct super_block *sb, u64 ino) |
185 | { |
186 | struct inode *inode = __lookup_inode(sb, ino); |
187 | struct ceph_inode_info *ci = ceph_inode(inode); |
188 | int err; |
189 | |
190 | if (IS_ERR(ptr: inode)) |
191 | return ERR_CAST(ptr: inode); |
192 | /* We need LINK caps to reliably check i_nlink */ |
193 | err = ceph_do_getattr(inode, CEPH_CAP_LINK_SHARED, force: false); |
194 | if (err) { |
195 | iput(inode); |
196 | return ERR_PTR(error: err); |
197 | } |
198 | /* -ESTALE if inode as been unlinked and no file is open */ |
199 | if ((inode->i_nlink == 0) && !__ceph_is_file_opened(ci)) { |
200 | iput(inode); |
201 | return ERR_PTR(error: -ESTALE); |
202 | } |
203 | return d_obtain_alias(inode); |
204 | } |
205 | |
206 | static struct dentry *__snapfh_to_dentry(struct super_block *sb, |
207 | struct ceph_nfs_snapfh *sfh, |
208 | bool want_parent) |
209 | { |
210 | struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc; |
211 | struct ceph_client *cl = mdsc->fsc->client; |
212 | struct ceph_mds_request *req; |
213 | struct inode *inode; |
214 | struct ceph_vino vino; |
215 | int mask; |
216 | int err; |
217 | bool unlinked = false; |
218 | |
219 | if (want_parent) { |
220 | vino.ino = sfh->parent_ino; |
221 | if (sfh->snapid == CEPH_SNAPDIR) |
222 | vino.snap = CEPH_NOSNAP; |
223 | else if (sfh->ino == sfh->parent_ino) |
224 | vino.snap = CEPH_SNAPDIR; |
225 | else |
226 | vino.snap = sfh->snapid; |
227 | } else { |
228 | vino.ino = sfh->ino; |
229 | vino.snap = sfh->snapid; |
230 | } |
231 | |
232 | if (ceph_vino_is_reserved(vino)) |
233 | return ERR_PTR(error: -ESTALE); |
234 | |
235 | inode = ceph_find_inode(sb, vino); |
236 | if (inode) { |
237 | if (ceph_inode_is_shutdown(inode)) { |
238 | iput(inode); |
239 | return ERR_PTR(error: -ESTALE); |
240 | } |
241 | return d_obtain_alias(inode); |
242 | } |
243 | |
244 | req = ceph_mdsc_create_request(mdsc, op: CEPH_MDS_OP_LOOKUPINO, |
245 | mode: USE_ANY_MDS); |
246 | if (IS_ERR(ptr: req)) |
247 | return ERR_CAST(ptr: req); |
248 | |
249 | mask = CEPH_STAT_CAP_INODE; |
250 | if (ceph_security_xattr_wanted(in: d_inode(dentry: sb->s_root))) |
251 | mask |= CEPH_CAP_XATTR_SHARED; |
252 | req->r_args.lookupino.mask = cpu_to_le32(mask); |
253 | if (vino.snap < CEPH_NOSNAP) { |
254 | req->r_args.lookupino.snapid = cpu_to_le64(vino.snap); |
255 | if (!want_parent && sfh->ino != sfh->parent_ino) { |
256 | req->r_args.lookupino.parent = |
257 | cpu_to_le64(sfh->parent_ino); |
258 | req->r_args.lookupino.hash = |
259 | cpu_to_le32(sfh->hash); |
260 | } |
261 | } |
262 | |
263 | req->r_ino1 = vino; |
264 | req->r_num_caps = 1; |
265 | err = ceph_mdsc_do_request(mdsc, NULL, req); |
266 | inode = req->r_target_inode; |
267 | if (inode) { |
268 | if (vino.snap == CEPH_SNAPDIR) { |
269 | if (inode->i_nlink == 0) |
270 | unlinked = true; |
271 | inode = ceph_get_snapdir(parent: inode); |
272 | } else if (ceph_snap(inode) == vino.snap) { |
273 | ihold(inode); |
274 | } else { |
275 | /* mds does not support lookup snapped inode */ |
276 | inode = ERR_PTR(error: -EOPNOTSUPP); |
277 | } |
278 | } else { |
279 | inode = ERR_PTR(error: -ESTALE); |
280 | } |
281 | ceph_mdsc_put_request(req); |
282 | |
283 | if (want_parent) { |
284 | doutc(cl, "%llx.%llx\n err=%d\n" , vino.ino, vino.snap, err); |
285 | } else { |
286 | doutc(cl, "%llx.%llx parent %llx hash %x err=%d" , vino.ino, |
287 | vino.snap, sfh->parent_ino, sfh->hash, err); |
288 | } |
289 | /* see comments in ceph_get_parent() */ |
290 | return unlinked ? d_obtain_root(inode) : d_obtain_alias(inode); |
291 | } |
292 | |
293 | /* |
294 | * convert regular fh to dentry |
295 | */ |
296 | static struct dentry *ceph_fh_to_dentry(struct super_block *sb, |
297 | struct fid *fid, |
298 | int fh_len, int fh_type) |
299 | { |
300 | struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb); |
301 | struct ceph_nfs_fh *fh = (void *)fid->raw; |
302 | |
303 | if (fh_type == FILEID_BTRFS_WITH_PARENT) { |
304 | struct ceph_nfs_snapfh *sfh = (void *)fid->raw; |
305 | return __snapfh_to_dentry(sb, sfh, want_parent: false); |
306 | } |
307 | |
308 | if (fh_type != FILEID_INO32_GEN && |
309 | fh_type != FILEID_INO32_GEN_PARENT) |
310 | return NULL; |
311 | if (fh_len < sizeof(*fh) / 4) |
312 | return NULL; |
313 | |
314 | doutc(fsc->client, "%llx\n" , fh->ino); |
315 | return __fh_to_dentry(sb, ino: fh->ino); |
316 | } |
317 | |
318 | static struct dentry *__get_parent(struct super_block *sb, |
319 | struct dentry *child, u64 ino) |
320 | { |
321 | struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc; |
322 | struct ceph_mds_request *req; |
323 | struct inode *inode; |
324 | int mask; |
325 | int err; |
326 | |
327 | req = ceph_mdsc_create_request(mdsc, op: CEPH_MDS_OP_LOOKUPPARENT, |
328 | mode: USE_ANY_MDS); |
329 | if (IS_ERR(ptr: req)) |
330 | return ERR_CAST(ptr: req); |
331 | |
332 | if (child) { |
333 | req->r_inode = d_inode(dentry: child); |
334 | ihold(inode: d_inode(dentry: child)); |
335 | } else { |
336 | req->r_ino1 = (struct ceph_vino) { |
337 | .ino = ino, |
338 | .snap = CEPH_NOSNAP, |
339 | }; |
340 | } |
341 | |
342 | mask = CEPH_STAT_CAP_INODE; |
343 | if (ceph_security_xattr_wanted(in: d_inode(dentry: sb->s_root))) |
344 | mask |= CEPH_CAP_XATTR_SHARED; |
345 | req->r_args.getattr.mask = cpu_to_le32(mask); |
346 | |
347 | req->r_num_caps = 1; |
348 | err = ceph_mdsc_do_request(mdsc, NULL, req); |
349 | if (err) { |
350 | ceph_mdsc_put_request(req); |
351 | return ERR_PTR(error: err); |
352 | } |
353 | |
354 | inode = req->r_target_inode; |
355 | if (inode) |
356 | ihold(inode); |
357 | ceph_mdsc_put_request(req); |
358 | if (!inode) |
359 | return ERR_PTR(error: -ENOENT); |
360 | |
361 | return d_obtain_alias(inode); |
362 | } |
363 | |
364 | static struct dentry *ceph_get_parent(struct dentry *child) |
365 | { |
366 | struct inode *inode = d_inode(dentry: child); |
367 | struct ceph_client *cl = ceph_inode_to_client(inode); |
368 | struct dentry *dn; |
369 | |
370 | if (ceph_snap(inode) != CEPH_NOSNAP) { |
371 | struct inode* dir; |
372 | bool unlinked = false; |
373 | /* do not support non-directory */ |
374 | if (!d_is_dir(dentry: child)) { |
375 | dn = ERR_PTR(error: -EINVAL); |
376 | goto out; |
377 | } |
378 | dir = __lookup_inode(sb: inode->i_sb, ino: ceph_ino(inode)); |
379 | if (IS_ERR(ptr: dir)) { |
380 | dn = ERR_CAST(ptr: dir); |
381 | goto out; |
382 | } |
383 | /* There can be multiple paths to access snapped inode. |
384 | * For simplicity, treat snapdir of head inode as parent */ |
385 | if (ceph_snap(inode) != CEPH_SNAPDIR) { |
386 | struct inode *snapdir = ceph_get_snapdir(parent: dir); |
387 | if (dir->i_nlink == 0) |
388 | unlinked = true; |
389 | iput(dir); |
390 | if (IS_ERR(ptr: snapdir)) { |
391 | dn = ERR_CAST(ptr: snapdir); |
392 | goto out; |
393 | } |
394 | dir = snapdir; |
395 | } |
396 | /* If directory has already been deleted, futher get_parent |
397 | * will fail. Do not mark snapdir dentry as disconnected, |
398 | * this prevent exportfs from doing futher get_parent. */ |
399 | if (unlinked) |
400 | dn = d_obtain_root(dir); |
401 | else |
402 | dn = d_obtain_alias(dir); |
403 | } else { |
404 | dn = __get_parent(sb: child->d_sb, child, ino: 0); |
405 | } |
406 | out: |
407 | doutc(cl, "child %p %p %llx.%llx err=%ld\n" , child, inode, |
408 | ceph_vinop(inode), (long)PTR_ERR_OR_ZERO(dn)); |
409 | return dn; |
410 | } |
411 | |
412 | /* |
413 | * convert regular fh to parent |
414 | */ |
415 | static struct dentry *ceph_fh_to_parent(struct super_block *sb, |
416 | struct fid *fid, |
417 | int fh_len, int fh_type) |
418 | { |
419 | struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb); |
420 | struct ceph_nfs_confh *cfh = (void *)fid->raw; |
421 | struct dentry *dentry; |
422 | |
423 | if (fh_type == FILEID_BTRFS_WITH_PARENT) { |
424 | struct ceph_nfs_snapfh *sfh = (void *)fid->raw; |
425 | return __snapfh_to_dentry(sb, sfh, want_parent: true); |
426 | } |
427 | |
428 | if (fh_type != FILEID_INO32_GEN_PARENT) |
429 | return NULL; |
430 | if (fh_len < sizeof(*cfh) / 4) |
431 | return NULL; |
432 | |
433 | doutc(fsc->client, "%llx\n" , cfh->parent_ino); |
434 | dentry = __get_parent(sb, NULL, ino: cfh->ino); |
435 | if (unlikely(dentry == ERR_PTR(-ENOENT))) |
436 | dentry = __fh_to_dentry(sb, ino: cfh->parent_ino); |
437 | return dentry; |
438 | } |
439 | |
440 | static int __get_snap_name(struct dentry *parent, char *name, |
441 | struct dentry *child) |
442 | { |
443 | struct inode *inode = d_inode(dentry: child); |
444 | struct inode *dir = d_inode(dentry: parent); |
445 | struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode); |
446 | struct ceph_mds_request *req = NULL; |
447 | char *last_name = NULL; |
448 | unsigned next_offset = 2; |
449 | int err = -EINVAL; |
450 | |
451 | if (ceph_ino(inode) != ceph_ino(inode: dir)) |
452 | goto out; |
453 | if (ceph_snap(inode) == CEPH_SNAPDIR) { |
454 | if (ceph_snap(inode: dir) == CEPH_NOSNAP) { |
455 | strcpy(p: name, q: fsc->mount_options->snapdir_name); |
456 | err = 0; |
457 | } |
458 | goto out; |
459 | } |
460 | if (ceph_snap(inode: dir) != CEPH_SNAPDIR) |
461 | goto out; |
462 | |
463 | while (1) { |
464 | struct ceph_mds_reply_info_parsed *rinfo; |
465 | struct ceph_mds_reply_dir_entry *rde; |
466 | int i; |
467 | |
468 | req = ceph_mdsc_create_request(mdsc: fsc->mdsc, op: CEPH_MDS_OP_LSSNAP, |
469 | mode: USE_AUTH_MDS); |
470 | if (IS_ERR(ptr: req)) { |
471 | err = PTR_ERR(ptr: req); |
472 | req = NULL; |
473 | goto out; |
474 | } |
475 | err = ceph_alloc_readdir_reply_buffer(req, dir: inode); |
476 | if (err) |
477 | goto out; |
478 | |
479 | req->r_direct_mode = USE_AUTH_MDS; |
480 | req->r_readdir_offset = next_offset; |
481 | req->r_args.readdir.flags = |
482 | cpu_to_le16(CEPH_READDIR_REPLY_BITFLAGS); |
483 | if (last_name) { |
484 | req->r_path2 = last_name; |
485 | last_name = NULL; |
486 | } |
487 | |
488 | req->r_inode = dir; |
489 | ihold(inode: dir); |
490 | req->r_dentry = dget(dentry: parent); |
491 | |
492 | inode_lock(inode: dir); |
493 | err = ceph_mdsc_do_request(mdsc: fsc->mdsc, NULL, req); |
494 | inode_unlock(inode: dir); |
495 | |
496 | if (err < 0) |
497 | goto out; |
498 | |
499 | rinfo = &req->r_reply_info; |
500 | for (i = 0; i < rinfo->dir_nr; i++) { |
501 | rde = rinfo->dir_entries + i; |
502 | BUG_ON(!rde->inode.in); |
503 | if (ceph_snap(inode) == |
504 | le64_to_cpu(rde->inode.in->snapid)) { |
505 | memcpy(name, rde->name, rde->name_len); |
506 | name[rde->name_len] = '\0'; |
507 | err = 0; |
508 | goto out; |
509 | } |
510 | } |
511 | |
512 | if (rinfo->dir_end) |
513 | break; |
514 | |
515 | BUG_ON(rinfo->dir_nr <= 0); |
516 | rde = rinfo->dir_entries + (rinfo->dir_nr - 1); |
517 | next_offset += rinfo->dir_nr; |
518 | last_name = kstrndup(s: rde->name, len: rde->name_len, GFP_KERNEL); |
519 | if (!last_name) { |
520 | err = -ENOMEM; |
521 | goto out; |
522 | } |
523 | |
524 | ceph_mdsc_put_request(req); |
525 | req = NULL; |
526 | } |
527 | err = -ENOENT; |
528 | out: |
529 | if (req) |
530 | ceph_mdsc_put_request(req); |
531 | kfree(objp: last_name); |
532 | doutc(fsc->client, "child dentry %p %p %llx.%llx err=%d\n" , child, |
533 | inode, ceph_vinop(inode), err); |
534 | return err; |
535 | } |
536 | |
537 | static int ceph_get_name(struct dentry *parent, char *name, |
538 | struct dentry *child) |
539 | { |
540 | struct ceph_mds_client *mdsc; |
541 | struct ceph_mds_request *req; |
542 | struct inode *dir = d_inode(dentry: parent); |
543 | struct inode *inode = d_inode(dentry: child); |
544 | struct ceph_mds_reply_info_parsed *rinfo; |
545 | int err; |
546 | |
547 | if (ceph_snap(inode) != CEPH_NOSNAP) |
548 | return __get_snap_name(parent, name, child); |
549 | |
550 | mdsc = ceph_inode_to_fs_client(inode)->mdsc; |
551 | req = ceph_mdsc_create_request(mdsc, op: CEPH_MDS_OP_LOOKUPNAME, |
552 | mode: USE_ANY_MDS); |
553 | if (IS_ERR(ptr: req)) |
554 | return PTR_ERR(ptr: req); |
555 | |
556 | inode_lock(inode: dir); |
557 | req->r_inode = inode; |
558 | ihold(inode); |
559 | req->r_ino2 = ceph_vino(inode: d_inode(dentry: parent)); |
560 | req->r_parent = dir; |
561 | ihold(inode: dir); |
562 | set_bit(CEPH_MDS_R_PARENT_LOCKED, addr: &req->r_req_flags); |
563 | req->r_num_caps = 2; |
564 | err = ceph_mdsc_do_request(mdsc, NULL, req); |
565 | inode_unlock(inode: dir); |
566 | |
567 | if (err) |
568 | goto out; |
569 | |
570 | rinfo = &req->r_reply_info; |
571 | if (!IS_ENCRYPTED(dir)) { |
572 | memcpy(name, rinfo->dname, rinfo->dname_len); |
573 | name[rinfo->dname_len] = 0; |
574 | } else { |
575 | struct fscrypt_str oname = FSTR_INIT(NULL, 0); |
576 | struct ceph_fname fname = { .dir = dir, |
577 | .name = rinfo->dname, |
578 | .ctext = rinfo->altname, |
579 | .name_len = rinfo->dname_len, |
580 | .ctext_len = rinfo->altname_len }; |
581 | |
582 | err = ceph_fname_alloc_buffer(parent: dir, fname: &oname); |
583 | if (err < 0) |
584 | goto out; |
585 | |
586 | err = ceph_fname_to_usr(fname: &fname, NULL, oname: &oname, NULL); |
587 | if (!err) { |
588 | memcpy(name, oname.name, oname.len); |
589 | name[oname.len] = 0; |
590 | } |
591 | ceph_fname_free_buffer(parent: dir, fname: &oname); |
592 | } |
593 | out: |
594 | doutc(mdsc->fsc->client, "child dentry %p %p %llx.%llx err %d %s%s\n" , |
595 | child, inode, ceph_vinop(inode), err, err ? "" : "name " , |
596 | err ? "" : name); |
597 | ceph_mdsc_put_request(req); |
598 | return err; |
599 | } |
600 | |
601 | const struct export_operations ceph_export_ops = { |
602 | .encode_fh = ceph_encode_fh, |
603 | .fh_to_dentry = ceph_fh_to_dentry, |
604 | .fh_to_parent = ceph_fh_to_parent, |
605 | .get_parent = ceph_get_parent, |
606 | .get_name = ceph_get_name, |
607 | }; |
608 | |