/* * Copyright (C) Sistina Software, Inc. 1997-2003 All rights reserved. * Copyright (C) 2004-2005 Red Hat, Inc. All rights reserved. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU General Public License v.2. */ #include #include #include #include #include #include #include #include "gfs2.h" #include "lm_interface.h" #include "incore.h" #include "bmap.h" #include "inode.h" #include "meta_io.h" #include "trans.h" #include "unlinked.h" #include "util.h" static int munge_ondisk(struct gfs2_sbd *sdp, unsigned int slot, struct gfs2_unlinked_tag *ut) { struct gfs2_inode *ip = sdp->sd_ut_inode->u.generic_ip; unsigned int block, offset; uint64_t dblock; int new = 0; struct buffer_head *bh; int error; int boundary; block = slot / sdp->sd_ut_per_block; offset = slot % sdp->sd_ut_per_block; error = gfs2_block_map(ip->i_vnode, block, &new, &dblock, &boundary); if (error) return error; error = gfs2_meta_read(ip->i_gl, dblock, DIO_START | DIO_WAIT, &bh); if (error) return error; if (gfs2_metatype_check(sdp, bh, GFS2_METATYPE_UT)) { error = -EIO; goto out; } mutex_lock(&sdp->sd_unlinked_mutex); gfs2_trans_add_bh(ip->i_gl, bh, 1); gfs2_unlinked_tag_out(ut, bh->b_data + sizeof(struct gfs2_meta_header) + offset * sizeof(struct gfs2_unlinked_tag)); mutex_unlock(&sdp->sd_unlinked_mutex); out: brelse(bh); return error; } static void ul_hash(struct gfs2_sbd *sdp, struct gfs2_unlinked *ul) { spin_lock(&sdp->sd_unlinked_spin); list_add(&ul->ul_list, &sdp->sd_unlinked_list); gfs2_assert(sdp, ul->ul_count); ul->ul_count++; atomic_inc(&sdp->sd_unlinked_count); spin_unlock(&sdp->sd_unlinked_spin); } static void ul_unhash(struct gfs2_sbd *sdp, struct gfs2_unlinked *ul) { spin_lock(&sdp->sd_unlinked_spin); list_del_init(&ul->ul_list); gfs2_assert(sdp, ul->ul_count > 1); ul->ul_count--; gfs2_assert_warn(sdp, atomic_read(&sdp->sd_unlinked_count) > 0); atomic_dec(&sdp->sd_unlinked_count); spin_unlock(&sdp->sd_unlinked_spin); } static struct gfs2_unlinked *ul_fish(struct gfs2_sbd *sdp) { struct list_head *head; struct gfs2_unlinked *ul; int found = 0; if (sdp->sd_vfs->s_flags & MS_RDONLY) return NULL; spin_lock(&sdp->sd_unlinked_spin); head = &sdp->sd_unlinked_list; list_for_each_entry(ul, head, ul_list) { if (test_bit(ULF_LOCKED, &ul->ul_flags)) continue; list_move_tail(&ul->ul_list, head); ul->ul_count++; set_bit(ULF_LOCKED, &ul->ul_flags); found = 1; break; } if (!found) ul = NULL; spin_unlock(&sdp->sd_unlinked_spin); return ul; } /** * enforce_limit - limit the number of inodes waiting to be deallocated * @sdp: the filesystem * * Returns: errno */ static void enforce_limit(struct gfs2_sbd *sdp) { unsigned int tries = 0, min = 0; int error; if (atomic_read(&sdp->sd_unlinked_count) < gfs2_tune_get(sdp, gt_ilimit)) return; tries = gfs2_tune_get(sdp, gt_ilimit_tries); min = gfs2_tune_get(sdp, gt_ilimit_min); while (tries--) { struct gfs2_unlinked *ul = ul_fish(sdp); if (!ul) break; error = gfs2_inode_dealloc(sdp, ul); gfs2_unlinked_put(sdp, ul); if (!error) { if (!--min) break; } else if (error != 1) break; } } static struct gfs2_unlinked *ul_alloc(struct gfs2_sbd *sdp) { struct gfs2_unlinked *ul; ul = kzalloc(sizeof(struct gfs2_unlinked), GFP_KERNEL); if (ul) { INIT_LIST_HEAD(&ul->ul_list); ul->ul_count = 1; set_bit(ULF_LOCKED, &ul->ul_flags); } return ul; } int gfs2_unlinked_get(struct gfs2_sbd *sdp, struct gfs2_unlinked **ul) { unsigned int c, o = 0, b; unsigned char byte = 0; enforce_limit(sdp); *ul = ul_alloc(sdp); if (!*ul) return -ENOMEM; spin_lock(&sdp->sd_unlinked_spin); for (c = 0; c < sdp->sd_unlinked_chunks; c++) for (o = 0; o < PAGE_SIZE; o++) { byte = sdp->sd_unlinked_bitmap[c][o]; if (byte != 0xFF) goto found; } goto fail; found: for (b = 0; b < 8; b++) if (!(byte & (1 << b))) break; (*ul)->ul_slot = c * (8 * PAGE_SIZE) + o * 8 + b; if ((*ul)->ul_slot >= sdp->sd_unlinked_slots) goto fail; sdp->sd_unlinked_bitmap[c][o] |= 1 << b; spin_unlock(&sdp->sd_unlinked_spin); return 0; fail: spin_unlock(&sdp->sd_unlinked_spin); kfree(*ul); return -ENOSPC; } void gfs2_unlinked_put(struct gfs2_sbd *sdp, struct gfs2_unlinked *ul) { gfs2_assert_warn(sdp, test_and_clear_bit(ULF_LOCKED, &ul->ul_flags)); spin_lock(&sdp->sd_unlinked_spin); gfs2_assert(sdp, ul->ul_count); ul->ul_count--; if (!ul->ul_count) { gfs2_icbit_munge(sdp, sdp->sd_unlinked_bitmap, ul->ul_slot, 0); spin_unlock(&sdp->sd_unlinked_spin); kfree(ul); } else spin_unlock(&sdp->sd_unlinked_spin); } int gfs2_unlinked_ondisk_add(struct gfs2_sbd *sdp, struct gfs2_unlinked *ul) { int error; gfs2_assert_warn(sdp, test_bit(ULF_LOCKED, &ul->ul_flags)); gfs2_assert_warn(sdp, list_empty(&ul->ul_list)); error = munge_ondisk(sdp, ul->ul_slot, &ul->ul_ut); if (!error) ul_hash(sdp, ul); return error; } int gfs2_unlinked_ondisk_munge(struct gfs2_sbd *sdp, struct gfs2_unlinked *ul) { int error; gfs2_assert_warn(sdp, test_bit(ULF_LOCKED, &ul->ul_flags)); gfs2_assert_warn(sdp, !list_empty(&ul->ul_list)); error = munge_ondisk(sdp, ul->ul_slot, &ul->ul_ut); return error; } int gfs2_unlinked_ondisk_rm(struct gfs2_sbd *sdp, struct gfs2_unlinked *ul) { struct gfs2_unlinked_tag ut; int error; gfs2_assert_warn(sdp, test_bit(ULF_LOCKED, &ul->ul_flags)); gfs2_assert_warn(sdp, !list_empty(&ul->ul_list)); memset(&ut, 0, sizeof(struct gfs2_unlinked_tag)); error = munge_ondisk(sdp, ul->ul_slot, &ut); if (error) return error; ul_unhash(sdp, ul); return 0; } /** * gfs2_unlinked_dealloc - Go through the list of inodes to be deallocated * @sdp: the filesystem * * Returns: errno */ int gfs2_unlinked_dealloc(struct gfs2_sbd *sdp) { unsigned int hits, strikes; int error; for (;;) { hits = 0; strikes = 0; for (;;) { struct gfs2_unlinked *ul = ul_fish(sdp); if (!ul) return 0; error = gfs2_inode_dealloc(sdp, ul); gfs2_unlinked_put(sdp, ul); if (!error) { hits++; if (strikes) strikes--; } else if (error == 1) { strikes++; if (strikes >= atomic_read(&sdp->sd_unlinked_count)) { error = 0; break; } } else return error; } if (!hits || kthread_should_stop()) break; cond_resched(); } return 0; } int gfs2_unlinked_init(struct gfs2_sbd *sdp) { struct gfs2_inode *ip = sdp->sd_ut_inode->u.generic_ip; unsigned int blocks = ip->i_di.di_size >> sdp->sd_sb.sb_bsize_shift; unsigned int x, slot = 0; unsigned int found = 0; uint64_t dblock; uint32_t extlen = 0; int error; if (!ip->i_di.di_size || ip->i_di.di_size > (64 << 20) || ip->i_di.di_size & (sdp->sd_sb.sb_bsize - 1)) { gfs2_consist_inode(ip); return -EIO; } sdp->sd_unlinked_slots = blocks * sdp->sd_ut_per_block; sdp->sd_unlinked_chunks = DIV_ROUND_UP(sdp->sd_unlinked_slots, 8 * PAGE_SIZE); error = -ENOMEM; sdp->sd_unlinked_bitmap = kcalloc(sdp->sd_unlinked_chunks, sizeof(unsigned char *), GFP_KERNEL); if (!sdp->sd_unlinked_bitmap) return error; for (x = 0; x < sdp->sd_unlinked_chunks; x++) { sdp->sd_unlinked_bitmap[x] = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!sdp->sd_unlinked_bitmap[x]) goto fail; } for (x = 0; x < blocks; x++) { struct buffer_head *bh; unsigned int y; if (!extlen) { int new = 0; error = gfs2_extent_map(ip->i_vnode, x, &new, &dblock, &extlen); if (error) goto fail; } gfs2_meta_ra(ip->i_gl, dblock, extlen); error = gfs2_meta_read(ip->i_gl, dblock, DIO_START | DIO_WAIT, &bh); if (error) goto fail; error = -EIO; if (gfs2_metatype_check(sdp, bh, GFS2_METATYPE_UT)) { brelse(bh); goto fail; } for (y = 0; y < sdp->sd_ut_per_block && slot < sdp->sd_unlinked_slots; y++, slot++) { struct gfs2_unlinked_tag ut; struct gfs2_unlinked *ul; gfs2_unlinked_tag_in(&ut, bh->b_data + sizeof(struct gfs2_meta_header) + y * sizeof(struct gfs2_unlinked_tag)); if (!ut.ut_inum.no_addr) continue; error = -ENOMEM; ul = ul_alloc(sdp); if (!ul) { brelse(bh); goto fail; } ul->ul_ut = ut; ul->ul_slot = slot; spin_lock(&sdp->sd_unlinked_spin); gfs2_icbit_munge(sdp, sdp->sd_unlinked_bitmap, slot, 1); spin_unlock(&sdp->sd_unlinked_spin); ul_hash(sdp, ul); gfs2_unlinked_put(sdp, ul); found++; } brelse(bh); dblock++; extlen--; } if (found) fs_info(sdp, "found %u unlinked inodes\n", found); return 0; fail: gfs2_unlinked_cleanup(sdp); return error; } /** * gfs2_unlinked_cleanup - get rid of any extra struct gfs2_unlinked structures * @sdp: the filesystem * */ void gfs2_unlinked_cleanup(struct gfs2_sbd *sdp) { struct list_head *head = &sdp->sd_unlinked_list; struct gfs2_unlinked *ul; unsigned int x; spin_lock(&sdp->sd_unlinked_spin); while (!list_empty(head)) { ul = list_entry(head->next, struct gfs2_unlinked, ul_list); if (ul->ul_count > 1) { list_move_tail(&ul->ul_list, head); spin_unlock(&sdp->sd_unlinked_spin); schedule(); spin_lock(&sdp->sd_unlinked_spin); continue; } list_del_init(&ul->ul_list); atomic_dec(&sdp->sd_unlinked_count); gfs2_assert_warn(sdp, ul->ul_count == 1); gfs2_assert_warn(sdp, !test_bit(ULF_LOCKED, &ul->ul_flags)); kfree(ul); } spin_unlock(&sdp->sd_unlinked_spin); gfs2_assert_warn(sdp, !atomic_read(&sdp->sd_unlinked_count)); if (sdp->sd_unlinked_bitmap) { for (x = 0; x < sdp->sd_unlinked_chunks; x++) kfree(sdp->sd_unlinked_bitmap[x]); kfree(sdp->sd_unlinked_bitmap); } }