VM: make mapping types explicit
[minix.git] / servers / ext2 / write.c
blob020282afd22d318b829f03792e4382e6bcd3ecf8
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 "buf.h"
16 #include "inode.h"
17 #include "super.h"
19 static void wr_indir(struct buf *bp, int index, block_t block);
20 static int empty_indir(struct buf *, struct super_block *);
22 /*===========================================================================*
23 * write_map *
24 *===========================================================================*/
25 int write_map(rip, position, new_block, op)
26 struct inode *rip; /* pointer to inode to be changed */
27 off_t position; /* file address to be mapped */
28 block_t new_block; /* block # to be inserted */
29 int op; /* special actions */
31 /* Write a new block into an inode.
33 * If op includes WMAP_FREE, free the block corresponding to that position
34 * in the inode ('new_block' is ignored then). Also free the indirect block
35 * if that was the last entry in the indirect block.
36 * Also free the double/triple indirect block if that was the last entry in
37 * the double/triple indirect block.
38 * It's the only function which should take care about rip->i_blocks counter.
40 int index1, index2, index3; /* indexes in single..triple indirect blocks */
41 long excess, block_pos;
42 char new_ind = 0, new_dbl = 0, new_triple = 0;
43 int single = 0, triple = 0;
44 block_t old_block = NO_BLOCK, b1 = NO_BLOCK, b2 = NO_BLOCK, b3 = NO_BLOCK;
45 struct buf *bp = NULL,
46 *bp_dindir = NULL,
47 *bp_tindir = NULL;
48 static char first_time = TRUE;
49 static long addr_in_block;
50 static long addr_in_block2;
51 static long doub_ind_s;
52 static long triple_ind_s;
53 static long out_range_s;
55 if (first_time) {
56 addr_in_block = rip->i_sp->s_block_size / BLOCK_ADDRESS_BYTES;
57 addr_in_block2 = addr_in_block * addr_in_block;
58 doub_ind_s = EXT2_NDIR_BLOCKS + addr_in_block;
59 triple_ind_s = doub_ind_s + addr_in_block2;
60 out_range_s = triple_ind_s + addr_in_block2 * addr_in_block;
61 first_time = FALSE;
64 block_pos = position / rip->i_sp->s_block_size; /* relative blk # in file */
65 rip->i_dirt = DIRTY; /* inode will be changed */
67 /* Is 'position' to be found in the inode itself? */
68 if (block_pos < EXT2_NDIR_BLOCKS) {
69 if (rip->i_block[block_pos] != NO_BLOCK && (op & WMAP_FREE)) {
70 free_block(rip->i_sp, rip->i_block[block_pos]);
71 rip->i_block[block_pos] = NO_BLOCK;
72 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
73 } else {
74 rip->i_block[block_pos] = new_block;
75 rip->i_blocks += rip->i_sp->s_sectors_in_block;
77 return(OK);
80 /* It is not in the inode, so it must be single, double or triple indirect */
81 if (block_pos < doub_ind_s) {
82 b1 = rip->i_block[EXT2_NDIR_BLOCKS]; /* addr of single indirect block */
83 index1 = block_pos - EXT2_NDIR_BLOCKS;
84 single = TRUE;
85 } else if (block_pos >= out_range_s) { /* TODO: do we need it? */
86 return(EFBIG);
87 } else {
88 /* double or triple indirect block. At first if it's triple,
89 * find double indirect block.
91 excess = block_pos - doub_ind_s;
92 b2 = rip->i_block[EXT2_DIND_BLOCK];
93 if (block_pos >= triple_ind_s) {
94 b3 = rip->i_block[EXT2_TIND_BLOCK];
95 if (b3 == NO_BLOCK && !(op & WMAP_FREE)) {
96 /* Create triple indirect block. */
97 if ( (b3 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
98 ext2_debug("failed to allocate tblock near %d\n", rip->i_block[0]);
99 return(ENOSPC);
101 rip->i_block[EXT2_TIND_BLOCK] = b3;
102 rip->i_blocks += rip->i_sp->s_sectors_in_block;
103 new_triple = TRUE;
105 /* 'b3' is block number for triple indirect block, either old
106 * or newly created.
107 * If there wasn't one and WMAP_FREE is set, 'b3' is NO_BLOCK.
109 if (b3 == NO_BLOCK && (op & WMAP_FREE)) {
110 /* WMAP_FREE and no triple indirect block - then no
111 * double and single indirect blocks either.
113 b1 = b2 = NO_BLOCK;
114 } else {
115 bp_tindir = get_block(rip->i_dev, b3, (new_triple ? NO_READ : NORMAL));
116 if (new_triple) {
117 zero_block(bp_tindir);
118 bp_tindir->b_dirt = DIRTY;
120 excess = block_pos - triple_ind_s;
121 index3 = excess / addr_in_block2;
122 b2 = rd_indir(bp_tindir, index3);
123 excess = excess % addr_in_block2;
125 triple = TRUE;
128 if (b2 == NO_BLOCK && !(op & WMAP_FREE)) {
129 /* Create the double indirect block. */
130 if ( (b2 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
131 /* Release triple ind blk. */
132 put_block(bp_tindir, INDIRECT_BLOCK);
133 ext2_debug("failed to allocate dblock near %d\n", rip->i_block[0]);
134 return(ENOSPC);
136 if (triple) {
137 wr_indir(bp_tindir, index3, b2); /* update triple indir */
138 bp_tindir->b_dirt = DIRTY;
139 } else {
140 rip->i_block[EXT2_DIND_BLOCK] = b2;
142 rip->i_blocks += rip->i_sp->s_sectors_in_block;
143 new_dbl = TRUE; /* set flag for later */
146 /* 'b2' is block number for double indirect block, either old
147 * or newly created.
148 * If there wasn't one and WMAP_FREE is set, 'b2' is NO_BLOCK.
150 if (b2 == NO_BLOCK && (op & WMAP_FREE)) {
151 /* WMAP_FREE and no double indirect block - then no
152 * single indirect block either.
154 b1 = NO_BLOCK;
155 } else {
156 bp_dindir = get_block(rip->i_dev, b2, (new_dbl ? NO_READ : NORMAL));
157 if (new_dbl) {
158 zero_block(bp_dindir);
159 bp_dindir->b_dirt = DIRTY;
161 index2 = excess / addr_in_block;
162 b1 = rd_indir(bp_dindir, index2);
163 index1 = excess % addr_in_block;
165 single = FALSE;
168 /* b1 is now single indirect block or NO_BLOCK; 'index' is index.
169 * We have to create the indirect block if it's NO_BLOCK. Unless
170 * we're freing (WMAP_FREE).
172 if (b1 == NO_BLOCK && !(op & WMAP_FREE)) {
173 if ( (b1 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
174 /* Release dbl and triple indirect blks. */
175 put_block(bp_dindir, INDIRECT_BLOCK);
176 put_block(bp_tindir, INDIRECT_BLOCK);
177 ext2_debug("failed to allocate dblock near %d\n", rip->i_block[0]);
178 return(ENOSPC);
180 if (single) {
181 rip->i_block[EXT2_NDIR_BLOCKS] = b1; /* update inode single indirect */
182 } else {
183 wr_indir(bp_dindir, index2, b1); /* update dbl indir */
184 bp_dindir->b_dirt = DIRTY;
186 rip->i_blocks += rip->i_sp->s_sectors_in_block;
187 new_ind = TRUE;
190 /* b1 is indirect block's number (unless it's NO_BLOCK when we're
191 * freeing).
193 if (b1 != NO_BLOCK) {
194 bp = get_block(rip->i_dev, b1, (new_ind ? NO_READ : NORMAL) );
195 if (new_ind)
196 zero_block(bp);
197 if (op & WMAP_FREE) {
198 if ((old_block = rd_indir(bp, index1)) != NO_BLOCK) {
199 free_block(rip->i_sp, old_block);
200 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
201 wr_indir(bp, index1, NO_BLOCK);
204 /* Last reference in the indirect block gone? Then
205 * free the indirect block.
207 if (empty_indir(bp, rip->i_sp)) {
208 free_block(rip->i_sp, b1);
209 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
210 b1 = NO_BLOCK;
211 /* Update the reference to the indirect block to
212 * NO_BLOCK - in the double indirect block if there
213 * is one, otherwise in the inode directly.
215 if (single) {
216 rip->i_block[EXT2_NDIR_BLOCKS] = b1;
217 } else {
218 wr_indir(bp_dindir, index2, b1);
219 bp_dindir->b_dirt = DIRTY;
222 } else {
223 wr_indir(bp, index1, new_block);
224 rip->i_blocks += rip->i_sp->s_sectors_in_block;
226 /* b1 equals NO_BLOCK only when we are freeing up the indirect block. */
227 bp->b_dirt = (b1 == NO_BLOCK) ? CLEAN : DIRTY;;
228 put_block(bp, INDIRECT_BLOCK);
231 /* If the single indirect block isn't there (or was just freed),
232 * see if we have to keep the double indirect block, if any.
233 * If we don't have to keep it, don't bother writing it out.
235 if (b1 == NO_BLOCK && !single && b2 != NO_BLOCK &&
236 empty_indir(bp_dindir, rip->i_sp)) {
237 bp_dindir->b_dirt = CLEAN;
238 free_block(rip->i_sp, b2);
239 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
240 b2 = NO_BLOCK;
241 if (triple) {
242 wr_indir(bp_tindir, index3, b2); /* update triple indir */
243 bp_tindir->b_dirt = DIRTY;
244 } else {
245 rip->i_block[EXT2_DIND_BLOCK] = b2;
248 /* If the double indirect block isn't there (or was just freed),
249 * see if we have to keep the triple indirect block, if any.
250 * If we don't have to keep it, don't bother writing it out.
252 if (b2 == NO_BLOCK && triple && b3 != NO_BLOCK &&
253 empty_indir(bp_tindir, rip->i_sp)) {
254 bp_tindir->b_dirt = CLEAN;
255 free_block(rip->i_sp, b3);
256 rip->i_blocks -= rip->i_sp->s_sectors_in_block;
257 rip->i_block[EXT2_TIND_BLOCK] = NO_BLOCK;
260 put_block(bp_dindir, INDIRECT_BLOCK); /* release double indirect blk */
261 put_block(bp_tindir, INDIRECT_BLOCK); /* release triple indirect blk */
263 return(OK);
267 /*===========================================================================*
268 * wr_indir *
269 *===========================================================================*/
270 static void wr_indir(bp, index, block)
271 struct buf *bp; /* pointer to indirect block */
272 int index; /* index into *bp */
273 block_t block; /* block to write */
275 /* Given a pointer to an indirect block, write one entry. */
277 if(bp == NULL)
278 panic("wr_indir() on NULL");
280 /* write a block into an indirect block */
281 bp->b_ind[index] = conv4(le_CPU, block);
285 /*===========================================================================*
286 * empty_indir *
287 *===========================================================================*/
288 static int empty_indir(bp, sb)
289 struct buf *bp; /* pointer to indirect block */
290 struct super_block *sb; /* superblock of device block resides on */
292 /* Return nonzero if the indirect block pointed to by bp contains
293 * only NO_BLOCK entries.
295 long addr_in_block = sb->s_block_size/4; /* 4 bytes per addr */
296 int i;
297 for(i = 0; i < addr_in_block; i++)
298 if(bp->b_ind[i] != NO_BLOCK)
299 return(0);
300 return(1);
303 /*===========================================================================*
304 * new_block *
305 *===========================================================================*/
306 struct buf *new_block(rip, position)
307 register struct inode *rip; /* pointer to inode */
308 off_t position; /* file pointer */
310 /* Acquire a new block and return a pointer to it. */
311 register struct buf *bp;
312 int r;
313 block_t b;
315 /* Is another block available? */
316 if ( (b = read_map(rip, position)) == NO_BLOCK) {
317 /* Check if this position follows last allocated
318 * block.
320 block_t goal = NO_BLOCK;
321 if (rip->i_last_pos_bl_alloc != 0) {
322 off_t position_diff = position - rip->i_last_pos_bl_alloc;
323 if (rip->i_bsearch == 0) {
324 /* Should never happen, but not critical */
325 ext2_debug("warning, i_bsearch is 0, while\
326 i_last_pos_bl_alloc is not!");
328 if (position_diff <= rip->i_sp->s_block_size) {
329 goal = rip->i_bsearch + 1;
330 } else {
331 /* Non-sequential write operation,
332 * disable preallocation
333 * for this inode.
335 rip->i_preallocation = 0;
336 discard_preallocated_blocks(rip);
340 if ( (b = alloc_block(rip, goal) ) == NO_BLOCK) {
341 err_code = ENOSPC;
342 return(NULL);
344 if ( (r = write_map(rip, position, b, 0)) != OK) {
345 free_block(rip->i_sp, b);
346 err_code = r;
347 ext2_debug("write_map failed\n");
348 return(NULL);
350 rip->i_last_pos_bl_alloc = position;
351 if (position == 0) {
352 /* rip->i_last_pos_bl_alloc points to the block position,
353 * and zero indicates first usage, thus just increment.
355 rip->i_last_pos_bl_alloc++;
359 bp = get_block(rip->i_dev, b, NO_READ);
360 zero_block(bp);
361 return(bp);
364 /*===========================================================================*
365 * zero_block *
366 *===========================================================================*/
367 void zero_block(bp)
368 register struct buf *bp; /* pointer to buffer to zero */
370 /* Zero a block. */
371 ASSERT(bp->b_bytes > 0);
372 ASSERT(bp->bp);
373 memset(bp->b_data, 0, (size_t) bp->b_bytes);
374 bp->b_dirt = DIRTY;