| /* |
| * 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 <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/completion.h> |
| #include <linux/buffer_head.h> |
| #include <linux/kthread.h> |
| #include <linux/gfs2_ondisk.h> |
| |
| #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); |
| } |
| } |
| |