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