Add proper per-file copyright notices/licenses and top-level license.
[bluesky.git] / bluesky / inode.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 #include <stdio.h>
32 #include <stdint.h>
33 #include <inttypes.h>
34 #include <glib.h>
35 #include <string.h>
36
37 #include "bluesky-private.h"
38
39 static void inode_fetch_task(gpointer a, gpointer b);
40
41 /* Core filesystem.  Different proxies, such as the NFSv3 one, interface to
42  * this, but the core actually tracks the data which is stored.  So far we just
43  * implement an in-memory filesystem, but eventually this will be state which
44  * is persisted to the cloud. */
45
46 /* Return the current time, in microseconds since the epoch. */
47 int64_t bluesky_get_current_time()
48 {
49     GTimeVal t;
50     g_get_current_time(&t);
51     return (int64_t)t.tv_sec * 1000000 + t.tv_usec;
52 }
53
54 /* Update an inode to indicate that a modification was made.  This increases
55  * the change counter, updates the ctime to the current time, and optionally
56  * updates the mtime.  This also makes the inode contents subject to writeback
57  * to storage in the future.  inode must already be locked. */
58 void bluesky_inode_update_ctime(BlueSkyInode *inode, gboolean update_mtime)
59 {
60     int64_t now = bluesky_get_current_time();
61     inode->change_count++;
62     inode->ctime = now;
63     if (update_mtime)
64         inode->mtime = now;
65
66     if (inode->change_time == 0)
67         inode->change_time = now;
68
69 #if 0
70     if (bluesky_options.writethrough_cache)
71         bluesky_file_flush(inode, NULL);
72 #endif
73
74     g_mutex_lock(inode->fs->lock);
75     bluesky_list_unlink(&inode->fs->unlogged_list, inode->unlogged_list);
76     inode->unlogged_list = bluesky_list_prepend(&inode->fs->unlogged_list, inode);
77     bluesky_list_unlink(&inode->fs->accessed_list, inode->accessed_list);
78     inode->accessed_list = bluesky_list_prepend(&inode->fs->accessed_list, inode);
79     g_mutex_unlock(inode->fs->lock);
80 }
81
82 /* Unfortunately a glib hash table is only guaranteed to be able to store
83  * 32-bit keys if we use the key directly.  If we want 64-bit inode numbers,
84  * we'll have to allocate memory to store the 64-bit inumber, and use a pointer
85  * to it.  Rather than allocate the memory for the key, we'll just include a
86  * pointer to the 64-bit inum stored in the inode itself, so that we don't need
87  * to do any more memory management.  */
88 static guint bluesky_fs_key_hash_func(gconstpointer key)
89 {
90     uint64_t inum = *(const uint64_t *)key;
91     return (guint)inum;
92 }
93
94 static gboolean bluesky_fs_key_equal_func(gconstpointer a, gconstpointer b)
95 {
96     uint64_t i1 = *(const uint64_t *)a;
97     uint64_t i2 = *(const uint64_t *)b;
98     return i1 == i2;
99 }
100
101 /* Filesystem-level operations.  A filesystem is like a directory tree that we
102  * are willing to export. */
103 BlueSkyFS *bluesky_new_fs(gchar *name)
104 {
105     BlueSkyFS *fs = g_new0(BlueSkyFS, 1);
106     fs->lock = g_mutex_new();
107     fs->name = g_strdup(name);
108     fs->inodes = g_hash_table_new(bluesky_fs_key_hash_func,
109                                   bluesky_fs_key_equal_func);
110     fs->next_inum = BLUESKY_ROOT_INUM + 1;
111     fs->store = bluesky_store_new("file");
112     fs->flushd_lock = g_mutex_new();
113     fs->flushd_cond = g_cond_new();
114     fs->locations = g_hash_table_new(bluesky_cloudlog_hash,
115                                      bluesky_cloudlog_equal);
116     fs->inode_map = g_sequence_new(NULL);
117
118     fs->log_state = g_new0(BlueSkyCloudLogState, 1);
119     fs->log_state->data = g_string_new("");
120     fs->log_state->latest_cleaner_seq_seen = -1;
121     fs->log_state->uploads_pending_lock = g_mutex_new();
122     fs->log_state->uploads_pending_cond = g_cond_new();
123
124     bluesky_cloudlog_threads_init(fs);
125     fs->inode_fetch_thread_pool = g_thread_pool_new(inode_fetch_task, NULL,
126                                                     bluesky_max_threads,
127                                                     FALSE, NULL);
128
129     return fs;
130 }
131
132 BlueSkyFS *bluesky_init_fs(gchar *name, BlueSkyStore *store,
133                            const gchar *master_key)
134 {
135     BlueSkyFS *fs = bluesky_new_fs(name);
136     fs->master_key = g_strdup(master_key);
137     fs->keys = g_new(BlueSkyCryptKeys, 1);
138     bluesky_crypt_derive_keys(fs->keys, master_key);
139     fs->store = store;
140     fs->log = bluesky_log_new("journal");
141     fs->log->fs = fs;
142
143     if (bluesky_checkpoint_load(fs)) {
144         g_print("Filesystem checkpoint loaded, starting journal replay...\n");
145         bluesky_replay(fs);
146         g_print("Journal replay complete, filesystem ready.\n");
147     } else {
148         /* Initialize a fresh filesystem */
149         g_print("Initializing new filesystem...\n");
150         BlueSkyInode *root = bluesky_new_inode(BLUESKY_ROOT_INUM, fs,
151                                                BLUESKY_DIRECTORY);
152         root->nlink = 1;
153         root->mode = 0755;
154         bluesky_insert_inode(fs, root);
155         bluesky_inode_update_ctime(root, TRUE);
156         bluesky_inode_do_sync(root);
157     }
158
159     bluesky_cleaner_thread_launch(fs);
160
161     return fs;
162 }
163
164 /* Inode reference counting. */
165 void bluesky_inode_ref(BlueSkyInode *inode)
166 {
167     g_atomic_int_inc(&inode->refcount);
168 }
169
170 /* Free most of the resources used by an inode structure, but do not free the
171  * inode itself.  Can be used if the inode data will be reloaded from
172  * serialized form to clear out old information first. */
173 void bluesky_inode_free_resources(BlueSkyInode *inode)
174 {
175     switch (inode->type) {
176     case BLUESKY_REGULAR:
177         if (inode->blocks != NULL) {
178             for (int i = 0; i < inode->blocks->len; i++) {
179                 BlueSkyBlock *b = &g_array_index(inode->blocks,
180                                                  BlueSkyBlock, i);
181                 if (b->type == BLUESKY_BLOCK_DIRTY) {
182                     g_error("Deleting an inode with dirty file data!");
183                 }
184                 bluesky_cloudlog_unref(b->ref);
185                 bluesky_string_unref(b->dirty);
186             }
187             g_array_unref(inode->blocks);
188             inode->blocks = NULL;
189         }
190         break;
191
192     case BLUESKY_DIRECTORY:
193         if (inode->dirhash != NULL)
194             g_hash_table_destroy(inode->dirhash);
195         inode->dirhash = NULL;
196         if (inode->dirhash_folded != NULL)
197             g_hash_table_destroy(inode->dirhash_folded);
198         inode->dirhash_folded = NULL;
199         if (inode->dirents != NULL)
200             g_sequence_free(inode->dirents);
201         inode->dirents = NULL;
202         break;
203
204     case BLUESKY_SYMLINK:
205         g_free(inode->symlink_contents);
206         inode->symlink_contents = NULL;
207         break;
208
209     default:
210         break;
211     }
212 }
213
214 void bluesky_inode_unref(BlueSkyInode *inode)
215 {
216     if (g_atomic_int_dec_and_test(&inode->refcount)) {
217         if (bluesky_verbose) {
218             g_log("bluesky/inode", G_LOG_LEVEL_DEBUG,
219                   "Reference count for inode %"PRIu64" dropped to zero.",
220                   inode->inum);
221         }
222
223         /* Sanity check: Is the inode clean? */
224         if (inode->change_commit < inode->change_count
225                 || inode->accessed_list != NULL
226                 || inode->unlogged_list != NULL
227                 || inode->dirty_list != NULL) {
228             g_warning("Dropping inode which is not clean (commit %"PRIi64" < change %"PRIi64"; accessed_list = %p; dirty_list = %p)\n", inode->change_commit, inode->change_count, inode->accessed_list, inode->dirty_list);
229         }
230
231         /* These shouldn't be needed, but in case the above warning fires and
232          * we delete the inode anyway, we ought to be sure the inode is not on
233          * any LRU list. */
234         g_mutex_lock(inode->fs->lock);
235         bluesky_list_unlink(&inode->fs->accessed_list, inode->accessed_list);
236         bluesky_list_unlink(&inode->fs->dirty_list, inode->dirty_list);
237         bluesky_list_unlink(&inode->fs->unlogged_list, inode->unlogged_list);
238         g_mutex_unlock(inode->fs->lock);
239
240         bluesky_inode_free_resources(inode);
241
242         g_mutex_free(inode->lock);
243         g_free(inode);
244     }
245 }
246
247 /* Allocate a fresh inode number which has not been used before within a
248  * filesystem.  fs must already be locked. */
249 uint64_t bluesky_fs_alloc_inode(BlueSkyFS *fs)
250 {
251     uint64_t inum;
252
253     inum = fs->next_inum;
254     fs->next_inum++;
255
256     return inum;
257 }
258
259 /* Perform type-specification initialization of an inode.  Normally performed
260  * in bluesky_new_inode, but can be separated if an inode is created first,
261  * then deserialized. */
262 void bluesky_init_inode(BlueSkyInode *i, BlueSkyFileType type)
263 {
264     i->type = type;
265
266     switch (type) {
267     case BLUESKY_REGULAR:
268         i->blocks = g_array_new(FALSE, TRUE, sizeof(BlueSkyBlock));
269         break;
270     case BLUESKY_DIRECTORY:
271         i->dirents = g_sequence_new(bluesky_dirent_destroy);
272         i->dirhash = g_hash_table_new(g_str_hash, g_str_equal);
273         i->dirhash_folded = g_hash_table_new(g_str_hash, g_str_equal);
274         break;
275     default:
276         break;
277     }
278 }
279
280 BlueSkyInode *bluesky_new_inode(uint64_t inum, BlueSkyFS *fs,
281                                 BlueSkyFileType type)
282 {
283     BlueSkyInode *i = g_new0(BlueSkyInode, 1);
284
285     i->lock = g_mutex_new();
286     i->refcount = 1;
287     i->fs = fs;
288     i->inum = inum;
289     i->change_count = 1;
290     bluesky_init_inode(i, type);
291
292     return i;
293 }
294
295 /* Issue a prefetch hint for an inode.  This signals that the inode may be
296  * needed soon.  Does not return any useful data. */
297 void bluesky_inode_prefetch(BlueSkyFS *fs, uint64_t inum)
298 {
299     BlueSkyInode *inode = NULL;
300
301     g_mutex_lock(fs->lock);
302     inode = (BlueSkyInode *)g_hash_table_lookup(fs->inodes, &inum);
303
304     if (inode != NULL) {
305         /* Inode is already available, no need for any prefetching... */
306         g_mutex_unlock(fs->lock);
307         return;
308     }
309
310     InodeMapEntry *entry = bluesky_inode_map_lookup(fs->inode_map, inum, 0);
311     if (entry != NULL) {
312         bluesky_cloudlog_prefetch(entry->item);
313     }
314
315     g_mutex_unlock(fs->lock);
316     return;
317 }
318
319 /* Retrieve an inode from the filesystem.  Eventually this will be a cache and
320  * so we might need to go fetch the inode from elsewhere; for now all
321  * filesystem state is stored here.  inode is returned with a reference held
322  * but not locked. */
323 BlueSkyInode *bluesky_get_inode(BlueSkyFS *fs, uint64_t inum)
324 {
325     BlueSkyInode *inode = NULL;
326
327     if (inum == 0) {
328         return NULL;
329     }
330
331     g_mutex_lock(fs->lock);
332     inode = (BlueSkyInode *)g_hash_table_lookup(fs->inodes, &inum);
333
334     if (inode == NULL) {
335         bluesky_inode_fetch(fs, inum);
336         inode = (BlueSkyInode *)g_hash_table_lookup(fs->inodes, &inum);
337     }
338
339     if (inode != NULL) {
340         bluesky_inode_ref(inode);
341
342         /* FIXME: We assume we can atomically update the in-memory access time
343          * without a lock. */
344         inode->access_time = bluesky_get_current_time();
345     }
346
347     g_mutex_unlock(fs->lock);
348
349     return inode;
350 }
351
352 /* Insert an inode into the filesystem inode cache.  fs should be locked. */
353 void bluesky_insert_inode(BlueSkyFS *fs, BlueSkyInode *inode)
354 {
355     g_hash_table_insert(fs->inodes, &inode->inum, inode);
356 }
357
358 /* Start writeback of an inode and all associated data. */
359 void bluesky_inode_start_sync(BlueSkyInode *inode)
360 {
361     GList *log_items = NULL;
362
363     if (inode->type == BLUESKY_REGULAR)
364         bluesky_file_flush(inode, &log_items);
365
366     BlueSkyCloudLog *cloudlog = bluesky_serialize_inode(inode);
367
368     bluesky_cloudlog_unref(inode->committed_item);
369     inode->committed_item = cloudlog;
370
371     bluesky_cloudlog_sync(cloudlog);
372     bluesky_cloudlog_ref(cloudlog);
373     log_items = g_list_prepend(log_items, cloudlog);
374
375     /* Wait for all log items to be committed to disk. */
376     bluesky_log_finish_all(log_items);
377
378     /* Mark the inode as clean */
379     inode->change_commit = inode->change_count;
380     inode->change_time = 0;
381     g_mutex_lock(inode->fs->lock);
382     bluesky_list_unlink(&inode->fs->unlogged_list, inode->unlogged_list);
383     inode->unlogged_list = NULL;
384
385     /* Since a new version of the inode has been written to the log, also
386      * schedule a future flush of the new data to cloud storage. */
387     bluesky_list_unlink(&inode->fs->dirty_list, inode->dirty_list);
388     inode->dirty_list = bluesky_list_prepend(&inode->fs->dirty_list, inode);
389     inode->change_cloud = inode->change_count;
390
391     g_mutex_unlock(inode->fs->lock);
392 }
393
394 /* Write back an inode and all associated data and wait for completion.  Inode
395  * should already be locked. */
396 void bluesky_inode_do_sync(BlueSkyInode *inode)
397 {
398     if (bluesky_verbose) {
399         g_log("bluesky/inode", G_LOG_LEVEL_DEBUG,
400             "Synchronous writeback for inode %"PRIu64"...", inode->inum);
401     }
402     bluesky_inode_start_sync(inode);
403     if (bluesky_verbose) {
404         g_log("bluesky/inode", G_LOG_LEVEL_DEBUG,
405               "Writeback for inode %"PRIu64" complete", inode->inum);
406     }
407 }
408
409 static void inode_fetch_task(gpointer a, gpointer b)
410 {
411     BlueSkyInode *inode = (BlueSkyInode *)a;
412
413     bluesky_profile_set((BlueSkyProfile *)inode->private_data);
414
415     BlueSkyCloudLog *item = inode->committed_item;
416     inode->committed_item = NULL;
417     g_print("Completing fetch of inode %"PRIu64"...\n", inode->inum);
418
419     g_mutex_lock(item->lock);
420     bluesky_cloudlog_fetch(item);
421     if (!bluesky_deserialize_inode(inode, item))
422         g_print("Error deserializing inode %"PRIu64"\n", inode->inum);
423     g_mutex_unlock(item->lock);
424
425     inode->access_time = bluesky_get_current_time();
426     g_mutex_lock(inode->fs->lock);
427     bluesky_list_unlink(&inode->fs->accessed_list, inode->accessed_list);
428     inode->accessed_list = bluesky_list_prepend(&inode->fs->accessed_list, inode);
429     g_mutex_unlock(inode->fs->lock);
430
431     g_mutex_unlock(inode->lock);
432     bluesky_cloudlog_unref(item);
433     bluesky_inode_unref(inode);
434 }
435
436 /* Fetch an inode from stable storage.  The fetch can be performed
437  * asynchronously: the in-memory inode is allocated, but not filled with data
438  * immediately.  It is kept locked until it has been filled in, so any users
439  * should try to acquire the lock on the inode before accessing any data.  The
440  * fs lock must be held. */
441 void bluesky_inode_fetch(BlueSkyFS *fs, uint64_t inum)
442 {
443     InodeMapEntry *entry = bluesky_inode_map_lookup(fs->inode_map, inum, 0);
444     if (entry == NULL)
445         return;
446
447     /* Non-portable behavior: We take the inode lock here, and release it in
448      * the fetching thread.  This works with the default Linux pthreads
449      * implementation but is not guaranteed. */
450
451     BlueSkyInode *inode = bluesky_new_inode(inum, fs, BLUESKY_PENDING);
452     inode->change_count = 0;
453     bluesky_inode_ref(inode);       // Extra ref held by fetching process
454     g_mutex_lock(inode->lock);
455
456     inode->committed_item = entry->item;
457     bluesky_cloudlog_ref(entry->item);
458     bluesky_insert_inode(fs, inode);
459
460     inode->private_data = bluesky_profile_get();
461     g_thread_pool_push(fs->inode_fetch_thread_pool, inode, NULL);
462 }