Add synchronous inode logging in the NFS server.
[bluesky.git] / nfs3 / nfs3.c
1 /*
2  * This is sample code generated by rpcgen.
3  * These are only templates and you can use them
4  * as a guideline for developing your own functions.
5  */
6
7 #include "nfs3_prot.h"
8 #include "bluesky.h"
9
10 extern BlueSkyFS *fs;
11
12 #define NFS_BLOCKSIZE 32768
13 #define NFS_MAXSIZE (1 << 20)
14
15 /* Check that a string is a valid file name.  We require that it be valid
16  * UTF-8, that it not be empty, and that it not contain embedded forward
17  * slashes.  Also checks that the length of the string is not more than the
18  * maximum allowed length.  This function does allow the names "." and "..".
19  * Returns TRUE if the string is allowed as a filename. */
20 gboolean validate_filename(const char *filename)
21 {
22     if (filename == NULL || filename[0] == '\0')
23         return FALSE;
24     if (strlen(filename) > 255)
25         return FALSE;
26     if (!g_utf8_validate(filename, -1, NULL))
27         return FALSE;
28     if (strchr(filename, '/') != NULL)
29         return FALSE;
30     return TRUE;
31 }
32
33 /* Arrange for a reference to an inode to be dropped when the RPC request
34  * completes. */
35 void schedule_inode_unref(RPCRequest *req, BlueSkyInode *inode)
36 {
37     struct cleanup_list *c = g_new(struct cleanup_list, 1);
38     c->func = (void (*)(void *))bluesky_inode_unref;
39     c->arg = inode;
40     c->next = req->cleanup;
41     req->cleanup = c;
42 }
43
44 /* Look up a BlueSkyInode given an NFS filehandle.  Returns NULL if the
45  * filehandle is invalid. */
46 BlueSkyInode *lookup_fh(RPCRequest *req, nfs_fh3 *fh)
47 {
48     BlueSkyInode *inode = NULL;
49     if (fh->data.data_len == 8) {
50         uint64_t inum = GUINT64_FROM_BE(*(uint64_t *)(fh->data.data_val));
51         inode = bluesky_get_inode(fs, inum);
52         if (inode != NULL)
53             schedule_inode_unref(req, inode);
54     }
55     return inode;
56 }
57
58 int64_t decode_nfstime3(nfstime3 *time)
59 {
60     int64_t result = (int64_t)time->seconds * 1000000;
61     result += time->nseconds / 1000;
62     return result;
63 }
64
65 void set_attributes(BlueSkyInode *inode, sattr3 *attributes)
66 {
67     int64_t now = bluesky_get_current_time();
68
69     if (attributes->mode.set) {
70         inode->mode = attributes->mode.set_uint32_u.val;
71     }
72
73     if (attributes->uid.set) {
74         inode->uid = attributes->uid.set_uint32_u.val;
75     }
76
77     if (attributes->gid.set) {
78         inode->gid = attributes->gid.set_uint32_u.val;
79     }
80
81     if (attributes->size.set) {
82         if (inode->type == BLUESKY_REGULAR) {
83             bluesky_file_truncate(inode, attributes->size.set_uint64_u.val);
84             inode->mtime = now;
85         }
86     }
87
88     switch (attributes->atime.set) {
89     case DONT_CHANGE:
90         break;
91     case SET_TO_SERVER_TIME:
92         inode->atime = now;
93         break;
94     case SET_TO_CLIENT_TIME:
95         inode->atime = decode_nfstime3(&attributes->atime.set_time_u.time);
96         break;
97     }
98
99     switch (attributes->mtime.set) {
100     case DONT_CHANGE:
101         break;
102     case SET_TO_SERVER_TIME:
103         inode->mtime = now;
104         break;
105     case SET_TO_CLIENT_TIME:
106         inode->mtime = decode_nfstime3(&attributes->mtime.set_time_u.time);
107         break;
108     }
109
110     bluesky_inode_update_ctime(inode, FALSE);
111 }
112
113 /* Copy inode attributes into NFS response.  The BlueSkyInode should be locked
114  * by the caller. */
115 void encode_fattr3(struct fattr3 *result, BlueSkyInode *inode)
116 {
117     result->type = inode->type;
118     result->mode = inode->mode;
119     result->nlink = inode->nlink;
120     result->uid = inode->uid;
121     result->gid = inode->gid;
122     result->size = inode->size;
123     result->used = 0;
124     result->rdev.major = 0;
125     result->rdev.minor = 0;
126     result->fsid = 0;
127     result->fileid = inode->inum;
128     result->atime.seconds = inode->atime / 1000000;
129     result->atime.nseconds = (inode->atime % 1000000) * 1000;
130     result->mtime.seconds = inode->mtime / 1000000;
131     result->mtime.nseconds = (inode->mtime % 1000000) * 1000;
132     result->ctime.seconds = inode->ctime / 1000000;
133     result->ctime.nseconds = (inode->ctime % 1000000) * 1000;
134
135     switch (inode->type) {
136     case BLUESKY_SYMLINK:
137         result->size = strlen(inode->symlink_contents);
138         break;
139     default:
140         break;
141     }
142 }
143
144 void encode_pre_wcc(struct wcc_data *wcc, BlueSkyInode *inode)
145 {
146     wcc->before.present = TRUE;
147     wcc->before.pre_op_attr_u.attributes.size = inode->size;
148     wcc->before.pre_op_attr_u.attributes.mtime.seconds = inode->mtime / 1000000;
149     wcc->before.pre_op_attr_u.attributes.mtime.nseconds = (inode->mtime % 1000000) * 1000;
150     wcc->before.pre_op_attr_u.attributes.ctime.seconds = inode->ctime / 1000000;
151     wcc->before.pre_op_attr_u.attributes.ctime.nseconds = (inode->ctime % 1000000) * 1000;
152 }
153
154 void nfsproc3_null_3_svc(void *argp, RPCRequest *req)
155 {
156     async_rpc_send_reply(req, NULL);
157 }
158
159 void nfsproc3_getattr_3_svc(nfs_fh3 *argp, RPCRequest *req)
160 {
161     getattr3res result;
162     memset(&result, 0, sizeof(result));
163
164     BlueSkyInode *inode = lookup_fh(req, argp);
165     if (inode != NULL) {
166         result.status = NFS3_OK;
167         g_mutex_lock(inode->lock);
168         encode_fattr3(&result.getattr3res_u.attributes, inode);
169         g_mutex_unlock(inode->lock);
170     } else {
171         result.status = NFS3ERR_STALE;
172     }
173
174     async_rpc_send_reply(req, &result);
175 }
176
177 void nfsproc3_setattr_3_svc(setattr3args *argp, RPCRequest *req)
178 {
179     wccstat3 result;
180     memset(&result, 0, sizeof(result));
181
182     result.wccstat3_u.wcc.before.present = FALSE;
183     result.wccstat3_u.wcc.after.present = FALSE;
184     BlueSkyInode *inode = lookup_fh(req, &argp->object);
185     if (inode == NULL) {
186         result.status = NFS3ERR_STALE;
187         async_rpc_send_reply(req, &result);
188         return;
189     }
190
191     g_mutex_lock(inode->lock);
192     encode_pre_wcc(&result.wccstat3_u.wcc, inode);
193     if (argp->guard.check) {
194         if (inode->ctime != decode_nfstime3(&argp->guard.sattrguard3_u.ctime)) {
195             result.status = NFS3ERR_NOT_SYNC;
196             result.wccstat3_u.wcc.after.present = TRUE;
197             encode_fattr3(&result.wccstat3_u.wcc.after.post_op_attr_u.attributes, inode);
198             g_mutex_unlock(inode->lock);
199             async_rpc_send_reply(req, &result);
200             return;
201         }
202     }
203
204     set_attributes(inode, &argp->new_attributes);
205
206     result.wccstat3_u.wcc.after.present = TRUE;
207     encode_fattr3(&result.wccstat3_u.wcc.after.post_op_attr_u.attributes,
208                   inode);
209     result.status = NFS3_OK;
210
211     bluesky_inode_do_sync(inode);
212
213     g_mutex_unlock(inode->lock);
214     async_rpc_send_reply(req, &result);
215 }
216
217 void nfsproc3_lookup_3_svc(diropargs3 *argp, RPCRequest *req)
218 {
219     lookup3res result;
220     memset(&result, 0, sizeof(result));
221
222     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
223     if (dir == NULL) {
224         result.status = NFS3ERR_STALE;
225         result.lookup3res_u.resfail.present = FALSE;
226         async_rpc_send_reply(req, &result);
227         return;
228     }
229
230     g_mutex_lock(dir->lock);
231     result.lookup3res_u.resfail.present = TRUE;
232     encode_fattr3(&result.lookup3res_u.resfail.post_op_attr_u.attributes, dir);
233     if (!validate_filename(argp->name)) {
234         if (strlen(argp->name) > 255)
235             result.status = NFS3ERR_NAMETOOLONG;
236         else
237             result.status = NFS3ERR_NOENT;
238         g_mutex_unlock(dir->lock);
239         async_rpc_send_reply(req, &result);
240         return;
241     }
242
243     /* TODO: Special-case "." and "..". */
244     uint64_t inum = bluesky_directory_lookup(dir, argp->name);
245     if (inum == 0) {
246         result.status = NFS3ERR_NOENT;
247         g_mutex_unlock(dir->lock);
248         async_rpc_send_reply(req, &result);
249         return;
250     }
251
252     result.lookup3res_u.resok.dir_attributes.present = TRUE;
253     encode_fattr3(&result.lookup3res_u.resok.dir_attributes.post_op_attr_u.attributes, dir);
254     g_mutex_unlock(dir->lock);
255
256     BlueSkyInode *inode = bluesky_get_inode(fs, inum);
257     if (inode == NULL) {
258         result.status = NFS3ERR_NOENT;
259         async_rpc_send_reply(req, &result);
260         return;
261     }
262     g_mutex_lock(inode->lock);
263     schedule_inode_unref(req, inode);
264
265     result.status = NFS3_OK;
266     result.lookup3res_u.resok.obj_attributes.present = TRUE;
267     encode_fattr3(&result.lookup3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
268
269     uint64_t fh_bytes;
270     fh_bytes = GUINT64_TO_BE(inum);
271     result.lookup3res_u.resok.object.data.data_len = 8;
272     result.lookup3res_u.resok.object.data.data_val = (char *)&fh_bytes;
273
274     g_mutex_unlock(inode->lock);
275     async_rpc_send_reply(req, &result);
276 }
277
278 void nfsproc3_access_3_svc(access3args *argp, RPCRequest *req)
279 {
280     access3res result;
281     memset(&result, 0, sizeof(result));
282
283     BlueSkyInode *inode = lookup_fh(req, &argp->object);
284     if (inode == NULL) {
285         result.status = NFS3ERR_STALE;
286         result.access3res_u.resfail.present = FALSE;
287         async_rpc_send_reply(req, &result);
288         return;
289     }
290
291     g_mutex_lock(inode->lock);
292     result.status = NFS3_OK;
293     result.access3res_u.resok.obj_attributes.present = TRUE;
294     encode_fattr3(&result.access3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
295     result.access3res_u.resok.access = argp->access;
296     g_mutex_unlock(inode->lock);
297
298     async_rpc_send_reply(req, &result);
299 }
300
301 void nfsproc3_readlink_3_svc(nfs_fh3 *argp, RPCRequest *req)
302 {
303     readlink3res result;
304     memset(&result, 0, sizeof(result));
305
306     BlueSkyInode *inode = lookup_fh(req, argp);
307     if (inode != NULL) {
308         g_mutex_lock(inode->lock);
309         if (inode->type == BLUESKY_SYMLINK) {
310             result.status = NFS3_OK;
311             result.readlink3res_u.resok.symlink_attributes.present = TRUE;
312             encode_fattr3(&result.readlink3res_u.resok.symlink_attributes.post_op_attr_u.attributes, inode);
313             result.readlink3res_u.resok.data = inode->symlink_contents;
314         } else {
315             result.status = NFS3ERR_INVAL;
316             result.readlink3res_u.resfail.present = TRUE;
317             encode_fattr3(&result.readlink3res_u.resfail.post_op_attr_u.attributes, inode);
318         }
319         g_mutex_unlock(inode->lock);
320     } else {
321         result.status = NFS3ERR_STALE;
322     }
323
324     async_rpc_send_reply(req, &result);
325 }
326
327 void nfsproc3_read_3_svc(read3args *argp, RPCRequest *req)
328 {
329     read3res result;
330     memset(&result, 0, sizeof(result));
331     char buf[NFS_MAXSIZE];
332
333     bluesky_flushd_invoke_conditional(fs);
334
335     BlueSkyInode *inode = lookup_fh(req, &argp->file);
336     if (inode == NULL) {
337         result.status = NFS3ERR_STALE;
338         result.read3res_u.resfail.present = FALSE;
339         async_rpc_send_reply(req, &result);
340         return;
341     }
342
343     g_mutex_lock(inode->lock);
344
345     int count = argp->count;
346     if (argp->offset >= inode->size) {
347         count = 0;
348         result.read3res_u.resok.eof = TRUE;
349     } else {
350         count = MIN(count, NFS_MAXSIZE);
351         count = MIN(count, inode->size - argp->offset);
352         if (argp->offset + count == inode->size)
353             result.read3res_u.resok.eof = TRUE;
354         else
355             result.read3res_u.resok.eof = FALSE;
356
357         bluesky_file_read(inode, argp->offset, buf, count);
358     }
359
360     result.status = NFS3_OK;
361     result.read3res_u.resok.file_attributes.present = TRUE;
362     encode_fattr3(&result.read3res_u.resok.file_attributes.post_op_attr_u.attributes, inode);
363     result.read3res_u.resok.count = count;
364     result.read3res_u.resok.data.data_val = buf;
365     result.read3res_u.resok.data.data_len = count;
366
367     g_mutex_unlock(inode->lock);
368
369     async_rpc_send_reply(req, &result);
370 }
371
372 void nfsproc3_write_3_svc(write3args *argp, RPCRequest *req)
373 {
374     write3res result;
375     memset(&result, 0, sizeof(result));
376     struct wcc_data wcc;
377     memset(&wcc, 0, sizeof(wcc));
378
379     bluesky_flushd_invoke_conditional(fs);
380
381     BlueSkyInode *inode = lookup_fh(req, &argp->file);
382     if (inode == NULL) {
383         result.status = NFS3ERR_STALE;
384         result.write3res_u.resfail = wcc;
385         async_rpc_send_reply(req, &result);
386         return;
387     }
388
389 #if 0
390     /* FIXME: Hack to throttle writes when there is too much dirty data still
391      * to be written out. */
392     while (g_atomic_int_get(&fs->cache_dirty) > 4096
393            || g_atomic_int_get(&fs->cache_total) > 8192) {
394         g_print("Too many dirty pages (%d) or total pages (%d); throttling writes...\n",
395                 g_atomic_int_get(&fs->cache_dirty),
396                 g_atomic_int_get(&fs->cache_total));
397         struct timespec delay;
398         delay.tv_sec = 2;
399         delay.tv_nsec = 0;
400         nanosleep(&delay, NULL);
401     }
402 #endif
403
404     g_mutex_lock(inode->lock);
405
406     encode_pre_wcc(&wcc, inode);
407     if (inode->type != BLUESKY_REGULAR) {
408         result.status = NFS3ERR_INVAL;
409         result.write3res_u.resfail = wcc;
410         g_mutex_unlock(inode->lock);
411         async_rpc_send_reply(req, &result);
412         return;
413     }
414
415     uint64_t lastbyte = argp->offset + argp->count;
416     if (lastbyte > inode->size) {
417         bluesky_file_truncate(inode, lastbyte);
418     }
419
420     if (argp->data.data_len < argp->count) {
421         /* ??? */
422     } else {
423         bluesky_file_write(inode, argp->offset,
424                            argp->data.data_val, argp->count);
425     }
426
427     wcc.after.present = TRUE;
428     encode_fattr3(&wcc.after.post_op_attr_u.attributes, inode);
429     result.write3res_u.resok.file_wcc = wcc;
430     result.write3res_u.resok.count = argp->count;
431     result.write3res_u.resok.committed = FILE_SYNC;
432
433     bluesky_inode_do_sync(inode);
434     g_mutex_unlock(inode->lock);
435
436     async_rpc_send_reply(req, &result);
437 }
438
439 void nfsproc3_create_3_svc(create3args *argp, RPCRequest *req)
440 {
441     diropres3 result;
442     memset(&result, 0, sizeof(result));
443     struct wcc_data wcc;
444     memset(&wcc, 0, sizeof(wcc));
445
446     BlueSkyInode *dir = lookup_fh(req, &argp->where.dir);
447     if (dir == NULL) {
448         result.status = NFS3ERR_STALE;
449         result.diropres3_u.resfail = wcc;
450         async_rpc_send_reply(req, &result);
451         return;
452     }
453
454     g_mutex_lock(dir->lock);
455
456     encode_pre_wcc(&wcc, dir);
457     if (dir->type != BLUESKY_DIRECTORY) {
458         result.status = NFS3ERR_NOTDIR;
459         result.diropres3_u.resfail = wcc;
460         g_mutex_unlock(dir->lock);
461         async_rpc_send_reply(req, &result);
462         return;
463     }
464
465     if (!validate_filename(argp->where.name)
466         || strcmp(argp->where.name, ".") == 0
467         || strcmp(argp->where.name, "..") == 0)
468     {
469         result.status = NFS3ERR_EXIST;
470         result.diropres3_u.resfail = wcc;
471         g_mutex_unlock(dir->lock);
472         async_rpc_send_reply(req, &result);
473         return;
474     }
475
476     g_mutex_lock(fs->lock);
477     BlueSkyInode *file;
478     file = bluesky_new_inode(bluesky_fs_alloc_inode(fs), fs, BLUESKY_REGULAR);
479     file->nlink = 1;
480     file->mode = 0755;
481     int64_t time = bluesky_get_current_time();
482     file->mtime = time;
483     file->ctime = time;
484     file->atime = time;
485     file->ntime = time;
486     g_mutex_lock(file->lock);
487     bluesky_insert_inode(fs, file);
488     g_mutex_unlock(fs->lock);
489     bluesky_directory_insert(dir, argp->where.name, file->inum);
490
491     bluesky_inode_update_ctime(dir, TRUE);
492
493     wcc.after.present = TRUE;
494     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
495     result.diropres3_u.resok.obj_attributes.present = TRUE;
496     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
497     result.diropres3_u.resok.dir_wcc = wcc;
498
499     uint64_t fh_bytes;
500     fh_bytes = GUINT64_TO_BE(file->inum);
501     result.diropres3_u.resok.obj.present = TRUE;
502     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
503     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
504
505     bluesky_inode_do_sync(file);
506     bluesky_inode_do_sync(dir);
507     g_mutex_unlock(file->lock);
508     g_mutex_unlock(dir->lock);
509
510     async_rpc_send_reply(req, &result);
511 }
512
513 void nfsproc3_mkdir_3_svc(mkdir3args *argp, RPCRequest *req)
514 {
515     diropres3 result;
516     memset(&result, 0, sizeof(result));
517     struct wcc_data wcc;
518     memset(&wcc, 0, sizeof(wcc));
519
520     BlueSkyInode *dir = lookup_fh(req, &argp->where.dir);
521     if (dir == NULL) {
522         result.status = NFS3ERR_STALE;
523         result.diropres3_u.resfail = wcc;
524         async_rpc_send_reply(req, &result);
525         return;
526     }
527
528     g_mutex_lock(dir->lock);
529
530     encode_pre_wcc(&wcc, dir);
531     if (dir->type != BLUESKY_DIRECTORY) {
532         result.status = NFS3ERR_NOTDIR;
533         result.diropres3_u.resfail = wcc;
534         g_mutex_unlock(dir->lock);
535         async_rpc_send_reply(req, &result);
536         return;
537     }
538
539     if (!validate_filename(argp->where.name)
540         || strcmp(argp->where.name, ".") == 0
541         || strcmp(argp->where.name, "..") == 0)
542     {
543         result.status = NFS3ERR_EXIST;
544         result.diropres3_u.resfail = wcc;
545         g_mutex_unlock(dir->lock);
546         async_rpc_send_reply(req, &result);
547         return;
548     }
549
550     g_mutex_lock(fs->lock);
551     BlueSkyInode *file;
552     file = bluesky_new_inode(bluesky_fs_alloc_inode(fs), fs, BLUESKY_DIRECTORY);
553     file->nlink = 1;
554     file->mode = 0755;
555     int64_t time = bluesky_get_current_time();
556     file->mtime = time;
557     file->ctime = time;
558     file->atime = time;
559     file->ntime = time;
560     g_mutex_lock(file->lock);
561     bluesky_insert_inode(fs, file);
562     g_mutex_unlock(fs->lock);
563     bluesky_directory_insert(dir, argp->where.name, file->inum);
564     set_attributes(file, &argp->attributes);
565
566     bluesky_inode_update_ctime(dir, TRUE);
567
568     wcc.after.present = TRUE;
569     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
570     result.diropres3_u.resok.obj_attributes.present = TRUE;
571     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
572     result.diropres3_u.resok.dir_wcc = wcc;
573
574     uint64_t fh_bytes;
575     fh_bytes = GUINT64_TO_BE(file->inum);
576     result.diropres3_u.resok.obj.present = TRUE;
577     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
578     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
579
580     bluesky_inode_do_sync(file);
581     bluesky_inode_do_sync(dir);
582     g_mutex_unlock(file->lock);
583     g_mutex_unlock(dir->lock);
584     async_rpc_send_reply(req, &result);
585 }
586
587 void nfsproc3_symlink_3_svc(symlink3args *argp, RPCRequest *req)
588 {
589     diropres3 result;
590     memset(&result, 0, sizeof(result));
591     struct wcc_data wcc;
592     memset(&wcc, 0, sizeof(wcc));
593
594     BlueSkyInode *dir = lookup_fh(req, &argp->where.dir);
595     if (dir == NULL) {
596         result.status = NFS3ERR_STALE;
597         result.diropres3_u.resfail = wcc;
598         async_rpc_send_reply(req, &result);
599         return;
600     }
601     g_mutex_lock(dir->lock);
602
603     encode_pre_wcc(&wcc, dir);
604     if (dir->type != BLUESKY_DIRECTORY) {
605         result.status = NFS3ERR_NOTDIR;
606         result.diropres3_u.resfail = wcc;
607         g_mutex_unlock(dir->lock);
608         async_rpc_send_reply(req, &result);
609         return;
610     }
611
612     if (!validate_filename(argp->where.name)
613         || strcmp(argp->where.name, ".") == 0
614         || strcmp(argp->where.name, "..") == 0)
615     {
616         result.status = NFS3ERR_EXIST;
617         result.diropres3_u.resfail = wcc;
618         g_mutex_unlock(dir->lock);
619         async_rpc_send_reply(req, &result);
620         return;
621     }
622
623     g_mutex_lock(fs->lock);
624     BlueSkyInode *file;
625     file = bluesky_new_inode(bluesky_fs_alloc_inode(fs), fs, BLUESKY_SYMLINK);
626     file->nlink = 1;
627     file->mode = 0755;
628     int64_t time = bluesky_get_current_time();
629     file->mtime = time;
630     file->ctime = time;
631     file->atime = time;
632     file->ntime = time;
633     file->symlink_contents = g_strdup(argp->symlink.symlink_data);
634     g_mutex_lock(file->lock);
635     bluesky_insert_inode(fs, file);
636     g_mutex_unlock(fs->lock);
637     bluesky_directory_insert(dir, argp->where.name, file->inum);
638
639     bluesky_inode_update_ctime(dir, TRUE);
640
641     wcc.after.present = TRUE;
642     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
643     result.diropres3_u.resok.obj_attributes.present = TRUE;
644     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
645     result.diropres3_u.resok.dir_wcc = wcc;
646
647     uint64_t fh_bytes;
648     fh_bytes = GUINT64_TO_BE(file->inum);
649     result.diropres3_u.resok.obj.present = TRUE;
650     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
651     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
652
653     bluesky_inode_do_sync(file);
654     bluesky_inode_do_sync(dir);
655     g_mutex_unlock(file->lock);
656     g_mutex_unlock(dir->lock);
657     async_rpc_send_reply(req, &result);
658 }
659
660 void nfsproc3_mknod_3_svc(mknod3args *argp, RPCRequest *req)
661 {
662     diropres3 result;
663     memset(&result, 0, sizeof(result));
664
665     result.status = NFS3ERR_NOTSUPP;
666
667     async_rpc_send_reply(req, &result);
668 }
669
670 void nfsproc3_remove_3_svc(diropargs3 *argp, RPCRequest *req)
671 {
672     wccstat3 result;
673     memset(&result, 0, sizeof(result));
674
675     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
676     if (dir == NULL) {
677         result.status = NFS3ERR_STALE;
678         async_rpc_send_reply(req, &result);
679         return;
680     }
681
682     g_mutex_lock(dir->lock);
683
684     encode_pre_wcc(&result.wccstat3_u.wcc, dir);
685
686     if (!validate_filename(argp->name)
687         || strcmp(argp->name, ".") == 0
688         || strcmp(argp->name, "..") == 0)
689     {
690         result.status = NFS3ERR_NOENT;
691         g_mutex_unlock(dir->lock);
692         async_rpc_send_reply(req, &result);
693         return;
694     }
695
696     /* TODO: Decrement link count, deallocate inode if needed. */
697
698     bluesky_directory_remove(dir, argp->name);
699
700     result.status = NFS3_OK;
701     result.wccstat3_u.wcc.after.present = TRUE;
702     encode_fattr3(&result.wccstat3_u.wcc.after.post_op_attr_u.attributes,
703                   dir);
704
705     bluesky_inode_do_sync(dir);
706     g_mutex_unlock(dir->lock);
707     async_rpc_send_reply(req, &result);
708 }
709
710 void nfsproc3_rmdir_3_svc(diropargs3 *argp, RPCRequest *req)
711 {
712     wccstat3 result;
713     memset(&result, 0, sizeof(result));
714
715     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
716     if (dir == NULL) {
717         result.status = NFS3ERR_STALE;
718         async_rpc_send_reply(req, &result);
719         return;
720     }
721
722     g_mutex_lock(dir->lock);
723
724     encode_pre_wcc(&result.wccstat3_u.wcc, dir);
725
726     if (!validate_filename(argp->name)
727         || strcmp(argp->name, ".") == 0
728         || strcmp(argp->name, "..") == 0)
729     {
730         result.status = NFS3ERR_NOENT;
731         g_mutex_unlock(dir->lock);
732         async_rpc_send_reply(req, &result);
733         return;
734     }
735
736     uint64_t inum = bluesky_directory_lookup(dir, argp->name);
737     BlueSkyInode *inode = bluesky_get_inode(fs, inum);
738     if (inode == NULL) {
739         result.status = NFS3ERR_NOENT;
740         g_mutex_unlock(dir->lock);
741         async_rpc_send_reply(req, &result);
742         return;
743     }
744     g_mutex_lock(inode->lock);
745     schedule_inode_unref(req, inode);
746
747     if (inode->type != BLUESKY_DIRECTORY) {
748         result.status = NFS3ERR_NOTDIR;
749         g_mutex_unlock(inode->lock);
750         g_mutex_unlock(dir->lock);
751         async_rpc_send_reply(req, &result);
752         return;
753     }
754     if (g_sequence_get_length(inode->dirents) > 0) {
755         printf("Directory not empty: %d entries\n",
756                g_sequence_get_length(inode->dirents));
757         result.status = NFS3ERR_NOTEMPTY;
758         g_mutex_unlock(inode->lock);
759         g_mutex_unlock(dir->lock);
760         async_rpc_send_reply(req, &result);
761         return;
762     }
763
764     /* TODO: Decrement link count, deallocate inode if needed. */
765
766     bluesky_directory_remove(dir, argp->name);
767
768     result.status = NFS3_OK;
769     result.wccstat3_u.wcc.after.present = TRUE;
770     encode_fattr3(&result.wccstat3_u.wcc.after.post_op_attr_u.attributes,
771                   dir);
772
773     bluesky_inode_do_sync(dir);
774     bluesky_inode_do_sync(inode);
775     g_mutex_unlock(inode->lock);
776     g_mutex_unlock(dir->lock);
777     async_rpc_send_reply(req, &result);
778 }
779
780 void nfsproc3_rename_3_svc(rename3args *argp, RPCRequest *req)
781 {
782     rename3res result;
783     memset(&result, 0, sizeof(result));
784     wcc_data *wcc1 = &result.rename3res_u.res.fromdir_wcc;
785     wcc_data *wcc2 = &result.rename3res_u.res.todir_wcc;
786
787     BlueSkyInode *dir1 = lookup_fh(req, &argp->from.dir);
788     if (dir1 == NULL) {
789         result.status = NFS3ERR_STALE;
790         async_rpc_send_reply(req, &result);
791         return;
792     }
793
794     BlueSkyInode *dir2 = lookup_fh(req, &argp->to.dir);
795     if (dir2 == NULL) {
796         result.status = NFS3ERR_STALE;
797         async_rpc_send_reply(req, &result);
798         return;
799     }
800
801     if (dir1->inum < dir2->inum) {
802         g_mutex_lock(dir1->lock);
803         g_mutex_lock(dir2->lock);
804     } else if (dir1->inum > dir2->inum) {
805         g_mutex_lock(dir2->lock);
806         g_mutex_lock(dir1->lock);
807     }
808     encode_pre_wcc(wcc1, dir1);
809     encode_pre_wcc(wcc2, dir1);
810
811     gboolean status = bluesky_rename(dir1, argp->from.name,
812                                      dir2, argp->to.name,
813                                      TRUE, TRUE);
814
815     wcc1->after.present = TRUE;
816     encode_fattr3(&wcc1->after.post_op_attr_u.attributes, dir1);
817     wcc2->after.present = TRUE;
818     encode_fattr3(&wcc2->after.post_op_attr_u.attributes, dir2);
819     if (status)
820         result.status = NFS3_OK;
821     else
822         result.status = NFS3ERR_PERM;
823
824     bluesky_inode_do_sync(dir2);
825     bluesky_inode_do_sync(dir1);
826
827     g_mutex_unlock(dir1->lock);
828     if (dir1->inum != dir2->inum)
829         g_mutex_unlock(dir2->lock);
830     async_rpc_send_reply(req, &result);
831 }
832
833 void nfsproc3_link_3_svc(link3args *argp, RPCRequest *req)
834 {
835     link3res result;
836     memset(&result, 0, sizeof(result));
837     struct wcc_data wcc;
838     memset(&wcc, 0, sizeof(wcc));
839
840     BlueSkyInode *inode = lookup_fh(req, &argp->file);
841     if (inode == NULL) {
842         result.status = NFS3ERR_STALE;
843         result.link3res_u.res.linkdir_wcc = wcc;
844         async_rpc_send_reply(req, &result);
845         return;
846     }
847     g_mutex_lock(inode->lock);
848
849     BlueSkyInode *dir = lookup_fh(req, &argp->link.dir);
850     if (dir == NULL) {
851         result.status = NFS3ERR_STALE;
852         result.link3res_u.res.linkdir_wcc = wcc;
853         g_mutex_unlock(inode->lock);
854         async_rpc_send_reply(req, &result);
855         return;
856     }
857     g_mutex_lock(dir->lock);
858
859     encode_pre_wcc(&wcc, dir);
860     if (dir->type != BLUESKY_DIRECTORY) {
861         result.status = NFS3ERR_NOTDIR;
862         result.link3res_u.res.linkdir_wcc = wcc;
863         g_mutex_unlock(inode->lock);
864         g_mutex_unlock(dir->lock);
865         async_rpc_send_reply(req, &result);
866         return;
867     }
868
869     if (!validate_filename(argp->link.name)
870         || strcmp(argp->link.name, ".") == 0
871         || strcmp(argp->link.name, "..") == 0
872         || bluesky_directory_lookup(dir, argp->link.name) != 0)
873     {
874         result.status = NFS3ERR_EXIST;
875         result.link3res_u.res.linkdir_wcc = wcc;
876         g_mutex_unlock(inode->lock);
877         g_mutex_unlock(dir->lock);
878         async_rpc_send_reply(req, &result);
879         return;
880     }
881
882     if (!bluesky_directory_insert(dir, argp->link.name, inode->inum)) {
883         result.status = NFS3ERR_EXIST;
884         result.link3res_u.res.linkdir_wcc = wcc;
885         g_mutex_unlock(inode->lock);
886         g_mutex_unlock(dir->lock);
887         async_rpc_send_reply(req, &result);
888         return;
889     }
890     inode->nlink++;
891     bluesky_inode_update_ctime(inode, FALSE);
892     bluesky_inode_update_ctime(dir, TRUE);
893
894     result.status = NFS3_OK;
895     wcc.after.present = TRUE;
896     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
897     result.link3res_u.res.file_attributes.present = TRUE;
898     encode_fattr3(&result.link3res_u.res.file_attributes.post_op_attr_u.attributes, inode);
899     result.link3res_u.res.linkdir_wcc = wcc;
900
901     bluesky_inode_do_sync(inode);
902     bluesky_inode_do_sync(dir);
903     g_mutex_unlock(inode->lock);
904     g_mutex_unlock(dir->lock);
905     async_rpc_send_reply(req, &result);
906 }
907
908 gint bluesky_dirent_compare(gconstpointer a, gconstpointer b,
909                             gpointer unused);
910
911 #define MAX_READDIR_DIRENTS 64
912 void nfsproc3_readdir_3_svc(readdir3args *argp, RPCRequest *req)
913 {
914     readdir3res result;
915     memset(&result, 0, sizeof(result));
916
917     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
918     if (dir == NULL) {
919         result.status = NFS3ERR_STALE;
920         result.readdir3res_u.resfail.present = FALSE;
921         async_rpc_send_reply(req, &result);
922         return;
923     }
924     g_mutex_lock(dir->lock);
925
926     result.status = NFS3_OK;
927     result.readdir3res_u.resok.dir_attributes.present = TRUE;
928     encode_fattr3(&result.readdir3res_u.resok.dir_attributes.post_op_attr_u.attributes, dir);
929     memset(result.readdir3res_u.resok.cookieverf, 0,
930            sizeof(result.readdir3res_u.resok.cookieverf));
931
932     entry3 dirents[MAX_READDIR_DIRENTS];
933     int count = 0;
934
935     BlueSkyDirent start = {NULL, NULL, argp->cookie, 0};
936     GSequenceIter *i = g_sequence_search(dir->dirents, &start,
937                                          bluesky_dirent_compare, NULL);
938
939     while (count < MAX_READDIR_DIRENTS && !g_sequence_iter_is_end(i)) {
940         BlueSkyDirent *d = g_sequence_get(i);
941         dirents[count].fileid = d->inum;
942         dirents[count].name = d->name;
943         dirents[count].cookie = d->cookie;
944         dirents[count].nextentry = NULL;
945         if (count > 0)
946             dirents[count - 1].nextentry = &dirents[count];
947         i = g_sequence_iter_next(i);
948         count++;
949     }
950
951     if (count > 0)
952         result.readdir3res_u.resok.reply.entries = &dirents[0];
953     else
954         result.readdir3res_u.resok.reply.entries = NULL;
955     result.readdir3res_u.resok.reply.eof = g_sequence_iter_is_end(i);
956
957     g_mutex_unlock(dir->lock);
958     async_rpc_send_reply(req, &result);
959 }
960
961 void nfsproc3_readdirplus_3_svc(readdirplus3args *argp, RPCRequest *req)
962 {
963     /* XDR-encoded sizes:
964      *   post_op_attr: 88 bytes
965      *   base readdirplus3resok: 88 + 16 bytes
966      *   base directory entry: 24 bytes + filename
967      *   attributes/fh3: 88 + 8 + filehandle size
968      */
969     size_t dircount = 88 + 16, attrcount = 0;
970     readdirplus3res result;
971     memset(&result, 0, sizeof(result));
972
973     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
974     if (dir == NULL) {
975         result.status = NFS3ERR_STALE;
976         result.readdirplus3res_u.resfail.present = FALSE;
977         async_rpc_send_reply(req, &result);
978         return;
979     }
980     g_mutex_lock(dir->lock);
981
982     result.status = NFS3_OK;
983     result.readdirplus3res_u.resok.dir_attributes.present = TRUE;
984     encode_fattr3(&result.readdirplus3res_u.resok.dir_attributes.post_op_attr_u.attributes, dir);
985     memset(result.readdirplus3res_u.resok.cookieverf, 0,
986            sizeof(result.readdirplus3res_u.resok.cookieverf));
987
988     entryplus3 dirents[MAX_READDIR_DIRENTS];
989     uint64_t fh_bytes[MAX_READDIR_DIRENTS];
990     int count = 0;
991
992     GSequenceIter *i;
993     BlueSkyDirent start = {NULL, NULL, argp->cookie, 0};
994
995     /* Perform a prefetch pass on inodes: for all the inodes we think we will
996      * return information about, try to load each one but don't wait.  This
997      * should let multiple inodes be fetched in parallel, instead of
998      * sequentially in the loop that follows. */
999     i = g_sequence_search(dir->dirents, &start, bluesky_dirent_compare, NULL);
1000     while (count < MAX_READDIR_DIRENTS
1001            && !g_sequence_iter_is_end(i)
1002            && dircount <= argp->dircount
1003            && dircount + attrcount <= argp->maxcount)
1004     {
1005         BlueSkyDirent *d = g_sequence_get(i);
1006         BlueSkyInode *inode = bluesky_get_inode(fs, d->inum);
1007         if (inode != NULL)
1008             bluesky_inode_unref(inode);
1009         dircount += 24 + ((strlen(d->name) + 3) & ~3);
1010         attrcount += 88 + 8 + 8;
1011         i = g_sequence_iter_next(i);
1012     }
1013
1014     i = g_sequence_search(dir->dirents, &start, bluesky_dirent_compare, NULL);
1015     count = 0;
1016     dircount = 88 + 16;
1017     attrcount = 0;
1018     while (count < MAX_READDIR_DIRENTS && !g_sequence_iter_is_end(i)) {
1019         BlueSkyDirent *d = g_sequence_get(i);
1020         BlueSkyInode *inode = bluesky_get_inode(fs, d->inum);
1021         if (inode != NULL) {
1022             g_mutex_lock(inode->lock);
1023             dircount += 24 + ((strlen(d->name) + 3) & ~3);
1024             attrcount += 88 + 8 + 8;
1025             if (dircount > argp->dircount
1026                 || dircount + attrcount > argp->maxcount)
1027             {
1028                 g_mutex_unlock(inode->lock);
1029                 bluesky_inode_unref(inode);
1030                 break;
1031             }
1032             dirents[count].fileid = d->inum;
1033             dirents[count].name = d->name;
1034             dirents[count].cookie = d->cookie;
1035             dirents[count].nextentry = NULL;
1036             dirents[count].name_attributes.present = TRUE;
1037             encode_fattr3(&dirents[count].name_attributes.post_op_attr_u.attributes, inode);
1038             fh_bytes[count] = GUINT64_TO_BE(d->inum);
1039             dirents[count].name_handle.present = TRUE;
1040             dirents[count].name_handle.post_op_fh3_u.handle.data.data_len = 8;
1041             dirents[count].name_handle.post_op_fh3_u.handle.data.data_val
1042                 = (char *)&fh_bytes[count];
1043             if (count > 0)
1044                 dirents[count - 1].nextentry = &dirents[count];
1045             count++;
1046             g_mutex_unlock(inode->lock);
1047             bluesky_inode_unref(inode);
1048         }
1049         i = g_sequence_iter_next(i);
1050     }
1051
1052     if (count > 0)
1053         result.readdirplus3res_u.resok.reply.entries = &dirents[0];
1054     else
1055         result.readdirplus3res_u.resok.reply.entries = NULL;
1056     result.readdirplus3res_u.resok.reply.eof = g_sequence_iter_is_end(i);
1057
1058     g_mutex_unlock(dir->lock);
1059     async_rpc_send_reply(req, &result);
1060 }
1061
1062 void nfsproc3_fsstat_3_svc(nfs_fh3 *argp, RPCRequest *req)
1063 {
1064     fsstat3res result;
1065     memset(&result, 0, sizeof(result));
1066
1067     BlueSkyInode *inode = lookup_fh(req, argp);
1068     if (inode == NULL) {
1069         result.status = NFS3ERR_STALE;
1070         result.fsstat3res_u.resfail.present = FALSE;
1071         async_rpc_send_reply(req, &result);
1072         return;
1073     }
1074     g_mutex_lock(inode->lock);
1075
1076     result.status = NFS3_OK;
1077     result.fsstat3res_u.resok.obj_attributes.present = TRUE;
1078     encode_fattr3(&result.fsstat3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1079
1080     result.fsstat3res_u.resok.tbytes = (1 << 30);
1081     result.fsstat3res_u.resok.fbytes = (1 << 30);
1082     result.fsstat3res_u.resok.abytes = (1 << 30);
1083     result.fsstat3res_u.resok.tfiles = 0;
1084     result.fsstat3res_u.resok.ffiles = 0;
1085     result.fsstat3res_u.resok.afiles = 0;
1086     result.fsstat3res_u.resok.invarsec = 0;
1087
1088     g_mutex_unlock(inode->lock);
1089     async_rpc_send_reply(req, &result);
1090 }
1091
1092 void nfsproc3_fsinfo_3_svc(nfs_fh3 *argp, RPCRequest *req)
1093 {
1094     fsinfo3res result;
1095     memset(&result, 0, sizeof(result));
1096
1097     BlueSkyInode *inode = bluesky_get_inode(fs, 1);
1098     g_mutex_lock(inode->lock);
1099     result.status = NFS3_OK;
1100     result.fsinfo3res_u.resok.obj_attributes.present = TRUE;
1101     encode_fattr3(&result.fsinfo3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1102     result.fsinfo3res_u.resok.rtmax = NFS_MAXSIZE;
1103     result.fsinfo3res_u.resok.rtpref = NFS_MAXSIZE;
1104     result.fsinfo3res_u.resok.rtmult = NFS_BLOCKSIZE;
1105     result.fsinfo3res_u.resok.wtmax = NFS_MAXSIZE;
1106     result.fsinfo3res_u.resok.wtpref = NFS_MAXSIZE;
1107     result.fsinfo3res_u.resok.wtmult = NFS_BLOCKSIZE;
1108     result.fsinfo3res_u.resok.dtpref = NFS_BLOCKSIZE;
1109     result.fsinfo3res_u.resok.maxfilesize = 0x7fffffffffffffffULL;
1110     result.fsinfo3res_u.resok.time_delta.seconds = 0;
1111     result.fsinfo3res_u.resok.time_delta.nseconds = 1000;
1112     result.fsinfo3res_u.resok.properties
1113         = FSF3_LINK | FSF3_SYMLINK | FSF3_HOMOGENEOUS | FSF3_CANSETTIME;
1114
1115     g_mutex_unlock(inode->lock);
1116     bluesky_inode_unref(inode);
1117     async_rpc_send_reply(req, &result);
1118 }
1119
1120 void nfsproc3_pathconf_3_svc(nfs_fh3 *argp, RPCRequest *req)
1121 {
1122     pathconf3res result;
1123     memset(&result, 0, sizeof(result));
1124
1125     BlueSkyInode *inode = bluesky_get_inode(fs, 1);
1126     g_mutex_lock(inode->lock);
1127     result.status = NFS3_OK;
1128     result.pathconf3res_u.resok.obj_attributes.present = TRUE;
1129     encode_fattr3(&result.pathconf3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1130     result.pathconf3res_u.resok.linkmax = 0xffffffff;
1131     result.pathconf3res_u.resok.name_max = 255;
1132     result.pathconf3res_u.resok.no_trunc = TRUE;
1133     result.pathconf3res_u.resok.chown_restricted = TRUE;
1134     result.pathconf3res_u.resok.case_insensitive = FALSE;
1135     result.pathconf3res_u.resok.case_preserving = TRUE;
1136
1137     g_mutex_unlock(inode->lock);
1138     bluesky_inode_unref(inode);
1139     async_rpc_send_reply(req, &result);
1140 }
1141
1142 void nfsproc3_commit_3_svc(commit3args *argp, RPCRequest *req)
1143 {
1144     commit3res result;
1145     memset(&result, 0, sizeof(result));
1146
1147     result.status = NFS3_OK;
1148
1149     BlueSkyInode *inode = lookup_fh(req, &argp->file);
1150     if (inode == NULL) {
1151         result.status = NFS3ERR_STALE;
1152         async_rpc_send_reply(req, &result);
1153         return;
1154     }
1155
1156     g_mutex_lock(inode->lock);
1157     encode_pre_wcc(&result.commit3res_u.resok.file_wcc, inode);
1158
1159     bluesky_inode_do_sync(inode);
1160
1161     result.commit3res_u.resok.file_wcc.after.present = TRUE;
1162     encode_fattr3(&result.commit3res_u.resok.file_wcc.after.post_op_attr_u.attributes, inode);
1163
1164     g_mutex_unlock(inode->lock);
1165
1166     async_rpc_send_reply(req, &result);
1167 }