summaryrefslogtreecommitdiff
path: root/minix/fs/ext2/balloc.c
blob: e0d5752037e598a2b2502390898eb287687e5bcb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/* This files manages blocks allocation and deallocation.
 *
 * The entry points into this file are:
 *   discard_preallocated_blocks:	Discard preallocated blocks.
 *   alloc_block:	somebody wants to allocate a block; find one.
 *   free_block:	indicate that a block is available for new allocation.
 *
 * Created:
 *   June 2010 (Evgeniy Ivanov)
 */

#include "fs.h"
#include <string.h>
#include <stdlib.h>
#include <minix/com.h>
#include <minix/u64.h>
#include "buf.h"
#include "inode.h"
#include "super.h"
#include "const.h"


static block_t alloc_block_bit(struct super_block *sp, block_t origin,
	struct inode *rip);

/*===========================================================================*
 *                      discard_preallocated_blocks                          *
 *===========================================================================*/
void discard_preallocated_blocks(struct inode *rip)
{
/* When called for rip, discard (free) blocks preallocated for rip,
 * otherwise discard all preallocated blocks.
 * Normally it should be called in following situations:
 * 1. File is closed.
 * 2. File is truncated.
 * 3. Non-sequential write.
 * 4. inode is "unloaded" from the memory.
 * 5. No free blocks left (discard all preallocated blocks).
 */
  int i;

  if (rip) {
	rip->i_prealloc_count = rip->i_prealloc_index = 0;
	for (i = 0; i < EXT2_PREALLOC_BLOCKS; i++) {
		if (rip->i_prealloc_blocks[i] != NO_BLOCK) {
			free_block(rip->i_sp, rip->i_prealloc_blocks[i]);
			rip->i_prealloc_blocks[i] = NO_BLOCK;
		}
	}
	return;
  }

  /* Discard all allocated blocks.
   * Probably there are just few blocks on the disc, so forbid preallocation.*/
  for(rip = &inode[0]; rip < &inode[NR_INODES]; rip++) {
	rip->i_prealloc_count = rip->i_prealloc_index = 0;
	rip->i_preallocation = 0; /* forbid preallocation */
	for (i = 0; i < EXT2_PREALLOC_BLOCKS; i++) {
		if (rip->i_prealloc_blocks[i] != NO_BLOCK) {
			free_block(rip->i_sp, rip->i_prealloc_blocks[i]);
			rip->i_prealloc_blocks[i] = NO_BLOCK;
		}
	}
  }
}


/*===========================================================================*
 *                              alloc_block                                  *
 *===========================================================================*/
block_t alloc_block(struct inode *rip, block_t block)
{
/* Allocate a block for inode. If block is provided, then use it as a goal:
 * try to allocate this block or his neghbors.
 * If block is not provided then goal is group, where inode lives.
 */
  block_t goal;
  block_t b;
  struct super_block *sp = rip->i_sp;

  if (sp->s_rd_only)
	panic("can't alloc block on read-only filesys.");

  /* Check for free blocks. First time discard preallocation,
   * next time return NO_BLOCK
   */
  if (!opt.use_reserved_blocks &&
      sp->s_free_blocks_count <= sp->s_r_blocks_count) {
	discard_preallocated_blocks(NULL);
  } else if (sp->s_free_blocks_count <= EXT2_PREALLOC_BLOCKS) {
	discard_preallocated_blocks(NULL);
  }

  if (!opt.use_reserved_blocks &&
      sp->s_free_blocks_count <= sp->s_r_blocks_count) {
	return(NO_BLOCK);
  } else if (sp->s_free_blocks_count == 0) {
	return(NO_BLOCK);
  }

  if (block != NO_BLOCK) {
	goal = block;
	if (rip->i_preallocation && rip->i_prealloc_count > 0) {
		/* check if goal is preallocated */
		b = rip->i_prealloc_blocks[rip->i_prealloc_index];
		if (block == b || (block + 1) == b) {
			/* use preallocated block */
			rip->i_prealloc_blocks[rip->i_prealloc_index] = NO_BLOCK;
			rip->i_prealloc_count--;
			rip->i_prealloc_index++;
			if (rip->i_prealloc_index >= EXT2_PREALLOC_BLOCKS) {
				rip->i_prealloc_index = 0;
				ASSERT(rip->i_prealloc_count == 0);
			}
			rip->i_bsearch = b;
			return b;
		} else {
			/* probably non-sequential write operation,
			 * disable preallocation for this inode.
			 */
			rip->i_preallocation = 0;
			discard_preallocated_blocks(rip);
		}
	}
  } else {
	  int group = (rip->i_num - 1) / sp->s_inodes_per_group;
	  goal = sp->s_blocks_per_group*group + sp->s_first_data_block;
  }

  if (rip->i_preallocation && rip->i_prealloc_count) {
	ext2_debug("There're preallocated blocks, but they're\
			neither used or freed!");
  }

  b = alloc_block_bit(sp, goal, rip);

  if (b != NO_BLOCK)
	rip->i_bsearch = b;

  return b;
}


