ext2: annotate cache blocks with inode metadata
[minix3.git] / servers / ext2 / write.c
blobfe53161e65682fa6145471d8655701f265139e39
1 /* This file is the counterpart of "read.c". It contains the code for writing
2 * insofar as this is not contained in fs_readwrite().
4 * The entry points into this file are
5 * write_map: write a new block into an inode
6 * new_block: acquire a new block
7 * zero_block: overwrite a block with zeroes
9 * Created (MFS based):
10 * February 2010 (Evgeniy Ivanov)
13 #include "fs.h"
14 #include <string.h>
15 #include <assert.h>
16 #include <sys/param.h>
17 #include "buf.h"
18 #include "inode.h"
19 #include "super.h"
21 static void wr_indir(struct buf *bp, int index, block_t block);
22 static int empty_indir(struct buf *, struct super_block *);
24 /*===========================================================================*
25 * write_map *
26 *===========================================================================*/
27 int write_map(rip, position, new_block, op)
28 struct inode *rip; /* pointer to inode to be changed */
29 off_t position; /* file address to be mapped */
30 block_t new_block; /* block # to be inserted */
31 int op; /* special actions */
33 /* Write a new block into an inode.
35 * If op includes WMAP_FREE, free the block corresponding to that position
36 * in the inode ('new_block' is ignored then). Also free the indirect block
37 * if that was the last entry in the indirect block.
38 * Also free the double/triple indirect block if that was the last entry in
39 * the double/triple indirect block.
40 * It's the only function which should take care about rip->i_blocks counter.
42 int index1 = 0, index2 = 0, index3 = 0; /* indexes in single..triple indirect blocks */
43 long excess, block_pos;
44 char new_ind = 0, new_dbl = 0, new_triple = 0;
45 int single = 0, triple = 0;
46 block_t old_block = NO_BLOCK, b1 = NO_BLOCK, b2 = NO_BLOCK, b3 = NO_BLOCK;
47 struct buf *bp = NULL,
48 *bp_dindir = NULL,
49 *bp_tindir = NULL;
50 static char first_time = TRUE;
51 static long addr_in_block;
52 static long addr_in_block2;
53 static long doub_ind_s;
54 static long triple_ind_s;
55 static long out_range_s;
57 if (first_time) {
58 addr_in_block = rip->i_sp->s_block_size / BLOCK_ADDRESS_BYTES;
59 addr_in_block2 = addr_in_block * addr_in_block;
60 doub_ind_s = EXT2_NDIR_BLOCKS + addr_in_block;
61 triple_ind_s = doub_ind_s + addr_in_block2;
62 out_range_s = triple_ind_s + addr_in_block2 * addr_in_block;
63 first_time = FALSE;
66 block_pos = position / rip->i_sp->s_block_size; /* relative blk # in file */
67 rip->i_dirt = IN_DIRTY; /* inode will be changed */
69 /* Is 'position' to be found in the inode itself? */
70 if (block_pos < EXT2_NDIR_BLOCKS) {
71 if (rip->i_block[block_pos] != NO_BLOCK && (op & WMAP_FREE)) {
72 free_block(rip->i_sp, rip->i_block[block_pos]);
73 rip->i_block[block_pos] = NO_BLOCK;
74 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
75 } else {
76 rip->i_block[block_pos] = new_block;
77 rip->i_blocks += rip->i_sp->s_sectors_in_block;
79 return(OK);
82 /* It is not in the inode, so it must be single, double or triple indirect */
83 if (block_pos < doub_ind_s) {
84 b1 = rip->i_block[EXT2_NDIR_BLOCKS]; /* addr of single indirect block */
85 index1 = block_pos - EXT2_NDIR_BLOCKS;
86 single = TRUE;
87 } else if (block_pos >= out_range_s) { /* TODO: do we need it? */
88 return(EFBIG);
89 } else {
90 /* double or triple indirect block. At first if it's triple,
91 * find double indirect block.
93 excess = block_pos - doub_ind_s;
94 b2 = rip->i_block[EXT2_DIND_BLOCK];
95 if (block_pos >= triple_ind_s) {
96 b3 = rip->i_block[EXT2_TIND_BLOCK];
97 if (b3 == NO_BLOCK && !(op & WMAP_FREE)) {
98 /* Create triple indirect block. */
99 if ( (b3 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
100 ext2_debug("failed to allocate tblock near %d\n", rip->i_block[0]);
101 return(ENOSPC);
103 rip->i_block[EXT2_TIND_BLOCK] = b3;
104 rip->i_blocks += rip->i_sp->s_sectors_in_block;
105 new_triple = TRUE;
107 /* 'b3' is block number for triple indirect block, either old
108 * or newly created.
109 * If there wasn't one and WMAP_FREE is set, 'b3' is NO_BLOCK.
111 if (b3 == NO_BLOCK && (op & WMAP_FREE)) {
112 /* WMAP_FREE and no triple indirect block - then no
113 * double and single indirect blocks either.
115 b1 = b2 = NO_BLOCK;
116 } else {
117 bp_tindir = get_block(rip->i_dev, b3, (new_triple ? NO_READ : NORMAL));
118 if (new_triple) {
119 zero_block(bp_tindir);
120 lmfs_markdirty(bp_tindir);
122 excess = block_pos - triple_ind_s;
123 index3 = excess / addr_in_block2;
124 b2 = rd_indir(bp_tindir, index3);
125 excess = excess % addr_in_block2;
127 triple = TRUE;
130 if (b2 == NO_BLOCK && !(op & WMAP_FREE)) {
131 /* Create the double indirect block. */
132 if ( (b2 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
133 /* Release triple ind blk. */
134 put_block(bp_tindir, INDIRECT_BLOCK);
135 ext2_debug("failed to allocate dblock near %d\n", rip->i_block[0]);
136 return(ENOSPC);
138 if (triple) {
139 wr_indir(bp_tindir, index3, b2); /* update triple indir */
140 lmfs_markdirty(bp_tindir);
141 } else {
142 rip->i_block[EXT2_DIND_BLOCK] = b2;
144 rip->i_blocks += rip->i_sp->s_sectors_in_block;
145 new_dbl = TRUE; /* set flag for later */
148 /* 'b2' is block number for double indirect block, either old
149 * or newly created.
150 * If there wasn't one and WMAP_FREE is set, 'b2' is NO_BLOCK.
152 if (b2 == NO_BLOCK && (op & WMAP_FREE)) {
153 /* WMAP_FREE and no double indirect block - then no
154 * single indirect block either.
156 b1 = NO_BLOCK;
157 } else {
158 bp_dindir = get_block(rip->i_dev, b2, (new_dbl ? NO_READ : NORMAL));
159 if (new_dbl) {
160 zero_block(bp_dindir);
161 lmfs_markdirty(bp_dindir);
163 index2 = excess / addr_in_block;
164 b1 = rd_indir(bp_dindir, index2);
165 index1 = excess % addr_in_block;
167 single = FALSE;
170 /* b1 is now single indirect block or NO_BLOCK; 'index' is index.
171 * We have to create the indirect block if it's NO_BLOCK. Unless
172 * we're freing (WMAP_FREE).
174 if (b1 == NO_BLOCK && !(op & WMAP_FREE)) {
175 if ( (b1 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
176 /* Release dbl and triple indirect blks. */
177 put_block(bp_dindir, INDIRECT_BLOCK);
178 put_block(bp_tindir, INDIRECT_BLOCK);
179 ext2_debug("failed to allocate dblock near %d\n", rip->i_block[0]);
180 return(ENOSPC);
182 if (single) {
183 rip->i_block[EXT2_NDIR_BLOCKS] = b1; /* update inode single indirect */
184 } else {
185 wr_indir(bp_dindir, index2, b1); /* update dbl indir */
186 lmfs_markdirty(bp_dindir);
188 rip->i_blocks += rip->i_sp->s_sectors_in_block;
189 new_ind = TRUE;
192 /* b1 is indirect block's number (unless it's NO_BLOCK when we're
193 * freeing).
195 if (b1 != NO_BLOCK) {
196 bp = get_block(rip->i_dev, b1, (new_ind ? NO_READ : NORMAL) );
197 if (new_ind)
198 zero_block(bp);
199 if (op & WMAP_FREE) {
200 if ((old_block = rd_indir(bp, index1)) != NO_BLOCK) {
201 free_block(rip->i_sp, old_block);
202 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
203 wr_indir(bp, index1, NO_BLOCK);
206 /* Last reference in the indirect block gone? Then
207 * free the indirect block.
209 if (empty_indir(bp, rip->i_sp)) {
210 free_block(rip->i_sp, b1);
211 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
212 b1 = NO_BLOCK;
213 /* Update the reference to the indirect block to
214 * NO_BLOCK - in the double indirect block if there
215 * is one, otherwise in the inode directly.
217 if (single) {
218 rip->i_block[EXT2_NDIR_BLOCKS] = b1;
219 } else {
220 wr_indir(bp_dindir, index2, b1);
221 lmfs_markdirty(bp_dindir);
224 } else {
225 wr_indir(bp, index1, new_block);
226 rip->i_blocks += rip->i_sp->s_sectors_in_block;
228 /* b1 equals NO_BLOCK only when we are freeing up the indirect block. */
229 if(b1 == NO_BLOCK)
230 lmfs_markclean(bp);
231 else
232 lmfs_markdirty(bp);
233 put_block(bp, INDIRECT_BLOCK);
236 /* If the single indirect block isn't there (or was just freed),
237 * see if we have to keep the double indirect block, if any.
238 * If we don't have to keep it, don't bother writing it out.
240 if (b1 == NO_BLOCK && !single && b2 != NO_BLOCK &&
241 empty_indir(bp_dindir, rip->i_sp)) {
242 lmfs_markclean(bp_dindir);
243 free_block(rip->i_sp, b2);
244 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
245 b2 = NO_BLOCK;
246 if (triple) {
247 wr_indir(bp_tindir, index3, b2); /* update triple indir */
248 lmfs_markdirty(bp_tindir);
249 } else {
250 rip->i_block[EXT2_DIND_BLOCK] = b2;
253 /* If the double indirect block isn't there (or was just freed),
254 * see if we have to keep the triple indirect block, if any.
255 * If we don't have to keep it, don't bother writing it out.
257 if (b2 == NO_BLOCK && triple && b3 != NO_BLOCK &&
258 empty_indir(bp_tindir, rip->i_sp)) {
259 lmfs_markclean(bp_tindir);
260 free_block(rip->i_sp, b3);
261 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
262 rip->i_block[EXT2_TIND_BLOCK] = NO_BLOCK;
265 put_block(bp_dindir, INDIRECT_BLOCK); /* release double indirect blk */
266 put_block(bp_tindir, INDIRECT_BLOCK); /* release triple indirect blk */
268 return(OK);
272 /*===========================================================================*
273 * wr_indir *
274 *===========================================================================*/
275 static void wr_indir(bp, index, block)
276 struct buf *bp; /* pointer to indirect block */
277 int index; /* index into *bp */
278 block_t block; /* block to write */
280 /* Given a pointer to an indirect block, write one entry. */
282 if(bp == NULL)
283 panic("wr_indir() on NULL");
285 /* write a block into an indirect block */
286 b_ind(bp)[index] = conv4(le_CPU, block);
290 /*===========================================================================*
291 * empty_indir *
292 *===========================================================================*/
293 static int empty_indir(bp, sb)
294 struct buf *bp; /* pointer to indirect block */
295 struct super_block *sb; /* superblock of device block resides on */
297 /* Return nonzero if the indirect block pointed to by bp contains
298 * only NO_BLOCK entries.
300 long addr_in_block = sb->s_block_size/4; /* 4 bytes per addr */
301 int i;
302 for(i = 0; i < addr_in_block; i++)
303 if(b_ind(bp)[i] != NO_BLOCK)
304 return(0);
305 return(1);
308 /*===========================================================================*
309 * new_block *
310 *===========================================================================*/
311 struct buf *new_block(rip, position)
312 register struct inode *rip; /* pointer to inode */
313 off_t position; /* file pointer */
315 /* Acquire a new block and return a pointer to it. */
316 register struct buf *bp;
317 int r;
318 block_t b;
320 /* Is another block available? */
321 if ( (b = read_map(rip, position, 0)) == NO_BLOCK) {
322 /* Check if this position follows last allocated
323 * block.
325 block_t goal = NO_BLOCK;
326 if (rip->i_last_pos_bl_alloc != 0) {
327 off_t position_diff = position - rip->i_last_pos_bl_alloc;
328 if (rip->i_bsearch == 0) {
329 /* Should never happen, but not critical */
330 ext2_debug("warning, i_bsearch is 0, while\
331 i_last_pos_bl_alloc is not!");
333 if (position_diff <= rip->i_sp->s_block_size) {
334 goal = rip->i_bsearch + 1;
335 } else {
336 /* Non-sequential write operation,
337 * disable preallocation
338 * for this inode.
340 rip->i_preallocation = 0;
341 discard_preallocated_blocks(rip);
345 if ( (b = alloc_block(rip, goal) ) == NO_BLOCK) {
346 err_code = ENOSPC;
347 return(NULL);
349 if ( (r = write_map(rip, position, b, 0)) != OK) {
350 free_block(rip->i_sp, b);
351 err_code = r;
352 ext2_debug("write_map failed\n");
353 return(NULL);
355 rip->i_last_pos_bl_alloc = position;
356 if (position == 0) {
357 /* rip->i_last_pos_bl_alloc points to the block position,
358 * and zero indicates first usage, thus just increment.
360 rip->i_last_pos_bl_alloc++;
364 bp = lmfs_get_block_ino(rip->i_dev, b, NO_READ, rip->i_num,
365 rounddown(position, rip->i_sp->s_block_size));
366 zero_block(bp);
367 return(bp);
370 /*===========================================================================*
371 * zero_block *
372 *===========================================================================*/
373 void zero_block(bp)
374 register struct buf *bp; /* pointer to buffer to zero */
376 /* Zero a block. */
377 ASSERT(lmfs_bytes(bp) > 0);
378 ASSERT(bp->data);
379 memset(b_data(bp), 0, (size_t) lmfs_bytes(bp));
380 lmfs_markdirty(bp);