xref: /minix/minix/fs/ext2/write.c (revision 83133719)
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().
3  *
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
8  *
9  * Created (MFS based):
10  *   February 2010 (Evgeniy Ivanov)
11  */
12 
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"
20 
21 static void wr_indir(struct buf *bp, int index, block_t block);
22 static int empty_indir(struct buf *, struct super_block *);
23 
24 /*===========================================================================*
25  *				write_map				     *
26  *===========================================================================*/
27 int write_map(rip, position, new_wblock, op)
28 struct inode *rip;		/* pointer to inode to be changed */
29 off_t position;			/* file address to be mapped */
30 block_t new_wblock;		/* block # to be inserted */
31 int op;				/* special actions */
32 {
33 /* Write a new block into an inode.
34  *
35  * If op includes WMAP_FREE, free the block corresponding to that position
36  * in the inode ('new_wblock' 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.
41  */
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;
56 
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;
64   }
65 
66   block_pos = position / rip->i_sp->s_block_size; /* relative blk # in file */
67   rip->i_dirt = IN_DIRTY;		/* inode will be changed */
68 
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_wblock;
77 		rip->i_blocks += rip->i_sp->s_sectors_in_block;
78 	}
79 	return(OK);
80   }
81 
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.
92 	 */
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);
102 			}
103 			rip->i_block[EXT2_TIND_BLOCK] = b3;
104 			rip->i_blocks += rip->i_sp->s_sectors_in_block;
105 			new_triple = TRUE;
106 		}
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.
110 		 */
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.
114 		 */
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);
121 			}
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;
126 		}
127 		triple = TRUE;
128 	}
129 
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);
137 		}
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;
143 		}
144 		rip->i_blocks += rip->i_sp->s_sectors_in_block;
145 		new_dbl = TRUE; /* set flag for later */
146 	}
147 
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.
151 	 */
152 	if (b2 == NO_BLOCK && (op & WMAP_FREE)) {
153 	/* WMAP_FREE and no double indirect block - then no
154 	 * single indirect block either.
155 	 */
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);
162 		}
163 		index2 = excess / addr_in_block;
164 		b1 = rd_indir(bp_dindir, index2);
165 		index1 = excess % addr_in_block;
166 	}
167 	single = FALSE;
168   }
169 
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).
173    */
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);
181 	}
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);
187 	}
188 	rip->i_blocks += rip->i_sp->s_sectors_in_block;
189 	new_ind = TRUE;
190   }
191 
192   /* b1 is indirect block's number (unless it's NO_BLOCK when we're
193    * freeing).
194    */
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);
204 		}
205 
206 		/* Last reference in the indirect block gone? Then
207 		 * free the indirect block.
208 		 */
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.
216 			 */
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);
222 			}
223 		}
224 	} else {
225 		wr_indir(bp, index1, new_wblock);
226 		rip->i_blocks += rip->i_sp->s_sectors_in_block;
227 	}
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);
234   }
235 
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.
239    */
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;
251 	}
252   }
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.
256    */
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;
263   }
264 
265   put_block(bp_dindir, INDIRECT_BLOCK);	/* release double indirect blk */
266   put_block(bp_tindir, INDIRECT_BLOCK);	/* release triple indirect blk */
267 
268   return(OK);
269 }
270 
271 
272 /*===========================================================================*
273  *				wr_indir				     *
274  *===========================================================================*/
275 static void wr_indir(bp, wrindex, block)
276 struct buf *bp;			/* pointer to indirect block */
277 int wrindex;			/* index into *bp */
278 block_t block;			/* block to write */
279 {
280 /* Given a pointer to an indirect block, write one entry. */
281 
282   if(bp == NULL)
283 	panic("wr_indir() on NULL");
284 
285   /* write a block into an indirect block */
286   b_ind(bp)[wrindex] = conv4(le_CPU, block);
287 }
288 
289 
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 */
296 {
297 /* Return nonzero if the indirect block pointed to by bp contains
298  * only NO_BLOCK entries.
299  */
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);
306 }
307 
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 */
314 {
315 /* Acquire a new block and return a pointer to it. */
316   register struct buf *bp;
317   int r;
318   block_t b;
319 
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.
324 	 */
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!");
332 		}
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.
339 			 */
340 			rip->i_preallocation = 0;
341 			discard_preallocated_blocks(rip);
342 		}
343 	}
344 
345 	if ( (b = alloc_block(rip, goal) ) == NO_BLOCK) {
346 		err_code = ENOSPC;
347 		return(NULL);
348 	}
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);
354 	}
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.
359 		 */
360 		rip->i_last_pos_bl_alloc++;
361 	}
362   }
363 
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);
368 }
369 
370 /*===========================================================================*
371  *				zero_block				     *
372  *===========================================================================*/
373 void zero_block(bp)
374 register struct buf *bp;	/* pointer to buffer to zero */
375 {
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);
381 }
382