Do not pad the final block of a file with zeroes.
[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
13 #include "bluesky-private.h"
14
15 /* Core filesystem: handling of regular files and caching of file data. */
16
17 /* Mark a given block dirty and make sure that data is faulted in so that it
18  * can be written to. */
19 void bluesky_block_touch(BlueSkyInode *inode, uint64_t i)
20 {
21     g_return_if_fail(i < inode->blocks->len);
22     BlueSkyBlock *block = &g_array_index(inode->blocks, BlueSkyBlock, i);
23
24     gsize block_len;
25     if (i < inode->blocks->len - 1) {
26         block_len = BLUESKY_BLOCK_SIZE;
27     } else {
28         block_len = inode->size - i * BLUESKY_BLOCK_SIZE;
29     }
30
31     switch (block->type) {
32     case BLUESKY_BLOCK_ZERO:
33         g_print("Allocating zero block of size %zd\n", block_len);
34         block->data = bluesky_string_new(g_malloc0(block_len), block_len);
35         break;
36     case BLUESKY_BLOCK_REF:
37         bluesky_block_fetch(inode->fs, block);
38         g_assert(block->type == BLUESKY_BLOCK_CACHED);
39         /* Fall through */
40     case BLUESKY_BLOCK_CACHED:
41     case BLUESKY_BLOCK_DIRTY:
42         block->data = bluesky_string_dup(block->data);
43         break;
44     }
45
46     block->type = BLUESKY_BLOCK_DIRTY;
47 }
48
49 /* Set the size of a file.  This will truncate or extend the file as needed.
50  * Newly-allocated bytes are zeroed. */
51 void bluesky_file_truncate(BlueSkyInode *inode, uint64_t size)
52 {
53     g_return_if_fail(size <= BLUESKY_MAX_FILE_SIZE);
54
55     if (size == inode->size)
56         return;
57
58     uint64_t blocks = (size + BLUESKY_BLOCK_SIZE - 1) / BLUESKY_BLOCK_SIZE;
59
60     if (blocks > inode->blocks->len) {
61         /* Need to add new blocks to the end of a file.  New block structures
62          * are automatically zeroed, which initializes them to be pointers to
63          * zero blocks so we don't need to do any more work. */
64         g_array_set_size(inode->blocks, blocks);
65     } else if (blocks < inode->blocks->len) {
66         /* Delete blocks from a file.  Must reclaim memory. */
67         for (guint i = inode->blocks->len; i < blocks; i++) {
68             BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock, i);
69             g_free(b->ref);
70             bluesky_string_unref(b->data);
71         }
72         g_array_set_size(inode->blocks, blocks);
73     }
74
75     /* Ensure the last block of the file is properly sized.  If the block is
76      * extended, newly-added bytes must be zeroed. */
77     if (blocks > 0) {
78         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
79                                          blocks - 1);
80
81         if (b->type != BLUESKY_BLOCK_ZERO) {
82             bluesky_block_touch(inode, blocks - 1);
83             gsize old_size = b->data->len;
84             gsize new_size = size - (blocks - 1) * BLUESKY_BLOCK_SIZE;
85
86             bluesky_string_resize(b->data, new_size);
87
88             if (new_size > old_size) {
89                 memset(&b->data->data[old_size], 0, new_size - old_size);
90             }
91         }
92     }
93
94     inode->size = size;
95     bluesky_inode_update_ctime(inode, 1);
96 }
97
98 void bluesky_file_write(BlueSkyInode *inode, uint64_t offset,
99                         const char *data, gint len)
100 {
101     g_return_if_fail(inode->type == BLUESKY_REGULAR);
102     g_return_if_fail(offset < inode->size);
103     g_return_if_fail(len <= inode->size - offset);
104
105     if (len == 0)
106         return;
107
108     while (len > 0) {
109         uint64_t block_num = offset / BLUESKY_BLOCK_SIZE;
110         gint block_offset = offset % BLUESKY_BLOCK_SIZE;
111         gint bytes = MIN(BLUESKY_BLOCK_SIZE - block_offset, len);
112
113         bluesky_block_touch(inode, block_num);
114         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
115                                          block_num);
116         memcpy(&b->data->data[block_offset], data, bytes);
117         bluesky_block_flush(inode->fs, b);
118
119         offset += bytes;
120         data += bytes;
121         len -= bytes;
122     }
123
124     bluesky_inode_update_ctime(inode, 1);
125     bluesky_inode_flush(inode->fs, inode);
126 }
127
128 void bluesky_file_read(BlueSkyInode *inode, uint64_t offset,
129                        char *buf, gint len)
130 {
131     g_return_if_fail(inode->type == BLUESKY_REGULAR);
132     g_return_if_fail(offset < inode->size);
133     g_return_if_fail(len <= inode->size - offset);
134
135     while (len > 0) {
136         uint64_t block_num = offset / BLUESKY_BLOCK_SIZE;
137         gint block_offset = offset % BLUESKY_BLOCK_SIZE;
138         gint bytes = MIN(BLUESKY_BLOCK_SIZE - block_offset, len);
139
140         BlueSkyBlock *b = &g_array_index(inode->blocks, BlueSkyBlock,
141                                          block_num);
142         switch (b->type) {
143         case BLUESKY_BLOCK_ZERO:
144             memset(buf, 0, bytes);
145             break;
146         case BLUESKY_BLOCK_REF:
147             bluesky_block_fetch(inode->fs, b);
148             /* Fall through */
149         case BLUESKY_BLOCK_CACHED:
150         case BLUESKY_BLOCK_DIRTY:
151             memcpy(buf, &b->data->data[block_offset], bytes);
152             break;
153         }
154
155         offset += bytes;
156         buf += bytes;
157         len -= bytes;
158     }
159 }
160
161 /* Read the given block from cloud-backed storage if the data is not already
162  * cached. */
163 void bluesky_block_fetch(BlueSkyFS *fs, BlueSkyBlock *block)
164 {
165     if (block->type != BLUESKY_BLOCK_REF)
166         return;
167
168     BlueSkyRCStr *string = bluesky_store_get(fs->store, block->ref);
169
170     bluesky_string_unref(block->data);
171     block->data = string;
172     block->type = BLUESKY_BLOCK_CACHED;
173 }
174
175 /* Write the given block to cloud-backed storage and mark it clean. */
176 void bluesky_block_flush(BlueSkyFS *fs, BlueSkyBlock *block)
177 {
178     if (block->type != BLUESKY_BLOCK_DIRTY)
179         return;
180
181     BlueSkyRCStr *data = block->data;
182
183     GChecksum *csum = g_checksum_new(G_CHECKSUM_SHA256);
184     g_checksum_update(csum, data->data, data->len);
185     gchar *name = g_strdup(g_checksum_get_string(csum));
186
187     bluesky_store_put(fs->store, name, data);
188     g_free(block->ref);
189     block->ref = name;
190
191     /* block->type = BLUESKY_BLOCK_CACHED; */
192     bluesky_string_unref(block->data);
193     block->data = NULL;
194     block->type = BLUESKY_BLOCK_REF;
195
196     g_checksum_free(csum);
197     //bluesky_string_unref(data);
198 }