static void check_block_number(block_t block, struct super_block *sp,
	struct group_desc *gd);

/*===========================================================================*
 *                              alloc_block_bit                              *
 *===========================================================================*/
static block_t alloc_block_bit(sp, goal, rip)
struct super_block *sp;		/* the filesystem to allocate from */
block_t goal;			/* try to allocate near this block */
struct inode *rip;		/* used for preallocation */
{
  block_t block = NO_BLOCK;	/* allocated block */
  int word;			/* word in block bitmap */
  bit_t	bit = -1;
  int group;
  char update_bsearch = FALSE;
  int i;

  if (goal >= sp->s_blocks_count ||
      (goal < sp->s_first_data_block && goal != 0)) {
	goal = sp->s_bsearch;
  }

  if (goal <= sp->s_bsearch) {
	/* No reason to search in a place with no free blocks */
	goal = sp->s_bsearch;
	update_bsearch = TRUE;
  }

  /* Figure out where to start the bit search. */
  word = ((goal - sp->s_first_data_block) % sp->s_blocks_per_group)
			/ FS_BITCHUNK_BITS;

  /* Try to allocate block at any group starting from the goal's group.
   * First time goal's group is checked from the word=goal, after all
   * groups checked, it's checked again from word=0, that's why "i <=".
   */
  group = (goal - sp->s_first_data_block) / sp->s_blocks_per_group;
  for (i = 0; i <= sp->s_groups_count; i++, group++) {
	struct buf *bp;
	struct group_desc *gd;

	if (group >= sp->s_groups_count)
		group = 0;

	gd = get_group_desc(group);
	if (gd == NULL)
		panic("can't get group_desc to alloc block");

	if (gd->free_blocks_count == 0) {
		word = 0;
		continue;
	}

	bp = get_block(sp->s_dev, gd->block_bitmap, NORMAL);

	if (rip->i_preallocation &&
	    gd->free_blocks_count >= (EXT2_PREALLOC_BLOCKS * 4) ) {
		/* Try to preallocate blocks */
		if (rip->i_prealloc_count != 0) {
			/* kind of glitch... */
			discard_preallocated_blocks(rip);
			ext2_debug("warning, discarding previously preallocated\
				    blocks! It had to be done by another code.");
		}
		ASSERT(rip->i_prealloc_count == 0);
		/* we preallocate bytes only */
		ASSERT(EXT2_PREALLOC_BLOCKS == sizeof(char)*CHAR_BIT);

		bit = setbyte(b_bitmap(bp), sp->s_blocks_per_group);
		if (bit != -1) {
			block = bit + sp->s_first_data_block +
					group * sp->s_blocks_per_group;
			check_block_number(block, sp, gd);

			/* We preallocate a byte starting from block.
			 * First preallocated block will be returned as
			 * normally allocated block.
			 */
			for (i = 1; i < EXT2_PREALLOC_BLOCKS; i++) {
				check_block_number(block + i, sp, gd);
				rip->i_prealloc_blocks[i-1] = block + i;
			}
			rip->i_prealloc_index = 0;
			rip->i_prealloc_count = EXT2_PREALLOC_BLOCKS - 1;

			lmfs_markdirty(bp);
			put_block(bp);

			gd->free_blocks_count -= EXT2_PREALLOC_BLOCKS;
			sp->s_free_blocks_count -= EXT2_PREALLOC_BLOCKS;
			lmfs_change_blockusage(EXT2_PREALLOC_BLOCKS);
			group_descriptors_dirty = 1;
			return block;
		}
	}

        bit = setbit(b_bitmap(bp), sp->s_blocks_per_group, word);
	if (bit == -1) {
		if (word == 0) {
			panic("ext2: allocator failed to allocate a bit in bitmap\
				with free bits.");
		} else {
			word = 0;
			continue;
		}
	}

	block = sp->s_first_data_block + group * sp->s_blocks_per_group + bit;
	check_block_number(block, sp, gd);

	lmfs_markdirty(bp);
	put_block(bp);

	gd->free_blocks_count--;
	sp->s_free_blocks_count--;
	lmfs_change_blockusage(1);
	group_descriptors_dirty = 1;

	if (update_bsearch && block != -1 && block != NO_BLOCK) {
		/* We searched from the beginning, update bsearch. */
		sp->s_bsearch = block;
	}

	return block;
  }

