1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * linux/fs/hfsplus/dir.c |
4 | * |
5 | * Copyright (C) 2001 |
6 | * Brad Boyer (flar@allandria.com) |
7 | * (C) 2003 Ardis Technologies <roman@ardistech.com> |
8 | * |
9 | * Handling of directories |
10 | */ |
11 | |
12 | #include <linux/errno.h> |
13 | #include <linux/fs.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/random.h> |
16 | #include <linux/nls.h> |
17 | |
18 | #include "hfsplus_fs.h" |
19 | #include "hfsplus_raw.h" |
20 | #include "xattr.h" |
21 | |
22 | static inline void hfsplus_instantiate(struct dentry *dentry, |
23 | struct inode *inode, u32 cnid) |
24 | { |
25 | dentry->d_fsdata = (void *)(unsigned long)cnid; |
26 | d_instantiate(dentry, inode); |
27 | } |
28 | |
29 | /* Find the entry inside dir named dentry->d_name */ |
30 | static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry, |
31 | unsigned int flags) |
32 | { |
33 | struct inode *inode = NULL; |
34 | struct hfs_find_data fd; |
35 | struct super_block *sb; |
36 | hfsplus_cat_entry entry; |
37 | int err; |
38 | u32 cnid, linkid = 0; |
39 | u16 type; |
40 | |
41 | sb = dir->i_sb; |
42 | |
43 | dentry->d_fsdata = NULL; |
44 | err = hfs_find_init(tree: HFSPLUS_SB(sb)->cat_tree, fd: &fd); |
45 | if (err) |
46 | return ERR_PTR(error: err); |
47 | err = hfsplus_cat_build_key(sb, key: fd.search_key, parent: dir->i_ino, |
48 | str: &dentry->d_name); |
49 | if (unlikely(err < 0)) |
50 | goto fail; |
51 | again: |
52 | err = hfs_brec_read(fd: &fd, rec: &entry, rec_len: sizeof(entry)); |
53 | if (err) { |
54 | if (err == -ENOENT) { |
55 | hfs_find_exit(fd: &fd); |
56 | /* No such entry */ |
57 | inode = NULL; |
58 | goto out; |
59 | } |
60 | goto fail; |
61 | } |
62 | type = be16_to_cpu(entry.type); |
63 | if (type == HFSPLUS_FOLDER) { |
64 | if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) { |
65 | err = -EIO; |
66 | goto fail; |
67 | } |
68 | cnid = be32_to_cpu(entry.folder.id); |
69 | dentry->d_fsdata = (void *)(unsigned long)cnid; |
70 | } else if (type == HFSPLUS_FILE) { |
71 | if (fd.entrylength < sizeof(struct hfsplus_cat_file)) { |
72 | err = -EIO; |
73 | goto fail; |
74 | } |
75 | cnid = be32_to_cpu(entry.file.id); |
76 | if (entry.file.user_info.fdType == |
77 | cpu_to_be32(HFSP_HARDLINK_TYPE) && |
78 | entry.file.user_info.fdCreator == |
79 | cpu_to_be32(HFSP_HFSPLUS_CREATOR) && |
80 | HFSPLUS_SB(sb)->hidden_dir && |
81 | (entry.file.create_date == |
82 | HFSPLUS_I(inode: HFSPLUS_SB(sb)->hidden_dir)-> |
83 | create_date || |
84 | entry.file.create_date == |
85 | HFSPLUS_I(inode: d_inode(dentry: sb->s_root))-> |
86 | create_date)) { |
87 | struct qstr str; |
88 | char name[32]; |
89 | |
90 | if (dentry->d_fsdata) { |
91 | /* |
92 | * We found a link pointing to another link, |
93 | * so ignore it and treat it as regular file. |
94 | */ |
95 | cnid = (unsigned long)dentry->d_fsdata; |
96 | linkid = 0; |
97 | } else { |
98 | dentry->d_fsdata = (void *)(unsigned long)cnid; |
99 | linkid = |
100 | be32_to_cpu(entry.file.permissions.dev); |
101 | str.len = sprintf(buf: name, fmt: "iNode%d" , linkid); |
102 | str.name = name; |
103 | err = hfsplus_cat_build_key(sb, key: fd.search_key, |
104 | parent: HFSPLUS_SB(sb)->hidden_dir->i_ino, |
105 | str: &str); |
106 | if (unlikely(err < 0)) |
107 | goto fail; |
108 | goto again; |
109 | } |
110 | } else if (!dentry->d_fsdata) |
111 | dentry->d_fsdata = (void *)(unsigned long)cnid; |
112 | } else { |
113 | pr_err("invalid catalog entry type in lookup\n" ); |
114 | err = -EIO; |
115 | goto fail; |
116 | } |
117 | hfs_find_exit(fd: &fd); |
118 | inode = hfsplus_iget(sb: dir->i_sb, ino: cnid); |
119 | if (IS_ERR(ptr: inode)) |
120 | return ERR_CAST(ptr: inode); |
121 | if (S_ISREG(inode->i_mode)) |
122 | HFSPLUS_I(inode)->linkid = linkid; |
123 | out: |
124 | return d_splice_alias(inode, dentry); |
125 | fail: |
126 | hfs_find_exit(fd: &fd); |
127 | return ERR_PTR(error: err); |
128 | } |
129 | |
130 | static int hfsplus_readdir(struct file *file, struct dir_context *ctx) |
131 | { |
132 | struct inode *inode = file_inode(f: file); |
133 | struct super_block *sb = inode->i_sb; |
134 | int len, err; |
135 | char *strbuf; |
136 | hfsplus_cat_entry entry; |
137 | struct hfs_find_data fd; |
138 | struct hfsplus_readdir_data *rd; |
139 | u16 type; |
140 | |
141 | if (file->f_pos >= inode->i_size) |
142 | return 0; |
143 | |
144 | err = hfs_find_init(tree: HFSPLUS_SB(sb)->cat_tree, fd: &fd); |
145 | if (err) |
146 | return err; |
147 | strbuf = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN + 1, GFP_KERNEL); |
148 | if (!strbuf) { |
149 | err = -ENOMEM; |
150 | goto out; |
151 | } |
152 | hfsplus_cat_build_key_with_cnid(sb, key: fd.search_key, parent: inode->i_ino); |
153 | err = hfs_brec_find(fd: &fd, do_key_compare: hfs_find_rec_by_key); |
154 | if (err) |
155 | goto out; |
156 | |
157 | if (ctx->pos == 0) { |
158 | /* This is completely artificial... */ |
159 | if (!dir_emit_dot(file, ctx)) |
160 | goto out; |
161 | ctx->pos = 1; |
162 | } |
163 | if (ctx->pos == 1) { |
164 | if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) { |
165 | err = -EIO; |
166 | goto out; |
167 | } |
168 | |
169 | hfs_bnode_read(node: fd.bnode, buf: &entry, off: fd.entryoffset, |
170 | len: fd.entrylength); |
171 | if (be16_to_cpu(entry.type) != HFSPLUS_FOLDER_THREAD) { |
172 | pr_err("bad catalog folder thread\n" ); |
173 | err = -EIO; |
174 | goto out; |
175 | } |
176 | if (fd.entrylength < HFSPLUS_MIN_THREAD_SZ) { |
177 | pr_err("truncated catalog thread\n" ); |
178 | err = -EIO; |
179 | goto out; |
180 | } |
181 | if (!dir_emit(ctx, name: ".." , namelen: 2, |
182 | be32_to_cpu(entry.thread.parentID), DT_DIR)) |
183 | goto out; |
184 | ctx->pos = 2; |
185 | } |
186 | if (ctx->pos >= inode->i_size) |
187 | goto out; |
188 | err = hfs_brec_goto(fd: &fd, cnt: ctx->pos - 1); |
189 | if (err) |
190 | goto out; |
191 | for (;;) { |
192 | if (be32_to_cpu(fd.key->cat.parent) != inode->i_ino) { |
193 | pr_err("walked past end of dir\n" ); |
194 | err = -EIO; |
195 | goto out; |
196 | } |
197 | |
198 | if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) { |
199 | err = -EIO; |
200 | goto out; |
201 | } |
202 | |
203 | hfs_bnode_read(node: fd.bnode, buf: &entry, off: fd.entryoffset, |
204 | len: fd.entrylength); |
205 | type = be16_to_cpu(entry.type); |
206 | len = NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN; |
207 | err = hfsplus_uni2asc(sb, ustr: &fd.key->cat.name, astr: strbuf, len_p: &len); |
208 | if (err) |
209 | goto out; |
210 | if (type == HFSPLUS_FOLDER) { |
211 | if (fd.entrylength < |
212 | sizeof(struct hfsplus_cat_folder)) { |
213 | pr_err("small dir entry\n" ); |
214 | err = -EIO; |
215 | goto out; |
216 | } |
217 | if (HFSPLUS_SB(sb)->hidden_dir && |
218 | HFSPLUS_SB(sb)->hidden_dir->i_ino == |
219 | be32_to_cpu(entry.folder.id)) |
220 | goto next; |
221 | if (!dir_emit(ctx, name: strbuf, namelen: len, |
222 | be32_to_cpu(entry.folder.id), DT_DIR)) |
223 | break; |
224 | } else if (type == HFSPLUS_FILE) { |
225 | u16 mode; |
226 | unsigned type = DT_UNKNOWN; |
227 | |
228 | if (fd.entrylength < sizeof(struct hfsplus_cat_file)) { |
229 | pr_err("small file entry\n" ); |
230 | err = -EIO; |
231 | goto out; |
232 | } |
233 | |
234 | mode = be16_to_cpu(entry.file.permissions.mode); |
235 | if (S_ISREG(mode)) |
236 | type = DT_REG; |
237 | else if (S_ISLNK(mode)) |
238 | type = DT_LNK; |
239 | else if (S_ISFIFO(mode)) |
240 | type = DT_FIFO; |
241 | else if (S_ISCHR(mode)) |
242 | type = DT_CHR; |
243 | else if (S_ISBLK(mode)) |
244 | type = DT_BLK; |
245 | else if (S_ISSOCK(mode)) |
246 | type = DT_SOCK; |
247 | |
248 | if (!dir_emit(ctx, name: strbuf, namelen: len, |
249 | be32_to_cpu(entry.file.id), type)) |
250 | break; |
251 | } else { |
252 | pr_err("bad catalog entry type\n" ); |
253 | err = -EIO; |
254 | goto out; |
255 | } |
256 | next: |
257 | ctx->pos++; |
258 | if (ctx->pos >= inode->i_size) |
259 | goto out; |
260 | err = hfs_brec_goto(fd: &fd, cnt: 1); |
261 | if (err) |
262 | goto out; |
263 | } |
264 | rd = file->private_data; |
265 | if (!rd) { |
266 | rd = kmalloc(size: sizeof(struct hfsplus_readdir_data), GFP_KERNEL); |
267 | if (!rd) { |
268 | err = -ENOMEM; |
269 | goto out; |
270 | } |
271 | file->private_data = rd; |
272 | rd->file = file; |
273 | spin_lock(lock: &HFSPLUS_I(inode)->open_dir_lock); |
274 | list_add(new: &rd->list, head: &HFSPLUS_I(inode)->open_dir_list); |
275 | spin_unlock(lock: &HFSPLUS_I(inode)->open_dir_lock); |
276 | } |
277 | /* |
278 | * Can be done after the list insertion; exclusion with |
279 | * hfsplus_delete_cat() is provided by directory lock. |
280 | */ |
281 | memcpy(&rd->key, fd.key, sizeof(struct hfsplus_cat_key)); |
282 | out: |
283 | kfree(objp: strbuf); |
284 | hfs_find_exit(fd: &fd); |
285 | return err; |
286 | } |
287 | |
288 | static int hfsplus_dir_release(struct inode *inode, struct file *file) |
289 | { |
290 | struct hfsplus_readdir_data *rd = file->private_data; |
291 | if (rd) { |
292 | spin_lock(lock: &HFSPLUS_I(inode)->open_dir_lock); |
293 | list_del(entry: &rd->list); |
294 | spin_unlock(lock: &HFSPLUS_I(inode)->open_dir_lock); |
295 | kfree(objp: rd); |
296 | } |
297 | return 0; |
298 | } |
299 | |
300 | static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir, |
301 | struct dentry *dst_dentry) |
302 | { |
303 | struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb: dst_dir->i_sb); |
304 | struct inode *inode = d_inode(dentry: src_dentry); |
305 | struct inode *src_dir = d_inode(dentry: src_dentry->d_parent); |
306 | struct qstr str; |
307 | char name[32]; |
308 | u32 cnid, id; |
309 | int res; |
310 | |
311 | if (HFSPLUS_IS_RSRC(inode)) |
312 | return -EPERM; |
313 | if (!S_ISREG(inode->i_mode)) |
314 | return -EPERM; |
315 | |
316 | mutex_lock(&sbi->vh_mutex); |
317 | if (inode->i_ino == (u32)(unsigned long)src_dentry->d_fsdata) { |
318 | for (;;) { |
319 | get_random_bytes(buf: &id, len: sizeof(cnid)); |
320 | id &= 0x3fffffff; |
321 | str.name = name; |
322 | str.len = sprintf(buf: name, fmt: "iNode%d" , id); |
323 | res = hfsplus_rename_cat(cnid: inode->i_ino, |
324 | src_dir, src_name: &src_dentry->d_name, |
325 | dst_dir: sbi->hidden_dir, dst_name: &str); |
326 | if (!res) |
327 | break; |
328 | if (res != -EEXIST) |
329 | goto out; |
330 | } |
331 | HFSPLUS_I(inode)->linkid = id; |
332 | cnid = sbi->next_cnid++; |
333 | src_dentry->d_fsdata = (void *)(unsigned long)cnid; |
334 | res = hfsplus_create_cat(cnid, dir: src_dir, |
335 | str: &src_dentry->d_name, inode); |
336 | if (res) |
337 | /* panic? */ |
338 | goto out; |
339 | sbi->file_count++; |
340 | } |
341 | cnid = sbi->next_cnid++; |
342 | res = hfsplus_create_cat(cnid, dir: dst_dir, str: &dst_dentry->d_name, inode); |
343 | if (res) |
344 | goto out; |
345 | |
346 | inc_nlink(inode); |
347 | hfsplus_instantiate(dentry: dst_dentry, inode, cnid); |
348 | ihold(inode); |
349 | inode_set_ctime_current(inode); |
350 | mark_inode_dirty(inode); |
351 | sbi->file_count++; |
352 | hfsplus_mark_mdb_dirty(sb: dst_dir->i_sb); |
353 | out: |
354 | mutex_unlock(lock: &sbi->vh_mutex); |
355 | return res; |
356 | } |
357 | |
358 | static int hfsplus_unlink(struct inode *dir, struct dentry *dentry) |
359 | { |
360 | struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb: dir->i_sb); |
361 | struct inode *inode = d_inode(dentry); |
362 | struct qstr str; |
363 | char name[32]; |
364 | u32 cnid; |
365 | int res; |
366 | |
367 | if (HFSPLUS_IS_RSRC(inode)) |
368 | return -EPERM; |
369 | |
370 | mutex_lock(&sbi->vh_mutex); |
371 | cnid = (u32)(unsigned long)dentry->d_fsdata; |
372 | if (inode->i_ino == cnid && |
373 | atomic_read(v: &HFSPLUS_I(inode)->opencnt)) { |
374 | str.name = name; |
375 | str.len = sprintf(buf: name, fmt: "temp%lu" , inode->i_ino); |
376 | res = hfsplus_rename_cat(cnid: inode->i_ino, |
377 | src_dir: dir, src_name: &dentry->d_name, |
378 | dst_dir: sbi->hidden_dir, dst_name: &str); |
379 | if (!res) { |
380 | inode->i_flags |= S_DEAD; |
381 | drop_nlink(inode); |
382 | } |
383 | goto out; |
384 | } |
385 | res = hfsplus_delete_cat(cnid, dir, str: &dentry->d_name); |
386 | if (res) |
387 | goto out; |
388 | |
389 | if (inode->i_nlink > 0) |
390 | drop_nlink(inode); |
391 | if (inode->i_ino == cnid) |
392 | clear_nlink(inode); |
393 | if (!inode->i_nlink) { |
394 | if (inode->i_ino != cnid) { |
395 | sbi->file_count--; |
396 | if (!atomic_read(v: &HFSPLUS_I(inode)->opencnt)) { |
397 | res = hfsplus_delete_cat(cnid: inode->i_ino, |
398 | dir: sbi->hidden_dir, |
399 | NULL); |
400 | if (!res) |
401 | hfsplus_delete_inode(inode); |
402 | } else |
403 | inode->i_flags |= S_DEAD; |
404 | } else |
405 | hfsplus_delete_inode(inode); |
406 | } else |
407 | sbi->file_count--; |
408 | inode_set_ctime_current(inode); |
409 | mark_inode_dirty(inode); |
410 | out: |
411 | mutex_unlock(lock: &sbi->vh_mutex); |
412 | return res; |
413 | } |
414 | |
415 | static int hfsplus_rmdir(struct inode *dir, struct dentry *dentry) |
416 | { |
417 | struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb: dir->i_sb); |
418 | struct inode *inode = d_inode(dentry); |
419 | int res; |
420 | |
421 | if (inode->i_size != 2) |
422 | return -ENOTEMPTY; |
423 | |
424 | mutex_lock(&sbi->vh_mutex); |
425 | res = hfsplus_delete_cat(cnid: inode->i_ino, dir, str: &dentry->d_name); |
426 | if (res) |
427 | goto out; |
428 | clear_nlink(inode); |
429 | inode_set_ctime_current(inode); |
430 | hfsplus_delete_inode(inode); |
431 | mark_inode_dirty(inode); |
432 | out: |
433 | mutex_unlock(lock: &sbi->vh_mutex); |
434 | return res; |
435 | } |
436 | |
437 | static int hfsplus_symlink(struct mnt_idmap *idmap, struct inode *dir, |
438 | struct dentry *dentry, const char *symname) |
439 | { |
440 | struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb: dir->i_sb); |
441 | struct inode *inode; |
442 | int res = -ENOMEM; |
443 | |
444 | mutex_lock(&sbi->vh_mutex); |
445 | inode = hfsplus_new_inode(sb: dir->i_sb, dir, S_IFLNK | S_IRWXUGO); |
446 | if (!inode) |
447 | goto out; |
448 | |
449 | res = page_symlink(inode, symname, strlen(symname) + 1); |
450 | if (res) |
451 | goto out_err; |
452 | |
453 | res = hfsplus_create_cat(cnid: inode->i_ino, dir, str: &dentry->d_name, inode); |
454 | if (res) |
455 | goto out_err; |
456 | |
457 | res = hfsplus_init_security(inode, dir, qstr: &dentry->d_name); |
458 | if (res == -EOPNOTSUPP) |
459 | res = 0; /* Operation is not supported. */ |
460 | else if (res) { |
461 | /* Try to delete anyway without error analysis. */ |
462 | hfsplus_delete_cat(cnid: inode->i_ino, dir, str: &dentry->d_name); |
463 | goto out_err; |
464 | } |
465 | |
466 | hfsplus_instantiate(dentry, inode, cnid: inode->i_ino); |
467 | mark_inode_dirty(inode); |
468 | goto out; |
469 | |
470 | out_err: |
471 | clear_nlink(inode); |
472 | hfsplus_delete_inode(inode); |
473 | iput(inode); |
474 | out: |
475 | mutex_unlock(lock: &sbi->vh_mutex); |
476 | return res; |
477 | } |
478 | |
479 | static int hfsplus_mknod(struct mnt_idmap *idmap, struct inode *dir, |
480 | struct dentry *dentry, umode_t mode, dev_t rdev) |
481 | { |
482 | struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb: dir->i_sb); |
483 | struct inode *inode; |
484 | int res = -ENOMEM; |
485 | |
486 | mutex_lock(&sbi->vh_mutex); |
487 | inode = hfsplus_new_inode(sb: dir->i_sb, dir, mode); |
488 | if (!inode) |
489 | goto out; |
490 | |
491 | if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) |
492 | init_special_inode(inode, mode, rdev); |
493 | |
494 | res = hfsplus_create_cat(cnid: inode->i_ino, dir, str: &dentry->d_name, inode); |
495 | if (res) |
496 | goto failed_mknod; |
497 | |
498 | res = hfsplus_init_security(inode, dir, qstr: &dentry->d_name); |
499 | if (res == -EOPNOTSUPP) |
500 | res = 0; /* Operation is not supported. */ |
501 | else if (res) { |
502 | /* Try to delete anyway without error analysis. */ |
503 | hfsplus_delete_cat(cnid: inode->i_ino, dir, str: &dentry->d_name); |
504 | goto failed_mknod; |
505 | } |
506 | |
507 | hfsplus_instantiate(dentry, inode, cnid: inode->i_ino); |
508 | mark_inode_dirty(inode); |
509 | goto out; |
510 | |
511 | failed_mknod: |
512 | clear_nlink(inode); |
513 | hfsplus_delete_inode(inode); |
514 | iput(inode); |
515 | out: |
516 | mutex_unlock(lock: &sbi->vh_mutex); |
517 | return res; |
518 | } |
519 | |
520 | static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir, |
521 | struct dentry *dentry, umode_t mode, bool excl) |
522 | { |
523 | return hfsplus_mknod(idmap: &nop_mnt_idmap, dir, dentry, mode, rdev: 0); |
524 | } |
525 | |
526 | static int hfsplus_mkdir(struct mnt_idmap *idmap, struct inode *dir, |
527 | struct dentry *dentry, umode_t mode) |
528 | { |
529 | return hfsplus_mknod(idmap: &nop_mnt_idmap, dir, dentry, mode: mode | S_IFDIR, rdev: 0); |
530 | } |
531 | |
532 | static int hfsplus_rename(struct mnt_idmap *idmap, |
533 | struct inode *old_dir, struct dentry *old_dentry, |
534 | struct inode *new_dir, struct dentry *new_dentry, |
535 | unsigned int flags) |
536 | { |
537 | int res; |
538 | |
539 | if (flags & ~RENAME_NOREPLACE) |
540 | return -EINVAL; |
541 | |
542 | /* Unlink destination if it already exists */ |
543 | if (d_really_is_positive(dentry: new_dentry)) { |
544 | if (d_is_dir(dentry: new_dentry)) |
545 | res = hfsplus_rmdir(dir: new_dir, dentry: new_dentry); |
546 | else |
547 | res = hfsplus_unlink(dir: new_dir, dentry: new_dentry); |
548 | if (res) |
549 | return res; |
550 | } |
551 | |
552 | res = hfsplus_rename_cat(cnid: (u32)(unsigned long)old_dentry->d_fsdata, |
553 | src_dir: old_dir, src_name: &old_dentry->d_name, |
554 | dst_dir: new_dir, dst_name: &new_dentry->d_name); |
555 | if (!res) |
556 | new_dentry->d_fsdata = old_dentry->d_fsdata; |
557 | return res; |
558 | } |
559 | |
560 | const struct inode_operations hfsplus_dir_inode_operations = { |
561 | .lookup = hfsplus_lookup, |
562 | .create = hfsplus_create, |
563 | .link = hfsplus_link, |
564 | .unlink = hfsplus_unlink, |
565 | .mkdir = hfsplus_mkdir, |
566 | .rmdir = hfsplus_rmdir, |
567 | .symlink = hfsplus_symlink, |
568 | .mknod = hfsplus_mknod, |
569 | .rename = hfsplus_rename, |
570 | .getattr = hfsplus_getattr, |
571 | .listxattr = hfsplus_listxattr, |
572 | .fileattr_get = hfsplus_fileattr_get, |
573 | .fileattr_set = hfsplus_fileattr_set, |
574 | }; |
575 | |
576 | const struct file_operations hfsplus_dir_operations = { |
577 | .fsync = hfsplus_file_fsync, |
578 | .read = generic_read_dir, |
579 | .iterate_shared = hfsplus_readdir, |
580 | .unlocked_ioctl = hfsplus_ioctl, |
581 | .llseek = generic_file_llseek, |
582 | .release = hfsplus_dir_release, |
583 | }; |
584 | |