Tweaks to code that handles read-modify-write on data blocks
[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                 g_warn_if_fail(lastblock_old != old_size);
104                 bluesky_string_resize(b->dirty, BLUESKY_BLOCK_SIZE);
105                 memset(&b->dirty->data[old_size], 0,
106                        BLUESKY_BLOCK_SIZE - old_size);
107             }
108         }
109
110         g_array_set_size(inode->blocks, blocks);
111     } else if (blocks < inode->blocks->len) {
112         /* Delete blocks from a file.  Must reclaim memory. */
113         for (guint i = blocks; i < inode->blocks->len; i++) {
114             BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock, i);
115             if (b->type == BLUESKY_BLOCK_DIRTY)
116                 g_atomic_int_add(&inode->fs->cache_dirty, -1);
117             bluesky_string_unref(b->dirty);
118             bluesky_cloudlog_unref(b->ref);
119         }
120         g_array_set_size(inode->blocks, blocks);
121     }
122
123     /* Ensure the new last block of the file is properly sized.  If the block
124      * is extended, newly-added bytes must be zeroed. */
125     if (blocks > 0) {
126         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
127                                          blocks - 1);
128
129         gboolean need_resize = TRUE;
130         if (b->type == BLUESKY_BLOCK_ZERO)
131             need_resize = FALSE;
132         else if (size < inode->size && lastblock_new == BLUESKY_BLOCK_SIZE)
133             need_resize = FALSE;
134
135         if (need_resize) {
136             bluesky_block_touch(inode, blocks - 1, TRUE);
137             gsize old_size = b->dirty->len;
138             gsize new_size = size - (blocks - 1) * BLUESKY_BLOCK_SIZE;
139
140             bluesky_string_resize(b->dirty, new_size);
141
142             if (new_size > old_size) {
143                 memset(&b->dirty->data[old_size], 0, new_size - old_size);
144             }
145         }
146     }
147
148     inode->size = size;
149     bluesky_inode_update_ctime(inode, 1);
150 }
151
152 void bluesky_file_write(BlueSkyInode *inode, uint64_t offset,
153                         const char *data, gint len)
154 {
155     g_return_if_fail(inode->type == BLUESKY_REGULAR);
156     g_return_if_fail(offset < inode->size);
157     g_return_if_fail(len <= inode->size - offset);
158
159     if (len == 0)
160         return;
161
162     // TODO: Optimization: If we are entirely overwriting a block we don't need
163     // to fetch it frm storage first.  We don't yet handle the case where the
164     // partial last block of a file is entirely overwritten.
165     while (len > 0) {
166         uint64_t block_num = offset / BLUESKY_BLOCK_SIZE;
167         gint block_offset = offset % BLUESKY_BLOCK_SIZE;
168         gint bytes = MIN(BLUESKY_BLOCK_SIZE - block_offset, len);
169
170         gboolean preserve = TRUE;
171         if (block_offset == 0 && bytes == BLUESKY_BLOCK_SIZE) {
172             preserve = FALSE;
173         }
174         bluesky_block_touch(inode, block_num, preserve);
175         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
176                                          block_num);
177         memcpy(&b->dirty->data[block_offset], data, bytes);
178
179         offset += bytes;
180         data += bytes;
181         len -= bytes;
182     }
183
184     bluesky_inode_update_ctime(inode, 1);
185 }
186
187 void bluesky_file_read(BlueSkyInode *inode, uint64_t offset,
188                        char *buf, gint len)
189 {
190     if (len == 0 && offset <= inode->size)
191         return;
192
193     g_return_if_fail(inode->type == BLUESKY_REGULAR);
194     g_return_if_fail(offset < inode->size);
195     g_return_if_fail(len <= inode->size - offset);
196
197     BlueSkyProfile *profile = bluesky_profile_get();
198
199     bluesky_profile_add_event(profile,
200                               g_strdup_printf("Start file read prefetch"));
201     uint64_t start_block, end_block;
202     start_block = offset / BLUESKY_BLOCK_SIZE;
203     end_block = (offset + len - 1) / BLUESKY_BLOCK_SIZE;
204     for (uint64_t i = start_block; i <= end_block; i++) {
205         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
206                                          i);
207         if (b->type == BLUESKY_BLOCK_REF)
208             bluesky_cloudlog_prefetch(b->ref);
209     }
210
211     bluesky_profile_add_event(profile,
212                               g_strdup_printf("End file read prefetch"));
213
214     while (len > 0) {
215         uint64_t block_num = offset / BLUESKY_BLOCK_SIZE;
216         gint block_offset = offset % BLUESKY_BLOCK_SIZE;
217         gint bytes = MIN(BLUESKY_BLOCK_SIZE - block_offset, len);
218
219         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
220                                          block_num);
221         if (b->type == BLUESKY_BLOCK_ZERO) {
222             memset(buf, 0, bytes);
223         } else {
224             BlueSkyRCStr *data = NULL;
225             if (b->type == BLUESKY_BLOCK_REF) {
226                 bluesky_block_fetch(inode, b, NULL);
227                 data = b->ref->data;
228             } else if (b->type == BLUESKY_BLOCK_DIRTY) {
229                 data = b->dirty;
230             }
231             memcpy(buf, &data->data[block_offset], bytes);
232         }
233
234         offset += bytes;
235         buf += bytes;
236         len -= bytes;
237     }
238
239     bluesky_profile_add_event(profile,
240                               g_strdup_printf("BlueSky read complete"));
241 }
242
243 void bluesky_block_fetch(BlueSkyInode *inode, BlueSkyBlock *block,
244                          BlueSkyStoreAsync *barrier)
245 {
246     if (block->type != BLUESKY_BLOCK_REF)
247         return;
248
249     g_mutex_lock(block->ref->lock);
250     bluesky_cloudlog_fetch(block->ref);
251     g_mutex_unlock(block->ref->lock);
252     block->type = BLUESKY_BLOCK_REF;
253 }
254
255 /* Write the given block to cloud-backed storage and mark it clean. */
256 void bluesky_block_flush(BlueSkyInode *inode, BlueSkyBlock *block,
257                          GList **log_items)
258 {
259     BlueSkyFS *fs = inode->fs;
260
261     if (block->type != BLUESKY_BLOCK_DIRTY)
262         return;
263
264     g_assert(block->ref == NULL);
265
266     BlueSkyCloudLog *cloudlog = bluesky_cloudlog_new(fs, NULL);
267     cloudlog->type = LOGTYPE_DATA;
268     cloudlog->inum = inode->inum;
269     cloudlog->data = block->dirty;      // String ownership is transferred
270     bluesky_cloudlog_stats_update(cloudlog, 1);
271     bluesky_cloudlog_sync(cloudlog);
272     bluesky_cloudlog_ref(cloudlog);     // Reference for log_items list
273     *log_items = g_list_prepend(*log_items, cloudlog);
274     bluesky_cloudlog_insert(cloudlog);
275
276     block->ref = cloudlog;              // Uses initial reference from _new()
277
278     block->type = BLUESKY_BLOCK_REF;
279     block->dirty = NULL;
280     g_atomic_int_add(&fs->cache_dirty, -1);
281 }
282
283 /* Flush all blocks in a file to stable storage. */
284 void bluesky_file_flush(BlueSkyInode *inode, GList **log_items)
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, b, log_items);
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_REF) {
302             g_mutex_lock(b->ref->lock);
303             if (b->ref->data != NULL
304                 && g_atomic_int_get(&b->ref->data_lock_count) == 0
305                 && (b->ref->location_flags != 0))
306             {
307                 bluesky_cloudlog_stats_update(b->ref, -1);
308                 bluesky_string_unref(b->ref->data);
309                 b->ref->data = NULL;
310                 bluesky_cloudlog_stats_update(b->ref, 1);
311             }
312             g_mutex_unlock(b->ref->lock);
313         }
314     }
315 }