Add general statistics-gathering infrastructure.
[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     g_mutex_unlock(inode->lock);
212     async_rpc_send_reply(req, &result);
213 }
214
215 void nfsproc3_lookup_3_svc(diropargs3 *argp, RPCRequest *req)
216 {
217     lookup3res result;
218     memset(&result, 0, sizeof(result));
219
220     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
221     if (dir == NULL) {
222         result.status = NFS3ERR_STALE;
223         result.lookup3res_u.resfail.present = FALSE;
224         async_rpc_send_reply(req, &result);
225         return;
226     }
227
228     g_mutex_lock(dir->lock);
229     result.lookup3res_u.resfail.present = TRUE;
230     encode_fattr3(&result.lookup3res_u.resfail.post_op_attr_u.attributes, dir);
231     if (!validate_filename(argp->name)) {
232         if (strlen(argp->name) > 255)
233             result.status = NFS3ERR_NAMETOOLONG;
234         else
235             result.status = NFS3ERR_NOENT;
236         g_mutex_unlock(dir->lock);
237         async_rpc_send_reply(req, &result);
238         return;
239     }
240
241     /* TODO: Special-case "." and "..". */
242     uint64_t inum = bluesky_directory_lookup(dir, argp->name);
243     if (inum == 0) {
244         result.status = NFS3ERR_NOENT;
245         g_mutex_unlock(dir->lock);
246         async_rpc_send_reply(req, &result);
247         return;
248     }
249
250     result.lookup3res_u.resok.dir_attributes.present = TRUE;
251     encode_fattr3(&result.lookup3res_u.resok.dir_attributes.post_op_attr_u.attributes, dir);
252     g_mutex_unlock(dir->lock);
253
254     BlueSkyInode *inode = bluesky_get_inode(fs, inum);
255     if (inode == NULL) {
256         result.status = NFS3ERR_NOENT;
257         async_rpc_send_reply(req, &result);
258         return;
259     }
260     g_mutex_lock(inode->lock);
261     schedule_inode_unref(req, inode);
262
263     result.status = NFS3_OK;
264     result.lookup3res_u.resok.obj_attributes.present = TRUE;
265     encode_fattr3(&result.lookup3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
266
267     uint64_t fh_bytes;
268     fh_bytes = GUINT64_TO_BE(inum);
269     result.lookup3res_u.resok.object.data.data_len = 8;
270     result.lookup3res_u.resok.object.data.data_val = (char *)&fh_bytes;
271
272     g_mutex_unlock(inode->lock);
273     async_rpc_send_reply(req, &result);
274 }
275
276 void nfsproc3_access_3_svc(access3args *argp, RPCRequest *req)
277 {
278     access3res result;
279     memset(&result, 0, sizeof(result));
280
281     BlueSkyInode *inode = lookup_fh(req, &argp->object);
282     if (inode == NULL) {
283         result.status = NFS3ERR_STALE;
284         result.access3res_u.resfail.present = FALSE;
285         async_rpc_send_reply(req, &result);
286         return;
287     }
288
289     g_mutex_lock(inode->lock);
290     result.status = NFS3_OK;
291     result.access3res_u.resok.obj_attributes.present = TRUE;
292     encode_fattr3(&result.access3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
293     result.access3res_u.resok.access = argp->access;
294     g_mutex_unlock(inode->lock);
295
296     async_rpc_send_reply(req, &result);
297 }
298
299 void nfsproc3_readlink_3_svc(nfs_fh3 *argp, RPCRequest *req)
300 {
301     readlink3res result;
302     memset(&result, 0, sizeof(result));
303
304     BlueSkyInode *inode = lookup_fh(req, argp);
305     if (inode != NULL) {
306         g_mutex_lock(inode->lock);
307         if (inode->type == BLUESKY_SYMLINK) {
308             result.status = NFS3_OK;
309             result.readlink3res_u.resok.symlink_attributes.present = TRUE;
310             encode_fattr3(&result.readlink3res_u.resok.symlink_attributes.post_op_attr_u.attributes, inode);
311             result.readlink3res_u.resok.data = inode->symlink_contents;
312         } else {
313             result.status = NFS3ERR_INVAL;
314             result.readlink3res_u.resfail.present = TRUE;
315             encode_fattr3(&result.readlink3res_u.resfail.post_op_attr_u.attributes, inode);
316         }
317         g_mutex_unlock(inode->lock);
318     } else {
319         result.status = NFS3ERR_STALE;
320     }
321
322     async_rpc_send_reply(req, &result);
323 }
324
325 void nfsproc3_read_3_svc(read3args *argp, RPCRequest *req)
326 {
327     read3res result;
328     memset(&result, 0, sizeof(result));
329     char buf[NFS_MAXSIZE];
330
331     bluesky_flushd_invoke_conditional(fs);
332
333     BlueSkyInode *inode = lookup_fh(req, &argp->file);
334     if (inode == NULL) {
335         result.status = NFS3ERR_STALE;
336         result.read3res_u.resfail.present = FALSE;
337         async_rpc_send_reply(req, &result);
338         return;
339     }
340
341     g_mutex_lock(inode->lock);
342
343     int count = argp->count;
344     if (argp->offset >= inode->size) {
345         count = 0;
346         result.read3res_u.resok.eof = TRUE;
347     } else {
348         count = MIN(count, NFS_MAXSIZE);
349         count = MIN(count, inode->size - argp->offset);
350         if (argp->offset + count == inode->size)
351             result.read3res_u.resok.eof = TRUE;
352         else
353             result.read3res_u.resok.eof = FALSE;
354
355         bluesky_file_read(inode, argp->offset, buf, count);
356     }
357
358     result.status = NFS3_OK;
359     result.read3res_u.resok.file_attributes.present = TRUE;
360     encode_fattr3(&result.read3res_u.resok.file_attributes.post_op_attr_u.attributes, inode);
361     result.read3res_u.resok.count = count;
362     result.read3res_u.resok.data.data_val = buf;
363     result.read3res_u.resok.data.data_len = count;
364
365     g_mutex_unlock(inode->lock);
366
367     async_rpc_send_reply(req, &result);
368 }
369
370 void nfsproc3_write_3_svc(write3args *argp, RPCRequest *req)
371 {
372     write3res result;
373     memset(&result, 0, sizeof(result));
374     struct wcc_data wcc;
375     memset(&wcc, 0, sizeof(wcc));
376
377     bluesky_flushd_invoke_conditional(fs);
378
379     BlueSkyInode *inode = lookup_fh(req, &argp->file);
380     if (inode == NULL) {
381         result.status = NFS3ERR_STALE;
382         result.write3res_u.resfail = wcc;
383         async_rpc_send_reply(req, &result);
384         return;
385     }
386
387 #if 0
388     /* FIXME: Hack to throttle writes when there is too much dirty data still
389      * to be written out. */
390     while (g_atomic_int_get(&fs->cache_dirty) > 4096
391            || g_atomic_int_get(&fs->cache_total) > 8192) {
392         g_print("Too many dirty pages (%d) or total pages (%d); throttling writes...\n",
393                 g_atomic_int_get(&fs->cache_dirty),
394                 g_atomic_int_get(&fs->cache_total));
395         struct timespec delay;
396         delay.tv_sec = 2;
397         delay.tv_nsec = 0;
398         nanosleep(&delay, NULL);
399     }
400 #endif
401
402     g_mutex_lock(inode->lock);
403
404     encode_pre_wcc(&wcc, inode);
405     if (inode->type != BLUESKY_REGULAR) {
406         result.status = NFS3ERR_INVAL;
407         result.write3res_u.resfail = wcc;
408         g_mutex_unlock(inode->lock);
409         async_rpc_send_reply(req, &result);
410         return;
411     }
412
413     uint64_t lastbyte = argp->offset + argp->count;
414     if (lastbyte > inode->size) {
415         bluesky_file_truncate(inode, lastbyte);
416     }
417
418     if (argp->data.data_len < argp->count) {
419         /* ??? */
420     } else {
421         bluesky_file_write(inode, argp->offset,
422                            argp->data.data_val, argp->count);
423     }
424
425     wcc.after.present = TRUE;
426     encode_fattr3(&wcc.after.post_op_attr_u.attributes, inode);
427     result.write3res_u.resok.file_wcc = wcc;
428     result.write3res_u.resok.count = argp->count;
429     result.write3res_u.resok.committed = FILE_SYNC;
430
431     g_mutex_unlock(inode->lock);
432
433     async_rpc_send_reply(req, &result);
434 }
435
436 void nfsproc3_create_3_svc(create3args *argp, RPCRequest *req)
437 {
438     diropres3 result;
439     memset(&result, 0, sizeof(result));
440     struct wcc_data wcc;
441     memset(&wcc, 0, sizeof(wcc));
442
443     BlueSkyInode *dir = lookup_fh(req, &argp->where.dir);
444     if (dir == NULL) {
445         result.status = NFS3ERR_STALE;
446         result.diropres3_u.resfail = wcc;
447         async_rpc_send_reply(req, &result);
448         return;
449     }
450
451     g_mutex_lock(dir->lock);
452
453     encode_pre_wcc(&wcc, dir);
454     if (dir->type != BLUESKY_DIRECTORY) {
455         result.status = NFS3ERR_NOTDIR;
456         result.diropres3_u.resfail = wcc;
457         g_mutex_unlock(dir->lock);
458         async_rpc_send_reply(req, &result);
459         return;
460     }
461
462     if (!validate_filename(argp->where.name)
463         || strcmp(argp->where.name, ".") == 0
464         || strcmp(argp->where.name, "..") == 0)
465     {
466         result.status = NFS3ERR_EXIST;
467         result.diropres3_u.resfail = wcc;
468         g_mutex_unlock(dir->lock);
469         async_rpc_send_reply(req, &result);
470         return;
471     }
472
473     g_mutex_lock(fs->lock);
474     BlueSkyInode *file;
475     file = bluesky_new_inode(bluesky_fs_alloc_inode(fs), fs, BLUESKY_REGULAR);
476     file->nlink = 1;
477     file->mode = 0755;
478     int64_t time = bluesky_get_current_time();
479     file->mtime = time;
480     file->ctime = time;
481     file->atime = time;
482     file->ntime = time;
483     g_mutex_lock(file->lock);
484     bluesky_insert_inode(fs, file);
485     g_mutex_unlock(fs->lock);
486     bluesky_directory_insert(dir, argp->where.name, file->inum);
487
488     bluesky_inode_update_ctime(dir, TRUE);
489
490     wcc.after.present = TRUE;
491     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
492     result.diropres3_u.resok.obj_attributes.present = TRUE;
493     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
494     result.diropres3_u.resok.dir_wcc = wcc;
495
496     uint64_t fh_bytes;
497     fh_bytes = GUINT64_TO_BE(file->inum);
498     result.diropres3_u.resok.obj.present = TRUE;
499     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
500     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
501
502     g_mutex_unlock(file->lock);
503     g_mutex_unlock(dir->lock);
504
505     async_rpc_send_reply(req, &result);
506 }
507
508 void nfsproc3_mkdir_3_svc(mkdir3args *argp, RPCRequest *req)
509 {
510     diropres3 result;
511     memset(&result, 0, sizeof(result));
512     struct wcc_data wcc;
513     memset(&wcc, 0, sizeof(wcc));
514
515     BlueSkyInode *dir = lookup_fh(req, &argp->where.dir);
516     if (dir == NULL) {
517         result.status = NFS3ERR_STALE;
518         result.diropres3_u.resfail = wcc;
519         async_rpc_send_reply(req, &result);
520         return;
521     }
522
523     g_mutex_lock(dir->lock);
524
525     encode_pre_wcc(&wcc, dir);
526     if (dir->type != BLUESKY_DIRECTORY) {
527         result.status = NFS3ERR_NOTDIR;
528         result.diropres3_u.resfail = wcc;
529         g_mutex_unlock(dir->lock);
530         async_rpc_send_reply(req, &result);
531         return;
532     }
533
534     if (!validate_filename(argp->where.name)
535         || strcmp(argp->where.name, ".") == 0
536         || strcmp(argp->where.name, "..") == 0)
537     {
538         result.status = NFS3ERR_EXIST;
539         result.diropres3_u.resfail = wcc;
540         g_mutex_unlock(dir->lock);
541         async_rpc_send_reply(req, &result);
542         return;
543     }
544
545     g_mutex_lock(fs->lock);
546     BlueSkyInode *file;
547     file = bluesky_new_inode(bluesky_fs_alloc_inode(fs), fs, BLUESKY_DIRECTORY);
548     file->nlink = 1;
549     file->mode = 0755;
550     int64_t time = bluesky_get_current_time();
551     file->mtime = time;
552     file->ctime = time;
553     file->atime = time;
554     file->ntime = time;
555     g_mutex_lock(file->lock);
556     bluesky_insert_inode(fs, file);
557     g_mutex_unlock(fs->lock);
558     bluesky_directory_insert(dir, argp->where.name, file->inum);
559     set_attributes(file, &argp->attributes);
560
561     bluesky_inode_update_ctime(dir, TRUE);
562
563     wcc.after.present = TRUE;
564     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
565     result.diropres3_u.resok.obj_attributes.present = TRUE;
566     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
567     result.diropres3_u.resok.dir_wcc = wcc;
568
569     uint64_t fh_bytes;
570     fh_bytes = GUINT64_TO_BE(file->inum);
571     result.diropres3_u.resok.obj.present = TRUE;
572     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
573     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
574
575     g_mutex_unlock(file->lock);
576     g_mutex_unlock(dir->lock);
577     async_rpc_send_reply(req, &result);
578 }
579
580 void nfsproc3_symlink_3_svc(symlink3args *argp, RPCRequest *req)
581 {
582     diropres3 result;
583     memset(&result, 0, sizeof(result));
584     struct wcc_data wcc;
585     memset(&wcc, 0, sizeof(wcc));
586
587     BlueSkyInode *dir = lookup_fh(req, &argp->where.dir);
588     if (dir == NULL) {
589         result.status = NFS3ERR_STALE;
590         result.diropres3_u.resfail = wcc;
591         async_rpc_send_reply(req, &result);
592         return;
593     }
594     g_mutex_lock(dir->lock);
595
596     encode_pre_wcc(&wcc, dir);
597     if (dir->type != BLUESKY_DIRECTORY) {
598         result.status = NFS3ERR_NOTDIR;
599         result.diropres3_u.resfail = wcc;
600         g_mutex_unlock(dir->lock);
601         async_rpc_send_reply(req, &result);
602         return;
603     }
604
605     if (!validate_filename(argp->where.name)
606         || strcmp(argp->where.name, ".") == 0
607         || strcmp(argp->where.name, "..") == 0)
608     {
609         result.status = NFS3ERR_EXIST;
610         result.diropres3_u.resfail = wcc;
611         g_mutex_unlock(dir->lock);
612         async_rpc_send_reply(req, &result);
613         return;
614     }
615
616     g_mutex_lock(fs->lock);
617     BlueSkyInode *file;
618     file = bluesky_new_inode(bluesky_fs_alloc_inode(fs), fs, BLUESKY_SYMLINK);
619     file->nlink = 1;
620     file->mode = 0755;
621     int64_t time = bluesky_get_current_time();
622     file->mtime = time;
623     file->ctime = time;
624     file->atime = time;
625     file->ntime = time;
626     file->symlink_contents = g_strdup(argp->symlink.symlink_data);
627     g_mutex_lock(file->lock);
628     bluesky_insert_inode(fs, file);
629     g_mutex_unlock(fs->lock);
630     bluesky_directory_insert(dir, argp->where.name, file->inum);
631
632     bluesky_inode_update_ctime(dir, TRUE);
633
634     wcc.after.present = TRUE;
635     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
636     result.diropres3_u.resok.obj_attributes.present = TRUE;
637     encode_fattr3(&result.diropres3_u.resok.obj_attributes.post_op_attr_u.attributes, file);
638     result.diropres3_u.resok.dir_wcc = wcc;
639
640     uint64_t fh_bytes;
641     fh_bytes = GUINT64_TO_BE(file->inum);
642     result.diropres3_u.resok.obj.present = TRUE;
643     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_len = 8;
644     result.diropres3_u.resok.obj.post_op_fh3_u.handle.data.data_val = (char *)&fh_bytes;
645
646     g_mutex_unlock(file->lock);
647     g_mutex_unlock(dir->lock);
648     async_rpc_send_reply(req, &result);
649 }
650
651 void nfsproc3_mknod_3_svc(mknod3args *argp, RPCRequest *req)
652 {
653     diropres3 result;
654     memset(&result, 0, sizeof(result));
655
656     result.status = NFS3ERR_NOTSUPP;
657
658     async_rpc_send_reply(req, &result);
659 }
660
661 void nfsproc3_remove_3_svc(diropargs3 *argp, RPCRequest *req)
662 {
663     wccstat3 result;
664     memset(&result, 0, sizeof(result));
665
666     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
667     if (dir == NULL) {
668         result.status = NFS3ERR_STALE;
669         async_rpc_send_reply(req, &result);
670         return;
671     }
672
673     g_mutex_lock(dir->lock);
674
675     encode_pre_wcc(&result.wccstat3_u.wcc, dir);
676
677     if (!validate_filename(argp->name)
678         || strcmp(argp->name, ".") == 0
679         || strcmp(argp->name, "..") == 0)
680     {
681         result.status = NFS3ERR_NOENT;
682         g_mutex_unlock(dir->lock);
683         async_rpc_send_reply(req, &result);
684         return;
685     }
686
687     /* TODO: Decrement link count, deallocate inode if needed. */
688
689     bluesky_directory_remove(dir, argp->name);
690
691     result.status = NFS3_OK;
692     result.wccstat3_u.wcc.after.present = TRUE;
693     encode_fattr3(&result.wccstat3_u.wcc.after.post_op_attr_u.attributes,
694                   dir);
695
696     g_mutex_unlock(dir->lock);
697     async_rpc_send_reply(req, &result);
698 }
699
700 void nfsproc3_rmdir_3_svc(diropargs3 *argp, RPCRequest *req)
701 {
702     wccstat3 result;
703     memset(&result, 0, sizeof(result));
704
705     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
706     if (dir == NULL) {
707         result.status = NFS3ERR_STALE;
708         async_rpc_send_reply(req, &result);
709         return;
710     }
711
712     g_mutex_lock(dir->lock);
713
714     encode_pre_wcc(&result.wccstat3_u.wcc, dir);
715
716     if (!validate_filename(argp->name)
717         || strcmp(argp->name, ".") == 0
718         || strcmp(argp->name, "..") == 0)
719     {
720         result.status = NFS3ERR_NOENT;
721         g_mutex_unlock(dir->lock);
722         async_rpc_send_reply(req, &result);
723         return;
724     }
725
726     uint64_t inum = bluesky_directory_lookup(dir, argp->name);
727     BlueSkyInode *inode = bluesky_get_inode(fs, inum);
728     if (inode == NULL) {
729         result.status = NFS3ERR_NOENT;
730         g_mutex_unlock(dir->lock);
731         async_rpc_send_reply(req, &result);
732         return;
733     }
734     g_mutex_lock(inode->lock);
735     schedule_inode_unref(req, inode);
736
737     if (inode->type != BLUESKY_DIRECTORY) {
738         result.status = NFS3ERR_NOTDIR;
739         g_mutex_unlock(inode->lock);
740         g_mutex_unlock(dir->lock);
741         async_rpc_send_reply(req, &result);
742         return;
743     }
744     if (g_sequence_get_length(inode->dirents) > 0) {
745         printf("Directory not empty: %d entries\n",
746                g_sequence_get_length(inode->dirents));
747         result.status = NFS3ERR_NOTEMPTY;
748         g_mutex_unlock(inode->lock);
749         g_mutex_unlock(dir->lock);
750         async_rpc_send_reply(req, &result);
751         return;
752     }
753
754     /* TODO: Decrement link count, deallocate inode if needed. */
755
756     bluesky_directory_remove(dir, argp->name);
757
758     result.status = NFS3_OK;
759     result.wccstat3_u.wcc.after.present = TRUE;
760     encode_fattr3(&result.wccstat3_u.wcc.after.post_op_attr_u.attributes,
761                   dir);
762
763     g_mutex_unlock(inode->lock);
764     g_mutex_unlock(dir->lock);
765     async_rpc_send_reply(req, &result);
766 }
767
768 void nfsproc3_rename_3_svc(rename3args *argp, RPCRequest *req)
769 {
770     rename3res result;
771     memset(&result, 0, sizeof(result));
772     wcc_data *wcc1 = &result.rename3res_u.res.fromdir_wcc;
773     wcc_data *wcc2 = &result.rename3res_u.res.todir_wcc;
774
775     BlueSkyInode *dir1 = lookup_fh(req, &argp->from.dir);
776     if (dir1 == NULL) {
777         result.status = NFS3ERR_STALE;
778         async_rpc_send_reply(req, &result);
779         return;
780     }
781
782     BlueSkyInode *dir2 = lookup_fh(req, &argp->to.dir);
783     if (dir2 == NULL) {
784         result.status = NFS3ERR_STALE;
785         async_rpc_send_reply(req, &result);
786         return;
787     }
788
789     if (dir1->inum < dir2->inum) {
790         g_mutex_lock(dir1->lock);
791         g_mutex_lock(dir2->lock);
792     } else if (dir1->inum > dir2->inum) {
793         g_mutex_lock(dir2->lock);
794         g_mutex_lock(dir1->lock);
795     }
796     encode_pre_wcc(wcc1, dir1);
797     encode_pre_wcc(wcc2, dir1);
798
799     gboolean status = bluesky_rename(dir1, argp->from.name,
800                                      dir2, argp->to.name,
801                                      TRUE, TRUE);
802
803     wcc1->after.present = TRUE;
804     encode_fattr3(&wcc1->after.post_op_attr_u.attributes, dir1);
805     wcc2->after.present = TRUE;
806     encode_fattr3(&wcc2->after.post_op_attr_u.attributes, dir2);
807     if (status)
808         result.status = NFS3_OK;
809     else
810         result.status = NFS3ERR_PERM;
811
812     g_mutex_unlock(dir1->lock);
813     if (dir1->inum != dir2->inum)
814         g_mutex_unlock(dir2->lock);
815     async_rpc_send_reply(req, &result);
816 }
817
818 void nfsproc3_link_3_svc(link3args *argp, RPCRequest *req)
819 {
820     link3res result;
821     memset(&result, 0, sizeof(result));
822     struct wcc_data wcc;
823     memset(&wcc, 0, sizeof(wcc));
824
825     BlueSkyInode *inode = lookup_fh(req, &argp->file);
826     if (inode == NULL) {
827         result.status = NFS3ERR_STALE;
828         result.link3res_u.res.linkdir_wcc = wcc;
829         async_rpc_send_reply(req, &result);
830         return;
831     }
832     g_mutex_lock(inode->lock);
833
834     BlueSkyInode *dir = lookup_fh(req, &argp->link.dir);
835     if (dir == NULL) {
836         result.status = NFS3ERR_STALE;
837         result.link3res_u.res.linkdir_wcc = wcc;
838         g_mutex_unlock(inode->lock);
839         async_rpc_send_reply(req, &result);
840         return;
841     }
842     g_mutex_lock(dir->lock);
843
844     encode_pre_wcc(&wcc, dir);
845     if (dir->type != BLUESKY_DIRECTORY) {
846         result.status = NFS3ERR_NOTDIR;
847         result.link3res_u.res.linkdir_wcc = wcc;
848         g_mutex_unlock(inode->lock);
849         g_mutex_unlock(dir->lock);
850         async_rpc_send_reply(req, &result);
851         return;
852     }
853
854     if (!validate_filename(argp->link.name)
855         || strcmp(argp->link.name, ".") == 0
856         || strcmp(argp->link.name, "..") == 0
857         || bluesky_directory_lookup(dir, argp->link.name) != 0)
858     {
859         result.status = NFS3ERR_EXIST;
860         result.link3res_u.res.linkdir_wcc = wcc;
861         g_mutex_unlock(inode->lock);
862         g_mutex_unlock(dir->lock);
863         async_rpc_send_reply(req, &result);
864         return;
865     }
866
867     if (!bluesky_directory_insert(dir, argp->link.name, inode->inum)) {
868         result.status = NFS3ERR_EXIST;
869         result.link3res_u.res.linkdir_wcc = wcc;
870         g_mutex_unlock(inode->lock);
871         g_mutex_unlock(dir->lock);
872         async_rpc_send_reply(req, &result);
873         return;
874     }
875     inode->nlink++;
876     bluesky_inode_update_ctime(inode, FALSE);
877     bluesky_inode_update_ctime(dir, TRUE);
878
879     result.status = NFS3_OK;
880     wcc.after.present = TRUE;
881     encode_fattr3(&wcc.after.post_op_attr_u.attributes, dir);
882     result.link3res_u.res.file_attributes.present = TRUE;
883     encode_fattr3(&result.link3res_u.res.file_attributes.post_op_attr_u.attributes, inode);
884     result.link3res_u.res.linkdir_wcc = wcc;
885
886     g_mutex_unlock(inode->lock);
887     g_mutex_unlock(dir->lock);
888     async_rpc_send_reply(req, &result);
889 }
890
891 gint bluesky_dirent_compare(gconstpointer a, gconstpointer b,
892                             gpointer unused);
893
894 #define MAX_READDIR_DIRENTS 64
895 void nfsproc3_readdir_3_svc(readdir3args *argp, RPCRequest *req)
896 {
897     readdir3res result;
898     memset(&result, 0, sizeof(result));
899
900     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
901     if (dir == NULL) {
902         result.status = NFS3ERR_STALE;
903         result.readdir3res_u.resfail.present = FALSE;
904         async_rpc_send_reply(req, &result);
905         return;
906     }
907     g_mutex_lock(dir->lock);
908
909     result.status = NFS3_OK;
910     result.readdir3res_u.resok.dir_attributes.present = TRUE;
911     encode_fattr3(&result.readdir3res_u.resok.dir_attributes.post_op_attr_u.attributes, dir);
912     memset(result.readdir3res_u.resok.cookieverf, 0,
913            sizeof(result.readdir3res_u.resok.cookieverf));
914
915     entry3 dirents[MAX_READDIR_DIRENTS];
916     int count = 0;
917
918     BlueSkyDirent start = {NULL, NULL, argp->cookie, 0};
919     GSequenceIter *i = g_sequence_search(dir->dirents, &start,
920                                          bluesky_dirent_compare, NULL);
921
922     while (count < MAX_READDIR_DIRENTS && !g_sequence_iter_is_end(i)) {
923         BlueSkyDirent *d = g_sequence_get(i);
924         dirents[count].fileid = d->inum;
925         dirents[count].name = d->name;
926         dirents[count].cookie = d->cookie;
927         dirents[count].nextentry = NULL;
928         if (count > 0)
929             dirents[count - 1].nextentry = &dirents[count];
930         i = g_sequence_iter_next(i);
931         count++;
932     }
933
934     if (count > 0)
935         result.readdir3res_u.resok.reply.entries = &dirents[0];
936     else
937         result.readdir3res_u.resok.reply.entries = NULL;
938     result.readdir3res_u.resok.reply.eof = g_sequence_iter_is_end(i);
939
940     g_mutex_unlock(dir->lock);
941     async_rpc_send_reply(req, &result);
942 }
943
944 void nfsproc3_readdirplus_3_svc(readdirplus3args *argp, RPCRequest *req)
945 {
946     /* XDR-encoded sizes:
947      *   post_op_attr: 88 bytes
948      *   base readdirplus3resok: 88 + 16 bytes
949      *   base directory entry: 24 bytes + filename
950      *   attributes/fh3: 88 + 8 + filehandle size
951      */
952     size_t dircount = 88 + 16, attrcount = 0;
953     readdirplus3res result;
954     memset(&result, 0, sizeof(result));
955
956     BlueSkyInode *dir = lookup_fh(req, &argp->dir);
957     if (dir == NULL) {
958         result.status = NFS3ERR_STALE;
959         result.readdirplus3res_u.resfail.present = FALSE;
960         async_rpc_send_reply(req, &result);
961         return;
962     }
963     g_mutex_lock(dir->lock);
964
965     result.status = NFS3_OK;
966     result.readdirplus3res_u.resok.dir_attributes.present = TRUE;
967     encode_fattr3(&result.readdirplus3res_u.resok.dir_attributes.post_op_attr_u.attributes, dir);
968     memset(result.readdirplus3res_u.resok.cookieverf, 0,
969            sizeof(result.readdirplus3res_u.resok.cookieverf));
970
971     entryplus3 dirents[MAX_READDIR_DIRENTS];
972     uint64_t fh_bytes[MAX_READDIR_DIRENTS];
973     int count = 0;
974
975     GSequenceIter *i;
976     BlueSkyDirent start = {NULL, NULL, argp->cookie, 0};
977
978     /* Perform a prefetch pass on inodes: for all the inodes we think we will
979      * return information about, try to load each one but don't wait.  This
980      * should let multiple inodes be fetched in parallel, instead of
981      * sequentially in the loop that follows. */
982     i = g_sequence_search(dir->dirents, &start, bluesky_dirent_compare, NULL);
983     while (count < MAX_READDIR_DIRENTS
984            && !g_sequence_iter_is_end(i)
985            && dircount <= argp->dircount
986            && dircount + attrcount <= argp->maxcount)
987     {
988         BlueSkyDirent *d = g_sequence_get(i);
989         BlueSkyInode *inode = bluesky_get_inode(fs, d->inum);
990         if (inode != NULL)
991             bluesky_inode_unref(inode);
992         dircount += 24 + ((strlen(d->name) + 3) & ~3);
993         attrcount += 88 + 8 + 8;
994         i = g_sequence_iter_next(i);
995     }
996
997     i = g_sequence_search(dir->dirents, &start, bluesky_dirent_compare, NULL);
998     count = 0;
999     dircount = 88 + 16;
1000     attrcount = 0;
1001     while (count < MAX_READDIR_DIRENTS && !g_sequence_iter_is_end(i)) {
1002         BlueSkyDirent *d = g_sequence_get(i);
1003         BlueSkyInode *inode = bluesky_get_inode(fs, d->inum);
1004         if (inode != NULL) {
1005             g_mutex_lock(inode->lock);
1006             dircount += 24 + ((strlen(d->name) + 3) & ~3);
1007             attrcount += 88 + 8 + 8;
1008             if (dircount > argp->dircount
1009                 || dircount + attrcount > argp->maxcount)
1010             {
1011                 g_mutex_unlock(inode->lock);
1012                 bluesky_inode_unref(inode);
1013                 break;
1014             }
1015             dirents[count].fileid = d->inum;
1016             dirents[count].name = d->name;
1017             dirents[count].cookie = d->cookie;
1018             dirents[count].nextentry = NULL;
1019             dirents[count].name_attributes.present = TRUE;
1020             encode_fattr3(&dirents[count].name_attributes.post_op_attr_u.attributes, inode);
1021             fh_bytes[count] = GUINT64_TO_BE(d->inum);
1022             dirents[count].name_handle.present = TRUE;
1023             dirents[count].name_handle.post_op_fh3_u.handle.data.data_len = 8;
1024             dirents[count].name_handle.post_op_fh3_u.handle.data.data_val
1025                 = (char *)&fh_bytes[count];
1026             if (count > 0)
1027                 dirents[count - 1].nextentry = &dirents[count];
1028             count++;
1029             g_mutex_unlock(inode->lock);
1030             bluesky_inode_unref(inode);
1031         }
1032         i = g_sequence_iter_next(i);
1033     }
1034
1035     if (count > 0)
1036         result.readdirplus3res_u.resok.reply.entries = &dirents[0];
1037     else
1038         result.readdirplus3res_u.resok.reply.entries = NULL;
1039     result.readdirplus3res_u.resok.reply.eof = g_sequence_iter_is_end(i);
1040
1041     g_mutex_unlock(dir->lock);
1042     async_rpc_send_reply(req, &result);
1043 }
1044
1045 void nfsproc3_fsstat_3_svc(nfs_fh3 *argp, RPCRequest *req)
1046 {
1047     fsstat3res result;
1048     memset(&result, 0, sizeof(result));
1049
1050     BlueSkyInode *inode = lookup_fh(req, argp);
1051     if (inode == NULL) {
1052         result.status = NFS3ERR_STALE;
1053         result.fsstat3res_u.resfail.present = FALSE;
1054         async_rpc_send_reply(req, &result);
1055         return;
1056     }
1057     g_mutex_lock(inode->lock);
1058
1059     result.status = NFS3_OK;
1060     result.fsstat3res_u.resok.obj_attributes.present = TRUE;
1061     encode_fattr3(&result.fsstat3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1062
1063     result.fsstat3res_u.resok.tbytes = (1 << 30);
1064     result.fsstat3res_u.resok.fbytes = (1 << 30);
1065     result.fsstat3res_u.resok.abytes = (1 << 30);
1066     result.fsstat3res_u.resok.tfiles = 0;
1067     result.fsstat3res_u.resok.ffiles = 0;
1068     result.fsstat3res_u.resok.afiles = 0;
1069     result.fsstat3res_u.resok.invarsec = 0;
1070
1071     g_mutex_unlock(inode->lock);
1072     async_rpc_send_reply(req, &result);
1073 }
1074
1075 void nfsproc3_fsinfo_3_svc(nfs_fh3 *argp, RPCRequest *req)
1076 {
1077     fsinfo3res result;
1078     memset(&result, 0, sizeof(result));
1079
1080     BlueSkyInode *inode = bluesky_get_inode(fs, 1);
1081     g_mutex_lock(inode->lock);
1082     result.status = NFS3_OK;
1083     result.fsinfo3res_u.resok.obj_attributes.present = TRUE;
1084     encode_fattr3(&result.fsinfo3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1085     result.fsinfo3res_u.resok.rtmax = NFS_MAXSIZE;
1086     result.fsinfo3res_u.resok.rtpref = NFS_MAXSIZE;
1087     result.fsinfo3res_u.resok.rtmult = NFS_BLOCKSIZE;
1088     result.fsinfo3res_u.resok.wtmax = NFS_MAXSIZE;
1089     result.fsinfo3res_u.resok.wtpref = NFS_MAXSIZE;
1090     result.fsinfo3res_u.resok.wtmult = NFS_BLOCKSIZE;
1091     result.fsinfo3res_u.resok.dtpref = NFS_BLOCKSIZE;
1092     result.fsinfo3res_u.resok.maxfilesize = 0x7fffffffffffffffULL;
1093     result.fsinfo3res_u.resok.time_delta.seconds = 0;
1094     result.fsinfo3res_u.resok.time_delta.nseconds = 1000;
1095     result.fsinfo3res_u.resok.properties
1096         = FSF3_LINK | FSF3_SYMLINK | FSF3_HOMOGENEOUS | FSF3_CANSETTIME;
1097
1098     g_mutex_unlock(inode->lock);
1099     bluesky_inode_unref(inode);
1100     async_rpc_send_reply(req, &result);
1101 }
1102
1103 void nfsproc3_pathconf_3_svc(nfs_fh3 *argp, RPCRequest *req)
1104 {
1105     pathconf3res result;
1106     memset(&result, 0, sizeof(result));
1107
1108     BlueSkyInode *inode = bluesky_get_inode(fs, 1);
1109     g_mutex_lock(inode->lock);
1110     result.status = NFS3_OK;
1111     result.pathconf3res_u.resok.obj_attributes.present = TRUE;
1112     encode_fattr3(&result.pathconf3res_u.resok.obj_attributes.post_op_attr_u.attributes, inode);
1113     result.pathconf3res_u.resok.linkmax = 0xffffffff;
1114     result.pathconf3res_u.resok.name_max = 255;
1115     result.pathconf3res_u.resok.no_trunc = TRUE;
1116     result.pathconf3res_u.resok.chown_restricted = TRUE;
1117     result.pathconf3res_u.resok.case_insensitive = FALSE;
1118     result.pathconf3res_u.resok.case_preserving = TRUE;
1119
1120     g_mutex_unlock(inode->lock);
1121     bluesky_inode_unref(inode);
1122     async_rpc_send_reply(req, &result);
1123 }
1124
1125 void nfsproc3_commit_3_svc(commit3args *argp, RPCRequest *req)
1126 {
1127     commit3res result;
1128     memset(&result, 0, sizeof(result));
1129
1130     result.status = NFS3_OK;
1131
1132     BlueSkyInode *inode = lookup_fh(req, &argp->file);
1133     if (inode == NULL) {
1134         result.status = NFS3ERR_STALE;
1135         async_rpc_send_reply(req, &result);
1136         return;
1137     }
1138
1139     g_mutex_lock(inode->lock);
1140     encode_pre_wcc(&result.commit3res_u.resok.file_wcc, inode);
1141
1142     //bluesky_inode_do_sync(inode);
1143
1144     result.commit3res_u.resok.file_wcc.after.present = TRUE;
1145     encode_fattr3(&result.commit3res_u.resok.file_wcc.after.post_op_attr_u.attributes, inode);
1146
1147     g_mutex_unlock(inode->lock);
1148
1149     async_rpc_send_reply(req, &result);
1150 }