summaryrefslogtreecommitdiffstats
path: root/fs/gfs2/unlinked.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/gfs2/unlinked.c')
-rw-r--r--fs/gfs2/unlinked.c453
1 files changed, 453 insertions, 0 deletions
diff --git a/fs/gfs2/unlinked.c b/fs/gfs2/unlinked.c
new file mode 100644
index 000000000000..4a993af58c1a
--- /dev/null
+++ b/fs/gfs2/unlinked.c
@@ -0,0 +1,453 @@
+/*
+ * 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 <asm/semaphore.h>
+
+#include "gfs2.h"
+#include "bmap.h"
+#include "inode.h"
+#include "meta_io.h"
+#include "trans.h"
+#include "unlinked.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;
+ unsigned int block, offset;
+ uint64_t dblock;
+ int new = 0;
+ struct buffer_head *bh;
+ int error;
+
+ block = slot / sdp->sd_ut_per_block;
+ offset = slot % sdp->sd_ut_per_block;
+
+ error = gfs2_block_map(ip, block, &new, &dblock, NULL);
+ 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;
+ }
+
+ down(&sdp->sd_unlinked_mutex);
+ gfs2_trans_add_bh(ip->i_gl, bh);
+ gfs2_unlinked_tag_out(ut, bh->b_data +
+ sizeof(struct gfs2_meta_header) +
+ offset * sizeof(struct gfs2_unlinked_tag));
+ up(&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)) {
+ 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;
+ 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_RU(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_block_map(ip, 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);
+ }
+}
+