/*
* linux/fs/affs/file.c
*
* (c) 1996 Hans-Joachim Widmaier - Rewritten
*
* (C) 1993 Ray Burr - Modified for Amiga FFS filesystem.
*
* (C) 1992 Eric Youngdale Modified for ISO 9660 filesystem.
*
* (C) 1991 Linus Torvalds - minix filesystem
*
* affs regular file handling primitives
*/
#include "affs.h"
#if PAGE_SIZE < 4096
#error PAGE_SIZE must be at least 4096
#endif
static int affs_grow_extcache(struct inode *inode, u32 lc_idx);
static struct buffer_head *affs_alloc_extblock(struct inode *inode, struct buffer_head *bh, u32 ext);
static inline struct buffer_head *affs_get_extblock(struct inode *inode, u32 ext);
static struct buffer_head *affs_get_extblock_slow(struct inode *inode, u32 ext);
static int affs_file_open(struct inode *inode, struct file *filp);
static int affs_file_release(struct inode *inode, struct file *filp);
const struct file_operations affs_file_operations = {
.llseek = generic_file_llseek,
.read = generic_file_read,
.write = generic_file_write,
.mmap = generic_file_mmap,
.open = affs_file_open,
.release = affs_file_release,
.fsync = file_fsync,
.sendfile = generic_file_sendfile,
};
struct inode_operations affs_file_inode_operations = {
.truncate = affs_truncate,
.setattr = affs_notify_change,
};
static int
affs_file_open(struct inode *inode, struct file *filp)
{
if (atomic_read(&filp->f_count) != 1)
return 0;
pr_debug("AFFS: open(%d)\n", AFFS_I(inode)->i_opencnt);
AFFS_I(inode)->i_opencnt++;
return 0;
}
static int
affs_file_release(struct inode *inode, struct file *filp)
{
if (atomic_read(&filp->f_count) != 0)
return 0;
pr_debug("AFFS: release(%d)\n", AFFS_I(inode)->i_opencnt);
AFFS_I(inode)->i_opencnt--;
if (!AFFS_I(inode)->i_opencnt)
affs_free_prealloc(inode);
return 0;
}
static int
affs_grow_extcache(struct inode *inode, u32 lc_idx)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *bh;
u32 lc_max;
int i, j, key;
if (!AFFS_I(inode)->i_lc) {
char *ptr = (char *)get_zeroed_page(GFP_NOFS);
if (!ptr)
return -ENOMEM;
AFFS_I(inode)->i_lc = (u32 *)ptr;
AFFS_I(inode)->i_ac = (struct affs_ext_key *)(ptr + AFFS_CACHE_SIZE / 2);
}
lc_max = AFFS_LC_SIZE << AFFS_I(inode)->i_lc_shift;
if (AFFS_I(inode)->i_extcnt > lc_max) {
u32 lc_shift, lc_mask, tmp, off;
/* need to recalculate linear cache, start from old size */
lc_shift = AFFS_I(inode)->i_lc_shift;
tmp = (AFFS_I(inode)->i_extcnt / AFFS_LC_SIZE) >> lc_shift;
for (; tmp; tmp >>= 1)
lc_shift++;
lc_mask = (1 << lc_shift) - 1;
/* fix idx and old size to new shift */
lc_idx >>= (lc_shift - AFFS_I(inode)->i_lc_shift);
AFFS_I(inode)->i_lc_size >>= (lc_shift - AFFS_I(inode)->i_lc_shift);
/* first shrink old cache to make more space */
off = 1 << (lc_shift - AFFS_I(inode)->i_lc_shift);
for (i = 1, j = off; j < AFFS_LC_SIZE; i++, j += off)
AFFS_I(inode)->i_ac[i] = AFFS_I(inode)->i_ac[j];
AFFS_I(inode)->i_lc_shift = lc_shift;
AFFS_I(inode)->i_lc_mask = lc_mask;
}
/* fill cache to the needed index */
i = AFFS_I(inode)->i_lc_size;
AFFS_I(inode)->i_lc_size = lc_idx + 1;
for (; i <= lc_idx; i++) {
if (!i) {
AFFS_I(inode)->i_lc[0] = inode->i_ino;
continue;
}
key = AFFS_I(inode)->i_lc[i - 1];
j = AFFS_I(inode)->i_lc_mask + 1;
// unlock cache
for (; j > 0; j--) {
bh = affs_bread(sb, key);
if (!bh)
goto err;
key = be32_to_cpu(AFFS_TAIL(sb, bh)->extension);
affs_brelse(bh);
}
// lock cache
AFFS_I(inode)->i_lc[i] = key;
}
return 0;
err:
// lock cache
return -EIO;
}
static struct buffer_head *
affs_alloc_extblock(struct inode *inode, struct buffer_head *bh, u32 ext)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *new_bh;
u32 blocknr, tmp;
blocknr = affs_alloc_block(inode, bh->b_blocknr);
if (!blocknr)
return ERR_PTR(-ENOSPC);
new_bh = affs_getzeroblk(sb, blocknr);
if (!new_bh) {
affs_free_block(sb, blocknr);
return ERR_PTR(-EIO);
}
AFFS_HEAD(new_bh)->ptype = cpu_to_be32(T_LIST);
AFFS_HEAD(new_bh)->key = cpu_to_be32(blocknr);
AFFS_TAIL(sb, new_bh)->stype = cpu_to_be32(ST_FILE);
AFFS_TAIL(sb, new_bh)->parent = cpu_to_be32(inode->i_ino);
affs_fix_checksum(sb, new_bh);
mark_buffer_dirty_inode(new_bh, inode);
tmp = be32_to_cpu(AFFS_TAIL(sb, bh)->extension);
if (tmp)
affs_warning(sb, "alloc_ext", "previous extension set (%x)", tmp);
AFFS_TAIL(sb, bh)->extension = cpu_to_be32(blocknr);
affs_adjust_checksum(bh, blocknr - tmp);