Commit | Line | Data |
---|---|---|
e3eec94d EP |
1 | /* |
2 | * 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> | |
3 | * All rights reserved. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation; either version 2 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | */ | |
15 | ||
16 | #include <linux/kernel.h> | |
17 | #include <linux/fs.h> | |
18 | #include <linux/jhash.h> | |
19 | #include <linux/namei.h> | |
5a0e3ad6 | 20 | #include <linux/slab.h> |
e3eec94d EP |
21 | #include <linux/pagemap.h> |
22 | ||
23 | #include "netfs.h" | |
24 | ||
25 | static int pohmelfs_cmp_hash(struct pohmelfs_name *n, u32 hash) | |
26 | { | |
27 | if (n->hash > hash) | |
28 | return -1; | |
29 | if (n->hash < hash) | |
30 | return 1; | |
31 | ||
32 | return 0; | |
33 | } | |
34 | ||
35 | static struct pohmelfs_name *pohmelfs_search_hash_unprecise(struct pohmelfs_inode *pi, u32 hash) | |
36 | { | |
37 | struct rb_node *n = pi->hash_root.rb_node; | |
38 | struct pohmelfs_name *tmp = NULL; | |
39 | int cmp; | |
40 | ||
41 | while (n) { | |
42 | tmp = rb_entry(n, struct pohmelfs_name, hash_node); | |
43 | ||
44 | cmp = pohmelfs_cmp_hash(tmp, hash); | |
45 | if (cmp < 0) | |
46 | n = n->rb_left; | |
47 | else if (cmp > 0) | |
48 | n = n->rb_right; | |
49 | else | |
50 | break; | |
51 | ||
52 | } | |
53 | ||
54 | return tmp; | |
55 | } | |
56 | ||
57 | struct pohmelfs_name *pohmelfs_search_hash(struct pohmelfs_inode *pi, u32 hash) | |
58 | { | |
59 | struct pohmelfs_name *tmp; | |
60 | ||
61 | tmp = pohmelfs_search_hash_unprecise(pi, hash); | |
62 | if (tmp && (tmp->hash == hash)) | |
63 | return tmp; | |
64 | ||
65 | return NULL; | |
66 | } | |
67 | ||
68 | static void __pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *node) | |
69 | { | |
70 | rb_erase(&node->hash_node, &parent->hash_root); | |
71 | } | |
72 | ||
73 | /* | |
74 | * Remove name cache entry from its caches and free it. | |
75 | */ | |
76 | static void pohmelfs_name_free(struct pohmelfs_inode *parent, struct pohmelfs_name *node) | |
77 | { | |
78 | __pohmelfs_name_del(parent, node); | |
79 | list_del(&node->sync_create_entry); | |
80 | kfree(node); | |
81 | } | |
82 | ||
83 | static struct pohmelfs_name *pohmelfs_insert_hash(struct pohmelfs_inode *pi, | |
84 | struct pohmelfs_name *new) | |
85 | { | |
86 | struct rb_node **n = &pi->hash_root.rb_node, *parent = NULL; | |
87 | struct pohmelfs_name *ret = NULL, *tmp; | |
88 | int cmp; | |
89 | ||
90 | while (*n) { | |
91 | parent = *n; | |
92 | ||
93 | tmp = rb_entry(parent, struct pohmelfs_name, hash_node); | |
94 | ||
95 | cmp = pohmelfs_cmp_hash(tmp, new->hash); | |
96 | if (cmp < 0) | |
97 | n = &parent->rb_left; | |
98 | else if (cmp > 0) | |
99 | n = &parent->rb_right; | |
100 | else { | |
101 | ret = tmp; | |
102 | break; | |
103 | } | |
104 | } | |
105 | ||
106 | if (ret) { | |
107 | printk("%s: exist: parent: %llu, ino: %llu, hash: %x, len: %u, data: '%s', " | |
3420bc94 | 108 | "new: ino: %llu, hash: %x, len: %u, data: '%s'.\n", |
e3eec94d EP |
109 | __func__, pi->ino, |
110 | ret->ino, ret->hash, ret->len, ret->data, | |
111 | new->ino, new->hash, new->len, new->data); | |
112 | ret->ino = new->ino; | |
113 | return ret; | |
114 | } | |
115 | ||
116 | rb_link_node(&new->hash_node, parent, n); | |
117 | rb_insert_color(&new->hash_node, &pi->hash_root); | |
118 | ||
119 | return NULL; | |
120 | } | |
121 | ||
122 | /* | |
123 | * Free name cache for given inode. | |
124 | */ | |
125 | void pohmelfs_free_names(struct pohmelfs_inode *parent) | |
126 | { | |
127 | struct rb_node *rb_node; | |
128 | struct pohmelfs_name *n; | |
129 | ||
130 | for (rb_node = rb_first(&parent->hash_root); rb_node;) { | |
131 | n = rb_entry(rb_node, struct pohmelfs_name, hash_node); | |
132 | rb_node = rb_next(rb_node); | |
133 | ||
134 | pohmelfs_name_free(parent, n); | |
135 | } | |
136 | } | |
137 | ||
138 | static void pohmelfs_fix_offset(struct pohmelfs_inode *parent, struct pohmelfs_name *node) | |
139 | { | |
140 | parent->total_len -= node->len; | |
141 | } | |
142 | ||
143 | /* | |
144 | * Free name cache entry helper. | |
145 | */ | |
146 | void pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *node) | |
147 | { | |
148 | pohmelfs_fix_offset(parent, node); | |
149 | pohmelfs_name_free(parent, node); | |
150 | } | |
151 | ||
152 | /* | |
153 | * Insert new name cache entry into all hash cache. | |
154 | */ | |
155 | static int pohmelfs_insert_name(struct pohmelfs_inode *parent, struct pohmelfs_name *n) | |
156 | { | |
157 | struct pohmelfs_name *name; | |
158 | ||
159 | name = pohmelfs_insert_hash(parent, n); | |
160 | if (name) | |
161 | return -EEXIST; | |
162 | ||
163 | parent->total_len += n->len; | |
164 | list_add_tail(&n->sync_create_entry, &parent->sync_create_list); | |
165 | ||
166 | return 0; | |
167 | } | |
168 | ||
169 | /* | |
170 | * Allocate new name cache entry. | |
171 | */ | |
172 | static struct pohmelfs_name *pohmelfs_name_alloc(unsigned int len) | |
173 | { | |
174 | struct pohmelfs_name *n; | |
175 | ||
176 | n = kzalloc(sizeof(struct pohmelfs_name) + len, GFP_KERNEL); | |
177 | if (!n) | |
178 | return NULL; | |
179 | ||
180 | INIT_LIST_HEAD(&n->sync_create_entry); | |
181 | ||
182 | n->data = (char *)(n+1); | |
183 | ||
184 | return n; | |
185 | } | |
186 | ||
187 | /* | |
188 | * Add new name entry into directory's cache. | |
189 | */ | |
190 | static int pohmelfs_add_dir(struct pohmelfs_sb *psb, struct pohmelfs_inode *parent, | |
191 | struct pohmelfs_inode *npi, struct qstr *str, unsigned int mode, int link) | |
192 | { | |
193 | int err = -ENOMEM; | |
194 | struct pohmelfs_name *n; | |
195 | ||
196 | n = pohmelfs_name_alloc(str->len + 1); | |
197 | if (!n) | |
198 | goto err_out_exit; | |
199 | ||
200 | n->ino = npi->ino; | |
201 | n->mode = mode; | |
202 | n->len = str->len; | |
203 | n->hash = str->hash; | |
204 | sprintf(n->data, "%s", str->name); | |
205 | ||
206 | mutex_lock(&parent->offset_lock); | |
207 | err = pohmelfs_insert_name(parent, n); | |
208 | mutex_unlock(&parent->offset_lock); | |
209 | ||
210 | if (err) { | |
211 | if (err != -EEXIST) | |
212 | goto err_out_free; | |
213 | kfree(n); | |
214 | } | |
215 | ||
216 | return 0; | |
217 | ||
218 | err_out_free: | |
219 | kfree(n); | |
220 | err_out_exit: | |
221 | return err; | |
222 | } | |
223 | ||
224 | /* | |
225 | * Create new inode for given parameters (name, inode info, parent). | |
226 | * This does not create object on the server, it will be synced there during writeback. | |
227 | */ | |
228 | struct pohmelfs_inode *pohmelfs_new_inode(struct pohmelfs_sb *psb, | |
229 | struct pohmelfs_inode *parent, struct qstr *str, | |
230 | struct netfs_inode_info *info, int link) | |
231 | { | |
232 | struct inode *new = NULL; | |
233 | struct pohmelfs_inode *npi; | |
234 | int err = -EEXIST; | |
235 | ||
236 | dprintk("%s: creating inode: parent: %llu, ino: %llu, str: %p.\n", | |
3420bc94 | 237 | __func__, (parent) ? parent->ino : 0, info->ino, str); |
e3eec94d EP |
238 | |
239 | err = -ENOMEM; | |
240 | new = iget_locked(psb->sb, info->ino); | |
241 | if (!new) | |
242 | goto err_out_exit; | |
243 | ||
244 | npi = POHMELFS_I(new); | |
245 | npi->ino = info->ino; | |
246 | err = 0; | |
247 | ||
248 | if (new->i_state & I_NEW) { | |
249 | dprintk("%s: filling VFS inode: %lu/%llu.\n", | |
250 | __func__, new->i_ino, info->ino); | |
251 | pohmelfs_fill_inode(new, info); | |
252 | ||
253 | if (S_ISDIR(info->mode)) { | |
254 | struct qstr s; | |
255 | ||
256 | s.name = "."; | |
257 | s.len = 1; | |
258 | s.hash = jhash(s.name, s.len, 0); | |
259 | ||
260 | err = pohmelfs_add_dir(psb, npi, npi, &s, info->mode, 0); | |
261 | if (err) | |
262 | goto err_out_put; | |
263 | ||
264 | s.name = ".."; | |
265 | s.len = 2; | |
266 | s.hash = jhash(s.name, s.len, 0); | |
267 | ||
3420bc94 RP |
268 | err = pohmelfs_add_dir(psb, npi, (parent) ? parent : npi, &s, |
269 | (parent) ? parent->vfs_inode.i_mode : npi->vfs_inode.i_mode, 0); | |
e3eec94d EP |
270 | if (err) |
271 | goto err_out_put; | |
272 | } | |
273 | } | |
274 | ||
275 | if (str) { | |
276 | if (parent) { | |
277 | err = pohmelfs_add_dir(psb, parent, npi, str, info->mode, link); | |
278 | ||
279 | dprintk("%s: %s inserted name: '%s', new_offset: %llu, ino: %llu, parent: %llu.\n", | |
3420bc94 | 280 | __func__, (err) ? "unsuccessfully" : "successfully", |
e3eec94d EP |
281 | str->name, parent->total_len, info->ino, parent->ino); |
282 | ||
283 | if (err && err != -EEXIST) | |
284 | goto err_out_put; | |
285 | } | |
286 | } | |
287 | ||
288 | if (new->i_state & I_NEW) { | |
289 | if (parent) | |
290 | mark_inode_dirty(&parent->vfs_inode); | |
291 | mark_inode_dirty(new); | |
292 | } | |
293 | ||
294 | set_bit(NETFS_INODE_OWNED, &npi->state); | |
295 | npi->lock_type = POHMELFS_WRITE_LOCK; | |
296 | unlock_new_inode(new); | |
297 | ||
298 | return npi; | |
299 | ||
300 | err_out_put: | |
301 | printk("%s: putting inode: %p, npi: %p, error: %d.\n", __func__, new, npi, err); | |
302 | iput(new); | |
303 | err_out_exit: | |
304 | return ERR_PTR(err); | |
305 | } | |
306 | ||
307 | static int pohmelfs_remote_sync_complete(struct page **pages, unsigned int page_num, | |
308 | void *private, int err) | |
309 | { | |
310 | struct pohmelfs_inode *pi = private; | |
311 | struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb); | |
312 | ||
313 | dprintk("%s: ino: %llu, err: %d.\n", __func__, pi->ino, err); | |
314 | ||
315 | if (err) | |
316 | pi->error = err; | |
317 | wake_up(&psb->wait); | |
318 | pohmelfs_put_inode(pi); | |
319 | ||
320 | return err; | |
321 | } | |
322 | ||
323 | /* | |
324 | * Receive directory content from the server. | |
325 | * This should be only done for objects, which were not created locally, | |
326 | * and which were not synced previously. | |
327 | */ | |
328 | static int pohmelfs_sync_remote_dir(struct pohmelfs_inode *pi) | |
329 | { | |
330 | struct inode *inode = &pi->vfs_inode; | |
331 | struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb); | |
b82ba780 | 332 | long ret = psb->wait_on_page_timeout; |
e3eec94d EP |
333 | int err; |
334 | ||
335 | dprintk("%s: dir: %llu, state: %lx: remote_synced: %d.\n", | |
336 | __func__, pi->ino, pi->state, test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state)); | |
337 | ||
338 | if (test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &pi->state)) | |
339 | return 0; | |
340 | ||
341 | if (!igrab(inode)) { | |
342 | err = -ENOENT; | |
343 | goto err_out_exit; | |
344 | } | |
345 | ||
346 | err = pohmelfs_meta_command(pi, NETFS_READDIR, NETFS_TRANS_SINGLE_DST, | |
347 | pohmelfs_remote_sync_complete, pi, 0); | |
348 | if (err) | |
349 | goto err_out_exit; | |
350 | ||
351 | pi->error = 0; | |
352 | ret = wait_event_interruptible_timeout(psb->wait, | |
353 | test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &pi->state) || pi->error, ret); | |
354 | dprintk("%s: awake dir: %llu, ret: %ld, err: %d.\n", __func__, pi->ino, ret, pi->error); | |
355 | if (ret <= 0) { | |
2d7cf8ef EP |
356 | err = ret; |
357 | if (!err) | |
358 | err = -ETIMEDOUT; | |
e3eec94d EP |
359 | goto err_out_exit; |
360 | } | |
361 | ||
362 | if (pi->error) | |
363 | return pi->error; | |
364 | ||
365 | return 0; | |
366 | ||
367 | err_out_exit: | |
368 | clear_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state); | |
369 | ||
370 | return err; | |
371 | } | |
372 | ||
373 | static int pohmelfs_dir_open(struct inode *inode, struct file *file) | |
374 | { | |
375 | file->private_data = NULL; | |
376 | return 0; | |
377 | } | |
378 | ||
379 | /* | |
380 | * VFS readdir callback. Syncs directory content from server if needed, | |
381 | * and provides direntry info to the userspace. | |
382 | */ | |
383 | static int pohmelfs_readdir(struct file *file, void *dirent, filldir_t filldir) | |
384 | { | |
385 | struct inode *inode = file->f_path.dentry->d_inode; | |
386 | struct pohmelfs_inode *pi = POHMELFS_I(inode); | |
387 | struct pohmelfs_name *n; | |
388 | struct rb_node *rb_node; | |
389 | int err = 0, mode; | |
390 | u64 len; | |
391 | ||
392 | dprintk("%s: parent: %llu, fpos: %llu, hash: %08lx.\n", | |
393 | __func__, pi->ino, (u64)file->f_pos, | |
394 | (unsigned long)file->private_data); | |
f3b8fa70 | 395 | #if 0 |
e3eec94d EP |
396 | err = pohmelfs_data_lock(pi, 0, ~0, POHMELFS_READ_LOCK); |
397 | if (err) | |
398 | return err; | |
f3b8fa70 | 399 | #endif |
e3eec94d EP |
400 | err = pohmelfs_sync_remote_dir(pi); |
401 | if (err) | |
402 | return err; | |
403 | ||
404 | if (file->private_data && (file->private_data == (void *)(unsigned long)file->f_pos)) | |
405 | return 0; | |
406 | ||
407 | mutex_lock(&pi->offset_lock); | |
408 | n = pohmelfs_search_hash_unprecise(pi, (unsigned long)file->private_data); | |
409 | ||
410 | while (n) { | |
411 | mode = (n->mode >> 12) & 15; | |
412 | ||
413 | dprintk("%s: offset: %llu, parent ino: %llu, name: '%s', len: %u, ino: %llu, " | |
414 | "mode: %o/%o, fpos: %llu, hash: %08x.\n", | |
415 | __func__, file->f_pos, pi->ino, n->data, n->len, | |
416 | n->ino, n->mode, mode, file->f_pos, n->hash); | |
417 | ||
106a47ba | 418 | file->private_data = (void *)(unsigned long)n->hash; |
e3eec94d EP |
419 | |
420 | len = n->len; | |
421 | err = filldir(dirent, n->data, n->len, file->f_pos, n->ino, mode); | |
422 | ||
423 | if (err < 0) { | |
424 | dprintk("%s: err: %d.\n", __func__, err); | |
425 | err = 0; | |
426 | break; | |
427 | } | |
428 | ||
429 | file->f_pos += len; | |
430 | ||
431 | rb_node = rb_next(&n->hash_node); | |
432 | ||
433 | if (!rb_node || (rb_node == &n->hash_node)) { | |
434 | file->private_data = (void *)(unsigned long)file->f_pos; | |
435 | break; | |
436 | } | |
437 | ||
438 | n = rb_entry(rb_node, struct pohmelfs_name, hash_node); | |
439 | } | |
440 | mutex_unlock(&pi->offset_lock); | |
441 | ||
442 | return err; | |
443 | } | |
444 | ||
445 | static loff_t pohmelfs_dir_lseek(struct file *file, loff_t offset, int origin) | |
446 | { | |
447 | file->f_pos = offset; | |
448 | file->private_data = NULL; | |
449 | return offset; | |
450 | } | |
451 | ||
452 | const struct file_operations pohmelfs_dir_fops = { | |
453 | .open = pohmelfs_dir_open, | |
454 | .read = generic_read_dir, | |
455 | .llseek = pohmelfs_dir_lseek, | |
456 | .readdir = pohmelfs_readdir, | |
457 | }; | |
458 | ||
459 | /* | |
460 | * Lookup single object on server. | |
461 | */ | |
462 | static int pohmelfs_lookup_single(struct pohmelfs_inode *parent, | |
463 | struct qstr *str, u64 ino) | |
464 | { | |
465 | struct pohmelfs_sb *psb = POHMELFS_SB(parent->vfs_inode.i_sb); | |
466 | long ret = msecs_to_jiffies(5000); | |
467 | int err; | |
468 | ||
469 | set_bit(NETFS_COMMAND_PENDING, &parent->state); | |
470 | err = pohmelfs_meta_command_data(parent, parent->ino, NETFS_LOOKUP, | |
471 | (char *)str->name, NETFS_TRANS_SINGLE_DST, NULL, NULL, ino); | |
472 | if (err) | |
473 | goto err_out_exit; | |
474 | ||
475 | err = 0; | |
476 | ret = wait_event_interruptible_timeout(psb->wait, | |
477 | !test_bit(NETFS_COMMAND_PENDING, &parent->state), ret); | |
2d7cf8ef EP |
478 | if (ret <= 0) { |
479 | err = ret; | |
480 | if (!err) | |
481 | err = -ETIMEDOUT; | |
482 | } | |
e3eec94d EP |
483 | |
484 | if (err) | |
485 | goto err_out_exit; | |
486 | ||
487 | return 0; | |
488 | ||
489 | err_out_exit: | |
490 | clear_bit(NETFS_COMMAND_PENDING, &parent->state); | |
491 | ||
492 | printk("%s: failed: parent: %llu, ino: %llu, name: '%s', err: %d.\n", | |
493 | __func__, parent->ino, ino, str->name, err); | |
494 | ||
495 | return err; | |
496 | } | |
497 | ||
498 | /* | |
499 | * VFS lookup callback. | |
500 | * We first try to get inode number from local name cache, if we have one, | |
501 | * then inode can be found in inode cache. If there is no inode or no object in | |
502 | * local cache, try to lookup it on server. This only should be done for directories, | |
503 | * which were not created locally, otherwise remote server does not know about dir at all, | |
504 | * so no need to try to know that. | |
505 | */ | |
506 | struct dentry *pohmelfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) | |
507 | { | |
508 | struct pohmelfs_inode *parent = POHMELFS_I(dir); | |
509 | struct pohmelfs_name *n; | |
510 | struct inode *inode = NULL; | |
511 | unsigned long ino = 0; | |
2d7cf8ef | 512 | int err, lock_type = POHMELFS_READ_LOCK, need_lock = 1; |
e3eec94d EP |
513 | struct qstr str = dentry->d_name; |
514 | ||
515 | if ((nd->intent.open.flags & O_ACCMODE) > 1) | |
516 | lock_type = POHMELFS_WRITE_LOCK; | |
517 | ||
2d7cf8ef EP |
518 | if (test_bit(NETFS_INODE_OWNED, &parent->state)) { |
519 | if (lock_type == parent->lock_type) | |
520 | need_lock = 0; | |
521 | if ((lock_type == POHMELFS_READ_LOCK) && (parent->lock_type == POHMELFS_WRITE_LOCK)) | |
522 | need_lock = 0; | |
523 | } | |
524 | ||
525 | if ((lock_type == POHMELFS_READ_LOCK) && !test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &parent->state)) | |
526 | need_lock = 1; | |
e3eec94d | 527 | |
e3eec94d EP |
528 | str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); |
529 | ||
530 | mutex_lock(&parent->offset_lock); | |
531 | n = pohmelfs_search_hash(parent, str.hash); | |
532 | if (n) | |
533 | ino = n->ino; | |
534 | mutex_unlock(&parent->offset_lock); | |
535 | ||
b522efdd EP |
536 | dprintk("%s: start ino: %lu, inode: %p, name: '%s', hash: %x, parent_state: %lx, need_lock: %d.\n", |
537 | __func__, ino, inode, str.name, str.hash, parent->state, need_lock); | |
e3eec94d EP |
538 | |
539 | if (ino) { | |
540 | inode = ilookup(dir->i_sb, ino); | |
541 | if (inode) | |
542 | goto out; | |
543 | } | |
544 | ||
f3b8fa70 | 545 | dprintk("%s: no inode dir: %p, dir_ino: %llu, name: '%s', len: %u, dir_state: %lx, ino: %lu.\n", |
e3eec94d EP |
546 | __func__, dir, parent->ino, |
547 | str.name, str.len, parent->state, ino); | |
548 | ||
549 | if (!ino) { | |
550 | if (!need_lock) | |
551 | goto out; | |
552 | } | |
553 | ||
f3b8fa70 EP |
554 | err = pohmelfs_data_lock(parent, 0, ~0, lock_type); |
555 | if (err) | |
556 | goto out; | |
557 | ||
e3eec94d EP |
558 | err = pohmelfs_lookup_single(parent, &str, ino); |
559 | if (err) | |
560 | goto out; | |
561 | ||
562 | if (!ino) { | |
563 | mutex_lock(&parent->offset_lock); | |
564 | n = pohmelfs_search_hash(parent, str.hash); | |
565 | if (n) | |
566 | ino = n->ino; | |
567 | mutex_unlock(&parent->offset_lock); | |
568 | } | |
569 | ||
570 | if (ino) { | |
571 | inode = ilookup(dir->i_sb, ino); | |
d1ec6440 | 572 | dprintk("%s: second lookup ino: %lu, inode: %p, name: '%s', hash: %x.\n", |
e3eec94d EP |
573 | __func__, ino, inode, str.name, str.hash); |
574 | if (!inode) { | |
d1ec6440 | 575 | dprintk("%s: No inode for ino: %lu, name: '%s', hash: %x.\n", |
e3eec94d | 576 | __func__, ino, str.name, str.hash); |
3bafeab7 | 577 | /* return NULL; */ |
e3eec94d EP |
578 | return ERR_PTR(-EACCES); |
579 | } | |
580 | } else { | |
581 | printk("%s: No inode number : name: '%s', hash: %x.\n", | |
582 | __func__, str.name, str.hash); | |
583 | } | |
584 | out: | |
585 | return d_splice_alias(inode, dentry); | |
586 | } | |
587 | ||
588 | /* | |
589 | * Create new object in local cache. Object will be synced to server | |
590 | * during writeback for given inode. | |
591 | */ | |
592 | struct pohmelfs_inode *pohmelfs_create_entry_local(struct pohmelfs_sb *psb, | |
593 | struct pohmelfs_inode *parent, struct qstr *str, u64 start, int mode) | |
594 | { | |
595 | struct pohmelfs_inode *npi; | |
596 | int err = -ENOMEM; | |
597 | struct netfs_inode_info info; | |
598 | ||
599 | dprintk("%s: name: '%s', mode: %o, start: %llu.\n", | |
600 | __func__, str->name, mode, start); | |
601 | ||
602 | info.mode = mode; | |
603 | info.ino = start; | |
604 | ||
605 | if (!start) | |
606 | info.ino = pohmelfs_new_ino(psb); | |
607 | ||
3420bc94 | 608 | info.nlink = S_ISDIR(mode) ? 2 : 1; |
e3eec94d EP |
609 | info.uid = current_fsuid(); |
610 | info.gid = current_fsgid(); | |
611 | info.size = 0; | |
612 | info.blocksize = 512; | |
613 | info.blocks = 0; | |
614 | info.rdev = 0; | |
615 | info.version = 0; | |
616 | ||
617 | npi = pohmelfs_new_inode(psb, parent, str, &info, !!start); | |
618 | if (IS_ERR(npi)) { | |
619 | err = PTR_ERR(npi); | |
620 | goto err_out_unlock; | |
621 | } | |
622 | ||
623 | return npi; | |
624 | ||
625 | err_out_unlock: | |
626 | dprintk("%s: err: %d.\n", __func__, err); | |
627 | return ERR_PTR(err); | |
628 | } | |
629 | ||
630 | /* | |
631 | * Create local object and bind it to dentry. | |
632 | */ | |
633 | static int pohmelfs_create_entry(struct inode *dir, struct dentry *dentry, u64 start, int mode) | |
634 | { | |
635 | struct pohmelfs_sb *psb = POHMELFS_SB(dir->i_sb); | |
636 | struct pohmelfs_inode *npi, *parent; | |
637 | struct qstr str = dentry->d_name; | |
638 | int err; | |
639 | ||
640 | parent = POHMELFS_I(dir); | |
641 | ||
642 | err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK); | |
643 | if (err) | |
644 | return err; | |
645 | ||
646 | str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); | |
647 | ||
648 | npi = pohmelfs_create_entry_local(psb, parent, &str, start, mode); | |
649 | if (IS_ERR(npi)) | |
650 | return PTR_ERR(npi); | |
651 | ||
652 | d_instantiate(dentry, &npi->vfs_inode); | |
653 | ||
654 | dprintk("%s: parent: %llu, inode: %llu, name: '%s', parent_nlink: %d, nlink: %d.\n", | |
655 | __func__, parent->ino, npi->ino, dentry->d_name.name, | |
656 | (signed)dir->i_nlink, (signed)npi->vfs_inode.i_nlink); | |
657 | ||
658 | return 0; | |
659 | } | |
660 | ||
661 | /* | |
662 | * VFS create and mkdir callbacks. | |
663 | */ | |
664 | static int pohmelfs_create(struct inode *dir, struct dentry *dentry, int mode, | |
665 | struct nameidata *nd) | |
666 | { | |
667 | return pohmelfs_create_entry(dir, dentry, 0, mode); | |
668 | } | |
669 | ||
670 | static int pohmelfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) | |
671 | { | |
672 | int err; | |
673 | ||
674 | inode_inc_link_count(dir); | |
675 | err = pohmelfs_create_entry(dir, dentry, 0, mode | S_IFDIR); | |
676 | if (err) | |
677 | inode_dec_link_count(dir); | |
678 | ||
679 | return err; | |
680 | } | |
681 | ||
682 | static int pohmelfs_remove_entry(struct inode *dir, struct dentry *dentry) | |
683 | { | |
684 | struct pohmelfs_sb *psb = POHMELFS_SB(dir->i_sb); | |
685 | struct inode *inode = dentry->d_inode; | |
686 | struct pohmelfs_inode *parent = POHMELFS_I(dir), *pi = POHMELFS_I(inode); | |
687 | struct pohmelfs_name *n; | |
688 | int err = -ENOENT; | |
689 | struct qstr str = dentry->d_name; | |
690 | ||
691 | err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK); | |
692 | if (err) | |
693 | return err; | |
694 | ||
695 | str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); | |
696 | ||
697 | dprintk("%s: dir_ino: %llu, inode: %llu, name: '%s', nlink: %d.\n", | |
698 | __func__, parent->ino, pi->ino, | |
699 | str.name, (signed)inode->i_nlink); | |
700 | ||
701 | BUG_ON(!inode); | |
702 | ||
703 | mutex_lock(&parent->offset_lock); | |
704 | n = pohmelfs_search_hash(parent, str.hash); | |
705 | if (n) { | |
706 | pohmelfs_fix_offset(parent, n); | |
dc828461 | 707 | if (test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state)) |
e3eec94d | 708 | pohmelfs_remove_child(pi, n); |
dc828461 | 709 | |
e3eec94d EP |
710 | pohmelfs_name_free(parent, n); |
711 | err = 0; | |
712 | } | |
713 | mutex_unlock(&parent->offset_lock); | |
714 | ||
715 | if (!err) { | |
716 | psb->avail_size += inode->i_size; | |
717 | ||
718 | pohmelfs_inode_del_inode(psb, pi); | |
719 | ||
720 | mark_inode_dirty(dir); | |
721 | ||
722 | inode->i_ctime = dir->i_ctime; | |
723 | if (inode->i_nlink) | |
724 | inode_dec_link_count(inode); | |
725 | } | |
e3eec94d EP |
726 | |
727 | return err; | |
728 | } | |
729 | ||
730 | /* | |
731 | * Unlink and rmdir VFS callbacks. | |
732 | */ | |
733 | static int pohmelfs_unlink(struct inode *dir, struct dentry *dentry) | |
734 | { | |
735 | return pohmelfs_remove_entry(dir, dentry); | |
736 | } | |
737 | ||
738 | static int pohmelfs_rmdir(struct inode *dir, struct dentry *dentry) | |
739 | { | |
740 | int err; | |
741 | struct inode *inode = dentry->d_inode; | |
742 | ||
743 | dprintk("%s: parent: %llu, inode: %llu, name: '%s', parent_nlink: %d, nlink: %d.\n", | |
744 | __func__, POHMELFS_I(dir)->ino, POHMELFS_I(inode)->ino, | |
745 | dentry->d_name.name, (signed)dir->i_nlink, (signed)inode->i_nlink); | |
746 | ||
747 | err = pohmelfs_remove_entry(dir, dentry); | |
748 | if (!err) { | |
749 | inode_dec_link_count(dir); | |
750 | inode_dec_link_count(inode); | |
751 | } | |
752 | ||
753 | return err; | |
754 | } | |
755 | ||
756 | /* | |
757 | * Link creation is synchronous. | |
758 | * I'm lazy. | |
759 | * Earth is somewhat round. | |
760 | */ | |
761 | static int pohmelfs_create_link(struct pohmelfs_inode *parent, struct qstr *obj, | |
762 | struct pohmelfs_inode *target, struct qstr *tstr) | |
763 | { | |
764 | struct super_block *sb = parent->vfs_inode.i_sb; | |
765 | struct pohmelfs_sb *psb = POHMELFS_SB(sb); | |
766 | struct netfs_cmd *cmd; | |
767 | struct netfs_trans *t; | |
768 | void *data; | |
769 | int err, parent_len, target_len = 0, cur_len, path_size = 0; | |
770 | ||
771 | err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK); | |
772 | if (err) | |
773 | return err; | |
774 | ||
775 | err = sb->s_op->write_inode(&parent->vfs_inode, 0); | |
776 | if (err) | |
777 | goto err_out_exit; | |
778 | ||
779 | if (tstr) | |
780 | target_len = tstr->len; | |
781 | ||
782 | parent_len = pohmelfs_path_length(parent); | |
783 | if (target) | |
784 | target_len += pohmelfs_path_length(target); | |
785 | ||
786 | if (parent_len < 0) { | |
787 | err = parent_len; | |
788 | goto err_out_exit; | |
789 | } | |
790 | ||
791 | if (target_len < 0) { | |
792 | err = target_len; | |
793 | goto err_out_exit; | |
794 | } | |
795 | ||
796 | t = netfs_trans_alloc(psb, parent_len + target_len + obj->len + 2, 0, 0); | |
797 | if (!t) { | |
798 | err = -ENOMEM; | |
799 | goto err_out_exit; | |
800 | } | |
801 | cur_len = netfs_trans_cur_len(t); | |
802 | ||
803 | cmd = netfs_trans_current(t); | |
804 | if (IS_ERR(cmd)) { | |
805 | err = PTR_ERR(cmd); | |
806 | goto err_out_free; | |
807 | } | |
808 | ||
809 | data = (void *)(cmd + 1); | |
810 | cur_len -= sizeof(struct netfs_cmd); | |
811 | ||
812 | err = pohmelfs_construct_path_string(parent, data, parent_len); | |
813 | if (err > 0) { | |
814 | /* Do not place null-byte before the slash */ | |
815 | path_size = err - 1; | |
816 | cur_len -= path_size; | |
817 | ||
818 | err = snprintf(data + path_size, cur_len, "/%s|", obj->name); | |
819 | ||
820 | path_size += err; | |
821 | cur_len -= err; | |
822 | ||
823 | cmd->ext = path_size - 1; /* No | symbol */ | |
824 | ||
825 | if (target) { | |
826 | err = pohmelfs_construct_path_string(target, data + path_size, target_len); | |
827 | if (err > 0) { | |
828 | path_size += err; | |
829 | cur_len -= err; | |
830 | } | |
831 | } | |
832 | } | |
833 | ||
834 | if (err < 0) | |
835 | goto err_out_free; | |
836 | ||
837 | cmd->start = 0; | |
838 | ||
839 | if (!target && tstr) { | |
840 | if (tstr->len > cur_len - 1) { | |
841 | err = -ENAMETOOLONG; | |
842 | goto err_out_free; | |
843 | } | |
844 | ||
845 | err = snprintf(data + path_size, cur_len, "%s", tstr->name) + 1; /* 0-byte */ | |
846 | path_size += err; | |
847 | cur_len -= err; | |
848 | cmd->start = 1; | |
849 | } | |
850 | ||
851 | dprintk("%s: parent: %llu, obj: '%s', target_inode: %llu, target_str: '%s', full: '%s'.\n", | |
3420bc94 | 852 | __func__, parent->ino, obj->name, (target) ? target->ino : 0, (tstr) ? tstr->name : NULL, |
e3eec94d EP |
853 | (char *)data); |
854 | ||
855 | cmd->cmd = NETFS_LINK; | |
856 | cmd->size = path_size; | |
857 | cmd->id = parent->ino; | |
858 | ||
859 | netfs_convert_cmd(cmd); | |
860 | ||
861 | netfs_trans_update(cmd, t, path_size); | |
862 | ||
863 | err = netfs_trans_finish(t, psb); | |
864 | if (err) | |
865 | goto err_out_exit; | |
866 | ||
867 | return 0; | |
868 | ||
869 | err_out_free: | |
870 | t->result = err; | |
871 | netfs_trans_put(t); | |
872 | err_out_exit: | |
873 | return err; | |
874 | } | |
875 | ||
876 | /* | |
877 | * VFS hard and soft link callbacks. | |
878 | */ | |
879 | static int pohmelfs_link(struct dentry *old_dentry, struct inode *dir, | |
880 | struct dentry *dentry) | |
881 | { | |
882 | struct inode *inode = old_dentry->d_inode; | |
883 | struct pohmelfs_inode *pi = POHMELFS_I(inode); | |
884 | int err; | |
885 | struct qstr str = dentry->d_name; | |
886 | ||
887 | str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); | |
888 | ||
889 | err = inode->i_sb->s_op->write_inode(inode, 0); | |
890 | if (err) | |
891 | return err; | |
892 | ||
893 | err = pohmelfs_create_link(POHMELFS_I(dir), &str, pi, NULL); | |
894 | if (err) | |
895 | return err; | |
896 | ||
897 | return pohmelfs_create_entry(dir, dentry, pi->ino, inode->i_mode); | |
898 | } | |
899 | ||
900 | static int pohmelfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) | |
901 | { | |
902 | struct qstr sym_str; | |
903 | struct qstr str = dentry->d_name; | |
904 | struct inode *inode; | |
905 | int err; | |
906 | ||
907 | str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); | |
908 | ||
909 | sym_str.name = symname; | |
910 | sym_str.len = strlen(symname); | |
911 | ||
912 | err = pohmelfs_create_link(POHMELFS_I(dir), &str, NULL, &sym_str); | |
913 | if (err) | |
914 | goto err_out_exit; | |
915 | ||
916 | err = pohmelfs_create_entry(dir, dentry, 0, S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO); | |
917 | if (err) | |
918 | goto err_out_exit; | |
919 | ||
920 | inode = dentry->d_inode; | |
921 | ||
922 | err = page_symlink(inode, symname, sym_str.len + 1); | |
923 | if (err) | |
924 | goto err_out_put; | |
925 | ||
926 | return 0; | |
927 | ||
928 | err_out_put: | |
929 | iput(inode); | |
930 | err_out_exit: | |
931 | return err; | |
932 | } | |
933 | ||
934 | static int pohmelfs_send_rename(struct pohmelfs_inode *pi, struct pohmelfs_inode *parent, | |
935 | struct qstr *str) | |
936 | { | |
937 | int path_len, err, total_len = 0, inode_len, parent_len; | |
938 | char *path; | |
939 | struct netfs_trans *t; | |
940 | struct netfs_cmd *cmd; | |
941 | struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb); | |
942 | ||
943 | parent_len = pohmelfs_path_length(parent); | |
944 | inode_len = pohmelfs_path_length(pi); | |
945 | ||
946 | if (parent_len < 0 || inode_len < 0) | |
947 | return -EINVAL; | |
948 | ||
949 | path_len = parent_len + inode_len + str->len + 3; | |
950 | ||
951 | t = netfs_trans_alloc(psb, path_len, 0, 0); | |
952 | if (!t) | |
953 | return -ENOMEM; | |
954 | ||
955 | cmd = netfs_trans_current(t); | |
956 | path = (char *)(cmd + 1); | |
957 | ||
958 | err = pohmelfs_construct_path_string(pi, path, inode_len); | |
959 | if (err < 0) | |
960 | goto err_out_unlock; | |
961 | ||
962 | cmd->ext = err; | |
963 | ||
964 | path += err; | |
965 | total_len += err; | |
966 | path_len -= err; | |
967 | ||
968 | *path = '|'; | |
969 | path++; | |
970 | total_len++; | |
971 | path_len--; | |
972 | ||
973 | err = pohmelfs_construct_path_string(parent, path, parent_len); | |
974 | if (err < 0) | |
975 | goto err_out_unlock; | |
976 | ||
977 | /* | |
978 | * Do not place a null-byte before the final slash and the name. | |
979 | */ | |
980 | err--; | |
981 | path += err; | |
982 | total_len += err; | |
983 | path_len -= err; | |
984 | ||
985 | err = snprintf(path, path_len - 1, "/%s", str->name); | |
986 | ||
987 | total_len += err + 1; /* 0 symbol */ | |
988 | path_len -= err + 1; | |
989 | ||
990 | cmd->cmd = NETFS_RENAME; | |
991 | cmd->id = pi->ino; | |
992 | cmd->start = parent->ino; | |
993 | cmd->size = total_len; | |
994 | ||
995 | netfs_convert_cmd(cmd); | |
996 | ||
997 | netfs_trans_update(cmd, t, total_len); | |
998 | ||
999 | return netfs_trans_finish(t, psb); | |
1000 | ||
1001 | err_out_unlock: | |
1002 | netfs_trans_free(t); | |
1003 | return err; | |
1004 | } | |
1005 | ||
1006 | static int pohmelfs_rename(struct inode *old_dir, struct dentry *old_dentry, | |
1007 | struct inode *new_dir, struct dentry *new_dentry) | |
1008 | { | |
1009 | struct inode *inode = old_dentry->d_inode; | |
1010 | struct pohmelfs_inode *old_parent, *pi, *new_parent; | |
1011 | struct qstr str = new_dentry->d_name; | |
1012 | struct pohmelfs_name *n; | |
1013 | unsigned int old_hash; | |
1014 | int err = -ENOENT; | |
1015 | ||
1016 | pi = POHMELFS_I(inode); | |
1017 | old_parent = POHMELFS_I(old_dir); | |
1018 | ||
dc828461 | 1019 | if (new_dir) |
e3eec94d | 1020 | new_dir->i_sb->s_op->write_inode(new_dir, 0); |
e3eec94d EP |
1021 | |
1022 | old_hash = jhash(old_dentry->d_name.name, old_dentry->d_name.len, 0); | |
1023 | str.hash = jhash(new_dentry->d_name.name, new_dentry->d_name.len, 0); | |
1024 | ||
1025 | str.len = new_dentry->d_name.len; | |
1026 | str.name = new_dentry->d_name.name; | |
1027 | str.hash = jhash(new_dentry->d_name.name, new_dentry->d_name.len, 0); | |
1028 | ||
1029 | if (new_dir) { | |
1030 | new_parent = POHMELFS_I(new_dir); | |
1031 | err = -ENOTEMPTY; | |
1032 | ||
1033 | if (S_ISDIR(inode->i_mode) && | |
1034 | new_parent->total_len <= 3) | |
1035 | goto err_out_exit; | |
1036 | } else { | |
1037 | new_parent = old_parent; | |
1038 | } | |
1039 | ||
1040 | dprintk("%s: ino: %llu, parent: %llu, name: '%s' -> parent: %llu, name: '%s', i_size: %llu.\n", | |
1041 | __func__, pi->ino, old_parent->ino, old_dentry->d_name.name, | |
1042 | new_parent->ino, new_dentry->d_name.name, inode->i_size); | |
1043 | ||
1044 | if (test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state) && | |
1045 | test_bit(NETFS_INODE_OWNED, &pi->state)) { | |
1046 | err = pohmelfs_send_rename(pi, new_parent, &str); | |
1047 | if (err) | |
1048 | goto err_out_exit; | |
1049 | } | |
1050 | ||
1051 | n = pohmelfs_name_alloc(str.len + 1); | |
1052 | if (!n) | |
1053 | goto err_out_exit; | |
1054 | ||
1055 | mutex_lock(&new_parent->offset_lock); | |
1056 | n->ino = pi->ino; | |
1057 | n->mode = inode->i_mode; | |
1058 | n->len = str.len; | |
1059 | n->hash = str.hash; | |
1060 | sprintf(n->data, "%s", str.name); | |
1061 | ||
1062 | err = pohmelfs_insert_name(new_parent, n); | |
1063 | mutex_unlock(&new_parent->offset_lock); | |
1064 | ||
1065 | if (err) | |
1066 | goto err_out_exit; | |
1067 | ||
1068 | mutex_lock(&old_parent->offset_lock); | |
1069 | n = pohmelfs_search_hash(old_parent, old_hash); | |
1070 | if (n) | |
1071 | pohmelfs_name_del(old_parent, n); | |
1072 | mutex_unlock(&old_parent->offset_lock); | |
1073 | ||
1074 | mark_inode_dirty(inode); | |
1075 | mark_inode_dirty(&new_parent->vfs_inode); | |
1076 | ||
1077 | WARN_ON_ONCE(list_empty(&inode->i_dentry)); | |
1078 | ||
1079 | return 0; | |
1080 | ||
1081 | err_out_exit: | |
1082 | ||
1083 | clear_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state); | |
1084 | ||
1085 | mutex_unlock(&inode->i_mutex); | |
1086 | return err; | |
1087 | } | |
1088 | ||
1089 | /* | |
1090 | * POHMELFS directory inode operations. | |
1091 | */ | |
1092 | const struct inode_operations pohmelfs_dir_inode_ops = { | |
1093 | .link = pohmelfs_link, | |
1094 | .symlink = pohmelfs_symlink, | |
1095 | .unlink = pohmelfs_unlink, | |
1096 | .mkdir = pohmelfs_mkdir, | |
1097 | .rmdir = pohmelfs_rmdir, | |
1098 | .create = pohmelfs_create, | |
1099 | .lookup = pohmelfs_lookup, | |
1100 | .setattr = pohmelfs_setattr, | |
1101 | .rename = pohmelfs_rename, | |
1102 | }; |