blob: 2d9f43b1692eb3be4984b4b7541b4239a5476e7e [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "block_allocator.h"
#include "debug.h"
#include "transaction.h"
bool print_block_alloc = false;
/**
* find_free_block - Search for a free block
* @tr: Transaction object.
* @min_block_in: Block number to start search at.
*
* Return: Block number that is in commited free set and not already allocated
* by any transaction.
*/
static data_block_t find_free_block(struct transaction *tr,
data_block_t min_block_in)
{
data_block_t block;
data_block_t min_block = min_block_in;
struct block_set *set;
assert(list_in_list(&tr->node)); /* transaction must be active */
pr_read("min_block %lld\n", min_block);
block = min_block;
do {
block = block_set_find_next_block(tr, &tr->fs->free, block, true);
if (block < min_block) {
assert(!block);
if (LOCAL_TRACE >= TRACE_LEVEL_READ) {
if (min_block_in) {
block = find_free_block(tr, 0);
}
printf("%s: no space, min_block %lld, free block ignoring_min_block %lld\n",
__func__, min_block_in, block);
printf("%s: free\n", __func__);
block_set_print(tr, &tr->fs->free);
list_for_every_entry(&tr->fs->allocated, set, struct block_set, node) {
printf("%s: allocated %p\n", __func__, set);
block_set_print(tr, set);
}
if (tr->new_free_set) {
printf("%s: new free\n", __func__);
block_set_print(tr, tr->new_free_set);
}
}
return 0;
}
min_block = block;
pr_read("check free block %lld\n", block);
assert(!list_is_empty(&tr->fs->allocated));
list_for_every_entry(&tr->fs->allocated, set, struct block_set, node) {
block = block_set_find_next_block(tr, set, block, false);
assert(block >= min_block);
};
} while (block != min_block);
pr_read("found free block %lld\n", block);
return block;
}
/**
* block_allocate_etc - Allocate a block
* @tr: Transaction object.
* @is_tmp: %true if allocated block should be automatically freed when
* transaction completes, %false if allocated block should be added
* to free set when transaction completes.
*
* Find a free block and add it to the allocated set selected by @is_tmp. If
* called while completing the transaction update the new free set directly if
* needed.
*
* Return: Allocated block number.
*/
data_block_t block_allocate_etc(struct transaction *tr, bool is_tmp)
{
data_block_t block;
data_block_t min_block;
if (tr->failed) {
pr_err("transaction failed, abort\n");
return 0;
}
assert(transaction_is_active(tr));
/* TODO: group allocations by set */
if (is_tmp) {
if (!tr->tmp_allocated.updating) {
tr->last_tmp_free_block = tr->fs->dev->block_count / 4 * 3;
}
} else {
if (!tr->new_free_set || !tr->new_free_set->updating) {
tr->last_free_block = 0;
}
}
min_block = is_tmp ? tr->last_tmp_free_block : tr->last_free_block;
block = find_free_block(tr, min_block);
if (!block) {
block = find_free_block(tr, 0);
if (!block) {
pr_err("no space\n");
transaction_fail(tr);
return 0;
}
}
if (is_tmp) {
pr_write("add %lld to tmp_allocated\n", block);
block_set_add_block(tr, &tr->tmp_allocated, block);
tr->last_tmp_free_block = block + 1;
} else {
pr_write("add %lld to allocated\n", block);
block_set_add_block(tr, &tr->allocated, block);
if (block < tr->min_free_block) {
pr_write("remove %lld from new_free_set\n", block);
assert(tr->new_free_set);
block_set_remove_block(tr, tr->new_free_set, block);
tr->last_free_block = block + 1;
}
}
return block;
}
/**
* block_free_etc - Free a block
* @tr: Transaction object.
* @block: Block that should be freed.
* @is_tmp: Must match is_tmp value passed to block_allocate_etc (always
* %false if @block was not allocated by this transaction).
*
* If @block was allocated in this transaction, remove it from the allocated set
* (selected by @is_tmp). Otherwise add it to the set of blocks to remove when
* transaction completes. If called while completing the transaction update the
* new free set directly if needed.
*/
void block_free_etc(struct transaction *tr, data_block_t block, bool is_tmp)
{
assert(block_is_clean(tr->fs->dev, block));
if (is_tmp) {
assert(!block_set_block_in_set(tr, &tr->allocated, block));
assert(!block_set_block_in_set(tr, &tr->freed, block));
pr_write("remove %lld from tmp_allocated\n", block);
block_set_remove_block(tr, &tr->tmp_allocated, block);
return;
}
assert(!block_set_block_in_set(tr, &tr->tmp_allocated, block));
if (block_set_block_in_set(tr, &tr->allocated, block)) {
pr_write("remove %lld from allocated\n", block);
block_set_remove_block(tr, &tr->allocated, block);
if (block < tr->min_free_block) {
pr_write("add %lld to new_free_root\n", block);
assert(tr->new_free_set);
block_set_add_block(tr, tr->new_free_set, block);
}
} else {
if (block < tr->min_free_block) {
pr_write("add %lld to new_free_root\n", block);
assert(tr->new_free_set);
block_set_add_block(tr, tr->new_free_set, block);
} else {
pr_write("add %lld to freed\n", block);
block_set_add_block(tr, &tr->freed, block);
}
}
}