www.pudn.com > efs.rar > fs_buffer.c
/**********************************************************************
* fs_buffer.c
*
* Buffer layer implementation.
* Copyright (C) 2003, 2004, 2005, 2006, Qualcomm, Inc.
*/
/*===========================================================================
EDIT HISTORY FOR MODULE
This section contains comments describing changes made to the module.
Notice that changes are listed in reverse chronological order.
$Header: //depot/asic/MSMSHARED/services/efs/MSM_EFS.01.02/fs_buffer.c#17 $ $DateTime: 2006/11/29 17:29:09 $ $Author: davidb $
when who what, where, why
-------- --- ------------------------------------------------------
2006-11-27 dlb Fix freemap init flushing on large devices.
2006-11-27 dlb Check freemap size with 'if' instead of 'assert'.
2006-11-15 dlb Fix freemap base problem on restart.
2006-11-14 dlb Remove stray printf.
2006-11-09 dlb Add pm query of freemap.
2006-11-02 dlb Delete recovery support.
2006-10-26 dlb Put freemap changes back.
2006-10-11 dlb Back out freemap changes for now.
2006-10-10 dlb Partial transaction flush support.
2006-10-02 dlb Deallocate uses freemap, not chain.
2006-09-25 sh Remove "inline" keyword breaking ADS120
2006-09-18 dlb Cache freemap as independent data structure.
2006-05-02 sh Q&R fields must persist across reboot.
2006-03-23 dlb Always indicate freemap is allocated.
2006-01-27 sh Renamed some Q&R defines to be more explanatory
2006-01-03 nrs More Lint Cleanup
2005-12-22 dlb Fix charge and refund re general pool.
2005-10-26 sh Lint cleanup.
2005-10-25 nrs Create charge and refund functions Q&R understandable
2005-10-14 nrs Clean up to make more readable and logical
2005-10-11 nrs Fixed bug in allocation for when file system is full
2005-10-11 nrs Remove FS_ALLOC_HARD and FS_ALLOC_SOFT
2005-09-28 nrs Make GID 0 a special case
2005-09-19 nrs Implement quota limits
2005-09-13 nrs Implement reservation management for efs_chown ()
2005-09-12 nrs Implement reservation accounting
2005-08-25 nrs Implent buffer accounting for quotas and reservations
2005-07-21 nrs Added set and get for quotas and reservations
2005-05-26 sd Compilation fixes for L4.
2005-04-26 dlb Add 2K page support.
2005-01-04 dlb Update copyright line.
2004-12-30 dlb Provide hooks for new factory image code.
2004-12-01 dlb Eliminate spurious start transaction in rollback.
2004-11-14 dlb Fix test environment change.
2004-11-05 dlb Additional checking of buffers in unit test.
2004-10-15 dlb Fix copyright line.
2003-12-04 dlb Freemap setup works for large flashes.
2003-10-23 dlb Internal clusters use wires.
2003-10-08 dlb Tree root is no longer wiped.
2003-09-09 dlb Factory image works with bitmap and tree.
2003-08-28 dlb Bitmap allocate/deallocate with move from free.
2003-08-20 dlb Add tree allocate/deallocate.
2003-06-17 jkl Clean up code.
2003-04-21 gr Renamed and updated comments.
2003-04-04 bgc Added fatal error if free_wire does a write outside a
transaction.
2003-01-24 bgc Added call to fs_gc_dealloc() in b_dealloc() to mark
pages as garbage and clusters as deallocated.
2002-08-09 drh Created by dlb. Added history header
===========================================================================*/
#include "fs_buffer.h"
#include "fs_sys_types.h"
#include "fs_err.h"
#include "fs_errno.h"
#ifdef FEATURE_IG_EFS_EXT_SERVER
#include "amssassert.h"
#else
#include "assert.h"
#endif
#include
#ifdef FS_UNIT_TEST
#error code not present
#endif
#ifdef FS_UNIT_TEST
#error code not present
#endif
/* Methods we implement. */
static unsigned b_cluster_size (void);
static void b_start_transaction (fsb_t imp);
static void b_end_transaction (fsb_t imp);
static void b_rollback_transaction (fsb_t imp);
static int b_is_too_large (fsb_t imp);
static void b_partial_transaction_flush (fsb_t imp);
static int b_allocate (fsb_t imp, uint32 gid, cluster_id* id);
static void * b_read_wire (fsb_t imp, cluster_id id);
static void * b_write_wire (fsb_t imp, cluster_id id);
static void b_free_wire (fsb_t imp, cluster_id id);
static void b_deallocate (fsb_t imp, cluster_id id, uint32 gid);
static void b_deallocate_recovered (fsb_t imp, cluster_id id);
static void b_deallocate_tree (fsb_t imp, cluster_id id, unsigned depth,
unsigned count, uint32 gid);
static uint32 b_get_field (fsb_t imp, int field_id);
static void b_set_field (fsb_t imp, int field_id, uint32 value);
static unsigned b_get_write_count (fsb_t imp);
static cluster_id b_get_space_limit (fsb_t imp);
static cluster_id b_get_num_alloc (fsb_t imp);
static int b_set_reservation (fsb_t imp, uint32 gid, uint32 bytes);
static int b_set_quota (fsb_t imp, uint32 gid, uint32 bytes);
static int b_get_group_info (fsb_t imp, uint32 gid,
struct fs_group_info *ginfo);
static int b_chown (fsb_t imp, int old_uid, int new_uid, int old_gid,
int new_gid, uint32 blocks);
static void freemap_init (fsb_nand_t buf);
static void freemap_dealloc (fsb_nand_t buf, cluster_id id);
static cluster_id alloc_from_free_chain (fsb_nand_t buf);
static void wired_init (fsb_wired_t wired);
static void wired_flush (fsb_nand_t buf, fsb_wired_t wired);
static void wired_ready_cluster (fsb_nand_t buf, fsb_wired_t wired,
cluster_id cluster, int for_write);
static void read_all_fields (fsb_nand_t buf);
static unsigned lowest_set_bit (uint32 value);
static int charge_group (fsb_t imp, uint32 gid, uint32 blocks);
static void refund_group (fsb_t imp, uint32 gid, uint32 blocks);
static int quota_exceeded (fsb_t imp, uint32 gid, uint32 blocks);
static unsigned freemap_flush (fsb_nand_t buf, unsigned limit);
static void freemap_revert (fsb_nand_t buf);
static int buf_is_used (void *, cluster_id cluster);
void
fsb_buffer_initialize (fsb_nand_t buf, fs_pm_t gc_data)
{
int i;
buf->gc = gc_data;
buf->imp.cluster_size = b_cluster_size;
buf->imp.start_transaction = b_start_transaction;
buf->imp.end_transaction = b_end_transaction;
buf->imp.rollback_transaction = b_rollback_transaction;
buf->imp.is_too_large = b_is_too_large;
buf->imp.partial_transaction_flush = b_partial_transaction_flush;
buf->imp.allocate = b_allocate;
buf->imp.read_wire = b_read_wire;
buf->imp.write_wire = b_write_wire;
buf->imp.free_wire = b_free_wire;
buf->imp.deallocate = b_deallocate;
buf->imp.deallocate_tree = b_deallocate_tree;
buf->imp.deallocate_recovered = b_deallocate_recovered;
buf->imp.get_field = b_get_field;
buf->imp.set_field = b_set_field;
buf->imp.get_write_count = b_get_write_count;
buf->imp.get_space_limit = b_get_space_limit;
buf->imp.get_num_alloc = b_get_num_alloc;
buf->imp.set_reservation = b_set_reservation;
buf->imp.set_quota = b_set_quota;
buf->imp.get_group_info = b_get_group_info;
buf->imp.chown = b_chown;
for (i = 0; i < FS_BUFFER_NUMBER_BUFFERS; i++) {
buf->info[i].state = FS_BSTATE_FREE;
buf->info[i].cluster = INVALID_CLUSTER_ID;
}
wired_init (&buf->chain);
buf->transaction_nest = 0;
read_all_fields (buf);
/* For each Q&R group's fields... */
for (i = 1; i <= FS_QR_GROUP_COUNT; i++)
{
if (buf->imp.get_field (&buf->imp, FS_GROUP_RESERVATION_SIZE(i))
== INVALID_CLUSTER_ID)
{
/* Reset all Q&R fields for this group */
buf->imp.set_field (&buf->imp, FS_GROUP_QUOTA_SIZE(i), FS_MAX_RES_QUOTA);
buf->imp.set_field (&buf->imp, FS_GROUP_RESERVATION_SIZE(i), 0);
buf->imp.set_field (&buf->imp, FS_GROUP_SPACE_USED(i), 0);
}
}
/* Compute the hard space limit, if needed. Otherwise read the value from
* the superblock. */
buf->space_limit = buf->imp.get_field (&buf->imp, FS_FIELD_SPACE_LIMIT);
if (buf->space_limit == INVALID_CLUSTER_ID)
{
buf->space_limit = buf->gc->ops.space_limit (buf->gc);
buf->imp.set_field (&buf->imp, FS_FIELD_SPACE_LIMIT, buf->space_limit);
}
for (i = 0; i < FSB_MAX_DEPTH; i++) {
wired_init (&buf->tree[i]);
}
freemap_init (buf);
buf->gc->ops.register_free_check_cb (buf->gc, buf_is_used, (void *) buf);
}
static void
read_all_fields (fsb_nand_t buf)
{
unsigned i;
for (i = 0; i < FS_UPPER_DATA_COUNT; i++) {
buf->fields[i] = buf->gc->ops.get_info (buf->gc, i);
}
buf->field_dirty_mask = 0;
/* Special case to initialize the one we use. This doesn't need to be
* marked as dirty, since we will just do this check again next time. */
if (buf->fields[FS_FIELD_NUM_ALLOC] == INVALID_CLUSTER_ID)
buf->fields[FS_FIELD_NUM_ALLOC] = 0;
}
void
fsb_nand_shutdown (fsb_nand_t buf)
{
(void) buf;
FS_ERR_FATAL ("Not yet implemented", 0, 0, 0);
}
static unsigned
b_cluster_size (void)
{
return EFS_PAGE_SIZE;
}
static void
b_start_transaction (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
buf->transaction_nest++;
if (buf->transaction_nest == 1) {
buf->write_count = 0;
}
}
int fs_buffer_has_work (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
/* If there is a freechain at all, then return one unit of work. */
return buf->imp.get_field (&buf->imp, FS_FIELD_FREE_CHAIN)
!= INVALID_CLUSTER_ID;
}
/* Perform the work of finishing a transaction. If limit is non-zero, then
* only this many pages of the freemap will be flushed. */
static void
end_transaction_work (fsb_nand_t buf, unsigned limit)
{
unsigned counter = 0;
unsigned i;
unsigned index;
/* Flush out all of the wired pages. This should unwire everything. */
wired_flush (buf, &buf->chain);
for (i = 0; i < FSB_MAX_DEPTH; i++) {
wired_flush (buf, &buf->tree[i]);
}
/* Flush out any dirty buffers. Also check that everything that was
* wired has been freed. */
for (i = 0; i < FS_BUFFER_NUMBER_BUFFERS; i++) {
if (buf->info[i].state == FS_BSTATE_WRITE_DIRTY) {
buf->gc->ops.page_write (buf->gc, buf->info[i].cluster,
buf->buffers[i]);
buf->info[i].state = FS_BSTATE_FREE;
counter++;
buf->write_count--;
} else if (buf->info[i].state != FS_BSTATE_FREE) {
FS_ERR_FATAL ("End transaction with pending wired clusters",
0, 0, 0);
}
}
/* Write out the fields. */
while (buf->field_dirty_mask != 0) {
index = lowest_set_bit (buf->field_dirty_mask);
buf->gc->ops.store_info (buf->gc, index, buf->fields[index]);
buf->field_dirty_mask &= ~(1u << index);
}
/* The unlimited case always writes out the entire freemap. */
if (limit == 0)
buf->write_count -= freemap_flush (buf, 0);
else {
/* If there is any work left, write out some of the freemap. */
if (limit > counter) {
buf->write_count -= freemap_flush (buf, limit - counter);
}
}
}
static void
b_end_transaction (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
int free_limit;
#ifdef FS_UNIT_TEST
#error code not present
#endif
/* If this ends a transaction, writeout all of the data. Since some of
* the operations called here check for transaction consistency, do not
* decrement the transaction_nest value until after we have fully cleaned
* up. */
if (buf->transaction_nest == 1) {
buf->gc->ops.start_transaction (buf->gc);
/* If there is a free chain, move some of the nodes from there to the
* freemap. */
free_limit = 0;
while (buf->write_count < 20 && free_limit < 10) {
cluster_id clust = alloc_from_free_chain (buf);
if (clust == INVALID_CLUSTER_ID)
break;
freemap_dealloc (buf, clust);
free_limit++;
}
/* Do the main work to end a transaction, without any kind of limit. */
end_transaction_work (buf, 0);
buf->gc->ops.end_transaction (buf->gc);
}
buf->transaction_nest--;
}
static void
b_rollback_transaction (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
int i;
buf->transaction_nest = 0;
/* Obliterate all wired pages. */
for (i = 0; i < FS_BUFFER_NUMBER_BUFFERS; i++) {
buf->info[i].state = FS_BSTATE_FREE;
buf->info[i].cluster = INVALID_CLUSTER_ID;
}
read_all_fields (buf);
wired_init (&buf->chain);
for (i = 0; i < FSB_MAX_DEPTH; i++)
wired_init (&buf->tree[i]);
/* Re-read any dirty freemap pages. */
freemap_revert (buf);
}
static int
b_is_too_large (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
/* It is an error to call this with nested transactions. */
ASSERT (buf->transaction_nest == 1);
return (buf->write_count > 20);
}
static void
b_partial_transaction_flush (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
ASSERT (buf->transaction_nest == 1);
#ifdef FS_UNIT_TEST
#error code not present
#endif
buf->gc->ops.start_transaction (buf->gc);
end_transaction_work (buf, 22);
buf->gc->ops.end_transaction (buf->gc);
}
/* Find an appropriate buffer for the named cluster. Attempts to read in
* the contents of the data, if that is possible. Returns the buffer index
* in the array. The buffers are treated somewhat as a TLA cache.
* Information about the id is used to create a first index, and we start
* searching from there. The search must be full, since read buffers can
* get reused later. */
static int
find_buffer (fsb_nand_t buf, cluster_id id, int is_alloc)
{
int index = id & FS_BUFFER_NUMBER_MASK;
int i;
if (index >= FS_BUFFER_NUMBER_BUFFERS)
index -= FS_BUFFER_NUMBER_BUFFERS;
/* Search for the given buffer. */
for (i = 0; i < FS_BUFFER_NUMBER_BUFFERS; i++) {
if (buf->info[index].cluster == id)
break;
index++;
if (index == FS_BUFFER_NUMBER_BUFFERS)
index = 0;
}
if (i == FS_BUFFER_NUMBER_BUFFERS) {
/* It wasn't found, find a free space starting at the current index.
* The index at this point has wrapped around once, and is at the same
* point it was at the start. */
for (i = 0; i < FS_BUFFER_NUMBER_BUFFERS; i++) {
if (buf->info[index].state == FS_BSTATE_FREE)
break;
index++;
if (index == FS_BUFFER_NUMBER_BUFFERS)
index = 0;
}
if (i == FS_BUFFER_NUMBER_BUFFERS) {
FS_ERR_FATAL ("Out of buffers in filesystem", 0, 0, 0);
}
}
/* If this is buffer hasn't been visited yet, read in the data. */
if (is_alloc && buf->info[index].cluster != id) {
buf->info[index].cluster = id;
buf->gc->ops.page_read (buf->gc, id, buf->buffers[index]);
}
/* Must either find or allocate a buffer. */
ASSERT (buf->info[index].cluster == id);
return index;
}
static void *
b_read_wire (fsb_t imp, cluster_id id)
{
fsb_nand_t buf = (fsb_nand_t) imp;
int bnum;
/* Simple range check, but should catch most invalid things, such as
* trying to wire the INVALID_CLUSTER_ID. */
if (id > buf->space_limit)
FS_ERR_FATAL ("Attempt to read past allocated space", 0, 0, 0);
bnum = find_buffer (buf, id, 1);
/* If the buffer was write wired, this is a usage error. */
if (buf->info[bnum].state == FS_BSTATE_WRITE_WIRE) {
FS_ERR_FATAL ("Attempt to convert write wire to read wire", 0, 0, 0);
}
/* If the buffer is dirty, then use the special state
* FS_BSTATE_READ_WIRE_DIRTY to indicate that it still needs to become
* written. */
if (buf->info[bnum].state == FS_BSTATE_WRITE_DIRTY) {
buf->info[bnum].state = FS_BSTATE_READ_WIRE_DIRTY;
} else {
/* Mark the correct state, and return a pointer to the buffer. */
buf->info[bnum].state = FS_BSTATE_READ_WIRE;
}
#ifdef FS_UNIT_TEST
#error code not present
#endif
return buf->buffers[bnum];
}
static void *
b_write_wire (fsb_t imp, cluster_id id)
{
fsb_nand_t buf = (fsb_nand_t) imp;
int bnum;
/* Simple range check, but should catch most invalid things, such as
* trying to wire the INVALID_CLUSTER_ID. */
if (id > buf->space_limit)
FS_ERR_FATAL ("Attempt to write past allocated space", 0, 0, 0);
bnum = find_buffer (buf, id, 1);
/* XXX: Check for invalid transitions here, such as redundant write
* wires. */
/* Mark the correct state, and return a pointer to the buffer. */
buf->info[bnum].state = FS_BSTATE_WRITE_WIRE;
return buf->buffers[bnum];
}
static void
b_free_wire (fsb_t imp, cluster_id id)
{
fsb_nand_t buf = (fsb_nand_t) imp;
int bnum;
enum fs_buffer_state_t state;
bnum = find_buffer (buf, id, 0);
if (bnum < 0) {
FS_ERR_FATAL ("Freeing unwired cluster", 0, 0, 0);
}
state = buf->info[bnum].state;
/* If it is a write wire, mark the data as dirty. */
if (state == FS_BSTATE_WRITE_WIRE ||
state == FS_BSTATE_READ_WIRE_DIRTY)
{
if(buf->transaction_nest < 1)
FS_ERR_FATAL ("free_wire write outside a transaction.", 0, 0, 0);
if (state != FS_BSTATE_READ_WIRE_DIRTY)
buf->write_count++;
buf->info[bnum].state = FS_BSTATE_WRITE_DIRTY;
} else if (state != FS_BSTATE_WRITE_DIRTY) {
buf->info[bnum].state = FS_BSTATE_FREE;
#ifdef FS_UNIT_TEST
#error code not present
#endif
}
}
/**********************************************************************
* The fields are handled by the GC layer. */
static uint32
b_get_field (fsb_t imp, int field_id)
{
fsb_nand_t buf = (fsb_nand_t) imp;
return (buf->fields[field_id]);
}
static void
b_set_field (fsb_t imp, int field_id, uint32 value)
{
fsb_nand_t buf = (fsb_nand_t) imp;
buf->fields[field_id] = value;
buf->field_dirty_mask |= 1 << field_id;
}
static unsigned
b_get_write_count (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
if (buf->transaction_nest == 0)
return 0;
else
return buf->write_count;
}
static cluster_id
b_get_space_limit (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
return buf->space_limit;
}
static cluster_id
b_get_num_alloc (fsb_t imp)
{
fsb_nand_t buf = (fsb_nand_t) imp;
return buf->fields[FS_FIELD_NUM_ALLOC];
}
/**********************************************************************
* Free bitmap management.
*
* Clusters that have been returned to the garbage collector are maintained
* in the freemap. This freemap is a bitmap of all clusters. A set bit
* indicates this cluster is "free" according to the garbage collector. */
/* Return the index of the lowest number bit that is set. */
static unsigned
lowest_set_bit (uint32 value)
{
unsigned bit = 0;
if ((value & 0xFFFF) == 0) {
bit += 16;
value >>= 16;
}
if ((value & 0xFF) == 0) {
bit += 8;
value >>= 8;
}
if ((value & 0xF) == 0) {
bit += 4;
value >>= 4;
}
if ((value & 0x3) == 0) {
bit += 2;
value >>= 2;
}
if ((value & 1) == 0) {
bit += 1;
}
return bit;
}
#define FREEMAP_FREE 1
#define FREEMAP_ALLOCATED 0
/* Mark a single cluster in the freemap to a certain state. 'state' should
* be FREEMAP_FREE for 'free', and FREEMAP_ALLOCATED for allocated. Also
* marks the appropriate dirty bit.
*
* Note that since all of the divisors are powers of two, the compiler
* should optimize this into shifts and masks. */
static /* inline */ void
freemap_mark (fsb_nand_t buf, cluster_id cluster, unsigned state)
{
unsigned bitmap_offset = cluster / 32;
unsigned dirty_offset = (cluster / (EFS_PAGE_SIZE * 8)) / 32;
uint32 bitmap_bit = 1U << (cluster % 32);
uint32 dirty_bit = 1U << ((cluster / (EFS_PAGE_SIZE * 8)) % 32);
ASSERT (bitmap_offset < FS_BUFFER_FREE_BITMAP_UINT32_COUNT);
ASSERT (dirty_offset < FS_BUFFER_FREE_DIRTYMAP_UINT32_COUNT);
if (state == FREEMAP_FREE) {
buf->freemap.bitmap[bitmap_offset] |= bitmap_bit;
} else {
buf->freemap.bitmap[bitmap_offset] &= ~bitmap_bit;
}
if ((buf->freemap.dirty[dirty_offset] & dirty_bit) == 0) {
buf->freemap.dirty[dirty_offset] |= dirty_bit;
buf->write_count++;
}
}
/* Flush all clusters in the freemap that have been marked as dirty. If
* 'limit' is non-zero, only write at most this many pages. Returns the
* number of pages written. */
static unsigned
freemap_flush (fsb_nand_t buf, unsigned limit)
{
unsigned offset, index;
unsigned cluster_offset;
unsigned counter = 0;
offset = 0;
while (offset < FS_BUFFER_FREE_DIRTYMAP_UINT32_COUNT) {
if (buf->freemap.dirty[offset] == 0)
offset++;
else {
index = lowest_set_bit (buf->freemap.dirty[offset]);
cluster_offset = offset * 32 + index;
buf->gc->ops.page_write (buf->gc, buf->freemap.base + cluster_offset,
(uint8 *) buf->freemap.bitmap + (cluster_offset * EFS_PAGE_SIZE));
buf->freemap.dirty[offset] &= ~(1U << index);
/* Check to see if we've done too much work. */
counter++;
if (limit > 0 && counter >= limit) {
break;
}
}
}
return counter;
}
/* Re-read any freemap clusters that have been marked as dirty. Used to
* revert changes. */
static void
freemap_revert (fsb_nand_t buf)
{
unsigned offset, index;
unsigned cluster_offset;
offset = 0;
while (offset < FS_BUFFER_FREE_DIRTYMAP_UINT32_COUNT) {
if (buf->freemap.dirty[offset] == 0)
offset++;
else {
index = lowest_set_bit (buf->freemap.dirty[offset]);
cluster_offset = offset * 32 + index;
buf->gc->ops.page_read (buf->gc, buf->freemap.base + cluster_offset,
(uint8 *) buf->freemap.bitmap + (cluster_offset * EFS_PAGE_SIZE));
buf->freemap.dirty[offset] &= ~(1U << index);
}
}
}
static void
freemap_init (fsb_nand_t buf)
{
cluster_id base;
unsigned cluster_count;
cluster_id cluster;
unsigned count, offset, bit;
base = buf->imp.get_field (&buf->imp, FS_FIELD_FREEMAP_BASE);
cluster_count = (buf->space_limit + FS_BUFFER_NAND_PAGE_SIZE*8 - 1) /
(FS_BUFFER_NAND_PAGE_SIZE*8);
/* Make sure the configured size is big enough. It is important that this
* is not an assertion, since those can be turned off. */
if (cluster_count > FS_BUFFER_FREEMAP_SIZE / EFS_PAGE_SIZE) {
FS_ERR_FATAL ("FS_BUFFER_FREEMAP_SIZE not configured large enough",
0, 0, 0);
}
/* Start by clearing the freemap, nothing is free. */
memset (buf->freemap.bitmap, 0,
FS_BUFFER_FREE_BITMAP_UINT32_COUNT * sizeof (uint32));
/* Also clear out the dirty map. */
memset (buf->freemap.dirty, 0,
FS_BUFFER_FREE_DIRTYMAP_UINT32_COUNT * sizeof (uint32));
if (base == INVALID_CLUSTER_ID) {
base = 1;
buf->freemap.base = base;
/* Mark all clusters between one past the end of the freemap and the
* end of the usable space as available. */
for (cluster = base + cluster_count;
cluster < buf->space_limit;
cluster++)
{
freemap_mark (buf, cluster, FREEMAP_FREE);
}
/* Flush out the freemap. */
buf->gc->ops.start_transaction (buf->gc);
count = 0;
for (offset = 0; offset < FS_BUFFER_FREE_DIRTYMAP_UINT32_COUNT;
offset++)
{
for (bit = 0; bit < 32; bit++) {
if ((buf->freemap.dirty[offset] & (1 << bit)) != 0) {
/* Flush transaction if it grows too large. */
if (count > 20) {
buf->gc->ops.end_transaction (buf->gc);
buf->gc->ops.start_transaction (buf->gc);
count = 0;
}
cluster = (offset * 32) + bit;
buf->gc->ops.page_write (buf->gc, cluster + base,
&buf->freemap.bitmap[cluster *
(EFS_PAGE_SIZE / sizeof (uint32))]);
count++;
buf->freemap.dirty[offset] &= ~(1U << bit);
}
}
buf->fields[FS_FIELD_FREEMAP_BASE] = base;
buf->gc->ops.store_info (buf->gc, FS_FIELD_FREEMAP_BASE, base);
buf->fields[FS_FIELD_NUM_ALLOC] = cluster_count + 1;
buf->gc->ops.store_info (buf->gc, FS_FIELD_NUM_ALLOC,
cluster_count + 1);
buf->fields[FS_FIELD_GENERAL_POOL] = cluster_count + 1;
buf->gc->ops.store_info (buf->gc, FS_FIELD_GENERAL_POOL,
cluster_count + 1);
buf->gc->ops.end_transaction (buf->gc);
}
} else {
buf->freemap.base = base;
/* Read in the existing freemap. */
for (cluster = 0; cluster < cluster_count; cluster++) {
buf->gc->ops.page_read (buf->gc, cluster + base,
&buf->freemap.bitmap[cluster *
(EFS_PAGE_SIZE / sizeof (uint32))]);
}
}
buf->freemap.last_index = 0;
}
static void
wired_init (fsb_wired_t wired)
{
wired->cluster = INVALID_CLUSTER_ID;
wired->data = NULL;
wired->write_wired = 0;
}
static void
wired_flush (fsb_nand_t buf, fsb_wired_t wired)
{
if (wired->cluster != INVALID_CLUSTER_ID) {
buf->imp.free_wire (&buf->imp, wired->cluster);
wired->cluster = INVALID_CLUSTER_ID;
wired->write_wired = 0;
wired->data = NULL;
}
}
/* Bring the appropriate wired page in. */
static void
wired_ready_cluster (fsb_nand_t buf, fsb_wired_t wired,
cluster_id cluster, int for_write)
{
if (wired->cluster == cluster) {
if (!wired->write_wired && for_write) {
wired->data = buf->imp.write_wire (&buf->imp, cluster);
wired->write_wired = 1;
}
} else {
if (wired->cluster != INVALID_CLUSTER_ID) {
buf->imp.free_wire (&buf->imp, wired->cluster);
}
if (for_write) {
wired->data = buf->imp.write_wire (&buf->imp, cluster);
wired->write_wired = 1;
} else {
wired->data = buf->imp.read_wire (&buf->imp, cluster);
wired->write_wired = 0;
}
}
wired->cluster = cluster;
}
/* Allocate a single cluster from the freemap. */
static cluster_id
freemap_alloc (fsb_nand_t buf)
{
unsigned limit = (buf->space_limit + 31) / 32;
unsigned index = buf->freemap.last_index;
unsigned count = 0;
unsigned bit;
cluster_id cluster;
/* Scan the free bitmap, looking for a bit that is set. */
while (buf->freemap.bitmap[index] == 0) {
index++;
if (index > limit) {
index = 0;
count++;
if (count > 2) {
/* We've scanned the bitmap twice and found nothing set in it.
* Space is exhausted. */
return INVALID_CLUSTER_ID;
}
}
}
buf->freemap.last_index = index;
bit = lowest_set_bit (buf->freemap.bitmap[index]);
cluster = index * 32 + bit;
freemap_mark (buf, cluster, FREEMAP_ALLOCATED);
ASSERT (cluster <= buf->space_limit);
return cluster;
}
static void
freemap_dealloc (fsb_nand_t buf, cluster_id id)
{
/* Make sure nothing tries to deallocate something that is already free.
*/
ASSERT (fs_buf_is_allocated (buf, id));
freemap_mark (buf, id, FREEMAP_FREE);
}
int
fs_buf_is_allocated (fsb_nand_t buf, cluster_id id)
{
ASSERT (id <= buf->space_limit);
return (buf->freemap.bitmap[id / 32] & (1 << (id % 32))) == 0;
}
static int
buf_is_used (void *data, cluster_id id)
{
return fs_buf_is_allocated ((fsb_nand_t) data, id);
}
/**********************************************************************
* Free cluster management.
*
* Although the lower layer does maintain information about which clusters
* have been allocated, this information is not readily available.
* Instead, we keep track of our own free cluster information. We have two
* pieces of information in the FS superblock (stored in cluster 1). One
* is the next allocation location. This is the next cluster to allocate
* when the free list is completely empty.
*
* The second is the head of the free cluster chain. Each chain is a
* cluster (itself free) containing a list of other cluster chains. Its
* contents are defined by the cluster_chain structure.
*
* The head page of the free chain is kept cached in our data structure
* until the end of a transaction.
*/
/* Remove the lowest number entry from the given heap data. Does not check
* if the heap is empty, so beware. */
static uint32 heap_take (struct cluster_chain *heap);
/* Peak at the lowest number entry in the given heap. Also doesn't check
* if the heap is empty. */
static uint32 heap_peek (struct cluster_chain *heap);
/* Insert an entry into the heap. Does not check for overflow, so beware.
*/
static void heap_insert (struct cluster_chain *heap, uint32 item);
static cluster_id tree_node_alloc (fsb_nand_t buf, cluster_id node,
int level, int depth);
/* Read in the head of the free chain if necessary. */
static void
ready_free_chain (fsb_nand_t buf, int for_write)
{
cluster_id chain;
chain = buf->imp.get_field (&buf->imp, FS_FIELD_FREE_CHAIN);
if (chain == INVALID_CLUSTER_ID) {
buf->chain.cluster = INVALID_CLUSTER_ID;
} else {
wired_ready_cluster (buf, &buf->chain, chain, for_write);
}
}
static void
add_to_free_chain (fsb_nand_t buf, uint32 entry)
{
cluster_id old_node, new_node;
struct cluster_chain *cchain;
int do_heap_insert = 0;
ready_free_chain (buf, 0);
cchain = (struct cluster_chain *) buf->chain.data;
/* If this chain node is full (or non-existant), find a block to use for
* the chain block. */
if (buf->chain.cluster == INVALID_CLUSTER_ID
|| cchain->data[0] >= 126)
{
old_node = buf->chain.cluster;
if (FSB_TREE_DEPTH (entry) == 0) {
/* This is a leaf node, so it can just become the new free chain
* element. */
new_node = entry;
} else {
new_node = tree_node_alloc (buf,
FSB_TREE_CLUSTER (entry), 0,
FSB_TREE_DEPTH (entry));
if (new_node != FSB_TREE_CLUSTER (entry)) {
/* This node must be added to the new heap. */
do_heap_insert = 1;
}
}
buf->imp.set_field (&buf->imp, FS_FIELD_FREE_CHAIN, new_node);
ready_free_chain (buf, 1);
cchain = (struct cluster_chain *) buf->chain.data;
cchain->next = old_node;
cchain->data[0] = 0;
if (do_heap_insert)
heap_insert (cchain, entry);
} else {
ready_free_chain (buf, 1);
cchain = (struct cluster_chain *) buf->chain.data;
heap_insert (cchain, entry);
/* XXX: Wrong, but code using this expects it right now. */
/* buf->write_count++; */
}
}
/* Allocate a single node from the given tree, at the specified level. If
* the return result is the same as 'node', then this node should now be
* marked as in use. */
static cluster_id
tree_node_alloc (fsb_nand_t buf, cluster_id node,
int level, int depth)
{
int tmp;
cluster_id child;
uint32 *nodes;
if (level >= depth)
return node;
/* Are we viewing the correct node? */
if (buf->tree[level].cluster != node) {
buf->tree_offset[level] = 0;
}
wired_ready_cluster (buf, &buf->tree[level], node, 0);
nodes = (uint32 *) buf->tree[level].data;
/* Find the next entry in this node that has a value. */
tmp = buf->tree_offset[level];
while (tmp < FS_BUFFER_NAND_PAGE_SIZE/4
&& nodes[tmp] == INVALID_CLUSTER_ID)
tmp++;
buf->tree_offset[level] = tmp;
/* If there are no pointers down, just return this node. */
if (tmp >= FS_BUFFER_NAND_PAGE_SIZE/4) {
return node;
}
/* Otherwise, get the next node down. */
child = tree_node_alloc (buf, nodes[tmp],
level + 1, depth);
/* If the child is empty, mark it as free at this level. */
if (child == nodes[tmp]) {
wired_ready_cluster (buf, &buf->tree[level], node, 1);
nodes = (uint32 *) buf->tree[level].data;
nodes[tmp] = INVALID_CLUSTER_ID;
buf->tree_offset[level]++;
}
return child;
}
/* Attempt to take a cluster from the free chain. Returns
* INVALID_CLUSTER_ID if the free_chain is empty. */
static cluster_id
alloc_from_free_chain (fsb_nand_t buf)
{
cluster_id clust, tmp;
struct cluster_chain *cchain;
ready_free_chain (buf, 0);
cchain = (struct cluster_chain *) buf->chain.data;
if (buf->chain.cluster == INVALID_CLUSTER_ID)
return INVALID_CLUSTER_ID;
if (cchain->data[0] == 0) {
/* The node at the head of the chain is empty, so use it as the
* allocated cluster. */
buf->imp.set_field (&buf->imp, FS_FIELD_FREE_CHAIN, cchain->next);
clust = buf->chain.cluster;
/* We don't really need to flush this out, since it doesn't have
* meaningful data in it. However, since we're going to return it
* anyway, and the user will soon write to it, it doesn't hurt anything
* to leave the cluster marked as dirty. */
wired_flush (buf, &buf->chain);
} else {
/* There is a free chain node, and it points to something. */
ready_free_chain (buf, 1);
cchain = (struct cluster_chain *) buf->chain.data;
clust = heap_peek (cchain);
if (FSB_TREE_DEPTH (clust) > 0) {
tmp = tree_node_alloc (buf,
FSB_TREE_CLUSTER (clust), 0,
FSB_TREE_DEPTH (clust));
if (tmp == FSB_TREE_CLUSTER (clust)) {
/* This cluster is now going to be used for user data, so release
* our wire upon it. */
ASSERT (buf->tree[0].cluster == tmp);
wired_flush (buf, &buf->tree[0]);
(void) heap_take (cchain);
}
clust = tmp;
} else {
(void) heap_take (cchain);
}
}
return clust;
}
static int
b_allocate (fsb_t imp, uint32 gid, cluster_id * id)
{
fsb_nand_t buf = (fsb_nand_t) imp;
cluster_id clust;
int result;
ASSERT (buf->transaction_nest > 0);
/* Verify the quota */
if(quota_exceeded (&buf->imp, gid, 1))
return -EDQUOT;
/* Charge the group for now. If there is no space, later it can be
* refunded */
result = charge_group (&buf->imp, gid, 1);
if (result < 0)
return -ENOSPC;
/* The simple case, there is nothing on the free chain, so advance the
* allocation pointer. */
clust = alloc_from_free_chain (buf);
if (clust == INVALID_CLUSTER_ID)
clust = freemap_alloc (buf);
if (clust == INVALID_CLUSTER_ID)
{
/* No space was left for anything, so we have to refund what was
* charged earlier. This is only done for the general pool and
* reservations that have exceeded their reserved space. */
refund_group (&buf->imp, gid, 1);
return -ENOSPC;
}
*id = clust;
return 0;
}
static void
b_deallocate (fsb_t imp, cluster_id id, uint32 gid)
{
fsb_nand_t buf = (fsb_nand_t) imp;
ASSERT (buf->transaction_nest > 0);
refund_group (&buf->imp, gid, 1);
freemap_mark (buf, id, FREEMAP_FREE);
}
static void
b_deallocate_recovered (fsb_t imp, cluster_id id)
{
fsb_nand_t buf = (fsb_nand_t) imp;
ASSERT (buf->transaction_nest > 0);
freemap_mark (buf, id, FREEMAP_FREE);
}
static void
b_deallocate_tree (fsb_t imp, cluster_id id, unsigned depth, unsigned count,
uint32 gid)
{
fsb_nand_t buf = (fsb_nand_t) imp;
ASSERT (buf->transaction_nest > 0);
ASSERT (depth < FSB_MAX_DEPTH);
refund_group (&buf->imp, gid, count);
add_to_free_chain (buf, FSB_TREE_MAKE (id, depth));
}
static uint32
heap_peek (struct cluster_chain *heap)
{
return heap->data[1];
}
static uint32
heap_take (struct cluster_chain *heap)
{
uint32 item;
unsigned node;
unsigned next;
uint32 tmp;
/* The smallest item is always in front. */
item = heap->data[1];
if (heap->data[0] > 1) {
heap->data[1] = heap->data[heap->data[0]];
heap->data[0]--;
node = 1;
while (1) {
next = node << 1;
if (next > heap->data[0])
break;
if (next < heap->data[0]
&& heap->data[next + 1] < heap->data[next])
{
next++;
}
if (heap->data[node] > heap->data[next]) {
/* Swap the two entries. */
tmp = heap->data[node];
heap->data[node] = heap->data[next];
heap->data[next] = tmp;
node = next;
} else {
break;
}
}
} else {
heap->data[0]--;
}
return item;
}
static void
heap_insert (struct cluster_chain *heap, uint32 item)
{
unsigned node;
unsigned next;
uint32 tmp;
/* Append the item to the heap. */
heap->data[0]++;
node = heap->data[0];
heap->data[node] = item;
/* Bubble the item up in the heap, maintaining the heap property. */
while (node > 1) {
next = node >> 1;
if (heap->data[node] < heap->data[next]) {
/* Swap the two entries. */
tmp = heap->data[node];
heap->data[node] = heap->data[next];
heap->data[next] = tmp;
} else {
/* If no swapping needed, we are done, so leave the loop. */
break;
}
node = next;
}
}
static int
b_set_reservation (fsb_t imp, uint32 gid, uint32 bytes)
{
fsb_nand_t buf = (fsb_nand_t) imp;
uint32 new_res_blocks = 0;
uint32 old_res_blocks = 0;
uint32 space_used = 0;
uint32 alloc_returned = 0;
if (!IS_QUOTA_RESERVED_GROUP (gid))
return -EPERM;
/* Get the old reservation size in the case where the reservation is
* reduced below the current size */
old_res_blocks = buf->imp.get_field (&buf->imp,
FS_GROUP_RESERVATION_SIZE(gid));
/* Recalculate the size from bytes as input to pages for storage. */
new_res_blocks = (bytes + EFS_PAGE_SIZE - 1)/ EFS_PAGE_SIZE;
/* Check to ensure the new allocation will not exceed the space limit. To
* allow resizing of the same reservation, first subtract the reservation
* size from the current blocks allocated. */
if ((buf->imp.get_field(&buf->imp, FS_FIELD_NUM_ALLOC) -
old_res_blocks + new_res_blocks) > buf->space_limit)
return -ENOSPC;
/* Set the new reservation size */
buf->imp.set_field (&buf->imp, FS_GROUP_RESERVATION_SIZE (gid),
new_res_blocks);
space_used = buf->imp.get_field(&buf->imp, FS_GROUP_SPACE_USED (gid));
/* Since reservation re-sizing is allowed, there are several cases that
* need to be checked. The two major cases are when the new reservation
* is either more or less than the old reservation. In each case we need
* to see if there are any blocks that need to be charged or refunded to
* the general pool */
if (new_res_blocks == old_res_blocks)
{
/* Trivial case, nothing changes in the file system */
}
else if (new_res_blocks > old_res_blocks)
{
/* The reservation being set is greater than the old reservation */
if (new_res_blocks <= space_used)
{
/* No new blocks are being taken from the global pool */
new_res_blocks = 0;
}
else
{
/* Calculate the number of blocks needed to be set aside */
if (old_res_blocks > space_used)
new_res_blocks = new_res_blocks - old_res_blocks;
else
new_res_blocks = new_res_blocks - space_used;
}
/* Set aside the new reserved blocks */
buf->imp.set_field (&buf->imp, FS_FIELD_NUM_ALLOC,
buf->imp.get_field (&buf->imp, FS_FIELD_NUM_ALLOC) + new_res_blocks);
}
else /* new_res_blocks < old_res_blocks */
{
/* The reservation being set is smaller than the current usage */
if (new_res_blocks >= space_used)
{
/* Since the new reservation is smaller than the old one, return the
* difference between the old reservation and the new reservation */
alloc_returned = old_res_blocks - new_res_blocks;
}
else
{
if (old_res_blocks <= space_used)
{
/* All the blocks for the old reservation are used up, so nothing
* to return to the global pool. */
alloc_returned = 0;
}
else
{
/* There are some blocks that were initially set aside, but are no
* longer needed for the reservation, so it is time to return them
* to the global pool */
alloc_returned = old_res_blocks - space_used;
}
}
/* Return the allocations */
if (alloc_returned > 0)
buf->imp.set_field (&buf->imp, FS_FIELD_NUM_ALLOC,
buf->imp.get_field (&buf->imp, FS_FIELD_NUM_ALLOC) - alloc_returned);
}
return 0;
} /* end b_set_reservation */
static int
b_set_quota (fsb_t imp, uint32 gid, uint32 bytes)
{
fsb_nand_t buf = (fsb_nand_t) imp;
uint32 quota_limit = 0;
if (!IS_QUOTA_RESERVED_GROUP (gid))
return -EPERM;
/* Recalculate the size from bytes as input to pages for storage. */
quota_limit = (bytes + EFS_PAGE_SIZE -1) / EFS_PAGE_SIZE;
/* Set the Quota limit. It does not matter what the other fields are. */
buf->imp.set_field (&buf->imp, FS_GROUP_QUOTA_SIZE (gid), quota_limit);
return 0;
} /* end b_set_quota */
static int
b_get_group_info (fsb_t imp, uint32 gid, struct fs_group_info *ginfo)
{
fsb_nand_t buf = (fsb_nand_t) imp;
if (IS_QUOTA_RESERVED_GROUP (gid))
{
ginfo->quota_size = buf->imp.get_field(&buf->imp,
FS_GROUP_QUOTA_SIZE(gid)) * EFS_PAGE_SIZE;
ginfo->reservation_size = buf->imp.get_field (&buf->imp,
FS_GROUP_RESERVATION_SIZE(gid)) * EFS_PAGE_SIZE;
ginfo->space_used = buf->imp.get_field (&buf->imp,
FS_GROUP_SPACE_USED(gid)) * EFS_PAGE_SIZE;
}
else
return -EPERM;
return 0;
} /* end b_get_group_info */
static int
b_chown (fsb_t imp, int old_uid, int new_uid, int old_gid, int new_gid,
uint32 blocks)
{
fsb_nand_t buf = (fsb_nand_t) imp;
int result = 0;
(void) new_uid;
(void) old_uid;
/* Set all non-Q&R gid's to 0 */
if (!IS_QUOTA_RESERVED_GROUP (new_gid))
new_gid = 0;
if (!IS_QUOTA_RESERVED_GROUP (old_gid))
old_gid = 0;
/* Check to see if we chown to the same group and shortcut the process */
if (old_gid == new_gid)
return 0;
/* Check to make the new group quota will not be exceeded */
if (quota_exceeded (&buf->imp, new_gid, blocks))
return -EDQUOT;
/* Refund the old group first to guarantee enough space is available to
* chown the file. If the charge group fails, re-charge the old group */
refund_group (&buf->imp, old_gid, blocks);
result = charge_group (&buf->imp, new_gid, blocks);
if (result < 0)
{
(void) charge_group (&buf->imp, old_gid, blocks);
return -ENOSPC;
}
return 0;
} /* end b_chown */
static int
charge_group (fsb_t imp, uint32 gid, uint32 blocks)
{
fsb_nand_t buf = (fsb_nand_t) imp;
uint32 general_blocks;
uint32 billed_blocks;
uint32 reservation_blocks, reservation_used;
unsigned bill_to_field;
/* Determine who is going to be charged for the allocation and if
* allocations in the general pool are required. */
if (!IS_QUOTA_RESERVED_GROUP (gid))
{
general_blocks = blocks;
billed_blocks = 0;
bill_to_field = FS_FIELD_GENERAL_POOL;
}
else
{
billed_blocks = blocks;
reservation_blocks = buf->imp.get_field(&buf->imp,
FS_GROUP_RESERVATION_SIZE (gid));
reservation_used = buf->imp.get_field (&buf->imp,
FS_GROUP_SPACE_USED (gid));
bill_to_field = FS_GROUP_SPACE_USED (gid);
/* Find out how many blocks come from the general pool. */
if (reservation_used + blocks <= reservation_blocks)
{
/* None of the blocks come from the general pool */
general_blocks = 0;
}
else if (reservation_used < reservation_blocks)
{
/* Some of the blocks go to the general pool */
general_blocks = blocks - (reservation_blocks - reservation_used);
}
else
{
/* All the blocks will be come from the general pool */
general_blocks = blocks;
}
}
/* Check to make sure we won't run out of free space */
if ((buf->imp.get_field(&buf->imp, FS_FIELD_NUM_ALLOC) + general_blocks) <
buf->space_limit)
{
/* Charge the appropriate allocations */
if (billed_blocks > 0) {
buf->imp.set_field (&buf->imp, bill_to_field,
buf->imp.get_field (&buf->imp, bill_to_field) + billed_blocks);
}
if (general_blocks > 0) {
buf->imp.set_field (&buf->imp, FS_FIELD_NUM_ALLOC,
buf->imp.get_field (&buf->imp, FS_FIELD_NUM_ALLOC) + general_blocks);
buf->imp.set_field (&buf->imp, FS_FIELD_GENERAL_POOL,
(buf->imp.get_field (&buf->imp, FS_FIELD_GENERAL_POOL)
+ general_blocks));
}
return 0;
}
return -1;
}
static void
refund_group (fsb_t imp, uint32 gid, uint32 blocks)
{
fsb_nand_t buf = (fsb_nand_t) imp;
uint32 general_blocks = blocks;
uint32 reservation_blocks, reservation_used;
uint32 refund_blocks;
unsigned refund_to_field;
/* Determine who is going to be charged for the allocation and if
* allocations in the general pool are required. */
if (!IS_QUOTA_RESERVED_GROUP (gid))
{
refund_to_field = FS_FIELD_GENERAL_POOL;
refund_blocks = 0;
general_blocks = blocks;
}
else
{
reservation_used = buf->imp.get_field (&buf->imp,
FS_GROUP_SPACE_USED (gid));
reservation_blocks = buf->imp.get_field(&buf->imp,
FS_GROUP_RESERVATION_SIZE (gid));
refund_to_field = FS_GROUP_SPACE_USED (gid);
refund_blocks = blocks;
ASSERT (reservation_used >= blocks);
if (reservation_used < reservation_blocks)
{
/* How many blocks go to the general pool */
general_blocks = 0;
}
else if ((reservation_used - blocks) < reservation_blocks)
{
/* The reservation limit was crossed, so determine how many blocks
* over the limit need to be returned to the global allocation count.
*/
general_blocks = reservation_used - reservation_blocks;
}
else
{
/* All the blocks are returned to the global pool */
general_blocks = blocks;
}
}
/* Refund the appropriate allocations */
if (refund_blocks > 0) {
buf->imp.set_field (&buf->imp, refund_to_field,
buf->imp.get_field (&buf->imp, refund_to_field) - refund_blocks);
}
if (general_blocks > 0) {
buf->imp.set_field (&buf->imp, FS_FIELD_NUM_ALLOC,
buf->imp.get_field (&buf->imp, FS_FIELD_NUM_ALLOC) - general_blocks);
buf->imp.set_field (&buf->imp, FS_FIELD_GENERAL_POOL,
buf->imp.get_field (&buf->imp, FS_FIELD_GENERAL_POOL) - general_blocks);
}
}
static int
quota_exceeded (fsb_t imp, uint32 gid, uint32 blocks)
{
fsb_nand_t buf = (fsb_nand_t) imp;
if (IS_QUOTA_RESERVED_GROUP(gid))
{
if (buf->imp.get_field (&buf->imp, FS_GROUP_SPACE_USED (gid)) + blocks >=
buf->imp.get_field (&buf->imp, FS_GROUP_QUOTA_SIZE (gid)))
return -1;
}
return 0;
}
#ifdef FS_UNIT_TEST
#error code not present
#endif