72467f388144860979b78f0cd4633ff031cef66f
[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  *
21  * If preserve is set to false, this is a hint that the block is about to be
22  * entirely overwritten.  In this case, a dirty block is made available but any
23  * prior contents might be lost.  A value of preserve = TRUE is always safe. */
24 void bluesky_block_touch(BlueSkyInode *inode, uint64_t i, gboolean preserve)
25 {
26     g_return_if_fail(i < inode->blocks->len);
27     BlueSkyBlock *block = &g_array_index(inode->blocks, BlueSkyBlock, i);
28
29     gsize block_len;
30     if (i < inode->blocks->len - 1) {
31         block_len = BLUESKY_BLOCK_SIZE;
32     } else {
33         block_len = inode->size - i * BLUESKY_BLOCK_SIZE;
34     }
35
36     switch (block->type) {
37     case BLUESKY_BLOCK_ZERO:
38         block->dirty = bluesky_string_new(g_malloc0(block_len), block_len);
39         break;
40     case BLUESKY_BLOCK_REF:
41         if (preserve) {
42             // FIXME: locking on the cloudlog?
43             bluesky_block_fetch(inode, block, NULL);
44             bluesky_string_ref(block->ref->data);
45             block->dirty = bluesky_string_dup(block->ref->data);
46         } else {
47             block->dirty = bluesky_string_new(g_malloc0(block_len), block_len);
48         }
49         break;
50     case BLUESKY_BLOCK_DIRTY:
51         block->dirty = bluesky_string_dup(block->dirty);
52         break;
53     }
54
55     if (block->type != BLUESKY_BLOCK_DIRTY)
56         g_atomic_int_add(&inode->fs->cache_dirty, 1);
57
58     block->type = BLUESKY_BLOCK_DIRTY;
59     bluesky_cloudlog_unref(block->ref);
60     block->ref = NULL;
61 }
62
63 /* Set the size of a file.  This will truncate or extend the file as needed.
64  * Newly-allocated bytes are zeroed. */
65 // FIXME
66 void bluesky_file_truncate(BlueSkyInode *inode, uint64_t size)
67 {
68     g_return_if_fail(size <= BLUESKY_MAX_FILE_SIZE);
69
70     if (size == inode->size)
71         return;
72
73     if (bluesky_verbose) {
74         g_log("bluesky/file", G_LOG_LEVEL_DEBUG,
75               "Truncating file to %"PRIi64" bytes", size);
76     }
77
78     uint64_t blocks = (size + BLUESKY_BLOCK_SIZE - 1) / BLUESKY_BLOCK_SIZE;
79
80     /* Calculate number of bytes in the last block of the file */
81     int lastblock_old, lastblock_new;
82     lastblock_old = inode->size % BLUESKY_BLOCK_SIZE;
83     if (lastblock_old == 0 && inode->size > 0)
84         lastblock_old = BLUESKY_BLOCK_SIZE;
85     lastblock_new = size % BLUESKY_BLOCK_SIZE;
86     if (lastblock_new == 0 && size > 0)
87         lastblock_new = BLUESKY_BLOCK_SIZE;
88
89     if (blocks > inode->blocks->len) {
90         /* Need to add new blocks to the end of a file.  New block structures
91          * are automatically zeroed, which initializes them to be pointers to
92          * zero blocks so we don't need to do any more work.  If the
93          * previously-last block in the file is smaller than
94          * BLUESKY_BLOCK_SIZE, extend it to full size. */
95         if (inode->blocks->len > 0) {
96             BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
97                                              inode->blocks->len - 1);
98
99             if (b->type != BLUESKY_BLOCK_ZERO
100                     && lastblock_old < BLUESKY_BLOCK_SIZE) {
101                 bluesky_block_touch(inode, inode->blocks->len - 1, TRUE);
102                 gsize old_size = b->dirty->len;
103                 if (lastblock_old != old_size) {
104                     fprintf(stderr,
105                             "Warning: last block size = %zd, expected %d\n",
106                             old_size, lastblock_old);
107                 }
108                 bluesky_string_resize(b->dirty, BLUESKY_BLOCK_SIZE);
109                 memset(&b->dirty->data[old_size], 0,
110                        BLUESKY_BLOCK_SIZE - old_size);
111             }
112         }
113
114         g_array_set_size(inode->blocks, blocks);
115     } else if (blocks < inode->blocks->len) {
116         /* Delete blocks from a file.  Must reclaim memory. */
117         for (guint i = blocks; i < inode->blocks->len; i++) {
118             BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock, i);
119             if (b->type == BLUESKY_BLOCK_DIRTY)
120                 g_atomic_int_add(&inode->fs->cache_dirty, -1);
121             bluesky_string_unref(b->dirty);
122             bluesky_cloudlog_unref(b->ref);
123         }
124         g_array_set_size(inode->blocks, blocks);
125     }
126
127     /* Ensure the new last block of the file is properly sized.  If the block
128      * is extended, newly-added bytes must be zeroed. */
129     if (blocks > 0) {
130         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
131                                          blocks - 1);
132
133         gboolean need_resize = TRUE;
134         if (b->type == BLUESKY_BLOCK_ZERO)
135             need_resize = FALSE;
136         else if (size < inode->size && lastblock_new == BLUESKY_BLOCK_SIZE)
137             need_resize = FALSE;
138
139         if (need_resize) {
140             bluesky_block_touch(inode, blocks - 1, TRUE);
141             gsize old_size = b->dirty->len;
142             gsize new_size = size - (blocks - 1) * BLUESKY_BLOCK_SIZE;
143
144             bluesky_string_resize(b->dirty, new_size);
145
146             if (new_size > old_size) {
147                 memset(&b->dirty->data[old_size], 0, new_size - old_size);
148             }
149         }
150     }
151
152     inode->size = size;
153     bluesky_inode_update_ctime(inode, 1);
154 }
155
156 void bluesky_file_write(BlueSkyInode *inode, uint64_t offset,
157                         const char *data, gint len)
158 {
159     g_return_if_fail(inode->type == BLUESKY_REGULAR);
160     g_return_if_fail(offset < inode->size);
161     g_return_if_fail(len <= inode->size - offset);
162
163     if (len == 0)
164         return;
165
166     // TODO: Optimization: If we are entirely overwriting a block we don't need
167     // to fetch it frm storage first.  We don't yet handle the case where the
168     // partial last block of a file is entirely overwritten.
169     while (len > 0) {
170         uint64_t block_num = offset / BLUESKY_BLOCK_SIZE;
171         gint block_offset = offset % BLUESKY_BLOCK_SIZE;
172         gint bytes = MIN(BLUESKY_BLOCK_SIZE - block_offset, len);
173
174         gboolean preserve = TRUE;
175         if (block_offset == 0 && bytes == BLUESKY_BLOCK_SIZE) {
176             preserve = FALSE;
177         }
178         bluesky_block_touch(inode, block_num, preserve);
179         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
180                                          block_num);
181         memcpy(&b->dirty->data[block_offset], data, bytes);
182
183         offset += bytes;
184         data += bytes;
185         len -= bytes;
186     }
187
188     bluesky_inode_update_ctime(inode, 1);
189 }
190
191 void bluesky_file_read(BlueSkyInode *inode, uint64_t offset,
192                        char *buf, gint len)
193 {
194     if (len == 0 && offset <= inode->size)
195         return;
196
197     g_return_if_fail(inode->type == BLUESKY_REGULAR);
198     g_return_if_fail(offset < inode->size);
199     g_return_if_fail(len <= inode->size - offset);
200
201     BlueSkyProfile *profile = bluesky_profile_get();
202
203     bluesky_profile_add_event(profile,
204                               g_strdup_printf("Start file read prefetch"));
205     uint64_t start_block, end_block;
206     start_block = offset / BLUESKY_BLOCK_SIZE;
207     end_block = (offset + len - 1) / BLUESKY_BLOCK_SIZE;
208     for (uint64_t i = start_block; i <= end_block; i++) {
209         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
210                                          i);
211         if (b->type == BLUESKY_BLOCK_REF)
212             bluesky_cloudlog_prefetch(b->ref);
213     }
214
215     bluesky_profile_add_event(profile,
216                               g_strdup_printf("End file read prefetch"));
217
218     while (len > 0) {
219         uint64_t block_num = offset / BLUESKY_BLOCK_SIZE;
220         gint block_offset = offset % BLUESKY_BLOCK_SIZE;
221         gint bytes = MIN(BLUESKY_BLOCK_SIZE - block_offset, len);
222
223         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
224                                          block_num);
225         if (b->type == BLUESKY_BLOCK_ZERO) {
226             memset(buf, 0, bytes);
227         } else {
228             BlueSkyRCStr *data = NULL;
229             if (b->type == BLUESKY_BLOCK_REF) {
230                 bluesky_block_fetch(inode, b, NULL);
231                 data = b->ref->data;
232             } else if (b->type == BLUESKY_BLOCK_DIRTY) {
233                 data = b->dirty;
234             }
235             memcpy(buf, &data->data[block_offset], bytes);
236         }
237
238         offset += bytes;
239         buf += bytes;
240         len -= bytes;
241     }
242
243     bluesky_profile_add_event(profile,
244                               g_strdup_printf("BlueSky read complete"));
245 }
246
247 void bluesky_block_fetch(BlueSkyInode *inode, BlueSkyBlock *block,
248                          BlueSkyStoreAsync *barrier)
249 {
250     if (block->type != BLUESKY_BLOCK_REF)
251         return;
252
253     g_mutex_lock(block->ref->lock);
254     bluesky_cloudlog_fetch(block->ref);
255     g_mutex_unlock(block->ref->lock);
256     block->type = BLUESKY_BLOCK_REF;
257 }
258
259 /* Write the given block to cloud-backed storage and mark it clean. */
260 void bluesky_block_flush(BlueSkyInode *inode, BlueSkyBlock *block,
261                          GList **log_items)
262 {
263     BlueSkyFS *fs = inode->fs;
264
265     if (block->type != BLUESKY_BLOCK_DIRTY)
266         return;
267
268     g_assert(block->ref == NULL);
269
270     BlueSkyCloudLog *cloudlog = bluesky_cloudlog_new(fs, NULL);
271     cloudlog->type = LOGTYPE_DATA;
272     cloudlog->inum = inode->inum;
273     cloudlog->data = block->dirty;      // String ownership is transferred
274     bluesky_cloudlog_stats_update(cloudlog, 1);
275     bluesky_cloudlog_sync(cloudlog);
276     bluesky_cloudlog_ref(cloudlog);     // Reference for log_items list
277     *log_items = g_list_prepend(*log_items, cloudlog);
278     bluesky_cloudlog_insert(cloudlog);
279
280     block->ref = cloudlog;              // Uses initial reference from _new()
281
282     block->type = BLUESKY_BLOCK_REF;
283     block->dirty = NULL;
284     g_atomic_int_add(&fs->cache_dirty, -1);
285 }
286
287 /* Flush all blocks in a file to stable storage. */
288 void bluesky_file_flush(BlueSkyInode *inode, GList **log_items)
289 {
290     g_return_if_fail(inode->type == BLUESKY_REGULAR);
291
292     for (int i = 0; i < inode->blocks->len; i++) {
293         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock, i);
294         bluesky_block_flush(inode, b, log_items);
295     }
296 }
297
298 /* Drop clean data blocks for a file from cache. */
299 void bluesky_file_drop_cached(BlueSkyInode *inode)
300 {
301     g_return_if_fail(inode->type == BLUESKY_REGULAR);
302
303     for (int i = 0; i < inode->blocks->len; i++) {
304         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock, i);
305         if (b->type == BLUESKY_BLOCK_REF) {
306             g_mutex_lock(b->ref->lock);
307             if (b->ref->data != NULL
308                 && g_atomic_int_get(&b->ref->data_lock_count) == 0
309                 && (b->ref->location_flags != 0))
310             {
311                 bluesky_cloudlog_stats_update(b->ref, -1);
312                 bluesky_string_unref(b->ref->data);
313                 b->ref->data = NULL;
314                 bluesky_cloudlog_stats_update(b->ref, 1);
315             }
316             g_mutex_unlock(b->ref->lock);
317         }
318     }
319 }