  return block;
}


/*===========================================================================*
 *                        free_block	                                     *
 *===========================================================================*/
void free_block(struct super_block *sp, bit_t bit_returned)
{
/* Return a block by turning off its bitmap bit. */
  int group;		/* group number of bit_returned */
  int bit;		/* bit_returned number within its group */
  struct buf *bp;
  struct group_desc *gd;

  if (sp->s_rd_only)
	panic("can't free bit on read-only filesys.");

  if (bit_returned >= sp->s_blocks_count ||
      bit_returned < sp->s_first_data_block)
	panic("trying to free block %d beyond blocks scope.",
		bit_returned);

  /* At first search group, to which bit_returned belongs to
   * and figure out in what word bit is stored.
   */
  group = (bit_returned - sp->s_first_data_block) / sp->s_blocks_per_group;
  bit = (bit_returned - sp->s_first_data_block) % sp->s_blocks_per_group;

  gd = get_group_desc(group);
  if (gd == NULL)
	panic("can't get group_desc to alloc block");

  /* We might be buggy (No way! :P), so check if we deallocate
   * data block, but not control (system) block.
   * This should never happen.
   */
  if (bit_returned == gd->inode_bitmap || bit_returned == gd->block_bitmap
      || (bit_returned >= gd->inode_table
          && bit_returned < (gd->inode_table + sp->s_itb_per_group))) {
	ext2_debug("ext2: freeing non-data block %d\n", bit_returned);
	panic("trying to deallocate \
		system/control block, hardly poke author.");
  }

  bp = get_block(sp->s_dev, gd->block_bitmap, NORMAL);

  if (unsetbit(b_bitmap(bp), bit))
	panic("Tried to free unused block %d", bit_returned);

  lmfs_markdirty(bp);
  put_block(bp);

  gd->free_blocks_count++;
  sp->s_free_blocks_count++;
  lmfs_change_blockusage(-1);

  group_descriptors_dirty = 1;

  if (bit_returned < sp->s_bsearch)
	sp->s_bsearch = bit_returned;

  /* Also tell libminixfs, so that 1) if it has this block in its cache, it can
   * mark it as clean, thus reducing useless writes, and 2) it can tell VM that
   * any previous inode association is to be broken for this block, so that the
   * block will not be mapped in erroneously later on.
   */
  lmfs_free_block(sp->s_dev, (block_t)bit_returned);
}


static void check_block_number(block_t block, struct super_block *sp,
				struct group_desc *gd)
{

  /* Check if we allocated a data block, but not control (system) block.
   * Only major bug can cause us to allocate wrong block. If it happens,
   * we panic (and don't bloat filesystem's bitmap).
   */
  if (block == gd->inode_bitmap || block == gd->block_bitmap ||
      (block >= gd->inode_table
       && block < (gd->inode_table + sp->s_itb_per_group))) {
	ext2_debug("ext2: allocating non-data block %d\n", block);
	panic("ext2: block allocator tryed to return \
		system/control block, poke author.\n");
  }

  if (block >= sp->s_blocks_count) {
	panic("ext2: allocator returned blocknum greater, than \
			total number of blocks.\n");
  }
}