/*
* 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 <asm/semaphore.h>
#include "gfs2.h"
#include "bmap.h"
#include "glock.h"
#include "inode.h"
#include "jdata.h"
#include "meta_io.h"
#include "page.h"
#include "quota.h"
#include "rgrp.h"
#include "trans.h"
/* This doesn't need to be that large as max 64 bit pointers in a 4k
* block is 512, so __u16 is fine for that. It saves stack space to
* keep it small.
*/
struct metapath {
__u16 mp_list[GFS2_MAX_META_HEIGHT];
};
typedef int (*block_call_t) (struct gfs2_inode *ip, struct buffer_head *dibh,
struct buffer_head *bh, uint64_t *top,
uint64_t *bottom, unsigned int height,
void *data);
struct strip_mine {
int sm_first;
unsigned int sm_height;
};
/**
* @gfs2_unstuffer_sync - Synchronously unstuff a dinode
* @ip:
* @dibh:
* @block:
* @private:
*
* Cheat and use a metadata buffer instead of a data page.
*
* Returns: errno
*/
int gfs2_unstuffer_sync(struct gfs2_inode *ip, struct buffer_head *dibh,
uint64_t block, void *private)
{
struct buffer_head *bh;
int error;
bh = gfs2_meta_new(ip->i_gl, block);
gfs2_buffer_copy_tail(bh, 0, dibh, sizeof(struct gfs2_dinode));
set_buffer_dirty(bh);
error = sync_dirty_buffer(bh);
brelse(bh);
return error;
}
/**
* gfs2_unstuff_dinode - Unstuff a dinode when the data has grown too big
* @ip: The GFS2 inode to unstuff
* @unstuffer: the routine that handles unstuffing a non-zero length file
* @private: private data for the unstuffer
*
* This routine unstuffs a dinode and returns it to a "normal" state such
* that the height can be grown in the traditional way.
*
* Returns: errno
*/
int gfs2_unstuff_dinode(struct gfs2_inode *ip, gfs2_unstuffer_t unstuffer,
void *private)
{
struct buffer_head *bh, *dibh;
uint64_t block = 0;
int journaled = gfs2_is_jdata(ip);
int error;
down_write(&ip->i_rw_mutex);
error = gfs2_meta_inode_buffer(ip, &dibh);
if (error)
goto out;
if (ip->i_di.di_size) {
/* Get a free block, fill it with the stuffed data,
and write it out to disk */
if (journaled) {
block = gfs2_alloc_meta(ip);
error = gfs2_jdata_get_buffer(ip, block, 1, &bh);
if (error)
goto out_brelse;
gfs2_buffer_copy_tail(bh,
sizeof(struct gfs2_meta_header),
dibh, sizeof(struct gfs2_dinode));
brelse(bh);
} else {
block = gfs2_alloc_data(ip);
error = unstuffer(ip, dibh, block, private);
if (error)
goto out_brelse;
}
}
/* Set up the pointer to the new block */
gfs2_trans_add_bh(ip->i_gl, dibh);
gfs2_buffer_clear_tail(dibh, sizeof(struct gfs2_dinode));
if (ip->i_di.di_size) {
*(uint64_t *)(dibh->b_data + sizeof(struct gfs2_dinode)) = cpu_to_be64(block);
ip->i_di.di_blocks++;
}
ip->i_di.di_height = 1;
gfs2_dinode_out(&ip->i_di, dibh->b_data);
out_brelse:
brelse(dibh);
out:
up_write(&ip->i_rw_mutex);
return error;
}
/**
* calc_tree_height - Calculate the height of a metadata tree
* @ip: The GFS2 inode
* @size: The proposed size of the file
*
* Work out how tall a metadata tree needs to be in order to accommodate a
* file of a particular size. If size is less than the current size of
* the inode, then the current size of the inode is used instead of the
* supplied one.
*
* Returns: the height the t