Track memory usage statistics for cached data.
[bluesky.git] / bluesky / file.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  * TODO: Licensing
7  */
8
9 #include <stdint.h>
10 #include <glib.h>
11 #include <string.h>
12 #include <inttypes.h>
13
14 #include "bluesky-private.h"
15
16 /* Core filesystem: handling of regular files and caching of file data. */
17
18 /* Mark a given block dirty and make sure that data is faulted in so that it
19  * can be written to. */
20 void bluesky_block_touch(BlueSkyInode *inode, uint64_t i)
21 {
22     g_return_if_fail(i < inode->blocks->len);
23     BlueSkyBlock *block = &g_array_index(inode->blocks, BlueSkyBlock, i);
24
25     gsize block_len;
26     if (i < inode->blocks->len - 1) {
27         block_len = BLUESKY_BLOCK_SIZE;
28     } else {
29         block_len = inode->size - i * BLUESKY_BLOCK_SIZE;
30     }
31
32     switch (block->type) {
33     case BLUESKY_BLOCK_ZERO:
34         g_print("Allocating zero block of size %zd\n", block_len);
35         block->data = bluesky_string_new(g_malloc0(block_len), block_len);
36         break;
37     case BLUESKY_BLOCK_REF:
38         bluesky_block_fetch(inode->fs, block, NULL);
39         g_assert(block->type == BLUESKY_BLOCK_CACHED);
40         /* Fall through */
41     case BLUESKY_BLOCK_CACHED:
42     case BLUESKY_BLOCK_DIRTY:
43         block->data = bluesky_string_dup(block->data);
44         break;
45     }
46
47     if (block->type != BLUESKY_BLOCK_CACHED
48             && block->type != BLUESKY_BLOCK_DIRTY)
49         g_atomic_int_add(&inode->fs->cache_total, 1);
50     if (block->type != BLUESKY_BLOCK_DIRTY)
51         g_atomic_int_add(&inode->fs->cache_dirty, 1);
52
53     block->type = BLUESKY_BLOCK_DIRTY;
54 }
55
56 /* Set the size of a file.  This will truncate or extend the file as needed.
57  * Newly-allocated bytes are zeroed. */
58 void bluesky_file_truncate(BlueSkyInode *inode, uint64_t size)
59 {
60     g_return_if_fail(size <= BLUESKY_MAX_FILE_SIZE);
61
62     if (size == inode->size)
63         return;
64
65     g_print("Truncating file to %"PRIi64" bytes\n", size);
66
67     uint64_t blocks = (size + BLUESKY_BLOCK_SIZE - 1) / BLUESKY_BLOCK_SIZE;
68
69     if (blocks > inode->blocks->len) {
70         /* Need to add new blocks to the end of a file.  New block structures
71          * are automatically zeroed, which initializes them to be pointers to
72          * zero blocks so we don't need to do any more work.  If the
73          * previously-last block in the file is smaller than
74          * BLUESKY_BLOCK_SIZE, extend it to full size. */
75         if (inode->blocks->len > 0) {
76             BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
77                                              inode->blocks->len - 1);
78
79             if (b->type != BLUESKY_BLOCK_ZERO
80                     && (b->type == BLUESKY_BLOCK_REF
81                         || b->data->len < BLUESKY_BLOCK_SIZE)) {
82                 bluesky_block_touch(inode, inode->blocks->len - 1);
83                 gsize old_size = b->data->len;
84                 bluesky_string_resize(b->data, BLUESKY_BLOCK_SIZE);
85                 memset(&b->data->data[old_size], 0,
86                        BLUESKY_BLOCK_SIZE - old_size);
87             }
88         }
89
90         g_array_set_size(inode->blocks, blocks);
91     } else if (blocks < inode->blocks->len) {
92         /* Delete blocks from a file.  Must reclaim memory. */
93         for (guint i = inode->blocks->len; i < blocks; i++) {
94             BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock, i);
95             g_free(b->ref);
96             if (b->type == BLUESKY_BLOCK_CACHED
97                     || b->type == BLUESKY_BLOCK_DIRTY)
98                 g_atomic_int_add(&inode->fs->cache_total, -1);
99             if (b->type == BLUESKY_BLOCK_DIRTY)
100                 g_atomic_int_add(&inode->fs->cache_dirty, -1);
101             bluesky_string_unref(b->data);
102         }
103         g_array_set_size(inode->blocks, blocks);
104     }
105
106     /* Ensure the new last block of the file is properly sized.  If the block
107      * is extended, newly-added bytes must be zeroed. */
108     if (blocks > 0) {
109         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
110                                          blocks - 1);
111
112         if (b->type != BLUESKY_BLOCK_ZERO) {
113             bluesky_block_touch(inode, blocks - 1);
114             gsize old_size = b->data->len;
115             gsize new_size = size - (blocks - 1) * BLUESKY_BLOCK_SIZE;
116
117             bluesky_string_resize(b->data, new_size);
118
119             if (new_size > old_size) {
120                 memset(&b->data->data[old_size], 0, new_size - old_size);
121             }
122         }
123     }
124
125     inode->size = size;
126     bluesky_inode_update_ctime(inode, 1);
127 }
128
129 void bluesky_file_write(BlueSkyInode *inode, uint64_t offset,
130                         const char *data, gint len)
131 {
132     g_print("Write %d bytes at offset %"PRIi64"\n", len, offset);
133
134     g_return_if_fail(inode->type == BLUESKY_REGULAR);
135     g_return_if_fail(offset < inode->size);
136     g_return_if_fail(len <= inode->size - offset);
137
138     if (len == 0)
139         return;
140
141     while (len > 0) {
142         uint64_t block_num = offset / BLUESKY_BLOCK_SIZE;
143         gint block_offset = offset % BLUESKY_BLOCK_SIZE;
144         gint bytes = MIN(BLUESKY_BLOCK_SIZE - block_offset, len);
145
146         bluesky_block_touch(inode, block_num);
147         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
148                                          block_num);
149         memcpy(&b->data->data[block_offset], data, bytes);
150
151         offset += bytes;
152         data += bytes;
153         len -= bytes;
154     }
155
156     bluesky_inode_update_ctime(inode, 1);
157 }
158
159 void bluesky_file_read(BlueSkyInode *inode, uint64_t offset,
160                        char *buf, gint len)
161 {
162     g_print("Read %d bytes at offset %"PRIi64"\n", len, offset);
163
164     if (len == 0 && offset <= inode->size)
165         return;
166
167     g_return_if_fail(inode->type == BLUESKY_REGULAR);
168     g_return_if_fail(offset < inode->size);
169     g_return_if_fail(len <= inode->size - offset);
170
171     /* Start fetches on any data blocks that we will need for this read. */
172     BlueSkyStoreAsync *barrier = bluesky_store_async_new(inode->fs->store);
173     barrier->op = STORE_OP_BARRIER;
174     uint64_t start_block, end_block;
175     start_block = offset / BLUESKY_BLOCK_SIZE;
176     end_block = (offset + len - 1) / BLUESKY_BLOCK_SIZE;
177     g_print("Start prefetch on blocks %"PRIi64" .. %"PRIi64"\n",
178             start_block, end_block);
179     for (uint64_t i = start_block; i <= end_block; i++) {
180         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
181                                          i);
182         if (b->type == BLUESKY_BLOCK_REF)
183             bluesky_block_fetch(inode->fs, b, barrier);
184     }
185     bluesky_store_async_submit(barrier);
186     bluesky_store_async_wait(barrier);
187     bluesky_store_async_unref(barrier);
188     g_print("Prefetch complete.\n");
189
190     while (len > 0) {
191         uint64_t block_num = offset / BLUESKY_BLOCK_SIZE;
192         gint block_offset = offset % BLUESKY_BLOCK_SIZE;
193         gint bytes = MIN(BLUESKY_BLOCK_SIZE - block_offset, len);
194
195         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
196                                          block_num);
197         switch (b->type) {
198         case BLUESKY_BLOCK_ZERO:
199             memset(buf, 0, bytes);
200             break;
201         case BLUESKY_BLOCK_REF:
202             bluesky_block_fetch(inode->fs, b, NULL);
203             /* Fall through */
204         case BLUESKY_BLOCK_CACHED:
205         case BLUESKY_BLOCK_DIRTY:
206             memcpy(buf, &b->data->data[block_offset], bytes);
207             break;
208         }
209
210         offset += bytes;
211         buf += bytes;
212         len -= bytes;
213     }
214 }
215
216 /* Read the given block from cloud-backed storage if the data is not already
217  * cached. */
218 static void block_fetch_completion(BlueSkyStoreAsync *async, gpointer data)
219 {
220     BlueSkyBlock *block = (BlueSkyBlock *)data;
221
222     bluesky_string_unref(block->data);
223     block->data = async->data;
224     bluesky_string_ref(block->data);
225     block->type = BLUESKY_BLOCK_CACHED;
226 }
227
228 void bluesky_block_fetch(BlueSkyFS *fs, BlueSkyBlock *block,
229                          BlueSkyStoreAsync *barrier)
230 {
231     if (block->type != BLUESKY_BLOCK_REF)
232         return;
233
234     BlueSkyStoreAsync *async = bluesky_store_async_new(fs->store);
235     async->op = STORE_OP_GET;
236     async->key = g_strdup(block->ref);
237     bluesky_store_async_add_notifier(async, (GFunc)block_fetch_completion, block);
238     bluesky_store_async_submit(async);
239
240     if (barrier != NULL)
241         bluesky_store_add_barrier(barrier, async);
242     else
243         bluesky_store_async_wait(async);
244
245     bluesky_store_async_unref(async);
246     g_atomic_int_add(&fs->cache_total, 1);
247 }
248
249 /* Write the given block to cloud-backed storage and mark it clean. */
250 void bluesky_block_flush(BlueSkyFS *fs, BlueSkyBlock *block,
251                          BlueSkyStoreAsync *barrier)
252 {
253     if (block->type != BLUESKY_BLOCK_DIRTY)
254         return;
255
256     BlueSkyRCStr *data = block->data;
257
258     GChecksum *csum = g_checksum_new(G_CHECKSUM_SHA256);
259     g_checksum_update(csum, (const guchar *)data->data, data->len);
260     gchar *name = g_strdup(g_checksum_get_string(csum));
261
262     /* Store the file data asynchronously, and don't bother waiting for a
263      * response. */
264     BlueSkyStoreAsync *async = bluesky_store_async_new(fs->store);
265     async->op = STORE_OP_PUT;
266     async->key = g_strdup(name);
267     bluesky_string_ref(data);
268     async->data = data;
269     bluesky_store_async_submit(async);
270     if (barrier != NULL)
271         bluesky_store_add_barrier(barrier, async);
272     bluesky_store_async_unref(async);
273
274     g_free(block->ref);
275     block->ref = name;
276
277     block->type = BLUESKY_BLOCK_CACHED;
278     g_atomic_int_add(&fs->cache_dirty, -1);
279
280     g_checksum_free(csum);
281 }
282
283 /* Flush all blocks in a file to stable storage. */
284 void bluesky_file_flush(BlueSkyInode *inode, BlueSkyStoreAsync *barrier)
285 {
286     g_return_if_fail(inode->type == BLUESKY_REGULAR);
287
288     for (int i = 0; i < inode->blocks->len; i++) {
289         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock, i);
290         bluesky_block_flush(inode->fs, b, barrier);
291     }
292 }
293
294 /* Drop clean data blocks for a file from cache. */
295 void bluesky_file_drop_cached(BlueSkyInode *inode)
296 {
297     g_return_if_fail(inode->type == BLUESKY_REGULAR);
298
299     for (int i = 0; i < inode->blocks->len; i++) {
300         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock, i);
301         if (b->type == BLUESKY_BLOCK_CACHED) {
302             g_log("bluesky/cache", G_LOG_LEVEL_DEBUG,
303                   "Dropping block %d of inode %"PRIu64" from cache",
304                   i, inode->inum);
305
306             g_log("bluesky/cache", G_LOG_LEVEL_DEBUG,
307                   "  (reference count was %d)", b->data->refcount);
308             bluesky_string_unref(b->data);
309             b->data = NULL;
310             b->type = BLUESKY_BLOCK_REF;
311             g_atomic_int_add(&inode->fs->cache_total, -1);
312         }
313     }
314 }