Newly-created inodes should be marked as modified.
[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     bluesky_inode_update_ctime(file, FALSE);
493
494     wcc.after.present = TRUE;
495     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
496     result.diropres3_u.resok.obj_attributes.present = TRUE;
497     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
498     result.diropres3_u.resok.dir_wcc = wcc;
499
500     uint64_t fh_bytes;
501     fh_bytes = GUINT64_TO_BE(file->inum);
502     result.diropres3_u.resok.obj.present = TRUE;
503     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
504     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
505
506     bluesky_inode_do_sync(file);
507     bluesky_inode_do_sync(dir);
508     g_mutex_unlock(file->lock);
509     g_mutex_unlock(dir->lock);
510
511     async_rpc_send_reply(req, &result);
512 }
513
514 void nfsproc3_mkdir_3_svc(mkdir3args *argp, RPCRequest *req)
515 {
516     diropres3 result;
517     memset(&result, 0, sizeof(result));
518     struct wcc_data wcc;
519     memset(&wcc, 0, sizeof(wcc));
520
521     BlueSkyInode *dir = lookup_fh(req, &argp->where.dir);
522     if (dir == NULL) {
523         result.status = NFS3ERR_STALE;
524         result.diropres3_u.resfail = wcc;
525         async_rpc_send_reply(req, &result);
526         return;
527     }
528
529     g_mutex_lock(dir->lock);
530
531     encode_pre_wcc(&wcc, dir);
532     if (dir->type != BLUESKY_DIRECTORY) {
533         result.status = NFS3ERR_NOTDIR;
534         result.diropres3_u.resfail = wcc;
535         g_mutex_unlock(dir->lock);
536         async_rpc_send_reply(req, &result);
537         return;
538     }
539
540     if (!validate_filename(argp->where.name)
541         || strcmp(argp->where.name, ".") == 0
542         || strcmp(argp->where.name, "..") == 0)
543     {
544         result.status = NFS3ERR_EXIST;
545         result.diropres3_u.resfail = wcc;
546         g_mutex_unlock(dir->lock);
547         async_rpc_send_reply(req, &result);
548         return;
549     }
550
551     g_mutex_lock(fs->lock);
552     BlueSkyInode *file;
553     file = bluesky_new_inode(bluesky_fs_alloc_inode(fs), fs, BLUESKY_DIRECTORY);
554     file->nlink = 1;
555     file->mode = 0755;
556     int64_t time = bluesky_get_current_time();
557     file->mtime = time;
558     file->ctime = time;
559     file->atime = time;
560     file->ntime = time;
561     g_mutex_lock(file->lock);
562     bluesky_insert_inode(fs, file);
563     g_mutex_unlock(fs->lock);
564     bluesky_directory_insert(dir, argp->where.name, file->inum);
565     set_attributes(file, &argp->attributes);
566
567     bluesky_inode_update_ctime(dir, TRUE);
568     bluesky_inode_update_ctime(file, FALSE);
569
570     wcc.after.present = TRUE;
571     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
572     result.diropres3_u.resok.obj_attributes.present = TRUE;
573     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
574     result.diropres3_u.resok.dir_wcc = wcc;
575
576     uint64_t fh_bytes;
577     fh_bytes = GUINT64_TO_BE(file->inum);
578     result.diropres3_u.resok.obj.present = TRUE;
579     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
580     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
581
582     bluesky_inode_do_sync(file);
583     bluesky_inode_do_sync(dir);
584     g_mutex_unlock(file->lock);
585     g_mutex_unlock(dir->lock);
586     async_rpc_send_reply(req, &result);
587 }
588
589 void nfsproc3_symlink_3_svc(symlink3args *argp, RPCRequest *req)
590 {
591     diropres3 result;
592     memset(&result, 0, sizeof(result));
593     struct wcc_data wcc;
594     memset(&wcc, 0, sizeof(wcc));
595
596     BlueSkyInode *dir = lookup_fh(req, &argp->where.dir);
597     if (dir == NULL) {
598         result.status = NFS3ERR_STALE;
599         result.diropres3_u.resfail = wcc;
600         async_rpc_send_reply(req, &result);
601         return;
602     }
603     g_mutex_lock(dir->lock);
604
605     encode_pre_wcc(&wcc, dir);
606     if (dir->type != BLUESKY_DIRECTORY) {
607         result.status = NFS3ERR_NOTDIR;
608         result.diropres3_u.resfail = wcc;
609         g_mutex_unlock(dir->lock);
610         async_rpc_send_reply(req, &result);
611         return;
612     }
613
614     if (!validate_filename(argp->where.name)
615         || strcmp(argp->where.name, ".") == 0
616         || strcmp(argp->where.name, "..") == 0)
617     {
618         result.status = NFS3ERR_EXIST;
619         result.diropres3_u.resfail = wcc;
620         g_mutex_unlock(dir->lock);
621         async_rpc_send_reply(req, &result);
622         return;
623     }
624
625     g_mutex_lock(fs->lock);
626     BlueSkyInode *file;
627     file = bluesky_new_inode(bluesky_fs_alloc_inode(fs), fs, BLUESKY_SYMLINK);
628     file->nlink = 1;
629     file->mode = 0755;
630     int64_t time = bluesky_get_current_time();
631     file->mtime = time;
632     file->ctime = time;
633     file->atime = time;
634     file->ntime = time;
635     file->symlink_contents = g_strdup(argp->symlink.symlink_data);
636     g_mutex_lock(file->lock);
637     bluesky_insert_inode(fs, file);
638     g_mutex_unlock(fs->lock);
639     bluesky_directory_insert(dir, argp->where.name, file->inum);
640
641     bluesky_inode_update_ctime(dir, TRUE);
642     bluesky_inode_update_ctime(file, FALSE);
643
644     wcc.after.present = TRUE;
645     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
646     result.diropres3_u.resok.obj_attributes.present = TRUE;
647     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
648     result.diropres3_u.resok.dir_wcc = wcc;
649
650     uint64_t fh_bytes;
651     fh_bytes = GUINT64_TO_BE(file->inum);
652     result.diropres3_u.resok.obj.present = TRUE;
653     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
654     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
655
656     bluesky_inode_do_sync(file);
657     bluesky_inode_do_sync(dir);
658     g_mutex_unlock(file->lock);
659     g_mutex_unlock(dir->lock);
660     async_rpc_send_reply(req, &result);
661 }
662
663 void nfsproc3_mknod_3_svc(mknod3args *argp, RPCRequest *req)
664 {
665     diropres3 result;
666     memset(&result, 0, sizeof(result));
667
668     result.status = NFS3ERR_NOTSUPP;
669
670     async_rpc_send_reply(req, &result);
671 }
672
673 void nfsproc3_remove_3_svc(diropargs3 *argp, RPCRequest *req)
674 {
675     wccstat3 result;
676     memset(&result, 0, sizeof(result));
677
678     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
679     if (dir == NULL) {
680         result.status = NFS3ERR_STALE;
681         async_rpc_send_reply(req, &result);
682         return;
683     }
684
685     g_mutex_lock(dir->lock);
686
687     encode_pre_wcc(&result.wccstat3_u.wcc, dir);
688
689     if (!validate_filename(argp->name)
690         || strcmp(argp->name, ".") == 0
691         || strcmp(argp->name, "..") == 0)
692     {
693         result.status = NFS3ERR_NOENT;
694         g_mutex_unlock(dir->lock);
695         async_rpc_send_reply(req, &result);
696         return;
697     }
698
699     /* TODO: Decrement link count, deallocate inode if needed. */
700
701     bluesky_directory_remove(dir, argp->name);
702
703     result.status = NFS3_OK;
704     result.wccstat3_u.wcc.after.present = TRUE;
705     encode_fattr3(&result.wccstat3_u.wcc.after.post_op_attr_u.attributes,
706                   dir);
707
708     bluesky_inode_do_sync(dir);
709     g_mutex_unlock(dir->lock);
710     async_rpc_send_reply(req, &result);
711 }
712
713 void nfsproc3_rmdir_3_svc(diropargs3 *argp, RPCRequest *req)
714 {
715     wccstat3 result;
716     memset(&result, 0, sizeof(result));
717
718     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
719     if (dir == NULL) {
720         result.status = NFS3ERR_STALE;
721         async_rpc_send_reply(req, &result);
722         return;
723     }
724
725     g_mutex_lock(dir->lock);
726
727     encode_pre_wcc(&result.wccstat3_u.wcc, dir);
728
729     if (!validate_filename(argp->name)
730         || strcmp(argp->name, ".") == 0
731         || strcmp(argp->name, "..") == 0)
732     {
733         result.status = NFS3ERR_NOENT;
734         g_mutex_unlock(dir->lock);
735         async_rpc_send_reply(req, &result);
736         return;
737     }
738
739     uint64_t inum = bluesky_directory_lookup(dir, argp->name);
740     BlueSkyInode *inode = bluesky_get_inode(fs, inum);
741     if (inode == NULL) {
742         result.status = NFS3ERR_NOENT;
743         g_mutex_unlock(dir->lock);
744         async_rpc_send_reply(req, &result);
745         return;
746     }
747     g_mutex_lock(inode->lock);
748     schedule_inode_unref(req, inode);
749
750     if (inode->type != BLUESKY_DIRECTORY) {
751         result.status = NFS3ERR_NOTDIR;
752         g_mutex_unlock(inode->lock);
753         g_mutex_unlock(dir->lock);
754         async_rpc_send_reply(req, &result);
755         return;
756     }
757     if (g_sequence_get_length(inode->dirents) > 0) {
758         printf("Directory not empty: %d entries\n",
759                g_sequence_get_length(inode->dirents));
760         result.status = NFS3ERR_NOTEMPTY;
761         g_mutex_unlock(inode->lock);
762         g_mutex_unlock(dir->lock);
763         async_rpc_send_reply(req, &result);
764         return;
765     }
766
767     /* TODO: Decrement link count, deallocate inode if needed. */
768
769     bluesky_directory_remove(dir, argp->name);
770
771     result.status = NFS3_OK;
772     result.wccstat3_u.wcc.after.present = TRUE;
773     encode_fattr3(&result.wccstat3_u.wcc.after.post_op_attr_u.attributes,
774                   dir);
775
776     bluesky_inode_do_sync(dir);
777     bluesky_inode_do_sync(inode);
778     g_mutex_unlock(inode->lock);
779     g_mutex_unlock(dir->lock);
780     async_rpc_send_reply(req, &result);
781 }
782
783 void nfsproc3_rename_3_svc(rename3args *argp, RPCRequest *req)
784 {
785     rename3res result;
786     memset(&result, 0, sizeof(result));
787     wcc_data *wcc1 = &result.rename3res_u.res.fromdir_wcc;
788     wcc_data *wcc2 = &result.rename3res_u.res.todir_wcc;
789
790     BlueSkyInode *dir1 = lookup_fh(req, &argp->from.dir);
791     if (dir1 == NULL) {
792         result.status = NFS3ERR_STALE;
793         async_rpc_send_reply(req, &result);
794         return;
795     }
796
797     BlueSkyInode *dir2 = lookup_fh(req, &argp->to.dir);
798     if (dir2 == NULL) {
799         result.status = NFS3ERR_STALE;
800         async_rpc_send_reply(req, &result);
801         return;
802     }
803
804     if (dir1->inum < dir2->inum) {
805         g_mutex_lock(dir1->lock);
806         g_mutex_lock(dir2->lock);
807     } else if (dir1->inum > dir2->inum) {
808         g_mutex_lock(dir2->lock);
809         g_mutex_lock(dir1->lock);
810     }
811     encode_pre_wcc(wcc1, dir1);
812     encode_pre_wcc(wcc2, dir1);
813
814     gboolean status = bluesky_rename(dir1, argp->from.name,
815                                      dir2, argp->to.name,
816                                      TRUE, TRUE);
817
818     wcc1->after.present = TRUE;
819     encode_fattr3(&wcc1->after.post_op_attr_u.attributes, dir1);
820     wcc2->after.present = TRUE;
821     encode_fattr3(&wcc2->after.post_op_attr_u.attributes, dir2);
822     if (status)
823         result.status = NFS3_OK;
824     else
825         result.status = NFS3ERR_PERM;
826
827     bluesky_inode_do_sync(dir2);
828     bluesky_inode_do_sync(dir1);
829
830     g_mutex_unlock(dir1->lock);
831     if (dir1->inum != dir2->inum)
832         g_mutex_unlock(dir2->lock);
833     async_rpc_send_reply(req, &result);
834 }
835
836 void nfsproc3_link_3_svc(link3args *argp, RPCRequest *req)
837 {
838     link3res result;
839     memset(&result, 0, sizeof(result));
840     struct wcc_data wcc;
841     memset(&wcc, 0, sizeof(wcc));
842
843     BlueSkyInode *inode = lookup_fh(req, &argp->file);
844     if (inode == NULL) {
845         result.status = NFS3ERR_STALE;
846         result.link3res_u.res.linkdir_wcc = wcc;
847         async_rpc_send_reply(req, &result);
848         return;
849     }
850     g_mutex_lock(inode->lock);
851
852     BlueSkyInode *dir = lookup_fh(req, &argp->link.dir);
853     if (dir == NULL) {
854         result.status = NFS3ERR_STALE;
855         result.link3res_u.res.linkdir_wcc = wcc;
856         g_mutex_unlock(inode->lock);
857         async_rpc_send_reply(req, &result);
858         return;
859     }
860     g_mutex_lock(dir->lock);
861
862     encode_pre_wcc(&wcc, dir);
863     if (dir->type != BLUESKY_DIRECTORY) {
864         result.status = NFS3ERR_NOTDIR;
865         result.link3res_u.res.linkdir_wcc = wcc;
866         g_mutex_unlock(inode->lock);
867         g_mutex_unlock(dir->lock);
868         async_rpc_send_reply(req, &result);
869         return;
870     }
871
872     if (!validate_filename(argp->link.name)
873         || strcmp(argp->link.name, ".") == 0
874         || strcmp(argp->link.name, "..") == 0
875         || bluesky_directory_lookup(dir, argp->link.name) != 0)
876     {
877         result.status = NFS3ERR_EXIST;
878         result.link3res_u.res.linkdir_wcc = wcc;
879         g_mutex_unlock(inode->lock);
880         g_mutex_unlock(dir->lock);
881         async_rpc_send_reply(req, &result);
882         return;
883     }
884
885     if (!bluesky_directory_insert(dir, argp->link.name, inode->inum)) {
886         result.status = NFS3ERR_EXIST;
887         result.link3res_u.res.linkdir_wcc = wcc;
888         g_mutex_unlock(inode->lock);
889         g_mutex_unlock(dir->lock);
890         async_rpc_send_reply(req, &result);
891         return;
892     }
893     inode->nlink++;
894     bluesky_inode_update_ctime(inode, FALSE);
895     bluesky_inode_update_ctime(dir, TRUE);
896
897     result.status = NFS3_OK;
898     wcc.after.present = TRUE;
899     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
900     result.link3res_u.res.file_attributes.present = TRUE;
901     encode_fattr3(&result.link3res_u.res.file_attributes.post_op_attr_u.attributes, inode);
902     result.link3res_u.res.linkdir_wcc = wcc;
903
904     bluesky_inode_do_sync(inode);
905     bluesky_inode_do_sync(dir);
906     g_mutex_unlock(inode->lock);
907     g_mutex_unlock(dir->lock);
908     async_rpc_send_reply(req, &result);
909 }
910
911 gint bluesky_dirent_compare(gconstpointer a, gconstpointer b,
912                             gpointer unused);
913
914 #define MAX_READDIR_DIRENTS 64
915 void nfsproc3_readdir_3_svc(readdir3args *argp, RPCRequest *req)
916 {
917     readdir3res result;
918     memset(&result, 0, sizeof(result));
919
920     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
921     if (dir == NULL) {
922         result.status = NFS3ERR_STALE;
923         result.readdir3res_u.resfail.present = FALSE;
924         async_rpc_send_reply(req, &result);
925         return;
926     }
927     g_mutex_lock(dir->lock);
928
929     result.status = NFS3_OK;
930     result.readdir3res_u.resok.dir_attributes.present = TRUE;
931     encode_fattr3(&result.readdir3res_u.resok.dir_attributes.post_op_attr_u.attributes, dir);
932     memset(result.readdir3res_u.resok.cookieverf, 0,
933            sizeof(result.readdir3res_u.resok.cookieverf));
934
935     entry3 dirents[MAX_READDIR_DIRENTS];
936     int count = 0;
937
938     BlueSkyDirent start = {NULL, NULL, argp->cookie, 0};
939     GSequenceIter *i = g_sequence_search(dir->dirents, &start,
940                                          bluesky_dirent_compare, NULL);
941
942     while (count < MAX_READDIR_DIRENTS && !g_sequence_iter_is_end(i)) {
943         BlueSkyDirent *d = g_sequence_get(i);
944         dirents[count].fileid = d->inum;
945         dirents[count].name = d->name;
946         dirents[count].cookie = d->cookie;
947         dirents[count].nextentry = NULL;
948         if (count > 0)
949             dirents[count - 1].nextentry = &dirents[count];
950         i = g_sequence_iter_next(i);
951         count++;
952     }
953
954     if (count > 0)
955         result.readdir3res_u.resok.reply.entries = &dirents[0];
956     else
957         result.readdir3res_u.resok.reply.entries = NULL;
958     result.readdir3res_u.resok.reply.eof = g_sequence_iter_is_end(i);
959
960     g_mutex_unlock(dir->lock);
961     async_rpc_send_reply(req, &result);
962 }
963
964 void nfsproc3_readdirplus_3_svc(readdirplus3args *argp, RPCRequest *req)
965 {
966     /* XDR-encoded sizes:
967      *   post_op_attr: 88 bytes
968      *   base readdirplus3resok: 88 + 16 bytes
969      *   base directory entry: 24 bytes + filename
970      *   attributes/fh3: 88 + 8 + filehandle size
971      */
972     size_t dircount = 88 + 16, attrcount = 0;
973     readdirplus3res result;
974     memset(&result, 0, sizeof(result));
975
976     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
977     if (dir == NULL) {
978         result.status = NFS3ERR_STALE;
979         result.readdirplus3res_u.resfail.present = FALSE;
980         async_rpc_send_reply(req, &result);
981         return;
982     }
983     g_mutex_lock(dir->lock);
984
985     result.status = NFS3_OK;
986     result.readdirplus3res_u.resok.dir_attributes.present = TRUE;
987     encode_fattr3(&result.readdirplus3res_u.resok.dir_attributes.post_op_attr_u.attributes, dir);
988     memset(result.readdirplus3res_u.resok.cookieverf, 0,
989            sizeof(result.readdirplus3res_u.resok.cookieverf));
990
991     entryplus3 dirents[MAX_READDIR_DIRENTS];
992     uint64_t fh_bytes[MAX_READDIR_DIRENTS];
993     int count = 0;
994
995     GSequenceIter *i;
996     BlueSkyDirent start = {NULL, NULL, argp->cookie, 0};
997
998     /* Perform a prefetch pass on inodes: for all the inodes we think we will
999      * return information about, try to load each one but don't wait.  This
1000      * should let multiple inodes be fetched in parallel, instead of
1001      * sequentially in the loop that follows. */
1002     i = g_sequence_search(dir->dirents, &start, bluesky_dirent_compare, NULL);
1003     while (count < MAX_READDIR_DIRENTS
1004            && !g_sequence_iter_is_end(i)
1005            && dircount <= argp->dircount
1006            && dircount + attrcount <= argp->maxcount)
1007     {
1008         BlueSkyDirent *d = g_sequence_get(i);
1009         BlueSkyInode *inode = bluesky_get_inode(fs, d->inum);
1010         if (inode != NULL)
1011             bluesky_inode_unref(inode);
1012         dircount += 24 + ((strlen(d->name) + 3) & ~3);
1013         attrcount += 88 + 8 + 8;
1014         i = g_sequence_iter_next(i);
1015     }
1016
1017     i = g_sequence_search(dir->dirents, &start, bluesky_dirent_compare, NULL);
1018     count = 0;
1019     dircount = 88 + 16;
1020     attrcount = 0;
1021     while (count < MAX_READDIR_DIRENTS && !g_sequence_iter_is_end(i)) {
1022         BlueSkyDirent *d = g_sequence_get(i);
1023         BlueSkyInode *inode = bluesky_get_inode(fs, d->inum);
1024         if (inode != NULL) {
1025             g_mutex_lock(inode->lock);
1026             dircount += 24 + ((strlen(d->name) + 3) & ~3);
1027             attrcount += 88 + 8 + 8;
1028             if (dircount > argp->dircount
1029                 || dircount + attrcount > argp->maxcount)
1030             {
1031                 g_mutex_unlock(inode->lock);
1032                 bluesky_inode_unref(inode);
1033                 break;
1034             }
1035             dirents[count].fileid = d->inum;
1036             dirents[count].name = d->name;
1037             dirents[count].cookie = d->cookie;
1038             dirents[count].nextentry = NULL;
1039             dirents[count].name_attributes.present = TRUE;
1040             encode_fattr3(&dirents[count].name_attributes.post_op_attr_u.attributes, inode);
1041             fh_bytes[count] = GUINT64_TO_BE(d->inum);
1042             dirents[count].name_handle.present = TRUE;
1043             dirents[count].name_handle.post_op_fh3_u.handle.data.data_len = 8;
1044             dirents[count].name_handle.post_op_fh3_u.handle.data.data_val
1045                 = (char *)&fh_bytes[count];
1046             if (count > 0)
1047                 dirents[count - 1].nextentry = &dirents[count];
1048             count++;
1049             g_mutex_unlock(inode->lock);
1050             bluesky_inode_unref(inode);
1051         }
1052         i = g_sequence_iter_next(i);
1053     }
1054
1055     if (count > 0)
1056         result.readdirplus3res_u.resok.reply.entries = &dirents[0];
1057     else
1058         result.readdirplus3res_u.resok.reply.entries = NULL;
1059     result.readdirplus3res_u.resok.reply.eof = g_sequence_iter_is_end(i);
1060
1061     g_mutex_unlock(dir->lock);
1062     async_rpc_send_reply(req, &result);
1063 }
1064
1065 void nfsproc3_fsstat_3_svc(nfs_fh3 *argp, RPCRequest *req)
1066 {
1067     fsstat3res result;
1068     memset(&result, 0, sizeof(result));
1069
1070     BlueSkyInode *inode = lookup_fh(req, argp);
1071     if (inode == NULL) {
1072         result.status = NFS3ERR_STALE;
1073         result.fsstat3res_u.resfail.present = FALSE;
1074         async_rpc_send_reply(req, &result);
1075         return;
1076     }
1077     g_mutex_lock(inode->lock);
1078
1079     result.status = NFS3_OK;
1080     result.fsstat3res_u.resok.obj_attributes.present = TRUE;
1081     encode_fattr3(&result.fsstat3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1082
1083     result.fsstat3res_u.resok.tbytes = (1 << 30);
1084     result.fsstat3res_u.resok.fbytes = (1 << 30);
1085     result.fsstat3res_u.resok.abytes = (1 << 30);
1086     result.fsstat3res_u.resok.tfiles = 0;
1087     result.fsstat3res_u.resok.ffiles = 0;
1088     result.fsstat3res_u.resok.afiles = 0;
1089     result.fsstat3res_u.resok.invarsec = 0;
1090
1091     g_mutex_unlock(inode->lock);
1092     async_rpc_send_reply(req, &result);
1093 }
1094
1095 void nfsproc3_fsinfo_3_svc(nfs_fh3 *argp, RPCRequest *req)
1096 {
1097     fsinfo3res result;
1098     memset(&result, 0, sizeof(result));
1099
1100     BlueSkyInode *inode = bluesky_get_inode(fs, 1);
1101     g_mutex_lock(inode->lock);
1102     result.status = NFS3_OK;
1103     result.fsinfo3res_u.resok.obj_attributes.present = TRUE;
1104     encode_fattr3(&result.fsinfo3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1105     result.fsinfo3res_u.resok.rtmax = NFS_MAXSIZE;
1106     result.fsinfo3res_u.resok.rtpref = NFS_MAXSIZE;
1107     result.fsinfo3res_u.resok.rtmult = NFS_BLOCKSIZE;
1108     result.fsinfo3res_u.resok.wtmax = NFS_MAXSIZE;
1109     result.fsinfo3res_u.resok.wtpref = NFS_MAXSIZE;
1110     result.fsinfo3res_u.resok.wtmult = NFS_BLOCKSIZE;
1111     result.fsinfo3res_u.resok.dtpref = NFS_BLOCKSIZE;
1112     result.fsinfo3res_u.resok.maxfilesize = 0x7fffffffffffffffULL;
1113     result.fsinfo3res_u.resok.time_delta.seconds = 0;
1114     result.fsinfo3res_u.resok.time_delta.nseconds = 1000;
1115     result.fsinfo3res_u.resok.properties
1116         = FSF3_LINK | FSF3_SYMLINK | FSF3_HOMOGENEOUS | FSF3_CANSETTIME;
1117
1118     g_mutex_unlock(inode->lock);
1119     bluesky_inode_unref(inode);
1120     async_rpc_send_reply(req, &result);
1121 }
1122
1123 void nfsproc3_pathconf_3_svc(nfs_fh3 *argp, RPCRequest *req)
1124 {
1125     pathconf3res result;
1126     memset(&result, 0, sizeof(result));
1127
1128     BlueSkyInode *inode = bluesky_get_inode(fs, 1);
1129     g_mutex_lock(inode->lock);
1130     result.status = NFS3_OK;
1131     result.pathconf3res_u.resok.obj_attributes.present = TRUE;
1132     encode_fattr3(&result.pathconf3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1133     result.pathconf3res_u.resok.linkmax = 0xffffffff;
1134     result.pathconf3res_u.resok.name_max = 255;
1135     result.pathconf3res_u.resok.no_trunc = TRUE;
1136     result.pathconf3res_u.resok.chown_restricted = TRUE;
1137     result.pathconf3res_u.resok.case_insensitive = FALSE;
1138     result.pathconf3res_u.resok.case_preserving = TRUE;
1139
1140     g_mutex_unlock(inode->lock);
1141     bluesky_inode_unref(inode);
1142     async_rpc_send_reply(req, &result);
1143 }
1144
1145 void nfsproc3_commit_3_svc(commit3args *argp, RPCRequest *req)
1146 {
1147     commit3res result;
1148     memset(&result, 0, sizeof(result));
1149
1150     result.status = NFS3_OK;
1151
1152     BlueSkyInode *inode = lookup_fh(req, &argp->file);
1153     if (inode == NULL) {
1154         result.status = NFS3ERR_STALE;
1155         async_rpc_send_reply(req, &result);
1156         return;
1157     }
1158
1159     g_mutex_lock(inode->lock);
1160     encode_pre_wcc(&result.commit3res_u.resok.file_wcc, inode);
1161
1162     bluesky_inode_do_sync(inode);
1163
1164     result.commit3res_u.resok.file_wcc.after.present = TRUE;
1165     encode_fattr3(&result.commit3res_u.resok.file_wcc.after.post_op_attr_u.attributes, inode);
1166
1167     g_mutex_unlock(inode->lock);
1168
1169     async_rpc_send_reply(req, &result);
1170 }