summaryrefslogtreecommitdiff
path: root/minix/fs/ext2/ialloc.c
blob: 97f6bcf76812fff1625c4765ef34e23088c1494f (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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
/* This files manages inodes allocation and deallocation.
 *
 * The entry points into this file are:
 *   alloc_inode:  allocate a new, unused inode.
 *   free_inode:   mark an inode as available for a new file.
 *
 * Created (alloc_inode/free_inode/wipe_inode are from MFS):
 *   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 bit_t alloc_inode_bit(struct super_block *sp, struct inode
	*parent, int is_dir);
static void free_inode_bit(struct super_block *sp, bit_t bit_returned,
	int is_dir);
static void wipe_inode(struct inode *rip);


/*===========================================================================*
 *                alloc_inode                                                *
 *===========================================================================*/
struct inode *alloc_inode(struct inode *parent, mode_t bits, uid_t uid,
	gid_t gid)
{
/* Allocate a free inode on parent's dev, and return a pointer to it. */

  register struct inode *rip;
  register struct super_block *sp;
  int inumb;
  bit_t b;
  static int print_oos_msg = 1;

  sp = get_super(parent->i_dev);    /* get pointer to super_block */
  if (sp->s_rd_only) {    /* can't allocate an inode on a read only device. */
	err_code = EROFS;
	return(NULL);
  }

  /* Acquire an inode from the bit map. */
  b = alloc_inode_bit(sp, parent, (bits & I_TYPE) == I_DIRECTORY);
  if (b == NO_BIT) {
	err_code = ENOSPC;
	if (print_oos_msg)
		ext2_debug("Out of i-nodes on device %d/%d\n",
			   major(sp->s_dev), minor(sp->s_dev));
	print_oos_msg = 0;	/* Don't repeat message */
	return(NULL);
  }
  print_oos_msg = 1;

  inumb = (int) b;        /* be careful not to pass unshort as param */

  /* Try to acquire a slot in the inode table. */
  if ((rip = get_inode(NO_DEV, inumb)) == NULL) {
	/* No inode table slots available.  Free the inode just allocated. */
	free_inode_bit(sp, b, (bits & I_TYPE) == I_DIRECTORY);
  } else {
	/* An inode slot is available. Put the inode just allocated into it. */
	rip->i_mode = bits;         /* set up RWX bits */
	rip->i_links_count = NO_LINK; /* initial no links */
	rip->i_uid = uid;           /* file's uid is owner's */
	rip->i_gid = gid;           /* ditto group id */
	rip->i_dev = parent->i_dev; /* mark which device it is on */
	rip->i_sp = sp;             /* pointer to super block */

	/* Fields not cleared already are cleared in wipe_inode(). They have
	 * been put there because truncate() needs to clear the same fields if
	 * the file happens to be open while being truncated. It saves space
	 * not to repeat the code twice.
	 */
	wipe_inode(rip);
  }

  return(rip);
}


/*===========================================================================*
 *                free_inode                                                 *
 *===========================================================================*/
void free_inode(
  register struct inode *rip  /* inode to free */
)
{
/* Return an inode to the pool of unallocated inodes. */
  register struct super_block *sp;
  dev_t dev = rip->i_dev;
  bit_t b = rip->i_num;
  u16_t mode = rip->i_mode;

  /* Locate the appropriate super_block. */
  sp = get_super(dev);

  if (b <= NO_ENTRY || b > sp->s_inodes_count)
	return;
  free_inode_bit(sp, b, (mode & I_TYPE) == I_DIRECTORY);

  rip->i_mode = I_NOT_ALLOC;     /* clear I_TYPE field */
}


static int find_group_dir(struct super_block *sp);
static int find_group_hashalloc(struct super_block *sp, struct inode
	*parent);
static int find_group_any(struct super_block *sp);
static int find_group_orlov(struct super_block *sp, struct inode
	*parent);


/*===========================================================================*
 *                              alloc_inode_bit                              *
 *===========================================================================*/
static bit_t alloc_inode_bit(sp, parent, is_dir)
struct super_block *sp;         /* the filesystem to allocate from */
struct inode *parent;		/* parent of newly allocated inode */
int is_dir;			/* inode will be a directory if it is TRUE */
{
  int group;
  ino_t inumber = NO_BIT;
  bit_t bit;
  struct buf *bp;
  struct group_desc *gd;

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

  if (opt.mfsalloc) {
	group = find_group_any(sp);
  } else {
	if (is_dir) {
		if (opt.use_orlov) {
			group = find_group_orlov(sp, parent);
		} else {
			group = find_group_dir(sp);
		}
	} else {
		group = find_group_hashalloc(sp, parent);
	}
  }
  /* Check if we have a group where to allocate an inode */
  if (group == -1)
	return(NO_BIT);	/* no bit could be allocated */

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

  /* find_group_* should always return either a group with
   * a free inode slot or -1, which we checked earlier.
   */
  ASSERT(gd->free_inodes_count);

  bp = get_block(sp->s_dev, gd->inode_bitmap, NORMAL);
  bit = setbit(b_bitmap(bp), sp->s_inodes_per_group, 0);
  ASSERT(bit != -1); /* group definitly contains free inode */

  inumber = group * sp->s_inodes_per_group + bit + 1;

  /* Extra checks before real allocation.
   * Only major bug can cause problems. Since setbit changed
   * bp->b_bitmap there is no way to recover from this bug.
   * Should never happen.
   */
  if (inumber > sp->s_inodes_count) {
	panic("ext2: allocator returned inum greater, than\
		    total number of inodes.\n");
  }

  if (inumber < EXT2_FIRST_INO(sp)) {
	panic("ext2: allocator tryed to use reserved inode.\n");
  }

  lmfs_markdirty(bp);
  put_block(bp);

  gd->free_inodes_count--;
  sp->s_free_inodes_count--;
  if (is_dir) {
	gd->used_dirs_count++;
	sp->s_dirs_counter++;
  }

  group_descriptors_dirty = 1;

  /* Almost the same as previous 'group' ASSERT */
  ASSERT(inumber != NO_BIT);
  return inumber;
}


/*===========================================================================*
 *                          free_inode_bit                                   *
 *===========================================================================*/
static void free_inode_bit(struct super_block *sp, bit_t bit_returned,
                           int is_dir)
{
 /* Return an inode 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.");

  /* At first search group, to which bit_returned belongs to
   * and figure out in what word bit is stored.
   */
  if (bit_returned > sp->s_inodes_count ||
      bit_returned < EXT2_FIRST_INO(sp))
	panic("trying to free inode %d beyond inodes scope.", bit_returned);

  group = (bit_returned - 1) / sp->s_inodes_per_group;
  bit = (bit_returned - 1) % sp->s_inodes_per_group; /* index in bitmap */

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

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

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

  lmfs_markdirty(bp);
  put_block(bp);

  gd->free_inodes_count++;
  sp->s_free_inodes_count++;

  if (is_dir) {
	gd->used_dirs_count--;
	sp->s_dirs_counter--;
  }

  group_descriptors_dirty = 1;

  if (group < sp->s_igsearch)
	sp->s_igsearch = group;
}


/* it's implemented very close to the linux' find_group_dir() */
static int find_group_dir(struct super_block *sp)
{
  int avefreei = sp->s_free_inodes_count / sp->s_groups_count;
  struct group_desc *gd, *best_gd = NULL;
  int group, best_group = -1;

  for (group = 0; group < sp->s_groups_count; ++group) {
	gd = get_group_desc(group);
	if (gd == NULL)
		panic("can't get group_desc to alloc inode");
	if (gd->free_inodes_count == 0)
		continue;
	if (gd->free_inodes_count < avefreei)
		continue;
	if (!best_gd ||
	     gd->free_blocks_count > best_gd->free_blocks_count) {
		best_gd = gd;
		best_group = group;
	}
  }

  return best_group; /* group or -1 */
}


/* Analog of ffs_hashalloc() from *BSD.
 * 1) Check parent's for free inodes and blocks.
 * 2) Quadradically rehash on the group number.
 * 3) Make a linear search for free inode.
 */
static int find_group_hashalloc(struct super_block *sp, struct inode *parent)
{
  int ngroups = sp->s_groups_count;
  struct group_desc *gd;
  int group, i;
  int parent_group = (parent->i_num - 1) / sp->s_inodes_per_group;

  /* Try to place new inode in its parent group */
  gd = get_group_desc(parent_group);
  if (gd == NULL)
	panic("can't get group_desc to alloc inode");
  if (gd->free_inodes_count && gd->free_blocks_count)
	return parent_group;

  /* We can't allocate inode in the parent's group.
   * Now we will try to place it in another blockgroup.
   * The main idea is still to keep files from the same
   * directory together and use different blockgroups for
   * files from another directory, which lives in the same
   * blockgroup as our parent.
   * Thus we will spread things on the disk.
   */
  group = (parent_group + parent->i_num) % ngroups;

  /* Make quadratic probing to find a group with free inodes and blocks. */
  for (i = 1; i < ngroups; i <<= 1) {
	group += i;
	if (group >= ngroups)
		group -= ngroups;
	gd = get_group_desc(group);
	if (gd == NULL)
		panic("can't get group_desc to alloc inode");
	if (gd->free_inodes_count && gd->free_blocks_count)
		return group;
  }

  /* Still no group for new inode, try linear search.
   * Also check parent again (but for free inodes only).
   */
  group = parent_group;
  for (i = 0; i < ngroups; i++, group++) {
	if (group >= ngroups)
		group = 0;
	gd = get_group_desc(group);
	if (gd == NULL)
		panic("can't get group_desc to alloc inode");
	if (gd->free_inodes_count)
		return group;
  }

  return -1;
}


/* Find first group which has free inode slot.
 * This is similar to what MFS does.
 */
static int find_group_any(struct super_block *sp)
{
  int ngroups = sp->s_groups_count;
  struct group_desc *gd;
  int group = sp->s_igsearch;

  for (; group < ngroups; group++) {
	gd = get_group_desc(group);
	if (gd == NULL)
		panic("can't get group_desc to alloc inode");
	if (gd->free_inodes_count) {
		sp->s_igsearch = group;
		return group;
	}
  }

  return -1;
}


/* We try to spread first-level directories (i.e. directories in the root
 * or in the directory marked as TOPDIR).
 * If there are blockgroups with counts for blocks and inodes less than average
 * we return a group with lowest directory count. Otherwise we either
 * return a group with good free inodes and blocks counts or just a group
 * with free inode.
 *
 * For other directories we try to find a 'good' group, we consider a group as
 * a 'good' if it has enough blocks and inodes (greater than min_blocks and
 * min_inodes).
 *
 */
static int find_group_orlov(struct super_block *sp, struct inode *parent)
{
  int avefreei = sp->s_free_inodes_count / sp->s_groups_count;
  int avefreeb = sp->s_free_blocks_count / sp->s_groups_count;

  int group = -1;
  int fallback_group = -1; /* Group with at least 1 free inode */
  struct group_desc *gd;
  int i;

  if (parent->i_num == ROOT_INODE ||
      parent->i_flags & EXT2_TOPDIR_FL) {
	int best_group = -1;
	int best_avefree_group = -1; /* Best value of avefreei/avefreeb */
	int best_ndir = sp->s_inodes_per_group;

	group = (unsigned int)random();
	for (i = 0; i < sp->s_groups_count; i++, group++) {
		if (group >= sp->s_groups_count)
			group = 0;
		gd = get_group_desc(group);
		if (gd == NULL)
			panic("can't get group_desc to alloc inode");
		if (gd->free_inodes_count == 0)
			continue;

		fallback_group = group;

		if (gd->free_inodes_count < avefreei ||
		    gd->free_blocks_count < avefreeb)
			continue;

		best_avefree_group  = group;

		if (gd->used_dirs_count >= best_ndir)
			continue;
		best_ndir = gd->used_dirs_count;
		best_group = group;
	}
	if (best_group >= 0)
		return best_group;
	if (best_avefree_group >= 0)
		return best_avefree_group;
	return fallback_group;
  } else {
	int parent_group = (parent->i_num - 1) / sp->s_inodes_per_group;
	/* 2 is kind of random thing for now,
	 * but performance results are still good.
	 */
	int min_blocks = avefreeb / 2;
	int min_inodes = avefreei / 2;

	group = parent_group;
	for (i = 0; i < sp->s_groups_count; i++, group++) {
		if (group >= sp->s_groups_count)
			group = 0;
		gd = get_group_desc(group);
		if (gd == NULL)
			panic("can't get group_desc to alloc inode");
		if (gd->free_inodes_count == 0)
			continue;

		fallback_group = group;

		if (gd->free_inodes_count >= min_inodes &&
		    gd->free_blocks_count >= min_blocks)
			return group;
	}
	return fallback_group;
  }

  return -1;
}


/*===========================================================================*
 *                wipe_inode                                                 *
 *===========================================================================*/
static void wipe_inode(
  register struct inode *rip     /* the inode to be erased */
)
{
/* Erase some fields in the inode. This function is called from alloc_inode()
 * when a new inode is to be allocated, and from truncate(), when an existing
 * inode is to be truncated.
 */

  register int i;

  rip->i_size = 0;
  rip->i_update = ATIME | CTIME | MTIME;    /* update all times later */
  rip->i_blocks = 0;
  rip->i_flags = 0;
  rip->i_generation = 0;
  rip->i_file_acl = 0;
  rip->i_dir_acl = 0;
  rip->i_faddr = 0;

  for (i = 0; i < EXT2_N_BLOCKS; i++)
	rip->i_block[i] = NO_BLOCK;
  rip->i_block[0] = NO_BLOCK;

  rip->i_dirt = IN_DIRTY;
}