Loading...
1/*
2 * (C) 2001 Clemson University and The University of Chicago
3 *
4 * See COPYING in top-level directory.
5 */
6
7#include "protocol.h"
8#include "orangefs-kernel.h"
9#include "orangefs-bufmap.h"
10
11/*
12 * decode routine used by kmod to deal with the blob sent from
13 * userspace for readdirs. The blob contains zero or more of these
14 * sub-blobs:
15 * __u32 - represents length of the character string that follows.
16 * string - between 1 and ORANGEFS_NAME_MAX bytes long.
17 * padding - (if needed) to cause the __u32 plus the string to be
18 * eight byte aligned.
19 * khandle - sizeof(khandle) bytes.
20 */
21static long decode_dirents(char *ptr, size_t size,
22 struct orangefs_readdir_response_s *readdir)
23{
24 int i;
25 struct orangefs_readdir_response_s *rd =
26 (struct orangefs_readdir_response_s *) ptr;
27 char *buf = ptr;
28 int khandle_size = sizeof(struct orangefs_khandle);
29 size_t offset = offsetof(struct orangefs_readdir_response_s,
30 dirent_array);
31 /* 8 reflects eight byte alignment */
32 int smallest_blob = khandle_size + 8;
33 __u32 len;
34 int aligned_len;
35 int sizeof_u32 = sizeof(__u32);
36 long ret;
37
38 gossip_debug(GOSSIP_DIR_DEBUG, "%s: size:%zu:\n", __func__, size);
39
40 /* size is = offset on empty dirs, > offset on non-empty dirs... */
41 if (size < offset) {
42 gossip_err("%s: size:%zu: offset:%zu:\n",
43 __func__,
44 size,
45 offset);
46 ret = -EINVAL;
47 goto out;
48 }
49
50 if ((size == offset) && (readdir->orangefs_dirent_outcount != 0)) {
51 gossip_err("%s: size:%zu: dirent_outcount:%d:\n",
52 __func__,
53 size,
54 readdir->orangefs_dirent_outcount);
55 ret = -EINVAL;
56 goto out;
57 }
58
59 readdir->token = rd->token;
60 readdir->orangefs_dirent_outcount = rd->orangefs_dirent_outcount;
61 readdir->dirent_array = kcalloc(readdir->orangefs_dirent_outcount,
62 sizeof(*readdir->dirent_array),
63 GFP_KERNEL);
64 if (readdir->dirent_array == NULL) {
65 gossip_err("%s: kcalloc failed.\n", __func__);
66 ret = -ENOMEM;
67 goto out;
68 }
69
70 buf += offset;
71 size -= offset;
72
73 for (i = 0; i < readdir->orangefs_dirent_outcount; i++) {
74 if (size < smallest_blob) {
75 gossip_err("%s: size:%zu: smallest_blob:%d:\n",
76 __func__,
77 size,
78 smallest_blob);
79 ret = -EINVAL;
80 goto free;
81 }
82
83 len = *(__u32 *)buf;
84 if ((len < 1) || (len > ORANGEFS_NAME_MAX)) {
85 gossip_err("%s: len:%d:\n", __func__, len);
86 ret = -EINVAL;
87 goto free;
88 }
89
90 gossip_debug(GOSSIP_DIR_DEBUG,
91 "%s: size:%zu: len:%d:\n",
92 __func__,
93 size,
94 len);
95
96 readdir->dirent_array[i].d_name = buf + sizeof_u32;
97 readdir->dirent_array[i].d_length = len;
98
99 /*
100 * Calculate "aligned" length of this string and its
101 * associated __u32 descriptor.
102 */
103 aligned_len = ((sizeof_u32 + len + 1) + 7) & ~7;
104 gossip_debug(GOSSIP_DIR_DEBUG,
105 "%s: aligned_len:%d:\n",
106 __func__,
107 aligned_len);
108
109 /*
110 * The end of the blob should coincide with the end
111 * of the last sub-blob.
112 */
113 if (size < aligned_len + khandle_size) {
114 gossip_err("%s: ran off the end of the blob.\n",
115 __func__);
116 ret = -EINVAL;
117 goto free;
118 }
119 size -= aligned_len + khandle_size;
120
121 buf += aligned_len;
122
123 readdir->dirent_array[i].khandle =
124 *(struct orangefs_khandle *) buf;
125 buf += khandle_size;
126 }
127 ret = buf - ptr;
128 gossip_debug(GOSSIP_DIR_DEBUG, "%s: returning:%ld:\n", __func__, ret);
129 goto out;
130
131free:
132 kfree(readdir->dirent_array);
133 readdir->dirent_array = NULL;
134
135out:
136 return ret;
137}
138
139/*
140 * Read directory entries from an instance of an open directory.
141 */
142static int orangefs_readdir(struct file *file, struct dir_context *ctx)
143{
144 int ret = 0;
145 int buffer_index;
146 /*
147 * ptoken supports Orangefs' distributed directory logic, added
148 * in 2.9.2.
149 */
150 __u64 *ptoken = file->private_data;
151 __u64 pos = 0;
152 ino_t ino = 0;
153 struct dentry *dentry = file->f_path.dentry;
154 struct orangefs_kernel_op_s *new_op = NULL;
155 struct orangefs_inode_s *orangefs_inode = ORANGEFS_I(dentry->d_inode);
156 struct orangefs_readdir_response_s readdir_response;
157 void *dents_buf;
158 int i = 0;
159 int len = 0;
160 ino_t current_ino = 0;
161 char *current_entry = NULL;
162 long bytes_decoded;
163
164 gossip_debug(GOSSIP_DIR_DEBUG,
165 "%s: ctx->pos:%lld, ptoken = %llu\n",
166 __func__,
167 lld(ctx->pos),
168 llu(*ptoken));
169
170 pos = (__u64) ctx->pos;
171
172 /* are we done? */
173 if (pos == ORANGEFS_READDIR_END) {
174 gossip_debug(GOSSIP_DIR_DEBUG,
175 "Skipping to termination path\n");
176 return 0;
177 }
178
179 gossip_debug(GOSSIP_DIR_DEBUG,
180 "orangefs_readdir called on %s (pos=%llu)\n",
181 dentry->d_name.name, llu(pos));
182
183 memset(&readdir_response, 0, sizeof(readdir_response));
184
185 new_op = op_alloc(ORANGEFS_VFS_OP_READDIR);
186 if (!new_op)
187 return -ENOMEM;
188
189 /*
190 * Only the indices are shared. No memory is actually shared, but the
191 * mechanism is used.
192 */
193 new_op->uses_shared_memory = 1;
194 new_op->upcall.req.readdir.refn = orangefs_inode->refn;
195 new_op->upcall.req.readdir.max_dirent_count =
196 ORANGEFS_MAX_DIRENT_COUNT_READDIR;
197
198 gossip_debug(GOSSIP_DIR_DEBUG,
199 "%s: upcall.req.readdir.refn.khandle: %pU\n",
200 __func__,
201 &new_op->upcall.req.readdir.refn.khandle);
202
203 new_op->upcall.req.readdir.token = *ptoken;
204
205get_new_buffer_index:
206 buffer_index = orangefs_readdir_index_get();
207 if (buffer_index < 0) {
208 ret = buffer_index;
209 gossip_lerr("orangefs_readdir: orangefs_readdir_index_get() failure (%d)\n",
210 ret);
211 goto out_free_op;
212 }
213 new_op->upcall.req.readdir.buf_index = buffer_index;
214
215 ret = service_operation(new_op,
216 "orangefs_readdir",
217 get_interruptible_flag(dentry->d_inode));
218
219 gossip_debug(GOSSIP_DIR_DEBUG,
220 "Readdir downcall status is %d. ret:%d\n",
221 new_op->downcall.status,
222 ret);
223
224 orangefs_readdir_index_put(buffer_index);
225
226 if (ret == -EAGAIN && op_state_purged(new_op)) {
227 /* Client-core indices are invalid after it restarted. */
228 gossip_debug(GOSSIP_DIR_DEBUG,
229 "%s: Getting new buffer_index for retry of readdir..\n",
230 __func__);
231 goto get_new_buffer_index;
232 }
233
234 if (ret == -EIO && op_state_purged(new_op)) {
235 gossip_err("%s: Client is down. Aborting readdir call.\n",
236 __func__);
237 goto out_free_op;
238 }
239
240 if (ret < 0 || new_op->downcall.status != 0) {
241 gossip_debug(GOSSIP_DIR_DEBUG,
242 "Readdir request failed. Status:%d\n",
243 new_op->downcall.status);
244 if (ret >= 0)
245 ret = new_op->downcall.status;
246 goto out_free_op;
247 }
248
249 dents_buf = new_op->downcall.trailer_buf;
250 if (dents_buf == NULL) {
251 gossip_err("Invalid NULL buffer in readdir response\n");
252 ret = -ENOMEM;
253 goto out_free_op;
254 }
255
256 bytes_decoded = decode_dirents(dents_buf, new_op->downcall.trailer_size,
257 &readdir_response);
258 if (bytes_decoded < 0) {
259 ret = bytes_decoded;
260 gossip_err("Could not decode readdir from buffer %d\n", ret);
261 goto out_vfree;
262 }
263
264 if (bytes_decoded != new_op->downcall.trailer_size) {
265 gossip_err("orangefs_readdir: # bytes decoded (%ld) "
266 "!= trailer size (%ld)\n",
267 bytes_decoded,
268 (long)new_op->downcall.trailer_size);
269 ret = -EINVAL;
270 goto out_destroy_handle;
271 }
272
273 /*
274 * orangefs doesn't actually store dot and dot-dot, but
275 * we need to have them represented.
276 */
277 if (pos == 0) {
278 ino = get_ino_from_khandle(dentry->d_inode);
279 gossip_debug(GOSSIP_DIR_DEBUG,
280 "%s: calling dir_emit of \".\" with pos = %llu\n",
281 __func__,
282 llu(pos));
283 ret = dir_emit(ctx, ".", 1, ino, DT_DIR);
284 pos += 1;
285 }
286
287 if (pos == 1) {
288 ino = get_parent_ino_from_dentry(dentry);
289 gossip_debug(GOSSIP_DIR_DEBUG,
290 "%s: calling dir_emit of \"..\" with pos = %llu\n",
291 __func__,
292 llu(pos));
293 ret = dir_emit(ctx, "..", 2, ino, DT_DIR);
294 pos += 1;
295 }
296
297 /*
298 * we stored ORANGEFS_ITERATE_NEXT in ctx->pos last time around
299 * to prevent "finding" dot and dot-dot on any iteration
300 * other than the first.
301 */
302 if (ctx->pos == ORANGEFS_ITERATE_NEXT)
303 ctx->pos = 0;
304
305 gossip_debug(GOSSIP_DIR_DEBUG,
306 "%s: dirent_outcount:%d:\n",
307 __func__,
308 readdir_response.orangefs_dirent_outcount);
309 for (i = ctx->pos;
310 i < readdir_response.orangefs_dirent_outcount;
311 i++) {
312 len = readdir_response.dirent_array[i].d_length;
313 current_entry = readdir_response.dirent_array[i].d_name;
314 current_ino = orangefs_khandle_to_ino(
315 &readdir_response.dirent_array[i].khandle);
316
317 gossip_debug(GOSSIP_DIR_DEBUG,
318 "calling dir_emit for %s with len %d"
319 ", ctx->pos %ld\n",
320 current_entry,
321 len,
322 (unsigned long)ctx->pos);
323 /*
324 * type is unknown. We don't return object type
325 * in the dirent_array. This leaves getdents
326 * clueless about type.
327 */
328 ret =
329 dir_emit(ctx, current_entry, len, current_ino, DT_UNKNOWN);
330 if (!ret)
331 break;
332 ctx->pos++;
333 gossip_debug(GOSSIP_DIR_DEBUG,
334 "%s: ctx->pos:%lld\n",
335 __func__,
336 lld(ctx->pos));
337
338 }
339
340 /*
341 * we ran all the way through the last batch, set up for
342 * getting another batch...
343 */
344 if (ret) {
345 *ptoken = readdir_response.token;
346 ctx->pos = ORANGEFS_ITERATE_NEXT;
347 }
348
349 /*
350 * Did we hit the end of the directory?
351 */
352 if (readdir_response.token == ORANGEFS_READDIR_END) {
353 gossip_debug(GOSSIP_DIR_DEBUG,
354 "End of dir detected; setting ctx->pos to ORANGEFS_READDIR_END.\n");
355 ctx->pos = ORANGEFS_READDIR_END;
356 }
357
358out_destroy_handle:
359 /* kfree(NULL) is safe */
360 kfree(readdir_response.dirent_array);
361out_vfree:
362 gossip_debug(GOSSIP_DIR_DEBUG, "vfree %p\n", dents_buf);
363 vfree(dents_buf);
364out_free_op:
365 op_release(new_op);
366 gossip_debug(GOSSIP_DIR_DEBUG, "orangefs_readdir returning %d\n", ret);
367 return ret;
368}
369
370static int orangefs_dir_open(struct inode *inode, struct file *file)
371{
372 __u64 *ptoken;
373
374 file->private_data = kmalloc(sizeof(__u64), GFP_KERNEL);
375 if (!file->private_data)
376 return -ENOMEM;
377
378 ptoken = file->private_data;
379 *ptoken = ORANGEFS_READDIR_START;
380 return 0;
381}
382
383static int orangefs_dir_release(struct inode *inode, struct file *file)
384{
385 orangefs_flush_inode(inode);
386 kfree(file->private_data);
387 return 0;
388}
389
390/** ORANGEFS implementation of VFS directory operations */
391const struct file_operations orangefs_dir_operations = {
392 .read = generic_read_dir,
393 .iterate = orangefs_readdir,
394 .open = orangefs_dir_open,
395 .release = orangefs_dir_release,
396};
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright 2017 Omnibond Systems, L.L.C.
4 */
5
6#include "protocol.h"
7#include "orangefs-kernel.h"
8#include "orangefs-bufmap.h"
9
10struct orangefs_dir_part {
11 struct orangefs_dir_part *next;
12 size_t len;
13};
14
15struct orangefs_dir {
16 __u64 token;
17 struct orangefs_dir_part *part;
18 loff_t end;
19 int error;
20};
21
22#define PART_SHIFT (24)
23#define PART_SIZE (1<<24)
24#define PART_MASK (~(PART_SIZE - 1))
25
26/*
27 * There can be up to 512 directory entries. Each entry is encoded as
28 * follows:
29 * 4 bytes: string size (n)
30 * n bytes: string
31 * 1 byte: trailing zero
32 * padding to 8 bytes
33 * 16 bytes: khandle
34 * padding to 8 bytes
35 *
36 * The trailer_buf starts with a struct orangefs_readdir_response_s
37 * which must be skipped to get to the directory data.
38 *
39 * The data which is received from the userspace daemon is termed a
40 * part and is stored in a linked list in case more than one part is
41 * needed for a large directory.
42 *
43 * The position pointer (ctx->pos) encodes the part and offset on which
44 * to begin reading at. Bits above PART_SHIFT encode the part and bits
45 * below PART_SHIFT encode the offset. Parts are stored in a linked
46 * list which grows as data is received from the server. The overhead
47 * associated with managing the list is presumed to be small compared to
48 * the overhead of communicating with the server.
49 *
50 * As data is received from the server, it is placed at the end of the
51 * part list. Data is parsed from the current position as it is needed.
52 * When data is determined to be corrupt, it is either because the
53 * userspace component has sent back corrupt data or because the file
54 * pointer has been moved to an invalid location. Since the two cannot
55 * be differentiated, return EIO.
56 *
57 * Part zero is synthesized to contains `.' and `..'. Part one is the
58 * first part of the part list.
59 */
60
61static int do_readdir(struct orangefs_inode_s *oi,
62 struct orangefs_dir *od, struct dentry *dentry,
63 struct orangefs_kernel_op_s *op)
64{
65 struct orangefs_readdir_response_s *resp;
66 int bufi, r;
67
68 /*
69 * Despite the badly named field, readdir does not use shared
70 * memory. However, there are a limited number of readdir
71 * slots, which must be allocated here. This flag simply tells
72 * the op scheduler to return the op here for retry.
73 */
74 op->uses_shared_memory = 1;
75 op->upcall.req.readdir.refn = oi->refn;
76 op->upcall.req.readdir.token = od->token;
77 op->upcall.req.readdir.max_dirent_count =
78 ORANGEFS_MAX_DIRENT_COUNT_READDIR;
79
80again:
81 bufi = orangefs_readdir_index_get();
82 if (bufi < 0) {
83 od->error = bufi;
84 return bufi;
85 }
86
87 op->upcall.req.readdir.buf_index = bufi;
88
89 r = service_operation(op, "orangefs_readdir",
90 get_interruptible_flag(dentry->d_inode));
91
92 orangefs_readdir_index_put(bufi);
93
94 if (op_state_purged(op)) {
95 if (r == -EAGAIN) {
96 vfree(op->downcall.trailer_buf);
97 goto again;
98 } else if (r == -EIO) {
99 vfree(op->downcall.trailer_buf);
100 od->error = r;
101 return r;
102 }
103 }
104
105 if (r < 0) {
106 vfree(op->downcall.trailer_buf);
107 od->error = r;
108 return r;
109 } else if (op->downcall.status) {
110 vfree(op->downcall.trailer_buf);
111 od->error = op->downcall.status;
112 return op->downcall.status;
113 }
114
115 /*
116 * The maximum size is size per entry times the 512 entries plus
117 * the header. This is well under the limit.
118 */
119 if (op->downcall.trailer_size > PART_SIZE) {
120 vfree(op->downcall.trailer_buf);
121 od->error = -EIO;
122 return -EIO;
123 }
124
125 resp = (struct orangefs_readdir_response_s *)
126 op->downcall.trailer_buf;
127 od->token = resp->token;
128 return 0;
129}
130
131static int parse_readdir(struct orangefs_dir *od,
132 struct orangefs_kernel_op_s *op)
133{
134 struct orangefs_dir_part *part, *new;
135 size_t count;
136
137 count = 1;
138 part = od->part;
139 while (part) {
140 count++;
141 if (part->next)
142 part = part->next;
143 else
144 break;
145 }
146
147 new = (void *)op->downcall.trailer_buf;
148 new->next = NULL;
149 new->len = op->downcall.trailer_size -
150 sizeof(struct orangefs_readdir_response_s);
151 if (!od->part)
152 od->part = new;
153 else
154 part->next = new;
155 count++;
156 od->end = count << PART_SHIFT;
157
158 return 0;
159}
160
161static int orangefs_dir_more(struct orangefs_inode_s *oi,
162 struct orangefs_dir *od, struct dentry *dentry)
163{
164 struct orangefs_kernel_op_s *op;
165 int r;
166
167 op = op_alloc(ORANGEFS_VFS_OP_READDIR);
168 if (!op) {
169 od->error = -ENOMEM;
170 return -ENOMEM;
171 }
172 r = do_readdir(oi, od, dentry, op);
173 if (r) {
174 od->error = r;
175 goto out;
176 }
177 r = parse_readdir(od, op);
178 if (r) {
179 od->error = r;
180 goto out;
181 }
182
183 od->error = 0;
184out:
185 op_release(op);
186 return od->error;
187}
188
189static int fill_from_part(struct orangefs_dir_part *part,
190 struct dir_context *ctx)
191{
192 const int offset = sizeof(struct orangefs_readdir_response_s);
193 struct orangefs_khandle *khandle;
194 __u32 *len, padlen;
195 loff_t i;
196 char *s;
197 i = ctx->pos & ~PART_MASK;
198
199 /* The file offset from userspace is too large. */
200 if (i > part->len)
201 return 1;
202
203 /*
204 * If the seek pointer is positioned just before an entry it
205 * should find the next entry.
206 */
207 if (i % 8)
208 i = i + (8 - i%8)%8;
209
210 while (i < part->len) {
211 if (part->len < i + sizeof *len)
212 break;
213 len = (void *)part + offset + i;
214 /*
215 * len is the size of the string itself. padlen is the
216 * total size of the encoded string.
217 */
218 padlen = (sizeof *len + *len + 1) +
219 (8 - (sizeof *len + *len + 1)%8)%8;
220 if (part->len < i + padlen + sizeof *khandle)
221 goto next;
222 s = (void *)part + offset + i + sizeof *len;
223 if (s[*len] != 0)
224 goto next;
225 khandle = (void *)part + offset + i + padlen;
226 if (!dir_emit(ctx, s, *len,
227 orangefs_khandle_to_ino(khandle),
228 DT_UNKNOWN))
229 return 0;
230 i += padlen + sizeof *khandle;
231 i = i + (8 - i%8)%8;
232 BUG_ON(i > part->len);
233 ctx->pos = (ctx->pos & PART_MASK) | i;
234 continue;
235next:
236 i += 8;
237 }
238 return 1;
239}
240
241static int orangefs_dir_fill(struct orangefs_inode_s *oi,
242 struct orangefs_dir *od, struct dentry *dentry,
243 struct dir_context *ctx)
244{
245 struct orangefs_dir_part *part;
246 size_t count;
247
248 count = ((ctx->pos & PART_MASK) >> PART_SHIFT) - 1;
249
250 part = od->part;
251 while (part->next && count) {
252 count--;
253 part = part->next;
254 }
255 /* This means the userspace file offset is invalid. */
256 if (count) {
257 od->error = -EIO;
258 return -EIO;
259 }
260
261 while (part && part->len) {
262 int r;
263 r = fill_from_part(part, ctx);
264 if (r < 0) {
265 od->error = r;
266 return r;
267 } else if (r == 0) {
268 /* Userspace buffer is full. */
269 break;
270 } else {
271 /*
272 * The part ran out of data. Move to the next
273 * part. */
274 ctx->pos = (ctx->pos & PART_MASK) +
275 (1 << PART_SHIFT);
276 part = part->next;
277 }
278 }
279 return 0;
280}
281
282static loff_t orangefs_dir_llseek(struct file *file, loff_t offset,
283 int whence)
284{
285 struct orangefs_dir *od = file->private_data;
286 /*
287 * Delete the stored data so userspace sees new directory
288 * entries.
289 */
290 if (!whence && offset < od->end) {
291 struct orangefs_dir_part *part = od->part;
292 while (part) {
293 struct orangefs_dir_part *next = part->next;
294 vfree(part);
295 part = next;
296 }
297 od->token = ORANGEFS_ITERATE_START;
298 od->part = NULL;
299 od->end = 1 << PART_SHIFT;
300 }
301 return default_llseek(file, offset, whence);
302}
303
304static int orangefs_dir_iterate(struct file *file,
305 struct dir_context *ctx)
306{
307 struct orangefs_inode_s *oi;
308 struct orangefs_dir *od;
309 struct dentry *dentry;
310 int r;
311
312 dentry = file->f_path.dentry;
313 oi = ORANGEFS_I(dentry->d_inode);
314 od = file->private_data;
315
316 if (od->error)
317 return od->error;
318
319 if (ctx->pos == 0) {
320 if (!dir_emit_dot(file, ctx))
321 return 0;
322 ctx->pos++;
323 }
324 if (ctx->pos == 1) {
325 if (!dir_emit_dotdot(file, ctx))
326 return 0;
327 ctx->pos = 1 << PART_SHIFT;
328 }
329
330 /*
331 * The seek position is in the first synthesized part but is not
332 * valid.
333 */
334 if ((ctx->pos & PART_MASK) == 0)
335 return -EIO;
336
337 r = 0;
338
339 /*
340 * Must read more if the user has sought past what has been read
341 * so far. Stop a user who has sought past the end.
342 */
343 while (od->token != ORANGEFS_ITERATE_END &&
344 ctx->pos > od->end) {
345 r = orangefs_dir_more(oi, od, dentry);
346 if (r)
347 return r;
348 }
349 if (od->token == ORANGEFS_ITERATE_END && ctx->pos > od->end)
350 return -EIO;
351
352 /* Then try to fill if there's any left in the buffer. */
353 if (ctx->pos < od->end) {
354 r = orangefs_dir_fill(oi, od, dentry, ctx);
355 if (r)
356 return r;
357 }
358
359 /* Finally get some more and try to fill. */
360 if (od->token != ORANGEFS_ITERATE_END) {
361 r = orangefs_dir_more(oi, od, dentry);
362 if (r)
363 return r;
364 r = orangefs_dir_fill(oi, od, dentry, ctx);
365 }
366
367 return r;
368}
369
370static int orangefs_dir_open(struct inode *inode, struct file *file)
371{
372 struct orangefs_dir *od;
373 file->private_data = kmalloc(sizeof(struct orangefs_dir),
374 GFP_KERNEL);
375 if (!file->private_data)
376 return -ENOMEM;
377 od = file->private_data;
378 od->token = ORANGEFS_ITERATE_START;
379 od->part = NULL;
380 od->end = 1 << PART_SHIFT;
381 od->error = 0;
382 return 0;
383}
384
385static int orangefs_dir_release(struct inode *inode, struct file *file)
386{
387 struct orangefs_dir *od = file->private_data;
388 struct orangefs_dir_part *part = od->part;
389 while (part) {
390 struct orangefs_dir_part *next = part->next;
391 vfree(part);
392 part = next;
393 }
394 kfree(od);
395 return 0;
396}
397
398const struct file_operations orangefs_dir_operations = {
399 .llseek = orangefs_dir_llseek,
400 .read = generic_read_dir,
401 .iterate = orangefs_dir_iterate,
402 .open = orangefs_dir_open,
403 .release = orangefs_dir_release
404};