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