Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
This commit is contained in:
Linus Torvalds
2005-04-16 15:20:36 -07:00
commit 1da177e4c3
17291 changed files with 6718755 additions and 0 deletions

1729
fs/Kconfig Normal file

File diff suppressed because it is too large Load Diff

134
fs/Kconfig.binfmt Normal file
View File

@@ -0,0 +1,134 @@
config BINFMT_ELF
bool "Kernel support for ELF binaries"
depends on MMU
default y
---help---
ELF (Executable and Linkable Format) is a format for libraries and
executables used across different architectures and operating
systems. Saying Y here will enable your kernel to run ELF binaries
and enlarge it by about 13 KB. ELF support under Linux has now all
but replaced the traditional Linux a.out formats (QMAGIC and ZMAGIC)
because it is portable (this does *not* mean that you will be able
to run executables from different architectures or operating systems
however) and makes building run-time libraries very easy. Many new
executables are distributed solely in ELF format. You definitely
want to say Y here.
Information about ELF is contained in the ELF HOWTO available from
<http://www.tldp.org/docs.html#howto>.
If you find that after upgrading from Linux kernel 1.2 and saying Y
here, you still can't run any ELF binaries (they just crash), then
you'll have to install the newest ELF runtime libraries, including
ld.so (check the file <file:Documentation/Changes> for location and
latest version).
config BINFMT_ELF_FDPIC
bool "Kernel support for FDPIC ELF binaries"
default y
depends on FRV
help
ELF FDPIC binaries are based on ELF, but allow the individual load
segments of a binary to be located in memory independently of each
other. This makes this format ideal for use in environments where no
MMU is available as it still permits text segments to be shared,
even if data segments are not.
It is also possible to run FDPIC ELF binaries on MMU linux also.
config BINFMT_FLAT
tristate "Kernel support for flat binaries"
depends on !MMU || SUPERH
help
Support uClinux FLAT format binaries.
config BINFMT_ZFLAT
bool "Enable ZFLAT support"
depends on BINFMT_FLAT
select ZLIB_INFLATE
help
Support FLAT format compressed binaries
config BINFMT_SHARED_FLAT
bool "Enable shared FLAT support"
depends on BINFMT_FLAT
help
Support FLAT shared libraries
config BINFMT_AOUT
tristate "Kernel support for a.out and ECOFF binaries"
depends on (X86 && !X86_64) || ALPHA || ARM || M68K || SPARC32
---help---
A.out (Assembler.OUTput) is a set of formats for libraries and
executables used in the earliest versions of UNIX. Linux used
the a.out formats QMAGIC and ZMAGIC until they were replaced
with the ELF format.
The conversion to ELF started in 1995. This option is primarily
provided for historical interest and for the benefit of those
who need to run binaries from that era.
Most people should answer N here. If you think you may have
occasional use for this format, enable module support above
and answer M here to compile this support as a module called
binfmt_aout.
If any crucial components of your system (such as /sbin/init
or /lib/ld.so) are still in a.out format, you will have to
say Y here.
config OSF4_COMPAT
bool "OSF/1 v4 readv/writev compatibility"
depends on ALPHA && BINFMT_AOUT
help
Say Y if you are using OSF/1 binaries (like Netscape and Acrobat)
with v4 shared libraries freely available from Compaq. If you're
going to use shared libraries from Tru64 version 5.0 or later, say N.
config BINFMT_EM86
tristate "Kernel support for Linux/Intel ELF binaries"
depends on ALPHA
---help---
Say Y here if you want to be able to execute Linux/Intel ELF
binaries just like native Alpha binaries on your Alpha machine. For
this to work, you need to have the emulator /usr/bin/em86 in place.
You can get the same functionality by saying N here and saying Y to
"Kernel support for MISC binaries".
You may answer M to compile the emulation support as a module and
later load the module when you want to use a Linux/Intel binary. The
module will be called binfmt_em86. If unsure, say Y.
config BINFMT_SOM
tristate "Kernel support for SOM binaries"
depends on PARISC && HPUX
help
SOM is a binary executable format inherited from HP/UX. Say
Y here to be able to load and execute SOM binaries directly.
config BINFMT_MISC
tristate "Kernel support for MISC binaries"
---help---
If you say Y here, it will be possible to plug wrapper-driven binary
formats into the kernel. You will like this especially when you use
programs that need an interpreter to run like Java, Python, .NET or
Emacs-Lisp. It's also useful if you often run DOS executables under
the Linux DOS emulator DOSEMU (read the DOSEMU-HOWTO, available from
<http://www.tldp.org/docs.html#howto>). Once you have
registered such a binary class with the kernel, you can start one of
those programs simply by typing in its name at a shell prompt; Linux
will automatically feed it to the correct interpreter.
You can do other nice things, too. Read the file
<file:Documentation/binfmt_misc.txt> to learn how to use this
feature, <file:Documentation/java.txt> for information about how
to include Java support. and <file:Documentation/mono.txt> for
information about how to include Mono-based .NET support.
To use binfmt_misc, you will need to mount it:
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
You may say M here for module support and later load the module when
you have use for it; the module is called binfmt_misc. If you
don't know what to answer at this point, say Y.

97
fs/Makefile Normal file
View File

@@ -0,0 +1,97 @@
#
# Makefile for the Linux filesystems.
#
# 14 Sep 2000, Christoph Hellwig <hch@infradead.org>
# Rewritten to use lists instead of if-statements.
#
obj-y := open.o read_write.o file_table.o buffer.o bio.o super.o \
block_dev.o char_dev.o stat.o exec.o pipe.o namei.o fcntl.o \
ioctl.o readdir.o select.o fifo.o locks.o dcache.o inode.o \
attr.o bad_inode.o file.o filesystems.o namespace.o aio.o \
seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \
obj-$(CONFIG_EPOLL) += eventpoll.o
obj-$(CONFIG_COMPAT) += compat.o
nfsd-$(CONFIG_NFSD) := nfsctl.o
obj-y += $(nfsd-y) $(nfsd-m)
obj-$(CONFIG_BINFMT_AOUT) += binfmt_aout.o
obj-$(CONFIG_BINFMT_EM86) += binfmt_em86.o
obj-$(CONFIG_BINFMT_MISC) += binfmt_misc.o
# binfmt_script is always there
obj-y += binfmt_script.o
obj-$(CONFIG_BINFMT_ELF) += binfmt_elf.o
obj-$(CONFIG_BINFMT_ELF_FDPIC) += binfmt_elf_fdpic.o
obj-$(CONFIG_BINFMT_SOM) += binfmt_som.o
obj-$(CONFIG_BINFMT_FLAT) += binfmt_flat.o
obj-$(CONFIG_FS_MBCACHE) += mbcache.o
obj-$(CONFIG_FS_POSIX_ACL) += posix_acl.o xattr_acl.o
obj-$(CONFIG_QUOTA) += dquot.o
obj-$(CONFIG_QFMT_V1) += quota_v1.o
obj-$(CONFIG_QFMT_V2) += quota_v2.o
obj-$(CONFIG_QUOTACTL) += quota.o
obj-$(CONFIG_DNOTIFY) += dnotify.o
obj-$(CONFIG_PROC_FS) += proc/
obj-y += partitions/
obj-$(CONFIG_SYSFS) += sysfs/
obj-y += devpts/
obj-$(CONFIG_PROFILING) += dcookies.o
# Do not add any filesystems before this line
obj-$(CONFIG_REISERFS_FS) += reiserfs/
obj-$(CONFIG_EXT3_FS) += ext3/ # Before ext2 so root fs can be ext3
obj-$(CONFIG_JBD) += jbd/
obj-$(CONFIG_EXT2_FS) += ext2/
obj-$(CONFIG_CRAMFS) += cramfs/
obj-$(CONFIG_RAMFS) += ramfs/
obj-$(CONFIG_HUGETLBFS) += hugetlbfs/
obj-$(CONFIG_CODA_FS) += coda/
obj-$(CONFIG_MINIX_FS) += minix/
obj-$(CONFIG_FAT_FS) += fat/
obj-$(CONFIG_MSDOS_FS) += msdos/
obj-$(CONFIG_VFAT_FS) += vfat/
obj-$(CONFIG_BFS_FS) += bfs/
obj-$(CONFIG_ISO9660_FS) += isofs/
obj-$(CONFIG_DEVFS_FS) += devfs/
obj-$(CONFIG_HFSPLUS_FS) += hfsplus/ # Before hfs to find wrapped HFS+
obj-$(CONFIG_HFS_FS) += hfs/
obj-$(CONFIG_VXFS_FS) += freevxfs/
obj-$(CONFIG_NFS_FS) += nfs/
obj-$(CONFIG_EXPORTFS) += exportfs/
obj-$(CONFIG_NFSD) += nfsd/
obj-$(CONFIG_LOCKD) += lockd/
obj-$(CONFIG_NLS) += nls/
obj-$(CONFIG_SYSV_FS) += sysv/
obj-$(CONFIG_SMB_FS) += smbfs/
obj-$(CONFIG_CIFS) += cifs/
obj-$(CONFIG_NCP_FS) += ncpfs/
obj-$(CONFIG_HPFS_FS) += hpfs/
obj-$(CONFIG_NTFS_FS) += ntfs/
obj-$(CONFIG_UFS_FS) += ufs/
obj-$(CONFIG_EFS_FS) += efs/
obj-$(CONFIG_JFFS_FS) += jffs/
obj-$(CONFIG_JFFS2_FS) += jffs2/
obj-$(CONFIG_AFFS_FS) += affs/
obj-$(CONFIG_ROMFS_FS) += romfs/
obj-$(CONFIG_QNX4FS_FS) += qnx4/
obj-$(CONFIG_AUTOFS_FS) += autofs/
obj-$(CONFIG_AUTOFS4_FS) += autofs4/
obj-$(CONFIG_ADFS_FS) += adfs/
obj-$(CONFIG_UDF_FS) += udf/
obj-$(CONFIG_SUN_OPENPROMFS) += openpromfs/
obj-$(CONFIG_JFS_FS) += jfs/
obj-$(CONFIG_XFS_FS) += xfs/
obj-$(CONFIG_AFS_FS) += afs/
obj-$(CONFIG_BEFS_FS) += befs/
obj-$(CONFIG_HOSTFS) += hostfs/
obj-$(CONFIG_HPPFS) += hppfs/
obj-$(CONFIG_DEBUG_FS) += debugfs/

7
fs/adfs/Makefile Normal file
View File

@@ -0,0 +1,7 @@
#
# Makefile for the linux adfs filesystem routines.
#
obj-$(CONFIG_ADFS_FS) += adfs.o
adfs-objs := dir.o dir_f.o dir_fplus.o file.o inode.o map.o super.o

127
fs/adfs/adfs.h Normal file
View File

@@ -0,0 +1,127 @@
/* Internal data structures for ADFS */
#define ADFS_FREE_FRAG 0
#define ADFS_BAD_FRAG 1
#define ADFS_ROOT_FRAG 2
#define ADFS_NDA_OWNER_READ (1 << 0)
#define ADFS_NDA_OWNER_WRITE (1 << 1)
#define ADFS_NDA_LOCKED (1 << 2)
#define ADFS_NDA_DIRECTORY (1 << 3)
#define ADFS_NDA_EXECUTE (1 << 4)
#define ADFS_NDA_PUBLIC_READ (1 << 5)
#define ADFS_NDA_PUBLIC_WRITE (1 << 6)
#include <linux/version.h>
#include "dir_f.h"
struct buffer_head;
/*
* Directory handling
*/
struct adfs_dir {
struct super_block *sb;
int nr_buffers;
struct buffer_head *bh[4];
unsigned int pos;
unsigned int parent_id;
struct adfs_dirheader dirhead;
union adfs_dirtail dirtail;
};
/*
* This is the overall maximum name length
*/
#define ADFS_MAX_NAME_LEN 256
struct object_info {
__u32 parent_id; /* parent object id */
__u32 file_id; /* object id */
__u32 loadaddr; /* load address */
__u32 execaddr; /* execution address */
__u32 size; /* size */
__u8 attr; /* RISC OS attributes */
unsigned char name_len; /* name length */
char name[ADFS_MAX_NAME_LEN];/* file name */
};
struct adfs_dir_ops {
int (*read)(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir);
int (*setpos)(struct adfs_dir *dir, unsigned int fpos);
int (*getnext)(struct adfs_dir *dir, struct object_info *obj);
int (*update)(struct adfs_dir *dir, struct object_info *obj);
int (*create)(struct adfs_dir *dir, struct object_info *obj);
int (*remove)(struct adfs_dir *dir, struct object_info *obj);
void (*free)(struct adfs_dir *dir);
};
struct adfs_discmap {
struct buffer_head *dm_bh;
__u32 dm_startblk;
unsigned int dm_startbit;
unsigned int dm_endbit;
};
/* Inode stuff */
struct inode *adfs_iget(struct super_block *sb, struct object_info *obj);
int adfs_write_inode(struct inode *inode,int unused);
int adfs_notify_change(struct dentry *dentry, struct iattr *attr);
/* map.c */
extern int adfs_map_lookup(struct super_block *sb, unsigned int frag_id, unsigned int offset);
extern unsigned int adfs_map_free(struct super_block *sb);
/* Misc */
void __adfs_error(struct super_block *sb, const char *function,
const char *fmt, ...);
#define adfs_error(sb, fmt...) __adfs_error(sb, __FUNCTION__, fmt)
/* super.c */
/*
* Inodes and file operations
*/
/* dir_*.c */
extern struct inode_operations adfs_dir_inode_operations;
extern struct file_operations adfs_dir_operations;
extern struct dentry_operations adfs_dentry_operations;
extern struct adfs_dir_ops adfs_f_dir_ops;
extern struct adfs_dir_ops adfs_fplus_dir_ops;
extern int adfs_dir_update(struct super_block *sb, struct object_info *obj);
/* file.c */
extern struct inode_operations adfs_file_inode_operations;
extern struct file_operations adfs_file_operations;
extern inline __u32 signed_asl(__u32 val, signed int shift)
{
if (shift >= 0)
val <<= shift;
else
val >>= -shift;
return val;
}
/*
* Calculate the address of a block in an object given the block offset
* and the object identity.
*
* The root directory ID should always be looked up in the map [3.4]
*/
extern inline int
__adfs_block_map(struct super_block *sb, unsigned int object_id,
unsigned int block)
{
if (object_id & 255) {
unsigned int off;
off = (object_id & 255) - 1;
block += off << ADFS_SB(sb)->s_log2sharesize;
}
return adfs_map_lookup(sb, object_id >> 8, block);
}

302
fs/adfs/dir.c Normal file
View File

@@ -0,0 +1,302 @@
/*
* linux/fs/adfs/dir.c
*
* Copyright (C) 1999-2000 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Common directory handling for ADFS
*/
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/adfs_fs.h>
#include <linux/time.h>
#include <linux/stat.h>
#include <linux/spinlock.h>
#include <linux/smp_lock.h>
#include <linux/buffer_head.h> /* for file_fsync() */
#include "adfs.h"
/*
* For future. This should probably be per-directory.
*/
static DEFINE_RWLOCK(adfs_dir_lock);
static int
adfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
struct inode *inode = filp->f_dentry->d_inode;
struct super_block *sb = inode->i_sb;
struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
struct object_info obj;
struct adfs_dir dir;
int ret = 0;
lock_kernel();
if (filp->f_pos >> 32)
goto out;
ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
if (ret)
goto out;
switch ((unsigned long)filp->f_pos) {
case 0:
if (filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR) < 0)
goto free_out;
filp->f_pos += 1;
case 1:
if (filldir(dirent, "..", 2, 1, dir.parent_id, DT_DIR) < 0)
goto free_out;
filp->f_pos += 1;
default:
break;
}
read_lock(&adfs_dir_lock);
ret = ops->setpos(&dir, filp->f_pos - 2);
if (ret)
goto unlock_out;
while (ops->getnext(&dir, &obj) == 0) {
if (filldir(dirent, obj.name, obj.name_len,
filp->f_pos, obj.file_id, DT_UNKNOWN) < 0)
goto unlock_out;
filp->f_pos += 1;
}
unlock_out:
read_unlock(&adfs_dir_lock);
free_out:
ops->free(&dir);
out:
unlock_kernel();
return ret;
}
int
adfs_dir_update(struct super_block *sb, struct object_info *obj)
{
int ret = -EINVAL;
#ifdef CONFIG_ADFS_FS_RW
struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
struct adfs_dir dir;
printk(KERN_INFO "adfs_dir_update: object %06X in dir %06X\n",
obj->file_id, obj->parent_id);
if (!ops->update) {
ret = -EINVAL;
goto out;
}
ret = ops->read(sb, obj->parent_id, 0, &dir);
if (ret)
goto out;
write_lock(&adfs_dir_lock);
ret = ops->update(&dir, obj);
write_unlock(&adfs_dir_lock);
ops->free(&dir);
out:
#endif
return ret;
}
static int
adfs_match(struct qstr *name, struct object_info *obj)
{
int i;
if (name->len != obj->name_len)
return 0;
for (i = 0; i < name->len; i++) {
char c1, c2;
c1 = name->name[i];
c2 = obj->name[i];
if (c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
if (c2 >= 'A' && c2 <= 'Z')
c2 += 'a' - 'A';
if (c1 != c2)
return 0;
}
return 1;
}
static int
adfs_dir_lookup_byname(struct inode *inode, struct qstr *name, struct object_info *obj)
{
struct super_block *sb = inode->i_sb;
struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
struct adfs_dir dir;
int ret;
ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
if (ret)
goto out;
if (ADFS_I(inode)->parent_id != dir.parent_id) {
adfs_error(sb, "parent directory changed under me! (%lx but got %lx)\n",
ADFS_I(inode)->parent_id, dir.parent_id);
ret = -EIO;
goto free_out;
}
obj->parent_id = inode->i_ino;
/*
* '.' is handled by reserved_lookup() in fs/namei.c
*/
if (name->len == 2 && name->name[0] == '.' && name->name[1] == '.') {
/*
* Currently unable to fill in the rest of 'obj',
* but this is better than nothing. We need to
* ascend one level to find it's parent.
*/
obj->name_len = 0;
obj->file_id = obj->parent_id;
goto free_out;
}
read_lock(&adfs_dir_lock);
ret = ops->setpos(&dir, 0);
if (ret)
goto unlock_out;
ret = -ENOENT;
while (ops->getnext(&dir, obj) == 0) {
if (adfs_match(name, obj)) {
ret = 0;
break;
}
}
unlock_out:
read_unlock(&adfs_dir_lock);
free_out:
ops->free(&dir);
out:
return ret;
}
struct file_operations adfs_dir_operations = {
.read = generic_read_dir,
.readdir = adfs_readdir,
.fsync = file_fsync,
};
static int
adfs_hash(struct dentry *parent, struct qstr *qstr)
{
const unsigned int name_len = ADFS_SB(parent->d_sb)->s_namelen;
const unsigned char *name;
unsigned long hash;
int i;
if (qstr->len < name_len)
return 0;
/*
* Truncate the name in place, avoids
* having to define a compare function.
*/
qstr->len = i = name_len;
name = qstr->name;
hash = init_name_hash();
while (i--) {
char c;
c = *name++;
if (c >= 'A' && c <= 'Z')
c += 'a' - 'A';
hash = partial_name_hash(c, hash);
}
qstr->hash = end_name_hash(hash);
return 0;
}
/*
* Compare two names, taking note of the name length
* requirements of the underlying filesystem.
*/
static int
adfs_compare(struct dentry *parent, struct qstr *entry, struct qstr *name)
{
int i;
if (entry->len != name->len)
return 1;
for (i = 0; i < name->len; i++) {
char a, b;
a = entry->name[i];
b = name->name[i];
if (a >= 'A' && a <= 'Z')
a += 'a' - 'A';
if (b >= 'A' && b <= 'Z')
b += 'a' - 'A';
if (a != b)
return 1;
}
return 0;
}
struct dentry_operations adfs_dentry_operations = {
.d_hash = adfs_hash,
.d_compare = adfs_compare,
};
static struct dentry *
adfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
struct inode *inode = NULL;
struct object_info obj;
int error;
dentry->d_op = &adfs_dentry_operations;
lock_kernel();
error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj);
if (error == 0) {
error = -EACCES;
/*
* This only returns NULL if get_empty_inode
* fails.
*/
inode = adfs_iget(dir->i_sb, &obj);
if (inode)
error = 0;
}
unlock_kernel();
d_add(dentry, inode);
return ERR_PTR(error);
}
/*
* directories can handle most operations...
*/
struct inode_operations adfs_dir_inode_operations = {
.lookup = adfs_lookup,
.setattr = adfs_notify_change,
};

460
fs/adfs/dir_f.c Normal file
View File

@@ -0,0 +1,460 @@
/*
* linux/fs/adfs/dir_f.c
*
* Copyright (C) 1997-1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* E and F format directory handling
*/
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/adfs_fs.h>
#include <linux/time.h>
#include <linux/stat.h>
#include <linux/spinlock.h>
#include <linux/buffer_head.h>
#include <linux/string.h>
#include "adfs.h"
#include "dir_f.h"
static void adfs_f_free(struct adfs_dir *dir);
/*
* Read an (unaligned) value of length 1..4 bytes
*/
static inline unsigned int adfs_readval(unsigned char *p, int len)
{
unsigned int val = 0;
switch (len) {
case 4: val |= p[3] << 24;
case 3: val |= p[2] << 16;
case 2: val |= p[1] << 8;
default: val |= p[0];
}
return val;
}
static inline void adfs_writeval(unsigned char *p, int len, unsigned int val)
{
switch (len) {
case 4: p[3] = val >> 24;
case 3: p[2] = val >> 16;
case 2: p[1] = val >> 8;
default: p[0] = val;
}
}
static inline int adfs_readname(char *buf, char *ptr, int maxlen)
{
char *old_buf = buf;
while (*ptr >= ' ' && maxlen--) {
if (*ptr == '/')
*buf++ = '.';
else
*buf++ = *ptr;
ptr++;
}
*buf = '\0';
return buf - old_buf;
}
#define ror13(v) ((v >> 13) | (v << 19))
#define dir_u8(idx) \
({ int _buf = idx >> blocksize_bits; \
int _off = idx - (_buf << blocksize_bits);\
*(u8 *)(bh[_buf]->b_data + _off); \
})
#define dir_u32(idx) \
({ int _buf = idx >> blocksize_bits; \
int _off = idx - (_buf << blocksize_bits);\
*(__le32 *)(bh[_buf]->b_data + _off); \
})
#define bufoff(_bh,_idx) \
({ int _buf = _idx >> blocksize_bits; \
int _off = _idx - (_buf << blocksize_bits);\
(u8 *)(_bh[_buf]->b_data + _off); \
})
/*
* There are some algorithms that are nice in
* assembler, but a bitch in C... This is one
* of them.
*/
static u8
adfs_dir_checkbyte(const struct adfs_dir *dir)
{
struct buffer_head * const *bh = dir->bh;
const int blocksize_bits = dir->sb->s_blocksize_bits;
union { __le32 *ptr32; u8 *ptr8; } ptr, end;
u32 dircheck = 0;
int last = 5 - 26;
int i = 0;
/*
* Accumulate each word up to the last whole
* word of the last directory entry. This
* can spread across several buffer heads.
*/
do {
last += 26;
do {
dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck);
i += sizeof(u32);
} while (i < (last & ~3));
} while (dir_u8(last) != 0);
/*
* Accumulate the last few bytes. These
* bytes will be within the same bh.
*/
if (i != last) {
ptr.ptr8 = bufoff(bh, i);
end.ptr8 = ptr.ptr8 + last - i;
do
dircheck = *ptr.ptr8++ ^ ror13(dircheck);
while (ptr.ptr8 < end.ptr8);
}
/*
* The directory tail is in the final bh
* Note that contary to the RISC OS PRMs,
* the first few bytes are NOT included
* in the check. All bytes are in the
* same bh.
*/
ptr.ptr8 = bufoff(bh, 2008);
end.ptr8 = ptr.ptr8 + 36;
do {
__le32 v = *ptr.ptr32++;
dircheck = le32_to_cpu(v) ^ ror13(dircheck);
} while (ptr.ptr32 < end.ptr32);
return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff;
}
/*
* Read and check that a directory is valid
*/
static int
adfs_dir_read(struct super_block *sb, unsigned long object_id,
unsigned int size, struct adfs_dir *dir)
{
const unsigned int blocksize_bits = sb->s_blocksize_bits;
int blk = 0;
/*
* Directories which are not a multiple of 2048 bytes
* are considered bad v2 [3.6]
*/
if (size & 2047)
goto bad_dir;
size >>= blocksize_bits;
dir->nr_buffers = 0;
dir->sb = sb;
for (blk = 0; blk < size; blk++) {
int phys;
phys = __adfs_block_map(sb, object_id, blk);
if (!phys) {
adfs_error(sb, "dir object %lX has a hole at offset %d",
object_id, blk);
goto release_buffers;
}
dir->bh[blk] = sb_bread(sb, phys);
if (!dir->bh[blk])
goto release_buffers;
}
memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
goto bad_dir;
if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
memcmp(&dir->dirhead.startname, "Hugo", 4))
goto bad_dir;
if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
goto bad_dir;
dir->nr_buffers = blk;
return 0;
bad_dir:
adfs_error(sb, "corrupted directory fragment %lX",
object_id);
release_buffers:
for (blk -= 1; blk >= 0; blk -= 1)
brelse(dir->bh[blk]);
dir->sb = NULL;
return -EIO;
}
/*
* convert a disk-based directory entry to a Linux ADFS directory entry
*/
static inline void
adfs_dir2obj(struct object_info *obj, struct adfs_direntry *de)
{
obj->name_len = adfs_readname(obj->name, de->dirobname, ADFS_F_NAME_LEN);
obj->file_id = adfs_readval(de->dirinddiscadd, 3);
obj->loadaddr = adfs_readval(de->dirload, 4);
obj->execaddr = adfs_readval(de->direxec, 4);
obj->size = adfs_readval(de->dirlen, 4);
obj->attr = de->newdiratts;
}
/*
* convert a Linux ADFS directory entry to a disk-based directory entry
*/
static inline void
adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj)
{
adfs_writeval(de->dirinddiscadd, 3, obj->file_id);
adfs_writeval(de->dirload, 4, obj->loadaddr);
adfs_writeval(de->direxec, 4, obj->execaddr);
adfs_writeval(de->dirlen, 4, obj->size);
de->newdiratts = obj->attr;
}
/*
* get a directory entry. Note that the caller is responsible
* for holding the relevant locks.
*/
static int
__adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj)
{
struct super_block *sb = dir->sb;
struct adfs_direntry de;
int thissize, buffer, offset;
buffer = pos >> sb->s_blocksize_bits;
if (buffer > dir->nr_buffers)
return -EINVAL;
offset = pos & (sb->s_blocksize - 1);
thissize = sb->s_blocksize - offset;
if (thissize > 26)
thissize = 26;
memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
if (thissize != 26)
memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
26 - thissize);
if (!de.dirobname[0])
return -ENOENT;
adfs_dir2obj(obj, &de);
return 0;
}
static int
__adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj)
{
struct super_block *sb = dir->sb;
struct adfs_direntry de;
int thissize, buffer, offset;
buffer = pos >> sb->s_blocksize_bits;
if (buffer > dir->nr_buffers)
return -EINVAL;
offset = pos & (sb->s_blocksize - 1);
thissize = sb->s_blocksize - offset;
if (thissize > 26)
thissize = 26;
/*
* Get the entry in total
*/
memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
if (thissize != 26)
memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
26 - thissize);
/*
* update it
*/
adfs_obj2dir(&de, obj);
/*
* Put the new entry back
*/
memcpy(dir->bh[buffer]->b_data + offset, &de, thissize);
if (thissize != 26)
memcpy(dir->bh[buffer + 1]->b_data, ((char *)&de) + thissize,
26 - thissize);
return 0;
}
/*
* the caller is responsible for holding the necessary
* locks.
*/
static int
adfs_dir_find_entry(struct adfs_dir *dir, unsigned long object_id)
{
int pos, ret;
ret = -ENOENT;
for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) {
struct object_info obj;
if (!__adfs_dir_get(dir, pos, &obj))
break;
if (obj.file_id == object_id) {
ret = pos;
break;
}
}
return ret;
}
static int
adfs_f_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
{
int ret;
if (sz != ADFS_NEWDIR_SIZE)
return -EIO;
ret = adfs_dir_read(sb, id, sz, dir);
if (ret)
adfs_error(sb, "unable to read directory");
else
dir->parent_id = adfs_readval(dir->dirtail.new.dirparent, 3);
return ret;
}
static int
adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)
{
if (fpos >= ADFS_NUM_DIR_ENTRIES)
return -ENOENT;
dir->pos = 5 + fpos * 26;
return 0;
}
static int
adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)
{
unsigned int ret;
ret = __adfs_dir_get(dir, dir->pos, obj);
if (ret == 0)
dir->pos += 26;
return ret;
}
static int
adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
{
struct super_block *sb = dir->sb;
int ret, i;
ret = adfs_dir_find_entry(dir, obj->file_id);
if (ret < 0) {
adfs_error(dir->sb, "unable to locate entry to update");
goto out;
}
__adfs_dir_put(dir, ret, obj);
/*
* Increment directory sequence number
*/
dir->bh[0]->b_data[0] += 1;
dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 6] += 1;
ret = adfs_dir_checkbyte(dir);
/*
* Update directory check byte
*/
dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret;
#if 1
{
const unsigned int blocksize_bits = sb->s_blocksize_bits;
memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
goto bad_dir;
if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
memcmp(&dir->dirhead.startname, "Hugo", 4))
goto bad_dir;
if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
goto bad_dir;
}
#endif
for (i = dir->nr_buffers - 1; i >= 0; i--)
mark_buffer_dirty(dir->bh[i]);
ret = 0;
out:
return ret;
#if 1
bad_dir:
adfs_error(dir->sb, "whoops! I broke a directory!");
return -EIO;
#endif
}
static void
adfs_f_free(struct adfs_dir *dir)
{
int i;
for (i = dir->nr_buffers - 1; i >= 0; i--) {
brelse(dir->bh[i]);
dir->bh[i] = NULL;
}
dir->nr_buffers = 0;
dir->sb = NULL;
}
struct adfs_dir_ops adfs_f_dir_ops = {
.read = adfs_f_read,
.setpos = adfs_f_setpos,
.getnext = adfs_f_getnext,
.update = adfs_f_update,
.free = adfs_f_free
};

65
fs/adfs/dir_f.h Normal file
View File

@@ -0,0 +1,65 @@
/*
* linux/fs/adfs/dir_f.h
*
* Copyright (C) 1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Structures of directories on the F format disk
*/
#ifndef ADFS_DIR_F_H
#define ADFS_DIR_F_H
/*
* Directory header
*/
struct adfs_dirheader {
unsigned char startmasseq;
unsigned char startname[4];
};
#define ADFS_NEWDIR_SIZE 2048
#define ADFS_NUM_DIR_ENTRIES 77
/*
* Directory entries
*/
struct adfs_direntry {
#define ADFS_F_NAME_LEN 10
char dirobname[ADFS_F_NAME_LEN];
__u8 dirload[4];
__u8 direxec[4];
__u8 dirlen[4];
__u8 dirinddiscadd[3];
__u8 newdiratts;
};
/*
* Directory tail
*/
union adfs_dirtail {
struct {
unsigned char dirlastmask;
char dirname[10];
unsigned char dirparent[3];
char dirtitle[19];
unsigned char reserved[14];
unsigned char endmasseq;
unsigned char endname[4];
unsigned char dircheckbyte;
} old;
struct {
unsigned char dirlastmask;
unsigned char reserved[2];
unsigned char dirparent[3];
char dirtitle[19];
char dirname[10];
unsigned char endmasseq;
unsigned char endname[4];
unsigned char dircheckbyte;
} new;
};
#endif

179
fs/adfs/dir_fplus.c Normal file
View File

@@ -0,0 +1,179 @@
/*
* linux/fs/adfs/dir_fplus.c
*
* Copyright (C) 1997-1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/adfs_fs.h>
#include <linux/time.h>
#include <linux/stat.h>
#include <linux/spinlock.h>
#include <linux/buffer_head.h>
#include <linux/string.h>
#include "adfs.h"
#include "dir_fplus.h"
static int
adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
{
struct adfs_bigdirheader *h;
struct adfs_bigdirtail *t;
unsigned long block;
unsigned int blk, size;
int i, ret = -EIO;
dir->nr_buffers = 0;
block = __adfs_block_map(sb, id, 0);
if (!block) {
adfs_error(sb, "dir object %X has a hole at offset 0", id);
goto out;
}
dir->bh[0] = sb_bread(sb, block);
if (!dir->bh[0])
goto out;
dir->nr_buffers += 1;
h = (struct adfs_bigdirheader *)dir->bh[0]->b_data;
size = le32_to_cpu(h->bigdirsize);
if (size != sz) {
printk(KERN_WARNING "adfs: adfs_fplus_read: directory header size\n"
" does not match directory size\n");
}
if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
h->bigdirversion[2] != 0 || size & 2047 ||
h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME))
goto out;
size >>= sb->s_blocksize_bits;
for (blk = 1; blk < size; blk++) {
block = __adfs_block_map(sb, id, blk);
if (!block) {
adfs_error(sb, "dir object %X has a hole at offset %d", id, blk);
goto out;
}
dir->bh[blk] = sb_bread(sb, block);
if (!dir->bh[blk])
goto out;
dir->nr_buffers = blk;
}
t = (struct adfs_bigdirtail *)(dir->bh[size - 1]->b_data + (sb->s_blocksize - 8));
if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) ||
t->bigdirendmasseq != h->startmasseq ||
t->reserved[0] != 0 || t->reserved[1] != 0)
goto out;
dir->parent_id = le32_to_cpu(h->bigdirparent);
dir->sb = sb;
return 0;
out:
for (i = 0; i < dir->nr_buffers; i++)
brelse(dir->bh[i]);
dir->sb = NULL;
return ret;
}
static int
adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos)
{
struct adfs_bigdirheader *h = (struct adfs_bigdirheader *)dir->bh[0]->b_data;
int ret = -ENOENT;
if (fpos <= le32_to_cpu(h->bigdirentries)) {
dir->pos = fpos;
ret = 0;
}
return ret;
}
static void
dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len)
{
struct super_block *sb = dir->sb;
unsigned int buffer, partial, remainder;
buffer = offset >> sb->s_blocksize_bits;
offset &= sb->s_blocksize - 1;
partial = sb->s_blocksize - offset;
if (partial >= len)
memcpy(to, dir->bh[buffer]->b_data + offset, len);
else {
char *c = (char *)to;
remainder = len - partial;
memcpy(c, dir->bh[buffer]->b_data + offset, partial);
memcpy(c + partial, dir->bh[buffer + 1]->b_data, remainder);
}
}
static int
adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj)
{
struct adfs_bigdirheader *h = (struct adfs_bigdirheader *)dir->bh[0]->b_data;
struct adfs_bigdirentry bde;
unsigned int offset;
int i, ret = -ENOENT;
if (dir->pos >= le32_to_cpu(h->bigdirentries))
goto out;
offset = offsetof(struct adfs_bigdirheader, bigdirname);
offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
offset += dir->pos * sizeof(struct adfs_bigdirentry);
dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry));
obj->loadaddr = le32_to_cpu(bde.bigdirload);
obj->execaddr = le32_to_cpu(bde.bigdirexec);
obj->size = le32_to_cpu(bde.bigdirlen);
obj->file_id = le32_to_cpu(bde.bigdirindaddr);
obj->attr = le32_to_cpu(bde.bigdirattr);
obj->name_len = le32_to_cpu(bde.bigdirobnamelen);
offset = offsetof(struct adfs_bigdirheader, bigdirname);
offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry);
offset += le32_to_cpu(bde.bigdirobnameptr);
dir_memcpy(dir, offset, obj->name, obj->name_len);
for (i = 0; i < obj->name_len; i++)
if (obj->name[i] == '/')
obj->name[i] = '.';
dir->pos += 1;
ret = 0;
out:
return ret;
}
static void
adfs_fplus_free(struct adfs_dir *dir)
{
int i;
for (i = 0; i < dir->nr_buffers; i++)
brelse(dir->bh[i]);
dir->sb = NULL;
}
struct adfs_dir_ops adfs_fplus_dir_ops = {
.read = adfs_fplus_read,
.setpos = adfs_fplus_setpos,
.getnext = adfs_fplus_getnext,
.free = adfs_fplus_free
};

45
fs/adfs/dir_fplus.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* linux/fs/adfs/dir_fplus.h
*
* Copyright (C) 1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Structures of directories on the F+ format disk
*/
#define ADFS_FPLUS_NAME_LEN 255
#define BIGDIRSTARTNAME ('S' | 'B' << 8 | 'P' << 16 | 'r' << 24)
#define BIGDIRENDNAME ('o' | 'v' << 8 | 'e' << 16 | 'n' << 24)
struct adfs_bigdirheader {
__u8 startmasseq;
__u8 bigdirversion[3];
__le32 bigdirstartname;
__le32 bigdirnamelen;
__le32 bigdirsize;
__le32 bigdirentries;
__le32 bigdirnamesize;
__le32 bigdirparent;
char bigdirname[1];
};
struct adfs_bigdirentry {
__le32 bigdirload;
__le32 bigdirexec;
__le32 bigdirlen;
__le32 bigdirindaddr;
__le32 bigdirattr;
__le32 bigdirobnamelen;
__le32 bigdirobnameptr;
};
struct adfs_bigdirtail {
__le32 bigdirendname;
__u8 bigdirendmasseq;
__u8 reserved[2];
__u8 bigdircheckbyte;
};

43
fs/adfs/file.c Normal file
View File

@@ -0,0 +1,43 @@
/*
* linux/fs/adfs/file.c
*
* Copyright (C) 1997-1999 Russell King
* from:
*
* linux/fs/ext2/file.c
*
* Copyright (C) 1992, 1993, 1994, 1995
* Remy Card (card@masi.ibp.fr)
* Laboratoire MASI - Institut Blaise Pascal
* Universite Pierre et Marie Curie (Paris VI)
*
* from
*
* linux/fs/minix/file.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* adfs regular file handling primitives
*/
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/time.h>
#include <linux/stat.h>
#include <linux/buffer_head.h> /* for file_fsync() */
#include <linux/adfs_fs.h>
#include "adfs.h"
struct file_operations adfs_file_operations = {
.llseek = generic_file_llseek,
.read = generic_file_read,
.mmap = generic_file_mmap,
.fsync = file_fsync,
.write = generic_file_write,
.sendfile = generic_file_sendfile,
};
struct inode_operations adfs_file_inode_operations = {
.setattr = adfs_notify_change,
};

395
fs/adfs/inode.c Normal file
View File

@@ -0,0 +1,395 @@
/*
* linux/fs/adfs/inode.c
*
* Copyright (C) 1997-1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/adfs_fs.h>
#include <linux/time.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/smp_lock.h>
#include <linux/module.h>
#include <linux/buffer_head.h>
#include "adfs.h"
/*
* Lookup/Create a block at offset 'block' into 'inode'. We currently do
* not support creation of new blocks, so we return -EIO for this case.
*/
static int
adfs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh,
int create)
{
if (block < 0)
goto abort_negative;
if (!create) {
if (block >= inode->i_blocks)
goto abort_toobig;
block = __adfs_block_map(inode->i_sb, inode->i_ino, block);
if (block)
map_bh(bh, inode->i_sb, block);
return 0;
}
/* don't support allocation of blocks yet */
return -EIO;
abort_negative:
adfs_error(inode->i_sb, "block %d < 0", block);
return -EIO;
abort_toobig:
return 0;
}
static int adfs_writepage(struct page *page, struct writeback_control *wbc)
{
return block_write_full_page(page, adfs_get_block, wbc);
}
static int adfs_readpage(struct file *file, struct page *page)
{
return block_read_full_page(page, adfs_get_block);
}
static int adfs_prepare_write(struct file *file, struct page *page, unsigned int from, unsigned int to)
{
return cont_prepare_write(page, from, to, adfs_get_block,
&ADFS_I(page->mapping->host)->mmu_private);
}
static sector_t _adfs_bmap(struct address_space *mapping, sector_t block)
{
return generic_block_bmap(mapping, block, adfs_get_block);
}
static struct address_space_operations adfs_aops = {
.readpage = adfs_readpage,
.writepage = adfs_writepage,
.sync_page = block_sync_page,
.prepare_write = adfs_prepare_write,
.commit_write = generic_commit_write,
.bmap = _adfs_bmap
};
static inline unsigned int
adfs_filetype(struct inode *inode)
{
unsigned int type;
if (ADFS_I(inode)->stamped)
type = (ADFS_I(inode)->loadaddr >> 8) & 0xfff;
else
type = (unsigned int) -1;
return type;
}
/*
* Convert ADFS attributes and filetype to Linux permission.
*/
static umode_t
adfs_atts2mode(struct super_block *sb, struct inode *inode)
{
unsigned int filetype, attr = ADFS_I(inode)->attr;
umode_t mode, rmask;
struct adfs_sb_info *asb = ADFS_SB(sb);
if (attr & ADFS_NDA_DIRECTORY) {
mode = S_IRUGO & asb->s_owner_mask;
return S_IFDIR | S_IXUGO | mode;
}
filetype = adfs_filetype(inode);
switch (filetype) {
case 0xfc0: /* LinkFS */
return S_IFLNK|S_IRWXUGO;
case 0xfe6: /* UnixExec */
rmask = S_IRUGO | S_IXUGO;
break;
default:
rmask = S_IRUGO;
}
mode = S_IFREG;
if (attr & ADFS_NDA_OWNER_READ)
mode |= rmask & asb->s_owner_mask;
if (attr & ADFS_NDA_OWNER_WRITE)
mode |= S_IWUGO & asb->s_owner_mask;
if (attr & ADFS_NDA_PUBLIC_READ)
mode |= rmask & asb->s_other_mask;
if (attr & ADFS_NDA_PUBLIC_WRITE)
mode |= S_IWUGO & asb->s_other_mask;
return mode;
}
/*
* Convert Linux permission to ADFS attribute. We try to do the reverse
* of atts2mode, but there is not a 1:1 translation.
*/
static int
adfs_mode2atts(struct super_block *sb, struct inode *inode)
{
umode_t mode;
int attr;
struct adfs_sb_info *asb = ADFS_SB(sb);
/* FIXME: should we be able to alter a link? */
if (S_ISLNK(inode->i_mode))
return ADFS_I(inode)->attr;
if (S_ISDIR(inode->i_mode))
attr = ADFS_NDA_DIRECTORY;
else
attr = 0;
mode = inode->i_mode & asb->s_owner_mask;
if (mode & S_IRUGO)
attr |= ADFS_NDA_OWNER_READ;
if (mode & S_IWUGO)
attr |= ADFS_NDA_OWNER_WRITE;
mode = inode->i_mode & asb->s_other_mask;
mode &= ~asb->s_owner_mask;
if (mode & S_IRUGO)
attr |= ADFS_NDA_PUBLIC_READ;
if (mode & S_IWUGO)
attr |= ADFS_NDA_PUBLIC_WRITE;
return attr;
}
/*
* Convert an ADFS time to Unix time. ADFS has a 40-bit centi-second time
* referenced to 1 Jan 1900 (til 2248)
*/
static void
adfs_adfs2unix_time(struct timespec *tv, struct inode *inode)
{
unsigned int high, low;
if (ADFS_I(inode)->stamped == 0)
goto cur_time;
high = ADFS_I(inode)->loadaddr << 24;
low = ADFS_I(inode)->execaddr;
high |= low >> 8;
low &= 255;
/* Files dated pre 01 Jan 1970 00:00:00. */
if (high < 0x336e996a)
goto too_early;
/* Files dated post 18 Jan 2038 03:14:05. */
if (high >= 0x656e9969)
goto too_late;
/* discard 2208988800 (0x336e996a00) seconds of time */
high -= 0x336e996a;
/* convert 40-bit centi-seconds to 32-bit seconds */
tv->tv_sec = (((high % 100) << 8) + low) / 100 + (high / 100 << 8);
tv->tv_nsec = 0;
return;
cur_time:
*tv = CURRENT_TIME_SEC;
return;
too_early:
tv->tv_sec = tv->tv_nsec = 0;
return;
too_late:
tv->tv_sec = 0x7ffffffd;
tv->tv_nsec = 0;
return;
}
/*
* Convert an Unix time to ADFS time. We only do this if the entry has a
* time/date stamp already.
*/
static void
adfs_unix2adfs_time(struct inode *inode, unsigned int secs)
{
unsigned int high, low;
if (ADFS_I(inode)->stamped) {
/* convert 32-bit seconds to 40-bit centi-seconds */
low = (secs & 255) * 100;
high = (secs / 256) * 100 + (low >> 8) + 0x336e996a;
ADFS_I(inode)->loadaddr = (high >> 24) |
(ADFS_I(inode)->loadaddr & ~0xff);
ADFS_I(inode)->execaddr = (low & 255) | (high << 8);
}
}
/*
* Fill in the inode information from the object information.
*
* Note that this is an inode-less filesystem, so we can't use the inode
* number to reference the metadata on the media. Instead, we use the
* inode number to hold the object ID, which in turn will tell us where
* the data is held. We also save the parent object ID, and with these
* two, we can locate the metadata.
*
* This does mean that we rely on an objects parent remaining the same at
* all times - we cannot cope with a cross-directory rename (yet).
*/
struct inode *
adfs_iget(struct super_block *sb, struct object_info *obj)
{
struct inode *inode;
inode = new_inode(sb);
if (!inode)
goto out;
inode->i_uid = ADFS_SB(sb)->s_uid;
inode->i_gid = ADFS_SB(sb)->s_gid;
inode->i_ino = obj->file_id;
inode->i_size = obj->size;
inode->i_nlink = 2;
inode->i_blksize = PAGE_SIZE;
inode->i_blocks = (inode->i_size + sb->s_blocksize - 1) >>
sb->s_blocksize_bits;
/*
* we need to save the parent directory ID so that
* write_inode can update the directory information
* for this file. This will need special handling
* for cross-directory renames.
*/
ADFS_I(inode)->parent_id = obj->parent_id;
ADFS_I(inode)->loadaddr = obj->loadaddr;
ADFS_I(inode)->execaddr = obj->execaddr;
ADFS_I(inode)->attr = obj->attr;
ADFS_I(inode)->stamped = ((obj->loadaddr & 0xfff00000) == 0xfff00000);
inode->i_mode = adfs_atts2mode(sb, inode);
adfs_adfs2unix_time(&inode->i_mtime, inode);
inode->i_atime = inode->i_mtime;
inode->i_ctime = inode->i_mtime;
if (S_ISDIR(inode->i_mode)) {
inode->i_op = &adfs_dir_inode_operations;
inode->i_fop = &adfs_dir_operations;
} else if (S_ISREG(inode->i_mode)) {
inode->i_op = &adfs_file_inode_operations;
inode->i_fop = &adfs_file_operations;
inode->i_mapping->a_ops = &adfs_aops;
ADFS_I(inode)->mmu_private = inode->i_size;
}
insert_inode_hash(inode);
out:
return inode;
}
/*
* Validate and convert a changed access mode/time to their ADFS equivalents.
* adfs_write_inode will actually write the information back to the directory
* later.
*/
int
adfs_notify_change(struct dentry *dentry, struct iattr *attr)
{
struct inode *inode = dentry->d_inode;
struct super_block *sb = inode->i_sb;
unsigned int ia_valid = attr->ia_valid;
int error;
lock_kernel();
error = inode_change_ok(inode, attr);
/*
* we can't change the UID or GID of any file -
* we have a global UID/GID in the superblock
*/
if ((ia_valid & ATTR_UID && attr->ia_uid != ADFS_SB(sb)->s_uid) ||
(ia_valid & ATTR_GID && attr->ia_gid != ADFS_SB(sb)->s_gid))
error = -EPERM;
if (error)
goto out;
if (ia_valid & ATTR_SIZE)
error = vmtruncate(inode, attr->ia_size);
if (error)
goto out;
if (ia_valid & ATTR_MTIME) {
inode->i_mtime = attr->ia_mtime;
adfs_unix2adfs_time(inode, attr->ia_mtime.tv_sec);
}
/*
* FIXME: should we make these == to i_mtime since we don't
* have the ability to represent them in our filesystem?
*/
if (ia_valid & ATTR_ATIME)
inode->i_atime = attr->ia_atime;
if (ia_valid & ATTR_CTIME)
inode->i_ctime = attr->ia_ctime;
if (ia_valid & ATTR_MODE) {
ADFS_I(inode)->attr = adfs_mode2atts(sb, inode);
inode->i_mode = adfs_atts2mode(sb, inode);
}
/*
* FIXME: should we be marking this inode dirty even if
* we don't have any metadata to write back?
*/
if (ia_valid & (ATTR_SIZE | ATTR_MTIME | ATTR_MODE))
mark_inode_dirty(inode);
out:
unlock_kernel();
return error;
}
/*
* write an existing inode back to the directory, and therefore the disk.
* The adfs-specific inode data has already been updated by
* adfs_notify_change()
*/
int adfs_write_inode(struct inode *inode, int unused)
{
struct super_block *sb = inode->i_sb;
struct object_info obj;
int ret;
lock_kernel();
obj.file_id = inode->i_ino;
obj.name_len = 0;
obj.parent_id = ADFS_I(inode)->parent_id;
obj.loadaddr = ADFS_I(inode)->loadaddr;
obj.execaddr = ADFS_I(inode)->execaddr;
obj.attr = ADFS_I(inode)->attr;
obj.size = inode->i_size;
ret = adfs_dir_update(sb, &obj);
unlock_kernel();
return ret;
}
MODULE_LICENSE("GPL");

296
fs/adfs/map.c Normal file
View File

@@ -0,0 +1,296 @@
/*
* linux/fs/adfs/map.c
*
* Copyright (C) 1997-2002 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/adfs_fs.h>
#include <linux/spinlock.h>
#include <linux/buffer_head.h>
#include <asm/unaligned.h>
#include "adfs.h"
/*
* The ADFS map is basically a set of sectors. Each sector is called a
* zone which contains a bitstream made up of variable sized fragments.
* Each bit refers to a set of bytes in the filesystem, defined by
* log2bpmb. This may be larger or smaller than the sector size, but
* the overall size it describes will always be a round number of
* sectors. A fragment id is always idlen bits long.
*
* < idlen > < n > <1>
* +---------+-------//---------+---+
* | frag id | 0000....000000 | 1 |
* +---------+-------//---------+---+
*
* The physical disk space used by a fragment is taken from the start of
* the fragment id up to and including the '1' bit - ie, idlen + n + 1
* bits.
*
* A fragment id can be repeated multiple times in the whole map for
* large or fragmented files. The first map zone a fragment starts in
* is given by fragment id / ids_per_zone - this allows objects to start
* from any zone on the disk.
*
* Free space is described by a linked list of fragments. Each free
* fragment describes free space in the same way as the other fragments,
* however, the frag id specifies an offset (in map bits) from the end
* of this fragment to the start of the next free fragment.
*
* Objects stored on the disk are allocated object ids (we use these as
* our inode numbers.) Object ids contain a fragment id and an optional
* offset. This allows a directory fragment to contain small files
* associated with that directory.
*/
/*
* For the future...
*/
static DEFINE_RWLOCK(adfs_map_lock);
/*
* This is fun. We need to load up to 19 bits from the map at an
* arbitary bit alignment. (We're limited to 19 bits by F+ version 2).
*/
#define GET_FRAG_ID(_map,_start,_idmask) \
({ \
unsigned char *_m = _map + (_start >> 3); \
u32 _frag = get_unaligned((u32 *)_m); \
_frag >>= (_start & 7); \
_frag & _idmask; \
})
/*
* return the map bit offset of the fragment frag_id in the zone dm.
* Note that the loop is optimised for best asm code - look at the
* output of:
* gcc -D__KERNEL__ -O2 -I../../include -o - -S map.c
*/
static int
lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,
const unsigned int frag_id, unsigned int *offset)
{
const unsigned int mapsize = dm->dm_endbit;
const u32 idmask = (1 << idlen) - 1;
unsigned char *map = dm->dm_bh->b_data + 4;
unsigned int start = dm->dm_startbit;
unsigned int mapptr;
u32 frag;
do {
frag = GET_FRAG_ID(map, start, idmask);
mapptr = start + idlen;
/*
* find end of fragment
*/
{
__le32 *_map = (__le32 *)map;
u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31);
while (v == 0) {
mapptr = (mapptr & ~31) + 32;
if (mapptr >= mapsize)
goto error;
v = le32_to_cpu(_map[mapptr >> 5]);
}
mapptr += 1 + ffz(~v);
}
if (frag == frag_id)
goto found;
again:
start = mapptr;
} while (mapptr < mapsize);
return -1;
error:
printk(KERN_ERR "adfs: oversized fragment 0x%x at 0x%x-0x%x\n",
frag, start, mapptr);
return -1;
found:
{
int length = mapptr - start;
if (*offset >= length) {
*offset -= length;
goto again;
}
}
return start + *offset;
}
/*
* Scan the free space map, for this zone, calculating the total
* number of map bits in each free space fragment.
*
* Note: idmask is limited to 15 bits [3.2]
*/
static unsigned int
scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm)
{
const unsigned int mapsize = dm->dm_endbit + 32;
const unsigned int idlen = asb->s_idlen;
const unsigned int frag_idlen = idlen <= 15 ? idlen : 15;
const u32 idmask = (1 << frag_idlen) - 1;
unsigned char *map = dm->dm_bh->b_data;
unsigned int start = 8, mapptr;
u32 frag;
unsigned long total = 0;
/*
* get fragment id
*/
frag = GET_FRAG_ID(map, start, idmask);
/*
* If the freelink is null, then no free fragments
* exist in this zone.
*/
if (frag == 0)
return 0;
do {
start += frag;
/*
* get fragment id
*/
frag = GET_FRAG_ID(map, start, idmask);
mapptr = start + idlen;
/*
* find end of fragment
*/
{
__le32 *_map = (__le32 *)map;
u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31);
while (v == 0) {
mapptr = (mapptr & ~31) + 32;
if (mapptr >= mapsize)
goto error;
v = le32_to_cpu(_map[mapptr >> 5]);
}
mapptr += 1 + ffz(~v);
}
total += mapptr - start;
} while (frag >= idlen + 1);
if (frag != 0)
printk(KERN_ERR "adfs: undersized free fragment\n");
return total;
error:
printk(KERN_ERR "adfs: oversized free fragment\n");
return 0;
}
static int
scan_map(struct adfs_sb_info *asb, unsigned int zone,
const unsigned int frag_id, unsigned int mapoff)
{
const unsigned int idlen = asb->s_idlen;
struct adfs_discmap *dm, *dm_end;
int result;
dm = asb->s_map + zone;
zone = asb->s_map_size;
dm_end = asb->s_map + zone;
do {
result = lookup_zone(dm, idlen, frag_id, &mapoff);
if (result != -1)
goto found;
dm ++;
if (dm == dm_end)
dm = asb->s_map;
} while (--zone > 0);
return -1;
found:
result -= dm->dm_startbit;
result += dm->dm_startblk;
return result;
}
/*
* calculate the amount of free blocks in the map.
*
* n=1
* total_free = E(free_in_zone_n)
* nzones
*/
unsigned int
adfs_map_free(struct super_block *sb)
{
struct adfs_sb_info *asb = ADFS_SB(sb);
struct adfs_discmap *dm;
unsigned int total = 0;
unsigned int zone;
dm = asb->s_map;
zone = asb->s_map_size;
do {
total += scan_free_map(asb, dm++);
} while (--zone > 0);
return signed_asl(total, asb->s_map2blk);
}
int
adfs_map_lookup(struct super_block *sb, unsigned int frag_id,
unsigned int offset)
{
struct adfs_sb_info *asb = ADFS_SB(sb);
unsigned int zone, mapoff;
int result;
/*
* map & root fragment is special - it starts in the center of the
* disk. The other fragments start at zone (frag / ids_per_zone)
*/
if (frag_id == ADFS_ROOT_FRAG)
zone = asb->s_map_size >> 1;
else
zone = frag_id / asb->s_ids_per_zone;
if (zone >= asb->s_map_size)
goto bad_fragment;
/* Convert sector offset to map offset */
mapoff = signed_asl(offset, -asb->s_map2blk);
read_lock(&adfs_map_lock);
result = scan_map(asb, zone, frag_id, mapoff);
read_unlock(&adfs_map_lock);
if (result > 0) {
unsigned int secoff;
/* Calculate sector offset into map block */
secoff = offset - signed_asl(mapoff, asb->s_map2blk);
return secoff + signed_asl(result, asb->s_map2blk);
}
adfs_error(sb, "fragment 0x%04x at offset %d not found in map",
frag_id, offset);
return 0;
bad_fragment:
adfs_error(sb, "invalid fragment 0x%04x (zone = %d, max = %d)",
frag_id, zone, asb->s_map_size);
return 0;
}

508
fs/adfs/super.c Normal file
View File

@@ -0,0 +1,508 @@
/*
* linux/fs/adfs/super.c
*
* Copyright (C) 1997-1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/adfs_fs.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/buffer_head.h>
#include <linux/vfs.h>
#include <linux/parser.h>
#include <linux/bitops.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <stdarg.h>
#include "adfs.h"
#include "dir_f.h"
#include "dir_fplus.h"
void __adfs_error(struct super_block *sb, const char *function, const char *fmt, ...)
{
char error_buf[128];
va_list args;
va_start(args, fmt);
vsprintf(error_buf, fmt, args);
va_end(args);
printk(KERN_CRIT "ADFS-fs error (device %s)%s%s: %s\n",
sb->s_id, function ? ": " : "",
function ? function : "", error_buf);
}
static int adfs_checkdiscrecord(struct adfs_discrecord *dr)
{
int i;
/* sector size must be 256, 512 or 1024 bytes */
if (dr->log2secsize != 8 &&
dr->log2secsize != 9 &&
dr->log2secsize != 10)
return 1;
/* idlen must be at least log2secsize + 3 */
if (dr->idlen < dr->log2secsize + 3)
return 1;
/* we cannot have such a large disc that we
* are unable to represent sector offsets in
* 32 bits. This works out at 2.0 TB.
*/
if (le32_to_cpu(dr->disc_size_high) >> dr->log2secsize)
return 1;
/* idlen must be no greater than 19 v2 [1.0] */
if (dr->idlen > 19)
return 1;
/* reserved bytes should be zero */
for (i = 0; i < sizeof(dr->unused52); i++)
if (dr->unused52[i] != 0)
return 1;
return 0;
}
static unsigned char adfs_calczonecheck(struct super_block *sb, unsigned char *map)
{
unsigned int v0, v1, v2, v3;
int i;
v0 = v1 = v2 = v3 = 0;
for (i = sb->s_blocksize - 4; i; i -= 4) {
v0 += map[i] + (v3 >> 8);
v3 &= 0xff;
v1 += map[i + 1] + (v0 >> 8);
v0 &= 0xff;
v2 += map[i + 2] + (v1 >> 8);
v1 &= 0xff;
v3 += map[i + 3] + (v2 >> 8);
v2 &= 0xff;
}
v0 += v3 >> 8;
v1 += map[1] + (v0 >> 8);
v2 += map[2] + (v1 >> 8);
v3 += map[3] + (v2 >> 8);
return v0 ^ v1 ^ v2 ^ v3;
}
static int adfs_checkmap(struct super_block *sb, struct adfs_discmap *dm)
{
unsigned char crosscheck = 0, zonecheck = 1;
int i;
for (i = 0; i < ADFS_SB(sb)->s_map_size; i++) {
unsigned char *map;
map = dm[i].dm_bh->b_data;
if (adfs_calczonecheck(sb, map) != map[0]) {
adfs_error(sb, "zone %d fails zonecheck", i);
zonecheck = 0;
}
crosscheck ^= map[3];
}
if (crosscheck != 0xff)
adfs_error(sb, "crosscheck != 0xff");
return crosscheck == 0xff && zonecheck;
}
static void adfs_put_super(struct super_block *sb)
{
int i;
struct adfs_sb_info *asb = ADFS_SB(sb);
for (i = 0; i < asb->s_map_size; i++)
brelse(asb->s_map[i].dm_bh);
kfree(asb->s_map);
kfree(asb);
sb->s_fs_info = NULL;
}
enum {Opt_uid, Opt_gid, Opt_ownmask, Opt_othmask, Opt_err};
static match_table_t tokens = {
{Opt_uid, "uid=%u"},
{Opt_gid, "gid=%u"},
{Opt_ownmask, "ownmask=%o"},
{Opt_othmask, "othmask=%o"},
{Opt_err, NULL}
};
static int parse_options(struct super_block *sb, char *options)
{
char *p;
struct adfs_sb_info *asb = ADFS_SB(sb);
int option;
if (!options)
return 0;
while ((p = strsep(&options, ",")) != NULL) {
substring_t args[MAX_OPT_ARGS];
int token;
if (!*p)
continue;
token = match_token(p, tokens, args);
switch (token) {
case Opt_uid:
if (match_int(args, &option))
return -EINVAL;
asb->s_uid = option;
break;
case Opt_gid:
if (match_int(args, &option))
return -EINVAL;
asb->s_gid = option;
break;
case Opt_ownmask:
if (match_octal(args, &option))
return -EINVAL;
asb->s_owner_mask = option;
break;
case Opt_othmask:
if (match_octal(args, &option))
return -EINVAL;
asb->s_other_mask = option;
break;
default:
printk("ADFS-fs: unrecognised mount option \"%s\" "
"or missing value\n", p);
return -EINVAL;
}
}
return 0;
}
static int adfs_remount(struct super_block *sb, int *flags, char *data)
{
*flags |= MS_NODIRATIME;
return parse_options(sb, data);
}
static int adfs_statfs(struct super_block *sb, struct kstatfs *buf)
{
struct adfs_sb_info *asb = ADFS_SB(sb);
buf->f_type = ADFS_SUPER_MAGIC;
buf->f_namelen = asb->s_namelen;
buf->f_bsize = sb->s_blocksize;
buf->f_blocks = asb->s_size;
buf->f_files = asb->s_ids_per_zone * asb->s_map_size;
buf->f_bavail =
buf->f_bfree = adfs_map_free(sb);
buf->f_ffree = (long)(buf->f_bfree * buf->f_files) / (long)buf->f_blocks;
return 0;
}
static kmem_cache_t *adfs_inode_cachep;
static struct inode *adfs_alloc_inode(struct super_block *sb)
{
struct adfs_inode_info *ei;
ei = (struct adfs_inode_info *)kmem_cache_alloc(adfs_inode_cachep, SLAB_KERNEL);
if (!ei)
return NULL;
return &ei->vfs_inode;
}
static void adfs_destroy_inode(struct inode *inode)
{
kmem_cache_free(adfs_inode_cachep, ADFS_I(inode));
}
static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
{
struct adfs_inode_info *ei = (struct adfs_inode_info *) foo;
if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
SLAB_CTOR_CONSTRUCTOR)
inode_init_once(&ei->vfs_inode);
}
static int init_inodecache(void)
{
adfs_inode_cachep = kmem_cache_create("adfs_inode_cache",
sizeof(struct adfs_inode_info),
0, SLAB_RECLAIM_ACCOUNT,
init_once, NULL);
if (adfs_inode_cachep == NULL)
return -ENOMEM;
return 0;
}
static void destroy_inodecache(void)
{
if (kmem_cache_destroy(adfs_inode_cachep))
printk(KERN_INFO "adfs_inode_cache: not all structures were freed\n");
}
static struct super_operations adfs_sops = {
.alloc_inode = adfs_alloc_inode,
.destroy_inode = adfs_destroy_inode,
.write_inode = adfs_write_inode,
.put_super = adfs_put_super,
.statfs = adfs_statfs,
.remount_fs = adfs_remount,
};
static struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr)
{
struct adfs_discmap *dm;
unsigned int map_addr, zone_size, nzones;
int i, zone;
struct adfs_sb_info *asb = ADFS_SB(sb);
nzones = asb->s_map_size;
zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare);
map_addr = (nzones >> 1) * zone_size -
((nzones > 1) ? ADFS_DR_SIZE_BITS : 0);
map_addr = signed_asl(map_addr, asb->s_map2blk);
asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1);
dm = kmalloc(nzones * sizeof(*dm), GFP_KERNEL);
if (dm == NULL) {
adfs_error(sb, "not enough memory");
return NULL;
}
for (zone = 0; zone < nzones; zone++, map_addr++) {
dm[zone].dm_startbit = 0;
dm[zone].dm_endbit = zone_size;
dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS;
dm[zone].dm_bh = sb_bread(sb, map_addr);
if (!dm[zone].dm_bh) {
adfs_error(sb, "unable to read map");
goto error_free;
}
}
/* adjust the limits for the first and last map zones */
i = zone - 1;
dm[0].dm_startblk = 0;
dm[0].dm_startbit = ADFS_DR_SIZE_BITS;
dm[i].dm_endbit = (le32_to_cpu(dr->disc_size_high) << (32 - dr->log2bpmb)) +
(le32_to_cpu(dr->disc_size) >> dr->log2bpmb) +
(ADFS_DR_SIZE_BITS - i * zone_size);
if (adfs_checkmap(sb, dm))
return dm;
adfs_error(sb, NULL, "map corrupted");
error_free:
while (--zone >= 0)
brelse(dm[zone].dm_bh);
kfree(dm);
return NULL;
}
static inline unsigned long adfs_discsize(struct adfs_discrecord *dr, int block_bits)
{
unsigned long discsize;
discsize = le32_to_cpu(dr->disc_size_high) << (32 - block_bits);
discsize |= le32_to_cpu(dr->disc_size) >> block_bits;
return discsize;
}
static int adfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct adfs_discrecord *dr;
struct buffer_head *bh;
struct object_info root_obj;
unsigned char *b_data;
struct adfs_sb_info *asb;
struct inode *root;
sb->s_flags |= MS_NODIRATIME;
asb = kmalloc(sizeof(*asb), GFP_KERNEL);
if (!asb)
return -ENOMEM;
sb->s_fs_info = asb;
memset(asb, 0, sizeof(*asb));
/* set default options */
asb->s_uid = 0;
asb->s_gid = 0;
asb->s_owner_mask = S_IRWXU;
asb->s_other_mask = S_IRWXG | S_IRWXO;
if (parse_options(sb, data))
goto error;
sb_set_blocksize(sb, BLOCK_SIZE);
if (!(bh = sb_bread(sb, ADFS_DISCRECORD / BLOCK_SIZE))) {
adfs_error(sb, "unable to read superblock");
goto error;
}
b_data = bh->b_data + (ADFS_DISCRECORD % BLOCK_SIZE);
if (adfs_checkbblk(b_data)) {
if (!silent)
printk("VFS: Can't find an adfs filesystem on dev "
"%s.\n", sb->s_id);
goto error_free_bh;
}
dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
/*
* Do some sanity checks on the ADFS disc record
*/
if (adfs_checkdiscrecord(dr)) {
if (!silent)
printk("VPS: Can't find an adfs filesystem on dev "
"%s.\n", sb->s_id);
goto error_free_bh;
}
brelse(bh);
if (sb_set_blocksize(sb, 1 << dr->log2secsize)) {
bh = sb_bread(sb, ADFS_DISCRECORD / sb->s_blocksize);
if (!bh) {
adfs_error(sb, "couldn't read superblock on "
"2nd try.");
goto error;
}
b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize);
if (adfs_checkbblk(b_data)) {
adfs_error(sb, "disc record mismatch, very weird!");
goto error_free_bh;
}
dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
} else {
if (!silent)
printk(KERN_ERR "VFS: Unsupported blocksize on dev "
"%s.\n", sb->s_id);
goto error;
}
/*
* blocksize on this device should now be set to the ADFS log2secsize
*/
sb->s_magic = ADFS_SUPER_MAGIC;
asb->s_idlen = dr->idlen;
asb->s_map_size = dr->nzones | (dr->nzones_high << 8);
asb->s_map2blk = dr->log2bpmb - dr->log2secsize;
asb->s_size = adfs_discsize(dr, sb->s_blocksize_bits);
asb->s_version = dr->format_version;
asb->s_log2sharesize = dr->log2sharesize;
asb->s_map = adfs_read_map(sb, dr);
if (!asb->s_map)
goto error_free_bh;
brelse(bh);
/*
* set up enough so that we can read an inode
*/
sb->s_op = &adfs_sops;
dr = (struct adfs_discrecord *)(asb->s_map[0].dm_bh->b_data + 4);
root_obj.parent_id = root_obj.file_id = le32_to_cpu(dr->root);
root_obj.name_len = 0;
root_obj.loadaddr = 0;
root_obj.execaddr = 0;
root_obj.size = ADFS_NEWDIR_SIZE;
root_obj.attr = ADFS_NDA_DIRECTORY | ADFS_NDA_OWNER_READ |
ADFS_NDA_OWNER_WRITE | ADFS_NDA_PUBLIC_READ;
/*
* If this is a F+ disk with variable length directories,
* get the root_size from the disc record.
*/
if (asb->s_version) {
root_obj.size = le32_to_cpu(dr->root_size);
asb->s_dir = &adfs_fplus_dir_ops;
asb->s_namelen = ADFS_FPLUS_NAME_LEN;
} else {
asb->s_dir = &adfs_f_dir_ops;
asb->s_namelen = ADFS_F_NAME_LEN;
}
root = adfs_iget(sb, &root_obj);
sb->s_root = d_alloc_root(root);
if (!sb->s_root) {
int i;
iput(root);
for (i = 0; i < asb->s_map_size; i++)
brelse(asb->s_map[i].dm_bh);
kfree(asb->s_map);
adfs_error(sb, "get root inode failed\n");
goto error;
} else
sb->s_root->d_op = &adfs_dentry_operations;
return 0;
error_free_bh:
brelse(bh);
error:
sb->s_fs_info = NULL;
kfree(asb);
return -EINVAL;
}
static struct super_block *adfs_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return get_sb_bdev(fs_type, flags, dev_name, data, adfs_fill_super);
}
static struct file_system_type adfs_fs_type = {
.owner = THIS_MODULE,
.name = "adfs",
.get_sb = adfs_get_sb,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
static int __init init_adfs_fs(void)
{
int err = init_inodecache();
if (err)
goto out1;
err = register_filesystem(&adfs_fs_type);
if (err)
goto out;
return 0;
out:
destroy_inodecache();
out1:
return err;
}
static void __exit exit_adfs_fs(void)
{
unregister_filesystem(&adfs_fs_type);
destroy_inodecache();
}
module_init(init_adfs_fs)
module_exit(exit_adfs_fs)

343
fs/affs/Changes Normal file
View File

@@ -0,0 +1,343 @@
(Note: I consider version numbers as cheap. That means
that I do not like numbers like 0.1 and the like for
things that can be used since quite some time. But
then, 3.1 doesn't mean 'perfectly stable', too.)
Known bugs:
-----------
- Doesn't work on the alpha. The only 64/32-bit
problem that I'm aware of (pointer/int conversion
in readdir()) gives compiler warnings but is
apparently not causing the failure, as directory
reads basically work (but all files are of size 0).
Alas, I've got no alpha to debug. :-(
- The partition checker (drivers/block/genhd.c)
doesn't work with devices which have 256 byte
blocks (some very old SCSI drives).
- The feature to automatically make the fs clean
might leave a trashed file system with the
bitmap flag set valid.
- When a file is truncated to a size that is not
a multiple of the blocksize, the rest of the
last allocated block is not cleared. Well,
this fs never claimed to be Posix conformant.
Please direct bug reports to: zippel@linux-m68k.org
Version 3.20
------------
- kill kernel lock
- fix for a possible bitmap corruption
Version 3.19
------------
- sizeof changes from Kernel Janitor Project
- several bug fixes found with fsx
Version 3.18
------------
- change to global min macro + warning fixes
- add module tags
Version 3.17
------------
- locking fixes
- wrong sign in __affs_hash_dentry
- remove unnecessary check in affs_new_inode
- enable international mode for dircache fs
Version 3.16
------------
- use mark_buffer_dirty_inode instead of mark_buffer_dirty.
- introduce affs_lock_{link|dir|ext}.
Version 3.15
------------
- disable link to directories until we can properly support them.
- locking fixes for link creation/removal.
Version 3.14
------------
- correctly cut off long file names for compares
- correctly initialize s_last_bmap
Version 3.13
------------
Major cleanup for 2.4 [Roman Zippel]
- new extended block handling
- new bitmap allocation functions
- locking should be safe for the future
- cleanup of some interfaces
Version 3.12
------------
more 2.4 fixes: [Roman Zippel]
- s_lock changes
- increased getblock mess
- clear meta blocks
Version 3.11
------------
- Converted to use 2.3.x page cache [Dave Jones <dave@powertweak.com>]
- Corruption in truncate() bugfix [Ken Tyler <kent@werple.net.au>]
Version 3.10
------------
- Changed partition checker to allow devices
with physical blocks != 512 bytes.
- The partition checker now also ignores the
word at 0xd0 that Windows likes to write to.
Version 3.9
-----------
- Moved cleanup from release_file() to put_inode().
This makes the first one obsolete.
- truncate() zeroes the unused remainder of a
partially used last block when a file is truncated.
It also marks the inode dirty now (which is not
really necessary as notify_change() will do
it anyway).
- Added a few comments, fixed some typos (and
introduced some new ones), made the debug messages
more consistent. Changed a bad example in the
doc file (affs.txt).
- Sets the NOEXEC flag in read_super() for old file
systems, since you can't run programs on them.
Version 3.8
-----------
Bill Hawes kindly reviewed the affs and sent me the
patches he did. They're marked (BH). Thanks, Bill!
- Cleanup of error handling in read_super().
Didn't release all resources in case of an
error. (BH)
- put_inode() releases the ext cache only if it's
no longer needed. (BH)
- One set of dentry callbacks is enough. (BH)
- Cleanup of error handling in namei.c. (BH)
- Cleanup of error handling in file.c. (BH)
- The original blocksize of the device is
restored when the fs is unmounted. (BH)
- getblock() did not invalidate the key cache
when it allocated a new block.
- Removed some unnecessary locks as Bill
suggested.
- Simplified match_name(), changed all hashing
and case insensitive name comparisons to use
uppercase. This makes the tolower() routines
obsolete.
- Added mount option 'mufs' to force muFS
uid/gid interpretation.
- File mode changes were not updated on disk.
This was fixed before, but somehow got lost.
Version 3.7
-----------
- Added dentry callbacks to allow the dcache to
operate case insensitive and length ignorant
like the affs itself.
- getblock() didn't update the lastblock field in the
inode if the fs was not an OFS. This bug only shows
up if a file was enlarged via truncate() and there
was not enough space.
- Remove some more superfluous code left over from
the old link days ...
- Fixed some oversights which were in patch 2.1.78.
- Fixed a few typos.
Version 3.6
-----------
- dentry changes. (Thanks to Jes Sorensen for his help.)
- Fixed bug in balloc(): Superblock was not set dirty after
the bitmap was changed, so the bitmap wasn't sync'd.
- Fixed nasty bug in find_new_zone(): If the current
zone number was zero, the loop didn't terminate,
causing a solid lock-up.
- Removed support for old-style directory reads.
- Fixed bug in add_entry(): When doing a sorted insert,
the pointer to the next entry in the hash chain wasn't
correctly byte-swapped. Since most of the users of the
affs use it on a 68k, they didn't notice. But why did
I not find this during my tests?
- Fixed some oversights (version wasn't updated on some
directory changes).
- Handling of hard links rewritten. To the VFS
they appear now as normal Unix links. They are
now resolved only once in lookup(). The backside
is that unlink(), rename() and rmdir() have to
be smart about them, but the result is worth the
effort. This also led to some code cleanup.
- Changed name type to unsigned char; the test for
invalid filenames didn't work correctly.
(Thanks to Michael Krause for pointing at this.)
- Changed mapping of executable flag.
- Changed all network byte-order macros to the
recommended ones.
- Added a remount function, so attempts to remount
a dircache filesystem or one with errors read/write
can be trapped. Previously, ro remounts didn't
flush the super block, and rw remounts didn't
create allocation zones ...
- Call shrink_dcache_parent() in rmdir().
(Thanks to Bill Hawes.)
- Permission checks in unlink().
- Allow mounting of volumes with superfluous
bitmap pointers read only, also allows them
to be remounted read/write.
- Owner/Group defaults now to the fs user (i.e.
the one that mounted it) instead of root. This
obsoletes the mount options uid and gid.
- Argument to volume option could overflow the
name buffer. It is now silently truncated to
30 characters. (Damn it! This kind of bug
is too embarrassing.)
- Split inode.c into 2 files, the superblock
routines desperately wanted their own file.
- truncate() didn't allocate an extension block
cache. If a file was extended by means of
truncate(), this led to an Oops.
- fsuser is now checked last.
- rename() will not ignore changes in filename
casing any more (though mv(1) still won't allow
you to do "mv oldname OldName").
Version 3.5
-----------
- Extension block caches are now allocated on
demand instead of when a file is opened, as
files can be read and written without opening
them (e. g. the loopback device does this).
- Removed an unused function.
Version 3.4
-----------
- Hash chains are now sorted by block numbers.
(Thanks to Kars de Jong for finding this.)
- Removed all unnecessary external symbols.
Version 3.3
-----------
- Tried to make all types 'correct' and consistent.
- Errors and warnings are now reported via a
function. They are all prefixed by a severity
and have the same appearance:
"AFFS: <function>: <error message>"
(There's one exception to this, as in that function
is no pointer to the super block available.)
- The filesystem is remounted read-only after an
error.
- The names of newly created filesystem objects are
now checked for validity.
- Minor cleanups in comments.
- Added this Changes file. At last!
Version 3.2
-----------
- Extension block cache: Reading/writing of huge files
(several MB) is much faster (of course the added
overhead slows down opening, but this is hardly
noticeable).
- The same get_block()-routine can now be used for
both OFS and FFS.
- The super block is now searched in the block that
was calculated and in the one following. This
should remedy the round-off error introduced by
the 1-k blocks that Linux uses.
- Minor changes to adhere to the new VFS interface.
- The number of used blocks is now also calculated
if the filesystem is mounted read-only.
- Prefixed some constants with AFFS_ to avoid name
clashes.
- Removed 'EXPERIMENTAL' status.
Version 3.1
-----------
- Fixed a nasty bug which didn't allow read-only
mounts.
- Allow dir-cache filesystems to be mounted
read only.
- OFS support.
- Several other changes I just cannot remember
any more.
Version 3.0
-----------
- Almost complete rewrite for the new VFS
interface in Linux 1.3.
- Write support.
- Support for hard and symbolic links.
- Lots of things I remember even less ...
Version 2.0
-----------
- Fixed a few things to get it compiled.
- Automatic root block calculation.
- Partition checker for genhd.c
========================================
Let's just call Ray Burr's original affs
'Version 1.0'.

9
fs/affs/Makefile Normal file
View File

@@ -0,0 +1,9 @@
#
# Makefile for the Linux affs filesystem routines.
#
#EXTRA_CFLAGS=-DDEBUG=1
obj-$(CONFIG_AFFS_FS) += affs.o
affs-objs := super.o namei.o inode.o file.o dir.o amigaffs.o bitmap.o symlink.o

304
fs/affs/affs.h Normal file
View File

@@ -0,0 +1,304 @@
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/buffer_head.h>
#include <linux/affs_fs.h>
#include <linux/amigaffs.h>
/* AmigaOS allows file names with up to 30 characters length.
* Names longer than that will be silently truncated. If you
* want to disallow this, comment out the following #define.
* Creating filesystem objects with longer names will then
* result in an error (ENAMETOOLONG).
*/
/*#define AFFS_NO_TRUNCATE */
/* Ugly macros make the code more pretty. */
#define GET_END_PTR(st,p,sz) ((st *)((char *)(p)+((sz)-sizeof(st))))
#define AFFS_GET_HASHENTRY(data,hashkey) be32_to_cpu(((struct dir_front *)data)->hashtable[hashkey])
#define AFFS_BLOCK(sb, bh, blk) (AFFS_HEAD(bh)->table[AFFS_SB(sb)->s_hashsize-1-(blk)])
#ifdef __LITTLE_ENDIAN
#define BO_EXBITS 0x18UL
#elif defined(__BIG_ENDIAN)
#define BO_EXBITS 0x00UL
#else
#error Endianness must be known for affs to work.
#endif
#define AFFS_HEAD(bh) ((struct affs_head *)(bh)->b_data)
#define AFFS_TAIL(sb, bh) ((struct affs_tail *)((bh)->b_data+(sb)->s_blocksize-sizeof(struct affs_tail)))
#define AFFS_ROOT_HEAD(bh) ((struct affs_root_head *)(bh)->b_data)
#define AFFS_ROOT_TAIL(sb, bh) ((struct affs_root_tail *)((bh)->b_data+(sb)->s_blocksize-sizeof(struct affs_root_tail)))
#define AFFS_DATA_HEAD(bh) ((struct affs_data_head *)(bh)->b_data)
#define AFFS_DATA(bh) (((struct affs_data_head *)(bh)->b_data)->data)
#define AFFS_CACHE_SIZE PAGE_SIZE
#define AFFS_MAX_PREALLOC 32
#define AFFS_LC_SIZE (AFFS_CACHE_SIZE/sizeof(u32)/2)
#define AFFS_AC_SIZE (AFFS_CACHE_SIZE/sizeof(struct affs_ext_key)/2)
#define AFFS_AC_MASK (AFFS_AC_SIZE-1)
struct affs_ext_key {
u32 ext; /* idx of the extended block */
u32 key; /* block number */
};
/*
* affs fs inode data in memory
*/
struct affs_inode_info {
u32 i_opencnt;
struct semaphore i_link_lock; /* Protects internal inode access. */
struct semaphore i_ext_lock; /* Protects internal inode access. */
#define i_hash_lock i_ext_lock
u32 i_blkcnt; /* block count */
u32 i_extcnt; /* extended block count */
u32 *i_lc; /* linear cache of extended blocks */
u32 i_lc_size;
u32 i_lc_shift;
u32 i_lc_mask;
struct affs_ext_key *i_ac; /* associative cache of extended blocks */
u32 i_ext_last; /* last accessed extended block */
struct buffer_head *i_ext_bh; /* bh of last extended block */
loff_t mmu_private;
u32 i_protect; /* unused attribute bits */
u32 i_lastalloc; /* last allocated block */
int i_pa_cnt; /* number of preallocated blocks */
struct inode vfs_inode;
};
/* short cut to get to the affs specific inode data */
static inline struct affs_inode_info *AFFS_I(struct inode *inode)
{
return list_entry(inode, struct affs_inode_info, vfs_inode);
}
/*
* super-block data in memory
*
* Block numbers are adjusted for their actual size
*
*/
struct affs_bm_info {
u32 bm_key; /* Disk block number */
u32 bm_free; /* Free blocks in here */
};
struct affs_sb_info {
int s_partition_size; /* Partition size in blocks. */
int s_reserved; /* Number of reserved blocks. */
//u32 s_blksize; /* Initial device blksize */
u32 s_data_blksize; /* size of the data block w/o header */
u32 s_root_block; /* FFS root block number. */
int s_hashsize; /* Size of hash table. */
unsigned long s_flags; /* See below. */
uid_t s_uid; /* uid to override */
gid_t s_gid; /* gid to override */
umode_t s_mode; /* mode to override */
struct buffer_head *s_root_bh; /* Cached root block. */
struct semaphore s_bmlock; /* Protects bitmap access. */
struct affs_bm_info *s_bitmap; /* Bitmap infos. */
u32 s_bmap_count; /* # of bitmap blocks. */
u32 s_bmap_bits; /* # of bits in one bitmap blocks */
u32 s_last_bmap;
struct buffer_head *s_bmap_bh;
char *s_prefix; /* Prefix for volumes and assigns. */
int s_prefix_len; /* Length of prefix. */
char s_volume[32]; /* Volume prefix for absolute symlinks. */
};
#define SF_INTL 0x0001 /* International filesystem. */
#define SF_BM_VALID 0x0002 /* Bitmap is valid. */
#define SF_IMMUTABLE 0x0004 /* Protection bits cannot be changed */
#define SF_QUIET 0x0008 /* chmod errors will be not reported */
#define SF_SETUID 0x0010 /* Ignore Amiga uid */
#define SF_SETGID 0x0020 /* Ignore Amiga gid */
#define SF_SETMODE 0x0040 /* Ignore Amiga protection bits */
#define SF_MUFS 0x0100 /* Use MUFS uid/gid mapping */
#define SF_OFS 0x0200 /* Old filesystem */
#define SF_PREFIX 0x0400 /* Buffer for prefix is allocated */
#define SF_VERBOSE 0x0800 /* Talk about fs when mounting */
/* short cut to get to the affs specific sb data */
static inline struct affs_sb_info *AFFS_SB(struct super_block *sb)
{
return sb->s_fs_info;
}
/* amigaffs.c */
extern int affs_insert_hash(struct inode *inode, struct buffer_head *bh);
extern int affs_remove_hash(struct inode *dir, struct buffer_head *rem_bh);
extern int affs_remove_header(struct dentry *dentry);
extern u32 affs_checksum_block(struct super_block *sb, struct buffer_head *bh);
extern void affs_fix_checksum(struct super_block *sb, struct buffer_head *bh);
extern void secs_to_datestamp(time_t secs, struct affs_date *ds);
extern mode_t prot_to_mode(u32 prot);
extern void mode_to_prot(struct inode *inode);
extern void affs_error(struct super_block *sb, const char *function, const char *fmt, ...);
extern void affs_warning(struct super_block *sb, const char *function, const char *fmt, ...);
extern int affs_check_name(const unsigned char *name, int len);
extern int affs_copy_name(unsigned char *bstr, struct dentry *dentry);
/* bitmap. c */
extern u32 affs_count_free_blocks(struct super_block *s);
extern void affs_free_block(struct super_block *sb, u32 block);
extern u32 affs_alloc_block(struct inode *inode, u32 goal);
extern int affs_init_bitmap(struct super_block *sb, int *flags);
extern void affs_free_bitmap(struct super_block *sb);
/* namei.c */
extern int affs_hash_name(struct super_block *sb, const u8 *name, unsigned int len);
extern struct dentry *affs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *);
extern int affs_unlink(struct inode *dir, struct dentry *dentry);
extern int affs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *);
extern int affs_mkdir(struct inode *dir, struct dentry *dentry, int mode);
extern int affs_rmdir(struct inode *dir, struct dentry *dentry);
extern int affs_link(struct dentry *olddentry, struct inode *dir,
struct dentry *dentry);
extern int affs_symlink(struct inode *dir, struct dentry *dentry,
const char *symname);
extern int affs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry);
/* inode.c */
extern unsigned long affs_parent_ino(struct inode *dir);
extern struct inode *affs_new_inode(struct inode *dir);
extern int affs_notify_change(struct dentry *dentry, struct iattr *attr);
extern void affs_put_inode(struct inode *inode);
extern void affs_delete_inode(struct inode *inode);
extern void affs_clear_inode(struct inode *inode);
extern void affs_read_inode(struct inode *inode);
extern int affs_write_inode(struct inode *inode, int);
extern int affs_add_entry(struct inode *dir, struct inode *inode, struct dentry *dentry, s32 type);
/* file.c */
void affs_free_prealloc(struct inode *inode);
extern void affs_truncate(struct inode *);
/* dir.c */
extern void affs_dir_truncate(struct inode *);
/* jump tables */
extern struct inode_operations affs_file_inode_operations;
extern struct inode_operations affs_dir_inode_operations;
extern struct inode_operations affs_symlink_inode_operations;
extern struct file_operations affs_file_operations;
extern struct file_operations affs_file_operations_ofs;
extern struct file_operations affs_dir_operations;
extern struct address_space_operations affs_symlink_aops;
extern struct address_space_operations affs_aops;
extern struct address_space_operations affs_aops_ofs;
extern struct dentry_operations affs_dentry_operations;
extern struct dentry_operations affs_dentry_operations_intl;
static inline void
affs_set_blocksize(struct super_block *sb, int size)
{
sb_set_blocksize(sb, size);
}
static inline struct buffer_head *
affs_bread(struct super_block *sb, int block)
{
pr_debug("affs_bread: %d\n", block);
if (block >= AFFS_SB(sb)->s_reserved && block < AFFS_SB(sb)->s_partition_size)
return sb_bread(sb, block);
return NULL;
}
static inline struct buffer_head *
affs_getblk(struct super_block *sb, int block)
{
pr_debug("affs_getblk: %d\n", block);
if (block >= AFFS_SB(sb)->s_reserved && block < AFFS_SB(sb)->s_partition_size)
return sb_getblk(sb, block);
return NULL;
}
static inline struct buffer_head *
affs_getzeroblk(struct super_block *sb, int block)
{
struct buffer_head *bh;
pr_debug("affs_getzeroblk: %d\n", block);
if (block >= AFFS_SB(sb)->s_reserved && block < AFFS_SB(sb)->s_partition_size) {
bh = sb_getblk(sb, block);
lock_buffer(bh);
memset(bh->b_data, 0 , sb->s_blocksize);
set_buffer_uptodate(bh);
unlock_buffer(bh);
return bh;
}
return NULL;
}
static inline struct buffer_head *
affs_getemptyblk(struct super_block *sb, int block)
{
struct buffer_head *bh;
pr_debug("affs_getemptyblk: %d\n", block);
if (block >= AFFS_SB(sb)->s_reserved && block < AFFS_SB(sb)->s_partition_size) {
bh = sb_getblk(sb, block);
wait_on_buffer(bh);
set_buffer_uptodate(bh);
return bh;
}
return NULL;
}
static inline void
affs_brelse(struct buffer_head *bh)
{
if (bh)
pr_debug("affs_brelse: %lld\n", (long long) bh->b_blocknr);
brelse(bh);
}
static inline void
affs_adjust_checksum(struct buffer_head *bh, u32 val)
{
u32 tmp = be32_to_cpu(((__be32 *)bh->b_data)[5]);
((__be32 *)bh->b_data)[5] = cpu_to_be32(tmp - val);
}
static inline void
affs_adjust_bitmapchecksum(struct buffer_head *bh, u32 val)
{
u32 tmp = be32_to_cpu(((__be32 *)bh->b_data)[0]);
((__be32 *)bh->b_data)[0] = cpu_to_be32(tmp - val);
}
static inline void
affs_lock_link(struct inode *inode)
{
down(&AFFS_I(inode)->i_link_lock);
}
static inline void
affs_unlock_link(struct inode *inode)
{
up(&AFFS_I(inode)->i_link_lock);
}
static inline void
affs_lock_dir(struct inode *inode)
{
down(&AFFS_I(inode)->i_hash_lock);
}
static inline void
affs_unlock_dir(struct inode *inode)
{
up(&AFFS_I(inode)->i_hash_lock);
}
static inline void
affs_lock_ext(struct inode *inode)
{
down(&AFFS_I(inode)->i_ext_lock);
}
static inline void
affs_unlock_ext(struct inode *inode)
{
up(&AFFS_I(inode)->i_ext_lock);
}

509
fs/affs/amigaffs.c Normal file
View File

@@ -0,0 +1,509 @@
/*
* linux/fs/affs/amigaffs.c
*
* (c) 1996 Hans-Joachim Widmaier - Rewritten
*
* (C) 1993 Ray Burr - Amiga FFS filesystem.
*
* Please send bug reports to: hjw@zvw.de
*/
#include "affs.h"
extern struct timezone sys_tz;
static char ErrorBuffer[256];
/*
* Functions for accessing Amiga-FFS structures.
*/
/* Insert a header block bh into the directory dir
* caller must hold AFFS_DIR->i_hash_lock!
*/
int
affs_insert_hash(struct inode *dir, struct buffer_head *bh)
{
struct super_block *sb = dir->i_sb;
struct buffer_head *dir_bh;
u32 ino, hash_ino;
int offset;
ino = bh->b_blocknr;
offset = affs_hash_name(sb, AFFS_TAIL(sb, bh)->name + 1, AFFS_TAIL(sb, bh)->name[0]);
pr_debug("AFFS: insert_hash(dir=%u, ino=%d)\n", (u32)dir->i_ino, ino);
dir_bh = affs_bread(sb, dir->i_ino);
if (!dir_bh)
return -EIO;
hash_ino = be32_to_cpu(AFFS_HEAD(dir_bh)->table[offset]);
while (hash_ino) {
affs_brelse(dir_bh);
dir_bh = affs_bread(sb, hash_ino);
if (!dir_bh)
return -EIO;
hash_ino = be32_to_cpu(AFFS_TAIL(sb, dir_bh)->hash_chain);
}
AFFS_TAIL(sb, bh)->parent = cpu_to_be32(dir->i_ino);
AFFS_TAIL(sb, bh)->hash_chain = 0;
affs_fix_checksum(sb, bh);
if (dir->i_ino == dir_bh->b_blocknr)
AFFS_HEAD(dir_bh)->table[offset] = cpu_to_be32(ino);
else
AFFS_TAIL(sb, dir_bh)->hash_chain = cpu_to_be32(ino);
affs_adjust_checksum(dir_bh, ino);
mark_buffer_dirty_inode(dir_bh, dir);
affs_brelse(dir_bh);
dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
dir->i_version++;
mark_inode_dirty(dir);
return 0;
}
/* Remove a header block from its directory.
* caller must hold AFFS_DIR->i_hash_lock!
*/
int
affs_remove_hash(struct inode *dir, struct buffer_head *rem_bh)
{
struct super_block *sb;
struct buffer_head *bh;
u32 rem_ino, hash_ino;
__be32 ino;
int offset, retval;
sb = dir->i_sb;
rem_ino = rem_bh->b_blocknr;
offset = affs_hash_name(sb, AFFS_TAIL(sb, rem_bh)->name+1, AFFS_TAIL(sb, rem_bh)->name[0]);
pr_debug("AFFS: remove_hash(dir=%d, ino=%d, hashval=%d)\n", (u32)dir->i_ino, rem_ino, offset);
bh = affs_bread(sb, dir->i_ino);
if (!bh)
return -EIO;
retval = -ENOENT;
hash_ino = be32_to_cpu(AFFS_HEAD(bh)->table[offset]);
while (hash_ino) {
if (hash_ino == rem_ino) {
ino = AFFS_TAIL(sb, rem_bh)->hash_chain;
if (dir->i_ino == bh->b_blocknr)
AFFS_HEAD(bh)->table[offset] = ino;
else
AFFS_TAIL(sb, bh)->hash_chain = ino;
affs_adjust_checksum(bh, be32_to_cpu(ino) - hash_ino);
mark_buffer_dirty_inode(bh, dir);
AFFS_TAIL(sb, rem_bh)->parent = 0;
retval = 0;
break;
}
affs_brelse(bh);
bh = affs_bread(sb, hash_ino);
if (!bh)
return -EIO;
hash_ino = be32_to_cpu(AFFS_TAIL(sb, bh)->hash_chain);
}
affs_brelse(bh);
dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
dir->i_version++;
mark_inode_dirty(dir);
return retval;
}
static void
affs_fix_dcache(struct dentry *dentry, u32 entry_ino)
{
struct inode *inode = dentry->d_inode;
void *data = dentry->d_fsdata;
struct list_head *head, *next;
spin_lock(&dcache_lock);
head = &inode->i_dentry;
next = head->next;
while (next != head) {
dentry = list_entry(next, struct dentry, d_alias);
if (entry_ino == (u32)(long)dentry->d_fsdata) {
dentry->d_fsdata = data;
break;
}
next = next->next;
}
spin_unlock(&dcache_lock);
}
/* Remove header from link chain */
static int
affs_remove_link(struct dentry *dentry)
{
struct inode *dir, *inode = dentry->d_inode;
struct super_block *sb = inode->i_sb;
struct buffer_head *bh = NULL, *link_bh = NULL;
u32 link_ino, ino;
int retval;
pr_debug("AFFS: remove_link(key=%ld)\n", inode->i_ino);
retval = -EIO;
bh = affs_bread(sb, inode->i_ino);
if (!bh)
goto done;
link_ino = (u32)(long)dentry->d_fsdata;
if (inode->i_ino == link_ino) {
/* we can't remove the head of the link, as its blocknr is still used as ino,
* so we remove the block of the first link instead.
*/
link_ino = be32_to_cpu(AFFS_TAIL(sb, bh)->link_chain);
link_bh = affs_bread(sb, link_ino);
if (!link_bh)
goto done;
dir = iget(sb, be32_to_cpu(AFFS_TAIL(sb, link_bh)->parent));
if (!dir)
goto done;
affs_lock_dir(dir);
affs_fix_dcache(dentry, link_ino);
retval = affs_remove_hash(dir, link_bh);
if (retval)
goto done;
mark_buffer_dirty_inode(link_bh, inode);
memcpy(AFFS_TAIL(sb, bh)->name, AFFS_TAIL(sb, link_bh)->name, 32);
retval = affs_insert_hash(dir, bh);
if (retval)
goto done;
mark_buffer_dirty_inode(bh, inode);
affs_unlock_dir(dir);
iput(dir);
} else {
link_bh = affs_bread(sb, link_ino);
if (!link_bh)
goto done;
}
while ((ino = be32_to_cpu(AFFS_TAIL(sb, bh)->link_chain)) != 0) {
if (ino == link_ino) {
__be32 ino2 = AFFS_TAIL(sb, link_bh)->link_chain;
AFFS_TAIL(sb, bh)->link_chain = ino2;
affs_adjust_checksum(bh, be32_to_cpu(ino2) - link_ino);
mark_buffer_dirty_inode(bh, inode);
retval = 0;
/* Fix the link count, if bh is a normal header block without links */
switch (be32_to_cpu(AFFS_TAIL(sb, bh)->stype)) {
case ST_LINKDIR:
case ST_LINKFILE:
break;
default:
if (!AFFS_TAIL(sb, bh)->link_chain)
inode->i_nlink = 1;
}
affs_free_block(sb, link_ino);
goto done;
}
affs_brelse(bh);
bh = affs_bread(sb, ino);
if (!bh)
goto done;
}
retval = -ENOENT;
done:
affs_brelse(link_bh);
affs_brelse(bh);
return retval;
}
static int
affs_empty_dir(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *bh;
int retval, size;
retval = -EIO;
bh = affs_bread(sb, inode->i_ino);
if (!bh)
goto done;
retval = -ENOTEMPTY;
for (size = AFFS_SB(sb)->s_hashsize - 1; size >= 0; size--)
if (AFFS_HEAD(bh)->table[size])
goto not_empty;
retval = 0;
not_empty:
affs_brelse(bh);
done:
return retval;
}
/* Remove a filesystem object. If the object to be removed has
* links to it, one of the links must be changed to inherit
* the file or directory. As above, any inode will do.
* The buffer will not be freed. If the header is a link, the
* block will be marked as free.
* This function returns a negative error number in case of
* an error, else 0 if the inode is to be deleted or 1 if not.
*/
int
affs_remove_header(struct dentry *dentry)
{
struct super_block *sb;
struct inode *inode, *dir;
struct buffer_head *bh = NULL;
int retval;
dir = dentry->d_parent->d_inode;
sb = dir->i_sb;
retval = -ENOENT;
inode = dentry->d_inode;
if (!inode)
goto done;
pr_debug("AFFS: remove_header(key=%ld)\n", inode->i_ino);
retval = -EIO;
bh = affs_bread(sb, (u32)(long)dentry->d_fsdata);
if (!bh)
goto done;
affs_lock_link(inode);
affs_lock_dir(dir);
switch (be32_to_cpu(AFFS_TAIL(sb, bh)->stype)) {
case ST_USERDIR:
/* if we ever want to support links to dirs
* i_hash_lock of the inode must only be
* taken after some checks
*/
affs_lock_dir(inode);
retval = affs_empty_dir(inode);
affs_unlock_dir(inode);
if (retval)
goto done_unlock;
break;
default:
break;
}
retval = affs_remove_hash(dir, bh);
if (retval)
goto done_unlock;
mark_buffer_dirty_inode(bh, inode);
affs_unlock_dir(dir);
if (inode->i_nlink > 1)
retval = affs_remove_link(dentry);
else
inode->i_nlink = 0;
affs_unlock_link(inode);
inode->i_ctime = CURRENT_TIME_SEC;
mark_inode_dirty(inode);
done:
affs_brelse(bh);
return retval;
done_unlock:
affs_unlock_dir(dir);
affs_unlock_link(inode);
goto done;
}
/* Checksum a block, do various consistency checks and optionally return
the blocks type number. DATA points to the block. If their pointers
are non-null, *PTYPE and *STYPE are set to the primary and secondary
block types respectively, *HASHSIZE is set to the size of the hashtable
(which lets us calculate the block size).
Returns non-zero if the block is not consistent. */
u32
affs_checksum_block(struct super_block *sb, struct buffer_head *bh)
{
__be32 *ptr = (__be32 *)bh->b_data;
u32 sum;
int bsize;
sum = 0;
for (bsize = sb->s_blocksize / sizeof(__be32); bsize > 0; bsize--)
sum += be32_to_cpu(*ptr++);
return sum;
}
/*
* Calculate the checksum of a disk block and store it
* at the indicated position.
*/
void
affs_fix_checksum(struct super_block *sb, struct buffer_head *bh)
{
int cnt = sb->s_blocksize / sizeof(__be32);
__be32 *ptr = (__be32 *)bh->b_data;
u32 checksum;
__be32 *checksumptr;
checksumptr = ptr + 5;
*checksumptr = 0;
for (checksum = 0; cnt > 0; ptr++, cnt--)
checksum += be32_to_cpu(*ptr);
*checksumptr = cpu_to_be32(-checksum);
}
void
secs_to_datestamp(time_t secs, struct affs_date *ds)
{
u32 days;
u32 minute;
secs -= sys_tz.tz_minuteswest * 60 + ((8 * 365 + 2) * 24 * 60 * 60);
if (secs < 0)
secs = 0;
days = secs / 86400;
secs -= days * 86400;
minute = secs / 60;
secs -= minute * 60;
ds->days = cpu_to_be32(days);
ds->mins = cpu_to_be32(minute);
ds->ticks = cpu_to_be32(secs * 50);
}
mode_t
prot_to_mode(u32 prot)
{
int mode = 0;
if (!(prot & FIBF_NOWRITE))
mode |= S_IWUSR;
if (!(prot & FIBF_NOREAD))
mode |= S_IRUSR;
if (!(prot & FIBF_NOEXECUTE))
mode |= S_IXUSR;
if (prot & FIBF_GRP_WRITE)
mode |= S_IWGRP;
if (prot & FIBF_GRP_READ)
mode |= S_IRGRP;
if (prot & FIBF_GRP_EXECUTE)
mode |= S_IXGRP;
if (prot & FIBF_OTR_WRITE)
mode |= S_IWOTH;
if (prot & FIBF_OTR_READ)
mode |= S_IROTH;
if (prot & FIBF_OTR_EXECUTE)
mode |= S_IXOTH;
return mode;
}
void
mode_to_prot(struct inode *inode)
{
u32 prot = AFFS_I(inode)->i_protect;
mode_t mode = inode->i_mode;
if (!(mode & S_IXUSR))
prot |= FIBF_NOEXECUTE;
if (!(mode & S_IRUSR))
prot |= FIBF_NOREAD;
if (!(mode & S_IWUSR))
prot |= FIBF_NOWRITE;
if (mode & S_IXGRP)
prot |= FIBF_GRP_EXECUTE;
if (mode & S_IRGRP)
prot |= FIBF_GRP_READ;
if (mode & S_IWGRP)
prot |= FIBF_GRP_WRITE;
if (mode & S_IXOTH)
prot |= FIBF_OTR_EXECUTE;
if (mode & S_IROTH)
prot |= FIBF_OTR_READ;
if (mode & S_IWOTH)
prot |= FIBF_OTR_WRITE;
AFFS_I(inode)->i_protect = prot;
}
void
affs_error(struct super_block *sb, const char *function, const char *fmt, ...)
{
va_list args;
va_start(args,fmt);
vsprintf(ErrorBuffer,fmt,args);
va_end(args);
printk(KERN_CRIT "AFFS error (device %s): %s(): %s\n", sb->s_id,
function,ErrorBuffer);
if (!(sb->s_flags & MS_RDONLY))
printk(KERN_WARNING "AFFS: Remounting filesystem read-only\n");
sb->s_flags |= MS_RDONLY;
}
void
affs_warning(struct super_block *sb, const char *function, const char *fmt, ...)
{
va_list args;
va_start(args,fmt);
vsprintf(ErrorBuffer,fmt,args);
va_end(args);
printk(KERN_WARNING "AFFS warning (device %s): %s(): %s\n", sb->s_id,
function,ErrorBuffer);
}
/* Check if the name is valid for a affs object. */
int
affs_check_name(const unsigned char *name, int len)
{
int i;
if (len > 30)
#ifdef AFFS_NO_TRUNCATE
return -ENAMETOOLONG;
#else
len = 30;
#endif
for (i = 0; i < len; i++) {
if (name[i] < ' ' || name[i] == ':'
|| (name[i] > 0x7e && name[i] < 0xa0))
return -EINVAL;
}
return 0;
}
/* This function copies name to bstr, with at most 30
* characters length. The bstr will be prepended by
* a length byte.
* NOTE: The name will must be already checked by
* affs_check_name()!
*/
int
affs_copy_name(unsigned char *bstr, struct dentry *dentry)
{
int len = min(dentry->d_name.len, 30u);
*bstr++ = len;
memcpy(bstr, dentry->d_name.name, len);
return len;
}

390
fs/affs/bitmap.c Normal file
View File

@@ -0,0 +1,390 @@
/*
* linux/fs/affs/bitmap.c
*
* (c) 1996 Hans-Joachim Widmaier
*
* bitmap.c contains the code that handles all bitmap related stuff -
* block allocation, deallocation, calculation of free space.
*/
#include "affs.h"
/* This is, of course, shamelessly stolen from fs/minix */
static int nibblemap[] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
static u32
affs_count_free_bits(u32 blocksize, const void *data)
{
const u32 *map;
u32 free;
u32 tmp;
map = data;
free = 0;
for (blocksize /= 4; blocksize > 0; blocksize--) {
tmp = *map++;
while (tmp) {
free += nibblemap[tmp & 0xf];
tmp >>= 4;
}
}
return free;
}
u32
affs_count_free_blocks(struct super_block *sb)
{
struct affs_bm_info *bm;
u32 free;
int i;
pr_debug("AFFS: count_free_blocks()\n");
if (sb->s_flags & MS_RDONLY)
return 0;
down(&AFFS_SB(sb)->s_bmlock);
bm = AFFS_SB(sb)->s_bitmap;
free = 0;
for (i = AFFS_SB(sb)->s_bmap_count; i > 0; bm++, i--)
free += bm->bm_free;
up(&AFFS_SB(sb)->s_bmlock);
return free;
}
void
affs_free_block(struct super_block *sb, u32 block)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
struct affs_bm_info *bm;
struct buffer_head *bh;
u32 blk, bmap, bit, mask, tmp;
__be32 *data;
pr_debug("AFFS: free_block(%u)\n", block);
if (block > sbi->s_partition_size)
goto err_range;
blk = block - sbi->s_reserved;
bmap = blk / sbi->s_bmap_bits;
bit = blk % sbi->s_bmap_bits;
bm = &sbi->s_bitmap[bmap];
down(&sbi->s_bmlock);
bh = sbi->s_bmap_bh;
if (sbi->s_last_bmap != bmap) {
affs_brelse(bh);
bh = affs_bread(sb, bm->bm_key);
if (!bh)
goto err_bh_read;
sbi->s_bmap_bh = bh;
sbi->s_last_bmap = bmap;
}
mask = 1 << (bit & 31);
data = (__be32 *)bh->b_data + bit / 32 + 1;
/* mark block free */
tmp = be32_to_cpu(*data);
if (tmp & mask)
goto err_free;
*data = cpu_to_be32(tmp | mask);
/* fix checksum */
tmp = be32_to_cpu(*(__be32 *)bh->b_data);
*(__be32 *)bh->b_data = cpu_to_be32(tmp - mask);
mark_buffer_dirty(bh);
sb->s_dirt = 1;
bm->bm_free++;
up(&sbi->s_bmlock);
return;
err_free:
affs_warning(sb,"affs_free_block","Trying to free block %u which is already free", block);
up(&sbi->s_bmlock);
return;
err_bh_read:
affs_error(sb,"affs_free_block","Cannot read bitmap block %u", bm->bm_key);
sbi->s_bmap_bh = NULL;
sbi->s_last_bmap = ~0;
up(&sbi->s_bmlock);
return;
err_range:
affs_error(sb, "affs_free_block","Block %u outside partition", block);
return;
}
/*
* Allocate a block in the given allocation zone.
* Since we have to byte-swap the bitmap on little-endian
* machines, this is rather expensive. Therefor we will
* preallocate up to 16 blocks from the same word, if
* possible. We are not doing preallocations in the
* header zone, though.
*/
u32
affs_alloc_block(struct inode *inode, u32 goal)
{
struct super_block *sb;
struct affs_sb_info *sbi;
struct affs_bm_info *bm;
struct buffer_head *bh;
__be32 *data, *enddata;
u32 blk, bmap, bit, mask, mask2, tmp;
int i;
sb = inode->i_sb;
sbi = AFFS_SB(sb);
pr_debug("AFFS: balloc(inode=%lu,goal=%u): ", inode->i_ino, goal);
if (AFFS_I(inode)->i_pa_cnt) {
pr_debug("%d\n", AFFS_I(inode)->i_lastalloc+1);
AFFS_I(inode)->i_pa_cnt--;
return ++AFFS_I(inode)->i_lastalloc;
}
if (!goal || goal > sbi->s_partition_size) {
if (goal)
affs_warning(sb, "affs_balloc", "invalid goal %d", goal);
//if (!AFFS_I(inode)->i_last_block)
// affs_warning(sb, "affs_balloc", "no last alloc block");
goal = sbi->s_reserved;
}
blk = goal - sbi->s_reserved;
bmap = blk / sbi->s_bmap_bits;
bm = &sbi->s_bitmap[bmap];
down(&sbi->s_bmlock);
if (bm->bm_free)
goto find_bmap_bit;
find_bmap:
/* search for the next bmap buffer with free bits */
i = sbi->s_bmap_count;
do {
if (--i < 0)
goto err_full;
bmap++;
bm++;
if (bmap < sbi->s_bmap_count)
continue;
/* restart search at zero */
bmap = 0;
bm = sbi->s_bitmap;
} while (!bm->bm_free);
blk = bmap * sbi->s_bmap_bits;
find_bmap_bit:
bh = sbi->s_bmap_bh;
if (sbi->s_last_bmap != bmap) {
affs_brelse(bh);
bh = affs_bread(sb, bm->bm_key);
if (!bh)
goto err_bh_read;
sbi->s_bmap_bh = bh;
sbi->s_last_bmap = bmap;
}
/* find an unused block in this bitmap block */
bit = blk % sbi->s_bmap_bits;
data = (__be32 *)bh->b_data + bit / 32 + 1;
enddata = (__be32 *)((u8 *)bh->b_data + sb->s_blocksize);
mask = ~0UL << (bit & 31);
blk &= ~31UL;
tmp = be32_to_cpu(*data);
if (tmp & mask)
goto find_bit;
/* scan the rest of the buffer */
do {
blk += 32;
if (++data >= enddata)
/* didn't find something, can only happen
* if scan didn't start at 0, try next bmap
*/
goto find_bmap;
} while (!*data);
tmp = be32_to_cpu(*data);
mask = ~0;
find_bit:
/* finally look for a free bit in the word */
bit = ffs(tmp & mask) - 1;
blk += bit + sbi->s_reserved;
mask2 = mask = 1 << (bit & 31);
AFFS_I(inode)->i_lastalloc = blk;
/* prealloc as much as possible within this word */
while ((mask2 <<= 1)) {
if (!(tmp & mask2))
break;
AFFS_I(inode)->i_pa_cnt++;
mask |= mask2;
}
bm->bm_free -= AFFS_I(inode)->i_pa_cnt + 1;
*data = cpu_to_be32(tmp & ~mask);
/* fix checksum */
tmp = be32_to_cpu(*(__be32 *)bh->b_data);
*(__be32 *)bh->b_data = cpu_to_be32(tmp + mask);
mark_buffer_dirty(bh);
sb->s_dirt = 1;
up(&sbi->s_bmlock);
pr_debug("%d\n", blk);
return blk;
err_bh_read:
affs_error(sb,"affs_read_block","Cannot read bitmap block %u", bm->bm_key);
sbi->s_bmap_bh = NULL;
sbi->s_last_bmap = ~0;
err_full:
up(&sbi->s_bmlock);
pr_debug("failed\n");
return 0;
}
int affs_init_bitmap(struct super_block *sb, int *flags)
{
struct affs_bm_info *bm;
struct buffer_head *bmap_bh = NULL, *bh = NULL;
__be32 *bmap_blk;
u32 size, blk, end, offset, mask;
int i, res = 0;
struct affs_sb_info *sbi = AFFS_SB(sb);
if (*flags & MS_RDONLY)
return 0;
if (!AFFS_ROOT_TAIL(sb, sbi->s_root_bh)->bm_flag) {
printk(KERN_NOTICE "AFFS: Bitmap invalid - mounting %s read only\n",
sb->s_id);
*flags |= MS_RDONLY;
return 0;
}
sbi->s_last_bmap = ~0;
sbi->s_bmap_bh = NULL;
sbi->s_bmap_bits = sb->s_blocksize * 8 - 32;
sbi->s_bmap_count = (sbi->s_partition_size - sbi->s_reserved +
sbi->s_bmap_bits - 1) / sbi->s_bmap_bits;
size = sbi->s_bmap_count * sizeof(*bm);
bm = sbi->s_bitmap = kmalloc(size, GFP_KERNEL);
if (!sbi->s_bitmap) {
printk(KERN_ERR "AFFS: Bitmap allocation failed\n");
return -ENOMEM;
}
memset(sbi->s_bitmap, 0, size);
bmap_blk = (__be32 *)sbi->s_root_bh->b_data;
blk = sb->s_blocksize / 4 - 49;
end = blk + 25;
for (i = sbi->s_bmap_count; i > 0; bm++, i--) {
affs_brelse(bh);
bm->bm_key = be32_to_cpu(bmap_blk[blk]);
bh = affs_bread(sb, bm->bm_key);
if (!bh) {
printk(KERN_ERR "AFFS: Cannot read bitmap\n");
res = -EIO;
goto out;
}
if (affs_checksum_block(sb, bh)) {
printk(KERN_WARNING "AFFS: Bitmap %u invalid - mounting %s read only.\n",
bm->bm_key, sb->s_id);
*flags |= MS_RDONLY;
goto out;
}
pr_debug("AFFS: read bitmap block %d: %d\n", blk, bm->bm_key);
bm->bm_free = affs_count_free_bits(sb->s_blocksize - 4, bh->b_data + 4);
/* Don't try read the extension if this is the last block,
* but we also need the right bm pointer below
*/
if (++blk < end || i == 1)
continue;
if (bmap_bh)
affs_brelse(bmap_bh);
bmap_bh = affs_bread(sb, be32_to_cpu(bmap_blk[blk]));
if (!bmap_bh) {
printk(KERN_ERR "AFFS: Cannot read bitmap extension\n");
res = -EIO;
goto out;
}
bmap_blk = (__be32 *)bmap_bh->b_data;
blk = 0;
end = sb->s_blocksize / 4 - 1;
}
offset = (sbi->s_partition_size - sbi->s_reserved) % sbi->s_bmap_bits;
mask = ~(0xFFFFFFFFU << (offset & 31));
pr_debug("last word: %d %d %d\n", offset, offset / 32 + 1, mask);
offset = offset / 32 + 1;
if (mask) {
u32 old, new;
/* Mark unused bits in the last word as allocated */
old = be32_to_cpu(((__be32 *)bh->b_data)[offset]);
new = old & mask;
//if (old != new) {
((__be32 *)bh->b_data)[offset] = cpu_to_be32(new);
/* fix checksum */
//new -= old;
//old = be32_to_cpu(*(__be32 *)bh->b_data);
//*(__be32 *)bh->b_data = cpu_to_be32(old - new);
//mark_buffer_dirty(bh);
//}
/* correct offset for the bitmap count below */
//offset++;
}
while (++offset < sb->s_blocksize / 4)
((__be32 *)bh->b_data)[offset] = 0;
((__be32 *)bh->b_data)[0] = 0;
((__be32 *)bh->b_data)[0] = cpu_to_be32(-affs_checksum_block(sb, bh));
mark_buffer_dirty(bh);
/* recalculate bitmap count for last block */
bm--;
bm->bm_free = affs_count_free_bits(sb->s_blocksize - 4, bh->b_data + 4);
out:
affs_brelse(bh);
affs_brelse(bmap_bh);
return res;
}
void affs_free_bitmap(struct super_block *sb)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
if (!sbi->s_bitmap)
return;
affs_brelse(sbi->s_bmap_bh);
sbi->s_bmap_bh = NULL;
sbi->s_last_bmap = ~0;
kfree(sbi->s_bitmap);
sbi->s_bitmap = NULL;
}

155
fs/affs/dir.c Normal file
View File

@@ -0,0 +1,155 @@
/*
* linux/fs/affs/dir.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 directory handling functions
*
*/
#include "affs.h"
static int affs_readdir(struct file *, void *, filldir_t);
struct file_operations affs_dir_operations = {
.read = generic_read_dir,
.readdir = affs_readdir,
.fsync = file_fsync,
};
/*
* directories can handle most operations...
*/
struct inode_operations affs_dir_inode_operations = {
.create = affs_create,
.lookup = affs_lookup,
.link = affs_link,
.unlink = affs_unlink,
.symlink = affs_symlink,
.mkdir = affs_mkdir,
.rmdir = affs_rmdir,
.rename = affs_rename,
.setattr = affs_notify_change,
};
static int
affs_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
struct inode *inode = filp->f_dentry->d_inode;
struct super_block *sb = inode->i_sb;
struct buffer_head *dir_bh;
struct buffer_head *fh_bh;
unsigned char *name;
int namelen;
u32 i;
int hash_pos;
int chain_pos;
u32 f_pos;
u32 ino;
int stored;
int res;
pr_debug("AFFS: readdir(ino=%lu,f_pos=%lx)\n",inode->i_ino,(unsigned long)filp->f_pos);
stored = 0;
res = -EIO;
dir_bh = NULL;
fh_bh = NULL;
f_pos = filp->f_pos;
if (f_pos == 0) {
filp->private_data = (void *)0;
if (filldir(dirent, ".", 1, f_pos, inode->i_ino, DT_DIR) < 0)
return 0;
filp->f_pos = f_pos = 1;
stored++;
}
if (f_pos == 1) {
if (filldir(dirent, "..", 2, f_pos, parent_ino(filp->f_dentry), DT_DIR) < 0)
return stored;
filp->f_pos = f_pos = 2;
stored++;
}
affs_lock_dir(inode);
chain_pos = (f_pos - 2) & 0xffff;
hash_pos = (f_pos - 2) >> 16;
if (chain_pos == 0xffff) {
affs_warning(sb, "readdir", "More than 65535 entries in chain");
chain_pos = 0;
hash_pos++;
filp->f_pos = ((hash_pos << 16) | chain_pos) + 2;
}
dir_bh = affs_bread(sb, inode->i_ino);
if (!dir_bh)
goto readdir_out;
/* If the directory hasn't changed since the last call to readdir(),
* we can jump directly to where we left off.
*/
ino = (u32)(long)filp->private_data;
if (ino && filp->f_version == inode->i_version) {
pr_debug("AFFS: readdir() left off=%d\n", ino);
goto inside;
}
ino = be32_to_cpu(AFFS_HEAD(dir_bh)->table[hash_pos]);
for (i = 0; ino && i < chain_pos; i++) {
fh_bh = affs_bread(sb, ino);
if (!fh_bh) {
affs_error(sb, "readdir","Cannot read block %d", i);
goto readdir_out;
}
ino = be32_to_cpu(AFFS_TAIL(sb, fh_bh)->hash_chain);
affs_brelse(fh_bh);
fh_bh = NULL;
}
if (ino)
goto inside;
hash_pos++;
for (; hash_pos < AFFS_SB(sb)->s_hashsize; hash_pos++) {
ino = be32_to_cpu(AFFS_HEAD(dir_bh)->table[hash_pos]);
if (!ino)
continue;
f_pos = (hash_pos << 16) + 2;
inside:
do {
fh_bh = affs_bread(sb, ino);
if (!fh_bh) {
affs_error(sb, "readdir","Cannot read block %d", ino);
goto readdir_done;
}
namelen = min(AFFS_TAIL(sb, fh_bh)->name[0], (u8)30);
name = AFFS_TAIL(sb, fh_bh)->name + 1;
pr_debug("AFFS: readdir(): filldir(\"%.*s\", ino=%u), hash=%d, f_pos=%x\n",
namelen, name, ino, hash_pos, f_pos);
if (filldir(dirent, name, namelen, f_pos, ino, DT_UNKNOWN) < 0)
goto readdir_done;
stored++;
f_pos++;
ino = be32_to_cpu(AFFS_TAIL(sb, fh_bh)->hash_chain);
affs_brelse(fh_bh);
fh_bh = NULL;
} while (ino);
}
readdir_done:
filp->f_pos = f_pos;
filp->f_version = inode->i_version;
filp->private_data = (void *)(long)ino;
res = stored;
readdir_out:
affs_brelse(dir_bh);
affs_brelse(fh_bh);
affs_unlock_dir(inode);
pr_debug("AFFS: readdir()=%d\n", stored);
return res;
}

920
fs/affs/file.c Normal file
View File

@@ -0,0 +1,920 @@
/*
* 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 ssize_t affs_file_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos);
static int affs_file_open(struct inode *inode, struct file *filp);
static int affs_file_release(struct inode *inode, struct file *filp);
struct file_operations affs_file_operations = {
.llseek = generic_file_llseek,
.read = generic_file_read,
.write = affs_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);
mark_buffer_dirty_inode(bh, inode);
AFFS_I(inode)->i_extcnt++;
mark_inode_dirty(inode);
return new_bh;
}
static inline struct buffer_head *
affs_get_extblock(struct inode *inode, u32 ext)
{
/* inline the simplest case: same extended block as last time */
struct buffer_head *bh = AFFS_I(inode)->i_ext_bh;
if (ext == AFFS_I(inode)->i_ext_last)
atomic_inc(&bh->b_count);
else
/* we have to do more (not inlined) */
bh = affs_get_extblock_slow(inode, ext);
return bh;
}
static struct buffer_head *
affs_get_extblock_slow(struct inode *inode, u32 ext)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *bh;
u32 ext_key;
u32 lc_idx, lc_off, ac_idx;
u32 tmp, idx;
if (ext == AFFS_I(inode)->i_ext_last + 1) {
/* read the next extended block from the current one */
bh = AFFS_I(inode)->i_ext_bh;
ext_key = be32_to_cpu(AFFS_TAIL(sb, bh)->extension);
if (ext < AFFS_I(inode)->i_extcnt)
goto read_ext;
if (ext > AFFS_I(inode)->i_extcnt)
BUG();
bh = affs_alloc_extblock(inode, bh, ext);
if (IS_ERR(bh))
return bh;
goto store_ext;
}
if (ext == 0) {
/* we seek back to the file header block */
ext_key = inode->i_ino;
goto read_ext;
}
if (ext >= AFFS_I(inode)->i_extcnt) {
struct buffer_head *prev_bh;
/* allocate a new extended block */
if (ext > AFFS_I(inode)->i_extcnt)
BUG();
/* get previous extended block */
prev_bh = affs_get_extblock(inode, ext - 1);
if (IS_ERR(prev_bh))
return prev_bh;
bh = affs_alloc_extblock(inode, prev_bh, ext);
affs_brelse(prev_bh);
if (IS_ERR(bh))
return bh;
goto store_ext;
}
again:
/* check if there is an extended cache and whether it's large enough */
lc_idx = ext >> AFFS_I(inode)->i_lc_shift;
lc_off = ext & AFFS_I(inode)->i_lc_mask;
if (lc_idx >= AFFS_I(inode)->i_lc_size) {
int err;
err = affs_grow_extcache(inode, lc_idx);
if (err)
return ERR_PTR(err);
goto again;
}
/* every n'th key we find in the linear cache */
if (!lc_off) {
ext_key = AFFS_I(inode)->i_lc[lc_idx];
goto read_ext;
}
/* maybe it's still in the associative cache */
ac_idx = (ext - lc_idx - 1) & AFFS_AC_MASK;
if (AFFS_I(inode)->i_ac[ac_idx].ext == ext) {
ext_key = AFFS_I(inode)->i_ac[ac_idx].key;
goto read_ext;
}
/* try to find one of the previous extended blocks */
tmp = ext;
idx = ac_idx;
while (--tmp, --lc_off > 0) {
idx = (idx - 1) & AFFS_AC_MASK;
if (AFFS_I(inode)->i_ac[idx].ext == tmp) {
ext_key = AFFS_I(inode)->i_ac[idx].key;
goto find_ext;
}
}
/* fall back to the linear cache */
ext_key = AFFS_I(inode)->i_lc[lc_idx];
find_ext:
/* read all extended blocks until we find the one we need */
//unlock cache
do {
bh = affs_bread(sb, ext_key);
if (!bh)
goto err_bread;
ext_key = be32_to_cpu(AFFS_TAIL(sb, bh)->extension);
affs_brelse(bh);
tmp++;
} while (tmp < ext);
//lock cache
/* store it in the associative cache */
// recalculate ac_idx?
AFFS_I(inode)->i_ac[ac_idx].ext = ext;
AFFS_I(inode)->i_ac[ac_idx].key = ext_key;
read_ext:
/* finally read the right extended block */
//unlock cache
bh = affs_bread(sb, ext_key);
if (!bh)
goto err_bread;
//lock cache
store_ext:
/* release old cached extended block and store the new one */
affs_brelse(AFFS_I(inode)->i_ext_bh);
AFFS_I(inode)->i_ext_last = ext;
AFFS_I(inode)->i_ext_bh = bh;
atomic_inc(&bh->b_count);
return bh;
err_bread:
affs_brelse(bh);
return ERR_PTR(-EIO);
}
static int
affs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh_result, int create)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *ext_bh;
u32 ext;
pr_debug("AFFS: get_block(%u, %lu)\n", (u32)inode->i_ino, (unsigned long)block);
if (block > (sector_t)0x7fffffffUL)
BUG();
if (block >= AFFS_I(inode)->i_blkcnt) {
if (block > AFFS_I(inode)->i_blkcnt || !create)
goto err_big;
} else
create = 0;
//lock cache
affs_lock_ext(inode);
ext = (u32)block / AFFS_SB(sb)->s_hashsize;
block -= ext * AFFS_SB(sb)->s_hashsize;
ext_bh = affs_get_extblock(inode, ext);
if (IS_ERR(ext_bh))
goto err_ext;
map_bh(bh_result, sb, (sector_t)be32_to_cpu(AFFS_BLOCK(sb, ext_bh, block)));
if (create) {
u32 blocknr = affs_alloc_block(inode, ext_bh->b_blocknr);
if (!blocknr)
goto err_alloc;
set_buffer_new(bh_result);
AFFS_I(inode)->mmu_private += AFFS_SB(sb)->s_data_blksize;
AFFS_I(inode)->i_blkcnt++;
/* store new block */
if (bh_result->b_blocknr)
affs_warning(sb, "get_block", "block already set (%x)", bh_result->b_blocknr);
AFFS_BLOCK(sb, ext_bh, block) = cpu_to_be32(blocknr);
AFFS_HEAD(ext_bh)->block_count = cpu_to_be32(block + 1);
affs_adjust_checksum(ext_bh, blocknr - bh_result->b_blocknr + 1);
bh_result->b_blocknr = blocknr;
if (!block) {
/* insert first block into header block */
u32 tmp = be32_to_cpu(AFFS_HEAD(ext_bh)->first_data);
if (tmp)
affs_warning(sb, "get_block", "first block already set (%d)", tmp);
AFFS_HEAD(ext_bh)->first_data = cpu_to_be32(blocknr);
affs_adjust_checksum(ext_bh, blocknr - tmp);
}
}
affs_brelse(ext_bh);
//unlock cache
affs_unlock_ext(inode);
return 0;
err_big:
affs_error(inode->i_sb,"get_block","strange block request %d", block);
return -EIO;
err_ext:
// unlock cache
affs_unlock_ext(inode);
return PTR_ERR(ext_bh);
err_alloc:
brelse(ext_bh);
clear_buffer_mapped(bh_result);
bh_result->b_bdev = NULL;
// unlock cache
affs_unlock_ext(inode);
return -ENOSPC;
}
static int affs_writepage(struct page *page, struct writeback_control *wbc)
{
return block_write_full_page(page, affs_get_block, wbc);
}
static int affs_readpage(struct file *file, struct page *page)
{
return block_read_full_page(page, affs_get_block);
}
static int affs_prepare_write(struct file *file, struct page *page, unsigned from, unsigned to)
{
return cont_prepare_write(page, from, to, affs_get_block,
&AFFS_I(page->mapping->host)->mmu_private);
}
static sector_t _affs_bmap(struct address_space *mapping, sector_t block)
{
return generic_block_bmap(mapping,block,affs_get_block);
}
struct address_space_operations affs_aops = {
.readpage = affs_readpage,
.writepage = affs_writepage,
.sync_page = block_sync_page,
.prepare_write = affs_prepare_write,
.commit_write = generic_commit_write,
.bmap = _affs_bmap
};
static inline struct buffer_head *
affs_bread_ino(struct inode *inode, int block, int create)
{
struct buffer_head *bh, tmp_bh;
int err;
tmp_bh.b_state = 0;
err = affs_get_block(inode, block, &tmp_bh, create);
if (!err) {
bh = affs_bread(inode->i_sb, tmp_bh.b_blocknr);
if (bh) {
bh->b_state |= tmp_bh.b_state;
return bh;
}
err = -EIO;
}
return ERR_PTR(err);
}
static inline struct buffer_head *
affs_getzeroblk_ino(struct inode *inode, int block)
{
struct buffer_head *bh, tmp_bh;
int err;
tmp_bh.b_state = 0;
err = affs_get_block(inode, block, &tmp_bh, 1);
if (!err) {
bh = affs_getzeroblk(inode->i_sb, tmp_bh.b_blocknr);
if (bh) {
bh->b_state |= tmp_bh.b_state;
return bh;
}
err = -EIO;
}
return ERR_PTR(err);
}
static inline struct buffer_head *
affs_getemptyblk_ino(struct inode *inode, int block)
{
struct buffer_head *bh, tmp_bh;
int err;
tmp_bh.b_state = 0;
err = affs_get_block(inode, block, &tmp_bh, 1);
if (!err) {
bh = affs_getemptyblk(inode->i_sb, tmp_bh.b_blocknr);
if (bh) {
bh->b_state |= tmp_bh.b_state;
return bh;
}
err = -EIO;
}
return ERR_PTR(err);
}
static ssize_t
affs_file_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
ssize_t retval;
retval = generic_file_write (file, buf, count, ppos);
if (retval >0) {
struct inode *inode = file->f_dentry->d_inode;
inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
mark_inode_dirty(inode);
}
return retval;
}
static int
affs_do_readpage_ofs(struct file *file, struct page *page, unsigned from, unsigned to)
{
struct inode *inode = page->mapping->host;
struct super_block *sb = inode->i_sb;
struct buffer_head *bh;
char *data;
u32 bidx, boff, bsize;
u32 tmp;
pr_debug("AFFS: read_page(%u, %ld, %d, %d)\n", (u32)inode->i_ino, page->index, from, to);
if (from > to || to > PAGE_CACHE_SIZE)
BUG();
kmap(page);
data = page_address(page);
bsize = AFFS_SB(sb)->s_data_blksize;
tmp = (page->index << PAGE_CACHE_SHIFT) + from;
bidx = tmp / bsize;
boff = tmp % bsize;
while (from < to) {
bh = affs_bread_ino(inode, bidx, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
tmp = min(bsize - boff, to - from);
if (from + tmp > to || tmp > bsize)
BUG();
memcpy(data + from, AFFS_DATA(bh) + boff, tmp);
affs_brelse(bh);
bidx++;
from += tmp;
boff = 0;
}
flush_dcache_page(page);
kunmap(page);
return 0;
}
static int
affs_extent_file_ofs(struct inode *inode, u32 newsize)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *bh, *prev_bh;
u32 bidx, boff;
u32 size, bsize;
u32 tmp;
pr_debug("AFFS: extent_file(%u, %d)\n", (u32)inode->i_ino, newsize);
bsize = AFFS_SB(sb)->s_data_blksize;
bh = NULL;
size = AFFS_I(inode)->mmu_private;
bidx = size / bsize;
boff = size % bsize;
if (boff) {
bh = affs_bread_ino(inode, bidx, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
tmp = min(bsize - boff, newsize - size);
if (boff + tmp > bsize || tmp > bsize)
BUG();
memset(AFFS_DATA(bh) + boff, 0, tmp);
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(be32_to_cpu(AFFS_DATA_HEAD(bh)->size) + tmp);
affs_fix_checksum(sb, bh);
mark_buffer_dirty_inode(bh, inode);
size += tmp;
bidx++;
} else if (bidx) {
bh = affs_bread_ino(inode, bidx - 1, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
}
while (size < newsize) {
prev_bh = bh;
bh = affs_getzeroblk_ino(inode, bidx);
if (IS_ERR(bh))
goto out;
tmp = min(bsize, newsize - size);
if (tmp > bsize)
BUG();
AFFS_DATA_HEAD(bh)->ptype = cpu_to_be32(T_DATA);
AFFS_DATA_HEAD(bh)->key = cpu_to_be32(inode->i_ino);
AFFS_DATA_HEAD(bh)->sequence = cpu_to_be32(bidx);
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(tmp);
affs_fix_checksum(sb, bh);
bh->b_state &= ~(1UL << BH_New);
mark_buffer_dirty_inode(bh, inode);
if (prev_bh) {
u32 tmp = be32_to_cpu(AFFS_DATA_HEAD(prev_bh)->next);
if (tmp)
affs_warning(sb, "extent_file_ofs", "next block already set for %d (%d)", bidx, tmp);
AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp);
mark_buffer_dirty_inode(prev_bh, inode);
affs_brelse(prev_bh);
}
size += bsize;
bidx++;
}
affs_brelse(bh);
inode->i_size = AFFS_I(inode)->mmu_private = newsize;
return 0;
out:
inode->i_size = AFFS_I(inode)->mmu_private = newsize;
return PTR_ERR(bh);
}
static int
affs_readpage_ofs(struct file *file, struct page *page)
{
struct inode *inode = page->mapping->host;
u32 to;
int err;
pr_debug("AFFS: read_page(%u, %ld)\n", (u32)inode->i_ino, page->index);
to = PAGE_CACHE_SIZE;
if (((page->index + 1) << PAGE_CACHE_SHIFT) > inode->i_size) {
to = inode->i_size & ~PAGE_CACHE_MASK;
memset(page_address(page) + to, 0, PAGE_CACHE_SIZE - to);
}
err = affs_do_readpage_ofs(file, page, 0, to);
if (!err)
SetPageUptodate(page);
unlock_page(page);
return err;
}
static int affs_prepare_write_ofs(struct file *file, struct page *page, unsigned from, unsigned to)
{
struct inode *inode = page->mapping->host;
u32 size, offset;
u32 tmp;
int err = 0;
pr_debug("AFFS: prepare_write(%u, %ld, %d, %d)\n", (u32)inode->i_ino, page->index, from, to);
offset = page->index << PAGE_CACHE_SHIFT;
if (offset + from > AFFS_I(inode)->mmu_private) {
err = affs_extent_file_ofs(inode, offset + from);
if (err)
return err;
}
size = inode->i_size;
if (PageUptodate(page))
return 0;
if (from) {
err = affs_do_readpage_ofs(file, page, 0, from);
if (err)
return err;
}
if (to < PAGE_CACHE_SIZE) {
char *kaddr = kmap_atomic(page, KM_USER0);
memset(kaddr + to, 0, PAGE_CACHE_SIZE - to);
flush_dcache_page(page);
kunmap_atomic(kaddr, KM_USER0);
if (size > offset + to) {
if (size < offset + PAGE_CACHE_SIZE)
tmp = size & ~PAGE_CACHE_MASK;
else
tmp = PAGE_CACHE_SIZE;
err = affs_do_readpage_ofs(file, page, to, tmp);
}
}
return err;
}
static int affs_commit_write_ofs(struct file *file, struct page *page, unsigned from, unsigned to)
{
struct inode *inode = page->mapping->host;
struct super_block *sb = inode->i_sb;
struct buffer_head *bh, *prev_bh;
char *data;
u32 bidx, boff, bsize;
u32 tmp;
int written;
pr_debug("AFFS: commit_write(%u, %ld, %d, %d)\n", (u32)inode->i_ino, page->index, from, to);
bsize = AFFS_SB(sb)->s_data_blksize;
data = page_address(page);
bh = NULL;
written = 0;
tmp = (page->index << PAGE_CACHE_SHIFT) + from;
bidx = tmp / bsize;
boff = tmp % bsize;
if (boff) {
bh = affs_bread_ino(inode, bidx, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
tmp = min(bsize - boff, to - from);
if (boff + tmp > bsize || tmp > bsize)
BUG();
memcpy(AFFS_DATA(bh) + boff, data + from, tmp);
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(be32_to_cpu(AFFS_DATA_HEAD(bh)->size) + tmp);
affs_fix_checksum(sb, bh);
mark_buffer_dirty_inode(bh, inode);
written += tmp;
from += tmp;
bidx++;
} else if (bidx) {
bh = affs_bread_ino(inode, bidx - 1, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
}
while (from + bsize <= to) {
prev_bh = bh;
bh = affs_getemptyblk_ino(inode, bidx);
if (IS_ERR(bh))
goto out;
memcpy(AFFS_DATA(bh), data + from, bsize);
if (buffer_new(bh)) {
AFFS_DATA_HEAD(bh)->ptype = cpu_to_be32(T_DATA);
AFFS_DATA_HEAD(bh)->key = cpu_to_be32(inode->i_ino);
AFFS_DATA_HEAD(bh)->sequence = cpu_to_be32(bidx);
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(bsize);
AFFS_DATA_HEAD(bh)->next = 0;
bh->b_state &= ~(1UL << BH_New);
if (prev_bh) {
u32 tmp = be32_to_cpu(AFFS_DATA_HEAD(prev_bh)->next);
if (tmp)
affs_warning(sb, "commit_write_ofs", "next block already set for %d (%d)", bidx, tmp);
AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp);
mark_buffer_dirty_inode(prev_bh, inode);
}
}
affs_brelse(prev_bh);
affs_fix_checksum(sb, bh);
mark_buffer_dirty_inode(bh, inode);
written += bsize;
from += bsize;
bidx++;
}
if (from < to) {
prev_bh = bh;
bh = affs_bread_ino(inode, bidx, 1);
if (IS_ERR(bh))
goto out;
tmp = min(bsize, to - from);
if (tmp > bsize)
BUG();
memcpy(AFFS_DATA(bh), data + from, tmp);
if (buffer_new(bh)) {
AFFS_DATA_HEAD(bh)->ptype = cpu_to_be32(T_DATA);
AFFS_DATA_HEAD(bh)->key = cpu_to_be32(inode->i_ino);
AFFS_DATA_HEAD(bh)->sequence = cpu_to_be32(bidx);
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(tmp);
AFFS_DATA_HEAD(bh)->next = 0;
bh->b_state &= ~(1UL << BH_New);
if (prev_bh) {
u32 tmp = be32_to_cpu(AFFS_DATA_HEAD(prev_bh)->next);
if (tmp)
affs_warning(sb, "commit_write_ofs", "next block already set for %d (%d)", bidx, tmp);
AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp);
mark_buffer_dirty_inode(prev_bh, inode);
}
} else if (be32_to_cpu(AFFS_DATA_HEAD(bh)->size) < tmp)
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(tmp);
affs_brelse(prev_bh);
affs_fix_checksum(sb, bh);
mark_buffer_dirty_inode(bh, inode);
written += tmp;
from += tmp;
bidx++;
}
SetPageUptodate(page);
done:
affs_brelse(bh);
tmp = (page->index << PAGE_CACHE_SHIFT) + from;
if (tmp > inode->i_size)
inode->i_size = AFFS_I(inode)->mmu_private = tmp;
return written;
out:
bh = prev_bh;
if (!written)
written = PTR_ERR(bh);
goto done;
}
struct address_space_operations affs_aops_ofs = {
.readpage = affs_readpage_ofs,
//.writepage = affs_writepage_ofs,
//.sync_page = affs_sync_page_ofs,
.prepare_write = affs_prepare_write_ofs,
.commit_write = affs_commit_write_ofs
};
/* Free any preallocated blocks. */
void
affs_free_prealloc(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
pr_debug("AFFS: free_prealloc(ino=%lu)\n", inode->i_ino);
while (AFFS_I(inode)->i_pa_cnt) {
AFFS_I(inode)->i_pa_cnt--;
affs_free_block(sb, ++AFFS_I(inode)->i_lastalloc);
}
}
/* Truncate (or enlarge) a file to the requested size. */
void
affs_truncate(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
u32 ext, ext_key;
u32 last_blk, blkcnt, blk;
u32 size;
struct buffer_head *ext_bh;
int i;
pr_debug("AFFS: truncate(inode=%d, oldsize=%u, newsize=%u)\n",
(u32)inode->i_ino, (u32)AFFS_I(inode)->mmu_private, (u32)inode->i_size);
last_blk = 0;
ext = 0;
if (inode->i_size) {
last_blk = ((u32)inode->i_size - 1) / AFFS_SB(sb)->s_data_blksize;
ext = last_blk / AFFS_SB(sb)->s_hashsize;
}
if (inode->i_size > AFFS_I(inode)->mmu_private) {
struct address_space *mapping = inode->i_mapping;
struct page *page;
u32 size = inode->i_size - 1;
int res;
page = grab_cache_page(mapping, size >> PAGE_CACHE_SHIFT);
if (!page)
return;
size = (size & (PAGE_CACHE_SIZE - 1)) + 1;
res = mapping->a_ops->prepare_write(NULL, page, size, size);
if (!res)
res = mapping->a_ops->commit_write(NULL, page, size, size);
unlock_page(page);
page_cache_release(page);
mark_inode_dirty(inode);
return;
} else if (inode->i_size == AFFS_I(inode)->mmu_private)
return;
// lock cache
ext_bh = affs_get_extblock(inode, ext);
if (IS_ERR(ext_bh)) {
affs_warning(sb, "truncate", "unexpected read error for ext block %u (%d)",
ext, PTR_ERR(ext_bh));
return;
}
if (AFFS_I(inode)->i_lc) {
/* clear linear cache */
i = (ext + 1) >> AFFS_I(inode)->i_lc_shift;
if (AFFS_I(inode)->i_lc_size > i) {
AFFS_I(inode)->i_lc_size = i;
for (; i < AFFS_LC_SIZE; i++)
AFFS_I(inode)->i_lc[i] = 0;
}
/* clear associative cache */
for (i = 0; i < AFFS_AC_SIZE; i++)
if (AFFS_I(inode)->i_ac[i].ext >= ext)
AFFS_I(inode)->i_ac[i].ext = 0;
}
ext_key = be32_to_cpu(AFFS_TAIL(sb, ext_bh)->extension);
blkcnt = AFFS_I(inode)->i_blkcnt;
i = 0;
blk = last_blk;
if (inode->i_size) {
i = last_blk % AFFS_SB(sb)->s_hashsize + 1;
blk++;
} else
AFFS_HEAD(ext_bh)->first_data = 0;
size = AFFS_SB(sb)->s_hashsize;
if (size > blkcnt - blk + i)
size = blkcnt - blk + i;
for (; i < size; i++, blk++) {
affs_free_block(sb, be32_to_cpu(AFFS_BLOCK(sb, ext_bh, i)));
AFFS_BLOCK(sb, ext_bh, i) = 0;
}
AFFS_TAIL(sb, ext_bh)->extension = 0;
affs_fix_checksum(sb, ext_bh);
mark_buffer_dirty_inode(ext_bh, inode);
affs_brelse(ext_bh);
if (inode->i_size) {
AFFS_I(inode)->i_blkcnt = last_blk + 1;
AFFS_I(inode)->i_extcnt = ext + 1;
if (AFFS_SB(sb)->s_flags & SF_OFS) {
struct buffer_head *bh = affs_bread_ino(inode, last_blk, 0);
u32 tmp;
if (IS_ERR(ext_bh)) {
affs_warning(sb, "truncate", "unexpected read error for last block %u (%d)",
ext, PTR_ERR(ext_bh));
return;
}
tmp = be32_to_cpu(AFFS_DATA_HEAD(bh)->next);
AFFS_DATA_HEAD(bh)->next = 0;
affs_adjust_checksum(bh, -tmp);
affs_brelse(bh);
}
} else {
AFFS_I(inode)->i_blkcnt = 0;
AFFS_I(inode)->i_extcnt = 1;
}
AFFS_I(inode)->mmu_private = inode->i_size;
// unlock cache
while (ext_key) {
ext_bh = affs_bread(sb, ext_key);
size = AFFS_SB(sb)->s_hashsize;
if (size > blkcnt - blk)
size = blkcnt - blk;
for (i = 0; i < size; i++, blk++)
affs_free_block(sb, be32_to_cpu(AFFS_BLOCK(sb, ext_bh, i)));
affs_free_block(sb, ext_key);
ext_key = be32_to_cpu(AFFS_TAIL(sb, ext_bh)->extension);
affs_brelse(ext_bh);
}
affs_free_prealloc(inode);
}

411
fs/affs/inode.c Normal file
View File

@@ -0,0 +1,411 @@
/*
* linux/fs/affs/inode.c
*
* (c) 1996 Hans-Joachim Widmaier - Rewritten
*
* (C) 1993 Ray Burr - Modified for Amiga FFS filesystem.
*
* (C) 1992 Eric Youngdale Modified for ISO9660 filesystem.
*
* (C) 1991 Linus Torvalds - minix filesystem
*/
#include "affs.h"
extern struct inode_operations affs_symlink_inode_operations;
extern struct timezone sys_tz;
void
affs_read_inode(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct affs_sb_info *sbi = AFFS_SB(sb);
struct buffer_head *bh;
struct affs_head *head;
struct affs_tail *tail;
u32 block;
u32 size;
u32 prot;
u16 id;
pr_debug("AFFS: read_inode(%lu)\n",inode->i_ino);
block = inode->i_ino;
bh = affs_bread(sb, block);
if (!bh) {
affs_warning(sb, "read_inode", "Cannot read block %d", block);
goto bad_inode;
}
if (affs_checksum_block(sb, bh) || be32_to_cpu(AFFS_HEAD(bh)->ptype) != T_SHORT) {
affs_warning(sb,"read_inode",
"Checksum or type (ptype=%d) error on inode %d",
AFFS_HEAD(bh)->ptype, block);
goto bad_inode;
}
head = AFFS_HEAD(bh);
tail = AFFS_TAIL(sb, bh);
prot = be32_to_cpu(tail->protect);
inode->i_size = 0;
inode->i_nlink = 1;
inode->i_mode = 0;
AFFS_I(inode)->i_extcnt = 1;
AFFS_I(inode)->i_ext_last = ~1;
AFFS_I(inode)->i_protect = prot;
AFFS_I(inode)->i_opencnt = 0;
AFFS_I(inode)->i_blkcnt = 0;
AFFS_I(inode)->i_lc = NULL;
AFFS_I(inode)->i_lc_size = 0;
AFFS_I(inode)->i_lc_shift = 0;
AFFS_I(inode)->i_lc_mask = 0;
AFFS_I(inode)->i_ac = NULL;
AFFS_I(inode)->i_ext_bh = NULL;
AFFS_I(inode)->mmu_private = 0;
AFFS_I(inode)->i_lastalloc = 0;
AFFS_I(inode)->i_pa_cnt = 0;
if (sbi->s_flags & SF_SETMODE)
inode->i_mode = sbi->s_mode;
else
inode->i_mode = prot_to_mode(prot);
id = be16_to_cpu(tail->uid);
if (id == 0 || sbi->s_flags & SF_SETUID)
inode->i_uid = sbi->s_uid;
else if (id == 0xFFFF && sbi->s_flags & SF_MUFS)
inode->i_uid = 0;
else
inode->i_uid = id;
id = be16_to_cpu(tail->gid);
if (id == 0 || sbi->s_flags & SF_SETGID)
inode->i_gid = sbi->s_gid;
else if (id == 0xFFFF && sbi->s_flags & SF_MUFS)
inode->i_gid = 0;
else
inode->i_gid = id;
switch (be32_to_cpu(tail->stype)) {
case ST_ROOT:
inode->i_uid = sbi->s_uid;
inode->i_gid = sbi->s_gid;
/* fall through */
case ST_USERDIR:
if (be32_to_cpu(tail->stype) == ST_USERDIR ||
sbi->s_flags & SF_SETMODE) {
if (inode->i_mode & S_IRUSR)
inode->i_mode |= S_IXUSR;
if (inode->i_mode & S_IRGRP)
inode->i_mode |= S_IXGRP;
if (inode->i_mode & S_IROTH)
inode->i_mode |= S_IXOTH;
inode->i_mode |= S_IFDIR;
} else
inode->i_mode = S_IRUGO | S_IXUGO | S_IWUSR | S_IFDIR;
if (tail->link_chain)
inode->i_nlink = 2;
/* Maybe it should be controlled by mount parameter? */
//inode->i_mode |= S_ISVTX;
inode->i_op = &affs_dir_inode_operations;
inode->i_fop = &affs_dir_operations;
break;
case ST_LINKDIR:
#if 0
affs_warning(sb, "read_inode", "inode is LINKDIR");
goto bad_inode;
#else
inode->i_mode |= S_IFDIR;
inode->i_op = NULL;
inode->i_fop = NULL;
break;
#endif
case ST_LINKFILE:
affs_warning(sb, "read_inode", "inode is LINKFILE");
goto bad_inode;
case ST_FILE:
size = be32_to_cpu(tail->size);
inode->i_mode |= S_IFREG;
AFFS_I(inode)->mmu_private = inode->i_size = size;
if (inode->i_size) {
AFFS_I(inode)->i_blkcnt = (size - 1) /
sbi->s_data_blksize + 1;
AFFS_I(inode)->i_extcnt = (AFFS_I(inode)->i_blkcnt - 1) /
sbi->s_hashsize + 1;
}
if (tail->link_chain)
inode->i_nlink = 2;
inode->i_mapping->a_ops = (sbi->s_flags & SF_OFS) ? &affs_aops_ofs : &affs_aops;
inode->i_op = &affs_file_inode_operations;
inode->i_fop = &affs_file_operations;
break;
case ST_SOFTLINK:
inode->i_mode |= S_IFLNK;
inode->i_op = &affs_symlink_inode_operations;
inode->i_data.a_ops = &affs_symlink_aops;
break;
}
inode->i_mtime.tv_sec = inode->i_atime.tv_sec = inode->i_ctime.tv_sec
= (be32_to_cpu(tail->change.days) * (24 * 60 * 60) +
be32_to_cpu(tail->change.mins) * 60 +
be32_to_cpu(tail->change.ticks) / 50 +
((8 * 365 + 2) * 24 * 60 * 60)) +
sys_tz.tz_minuteswest * 60;
inode->i_mtime.tv_nsec = inode->i_ctime.tv_nsec = inode->i_atime.tv_nsec = 0;
affs_brelse(bh);
return;
bad_inode:
make_bad_inode(inode);
affs_brelse(bh);
return;
}
int
affs_write_inode(struct inode *inode, int unused)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *bh;
struct affs_tail *tail;
uid_t uid;
gid_t gid;
pr_debug("AFFS: write_inode(%lu)\n",inode->i_ino);
if (!inode->i_nlink)
// possibly free block
return 0;
bh = affs_bread(sb, inode->i_ino);
if (!bh) {
affs_error(sb,"write_inode","Cannot read block %lu",inode->i_ino);
return -EIO;
}
tail = AFFS_TAIL(sb, bh);
if (tail->stype == cpu_to_be32(ST_ROOT)) {
secs_to_datestamp(inode->i_mtime.tv_sec,&AFFS_ROOT_TAIL(sb, bh)->root_change);
} else {
tail->protect = cpu_to_be32(AFFS_I(inode)->i_protect);
tail->size = cpu_to_be32(inode->i_size);
secs_to_datestamp(inode->i_mtime.tv_sec,&tail->change);
if (!(inode->i_ino == AFFS_SB(sb)->s_root_block)) {
uid = inode->i_uid;
gid = inode->i_gid;
if (AFFS_SB(sb)->s_flags & SF_MUFS) {
if (inode->i_uid == 0 || inode->i_uid == 0xFFFF)
uid = inode->i_uid ^ ~0;
if (inode->i_gid == 0 || inode->i_gid == 0xFFFF)
gid = inode->i_gid ^ ~0;
}
if (!(AFFS_SB(sb)->s_flags & SF_SETUID))
tail->uid = cpu_to_be16(uid);
if (!(AFFS_SB(sb)->s_flags & SF_SETGID))
tail->gid = cpu_to_be16(gid);
}
}
affs_fix_checksum(sb, bh);
mark_buffer_dirty_inode(bh, inode);
affs_brelse(bh);
affs_free_prealloc(inode);
return 0;
}
int
affs_notify_change(struct dentry *dentry, struct iattr *attr)
{
struct inode *inode = dentry->d_inode;
int error;
pr_debug("AFFS: notify_change(%lu,0x%x)\n",inode->i_ino,attr->ia_valid);
error = inode_change_ok(inode,attr);
if (error)
goto out;
if (((attr->ia_valid & ATTR_UID) && (AFFS_SB(inode->i_sb)->s_flags & SF_SETUID)) ||
((attr->ia_valid & ATTR_GID) && (AFFS_SB(inode->i_sb)->s_flags & SF_SETGID)) ||
((attr->ia_valid & ATTR_MODE) &&
(AFFS_SB(inode->i_sb)->s_flags & (SF_SETMODE | SF_IMMUTABLE)))) {
if (!(AFFS_SB(inode->i_sb)->s_flags & SF_QUIET))
error = -EPERM;
goto out;
}
error = inode_setattr(inode, attr);
if (!error && (attr->ia_valid & ATTR_MODE))
mode_to_prot(inode);
out:
return error;
}
void
affs_put_inode(struct inode *inode)
{
pr_debug("AFFS: put_inode(ino=%lu, nlink=%u)\n", inode->i_ino, inode->i_nlink);
affs_free_prealloc(inode);
if (atomic_read(&inode->i_count) == 1) {
down(&inode->i_sem);
if (inode->i_size != AFFS_I(inode)->mmu_private)
affs_truncate(inode);
up(&inode->i_sem);
}
}
void
affs_delete_inode(struct inode *inode)
{
pr_debug("AFFS: delete_inode(ino=%lu, nlink=%u)\n", inode->i_ino, inode->i_nlink);
inode->i_size = 0;
if (S_ISREG(inode->i_mode))
affs_truncate(inode);
clear_inode(inode);
affs_free_block(inode->i_sb, inode->i_ino);
}
void
affs_clear_inode(struct inode *inode)
{
unsigned long cache_page = (unsigned long) AFFS_I(inode)->i_lc;
pr_debug("AFFS: clear_inode(ino=%lu, nlink=%u)\n", inode->i_ino, inode->i_nlink);
if (cache_page) {
pr_debug("AFFS: freeing ext cache\n");
AFFS_I(inode)->i_lc = NULL;
AFFS_I(inode)->i_ac = NULL;
free_page(cache_page);
}
affs_brelse(AFFS_I(inode)->i_ext_bh);
AFFS_I(inode)->i_ext_last = ~1;
AFFS_I(inode)->i_ext_bh = NULL;
}
struct inode *
affs_new_inode(struct inode *dir)
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
u32 block;
struct buffer_head *bh;
if (!(inode = new_inode(sb)))
goto err_inode;
if (!(block = affs_alloc_block(dir, dir->i_ino)))
goto err_block;
bh = affs_getzeroblk(sb, block);
if (!bh)
goto err_bh;
mark_buffer_dirty_inode(bh, inode);
affs_brelse(bh);
inode->i_uid = current->fsuid;
inode->i_gid = current->fsgid;
inode->i_ino = block;
inode->i_nlink = 1;
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC;
AFFS_I(inode)->i_opencnt = 0;
AFFS_I(inode)->i_blkcnt = 0;
AFFS_I(inode)->i_lc = NULL;
AFFS_I(inode)->i_lc_size = 0;
AFFS_I(inode)->i_lc_shift = 0;
AFFS_I(inode)->i_lc_mask = 0;
AFFS_I(inode)->i_ac = NULL;
AFFS_I(inode)->i_ext_bh = NULL;
AFFS_I(inode)->mmu_private = 0;
AFFS_I(inode)->i_protect = 0;
AFFS_I(inode)->i_lastalloc = 0;
AFFS_I(inode)->i_pa_cnt = 0;
AFFS_I(inode)->i_extcnt = 1;
AFFS_I(inode)->i_ext_last = ~1;
insert_inode_hash(inode);
return inode;
err_bh:
affs_free_block(sb, block);
err_block:
iput(inode);
err_inode:
return NULL;
}
/*
* Add an entry to a directory. Create the header block
* and insert it into the hash table.
*/
int
affs_add_entry(struct inode *dir, struct inode *inode, struct dentry *dentry, s32 type)
{
struct super_block *sb = dir->i_sb;
struct buffer_head *inode_bh = NULL;
struct buffer_head *bh = NULL;
u32 block = 0;
int retval;
pr_debug("AFFS: add_entry(dir=%u, inode=%u, \"%*s\", type=%d)\n", (u32)dir->i_ino,
(u32)inode->i_ino, (int)dentry->d_name.len, dentry->d_name.name, type);
retval = -EIO;
bh = affs_bread(sb, inode->i_ino);
if (!bh)
goto done;
affs_lock_link(inode);
switch (type) {
case ST_LINKFILE:
case ST_LINKDIR:
inode_bh = bh;
retval = -ENOSPC;
block = affs_alloc_block(dir, dir->i_ino);
if (!block)
goto err;
retval = -EIO;
bh = affs_getzeroblk(sb, block);
if (!bh)
goto err;
break;
default:
break;
}
AFFS_HEAD(bh)->ptype = cpu_to_be32(T_SHORT);
AFFS_HEAD(bh)->key = cpu_to_be32(bh->b_blocknr);
affs_copy_name(AFFS_TAIL(sb, bh)->name, dentry);
AFFS_TAIL(sb, bh)->stype = cpu_to_be32(type);
AFFS_TAIL(sb, bh)->parent = cpu_to_be32(dir->i_ino);
if (inode_bh) {
__be32 chain;
chain = AFFS_TAIL(sb, inode_bh)->link_chain;
AFFS_TAIL(sb, bh)->original = cpu_to_be32(inode->i_ino);
AFFS_TAIL(sb, bh)->link_chain = chain;
AFFS_TAIL(sb, inode_bh)->link_chain = cpu_to_be32(block);
affs_adjust_checksum(inode_bh, block - be32_to_cpu(chain));
mark_buffer_dirty_inode(inode_bh, inode);
inode->i_nlink = 2;
atomic_inc(&inode->i_count);
}
affs_fix_checksum(sb, bh);
mark_buffer_dirty_inode(bh, inode);
dentry->d_fsdata = (void *)(long)bh->b_blocknr;
affs_lock_dir(dir);
retval = affs_insert_hash(dir, bh);
mark_buffer_dirty_inode(bh, inode);
affs_unlock_dir(dir);
affs_unlock_link(inode);
d_instantiate(dentry, inode);
done:
affs_brelse(inode_bh);
affs_brelse(bh);
return retval;
err:
if (block)
affs_free_block(sb, block);
affs_unlock_link(inode);
goto done;
}

443
fs/affs/namei.c Normal file
View File

@@ -0,0 +1,443 @@
/*
* linux/fs/affs/namei.c
*
* (c) 1996 Hans-Joachim Widmaier - Rewritten
*
* (C) 1993 Ray Burr - Modified for Amiga FFS filesystem.
*
* (C) 1991 Linus Torvalds - minix filesystem
*/
#include "affs.h"
typedef int (*toupper_t)(int);
static int affs_toupper(int ch);
static int affs_hash_dentry(struct dentry *, struct qstr *);
static int affs_compare_dentry(struct dentry *, struct qstr *, struct qstr *);
static int affs_intl_toupper(int ch);
static int affs_intl_hash_dentry(struct dentry *, struct qstr *);
static int affs_intl_compare_dentry(struct dentry *, struct qstr *, struct qstr *);
struct dentry_operations affs_dentry_operations = {
.d_hash = affs_hash_dentry,
.d_compare = affs_compare_dentry,
};
static struct dentry_operations affs_intl_dentry_operations = {
.d_hash = affs_intl_hash_dentry,
.d_compare = affs_intl_compare_dentry,
};
/* Simple toupper() for DOS\1 */
static int
affs_toupper(int ch)
{
return ch >= 'a' && ch <= 'z' ? ch -= ('a' - 'A') : ch;
}
/* International toupper() for DOS\3 ("international") */
static int
affs_intl_toupper(int ch)
{
return (ch >= 'a' && ch <= 'z') || (ch >= 0xE0
&& ch <= 0xFE && ch != 0xF7) ?
ch - ('a' - 'A') : ch;
}
static inline toupper_t
affs_get_toupper(struct super_block *sb)
{
return AFFS_SB(sb)->s_flags & SF_INTL ? affs_intl_toupper : affs_toupper;
}
/*
* Note: the dentry argument is the parent dentry.
*/
static inline int
__affs_hash_dentry(struct dentry *dentry, struct qstr *qstr, toupper_t toupper)
{
const u8 *name = qstr->name;
unsigned long hash;
int i;
i = affs_check_name(qstr->name,qstr->len);
if (i)
return i;
hash = init_name_hash();
i = min(qstr->len, 30u);
for (; i > 0; name++, i--)
hash = partial_name_hash(toupper(*name), hash);
qstr->hash = end_name_hash(hash);
return 0;
}
static int
affs_hash_dentry(struct dentry *dentry, struct qstr *qstr)
{
return __affs_hash_dentry(dentry, qstr, affs_toupper);
}
static int
affs_intl_hash_dentry(struct dentry *dentry, struct qstr *qstr)
{
return __affs_hash_dentry(dentry, qstr, affs_intl_toupper);
}
static inline int
__affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b, toupper_t toupper)
{
const u8 *aname = a->name;
const u8 *bname = b->name;
int len;
/* 'a' is the qstr of an already existing dentry, so the name
* must be valid. 'b' must be validated first.
*/
if (affs_check_name(b->name,b->len))
return 1;
/* If the names are longer than the allowed 30 chars,
* the excess is ignored, so their length may differ.
*/
len = a->len;
if (len >= 30) {
if (b->len < 30)
return 1;
len = 30;
} else if (len != b->len)
return 1;
for (; len > 0; len--)
if (toupper(*aname++) != toupper(*bname++))
return 1;
return 0;
}
static int
affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
{
return __affs_compare_dentry(dentry, a, b, affs_toupper);
}
static int
affs_intl_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
{
return __affs_compare_dentry(dentry, a, b, affs_intl_toupper);
}
/*
* NOTE! unlike strncmp, affs_match returns 1 for success, 0 for failure.
*/
static inline int
affs_match(struct dentry *dentry, const u8 *name2, toupper_t toupper)
{
const u8 *name = dentry->d_name.name;
int len = dentry->d_name.len;
if (len >= 30) {
if (*name2 < 30)
return 0;
len = 30;
} else if (len != *name2)
return 0;
for (name2++; len > 0; len--)
if (toupper(*name++) != toupper(*name2++))
return 0;
return 1;
}
int
affs_hash_name(struct super_block *sb, const u8 *name, unsigned int len)
{
toupper_t toupper = affs_get_toupper(sb);
int hash;
hash = len = min(len, 30u);
for (; len > 0; len--)
hash = (hash * 13 + toupper(*name++)) & 0x7ff;
return hash % AFFS_SB(sb)->s_hashsize;
}
static struct buffer_head *
affs_find_entry(struct inode *dir, struct dentry *dentry)
{
struct super_block *sb = dir->i_sb;
struct buffer_head *bh;
toupper_t toupper = affs_get_toupper(sb);
u32 key;
pr_debug("AFFS: find_entry(\"%.*s\")\n", (int)dentry->d_name.len, dentry->d_name.name);
bh = affs_bread(sb, dir->i_ino);
if (!bh)
return ERR_PTR(-EIO);
key = be32_to_cpu(AFFS_HEAD(bh)->table[affs_hash_name(sb, dentry->d_name.name, dentry->d_name.len)]);
for (;;) {
affs_brelse(bh);
if (key == 0)
return NULL;
bh = affs_bread(sb, key);
if (!bh)
return ERR_PTR(-EIO);
if (affs_match(dentry, AFFS_TAIL(sb, bh)->name, toupper))
return bh;
key = be32_to_cpu(AFFS_TAIL(sb, bh)->hash_chain);
}
}
struct dentry *
affs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
struct super_block *sb = dir->i_sb;
struct buffer_head *bh;
struct inode *inode = NULL;
pr_debug("AFFS: lookup(\"%.*s\")\n",(int)dentry->d_name.len,dentry->d_name.name);
affs_lock_dir(dir);
bh = affs_find_entry(dir, dentry);
affs_unlock_dir(dir);
if (IS_ERR(bh)) {
return ERR_PTR(PTR_ERR(bh));
}
if (bh) {
u32 ino = bh->b_blocknr;
/* store the real header ino in d_fsdata for faster lookups */
dentry->d_fsdata = (void *)(long)ino;
switch (be32_to_cpu(AFFS_TAIL(sb, bh)->stype)) {
//link to dirs disabled
//case ST_LINKDIR:
case ST_LINKFILE:
ino = be32_to_cpu(AFFS_TAIL(sb, bh)->original);
}
affs_brelse(bh);
inode = iget(sb, ino);
if (!inode) {
return ERR_PTR(-EACCES);
}
}
dentry->d_op = AFFS_SB(sb)->s_flags & SF_INTL ? &affs_intl_dentry_operations : &affs_dentry_operations;
d_add(dentry, inode);
return NULL;
}
int
affs_unlink(struct inode *dir, struct dentry *dentry)
{
pr_debug("AFFS: unlink(dir=%d, \"%.*s\")\n", (u32)dir->i_ino,
(int)dentry->d_name.len, dentry->d_name.name);
return affs_remove_header(dentry);
}
int
affs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd)
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
int error;
pr_debug("AFFS: create(%lu,\"%.*s\",0%o)\n",dir->i_ino,(int)dentry->d_name.len,
dentry->d_name.name,mode);
inode = affs_new_inode(dir);
if (!inode)
return -ENOSPC;
inode->i_mode = mode;
mode_to_prot(inode);
mark_inode_dirty(inode);
inode->i_op = &affs_file_inode_operations;
inode->i_fop = &affs_file_operations;
inode->i_mapping->a_ops = (AFFS_SB(sb)->s_flags & SF_OFS) ? &affs_aops_ofs : &affs_aops;
error = affs_add_entry(dir, inode, dentry, ST_FILE);
if (error) {
inode->i_nlink = 0;
iput(inode);
return error;
}
return 0;
}
int
affs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
struct inode *inode;
int error;
pr_debug("AFFS: mkdir(%lu,\"%.*s\",0%o)\n",dir->i_ino,
(int)dentry->d_name.len,dentry->d_name.name,mode);
inode = affs_new_inode(dir);
if (!inode)
return -ENOSPC;
inode->i_mode = S_IFDIR | mode;
mode_to_prot(inode);
inode->i_op = &affs_dir_inode_operations;
inode->i_fop = &affs_dir_operations;
error = affs_add_entry(dir, inode, dentry, ST_USERDIR);
if (error) {
inode->i_nlink = 0;
mark_inode_dirty(inode);
iput(inode);
return error;
}
return 0;
}
int
affs_rmdir(struct inode *dir, struct dentry *dentry)
{
pr_debug("AFFS: rmdir(dir=%u, \"%.*s\")\n", (u32)dir->i_ino,
(int)dentry->d_name.len, dentry->d_name.name);
return affs_remove_header(dentry);
}
int
affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
{
struct super_block *sb = dir->i_sb;
struct buffer_head *bh;
struct inode *inode;
char *p;
int i, maxlen, error;
char c, lc;
pr_debug("AFFS: symlink(%lu,\"%.*s\" -> \"%s\")\n",dir->i_ino,
(int)dentry->d_name.len,dentry->d_name.name,symname);
maxlen = AFFS_SB(sb)->s_hashsize * sizeof(u32) - 1;
inode = affs_new_inode(dir);
if (!inode)
return -ENOSPC;
inode->i_op = &affs_symlink_inode_operations;
inode->i_data.a_ops = &affs_symlink_aops;
inode->i_mode = S_IFLNK | 0777;
mode_to_prot(inode);
error = -EIO;
bh = affs_bread(sb, inode->i_ino);
if (!bh)
goto err;
i = 0;
p = (char *)AFFS_HEAD(bh)->table;
lc = '/';
if (*symname == '/') {
while (*symname == '/')
symname++;
while (AFFS_SB(sb)->s_volume[i]) /* Cannot overflow */
*p++ = AFFS_SB(sb)->s_volume[i++];
}
while (i < maxlen && (c = *symname++)) {
if (c == '.' && lc == '/' && *symname == '.' && symname[1] == '/') {
*p++ = '/';
i++;
symname += 2;
lc = '/';
} else if (c == '.' && lc == '/' && *symname == '/') {
symname++;
lc = '/';
} else {
*p++ = c;
lc = c;
i++;
}
if (lc == '/')
while (*symname == '/')
symname++;
}
*p = 0;
mark_buffer_dirty_inode(bh, inode);
affs_brelse(bh);
mark_inode_dirty(inode);
error = affs_add_entry(dir, inode, dentry, ST_SOFTLINK);
if (error)
goto err;
return 0;
err:
inode->i_nlink = 0;
mark_inode_dirty(inode);
iput(inode);
return error;
}
int
affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
{
struct inode *inode = old_dentry->d_inode;
pr_debug("AFFS: link(%u, %u, \"%.*s\")\n", (u32)inode->i_ino, (u32)dir->i_ino,
(int)dentry->d_name.len,dentry->d_name.name);
return affs_add_entry(dir, inode, dentry, ST_LINKFILE);
}
int
affs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
{
struct super_block *sb = old_dir->i_sb;
struct buffer_head *bh = NULL;
int retval;
pr_debug("AFFS: rename(old=%u,\"%*s\" to new=%u,\"%*s\")\n",
(u32)old_dir->i_ino, (int)old_dentry->d_name.len, old_dentry->d_name.name,
(u32)new_dir->i_ino, (int)new_dentry->d_name.len, new_dentry->d_name.name);
retval = affs_check_name(new_dentry->d_name.name,new_dentry->d_name.len);
if (retval)
return retval;
/* Unlink destination if it already exists */
if (new_dentry->d_inode) {
retval = affs_remove_header(new_dentry);
if (retval)
return retval;
}
retval = -EIO;
bh = affs_bread(sb, old_dentry->d_inode->i_ino);
if (!bh)
goto done;
/* Remove header from its parent directory. */
affs_lock_dir(old_dir);
retval = affs_remove_hash(old_dir, bh);
affs_unlock_dir(old_dir);
if (retval)
goto done;
/* And insert it into the new directory with the new name. */
affs_copy_name(AFFS_TAIL(sb, bh)->name, new_dentry);
affs_fix_checksum(sb, bh);
affs_lock_dir(new_dir);
retval = affs_insert_hash(new_dir, bh);
affs_unlock_dir(new_dir);
/* TODO: move it back to old_dir, if error? */
done:
mark_buffer_dirty_inode(bh, retval ? old_dir : new_dir);
affs_brelse(bh);
return retval;
}

569
fs/affs/super.c Normal file
View File

@@ -0,0 +1,569 @@
/*
* linux/fs/affs/inode.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
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/statfs.h>
#include <linux/parser.h>
#include "affs.h"
extern struct timezone sys_tz;
static int affs_statfs(struct super_block *sb, struct kstatfs *buf);
static int affs_remount (struct super_block *sb, int *flags, char *data);
static void
affs_put_super(struct super_block *sb)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
pr_debug("AFFS: put_super()\n");
if (!(sb->s_flags & MS_RDONLY)) {
AFFS_ROOT_TAIL(sb, sbi->s_root_bh)->bm_flag = cpu_to_be32(1);
secs_to_datestamp(get_seconds(),
&AFFS_ROOT_TAIL(sb, sbi->s_root_bh)->disk_change);
affs_fix_checksum(sb, sbi->s_root_bh);
mark_buffer_dirty(sbi->s_root_bh);
}
if (sbi->s_prefix)
kfree(sbi->s_prefix);
affs_free_bitmap(sb);
affs_brelse(sbi->s_root_bh);
kfree(sbi);
sb->s_fs_info = NULL;
return;
}
static void
affs_write_super(struct super_block *sb)
{
int clean = 2;
struct affs_sb_info *sbi = AFFS_SB(sb);
if (!(sb->s_flags & MS_RDONLY)) {
// if (sbi->s_bitmap[i].bm_bh) {
// if (buffer_dirty(sbi->s_bitmap[i].bm_bh)) {
// clean = 0;
AFFS_ROOT_TAIL(sb, sbi->s_root_bh)->bm_flag = cpu_to_be32(clean);
secs_to_datestamp(get_seconds(),
&AFFS_ROOT_TAIL(sb, sbi->s_root_bh)->disk_change);
affs_fix_checksum(sb, sbi->s_root_bh);
mark_buffer_dirty(sbi->s_root_bh);
sb->s_dirt = !clean; /* redo until bitmap synced */
} else
sb->s_dirt = 0;
pr_debug("AFFS: write_super() at %lu, clean=%d\n", get_seconds(), clean);
}
static kmem_cache_t * affs_inode_cachep;
static struct inode *affs_alloc_inode(struct super_block *sb)
{
struct affs_inode_info *ei;
ei = (struct affs_inode_info *)kmem_cache_alloc(affs_inode_cachep, SLAB_KERNEL);
if (!ei)
return NULL;
ei->vfs_inode.i_version = 1;
return &ei->vfs_inode;
}
static void affs_destroy_inode(struct inode *inode)
{
kmem_cache_free(affs_inode_cachep, AFFS_I(inode));
}
static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
{
struct affs_inode_info *ei = (struct affs_inode_info *) foo;
if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
SLAB_CTOR_CONSTRUCTOR) {
init_MUTEX(&ei->i_link_lock);
init_MUTEX(&ei->i_ext_lock);
inode_init_once(&ei->vfs_inode);
}
}
static int init_inodecache(void)
{
affs_inode_cachep = kmem_cache_create("affs_inode_cache",
sizeof(struct affs_inode_info),
0, SLAB_RECLAIM_ACCOUNT,
init_once, NULL);
if (affs_inode_cachep == NULL)
return -ENOMEM;
return 0;
}
static void destroy_inodecache(void)
{
if (kmem_cache_destroy(affs_inode_cachep))
printk(KERN_INFO "affs_inode_cache: not all structures were freed\n");
}
static struct super_operations affs_sops = {
.alloc_inode = affs_alloc_inode,
.destroy_inode = affs_destroy_inode,
.read_inode = affs_read_inode,
.write_inode = affs_write_inode,
.put_inode = affs_put_inode,
.delete_inode = affs_delete_inode,
.clear_inode = affs_clear_inode,
.put_super = affs_put_super,
.write_super = affs_write_super,
.statfs = affs_statfs,
.remount_fs = affs_remount,
};
enum {
Opt_bs, Opt_mode, Opt_mufs, Opt_prefix, Opt_protect,
Opt_reserved, Opt_root, Opt_setgid, Opt_setuid,
Opt_verbose, Opt_volume, Opt_ignore, Opt_err,
};
static match_table_t tokens = {
{Opt_bs, "bs=%u"},
{Opt_mode, "mode=%o"},
{Opt_mufs, "mufs"},
{Opt_prefix, "prefix=%s"},
{Opt_protect, "protect"},
{Opt_reserved, "reserved=%u"},
{Opt_root, "root=%u"},
{Opt_setgid, "setgid=%u"},
{Opt_setuid, "setuid=%u"},
{Opt_verbose, "verbose"},
{Opt_volume, "volume=%s"},
{Opt_ignore, "grpquota"},
{Opt_ignore, "noquota"},
{Opt_ignore, "quota"},
{Opt_ignore, "usrquota"},
{Opt_err, NULL},
};
static int
parse_options(char *options, uid_t *uid, gid_t *gid, int *mode, int *reserved, s32 *root,
int *blocksize, char **prefix, char *volume, unsigned long *mount_opts)
{
char *p;
substring_t args[MAX_OPT_ARGS];
/* Fill in defaults */
*uid = current->uid;
*gid = current->gid;
*reserved = 2;
*root = -1;
*blocksize = -1;
volume[0] = ':';
volume[1] = 0;
*mount_opts = 0;
if (!options)
return 1;
while ((p = strsep(&options, ",")) != NULL) {
int token, n, option;
if (!*p)
continue;
token = match_token(p, tokens, args);
switch (token) {
case Opt_bs:
if (match_int(&args[0], &n))
return -EINVAL;
if (n != 512 && n != 1024 && n != 2048
&& n != 4096) {
printk ("AFFS: Invalid blocksize (512, 1024, 2048, 4096 allowed)\n");
return 0;
}
*blocksize = n;
break;
case Opt_mode:
if (match_octal(&args[0], &option))
return 1;
*mode = option & 0777;
*mount_opts |= SF_SETMODE;
break;
case Opt_mufs:
*mount_opts |= SF_MUFS;
break;
case Opt_prefix:
if (*prefix) { /* Free any previous prefix */
kfree(*prefix);
*prefix = NULL;
}
*prefix = match_strdup(&args[0]);
if (!*prefix)
return 0;
*mount_opts |= SF_PREFIX;
break;
case Opt_protect:
*mount_opts |= SF_IMMUTABLE;
break;
case Opt_reserved:
if (match_int(&args[0], reserved))
return 1;
break;
case Opt_root:
if (match_int(&args[0], root))
return 1;
break;
case Opt_setgid:
if (match_int(&args[0], &option))
return 1;
*gid = option;
*mount_opts |= SF_SETGID;
break;
case Opt_setuid:
if (match_int(&args[0], &option))
return -EINVAL;
*uid = option;
*mount_opts |= SF_SETUID;
break;
case Opt_verbose:
*mount_opts |= SF_VERBOSE;
break;
case Opt_volume: {
char *vol = match_strdup(&args[0]);
strlcpy(volume, vol, 32);
kfree(vol);
break;
}
case Opt_ignore:
/* Silently ignore the quota options */
break;
default:
printk("AFFS: Unrecognized mount option \"%s\" "
"or missing value\n", p);
return 0;
}
}
return 1;
}
/* This function definitely needs to be split up. Some fine day I'll
* hopefully have the guts to do so. Until then: sorry for the mess.
*/
static int affs_fill_super(struct super_block *sb, void *data, int silent)
{
struct affs_sb_info *sbi;
struct buffer_head *root_bh = NULL;
struct buffer_head *boot_bh;
struct inode *root_inode = NULL;
s32 root_block;
int size, blocksize;
u32 chksum;
int num_bm;
int i, j;
s32 key;
uid_t uid;
gid_t gid;
int reserved;
unsigned long mount_flags;
int tmp_flags; /* fix remount prototype... */
pr_debug("AFFS: read_super(%s)\n",data ? (const char *)data : "no options");
sb->s_magic = AFFS_SUPER_MAGIC;
sb->s_op = &affs_sops;
sb->s_flags |= MS_NODIRATIME;
sbi = kmalloc(sizeof(struct affs_sb_info), GFP_KERNEL);
if (!sbi)
return -ENOMEM;
sb->s_fs_info = sbi;
memset(sbi, 0, sizeof(*sbi));
init_MUTEX(&sbi->s_bmlock);
if (!parse_options(data,&uid,&gid,&i,&reserved,&root_block,
&blocksize,&sbi->s_prefix,
sbi->s_volume, &mount_flags)) {
printk(KERN_ERR "AFFS: Error parsing options\n");
return -EINVAL;
}
/* N.B. after this point s_prefix must be released */
sbi->s_flags = mount_flags;
sbi->s_mode = i;
sbi->s_uid = uid;
sbi->s_gid = gid;
sbi->s_reserved= reserved;
/* Get the size of the device in 512-byte blocks.
* If we later see that the partition uses bigger
* blocks, we will have to change it.
*/
size = sb->s_bdev->bd_inode->i_size >> 9;
pr_debug("AFFS: initial blocksize=%d, #blocks=%d\n", 512, size);
affs_set_blocksize(sb, PAGE_SIZE);
/* Try to find root block. Its location depends on the block size. */
i = 512;
j = 4096;
if (blocksize > 0) {
i = j = blocksize;
size = size / (blocksize / 512);
}
for (blocksize = i, key = 0; blocksize <= j; blocksize <<= 1, size >>= 1) {
sbi->s_root_block = root_block;
if (root_block < 0)
sbi->s_root_block = (reserved + size - 1) / 2;
pr_debug("AFFS: setting blocksize to %d\n", blocksize);
affs_set_blocksize(sb, blocksize);
sbi->s_partition_size = size;
/* The root block location that was calculated above is not
* correct if the partition size is an odd number of 512-
* byte blocks, which will be rounded down to a number of
* 1024-byte blocks, and if there were an even number of
* reserved blocks. Ideally, all partition checkers should
* report the real number of blocks of the real blocksize,
* but since this just cannot be done, we have to try to
* find the root block anyways. In the above case, it is one
* block behind the calculated one. So we check this one, too.
*/
for (num_bm = 0; num_bm < 2; num_bm++) {
pr_debug("AFFS: Dev %s, trying root=%u, bs=%d, "
"size=%d, reserved=%d\n",
sb->s_id,
sbi->s_root_block + num_bm,
blocksize, size, reserved);
root_bh = affs_bread(sb, sbi->s_root_block + num_bm);
if (!root_bh)
continue;
if (!affs_checksum_block(sb, root_bh) &&
be32_to_cpu(AFFS_ROOT_HEAD(root_bh)->ptype) == T_SHORT &&
be32_to_cpu(AFFS_ROOT_TAIL(sb, root_bh)->stype) == ST_ROOT) {
sbi->s_hashsize = blocksize / 4 - 56;
sbi->s_root_block += num_bm;
key = 1;
goto got_root;
}
affs_brelse(root_bh);
root_bh = NULL;
}
}
if (!silent)
printk(KERN_ERR "AFFS: No valid root block on device %s\n",
sb->s_id);
goto out_error;
/* N.B. after this point bh must be released */
got_root:
root_block = sbi->s_root_block;
/* Find out which kind of FS we have */
boot_bh = sb_bread(sb, 0);
if (!boot_bh) {
printk(KERN_ERR "AFFS: Cannot read boot block\n");
goto out_error;
}
chksum = be32_to_cpu(*(__be32 *)boot_bh->b_data);
brelse(boot_bh);
/* Dircache filesystems are compatible with non-dircache ones
* when reading. As long as they aren't supported, writing is
* not recommended.
*/
if ((chksum == FS_DCFFS || chksum == MUFS_DCFFS || chksum == FS_DCOFS
|| chksum == MUFS_DCOFS) && !(sb->s_flags & MS_RDONLY)) {
printk(KERN_NOTICE "AFFS: Dircache FS - mounting %s read only\n",
sb->s_id);
sb->s_flags |= MS_RDONLY;
}
switch (chksum) {
case MUFS_FS:
case MUFS_INTLFFS:
case MUFS_DCFFS:
sbi->s_flags |= SF_MUFS;
/* fall thru */
case FS_INTLFFS:
case FS_DCFFS:
sbi->s_flags |= SF_INTL;
break;
case MUFS_FFS:
sbi->s_flags |= SF_MUFS;
break;
case FS_FFS:
break;
case MUFS_OFS:
sbi->s_flags |= SF_MUFS;
/* fall thru */
case FS_OFS:
sbi->s_flags |= SF_OFS;
sb->s_flags |= MS_NOEXEC;
break;
case MUFS_DCOFS:
case MUFS_INTLOFS:
sbi->s_flags |= SF_MUFS;
case FS_DCOFS:
case FS_INTLOFS:
sbi->s_flags |= SF_INTL | SF_OFS;
sb->s_flags |= MS_NOEXEC;
break;
default:
printk(KERN_ERR "AFFS: Unknown filesystem on device %s: %08X\n",
sb->s_id, chksum);
goto out_error;
}
if (mount_flags & SF_VERBOSE) {
chksum = cpu_to_be32(chksum);
printk(KERN_NOTICE "AFFS: Mounting volume \"%*s\": Type=%.3s\\%c, Blocksize=%d\n",
AFFS_ROOT_TAIL(sb, root_bh)->disk_name[0],
AFFS_ROOT_TAIL(sb, root_bh)->disk_name + 1,
(char *)&chksum,((char *)&chksum)[3] + '0',blocksize);
}
sb->s_flags |= MS_NODEV | MS_NOSUID;
sbi->s_data_blksize = sb->s_blocksize;
if (sbi->s_flags & SF_OFS)
sbi->s_data_blksize -= 24;
/* Keep super block in cache */
sbi->s_root_bh = root_bh;
/* N.B. after this point s_root_bh must be released */
tmp_flags = sb->s_flags;
if (affs_init_bitmap(sb, &tmp_flags))
goto out_error;
sb->s_flags = tmp_flags;
/* set up enough so that it can read an inode */
root_inode = iget(sb, root_block);
sb->s_root = d_alloc_root(root_inode);
if (!sb->s_root) {
printk(KERN_ERR "AFFS: Get root inode failed\n");
goto out_error;
}
sb->s_root->d_op = &affs_dentry_operations;
pr_debug("AFFS: s_flags=%lX\n",sb->s_flags);
return 0;
/*
* Begin the cascaded cleanup ...
*/
out_error:
if (root_inode)
iput(root_inode);
if (sbi->s_bitmap)
kfree(sbi->s_bitmap);
affs_brelse(root_bh);
if (sbi->s_prefix)
kfree(sbi->s_prefix);
kfree(sbi);
sb->s_fs_info = NULL;
return -EINVAL;
}
static int
affs_remount(struct super_block *sb, int *flags, char *data)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
int blocksize;
uid_t uid;
gid_t gid;
int mode;
int reserved;
int root_block;
unsigned long mount_flags;
int res = 0;
pr_debug("AFFS: remount(flags=0x%x,opts=\"%s\")\n",*flags,data);
*flags |= MS_NODIRATIME;
if (!parse_options(data,&uid,&gid,&mode,&reserved,&root_block,
&blocksize,&sbi->s_prefix,sbi->s_volume,&mount_flags))
return -EINVAL;
sbi->s_flags = mount_flags;
sbi->s_mode = mode;
sbi->s_uid = uid;
sbi->s_gid = gid;
if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY))
return 0;
if (*flags & MS_RDONLY) {
sb->s_dirt = 1;
while (sb->s_dirt)
affs_write_super(sb);
affs_free_bitmap(sb);
} else
res = affs_init_bitmap(sb, flags);
return res;
}
static int
affs_statfs(struct super_block *sb, struct kstatfs *buf)
{
int free;
pr_debug("AFFS: statfs() partsize=%d, reserved=%d\n",AFFS_SB(sb)->s_partition_size,
AFFS_SB(sb)->s_reserved);
free = affs_count_free_blocks(sb);
buf->f_type = AFFS_SUPER_MAGIC;
buf->f_bsize = sb->s_blocksize;
buf->f_blocks = AFFS_SB(sb)->s_partition_size - AFFS_SB(sb)->s_reserved;
buf->f_bfree = free;
buf->f_bavail = free;
return 0;
}
static struct super_block *affs_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return get_sb_bdev(fs_type, flags, dev_name, data, affs_fill_super);
}
static struct file_system_type affs_fs_type = {
.owner = THIS_MODULE,
.name = "affs",
.get_sb = affs_get_sb,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
static int __init init_affs_fs(void)
{
int err = init_inodecache();
if (err)
goto out1;
err = register_filesystem(&affs_fs_type);
if (err)
goto out;
return 0;
out:
destroy_inodecache();
out1:
return err;
}
static void __exit exit_affs_fs(void)
{
unregister_filesystem(&affs_fs_type);
destroy_inodecache();
}
MODULE_DESCRIPTION("Amiga filesystem support for Linux");
MODULE_LICENSE("GPL");
module_init(init_affs_fs)
module_exit(exit_affs_fs)

78
fs/affs/symlink.c Normal file
View File

@@ -0,0 +1,78 @@
/*
* linux/fs/affs/symlink.c
*
* 1995 Hans-Joachim Widmaier - Modified for affs.
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* affs symlink handling code
*/
#include "affs.h"
static int affs_symlink_readpage(struct file *file, struct page *page)
{
struct buffer_head *bh;
struct inode *inode = page->mapping->host;
char *link = kmap(page);
struct slink_front *lf;
int err;
int i, j;
char c;
char lc;
char *pf;
pr_debug("AFFS: follow_link(ino=%lu)\n",inode->i_ino);
err = -EIO;
bh = affs_bread(inode->i_sb, inode->i_ino);
if (!bh)
goto fail;
i = 0;
j = 0;
lf = (struct slink_front *)bh->b_data;
lc = 0;
pf = AFFS_SB(inode->i_sb)->s_prefix ? AFFS_SB(inode->i_sb)->s_prefix : "/";
if (strchr(lf->symname,':')) { /* Handle assign or volume name */
while (i < 1023 && (c = pf[i]))
link[i++] = c;
while (i < 1023 && lf->symname[j] != ':')
link[i++] = lf->symname[j++];
if (i < 1023)
link[i++] = '/';
j++;
lc = '/';
}
while (i < 1023 && (c = lf->symname[j])) {
if (c == '/' && lc == '/' && i < 1020) { /* parent dir */
link[i++] = '.';
link[i++] = '.';
}
link[i++] = c;
lc = c;
j++;
}
link[i] = '\0';
affs_brelse(bh);
SetPageUptodate(page);
kunmap(page);
unlock_page(page);
return 0;
fail:
SetPageError(page);
kunmap(page);
unlock_page(page);
return err;
}
struct address_space_operations affs_symlink_aops = {
.readpage = affs_symlink_readpage,
};
struct inode_operations affs_symlink_inode_operations = {
.readlink = generic_readlink,
.follow_link = page_follow_link_light,
.put_link = page_put_link,
.setattr = affs_notify_change,
};

28
fs/afs/Makefile Normal file
View File

@@ -0,0 +1,28 @@
#
# Makefile for Red Hat Linux AFS client.
#
#CFLAGS += -finstrument-functions
kafs-objs := \
callback.o \
cell.o \
cmservice.o \
dir.o \
file.o \
fsclient.o \
inode.o \
kafsasyncd.o \
kafstimod.o \
main.o \
misc.o \
mntpt.o \
proc.o \
server.o \
super.o \
vlclient.o \
vlocation.o \
vnode.o \
volume.o
obj-$(CONFIG_AFS_FS) := kafs.o

27
fs/afs/cache.h Normal file
View File

@@ -0,0 +1,27 @@
/* cache.h: AFS local cache management interface
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_CACHE_H
#define _LINUX_AFS_CACHE_H
#undef AFS_CACHING_SUPPORT
#include <linux/mm.h>
#ifdef AFS_CACHING_SUPPORT
#include <linux/cachefs.h>
#endif
#include "types.h"
#ifdef __KERNEL__
#endif /* __KERNEL__ */
#endif /* _LINUX_AFS_CACHE_H */

168
fs/afs/callback.c Normal file
View File

@@ -0,0 +1,168 @@
/*
* Copyright (c) 2002 Red Hat, Inc. All rights reserved.
*
* This software may be freely redistributed under the terms of the
* GNU General Public License.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Authors: David Woodhouse <dwmw2@cambridge.redhat.com>
* David Howells <dhowells@redhat.com>
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include "server.h"
#include "vnode.h"
#include "internal.h"
/*****************************************************************************/
/*
* allow the fileserver to request callback state (re-)initialisation
*/
int SRXAFSCM_InitCallBackState(struct afs_server *server)
{
struct list_head callbacks;
_enter("%p", server);
INIT_LIST_HEAD(&callbacks);
/* transfer the callback list from the server to a temp holding area */
spin_lock(&server->cb_lock);
list_add(&callbacks, &server->cb_promises);
list_del_init(&server->cb_promises);
/* munch our way through the list, grabbing the inode, dropping all the
* locks and regetting them in the right order
*/
while (!list_empty(&callbacks)) {
struct afs_vnode *vnode;
struct inode *inode;
vnode = list_entry(callbacks.next, struct afs_vnode, cb_link);
list_del_init(&vnode->cb_link);
/* try and grab the inode - may fail */
inode = igrab(AFS_VNODE_TO_I(vnode));
if (inode) {
int release = 0;
spin_unlock(&server->cb_lock);
spin_lock(&vnode->lock);
if (vnode->cb_server == server) {
vnode->cb_server = NULL;
afs_kafstimod_del_timer(&vnode->cb_timeout);
spin_lock(&afs_cb_hash_lock);
list_del_init(&vnode->cb_hash_link);
spin_unlock(&afs_cb_hash_lock);
release = 1;
}
spin_unlock(&vnode->lock);
iput(inode);
afs_put_server(server);
spin_lock(&server->cb_lock);
}
}
spin_unlock(&server->cb_lock);
_leave(" = 0");
return 0;
} /* end SRXAFSCM_InitCallBackState() */
/*****************************************************************************/
/*
* allow the fileserver to break callback promises
*/
int SRXAFSCM_CallBack(struct afs_server *server, size_t count,
struct afs_callback callbacks[])
{
_enter("%p,%u,", server, count);
for (; count > 0; callbacks++, count--) {
struct afs_vnode *vnode = NULL;
struct inode *inode = NULL;
int valid = 0;
_debug("- Fid { vl=%08x n=%u u=%u } CB { v=%u x=%u t=%u }",
callbacks->fid.vid,
callbacks->fid.vnode,
callbacks->fid.unique,
callbacks->version,
callbacks->expiry,
callbacks->type
);
/* find the inode for this fid */
spin_lock(&afs_cb_hash_lock);
list_for_each_entry(vnode,
&afs_cb_hash(server, &callbacks->fid),
cb_hash_link) {
if (memcmp(&vnode->fid, &callbacks->fid,
sizeof(struct afs_fid)) != 0)
continue;
/* right vnode, but is it same server? */
if (vnode->cb_server != server)
break; /* no */
/* try and nail the inode down */
inode = igrab(AFS_VNODE_TO_I(vnode));
break;
}
spin_unlock(&afs_cb_hash_lock);
if (inode) {
/* we've found the record for this vnode */
spin_lock(&vnode->lock);
if (vnode->cb_server == server) {
/* the callback _is_ on the calling server */
vnode->cb_server = NULL;
valid = 1;
afs_kafstimod_del_timer(&vnode->cb_timeout);
vnode->flags |= AFS_VNODE_CHANGED;
spin_lock(&server->cb_lock);
list_del_init(&vnode->cb_link);
spin_unlock(&server->cb_lock);
spin_lock(&afs_cb_hash_lock);
list_del_init(&vnode->cb_hash_link);
spin_unlock(&afs_cb_hash_lock);
}
spin_unlock(&vnode->lock);
if (valid) {
invalidate_remote_inode(inode);
afs_put_server(server);
}
iput(inode);
}
}
_leave(" = 0");
return 0;
} /* end SRXAFSCM_CallBack() */
/*****************************************************************************/
/*
* allow the fileserver to see if the cache manager is still alive
*/
int SRXAFSCM_Probe(struct afs_server *server)
{
_debug("SRXAFSCM_Probe(%p)\n", server);
return 0;
} /* end SRXAFSCM_Probe() */

569
fs/afs/cell.c Normal file
View File

@@ -0,0 +1,569 @@
/* cell.c: AFS cell and server record management
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <rxrpc/peer.h>
#include <rxrpc/connection.h>
#include "volume.h"
#include "cell.h"
#include "server.h"
#include "transport.h"
#include "vlclient.h"
#include "kafstimod.h"
#include "super.h"
#include "internal.h"
DECLARE_RWSEM(afs_proc_cells_sem);
LIST_HEAD(afs_proc_cells);
static struct list_head afs_cells = LIST_HEAD_INIT(afs_cells);
static DEFINE_RWLOCK(afs_cells_lock);
static DECLARE_RWSEM(afs_cells_sem); /* add/remove serialisation */
static struct afs_cell *afs_cell_root;
#ifdef AFS_CACHING_SUPPORT
static cachefs_match_val_t afs_cell_cache_match(void *target,
const void *entry);
static void afs_cell_cache_update(void *source, void *entry);
struct cachefs_index_def afs_cache_cell_index_def = {
.name = "cell_ix",
.data_size = sizeof(struct afs_cache_cell),
.keys[0] = { CACHEFS_INDEX_KEYS_ASCIIZ, 64 },
.match = afs_cell_cache_match,
.update = afs_cell_cache_update,
};
#endif
/*****************************************************************************/
/*
* create a cell record
* - "name" is the name of the cell
* - "vllist" is a colon separated list of IP addresses in "a.b.c.d" format
*/
int afs_cell_create(const char *name, char *vllist, struct afs_cell **_cell)
{
struct afs_cell *cell;
char *next;
int ret;
_enter("%s", name);
BUG_ON(!name); /* TODO: want to look up "this cell" in the cache */
/* allocate and initialise a cell record */
cell = kmalloc(sizeof(struct afs_cell) + strlen(name) + 1, GFP_KERNEL);
if (!cell) {
_leave(" = -ENOMEM");
return -ENOMEM;
}
down_write(&afs_cells_sem);
memset(cell, 0, sizeof(struct afs_cell));
atomic_set(&cell->usage, 0);
INIT_LIST_HEAD(&cell->link);
rwlock_init(&cell->sv_lock);
INIT_LIST_HEAD(&cell->sv_list);
INIT_LIST_HEAD(&cell->sv_graveyard);
spin_lock_init(&cell->sv_gylock);
init_rwsem(&cell->vl_sem);
INIT_LIST_HEAD(&cell->vl_list);
INIT_LIST_HEAD(&cell->vl_graveyard);
spin_lock_init(&cell->vl_gylock);
strcpy(cell->name,name);
/* fill in the VL server list from the rest of the string */
ret = -EINVAL;
do {
unsigned a, b, c, d;
next = strchr(vllist, ':');
if (next)
*next++ = 0;
if (sscanf(vllist, "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
goto badaddr;
if (a > 255 || b > 255 || c > 255 || d > 255)
goto badaddr;
cell->vl_addrs[cell->vl_naddrs++].s_addr =
htonl((a << 24) | (b << 16) | (c << 8) | d);
if (cell->vl_naddrs >= AFS_CELL_MAX_ADDRS)
break;
} while(vllist = next, vllist);
/* add a proc dir for this cell */
ret = afs_proc_cell_setup(cell);
if (ret < 0)
goto error;
#ifdef AFS_CACHING_SUPPORT
/* put it up for caching */
cachefs_acquire_cookie(afs_cache_netfs.primary_index,
&afs_vlocation_cache_index_def,
cell,
&cell->cache);
#endif
/* add to the cell lists */
write_lock(&afs_cells_lock);
list_add_tail(&cell->link, &afs_cells);
write_unlock(&afs_cells_lock);
down_write(&afs_proc_cells_sem);
list_add_tail(&cell->proc_link, &afs_proc_cells);
up_write(&afs_proc_cells_sem);
*_cell = cell;
up_write(&afs_cells_sem);
_leave(" = 0 (%p)", cell);
return 0;
badaddr:
printk(KERN_ERR "kAFS: bad VL server IP address: '%s'\n", vllist);
error:
up_write(&afs_cells_sem);
kfree(cell);
_leave(" = %d", ret);
return ret;
} /* end afs_cell_create() */
/*****************************************************************************/
/*
* initialise the cell database from module parameters
*/
int afs_cell_init(char *rootcell)
{
struct afs_cell *old_root, *new_root;
char *cp;
int ret;
_enter("");
if (!rootcell) {
/* module is loaded with no parameters, or built statically.
* - in the future we might initialize cell DB here.
*/
_leave(" = 0 (but no root)");
return 0;
}
cp = strchr(rootcell, ':');
if (!cp) {
printk(KERN_ERR "kAFS: no VL server IP addresses specified\n");
_leave(" = %d (no colon)", -EINVAL);
return -EINVAL;
}
/* allocate a cell record for the root cell */
*cp++ = 0;
ret = afs_cell_create(rootcell, cp, &new_root);
if (ret < 0) {
_leave(" = %d", ret);
return ret;
}
/* as afs_put_cell() takes locks by itself, we have to do
* a little gymnastics to be race-free.
*/
afs_get_cell(new_root);
write_lock(&afs_cells_lock);
while (afs_cell_root) {
old_root = afs_cell_root;
afs_cell_root = NULL;
write_unlock(&afs_cells_lock);
afs_put_cell(old_root);
write_lock(&afs_cells_lock);
}
afs_cell_root = new_root;
write_unlock(&afs_cells_lock);
_leave(" = %d", ret);
return ret;
} /* end afs_cell_init() */
/*****************************************************************************/
/*
* lookup a cell record
*/
int afs_cell_lookup(const char *name, unsigned namesz, struct afs_cell **_cell)
{
struct afs_cell *cell;
int ret;
_enter("\"%*.*s\",", namesz, namesz, name ? name : "");
*_cell = NULL;
if (name) {
/* if the cell was named, look for it in the cell record list */
ret = -ENOENT;
cell = NULL;
read_lock(&afs_cells_lock);
list_for_each_entry(cell, &afs_cells, link) {
if (strncmp(cell->name, name, namesz) == 0) {
afs_get_cell(cell);
goto found;
}
}
cell = NULL;
found:
read_unlock(&afs_cells_lock);
if (cell)
ret = 0;
}
else {
read_lock(&afs_cells_lock);
cell = afs_cell_root;
if (!cell) {
/* this should not happen unless user tries to mount
* when root cell is not set. Return an impossibly
* bizzare errno to alert the user. Things like
* ENOENT might be "more appropriate" but they happen
* for other reasons.
*/
ret = -EDESTADDRREQ;
}
else {
afs_get_cell(cell);
ret = 0;
}
read_unlock(&afs_cells_lock);
}
*_cell = cell;
_leave(" = %d (%p)", ret, cell);
return ret;
} /* end afs_cell_lookup() */
/*****************************************************************************/
/*
* try and get a cell record
*/
struct afs_cell *afs_get_cell_maybe(struct afs_cell **_cell)
{
struct afs_cell *cell;
write_lock(&afs_cells_lock);
cell = *_cell;
if (cell && !list_empty(&cell->link))
afs_get_cell(cell);
else
cell = NULL;
write_unlock(&afs_cells_lock);
return cell;
} /* end afs_get_cell_maybe() */
/*****************************************************************************/
/*
* destroy a cell record
*/
void afs_put_cell(struct afs_cell *cell)
{
if (!cell)
return;
_enter("%p{%d,%s}", cell, atomic_read(&cell->usage), cell->name);
/* sanity check */
BUG_ON(atomic_read(&cell->usage) <= 0);
/* to prevent a race, the decrement and the dequeue must be effectively
* atomic */
write_lock(&afs_cells_lock);
if (likely(!atomic_dec_and_test(&cell->usage))) {
write_unlock(&afs_cells_lock);
_leave("");
return;
}
write_unlock(&afs_cells_lock);
BUG_ON(!list_empty(&cell->sv_list));
BUG_ON(!list_empty(&cell->sv_graveyard));
BUG_ON(!list_empty(&cell->vl_list));
BUG_ON(!list_empty(&cell->vl_graveyard));
_leave(" [unused]");
} /* end afs_put_cell() */
/*****************************************************************************/
/*
* destroy a cell record
*/
static void afs_cell_destroy(struct afs_cell *cell)
{
_enter("%p{%d,%s}", cell, atomic_read(&cell->usage), cell->name);
/* to prevent a race, the decrement and the dequeue must be effectively
* atomic */
write_lock(&afs_cells_lock);
/* sanity check */
BUG_ON(atomic_read(&cell->usage) != 0);
list_del_init(&cell->link);
write_unlock(&afs_cells_lock);
down_write(&afs_cells_sem);
afs_proc_cell_remove(cell);
down_write(&afs_proc_cells_sem);
list_del_init(&cell->proc_link);
up_write(&afs_proc_cells_sem);
#ifdef AFS_CACHING_SUPPORT
cachefs_relinquish_cookie(cell->cache, 0);
#endif
up_write(&afs_cells_sem);
BUG_ON(!list_empty(&cell->sv_list));
BUG_ON(!list_empty(&cell->sv_graveyard));
BUG_ON(!list_empty(&cell->vl_list));
BUG_ON(!list_empty(&cell->vl_graveyard));
/* finish cleaning up the cell */
kfree(cell);
_leave(" [destroyed]");
} /* end afs_cell_destroy() */
/*****************************************************************************/
/*
* lookup the server record corresponding to an Rx RPC peer
*/
int afs_server_find_by_peer(const struct rxrpc_peer *peer,
struct afs_server **_server)
{
struct afs_server *server;
struct afs_cell *cell;
_enter("%p{a=%08x},", peer, ntohl(peer->addr.s_addr));
/* search the cell list */
read_lock(&afs_cells_lock);
list_for_each_entry(cell, &afs_cells, link) {
_debug("? cell %s",cell->name);
write_lock(&cell->sv_lock);
/* check the active list */
list_for_each_entry(server, &cell->sv_list, link) {
_debug("?? server %08x", ntohl(server->addr.s_addr));
if (memcmp(&server->addr, &peer->addr,
sizeof(struct in_addr)) == 0)
goto found_server;
}
/* check the inactive list */
spin_lock(&cell->sv_gylock);
list_for_each_entry(server, &cell->sv_graveyard, link) {
_debug("?? dead server %08x",
ntohl(server->addr.s_addr));
if (memcmp(&server->addr, &peer->addr,
sizeof(struct in_addr)) == 0)
goto found_dead_server;
}
spin_unlock(&cell->sv_gylock);
write_unlock(&cell->sv_lock);
}
read_unlock(&afs_cells_lock);
_leave(" = -ENOENT");
return -ENOENT;
/* we found it in the graveyard - resurrect it */
found_dead_server:
list_del(&server->link);
list_add_tail(&server->link, &cell->sv_list);
afs_get_server(server);
afs_kafstimod_del_timer(&server->timeout);
spin_unlock(&cell->sv_gylock);
goto success;
/* we found it - increment its ref count and return it */
found_server:
afs_get_server(server);
success:
write_unlock(&cell->sv_lock);
read_unlock(&afs_cells_lock);
*_server = server;
_leave(" = 0 (s=%p c=%p)", server, cell);
return 0;
} /* end afs_server_find_by_peer() */
/*****************************************************************************/
/*
* purge in-memory cell database on module unload or afs_init() failure
* - the timeout daemon is stopped before calling this
*/
void afs_cell_purge(void)
{
struct afs_vlocation *vlocation;
struct afs_cell *cell;
_enter("");
afs_put_cell(afs_cell_root);
while (!list_empty(&afs_cells)) {
cell = NULL;
/* remove the next cell from the front of the list */
write_lock(&afs_cells_lock);
if (!list_empty(&afs_cells)) {
cell = list_entry(afs_cells.next,
struct afs_cell, link);
list_del_init(&cell->link);
}
write_unlock(&afs_cells_lock);
if (cell) {
_debug("PURGING CELL %s (%d)",
cell->name, atomic_read(&cell->usage));
BUG_ON(!list_empty(&cell->sv_list));
BUG_ON(!list_empty(&cell->vl_list));
/* purge the cell's VL graveyard list */
_debug(" - clearing VL graveyard");
spin_lock(&cell->vl_gylock);
while (!list_empty(&cell->vl_graveyard)) {
vlocation = list_entry(cell->vl_graveyard.next,
struct afs_vlocation,
link);
list_del_init(&vlocation->link);
afs_kafstimod_del_timer(&vlocation->timeout);
spin_unlock(&cell->vl_gylock);
afs_vlocation_do_timeout(vlocation);
/* TODO: race if move to use krxtimod instead
* of kafstimod */
spin_lock(&cell->vl_gylock);
}
spin_unlock(&cell->vl_gylock);
/* purge the cell's server graveyard list */
_debug(" - clearing server graveyard");
spin_lock(&cell->sv_gylock);
while (!list_empty(&cell->sv_graveyard)) {
struct afs_server *server;
server = list_entry(cell->sv_graveyard.next,
struct afs_server, link);
list_del_init(&server->link);
afs_kafstimod_del_timer(&server->timeout);
spin_unlock(&cell->sv_gylock);
afs_server_do_timeout(server);
spin_lock(&cell->sv_gylock);
}
spin_unlock(&cell->sv_gylock);
/* now the cell should be left with no references */
afs_cell_destroy(cell);
}
}
_leave("");
} /* end afs_cell_purge() */
/*****************************************************************************/
/*
* match a cell record obtained from the cache
*/
#ifdef AFS_CACHING_SUPPORT
static cachefs_match_val_t afs_cell_cache_match(void *target,
const void *entry)
{
const struct afs_cache_cell *ccell = entry;
struct afs_cell *cell = target;
_enter("{%s},{%s}", ccell->name, cell->name);
if (strncmp(ccell->name, cell->name, sizeof(ccell->name)) == 0) {
_leave(" = SUCCESS");
return CACHEFS_MATCH_SUCCESS;
}
_leave(" = FAILED");
return CACHEFS_MATCH_FAILED;
} /* end afs_cell_cache_match() */
#endif
/*****************************************************************************/
/*
* update a cell record in the cache
*/
#ifdef AFS_CACHING_SUPPORT
static void afs_cell_cache_update(void *source, void *entry)
{
struct afs_cache_cell *ccell = entry;
struct afs_cell *cell = source;
_enter("%p,%p", source, entry);
strncpy(ccell->name, cell->name, sizeof(ccell->name));
memcpy(ccell->vl_servers,
cell->vl_addrs,
min(sizeof(ccell->vl_servers), sizeof(cell->vl_addrs)));
} /* end afs_cell_cache_update() */
#endif

78
fs/afs/cell.h Normal file
View File

@@ -0,0 +1,78 @@
/* cell.h: AFS cell record
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_CELL_H
#define _LINUX_AFS_CELL_H
#include "types.h"
#include "cache.h"
#define AFS_CELL_MAX_ADDRS 15
extern volatile int afs_cells_being_purged; /* T when cells are being purged by rmmod */
/*****************************************************************************/
/*
* entry in the cached cell catalogue
*/
struct afs_cache_cell
{
char name[64]; /* cell name (padded with NULs) */
struct in_addr vl_servers[15]; /* cached cell VL servers */
};
/*****************************************************************************/
/*
* AFS cell record
*/
struct afs_cell
{
atomic_t usage;
struct list_head link; /* main cell list link */
struct list_head proc_link; /* /proc cell list link */
struct proc_dir_entry *proc_dir; /* /proc dir for this cell */
#ifdef AFS_CACHING_SUPPORT
struct cachefs_cookie *cache; /* caching cookie */
#endif
/* server record management */
rwlock_t sv_lock; /* active server list lock */
struct list_head sv_list; /* active server list */
struct list_head sv_graveyard; /* inactive server list */
spinlock_t sv_gylock; /* inactive server list lock */
/* volume location record management */
struct rw_semaphore vl_sem; /* volume management serialisation semaphore */
struct list_head vl_list; /* cell's active VL record list */
struct list_head vl_graveyard; /* cell's inactive VL record list */
spinlock_t vl_gylock; /* graveyard lock */
unsigned short vl_naddrs; /* number of VL servers in addr list */
unsigned short vl_curr_svix; /* current server index */
struct in_addr vl_addrs[AFS_CELL_MAX_ADDRS]; /* cell VL server addresses */
char name[0]; /* cell name - must go last */
};
extern int afs_cell_init(char *rootcell);
extern int afs_cell_create(const char *name, char *vllist, struct afs_cell **_cell);
extern int afs_cell_lookup(const char *name, unsigned nmsize, struct afs_cell **_cell);
#define afs_get_cell(C) do { atomic_inc(&(C)->usage); } while(0)
extern struct afs_cell *afs_get_cell_maybe(struct afs_cell **_cell);
extern void afs_put_cell(struct afs_cell *cell);
extern void afs_cell_purge(void);
#endif /* _LINUX_AFS_CELL_H */

652
fs/afs/cmservice.c Normal file
View File

@@ -0,0 +1,652 @@
/* cmservice.c: AFS Cache Manager Service
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include "server.h"
#include "cell.h"
#include "transport.h"
#include <rxrpc/rxrpc.h>
#include <rxrpc/transport.h>
#include <rxrpc/connection.h>
#include <rxrpc/call.h>
#include "cmservice.h"
#include "internal.h"
static unsigned afscm_usage; /* AFS cache manager usage count */
static struct rw_semaphore afscm_sem; /* AFS cache manager start/stop semaphore */
static int afscm_new_call(struct rxrpc_call *call);
static void afscm_attention(struct rxrpc_call *call);
static void afscm_error(struct rxrpc_call *call);
static void afscm_aemap(struct rxrpc_call *call);
static void _SRXAFSCM_CallBack(struct rxrpc_call *call);
static void _SRXAFSCM_InitCallBackState(struct rxrpc_call *call);
static void _SRXAFSCM_Probe(struct rxrpc_call *call);
typedef void (*_SRXAFSCM_xxxx_t)(struct rxrpc_call *call);
static const struct rxrpc_operation AFSCM_ops[] = {
{
.id = 204,
.asize = RXRPC_APP_MARK_EOF,
.name = "CallBack",
.user = _SRXAFSCM_CallBack,
},
{
.id = 205,
.asize = RXRPC_APP_MARK_EOF,
.name = "InitCallBackState",
.user = _SRXAFSCM_InitCallBackState,
},
{
.id = 206,
.asize = RXRPC_APP_MARK_EOF,
.name = "Probe",
.user = _SRXAFSCM_Probe,
},
#if 0
{
.id = 207,
.asize = RXRPC_APP_MARK_EOF,
.name = "GetLock",
.user = _SRXAFSCM_GetLock,
},
{
.id = 208,
.asize = RXRPC_APP_MARK_EOF,
.name = "GetCE",
.user = _SRXAFSCM_GetCE,
},
{
.id = 209,
.asize = RXRPC_APP_MARK_EOF,
.name = "GetXStatsVersion",
.user = _SRXAFSCM_GetXStatsVersion,
},
{
.id = 210,
.asize = RXRPC_APP_MARK_EOF,
.name = "GetXStats",
.user = _SRXAFSCM_GetXStats,
}
#endif
};
static struct rxrpc_service AFSCM_service = {
.name = "AFS/CM",
.owner = THIS_MODULE,
.link = LIST_HEAD_INIT(AFSCM_service.link),
.new_call = afscm_new_call,
.service_id = 1,
.attn_func = afscm_attention,
.error_func = afscm_error,
.aemap_func = afscm_aemap,
.ops_begin = &AFSCM_ops[0],
.ops_end = &AFSCM_ops[sizeof(AFSCM_ops) / sizeof(AFSCM_ops[0])],
};
static DECLARE_COMPLETION(kafscmd_alive);
static DECLARE_COMPLETION(kafscmd_dead);
static DECLARE_WAIT_QUEUE_HEAD(kafscmd_sleepq);
static LIST_HEAD(kafscmd_attention_list);
static LIST_HEAD(afscm_calls);
static DEFINE_SPINLOCK(afscm_calls_lock);
static DEFINE_SPINLOCK(kafscmd_attention_lock);
static int kafscmd_die;
/*****************************************************************************/
/*
* AFS Cache Manager kernel thread
*/
static int kafscmd(void *arg)
{
DECLARE_WAITQUEUE(myself, current);
struct rxrpc_call *call;
_SRXAFSCM_xxxx_t func;
int die;
printk("kAFS: Started kafscmd %d\n", current->pid);
daemonize("kafscmd");
complete(&kafscmd_alive);
/* loop around looking for things to attend to */
do {
if (list_empty(&kafscmd_attention_list)) {
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&kafscmd_sleepq, &myself);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (!list_empty(&kafscmd_attention_list) ||
signal_pending(current) ||
kafscmd_die)
break;
schedule();
}
remove_wait_queue(&kafscmd_sleepq, &myself);
set_current_state(TASK_RUNNING);
}
die = kafscmd_die;
/* dequeue the next call requiring attention */
call = NULL;
spin_lock(&kafscmd_attention_lock);
if (!list_empty(&kafscmd_attention_list)) {
call = list_entry(kafscmd_attention_list.next,
struct rxrpc_call,
app_attn_link);
list_del_init(&call->app_attn_link);
die = 0;
}
spin_unlock(&kafscmd_attention_lock);
if (call) {
/* act upon it */
_debug("@@@ Begin Attend Call %p", call);
func = call->app_user;
if (func)
func(call);
rxrpc_put_call(call);
_debug("@@@ End Attend Call %p", call);
}
} while(!die);
/* and that's all */
complete_and_exit(&kafscmd_dead, 0);
} /* end kafscmd() */
/*****************************************************************************/
/*
* handle a call coming in to the cache manager
* - if I want to keep the call, I must increment its usage count
* - the return value will be negated and passed back in an abort packet if
* non-zero
* - serialised by virtue of there only being one krxiod
*/
static int afscm_new_call(struct rxrpc_call *call)
{
_enter("%p{cid=%u u=%d}",
call, ntohl(call->call_id), atomic_read(&call->usage));
rxrpc_get_call(call);
/* add to my current call list */
spin_lock(&afscm_calls_lock);
list_add(&call->app_link,&afscm_calls);
spin_unlock(&afscm_calls_lock);
_leave(" = 0");
return 0;
} /* end afscm_new_call() */
/*****************************************************************************/
/*
* queue on the kafscmd queue for attention
*/
static void afscm_attention(struct rxrpc_call *call)
{
_enter("%p{cid=%u u=%d}",
call, ntohl(call->call_id), atomic_read(&call->usage));
spin_lock(&kafscmd_attention_lock);
if (list_empty(&call->app_attn_link)) {
list_add_tail(&call->app_attn_link, &kafscmd_attention_list);
rxrpc_get_call(call);
}
spin_unlock(&kafscmd_attention_lock);
wake_up(&kafscmd_sleepq);
_leave(" {u=%d}", atomic_read(&call->usage));
} /* end afscm_attention() */
/*****************************************************************************/
/*
* handle my call being aborted
* - clean up, dequeue and put my ref to the call
*/
static void afscm_error(struct rxrpc_call *call)
{
int removed;
_enter("%p{est=%s ac=%u er=%d}",
call,
rxrpc_call_error_states[call->app_err_state],
call->app_abort_code,
call->app_errno);
spin_lock(&kafscmd_attention_lock);
if (list_empty(&call->app_attn_link)) {
list_add_tail(&call->app_attn_link, &kafscmd_attention_list);
rxrpc_get_call(call);
}
spin_unlock(&kafscmd_attention_lock);
removed = 0;
spin_lock(&afscm_calls_lock);
if (!list_empty(&call->app_link)) {
list_del_init(&call->app_link);
removed = 1;
}
spin_unlock(&afscm_calls_lock);
if (removed)
rxrpc_put_call(call);
wake_up(&kafscmd_sleepq);
_leave("");
} /* end afscm_error() */
/*****************************************************************************/
/*
* map afs abort codes to/from Linux error codes
* - called with call->lock held
*/
static void afscm_aemap(struct rxrpc_call *call)
{
switch (call->app_err_state) {
case RXRPC_ESTATE_LOCAL_ABORT:
call->app_abort_code = -call->app_errno;
break;
case RXRPC_ESTATE_PEER_ABORT:
call->app_errno = -ECONNABORTED;
break;
default:
break;
}
} /* end afscm_aemap() */
/*****************************************************************************/
/*
* start the cache manager service if not already started
*/
int afscm_start(void)
{
int ret;
down_write(&afscm_sem);
if (!afscm_usage) {
ret = kernel_thread(kafscmd, NULL, 0);
if (ret < 0)
goto out;
wait_for_completion(&kafscmd_alive);
ret = rxrpc_add_service(afs_transport, &AFSCM_service);
if (ret < 0)
goto kill;
afs_kafstimod_add_timer(&afs_mntpt_expiry_timer,
afs_mntpt_expiry_timeout * HZ);
}
afscm_usage++;
up_write(&afscm_sem);
return 0;
kill:
kafscmd_die = 1;
wake_up(&kafscmd_sleepq);
wait_for_completion(&kafscmd_dead);
out:
up_write(&afscm_sem);
return ret;
} /* end afscm_start() */
/*****************************************************************************/
/*
* stop the cache manager service
*/
void afscm_stop(void)
{
struct rxrpc_call *call;
down_write(&afscm_sem);
BUG_ON(afscm_usage == 0);
afscm_usage--;
if (afscm_usage == 0) {
/* don't want more incoming calls */
rxrpc_del_service(afs_transport, &AFSCM_service);
/* abort any calls I've still got open (the afscm_error() will
* dequeue them) */
spin_lock(&afscm_calls_lock);
while (!list_empty(&afscm_calls)) {
call = list_entry(afscm_calls.next,
struct rxrpc_call,
app_link);
list_del_init(&call->app_link);
rxrpc_get_call(call);
spin_unlock(&afscm_calls_lock);
rxrpc_call_abort(call, -ESRCH); /* abort, dequeue and
* put */
_debug("nuking active call %08x.%d",
ntohl(call->conn->conn_id),
ntohl(call->call_id));
rxrpc_put_call(call);
rxrpc_put_call(call);
spin_lock(&afscm_calls_lock);
}
spin_unlock(&afscm_calls_lock);
/* get rid of my daemon */
kafscmd_die = 1;
wake_up(&kafscmd_sleepq);
wait_for_completion(&kafscmd_dead);
/* dispose of any calls waiting for attention */
spin_lock(&kafscmd_attention_lock);
while (!list_empty(&kafscmd_attention_list)) {
call = list_entry(kafscmd_attention_list.next,
struct rxrpc_call,
app_attn_link);
list_del_init(&call->app_attn_link);
spin_unlock(&kafscmd_attention_lock);
rxrpc_put_call(call);
spin_lock(&kafscmd_attention_lock);
}
spin_unlock(&kafscmd_attention_lock);
afs_kafstimod_del_timer(&afs_mntpt_expiry_timer);
}
up_write(&afscm_sem);
} /* end afscm_stop() */
/*****************************************************************************/
/*
* handle the fileserver breaking a set of callbacks
*/
static void _SRXAFSCM_CallBack(struct rxrpc_call *call)
{
struct afs_server *server;
size_t count, qty, tmp;
int ret = 0, removed;
_enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]);
server = afs_server_get_from_peer(call->conn->peer);
switch (call->app_call_state) {
/* we've received the last packet
* - drain all the data from the call and send the reply
*/
case RXRPC_CSTATE_SRVR_GOT_ARGS:
ret = -EBADMSG;
qty = call->app_ready_qty;
if (qty < 8 || qty > 50 * (6 * 4) + 8)
break;
{
struct afs_callback *cb, *pcb;
int loop;
__be32 *fp, *bp;
fp = rxrpc_call_alloc_scratch(call, qty);
/* drag the entire argument block out to the scratch
* space */
ret = rxrpc_call_read_data(call, fp, qty, 0);
if (ret < 0)
break;
/* and unmarshall the parameter block */
ret = -EBADMSG;
count = ntohl(*fp++);
if (count>AFSCBMAX ||
(count * (3 * 4) + 8 != qty &&
count * (6 * 4) + 8 != qty))
break;
bp = fp + count*3;
tmp = ntohl(*bp++);
if (tmp > 0 && tmp != count)
break;
if (tmp == 0)
bp = NULL;
pcb = cb = rxrpc_call_alloc_scratch_s(
call, struct afs_callback);
for (loop = count - 1; loop >= 0; loop--) {
pcb->fid.vid = ntohl(*fp++);
pcb->fid.vnode = ntohl(*fp++);
pcb->fid.unique = ntohl(*fp++);
if (bp) {
pcb->version = ntohl(*bp++);
pcb->expiry = ntohl(*bp++);
pcb->type = ntohl(*bp++);
}
else {
pcb->version = 0;
pcb->expiry = 0;
pcb->type = AFSCM_CB_UNTYPED;
}
pcb++;
}
/* invoke the actual service routine */
ret = SRXAFSCM_CallBack(server, count, cb);
if (ret < 0)
break;
}
/* send the reply */
ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET,
GFP_KERNEL, 0, &count);
if (ret < 0)
break;
break;
/* operation complete */
case RXRPC_CSTATE_COMPLETE:
call->app_user = NULL;
removed = 0;
spin_lock(&afscm_calls_lock);
if (!list_empty(&call->app_link)) {
list_del_init(&call->app_link);
removed = 1;
}
spin_unlock(&afscm_calls_lock);
if (removed)
rxrpc_put_call(call);
break;
/* operation terminated on error */
case RXRPC_CSTATE_ERROR:
call->app_user = NULL;
break;
default:
break;
}
if (ret < 0)
rxrpc_call_abort(call, ret);
afs_put_server(server);
_leave(" = %d", ret);
} /* end _SRXAFSCM_CallBack() */
/*****************************************************************************/
/*
* handle the fileserver asking us to initialise our callback state
*/
static void _SRXAFSCM_InitCallBackState(struct rxrpc_call *call)
{
struct afs_server *server;
size_t count;
int ret = 0, removed;
_enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]);
server = afs_server_get_from_peer(call->conn->peer);
switch (call->app_call_state) {
/* we've received the last packet - drain all the data from the
* call */
case RXRPC_CSTATE_SRVR_GOT_ARGS:
/* shouldn't be any args */
ret = -EBADMSG;
break;
/* send the reply when asked for it */
case RXRPC_CSTATE_SRVR_SND_REPLY:
/* invoke the actual service routine */
ret = SRXAFSCM_InitCallBackState(server);
if (ret < 0)
break;
ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET,
GFP_KERNEL, 0, &count);
if (ret < 0)
break;
break;
/* operation complete */
case RXRPC_CSTATE_COMPLETE:
call->app_user = NULL;
removed = 0;
spin_lock(&afscm_calls_lock);
if (!list_empty(&call->app_link)) {
list_del_init(&call->app_link);
removed = 1;
}
spin_unlock(&afscm_calls_lock);
if (removed)
rxrpc_put_call(call);
break;
/* operation terminated on error */
case RXRPC_CSTATE_ERROR:
call->app_user = NULL;
break;
default:
break;
}
if (ret < 0)
rxrpc_call_abort(call, ret);
afs_put_server(server);
_leave(" = %d", ret);
} /* end _SRXAFSCM_InitCallBackState() */
/*****************************************************************************/
/*
* handle a probe from a fileserver
*/
static void _SRXAFSCM_Probe(struct rxrpc_call *call)
{
struct afs_server *server;
size_t count;
int ret = 0, removed;
_enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]);
server = afs_server_get_from_peer(call->conn->peer);
switch (call->app_call_state) {
/* we've received the last packet - drain all the data from the
* call */
case RXRPC_CSTATE_SRVR_GOT_ARGS:
/* shouldn't be any args */
ret = -EBADMSG;
break;
/* send the reply when asked for it */
case RXRPC_CSTATE_SRVR_SND_REPLY:
/* invoke the actual service routine */
ret = SRXAFSCM_Probe(server);
if (ret < 0)
break;
ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET,
GFP_KERNEL, 0, &count);
if (ret < 0)
break;
break;
/* operation complete */
case RXRPC_CSTATE_COMPLETE:
call->app_user = NULL;
removed = 0;
spin_lock(&afscm_calls_lock);
if (!list_empty(&call->app_link)) {
list_del_init(&call->app_link);
removed = 1;
}
spin_unlock(&afscm_calls_lock);
if (removed)
rxrpc_put_call(call);
break;
/* operation terminated on error */
case RXRPC_CSTATE_ERROR:
call->app_user = NULL;
break;
default:
break;
}
if (ret < 0)
rxrpc_call_abort(call, ret);
afs_put_server(server);
_leave(" = %d", ret);
} /* end _SRXAFSCM_Probe() */

29
fs/afs/cmservice.h Normal file
View File

@@ -0,0 +1,29 @@
/* cmservice.h: AFS Cache Manager Service declarations
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_CMSERVICE_H
#define _LINUX_AFS_CMSERVICE_H
#include <rxrpc/transport.h>
#include "types.h"
/* cache manager start/stop */
extern int afscm_start(void);
extern void afscm_stop(void);
/* cache manager server functions */
extern int SRXAFSCM_InitCallBackState(struct afs_server *server);
extern int SRXAFSCM_CallBack(struct afs_server *server,
size_t count,
struct afs_callback callbacks[]);
extern int SRXAFSCM_Probe(struct afs_server *server);
#endif /* _LINUX_AFS_CMSERVICE_H */

666
fs/afs/dir.c Normal file
View File

@@ -0,0 +1,666 @@
/* dir.c: AFS filesystem directory handling
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/smp_lock.h>
#include "vnode.h"
#include "volume.h"
#include <rxrpc/call.h>
#include "super.h"
#include "internal.h"
static struct dentry *afs_dir_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *nd);
static int afs_dir_open(struct inode *inode, struct file *file);
static int afs_dir_readdir(struct file *file, void *dirent, filldir_t filldir);
static int afs_d_revalidate(struct dentry *dentry, struct nameidata *nd);
static int afs_d_delete(struct dentry *dentry);
static int afs_dir_lookup_filldir(void *_cookie, const char *name, int nlen,
loff_t fpos, ino_t ino, unsigned dtype);
struct file_operations afs_dir_file_operations = {
.open = afs_dir_open,
.readdir = afs_dir_readdir,
};
struct inode_operations afs_dir_inode_operations = {
.lookup = afs_dir_lookup,
.getattr = afs_inode_getattr,
#if 0 /* TODO */
.create = afs_dir_create,
.link = afs_dir_link,
.unlink = afs_dir_unlink,
.symlink = afs_dir_symlink,
.mkdir = afs_dir_mkdir,
.rmdir = afs_dir_rmdir,
.mknod = afs_dir_mknod,
.rename = afs_dir_rename,
#endif
};
static struct dentry_operations afs_fs_dentry_operations = {
.d_revalidate = afs_d_revalidate,
.d_delete = afs_d_delete,
};
#define AFS_DIR_HASHTBL_SIZE 128
#define AFS_DIR_DIRENT_SIZE 32
#define AFS_DIRENT_PER_BLOCK 64
union afs_dirent {
struct {
uint8_t valid;
uint8_t unused[1];
__be16 hash_next;
__be32 vnode;
__be32 unique;
uint8_t name[16];
uint8_t overflow[4]; /* if any char of the name (inc
* NUL) reaches here, consume
* the next dirent too */
} u;
uint8_t extended_name[32];
};
/* AFS directory page header (one at the beginning of every 2048-byte chunk) */
struct afs_dir_pagehdr {
__be16 npages;
__be16 magic;
#define AFS_DIR_MAGIC htons(1234)
uint8_t nentries;
uint8_t bitmap[8];
uint8_t pad[19];
};
/* directory block layout */
union afs_dir_block {
struct afs_dir_pagehdr pagehdr;
struct {
struct afs_dir_pagehdr pagehdr;
uint8_t alloc_ctrs[128];
/* dir hash table */
uint16_t hashtable[AFS_DIR_HASHTBL_SIZE];
} hdr;
union afs_dirent dirents[AFS_DIRENT_PER_BLOCK];
};
/* layout on a linux VM page */
struct afs_dir_page {
union afs_dir_block blocks[PAGE_SIZE / sizeof(union afs_dir_block)];
};
struct afs_dir_lookup_cookie {
struct afs_fid fid;
const char *name;
size_t nlen;
int found;
};
/*****************************************************************************/
/*
* check that a directory page is valid
*/
static inline void afs_dir_check_page(struct inode *dir, struct page *page)
{
struct afs_dir_page *dbuf;
loff_t latter;
int tmp, qty;
#if 0
/* check the page count */
qty = desc.size / sizeof(dbuf->blocks[0]);
if (qty == 0)
goto error;
if (page->index==0 && qty!=ntohs(dbuf->blocks[0].pagehdr.npages)) {
printk("kAFS: %s(%lu): wrong number of dir blocks %d!=%hu\n",
__FUNCTION__,dir->i_ino,qty,ntohs(dbuf->blocks[0].pagehdr.npages));
goto error;
}
#endif
/* determine how many magic numbers there should be in this page */
latter = dir->i_size - (page->index << PAGE_CACHE_SHIFT);
if (latter >= PAGE_SIZE)
qty = PAGE_SIZE;
else
qty = latter;
qty /= sizeof(union afs_dir_block);
/* check them */
dbuf = page_address(page);
for (tmp = 0; tmp < qty; tmp++) {
if (dbuf->blocks[tmp].pagehdr.magic != AFS_DIR_MAGIC) {
printk("kAFS: %s(%lu): bad magic %d/%d is %04hx\n",
__FUNCTION__, dir->i_ino, tmp, qty,
ntohs(dbuf->blocks[tmp].pagehdr.magic));
goto error;
}
}
SetPageChecked(page);
return;
error:
SetPageChecked(page);
SetPageError(page);
} /* end afs_dir_check_page() */
/*****************************************************************************/
/*
* discard a page cached in the pagecache
*/
static inline void afs_dir_put_page(struct page *page)
{
kunmap(page);
page_cache_release(page);
} /* end afs_dir_put_page() */
/*****************************************************************************/
/*
* get a page into the pagecache
*/
static struct page *afs_dir_get_page(struct inode *dir, unsigned long index)
{
struct page *page;
_enter("{%lu},%lu", dir->i_ino, index);
page = read_cache_page(dir->i_mapping,index,
(filler_t *) dir->i_mapping->a_ops->readpage,
NULL);
if (!IS_ERR(page)) {
wait_on_page_locked(page);
kmap(page);
if (!PageUptodate(page))
goto fail;
if (!PageChecked(page))
afs_dir_check_page(dir, page);
if (PageError(page))
goto fail;
}
return page;
fail:
afs_dir_put_page(page);
return ERR_PTR(-EIO);
} /* end afs_dir_get_page() */
/*****************************************************************************/
/*
* open an AFS directory file
*/
static int afs_dir_open(struct inode *inode, struct file *file)
{
_enter("{%lu}", inode->i_ino);
BUG_ON(sizeof(union afs_dir_block) != 2048);
BUG_ON(sizeof(union afs_dirent) != 32);
if (AFS_FS_I(inode)->flags & AFS_VNODE_DELETED)
return -ENOENT;
_leave(" = 0");
return 0;
} /* end afs_dir_open() */
/*****************************************************************************/
/*
* deal with one block in an AFS directory
*/
static int afs_dir_iterate_block(unsigned *fpos,
union afs_dir_block *block,
unsigned blkoff,
void *cookie,
filldir_t filldir)
{
union afs_dirent *dire;
unsigned offset, next, curr;
size_t nlen;
int tmp, ret;
_enter("%u,%x,%p,,",*fpos,blkoff,block);
curr = (*fpos - blkoff) / sizeof(union afs_dirent);
/* walk through the block, an entry at a time */
for (offset = AFS_DIRENT_PER_BLOCK - block->pagehdr.nentries;
offset < AFS_DIRENT_PER_BLOCK;
offset = next
) {
next = offset + 1;
/* skip entries marked unused in the bitmap */
if (!(block->pagehdr.bitmap[offset / 8] &
(1 << (offset % 8)))) {
_debug("ENT[%Zu.%u]: unused\n",
blkoff / sizeof(union afs_dir_block), offset);
if (offset >= curr)
*fpos = blkoff +
next * sizeof(union afs_dirent);
continue;
}
/* got a valid entry */
dire = &block->dirents[offset];
nlen = strnlen(dire->u.name,
sizeof(*block) -
offset * sizeof(union afs_dirent));
_debug("ENT[%Zu.%u]: %s %Zu \"%s\"\n",
blkoff / sizeof(union afs_dir_block), offset,
(offset < curr ? "skip" : "fill"),
nlen, dire->u.name);
/* work out where the next possible entry is */
for (tmp = nlen; tmp > 15; tmp -= sizeof(union afs_dirent)) {
if (next >= AFS_DIRENT_PER_BLOCK) {
_debug("ENT[%Zu.%u]:"
" %u travelled beyond end dir block"
" (len %u/%Zu)\n",
blkoff / sizeof(union afs_dir_block),
offset, next, tmp, nlen);
return -EIO;
}
if (!(block->pagehdr.bitmap[next / 8] &
(1 << (next % 8)))) {
_debug("ENT[%Zu.%u]:"
" %u unmarked extension (len %u/%Zu)\n",
blkoff / sizeof(union afs_dir_block),
offset, next, tmp, nlen);
return -EIO;
}
_debug("ENT[%Zu.%u]: ext %u/%Zu\n",
blkoff / sizeof(union afs_dir_block),
next, tmp, nlen);
next++;
}
/* skip if starts before the current position */
if (offset < curr)
continue;
/* found the next entry */
ret = filldir(cookie,
dire->u.name,
nlen,
blkoff + offset * sizeof(union afs_dirent),
ntohl(dire->u.vnode),
filldir == afs_dir_lookup_filldir ?
ntohl(dire->u.unique) : DT_UNKNOWN);
if (ret < 0) {
_leave(" = 0 [full]");
return 0;
}
*fpos = blkoff + next * sizeof(union afs_dirent);
}
_leave(" = 1 [more]");
return 1;
} /* end afs_dir_iterate_block() */
/*****************************************************************************/
/*
* read an AFS directory
*/
static int afs_dir_iterate(struct inode *dir, unsigned *fpos, void *cookie,
filldir_t filldir)
{
union afs_dir_block *dblock;
struct afs_dir_page *dbuf;
struct page *page;
unsigned blkoff, limit;
int ret;
_enter("{%lu},%u,,", dir->i_ino, *fpos);
if (AFS_FS_I(dir)->flags & AFS_VNODE_DELETED) {
_leave(" = -ESTALE");
return -ESTALE;
}
/* round the file position up to the next entry boundary */
*fpos += sizeof(union afs_dirent) - 1;
*fpos &= ~(sizeof(union afs_dirent) - 1);
/* walk through the blocks in sequence */
ret = 0;
while (*fpos < dir->i_size) {
blkoff = *fpos & ~(sizeof(union afs_dir_block) - 1);
/* fetch the appropriate page from the directory */
page = afs_dir_get_page(dir, blkoff / PAGE_SIZE);
if (IS_ERR(page)) {
ret = PTR_ERR(page);
break;
}
limit = blkoff & ~(PAGE_SIZE - 1);
dbuf = page_address(page);
/* deal with the individual blocks stashed on this page */
do {
dblock = &dbuf->blocks[(blkoff % PAGE_SIZE) /
sizeof(union afs_dir_block)];
ret = afs_dir_iterate_block(fpos, dblock, blkoff,
cookie, filldir);
if (ret != 1) {
afs_dir_put_page(page);
goto out;
}
blkoff += sizeof(union afs_dir_block);
} while (*fpos < dir->i_size && blkoff < limit);
afs_dir_put_page(page);
ret = 0;
}
out:
_leave(" = %d", ret);
return ret;
} /* end afs_dir_iterate() */
/*****************************************************************************/
/*
* read an AFS directory
*/
static int afs_dir_readdir(struct file *file, void *cookie, filldir_t filldir)
{
unsigned fpos;
int ret;
_enter("{%Ld,{%lu}}", file->f_pos, file->f_dentry->d_inode->i_ino);
fpos = file->f_pos;
ret = afs_dir_iterate(file->f_dentry->d_inode, &fpos, cookie, filldir);
file->f_pos = fpos;
_leave(" = %d", ret);
return ret;
} /* end afs_dir_readdir() */
/*****************************************************************************/
/*
* search the directory for a name
* - if afs_dir_iterate_block() spots this function, it'll pass the FID
* uniquifier through dtype
*/
static int afs_dir_lookup_filldir(void *_cookie, const char *name, int nlen,
loff_t fpos, ino_t ino, unsigned dtype)
{
struct afs_dir_lookup_cookie *cookie = _cookie;
_enter("{%s,%Zu},%s,%u,,%lu,%u",
cookie->name, cookie->nlen, name, nlen, ino, dtype);
if (cookie->nlen != nlen || memcmp(cookie->name, name, nlen) != 0) {
_leave(" = 0 [no]");
return 0;
}
cookie->fid.vnode = ino;
cookie->fid.unique = dtype;
cookie->found = 1;
_leave(" = -1 [found]");
return -1;
} /* end afs_dir_lookup_filldir() */
/*****************************************************************************/
/*
* look up an entry in a directory
*/
static struct dentry *afs_dir_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *nd)
{
struct afs_dir_lookup_cookie cookie;
struct afs_super_info *as;
struct afs_vnode *vnode;
struct inode *inode;
unsigned fpos;
int ret;
_enter("{%lu},%p{%s}", dir->i_ino, dentry, dentry->d_name.name);
/* insanity checks first */
BUG_ON(sizeof(union afs_dir_block) != 2048);
BUG_ON(sizeof(union afs_dirent) != 32);
if (dentry->d_name.len > 255) {
_leave(" = -ENAMETOOLONG");
return ERR_PTR(-ENAMETOOLONG);
}
vnode = AFS_FS_I(dir);
if (vnode->flags & AFS_VNODE_DELETED) {
_leave(" = -ESTALE");
return ERR_PTR(-ESTALE);
}
as = dir->i_sb->s_fs_info;
/* search the directory */
cookie.name = dentry->d_name.name;
cookie.nlen = dentry->d_name.len;
cookie.fid.vid = as->volume->vid;
cookie.found = 0;
fpos = 0;
ret = afs_dir_iterate(dir, &fpos, &cookie, afs_dir_lookup_filldir);
if (ret < 0) {
_leave(" = %d", ret);
return ERR_PTR(ret);
}
ret = -ENOENT;
if (!cookie.found) {
_leave(" = %d", ret);
return ERR_PTR(ret);
}
/* instantiate the dentry */
ret = afs_iget(dir->i_sb, &cookie.fid, &inode);
if (ret < 0) {
_leave(" = %d", ret);
return ERR_PTR(ret);
}
dentry->d_op = &afs_fs_dentry_operations;
dentry->d_fsdata = (void *) (unsigned long) vnode->status.version;
d_add(dentry, inode);
_leave(" = 0 { vn=%u u=%u } -> { ino=%lu v=%lu }",
cookie.fid.vnode,
cookie.fid.unique,
dentry->d_inode->i_ino,
dentry->d_inode->i_version);
return NULL;
} /* end afs_dir_lookup() */
/*****************************************************************************/
/*
* check that a dentry lookup hit has found a valid entry
* - NOTE! the hit can be a negative hit too, so we can't assume we have an
* inode
* (derived from nfs_lookup_revalidate)
*/
static int afs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
{
struct afs_dir_lookup_cookie cookie;
struct dentry *parent;
struct inode *inode, *dir;
unsigned fpos;
int ret;
_enter("{sb=%p n=%s},", dentry->d_sb, dentry->d_name.name);
/* lock down the parent dentry so we can peer at it */
parent = dget_parent(dentry->d_parent);
dir = parent->d_inode;
inode = dentry->d_inode;
/* handle a negative dentry */
if (!inode)
goto out_bad;
/* handle a bad inode */
if (is_bad_inode(inode)) {
printk("kAFS: afs_d_revalidate: %s/%s has bad inode\n",
dentry->d_parent->d_name.name, dentry->d_name.name);
goto out_bad;
}
/* force a full look up if the parent directory changed since last the
* server was consulted
* - otherwise this inode must still exist, even if the inode details
* themselves have changed
*/
if (AFS_FS_I(dir)->flags & AFS_VNODE_CHANGED)
afs_vnode_fetch_status(AFS_FS_I(dir));
if (AFS_FS_I(dir)->flags & AFS_VNODE_DELETED) {
_debug("%s: parent dir deleted", dentry->d_name.name);
goto out_bad;
}
if (AFS_FS_I(inode)->flags & AFS_VNODE_DELETED) {
_debug("%s: file already deleted", dentry->d_name.name);
goto out_bad;
}
if ((unsigned long) dentry->d_fsdata !=
(unsigned long) AFS_FS_I(dir)->status.version) {
_debug("%s: parent changed %lu -> %u",
dentry->d_name.name,
(unsigned long) dentry->d_fsdata,
(unsigned) AFS_FS_I(dir)->status.version);
/* search the directory for this vnode */
cookie.name = dentry->d_name.name;
cookie.nlen = dentry->d_name.len;
cookie.fid.vid = AFS_FS_I(inode)->volume->vid;
cookie.found = 0;
fpos = 0;
ret = afs_dir_iterate(dir, &fpos, &cookie,
afs_dir_lookup_filldir);
if (ret < 0) {
_debug("failed to iterate dir %s: %d",
parent->d_name.name, ret);
goto out_bad;
}
if (!cookie.found) {
_debug("%s: dirent not found", dentry->d_name.name);
goto not_found;
}
/* if the vnode ID has changed, then the dirent points to a
* different file */
if (cookie.fid.vnode != AFS_FS_I(inode)->fid.vnode) {
_debug("%s: dirent changed", dentry->d_name.name);
goto not_found;
}
/* if the vnode ID uniqifier has changed, then the file has
* been deleted */
if (cookie.fid.unique != AFS_FS_I(inode)->fid.unique) {
_debug("%s: file deleted (uq %u -> %u I:%lu)",
dentry->d_name.name,
cookie.fid.unique,
AFS_FS_I(inode)->fid.unique,
inode->i_version);
spin_lock(&AFS_FS_I(inode)->lock);
AFS_FS_I(inode)->flags |= AFS_VNODE_DELETED;
spin_unlock(&AFS_FS_I(inode)->lock);
invalidate_remote_inode(inode);
goto out_bad;
}
dentry->d_fsdata =
(void *) (unsigned long) AFS_FS_I(dir)->status.version;
}
out_valid:
dput(parent);
_leave(" = 1 [valid]");
return 1;
/* the dirent, if it exists, now points to a different vnode */
not_found:
spin_lock(&dentry->d_lock);
dentry->d_flags |= DCACHE_NFSFS_RENAMED;
spin_unlock(&dentry->d_lock);
out_bad:
if (inode) {
/* don't unhash if we have submounts */
if (have_submounts(dentry))
goto out_valid;
}
shrink_dcache_parent(dentry);
_debug("dropping dentry %s/%s",
dentry->d_parent->d_name.name, dentry->d_name.name);
d_drop(dentry);
dput(parent);
_leave(" = 0 [bad]");
return 0;
} /* end afs_d_revalidate() */
/*****************************************************************************/
/*
* allow the VFS to enquire as to whether a dentry should be unhashed (mustn't
* sleep)
* - called from dput() when d_count is going to 0.
* - return 1 to request dentry be unhashed, 0 otherwise
*/
static int afs_d_delete(struct dentry *dentry)
{
_enter("%s", dentry->d_name.name);
if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
goto zap;
if (dentry->d_inode) {
if (AFS_FS_I(dentry->d_inode)->flags & AFS_VNODE_DELETED)
goto zap;
}
_leave(" = 0 [keep]");
return 0;
zap:
_leave(" = 1 [zap]");
return 1;
} /* end afs_d_delete() */

34
fs/afs/errors.h Normal file
View File

@@ -0,0 +1,34 @@
/* errors.h: AFS abort/error codes
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_ERRORS_H
#define _LINUX_AFS_ERRORS_H
#include "types.h"
/* file server abort codes */
typedef enum {
VSALVAGE = 101, /* volume needs salvaging */
VNOVNODE = 102, /* no such file/dir (vnode) */
VNOVOL = 103, /* no such volume or volume unavailable */
VVOLEXISTS = 104, /* volume name already exists */
VNOSERVICE = 105, /* volume not currently in service */
VOFFLINE = 106, /* volume is currently offline (more info available [VVL-spec]) */
VONLINE = 107, /* volume is already online */
VDISKFULL = 108, /* disk partition is full */
VOVERQUOTA = 109, /* volume's maximum quota exceeded */
VBUSY = 110, /* volume is temporarily unavailable */
VMOVED = 111, /* volume moved to new server - ask this FS where */
} afs_rxfs_abort_t;
extern int afs_abort_to_error(int abortcode);
#endif /* _LINUX_AFS_ERRORS_H */

305
fs/afs/file.c Normal file
View File

@@ -0,0 +1,305 @@
/* file.c: AFS filesystem file handling
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/buffer_head.h>
#include "volume.h"
#include "vnode.h"
#include <rxrpc/call.h>
#include "internal.h"
#if 0
static int afs_file_open(struct inode *inode, struct file *file);
static int afs_file_release(struct inode *inode, struct file *file);
#endif
static int afs_file_readpage(struct file *file, struct page *page);
static int afs_file_invalidatepage(struct page *page, unsigned long offset);
static int afs_file_releasepage(struct page *page, int gfp_flags);
static ssize_t afs_file_write(struct file *file, const char __user *buf,
size_t size, loff_t *off);
struct inode_operations afs_file_inode_operations = {
.getattr = afs_inode_getattr,
};
struct file_operations afs_file_file_operations = {
.read = generic_file_read,
.write = afs_file_write,
.mmap = generic_file_mmap,
#if 0
.open = afs_file_open,
.release = afs_file_release,
.fsync = afs_file_fsync,
#endif
};
struct address_space_operations afs_fs_aops = {
.readpage = afs_file_readpage,
.sync_page = block_sync_page,
.set_page_dirty = __set_page_dirty_nobuffers,
.releasepage = afs_file_releasepage,
.invalidatepage = afs_file_invalidatepage,
};
/*****************************************************************************/
/*
* AFS file write
*/
static ssize_t afs_file_write(struct file *file, const char __user *buf,
size_t size, loff_t *off)
{
struct afs_vnode *vnode;
vnode = AFS_FS_I(file->f_dentry->d_inode);
if (vnode->flags & AFS_VNODE_DELETED)
return -ESTALE;
return -EIO;
} /* end afs_file_write() */
/*****************************************************************************/
/*
* deal with notification that a page was read from the cache
*/
#ifdef AFS_CACHING_SUPPORT
static void afs_file_readpage_read_complete(void *cookie_data,
struct page *page,
void *data,
int error)
{
_enter("%p,%p,%p,%d", cookie_data, page, data, error);
if (error)
SetPageError(page);
else
SetPageUptodate(page);
unlock_page(page);
} /* end afs_file_readpage_read_complete() */
#endif
/*****************************************************************************/
/*
* deal with notification that a page was written to the cache
*/
#ifdef AFS_CACHING_SUPPORT
static void afs_file_readpage_write_complete(void *cookie_data,
struct page *page,
void *data,
int error)
{
_enter("%p,%p,%p,%d", cookie_data, page, data, error);
unlock_page(page);
} /* end afs_file_readpage_write_complete() */
#endif
/*****************************************************************************/
/*
* AFS read page from file (or symlink)
*/
static int afs_file_readpage(struct file *file, struct page *page)
{
struct afs_rxfs_fetch_descriptor desc;
#ifdef AFS_CACHING_SUPPORT
struct cachefs_page *pageio;
#endif
struct afs_vnode *vnode;
struct inode *inode;
int ret;
inode = page->mapping->host;
_enter("{%lu},{%lu}", inode->i_ino, page->index);
vnode = AFS_FS_I(inode);
if (!PageLocked(page))
PAGE_BUG(page);
ret = -ESTALE;
if (vnode->flags & AFS_VNODE_DELETED)
goto error;
#ifdef AFS_CACHING_SUPPORT
ret = cachefs_page_get_private(page, &pageio, GFP_NOIO);
if (ret < 0)
goto error;
/* is it cached? */
ret = cachefs_read_or_alloc_page(vnode->cache,
page,
afs_file_readpage_read_complete,
NULL,
GFP_KERNEL);
#else
ret = -ENOBUFS;
#endif
switch (ret) {
/* read BIO submitted and wb-journal entry found */
case 1:
BUG(); // TODO - handle wb-journal match
/* read BIO submitted (page in cache) */
case 0:
break;
/* no page available in cache */
case -ENOBUFS:
case -ENODATA:
default:
desc.fid = vnode->fid;
desc.offset = page->index << PAGE_CACHE_SHIFT;
desc.size = min((size_t) (inode->i_size - desc.offset),
(size_t) PAGE_SIZE);
desc.buffer = kmap(page);
clear_page(desc.buffer);
/* read the contents of the file from the server into the
* page */
ret = afs_vnode_fetch_data(vnode, &desc);
kunmap(page);
if (ret < 0) {
if (ret==-ENOENT) {
_debug("got NOENT from server"
" - marking file deleted and stale");
vnode->flags |= AFS_VNODE_DELETED;
ret = -ESTALE;
}
#ifdef AFS_CACHING_SUPPORT
cachefs_uncache_page(vnode->cache, page);
#endif
goto error;
}
SetPageUptodate(page);
#ifdef AFS_CACHING_SUPPORT
if (cachefs_write_page(vnode->cache,
page,
afs_file_readpage_write_complete,
NULL,
GFP_KERNEL) != 0
) {
cachefs_uncache_page(vnode->cache, page);
unlock_page(page);
}
#else
unlock_page(page);
#endif
}
_leave(" = 0");
return 0;
error:
SetPageError(page);
unlock_page(page);
_leave(" = %d", ret);
return ret;
} /* end afs_file_readpage() */
/*****************************************************************************/
/*
* get a page cookie for the specified page
*/
#ifdef AFS_CACHING_SUPPORT
int afs_cache_get_page_cookie(struct page *page,
struct cachefs_page **_page_cookie)
{
int ret;
_enter("");
ret = cachefs_page_get_private(page,_page_cookie, GFP_NOIO);
_leave(" = %d", ret);
return ret;
} /* end afs_cache_get_page_cookie() */
#endif
/*****************************************************************************/
/*
* invalidate part or all of a page
*/
static int afs_file_invalidatepage(struct page *page, unsigned long offset)
{
int ret = 1;
_enter("{%lu},%lu", page->index, offset);
BUG_ON(!PageLocked(page));
if (PagePrivate(page)) {
#ifdef AFS_CACHING_SUPPORT
struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
cachefs_uncache_page(vnode->cache,page);
#endif
/* We release buffers only if the entire page is being
* invalidated.
* The get_block cached value has been unconditionally
* invalidated, so real IO is not possible anymore.
*/
if (offset == 0) {
BUG_ON(!PageLocked(page));
ret = 0;
if (!PageWriteback(page))
ret = page->mapping->a_ops->releasepage(page,
0);
}
}
_leave(" = %d", ret);
return ret;
} /* end afs_file_invalidatepage() */
/*****************************************************************************/
/*
* release a page and cleanup its private data
*/
static int afs_file_releasepage(struct page *page, int gfp_flags)
{
struct cachefs_page *pageio;
_enter("{%lu},%x", page->index, gfp_flags);
if (PagePrivate(page)) {
#ifdef AFS_CACHING_SUPPORT
struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
cachefs_uncache_page(vnode->cache, page);
#endif
pageio = (struct cachefs_page *) page->private;
page->private = 0;
ClearPagePrivate(page);
if (pageio)
kfree(pageio);
}
_leave(" = 0");
return 0;
} /* end afs_file_releasepage() */

837
fs/afs/fsclient.c Normal file
View File

@@ -0,0 +1,837 @@
/* fsclient.c: AFS File Server client stubs
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/init.h>
#include <linux/sched.h>
#include <rxrpc/rxrpc.h>
#include <rxrpc/transport.h>
#include <rxrpc/connection.h>
#include <rxrpc/call.h>
#include "fsclient.h"
#include "cmservice.h"
#include "vnode.h"
#include "server.h"
#include "errors.h"
#include "internal.h"
#define FSFETCHSTATUS 132 /* AFS Fetch file status */
#define FSFETCHDATA 130 /* AFS Fetch file data */
#define FSGIVEUPCALLBACKS 147 /* AFS Discard callback promises */
#define FSGETVOLUMEINFO 148 /* AFS Get root volume information */
#define FSGETROOTVOLUME 151 /* AFS Get root volume name */
#define FSLOOKUP 161 /* AFS lookup file in directory */
/*****************************************************************************/
/*
* map afs abort codes to/from Linux error codes
* - called with call->lock held
*/
static void afs_rxfs_aemap(struct rxrpc_call *call)
{
switch (call->app_err_state) {
case RXRPC_ESTATE_LOCAL_ABORT:
call->app_abort_code = -call->app_errno;
break;
case RXRPC_ESTATE_PEER_ABORT:
call->app_errno = afs_abort_to_error(call->app_abort_code);
break;
default:
break;
}
} /* end afs_rxfs_aemap() */
/*****************************************************************************/
/*
* get the root volume name from a fileserver
* - this operation doesn't seem to work correctly in OpenAFS server 1.2.2
*/
#if 0
int afs_rxfs_get_root_volume(struct afs_server *server,
char *buf, size_t *buflen)
{
struct rxrpc_connection *conn;
struct rxrpc_call *call;
struct kvec piov[2];
size_t sent;
int ret;
u32 param[1];
DECLARE_WAITQUEUE(myself, current);
kenter("%p,%p,%u",server, buf, *buflen);
/* get hold of the fileserver connection */
ret = afs_server_get_fsconn(server, &conn);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = FSGETROOTVOLUME;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq, &myself);
/* marshall the parameters */
param[0] = htonl(FSGETROOTVOLUME);
piov[0].iov_len = sizeof(param);
piov[0].iov_base = param;
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0)
goto abort;
/* wait for the reply to completely arrive */
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY ||
signal_pending(current))
break;
schedule();
}
set_current_state(TASK_RUNNING);
ret = -EINTR;
if (signal_pending(current))
goto abort;
switch (call->app_call_state) {
case RXRPC_CSTATE_ERROR:
ret = call->app_errno;
kdebug("Got Error: %d", ret);
goto out_unwait;
case RXRPC_CSTATE_CLNT_GOT_REPLY:
/* read the reply */
kdebug("Got Reply: qty=%d", call->app_ready_qty);
ret = -EBADMSG;
if (call->app_ready_qty <= 4)
goto abort;
ret = rxrpc_call_read_data(call, NULL, call->app_ready_qty, 0);
if (ret < 0)
goto abort;
#if 0
/* unmarshall the reply */
bp = buffer;
for (loop = 0; loop < 65; loop++)
entry->name[loop] = ntohl(*bp++);
entry->name[64] = 0;
entry->type = ntohl(*bp++);
entry->num_servers = ntohl(*bp++);
for (loop = 0; loop < 8; loop++)
entry->servers[loop].addr.s_addr = *bp++;
for (loop = 0; loop < 8; loop++)
entry->servers[loop].partition = ntohl(*bp++);
for (loop = 0; loop < 8; loop++)
entry->servers[loop].flags = ntohl(*bp++);
for (loop = 0; loop < 3; loop++)
entry->volume_ids[loop] = ntohl(*bp++);
entry->clone_id = ntohl(*bp++);
entry->flags = ntohl(*bp);
#endif
/* success */
ret = 0;
goto out_unwait;
default:
BUG();
}
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq, &myself);
rxrpc_put_call(call);
out_put_conn:
afs_server_release_fsconn(server, conn);
out:
kleave("");
return ret;
} /* end afs_rxfs_get_root_volume() */
#endif
/*****************************************************************************/
/*
* get information about a volume
*/
#if 0
int afs_rxfs_get_volume_info(struct afs_server *server,
const char *name,
struct afs_volume_info *vinfo)
{
struct rxrpc_connection *conn;
struct rxrpc_call *call;
struct kvec piov[3];
size_t sent;
int ret;
u32 param[2], *bp, zero;
DECLARE_WAITQUEUE(myself, current);
_enter("%p,%s,%p", server, name, vinfo);
/* get hold of the fileserver connection */
ret = afs_server_get_fsconn(server, &conn);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = FSGETVOLUMEINFO;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq, &myself);
/* marshall the parameters */
piov[1].iov_len = strlen(name);
piov[1].iov_base = (char *) name;
zero = 0;
piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3;
piov[2].iov_base = &zero;
param[0] = htonl(FSGETVOLUMEINFO);
param[1] = htonl(piov[1].iov_len);
piov[0].iov_len = sizeof(param);
piov[0].iov_base = param;
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0)
goto abort;
/* wait for the reply to completely arrive */
bp = rxrpc_call_alloc_scratch(call, 64);
ret = rxrpc_call_read_data(call, bp, 64,
RXRPC_CALL_READ_BLOCK |
RXRPC_CALL_READ_ALL);
if (ret < 0) {
if (ret == -ECONNABORTED) {
ret = call->app_errno;
goto out_unwait;
}
goto abort;
}
/* unmarshall the reply */
vinfo->vid = ntohl(*bp++);
vinfo->type = ntohl(*bp++);
vinfo->type_vids[0] = ntohl(*bp++);
vinfo->type_vids[1] = ntohl(*bp++);
vinfo->type_vids[2] = ntohl(*bp++);
vinfo->type_vids[3] = ntohl(*bp++);
vinfo->type_vids[4] = ntohl(*bp++);
vinfo->nservers = ntohl(*bp++);
vinfo->servers[0].addr.s_addr = *bp++;
vinfo->servers[1].addr.s_addr = *bp++;
vinfo->servers[2].addr.s_addr = *bp++;
vinfo->servers[3].addr.s_addr = *bp++;
vinfo->servers[4].addr.s_addr = *bp++;
vinfo->servers[5].addr.s_addr = *bp++;
vinfo->servers[6].addr.s_addr = *bp++;
vinfo->servers[7].addr.s_addr = *bp++;
ret = -EBADMSG;
if (vinfo->nservers > 8)
goto abort;
/* success */
ret = 0;
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq, &myself);
rxrpc_put_call(call);
out_put_conn:
afs_server_release_fsconn(server, conn);
out:
_leave("");
return ret;
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
goto out_unwait;
} /* end afs_rxfs_get_volume_info() */
#endif
/*****************************************************************************/
/*
* fetch the status information for a file
*/
int afs_rxfs_fetch_file_status(struct afs_server *server,
struct afs_vnode *vnode,
struct afs_volsync *volsync)
{
struct afs_server_callslot callslot;
struct rxrpc_call *call;
struct kvec piov[1];
size_t sent;
int ret;
__be32 *bp;
DECLARE_WAITQUEUE(myself, current);
_enter("%p,{%u,%u,%u}",
server, vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique);
/* get hold of the fileserver connection */
ret = afs_server_request_callslot(server, &callslot);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap,
&call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = FSFETCHSTATUS;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq, &myself);
/* marshall the parameters */
bp = rxrpc_call_alloc_scratch(call, 16);
bp[0] = htonl(FSFETCHSTATUS);
bp[1] = htonl(vnode->fid.vid);
bp[2] = htonl(vnode->fid.vnode);
bp[3] = htonl(vnode->fid.unique);
piov[0].iov_len = 16;
piov[0].iov_base = bp;
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0)
goto abort;
/* wait for the reply to completely arrive */
bp = rxrpc_call_alloc_scratch(call, 120);
ret = rxrpc_call_read_data(call, bp, 120,
RXRPC_CALL_READ_BLOCK |
RXRPC_CALL_READ_ALL);
if (ret < 0) {
if (ret == -ECONNABORTED) {
ret = call->app_errno;
goto out_unwait;
}
goto abort;
}
/* unmarshall the reply */
vnode->status.if_version = ntohl(*bp++);
vnode->status.type = ntohl(*bp++);
vnode->status.nlink = ntohl(*bp++);
vnode->status.size = ntohl(*bp++);
vnode->status.version = ntohl(*bp++);
vnode->status.author = ntohl(*bp++);
vnode->status.owner = ntohl(*bp++);
vnode->status.caller_access = ntohl(*bp++);
vnode->status.anon_access = ntohl(*bp++);
vnode->status.mode = ntohl(*bp++);
vnode->status.parent.vid = vnode->fid.vid;
vnode->status.parent.vnode = ntohl(*bp++);
vnode->status.parent.unique = ntohl(*bp++);
bp++; /* seg size */
vnode->status.mtime_client = ntohl(*bp++);
vnode->status.mtime_server = ntohl(*bp++);
bp++; /* group */
bp++; /* sync counter */
vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32;
bp++; /* spare2 */
bp++; /* spare3 */
bp++; /* spare4 */
vnode->cb_version = ntohl(*bp++);
vnode->cb_expiry = ntohl(*bp++);
vnode->cb_type = ntohl(*bp++);
if (volsync) {
volsync->creation = ntohl(*bp++);
bp++; /* spare2 */
bp++; /* spare3 */
bp++; /* spare4 */
bp++; /* spare5 */
bp++; /* spare6 */
}
/* success */
ret = 0;
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq, &myself);
rxrpc_put_call(call);
out_put_conn:
afs_server_release_callslot(server, &callslot);
out:
_leave("");
return ret;
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
goto out_unwait;
} /* end afs_rxfs_fetch_file_status() */
/*****************************************************************************/
/*
* fetch the contents of a file or directory
*/
int afs_rxfs_fetch_file_data(struct afs_server *server,
struct afs_vnode *vnode,
struct afs_rxfs_fetch_descriptor *desc,
struct afs_volsync *volsync)
{
struct afs_server_callslot callslot;
struct rxrpc_call *call;
struct kvec piov[1];
size_t sent;
int ret;
__be32 *bp;
DECLARE_WAITQUEUE(myself, current);
_enter("%p,{fid={%u,%u,%u},sz=%Zu,of=%lu}",
server,
desc->fid.vid,
desc->fid.vnode,
desc->fid.unique,
desc->size,
desc->offset);
/* get hold of the fileserver connection */
ret = afs_server_request_callslot(server, &callslot);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap, &call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = FSFETCHDATA;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq, &myself);
/* marshall the parameters */
bp = rxrpc_call_alloc_scratch(call, 24);
bp[0] = htonl(FSFETCHDATA);
bp[1] = htonl(desc->fid.vid);
bp[2] = htonl(desc->fid.vnode);
bp[3] = htonl(desc->fid.unique);
bp[4] = htonl(desc->offset);
bp[5] = htonl(desc->size);
piov[0].iov_len = 24;
piov[0].iov_base = bp;
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0)
goto abort;
/* wait for the data count to arrive */
ret = rxrpc_call_read_data(call, bp, 4, RXRPC_CALL_READ_BLOCK);
if (ret < 0)
goto read_failed;
desc->actual = ntohl(bp[0]);
if (desc->actual != desc->size) {
ret = -EBADMSG;
goto abort;
}
/* call the app to read the actual data */
rxrpc_call_reset_scratch(call);
ret = rxrpc_call_read_data(call, desc->buffer, desc->actual,
RXRPC_CALL_READ_BLOCK);
if (ret < 0)
goto read_failed;
/* wait for the rest of the reply to completely arrive */
rxrpc_call_reset_scratch(call);
bp = rxrpc_call_alloc_scratch(call, 120);
ret = rxrpc_call_read_data(call, bp, 120,
RXRPC_CALL_READ_BLOCK |
RXRPC_CALL_READ_ALL);
if (ret < 0)
goto read_failed;
/* unmarshall the reply */
vnode->status.if_version = ntohl(*bp++);
vnode->status.type = ntohl(*bp++);
vnode->status.nlink = ntohl(*bp++);
vnode->status.size = ntohl(*bp++);
vnode->status.version = ntohl(*bp++);
vnode->status.author = ntohl(*bp++);
vnode->status.owner = ntohl(*bp++);
vnode->status.caller_access = ntohl(*bp++);
vnode->status.anon_access = ntohl(*bp++);
vnode->status.mode = ntohl(*bp++);
vnode->status.parent.vid = desc->fid.vid;
vnode->status.parent.vnode = ntohl(*bp++);
vnode->status.parent.unique = ntohl(*bp++);
bp++; /* seg size */
vnode->status.mtime_client = ntohl(*bp++);
vnode->status.mtime_server = ntohl(*bp++);
bp++; /* group */
bp++; /* sync counter */
vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32;
bp++; /* spare2 */
bp++; /* spare3 */
bp++; /* spare4 */
vnode->cb_version = ntohl(*bp++);
vnode->cb_expiry = ntohl(*bp++);
vnode->cb_type = ntohl(*bp++);
if (volsync) {
volsync->creation = ntohl(*bp++);
bp++; /* spare2 */
bp++; /* spare3 */
bp++; /* spare4 */
bp++; /* spare5 */
bp++; /* spare6 */
}
/* success */
ret = 0;
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq,&myself);
rxrpc_put_call(call);
out_put_conn:
afs_server_release_callslot(server, &callslot);
out:
_leave(" = %d", ret);
return ret;
read_failed:
if (ret == -ECONNABORTED) {
ret = call->app_errno;
goto out_unwait;
}
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
goto out_unwait;
} /* end afs_rxfs_fetch_file_data() */
/*****************************************************************************/
/*
* ask the AFS fileserver to discard a callback request on a file
*/
int afs_rxfs_give_up_callback(struct afs_server *server,
struct afs_vnode *vnode)
{
struct afs_server_callslot callslot;
struct rxrpc_call *call;
struct kvec piov[1];
size_t sent;
int ret;
__be32 *bp;
DECLARE_WAITQUEUE(myself, current);
_enter("%p,{%u,%u,%u}",
server, vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique);
/* get hold of the fileserver connection */
ret = afs_server_request_callslot(server, &callslot);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap, &call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = FSGIVEUPCALLBACKS;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq, &myself);
/* marshall the parameters */
bp = rxrpc_call_alloc_scratch(call, (1 + 4 + 4) * 4);
piov[0].iov_len = (1 + 4 + 4) * 4;
piov[0].iov_base = bp;
*bp++ = htonl(FSGIVEUPCALLBACKS);
*bp++ = htonl(1);
*bp++ = htonl(vnode->fid.vid);
*bp++ = htonl(vnode->fid.vnode);
*bp++ = htonl(vnode->fid.unique);
*bp++ = htonl(1);
*bp++ = htonl(vnode->cb_version);
*bp++ = htonl(vnode->cb_expiry);
*bp++ = htonl(vnode->cb_type);
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0)
goto abort;
/* wait for the reply to completely arrive */
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY ||
signal_pending(current))
break;
schedule();
}
set_current_state(TASK_RUNNING);
ret = -EINTR;
if (signal_pending(current))
goto abort;
switch (call->app_call_state) {
case RXRPC_CSTATE_ERROR:
ret = call->app_errno;
goto out_unwait;
case RXRPC_CSTATE_CLNT_GOT_REPLY:
ret = 0;
goto out_unwait;
default:
BUG();
}
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq, &myself);
rxrpc_put_call(call);
out_put_conn:
afs_server_release_callslot(server, &callslot);
out:
_leave("");
return ret;
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
goto out_unwait;
} /* end afs_rxfs_give_up_callback() */
/*****************************************************************************/
/*
* look a filename up in a directory
* - this operation doesn't seem to work correctly in OpenAFS server 1.2.2
*/
#if 0
int afs_rxfs_lookup(struct afs_server *server,
struct afs_vnode *dir,
const char *filename,
struct afs_vnode *vnode,
struct afs_volsync *volsync)
{
struct rxrpc_connection *conn;
struct rxrpc_call *call;
struct kvec piov[3];
size_t sent;
int ret;
u32 *bp, zero;
DECLARE_WAITQUEUE(myself, current);
kenter("%p,{%u,%u,%u},%s",
server, fid->vid, fid->vnode, fid->unique, filename);
/* get hold of the fileserver connection */
ret = afs_server_get_fsconn(server, &conn);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = FSLOOKUP;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq,&myself);
/* marshall the parameters */
bp = rxrpc_call_alloc_scratch(call, 20);
zero = 0;
piov[0].iov_len = 20;
piov[0].iov_base = bp;
piov[1].iov_len = strlen(filename);
piov[1].iov_base = (char *) filename;
piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3;
piov[2].iov_base = &zero;
*bp++ = htonl(FSLOOKUP);
*bp++ = htonl(dirfid->vid);
*bp++ = htonl(dirfid->vnode);
*bp++ = htonl(dirfid->unique);
*bp++ = htonl(piov[1].iov_len);
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0)
goto abort;
/* wait for the reply to completely arrive */
bp = rxrpc_call_alloc_scratch(call, 220);
ret = rxrpc_call_read_data(call, bp, 220,
RXRPC_CALL_READ_BLOCK |
RXRPC_CALL_READ_ALL);
if (ret < 0) {
if (ret == -ECONNABORTED) {
ret = call->app_errno;
goto out_unwait;
}
goto abort;
}
/* unmarshall the reply */
fid->vid = ntohl(*bp++);
fid->vnode = ntohl(*bp++);
fid->unique = ntohl(*bp++);
vnode->status.if_version = ntohl(*bp++);
vnode->status.type = ntohl(*bp++);
vnode->status.nlink = ntohl(*bp++);
vnode->status.size = ntohl(*bp++);
vnode->status.version = ntohl(*bp++);
vnode->status.author = ntohl(*bp++);
vnode->status.owner = ntohl(*bp++);
vnode->status.caller_access = ntohl(*bp++);
vnode->status.anon_access = ntohl(*bp++);
vnode->status.mode = ntohl(*bp++);
vnode->status.parent.vid = dirfid->vid;
vnode->status.parent.vnode = ntohl(*bp++);
vnode->status.parent.unique = ntohl(*bp++);
bp++; /* seg size */
vnode->status.mtime_client = ntohl(*bp++);
vnode->status.mtime_server = ntohl(*bp++);
bp++; /* group */
bp++; /* sync counter */
vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32;
bp++; /* spare2 */
bp++; /* spare3 */
bp++; /* spare4 */
dir->status.if_version = ntohl(*bp++);
dir->status.type = ntohl(*bp++);
dir->status.nlink = ntohl(*bp++);
dir->status.size = ntohl(*bp++);
dir->status.version = ntohl(*bp++);
dir->status.author = ntohl(*bp++);
dir->status.owner = ntohl(*bp++);
dir->status.caller_access = ntohl(*bp++);
dir->status.anon_access = ntohl(*bp++);
dir->status.mode = ntohl(*bp++);
dir->status.parent.vid = dirfid->vid;
dir->status.parent.vnode = ntohl(*bp++);
dir->status.parent.unique = ntohl(*bp++);
bp++; /* seg size */
dir->status.mtime_client = ntohl(*bp++);
dir->status.mtime_server = ntohl(*bp++);
bp++; /* group */
bp++; /* sync counter */
dir->status.version |= ((unsigned long long) ntohl(*bp++)) << 32;
bp++; /* spare2 */
bp++; /* spare3 */
bp++; /* spare4 */
callback->fid = *fid;
callback->version = ntohl(*bp++);
callback->expiry = ntohl(*bp++);
callback->type = ntohl(*bp++);
if (volsync) {
volsync->creation = ntohl(*bp++);
bp++; /* spare2 */
bp++; /* spare3 */
bp++; /* spare4 */
bp++; /* spare5 */
bp++; /* spare6 */
}
/* success */
ret = 0;
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq, &myself);
rxrpc_put_call(call);
out_put_conn:
afs_server_release_fsconn(server, conn);
out:
kleave("");
return ret;
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
goto out_unwait;
} /* end afs_rxfs_lookup() */
#endif

54
fs/afs/fsclient.h Normal file
View File

@@ -0,0 +1,54 @@
/* fsclient.h: AFS File Server client stub declarations
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_FSCLIENT_H
#define _LINUX_AFS_FSCLIENT_H
#include "server.h"
extern int afs_rxfs_get_volume_info(struct afs_server *server,
const char *name,
struct afs_volume_info *vinfo);
extern int afs_rxfs_fetch_file_status(struct afs_server *server,
struct afs_vnode *vnode,
struct afs_volsync *volsync);
struct afs_rxfs_fetch_descriptor {
struct afs_fid fid; /* file ID to fetch */
size_t size; /* total number of bytes to fetch */
off_t offset; /* offset in file to start from */
void *buffer; /* read buffer */
size_t actual; /* actual size sent back by server */
};
extern int afs_rxfs_fetch_file_data(struct afs_server *server,
struct afs_vnode *vnode,
struct afs_rxfs_fetch_descriptor *desc,
struct afs_volsync *volsync);
extern int afs_rxfs_give_up_callback(struct afs_server *server,
struct afs_vnode *vnode);
/* this doesn't appear to work in OpenAFS server */
extern int afs_rxfs_lookup(struct afs_server *server,
struct afs_vnode *dir,
const char *filename,
struct afs_vnode *vnode,
struct afs_volsync *volsync);
/* this is apparently mis-implemented in OpenAFS server */
extern int afs_rxfs_get_root_volume(struct afs_server *server,
char *buf,
size_t *buflen);
#endif /* _LINUX_AFS_FSCLIENT_H */

287
fs/afs/inode.c Normal file
View File

@@ -0,0 +1,287 @@
/*
* Copyright (c) 2002 Red Hat, Inc. All rights reserved.
*
* This software may be freely redistributed under the terms of the
* GNU General Public License.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Authors: David Woodhouse <dwmw2@cambridge.redhat.com>
* David Howells <dhowells@redhat.com>
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include "volume.h"
#include "vnode.h"
#include "super.h"
#include "internal.h"
struct afs_iget_data {
struct afs_fid fid;
struct afs_volume *volume; /* volume on which resides */
};
/*****************************************************************************/
/*
* map the AFS file status to the inode member variables
*/
static int afs_inode_map_status(struct afs_vnode *vnode)
{
struct inode *inode = AFS_VNODE_TO_I(vnode);
_debug("FS: ft=%d lk=%d sz=%Zu ver=%Lu mod=%hu",
vnode->status.type,
vnode->status.nlink,
vnode->status.size,
vnode->status.version,
vnode->status.mode);
switch (vnode->status.type) {
case AFS_FTYPE_FILE:
inode->i_mode = S_IFREG | vnode->status.mode;
inode->i_op = &afs_file_inode_operations;
inode->i_fop = &afs_file_file_operations;
break;
case AFS_FTYPE_DIR:
inode->i_mode = S_IFDIR | vnode->status.mode;
inode->i_op = &afs_dir_inode_operations;
inode->i_fop = &afs_dir_file_operations;
break;
case AFS_FTYPE_SYMLINK:
inode->i_mode = S_IFLNK | vnode->status.mode;
inode->i_op = &page_symlink_inode_operations;
break;
default:
printk("kAFS: AFS vnode with undefined type\n");
return -EBADMSG;
}
inode->i_nlink = vnode->status.nlink;
inode->i_uid = vnode->status.owner;
inode->i_gid = 0;
inode->i_size = vnode->status.size;
inode->i_ctime.tv_sec = vnode->status.mtime_server;
inode->i_ctime.tv_nsec = 0;
inode->i_atime = inode->i_mtime = inode->i_ctime;
inode->i_blksize = PAGE_CACHE_SIZE;
inode->i_blocks = 0;
inode->i_version = vnode->fid.unique;
inode->i_mapping->a_ops = &afs_fs_aops;
/* check to see whether a symbolic link is really a mountpoint */
if (vnode->status.type == AFS_FTYPE_SYMLINK) {
afs_mntpt_check_symlink(vnode);
if (vnode->flags & AFS_VNODE_MOUNTPOINT) {
inode->i_mode = S_IFDIR | vnode->status.mode;
inode->i_op = &afs_mntpt_inode_operations;
inode->i_fop = &afs_mntpt_file_operations;
}
}
return 0;
} /* end afs_inode_map_status() */
/*****************************************************************************/
/*
* attempt to fetch the status of an inode, coelescing multiple simultaneous
* fetches
*/
static int afs_inode_fetch_status(struct inode *inode)
{
struct afs_vnode *vnode;
int ret;
vnode = AFS_FS_I(inode);
ret = afs_vnode_fetch_status(vnode);
if (ret == 0)
ret = afs_inode_map_status(vnode);
return ret;
} /* end afs_inode_fetch_status() */
/*****************************************************************************/
/*
* iget5() comparator
*/
static int afs_iget5_test(struct inode *inode, void *opaque)
{
struct afs_iget_data *data = opaque;
return inode->i_ino == data->fid.vnode &&
inode->i_version == data->fid.unique;
} /* end afs_iget5_test() */
/*****************************************************************************/
/*
* iget5() inode initialiser
*/
static int afs_iget5_set(struct inode *inode, void *opaque)
{
struct afs_iget_data *data = opaque;
struct afs_vnode *vnode = AFS_FS_I(inode);
inode->i_ino = data->fid.vnode;
inode->i_version = data->fid.unique;
vnode->fid = data->fid;
vnode->volume = data->volume;
return 0;
} /* end afs_iget5_set() */
/*****************************************************************************/
/*
* inode retrieval
*/
inline int afs_iget(struct super_block *sb, struct afs_fid *fid,
struct inode **_inode)
{
struct afs_iget_data data = { .fid = *fid };
struct afs_super_info *as;
struct afs_vnode *vnode;
struct inode *inode;
int ret;
_enter(",{%u,%u,%u},,", fid->vid, fid->vnode, fid->unique);
as = sb->s_fs_info;
data.volume = as->volume;
inode = iget5_locked(sb, fid->vnode, afs_iget5_test, afs_iget5_set,
&data);
if (!inode) {
_leave(" = -ENOMEM");
return -ENOMEM;
}
vnode = AFS_FS_I(inode);
/* deal with an existing inode */
if (!(inode->i_state & I_NEW)) {
ret = afs_vnode_fetch_status(vnode);
if (ret==0)
*_inode = inode;
else
iput(inode);
_leave(" = %d", ret);
return ret;
}
#ifdef AFS_CACHING_SUPPORT
/* set up caching before reading the status, as fetch-status reads the
* first page of symlinks to see if they're really mntpts */
cachefs_acquire_cookie(vnode->volume->cache,
NULL,
vnode,
&vnode->cache);
#endif
/* okay... it's a new inode */
inode->i_flags |= S_NOATIME;
vnode->flags |= AFS_VNODE_CHANGED;
ret = afs_inode_fetch_status(inode);
if (ret<0)
goto bad_inode;
/* success */
unlock_new_inode(inode);
*_inode = inode;
_leave(" = 0 [CB { v=%u x=%lu t=%u }]",
vnode->cb_version,
vnode->cb_timeout.timo_jif,
vnode->cb_type);
return 0;
/* failure */
bad_inode:
make_bad_inode(inode);
unlock_new_inode(inode);
iput(inode);
_leave(" = %d [bad]", ret);
return ret;
} /* end afs_iget() */
/*****************************************************************************/
/*
* read the attributes of an inode
*/
int afs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry,
struct kstat *stat)
{
struct afs_vnode *vnode;
struct inode *inode;
int ret;
inode = dentry->d_inode;
_enter("{ ino=%lu v=%lu }", inode->i_ino, inode->i_version);
vnode = AFS_FS_I(inode);
ret = afs_inode_fetch_status(inode);
if (ret == -ENOENT) {
_leave(" = %d [%d %p]",
ret, atomic_read(&dentry->d_count), dentry->d_inode);
return ret;
}
else if (ret < 0) {
make_bad_inode(inode);
_leave(" = %d", ret);
return ret;
}
/* transfer attributes from the inode structure to the stat
* structure */
generic_fillattr(inode, stat);
_leave(" = 0 CB { v=%u x=%u t=%u }",
vnode->cb_version,
vnode->cb_expiry,
vnode->cb_type);
return 0;
} /* end afs_inode_getattr() */
/*****************************************************************************/
/*
* clear an AFS inode
*/
void afs_clear_inode(struct inode *inode)
{
struct afs_vnode *vnode;
vnode = AFS_FS_I(inode);
_enter("ino=%lu { vn=%08x v=%u x=%u t=%u }",
inode->i_ino,
vnode->fid.vnode,
vnode->cb_version,
vnode->cb_expiry,
vnode->cb_type
);
BUG_ON(inode->i_ino != vnode->fid.vnode);
afs_vnode_give_up_callback(vnode);
#ifdef AFS_CACHING_SUPPORT
cachefs_relinquish_cookie(vnode->cache, 0);
vnode->cache = NULL;
#endif
_leave("");
} /* end afs_clear_inode() */

140
fs/afs/internal.h Normal file
View File

@@ -0,0 +1,140 @@
/* internal.h: internal AFS stuff
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef AFS_INTERNAL_H
#define AFS_INTERNAL_H
#include <linux/compiler.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
/*
* debug tracing
*/
#define kenter(FMT, a...) printk("==> %s("FMT")\n",__FUNCTION__ , ## a)
#define kleave(FMT, a...) printk("<== %s()"FMT"\n",__FUNCTION__ , ## a)
#define kdebug(FMT, a...) printk(FMT"\n" , ## a)
#define kproto(FMT, a...) printk("### "FMT"\n" , ## a)
#define knet(FMT, a...) printk(FMT"\n" , ## a)
#ifdef __KDEBUG
#define _enter(FMT, a...) kenter(FMT , ## a)
#define _leave(FMT, a...) kleave(FMT , ## a)
#define _debug(FMT, a...) kdebug(FMT , ## a)
#define _proto(FMT, a...) kproto(FMT , ## a)
#define _net(FMT, a...) knet(FMT , ## a)
#else
#define _enter(FMT, a...) do { } while(0)
#define _leave(FMT, a...) do { } while(0)
#define _debug(FMT, a...) do { } while(0)
#define _proto(FMT, a...) do { } while(0)
#define _net(FMT, a...) do { } while(0)
#endif
static inline void afs_discard_my_signals(void)
{
while (signal_pending(current)) {
siginfo_t sinfo;
spin_lock_irq(&current->sighand->siglock);
dequeue_signal(current,&current->blocked, &sinfo);
spin_unlock_irq(&current->sighand->siglock);
}
}
/*
* cell.c
*/
extern struct rw_semaphore afs_proc_cells_sem;
extern struct list_head afs_proc_cells;
#ifdef AFS_CACHING_SUPPORT
extern struct cachefs_index_def afs_cache_cell_index_def;
#endif
/*
* dir.c
*/
extern struct inode_operations afs_dir_inode_operations;
extern struct file_operations afs_dir_file_operations;
/*
* file.c
*/
extern struct address_space_operations afs_fs_aops;
extern struct inode_operations afs_file_inode_operations;
extern struct file_operations afs_file_file_operations;
#ifdef AFS_CACHING_SUPPORT
extern int afs_cache_get_page_cookie(struct page *page,
struct cachefs_page **_page_cookie);
#endif
/*
* inode.c
*/
extern int afs_iget(struct super_block *sb, struct afs_fid *fid,
struct inode **_inode);
extern int afs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry,
struct kstat *stat);
extern void afs_clear_inode(struct inode *inode);
/*
* key_afs.c
*/
#ifdef CONFIG_KEYS
extern int afs_key_register(void);
extern void afs_key_unregister(void);
#endif
/*
* main.c
*/
#ifdef AFS_CACHING_SUPPORT
extern struct cachefs_netfs afs_cache_netfs;
#endif
/*
* mntpt.c
*/
extern struct inode_operations afs_mntpt_inode_operations;
extern struct file_operations afs_mntpt_file_operations;
extern struct afs_timer afs_mntpt_expiry_timer;
extern struct afs_timer_ops afs_mntpt_expiry_timer_ops;
extern unsigned long afs_mntpt_expiry_timeout;
extern int afs_mntpt_check_symlink(struct afs_vnode *vnode);
/*
* super.c
*/
extern int afs_fs_init(void);
extern void afs_fs_exit(void);
#define AFS_CB_HASH_COUNT (PAGE_SIZE / sizeof(struct list_head))
extern struct list_head afs_cb_hash_tbl[];
extern spinlock_t afs_cb_hash_lock;
#define afs_cb_hash(SRV,FID) \
afs_cb_hash_tbl[((unsigned long)(SRV) + \
(FID)->vid + (FID)->vnode + (FID)->unique) % \
AFS_CB_HASH_COUNT]
/*
* proc.c
*/
extern int afs_proc_init(void);
extern void afs_proc_cleanup(void);
extern int afs_proc_cell_setup(struct afs_cell *cell);
extern void afs_proc_cell_remove(struct afs_cell *cell);
#endif /* AFS_INTERNAL_H */

257
fs/afs/kafsasyncd.c Normal file
View File

@@ -0,0 +1,257 @@
/* kafsasyncd.c: AFS asynchronous operation daemon
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*
* The AFS async daemon is used to the following:
* - probe "dead" servers to see whether they've come back to life yet.
* - probe "live" servers that we haven't talked to for a while to see if they are better
* candidates for serving than what we're currently using
* - poll volume location servers to keep up to date volume location lists
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include "cell.h"
#include "server.h"
#include "volume.h"
#include "kafsasyncd.h"
#include "kafstimod.h"
#include <rxrpc/call.h>
#include <asm/errno.h>
#include "internal.h"
static DECLARE_COMPLETION(kafsasyncd_alive);
static DECLARE_COMPLETION(kafsasyncd_dead);
static DECLARE_WAIT_QUEUE_HEAD(kafsasyncd_sleepq);
static struct task_struct *kafsasyncd_task;
static int kafsasyncd_die;
static int kafsasyncd(void *arg);
static LIST_HEAD(kafsasyncd_async_attnq);
static LIST_HEAD(kafsasyncd_async_busyq);
static DEFINE_SPINLOCK(kafsasyncd_async_lock);
static void kafsasyncd_null_call_attn_func(struct rxrpc_call *call)
{
}
static void kafsasyncd_null_call_error_func(struct rxrpc_call *call)
{
}
/*****************************************************************************/
/*
* start the async daemon
*/
int afs_kafsasyncd_start(void)
{
int ret;
ret = kernel_thread(kafsasyncd, NULL, 0);
if (ret < 0)
return ret;
wait_for_completion(&kafsasyncd_alive);
return ret;
} /* end afs_kafsasyncd_start() */
/*****************************************************************************/
/*
* stop the async daemon
*/
void afs_kafsasyncd_stop(void)
{
/* get rid of my daemon */
kafsasyncd_die = 1;
wake_up(&kafsasyncd_sleepq);
wait_for_completion(&kafsasyncd_dead);
} /* end afs_kafsasyncd_stop() */
/*****************************************************************************/
/*
* probing daemon
*/
static int kafsasyncd(void *arg)
{
struct afs_async_op *op;
int die;
DECLARE_WAITQUEUE(myself, current);
kafsasyncd_task = current;
printk("kAFS: Started kafsasyncd %d\n", current->pid);
daemonize("kafsasyncd");
complete(&kafsasyncd_alive);
/* loop around looking for things to attend to */
do {
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&kafsasyncd_sleepq, &myself);
for (;;) {
if (!list_empty(&kafsasyncd_async_attnq) ||
signal_pending(current) ||
kafsasyncd_die)
break;
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
remove_wait_queue(&kafsasyncd_sleepq, &myself);
set_current_state(TASK_RUNNING);
try_to_freeze(PF_FREEZE);
/* discard pending signals */
afs_discard_my_signals();
die = kafsasyncd_die;
/* deal with the next asynchronous operation requiring
* attention */
if (!list_empty(&kafsasyncd_async_attnq)) {
struct afs_async_op *op;
_debug("@@@ Begin Asynchronous Operation");
op = NULL;
spin_lock(&kafsasyncd_async_lock);
if (!list_empty(&kafsasyncd_async_attnq)) {
op = list_entry(kafsasyncd_async_attnq.next,
struct afs_async_op, link);
list_del(&op->link);
list_add_tail(&op->link,
&kafsasyncd_async_busyq);
}
spin_unlock(&kafsasyncd_async_lock);
_debug("@@@ Operation %p {%p}\n",
op, op ? op->ops : NULL);
if (op)
op->ops->attend(op);
_debug("@@@ End Asynchronous Operation");
}
} while(!die);
/* need to kill all outstanding asynchronous operations before
* exiting */
kafsasyncd_task = NULL;
spin_lock(&kafsasyncd_async_lock);
/* fold the busy and attention queues together */
list_splice_init(&kafsasyncd_async_busyq,
&kafsasyncd_async_attnq);
/* dequeue kafsasyncd from all their wait queues */
list_for_each_entry(op, &kafsasyncd_async_attnq, link) {
op->call->app_attn_func = kafsasyncd_null_call_attn_func;
op->call->app_error_func = kafsasyncd_null_call_error_func;
remove_wait_queue(&op->call->waitq, &op->waiter);
}
spin_unlock(&kafsasyncd_async_lock);
/* abort all the operations */
while (!list_empty(&kafsasyncd_async_attnq)) {
op = list_entry(kafsasyncd_async_attnq.next, struct afs_async_op, link);
list_del_init(&op->link);
rxrpc_call_abort(op->call, -EIO);
rxrpc_put_call(op->call);
op->call = NULL;
op->ops->discard(op);
}
/* and that's all */
_leave("");
complete_and_exit(&kafsasyncd_dead, 0);
} /* end kafsasyncd() */
/*****************************************************************************/
/*
* begin an operation
* - place operation on busy queue
*/
void afs_kafsasyncd_begin_op(struct afs_async_op *op)
{
_enter("");
spin_lock(&kafsasyncd_async_lock);
init_waitqueue_entry(&op->waiter, kafsasyncd_task);
add_wait_queue(&op->call->waitq, &op->waiter);
list_del(&op->link);
list_add_tail(&op->link, &kafsasyncd_async_busyq);
spin_unlock(&kafsasyncd_async_lock);
_leave("");
} /* end afs_kafsasyncd_begin_op() */
/*****************************************************************************/
/*
* request attention for an operation
* - move to attention queue
*/
void afs_kafsasyncd_attend_op(struct afs_async_op *op)
{
_enter("");
spin_lock(&kafsasyncd_async_lock);
list_del(&op->link);
list_add_tail(&op->link, &kafsasyncd_async_attnq);
spin_unlock(&kafsasyncd_async_lock);
wake_up(&kafsasyncd_sleepq);
_leave("");
} /* end afs_kafsasyncd_attend_op() */
/*****************************************************************************/
/*
* terminate an operation
* - remove from either queue
*/
void afs_kafsasyncd_terminate_op(struct afs_async_op *op)
{
_enter("");
spin_lock(&kafsasyncd_async_lock);
if (!list_empty(&op->link)) {
list_del_init(&op->link);
remove_wait_queue(&op->call->waitq, &op->waiter);
}
spin_unlock(&kafsasyncd_async_lock);
wake_up(&kafsasyncd_sleepq);
_leave("");
} /* end afs_kafsasyncd_terminate_op() */

52
fs/afs/kafsasyncd.h Normal file
View File

@@ -0,0 +1,52 @@
/* kafsasyncd.h: AFS asynchronous operation daemon
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_KAFSASYNCD_H
#define _LINUX_AFS_KAFSASYNCD_H
#include "types.h"
struct afs_async_op;
struct afs_async_op_ops {
void (*attend)(struct afs_async_op *op);
void (*discard)(struct afs_async_op *op);
};
/*****************************************************************************/
/*
* asynchronous operation record
*/
struct afs_async_op
{
struct list_head link;
struct afs_server *server; /* server being contacted */
struct rxrpc_call *call; /* RxRPC call performing op */
wait_queue_t waiter; /* wait queue for kafsasyncd */
const struct afs_async_op_ops *ops; /* operations */
};
static inline void afs_async_op_init(struct afs_async_op *op,
const struct afs_async_op_ops *ops)
{
INIT_LIST_HEAD(&op->link);
op->call = NULL;
op->ops = ops;
}
extern int afs_kafsasyncd_start(void);
extern void afs_kafsasyncd_stop(void);
extern void afs_kafsasyncd_begin_op(struct afs_async_op *op);
extern void afs_kafsasyncd_attend_op(struct afs_async_op *op);
extern void afs_kafsasyncd_terminate_op(struct afs_async_op *op);
#endif /* _LINUX_AFS_KAFSASYNCD_H */

204
fs/afs/kafstimod.c Normal file
View File

@@ -0,0 +1,204 @@
/* kafstimod.c: AFS timeout daemon
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include "cell.h"
#include "volume.h"
#include "kafstimod.h"
#include <asm/errno.h>
#include "internal.h"
static DECLARE_COMPLETION(kafstimod_alive);
static DECLARE_COMPLETION(kafstimod_dead);
static DECLARE_WAIT_QUEUE_HEAD(kafstimod_sleepq);
static int kafstimod_die;
static LIST_HEAD(kafstimod_list);
static DEFINE_SPINLOCK(kafstimod_lock);
static int kafstimod(void *arg);
/*****************************************************************************/
/*
* start the timeout daemon
*/
int afs_kafstimod_start(void)
{
int ret;
ret = kernel_thread(kafstimod, NULL, 0);
if (ret < 0)
return ret;
wait_for_completion(&kafstimod_alive);
return ret;
} /* end afs_kafstimod_start() */
/*****************************************************************************/
/*
* stop the timeout daemon
*/
void afs_kafstimod_stop(void)
{
/* get rid of my daemon */
kafstimod_die = 1;
wake_up(&kafstimod_sleepq);
wait_for_completion(&kafstimod_dead);
} /* end afs_kafstimod_stop() */
/*****************************************************************************/
/*
* timeout processing daemon
*/
static int kafstimod(void *arg)
{
struct afs_timer *timer;
DECLARE_WAITQUEUE(myself, current);
printk("kAFS: Started kafstimod %d\n", current->pid);
daemonize("kafstimod");
complete(&kafstimod_alive);
/* loop around looking for things to attend to */
loop:
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&kafstimod_sleepq, &myself);
for (;;) {
unsigned long jif;
signed long timeout;
/* deal with the server being asked to die */
if (kafstimod_die) {
remove_wait_queue(&kafstimod_sleepq, &myself);
_leave("");
complete_and_exit(&kafstimod_dead, 0);
}
try_to_freeze(PF_FREEZE);
/* discard pending signals */
afs_discard_my_signals();
/* work out the time to elapse before the next event */
spin_lock(&kafstimod_lock);
if (list_empty(&kafstimod_list)) {
timeout = MAX_SCHEDULE_TIMEOUT;
}
else {
timer = list_entry(kafstimod_list.next,
struct afs_timer, link);
timeout = timer->timo_jif;
jif = jiffies;
if (time_before_eq((unsigned long) timeout, jif))
goto immediate;
else {
timeout = (long) timeout - (long) jiffies;
}
}
spin_unlock(&kafstimod_lock);
schedule_timeout(timeout);
set_current_state(TASK_INTERRUPTIBLE);
}
/* the thing on the front of the queue needs processing
* - we come here with the lock held and timer pointing to the expired
* entry
*/
immediate:
remove_wait_queue(&kafstimod_sleepq, &myself);
set_current_state(TASK_RUNNING);
_debug("@@@ Begin Timeout of %p", timer);
/* dequeue the timer */
list_del_init(&timer->link);
spin_unlock(&kafstimod_lock);
/* call the timeout function */
timer->ops->timed_out(timer);
_debug("@@@ End Timeout");
goto loop;
} /* end kafstimod() */
/*****************************************************************************/
/*
* (re-)queue a timer
*/
void afs_kafstimod_add_timer(struct afs_timer *timer, unsigned long timeout)
{
struct afs_timer *ptimer;
struct list_head *_p;
_enter("%p,%lu", timer, timeout);
spin_lock(&kafstimod_lock);
list_del(&timer->link);
/* the timer was deferred or reset - put it back in the queue at the
* right place */
timer->timo_jif = jiffies + timeout;
list_for_each(_p, &kafstimod_list) {
ptimer = list_entry(_p, struct afs_timer, link);
if (time_before(timer->timo_jif, ptimer->timo_jif))
break;
}
list_add_tail(&timer->link, _p); /* insert before stopping point */
spin_unlock(&kafstimod_lock);
wake_up(&kafstimod_sleepq);
_leave("");
} /* end afs_kafstimod_add_timer() */
/*****************************************************************************/
/*
* dequeue a timer
* - returns 0 if the timer was deleted or -ENOENT if it wasn't queued
*/
int afs_kafstimod_del_timer(struct afs_timer *timer)
{
int ret = 0;
_enter("%p", timer);
spin_lock(&kafstimod_lock);
if (list_empty(&timer->link))
ret = -ENOENT;
else
list_del_init(&timer->link);
spin_unlock(&kafstimod_lock);
wake_up(&kafstimod_sleepq);
_leave(" = %d", ret);
return ret;
} /* end afs_kafstimod_del_timer() */

49
fs/afs/kafstimod.h Normal file
View File

@@ -0,0 +1,49 @@
/* kafstimod.h: AFS timeout daemon
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_KAFSTIMOD_H
#define _LINUX_AFS_KAFSTIMOD_H
#include "types.h"
struct afs_timer;
struct afs_timer_ops {
/* called when the front of the timer queue has timed out */
void (*timed_out)(struct afs_timer *timer);
};
/*****************************************************************************/
/*
* AFS timer/timeout record
*/
struct afs_timer
{
struct list_head link; /* link in timer queue */
unsigned long timo_jif; /* timeout time */
const struct afs_timer_ops *ops; /* timeout expiry function */
};
static inline void afs_timer_init(struct afs_timer *timer,
const struct afs_timer_ops *ops)
{
INIT_LIST_HEAD(&timer->link);
timer->ops = ops;
}
extern int afs_kafstimod_start(void);
extern void afs_kafstimod_stop(void);
extern void afs_kafstimod_add_timer(struct afs_timer *timer,
unsigned long timeout);
extern int afs_kafstimod_del_timer(struct afs_timer *timer);
#endif /* _LINUX_AFS_KAFSTIMOD_H */

286
fs/afs/main.c Normal file
View File

@@ -0,0 +1,286 @@
/* main.c: AFS client file system
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include <rxrpc/rxrpc.h>
#include <rxrpc/transport.h>
#include <rxrpc/call.h>
#include <rxrpc/peer.h>
#include "cache.h"
#include "cell.h"
#include "server.h"
#include "fsclient.h"
#include "cmservice.h"
#include "kafstimod.h"
#include "kafsasyncd.h"
#include "internal.h"
struct rxrpc_transport *afs_transport;
static int afs_adding_peer(struct rxrpc_peer *peer);
static void afs_discarding_peer(struct rxrpc_peer *peer);
MODULE_DESCRIPTION("AFS Client File System");
MODULE_AUTHOR("Red Hat, Inc.");
MODULE_LICENSE("GPL");
static char *rootcell;
module_param(rootcell, charp, 0);
MODULE_PARM_DESC(rootcell, "root AFS cell name and VL server IP addr list");
static struct rxrpc_peer_ops afs_peer_ops = {
.adding = afs_adding_peer,
.discarding = afs_discarding_peer,
};
struct list_head afs_cb_hash_tbl[AFS_CB_HASH_COUNT];
DEFINE_SPINLOCK(afs_cb_hash_lock);
#ifdef AFS_CACHING_SUPPORT
static struct cachefs_netfs_operations afs_cache_ops = {
.get_page_cookie = afs_cache_get_page_cookie,
};
struct cachefs_netfs afs_cache_netfs = {
.name = "afs",
.version = 0,
.ops = &afs_cache_ops,
};
#endif
/*****************************************************************************/
/*
* initialise the AFS client FS module
*/
static int __init afs_init(void)
{
int loop, ret;
printk(KERN_INFO "kAFS: Red Hat AFS client v0.1 registering.\n");
/* initialise the callback hash table */
spin_lock_init(&afs_cb_hash_lock);
for (loop = AFS_CB_HASH_COUNT - 1; loop >= 0; loop--)
INIT_LIST_HEAD(&afs_cb_hash_tbl[loop]);
/* register the /proc stuff */
ret = afs_proc_init();
if (ret < 0)
return ret;
#ifdef AFS_CACHING_SUPPORT
/* we want to be able to cache */
ret = cachefs_register_netfs(&afs_cache_netfs,
&afs_cache_cell_index_def);
if (ret < 0)
goto error;
#endif
#ifdef CONFIG_KEYS_TURNED_OFF
ret = afs_key_register();
if (ret < 0)
goto error_cache;
#endif
/* initialise the cell DB */
ret = afs_cell_init(rootcell);
if (ret < 0)
goto error_keys;
/* start the timeout daemon */
ret = afs_kafstimod_start();
if (ret < 0)
goto error_keys;
/* start the async operation daemon */
ret = afs_kafsasyncd_start();
if (ret < 0)
goto error_kafstimod;
/* create the RxRPC transport */
ret = rxrpc_create_transport(7001, &afs_transport);
if (ret < 0)
goto error_kafsasyncd;
afs_transport->peer_ops = &afs_peer_ops;
/* register the filesystems */
ret = afs_fs_init();
if (ret < 0)
goto error_transport;
return ret;
error_transport:
rxrpc_put_transport(afs_transport);
error_kafsasyncd:
afs_kafsasyncd_stop();
error_kafstimod:
afs_kafstimod_stop();
error_keys:
#ifdef CONFIG_KEYS_TURNED_OFF
afs_key_unregister();
error_cache:
#endif
#ifdef AFS_CACHING_SUPPORT
cachefs_unregister_netfs(&afs_cache_netfs);
error:
#endif
afs_cell_purge();
afs_proc_cleanup();
printk(KERN_ERR "kAFS: failed to register: %d\n", ret);
return ret;
} /* end afs_init() */
/* XXX late_initcall is kludgy, but the only alternative seems to create
* a transport upon the first mount, which is worse. Or is it?
*/
late_initcall(afs_init); /* must be called after net/ to create socket */
/*****************************************************************************/
/*
* clean up on module removal
*/
static void __exit afs_exit(void)
{
printk(KERN_INFO "kAFS: Red Hat AFS client v0.1 unregistering.\n");
afs_fs_exit();
rxrpc_put_transport(afs_transport);
afs_kafstimod_stop();
afs_kafsasyncd_stop();
afs_cell_purge();
#ifdef CONFIG_KEYS_TURNED_OFF
afs_key_unregister();
#endif
#ifdef AFS_CACHING_SUPPORT
cachefs_unregister_netfs(&afs_cache_netfs);
#endif
afs_proc_cleanup();
} /* end afs_exit() */
module_exit(afs_exit);
/*****************************************************************************/
/*
* notification that new peer record is being added
* - called from krxsecd
* - return an error to induce an abort
* - mustn't sleep (caller holds an rwlock)
*/
static int afs_adding_peer(struct rxrpc_peer *peer)
{
struct afs_server *server;
int ret;
_debug("kAFS: Adding new peer %08x\n", ntohl(peer->addr.s_addr));
/* determine which server the peer resides in (if any) */
ret = afs_server_find_by_peer(peer, &server);
if (ret < 0)
return ret; /* none that we recognise, so abort */
_debug("Server %p{u=%d}\n", server, atomic_read(&server->usage));
_debug("Cell %p{u=%d}\n",
server->cell, atomic_read(&server->cell->usage));
/* cross-point the structs under a global lock */
spin_lock(&afs_server_peer_lock);
peer->user = server;
server->peer = peer;
spin_unlock(&afs_server_peer_lock);
afs_put_server(server);
return 0;
} /* end afs_adding_peer() */
/*****************************************************************************/
/*
* notification that a peer record is being discarded
* - called from krxiod or krxsecd
*/
static void afs_discarding_peer(struct rxrpc_peer *peer)
{
struct afs_server *server;
_enter("%p",peer);
_debug("Discarding peer %08x (rtt=%lu.%lumS)\n",
ntohl(peer->addr.s_addr),
(long) (peer->rtt / 1000),
(long) (peer->rtt % 1000));
/* uncross-point the structs under a global lock */
spin_lock(&afs_server_peer_lock);
server = peer->user;
if (server) {
peer->user = NULL;
server->peer = NULL;
}
spin_unlock(&afs_server_peer_lock);
_leave("");
} /* end afs_discarding_peer() */
/*****************************************************************************/
/*
* clear the dead space between task_struct and kernel stack
* - called by supplying -finstrument-functions to gcc
*/
#if 0
void __cyg_profile_func_enter (void *this_fn, void *call_site)
__attribute__((no_instrument_function));
void __cyg_profile_func_enter (void *this_fn, void *call_site)
{
asm volatile(" movl %%esp,%%edi \n"
" andl %0,%%edi \n"
" addl %1,%%edi \n"
" movl %%esp,%%ecx \n"
" subl %%edi,%%ecx \n"
" shrl $2,%%ecx \n"
" movl $0xedededed,%%eax \n"
" rep stosl \n"
:
: "i"(~(THREAD_SIZE - 1)), "i"(sizeof(struct thread_info))
: "eax", "ecx", "edi", "memory", "cc"
);
}
void __cyg_profile_func_exit(void *this_fn, void *call_site)
__attribute__((no_instrument_function));
void __cyg_profile_func_exit(void *this_fn, void *call_site)
{
asm volatile(" movl %%esp,%%edi \n"
" andl %0,%%edi \n"
" addl %1,%%edi \n"
" movl %%esp,%%ecx \n"
" subl %%edi,%%ecx \n"
" shrl $2,%%ecx \n"
" movl $0xdadadada,%%eax \n"
" rep stosl \n"
:
: "i"(~(THREAD_SIZE - 1)), "i"(sizeof(struct thread_info))
: "eax", "ecx", "edi", "memory", "cc"
);
}
#endif

39
fs/afs/misc.c Normal file
View File

@@ -0,0 +1,39 @@
/* misc.c: miscellaneous bits
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include "errors.h"
#include "internal.h"
/*****************************************************************************/
/*
* convert an AFS abort code to a Linux error number
*/
int afs_abort_to_error(int abortcode)
{
switch (abortcode) {
case VSALVAGE: return -EIO;
case VNOVNODE: return -ENOENT;
case VNOVOL: return -ENXIO;
case VVOLEXISTS: return -EEXIST;
case VNOSERVICE: return -EIO;
case VOFFLINE: return -ENOENT;
case VONLINE: return -EEXIST;
case VDISKFULL: return -ENOSPC;
case VOVERQUOTA: return -EDQUOT;
case VBUSY: return -EBUSY;
case VMOVED: return -ENXIO;
default: return -EIO;
}
} /* end afs_abort_to_error() */

287
fs/afs/mntpt.c Normal file
View File

@@ -0,0 +1,287 @@
/* mntpt.c: mountpoint management
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/namespace.h>
#include "super.h"
#include "cell.h"
#include "volume.h"
#include "vnode.h"
#include "internal.h"
static struct dentry *afs_mntpt_lookup(struct inode *dir,
struct dentry *dentry,
struct nameidata *nd);
static int afs_mntpt_open(struct inode *inode, struct file *file);
static int afs_mntpt_follow_link(struct dentry *dentry, struct nameidata *nd);
struct file_operations afs_mntpt_file_operations = {
.open = afs_mntpt_open,
};
struct inode_operations afs_mntpt_inode_operations = {
.lookup = afs_mntpt_lookup,
.follow_link = afs_mntpt_follow_link,
.readlink = page_readlink,
.getattr = afs_inode_getattr,
};
static LIST_HEAD(afs_vfsmounts);
static void afs_mntpt_expiry_timed_out(struct afs_timer *timer);
struct afs_timer_ops afs_mntpt_expiry_timer_ops = {
.timed_out = afs_mntpt_expiry_timed_out,
};
struct afs_timer afs_mntpt_expiry_timer;
unsigned long afs_mntpt_expiry_timeout = 20;
/*****************************************************************************/
/*
* check a symbolic link to see whether it actually encodes a mountpoint
* - sets the AFS_VNODE_MOUNTPOINT flag on the vnode appropriately
*/
int afs_mntpt_check_symlink(struct afs_vnode *vnode)
{
struct page *page;
filler_t *filler;
size_t size;
char *buf;
int ret;
_enter("{%u,%u}", vnode->fid.vnode, vnode->fid.unique);
/* read the contents of the symlink into the pagecache */
filler = (filler_t *) AFS_VNODE_TO_I(vnode)->i_mapping->a_ops->readpage;
page = read_cache_page(AFS_VNODE_TO_I(vnode)->i_mapping, 0,
filler, NULL);
if (IS_ERR(page)) {
ret = PTR_ERR(page);
goto out;
}
ret = -EIO;
wait_on_page_locked(page);
buf = kmap(page);
if (!PageUptodate(page))
goto out_free;
if (PageError(page))
goto out_free;
/* examine the symlink's contents */
size = vnode->status.size;
_debug("symlink to %*.*s", size, (int) size, buf);
if (size > 2 &&
(buf[0] == '%' || buf[0] == '#') &&
buf[size - 1] == '.'
) {
_debug("symlink is a mountpoint");
spin_lock(&vnode->lock);
vnode->flags |= AFS_VNODE_MOUNTPOINT;
spin_unlock(&vnode->lock);
}
ret = 0;
out_free:
kunmap(page);
page_cache_release(page);
out:
_leave(" = %d", ret);
return ret;
} /* end afs_mntpt_check_symlink() */
/*****************************************************************************/
/*
* no valid lookup procedure on this sort of dir
*/
static struct dentry *afs_mntpt_lookup(struct inode *dir,
struct dentry *dentry,
struct nameidata *nd)
{
kenter("%p,%p{%p{%s},%s}",
dir,
dentry,
dentry->d_parent,
dentry->d_parent ?
dentry->d_parent->d_name.name : (const unsigned char *) "",
dentry->d_name.name);
return ERR_PTR(-EREMOTE);
} /* end afs_mntpt_lookup() */
/*****************************************************************************/
/*
* no valid open procedure on this sort of dir
*/
static int afs_mntpt_open(struct inode *inode, struct file *file)
{
kenter("%p,%p{%p{%s},%s}",
inode, file,
file->f_dentry->d_parent,
file->f_dentry->d_parent ?
file->f_dentry->d_parent->d_name.name :
(const unsigned char *) "",
file->f_dentry->d_name.name);
return -EREMOTE;
} /* end afs_mntpt_open() */
/*****************************************************************************/
/*
* create a vfsmount to be automounted
*/
static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt)
{
struct afs_super_info *super;
struct vfsmount *mnt;
struct page *page = NULL;
size_t size;
char *buf, *devname = NULL, *options = NULL;
filler_t *filler;
int ret;
kenter("{%s}", mntpt->d_name.name);
BUG_ON(!mntpt->d_inode);
ret = -EINVAL;
size = mntpt->d_inode->i_size;
if (size > PAGE_SIZE - 1)
goto error;
ret = -ENOMEM;
devname = (char *) get_zeroed_page(GFP_KERNEL);
if (!devname)
goto error;
options = (char *) get_zeroed_page(GFP_KERNEL);
if (!options)
goto error;
/* read the contents of the AFS special symlink */
filler = (filler_t *)mntpt->d_inode->i_mapping->a_ops->readpage;
page = read_cache_page(mntpt->d_inode->i_mapping, 0, filler, NULL);
if (IS_ERR(page)) {
ret = PTR_ERR(page);
goto error;
}
ret = -EIO;
wait_on_page_locked(page);
if (!PageUptodate(page) || PageError(page))
goto error;
buf = kmap(page);
memcpy(devname, buf, size);
kunmap(page);
page_cache_release(page);
page = NULL;
/* work out what options we want */
super = AFS_FS_S(mntpt->d_sb);
memcpy(options, "cell=", 5);
strcpy(options + 5, super->volume->cell->name);
if (super->volume->type == AFSVL_RWVOL)
strcat(options, ",rwpath");
/* try and do the mount */
kdebug("--- attempting mount %s -o %s ---", devname, options);
mnt = do_kern_mount("afs", 0, devname, options);
kdebug("--- mount result %p ---", mnt);
free_page((unsigned long) devname);
free_page((unsigned long) options);
kleave(" = %p", mnt);
return mnt;
error:
if (page)
page_cache_release(page);
if (devname)
free_page((unsigned long) devname);
if (options)
free_page((unsigned long) options);
kleave(" = %d", ret);
return ERR_PTR(ret);
} /* end afs_mntpt_do_automount() */
/*****************************************************************************/
/*
* follow a link from a mountpoint directory, thus causing it to be mounted
*/
static int afs_mntpt_follow_link(struct dentry *dentry, struct nameidata *nd)
{
struct vfsmount *newmnt;
struct dentry *old_dentry;
int err;
kenter("%p{%s},{%s:%p{%s}}",
dentry,
dentry->d_name.name,
nd->mnt->mnt_devname,
dentry,
nd->dentry->d_name.name);
newmnt = afs_mntpt_do_automount(dentry);
if (IS_ERR(newmnt)) {
path_release(nd);
return PTR_ERR(newmnt);
}
old_dentry = nd->dentry;
nd->dentry = dentry;
err = do_add_mount(newmnt, nd, 0, &afs_vfsmounts);
nd->dentry = old_dentry;
path_release(nd);
if (!err) {
mntget(newmnt);
nd->mnt = newmnt;
dget(newmnt->mnt_root);
nd->dentry = newmnt->mnt_root;
}
kleave(" = %d", err);
return err;
} /* end afs_mntpt_follow_link() */
/*****************************************************************************/
/*
* handle mountpoint expiry timer going off
*/
static void afs_mntpt_expiry_timed_out(struct afs_timer *timer)
{
kenter("");
mark_mounts_for_expiry(&afs_vfsmounts);
afs_kafstimod_add_timer(&afs_mntpt_expiry_timer,
afs_mntpt_expiry_timeout * HZ);
kleave("");
} /* end afs_mntpt_expiry_timed_out() */

23
fs/afs/mount.h Normal file
View File

@@ -0,0 +1,23 @@
/* mount.h: mount parameters
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_MOUNT_H
#define _LINUX_AFS_MOUNT_H
struct afs_mountdata {
const char *volume; /* name of volume */
const char *cell; /* name of cell containing volume */
const char *cache; /* name of cache block device */
size_t nservers; /* number of server addresses listed */
uint32_t servers[10]; /* IP addresses of servers in this cell */
};
#endif /* _LINUX_AFS_MOUNT_H */

857
fs/afs/proc.c Normal file
View File

@@ -0,0 +1,857 @@
/* proc.c: /proc interface for AFS
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include "cell.h"
#include "volume.h"
#include <asm/uaccess.h>
#include "internal.h"
static struct proc_dir_entry *proc_afs;
static int afs_proc_cells_open(struct inode *inode, struct file *file);
static void *afs_proc_cells_start(struct seq_file *p, loff_t *pos);
static void *afs_proc_cells_next(struct seq_file *p, void *v, loff_t *pos);
static void afs_proc_cells_stop(struct seq_file *p, void *v);
static int afs_proc_cells_show(struct seq_file *m, void *v);
static ssize_t afs_proc_cells_write(struct file *file, const char __user *buf,
size_t size, loff_t *_pos);
static struct seq_operations afs_proc_cells_ops = {
.start = afs_proc_cells_start,
.next = afs_proc_cells_next,
.stop = afs_proc_cells_stop,
.show = afs_proc_cells_show,
};
static struct file_operations afs_proc_cells_fops = {
.open = afs_proc_cells_open,
.read = seq_read,
.write = afs_proc_cells_write,
.llseek = seq_lseek,
.release = seq_release,
};
static int afs_proc_rootcell_open(struct inode *inode, struct file *file);
static int afs_proc_rootcell_release(struct inode *inode, struct file *file);
static ssize_t afs_proc_rootcell_read(struct file *file, char __user *buf,
size_t size, loff_t *_pos);
static ssize_t afs_proc_rootcell_write(struct file *file,
const char __user *buf,
size_t size, loff_t *_pos);
static struct file_operations afs_proc_rootcell_fops = {
.open = afs_proc_rootcell_open,
.read = afs_proc_rootcell_read,
.write = afs_proc_rootcell_write,
.llseek = no_llseek,
.release = afs_proc_rootcell_release
};
static int afs_proc_cell_volumes_open(struct inode *inode, struct file *file);
static int afs_proc_cell_volumes_release(struct inode *inode,
struct file *file);
static void *afs_proc_cell_volumes_start(struct seq_file *p, loff_t *pos);
static void *afs_proc_cell_volumes_next(struct seq_file *p, void *v,
loff_t *pos);
static void afs_proc_cell_volumes_stop(struct seq_file *p, void *v);
static int afs_proc_cell_volumes_show(struct seq_file *m, void *v);
static struct seq_operations afs_proc_cell_volumes_ops = {
.start = afs_proc_cell_volumes_start,
.next = afs_proc_cell_volumes_next,
.stop = afs_proc_cell_volumes_stop,
.show = afs_proc_cell_volumes_show,
};
static struct file_operations afs_proc_cell_volumes_fops = {
.open = afs_proc_cell_volumes_open,
.read = seq_read,
.llseek = seq_lseek,
.release = afs_proc_cell_volumes_release,
};
static int afs_proc_cell_vlservers_open(struct inode *inode,
struct file *file);
static int afs_proc_cell_vlservers_release(struct inode *inode,
struct file *file);
static void *afs_proc_cell_vlservers_start(struct seq_file *p, loff_t *pos);
static void *afs_proc_cell_vlservers_next(struct seq_file *p, void *v,
loff_t *pos);
static void afs_proc_cell_vlservers_stop(struct seq_file *p, void *v);
static int afs_proc_cell_vlservers_show(struct seq_file *m, void *v);
static struct seq_operations afs_proc_cell_vlservers_ops = {
.start = afs_proc_cell_vlservers_start,
.next = afs_proc_cell_vlservers_next,
.stop = afs_proc_cell_vlservers_stop,
.show = afs_proc_cell_vlservers_show,
};
static struct file_operations afs_proc_cell_vlservers_fops = {
.open = afs_proc_cell_vlservers_open,
.read = seq_read,
.llseek = seq_lseek,
.release = afs_proc_cell_vlservers_release,
};
static int afs_proc_cell_servers_open(struct inode *inode, struct file *file);
static int afs_proc_cell_servers_release(struct inode *inode,
struct file *file);
static void *afs_proc_cell_servers_start(struct seq_file *p, loff_t *pos);
static void *afs_proc_cell_servers_next(struct seq_file *p, void *v,
loff_t *pos);
static void afs_proc_cell_servers_stop(struct seq_file *p, void *v);
static int afs_proc_cell_servers_show(struct seq_file *m, void *v);
static struct seq_operations afs_proc_cell_servers_ops = {
.start = afs_proc_cell_servers_start,
.next = afs_proc_cell_servers_next,
.stop = afs_proc_cell_servers_stop,
.show = afs_proc_cell_servers_show,
};
static struct file_operations afs_proc_cell_servers_fops = {
.open = afs_proc_cell_servers_open,
.read = seq_read,
.llseek = seq_lseek,
.release = afs_proc_cell_servers_release,
};
/*****************************************************************************/
/*
* initialise the /proc/fs/afs/ directory
*/
int afs_proc_init(void)
{
struct proc_dir_entry *p;
_enter("");
proc_afs = proc_mkdir("fs/afs", NULL);
if (!proc_afs)
goto error;
proc_afs->owner = THIS_MODULE;
p = create_proc_entry("cells", 0, proc_afs);
if (!p)
goto error_proc;
p->proc_fops = &afs_proc_cells_fops;
p->owner = THIS_MODULE;
p = create_proc_entry("rootcell", 0, proc_afs);
if (!p)
goto error_cells;
p->proc_fops = &afs_proc_rootcell_fops;
p->owner = THIS_MODULE;
_leave(" = 0");
return 0;
error_cells:
remove_proc_entry("cells", proc_afs);
error_proc:
remove_proc_entry("fs/afs", NULL);
error:
_leave(" = -ENOMEM");
return -ENOMEM;
} /* end afs_proc_init() */
/*****************************************************************************/
/*
* clean up the /proc/fs/afs/ directory
*/
void afs_proc_cleanup(void)
{
remove_proc_entry("cells", proc_afs);
remove_proc_entry("fs/afs", NULL);
} /* end afs_proc_cleanup() */
/*****************************************************************************/
/*
* open "/proc/fs/afs/cells" which provides a summary of extant cells
*/
static int afs_proc_cells_open(struct inode *inode, struct file *file)
{
struct seq_file *m;
int ret;
ret = seq_open(file, &afs_proc_cells_ops);
if (ret < 0)
return ret;
m = file->private_data;
m->private = PDE(inode)->data;
return 0;
} /* end afs_proc_cells_open() */
/*****************************************************************************/
/*
* set up the iterator to start reading from the cells list and return the
* first item
*/
static void *afs_proc_cells_start(struct seq_file *m, loff_t *_pos)
{
struct list_head *_p;
loff_t pos = *_pos;
/* lock the list against modification */
down_read(&afs_proc_cells_sem);
/* allow for the header line */
if (!pos)
return (void *) 1;
pos--;
/* find the n'th element in the list */
list_for_each(_p, &afs_proc_cells)
if (!pos--)
break;
return _p != &afs_proc_cells ? _p : NULL;
} /* end afs_proc_cells_start() */
/*****************************************************************************/
/*
* move to next cell in cells list
*/
static void *afs_proc_cells_next(struct seq_file *p, void *v, loff_t *pos)
{
struct list_head *_p;
(*pos)++;
_p = v;
_p = v == (void *) 1 ? afs_proc_cells.next : _p->next;
return _p != &afs_proc_cells ? _p : NULL;
} /* end afs_proc_cells_next() */
/*****************************************************************************/
/*
* clean up after reading from the cells list
*/
static void afs_proc_cells_stop(struct seq_file *p, void *v)
{
up_read(&afs_proc_cells_sem);
} /* end afs_proc_cells_stop() */
/*****************************************************************************/
/*
* display a header line followed by a load of cell lines
*/
static int afs_proc_cells_show(struct seq_file *m, void *v)
{
struct afs_cell *cell = list_entry(v, struct afs_cell, proc_link);
/* display header on line 1 */
if (v == (void *) 1) {
seq_puts(m, "USE NAME\n");
return 0;
}
/* display one cell per line on subsequent lines */
seq_printf(m, "%3d %s\n", atomic_read(&cell->usage), cell->name);
return 0;
} /* end afs_proc_cells_show() */
/*****************************************************************************/
/*
* handle writes to /proc/fs/afs/cells
* - to add cells: echo "add <cellname> <IP>[:<IP>][:<IP>]"
*/
static ssize_t afs_proc_cells_write(struct file *file, const char __user *buf,
size_t size, loff_t *_pos)
{
char *kbuf, *name, *args;
int ret;
/* start by dragging the command into memory */
if (size <= 1 || size >= PAGE_SIZE)
return -EINVAL;
kbuf = kmalloc(size + 1, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
ret = -EFAULT;
if (copy_from_user(kbuf, buf, size) != 0)
goto done;
kbuf[size] = 0;
/* trim to first NL */
name = memchr(kbuf, '\n', size);
if (name)
*name = 0;
/* split into command, name and argslist */
name = strchr(kbuf, ' ');
if (!name)
goto inval;
do {
*name++ = 0;
} while(*name == ' ');
if (!*name)
goto inval;
args = strchr(name, ' ');
if (!args)
goto inval;
do {
*args++ = 0;
} while(*args == ' ');
if (!*args)
goto inval;
/* determine command to perform */
_debug("cmd=%s name=%s args=%s", kbuf, name, args);
if (strcmp(kbuf, "add") == 0) {
struct afs_cell *cell;
ret = afs_cell_create(name, args, &cell);
if (ret < 0)
goto done;
printk("kAFS: Added new cell '%s'\n", name);
}
else {
goto inval;
}
ret = size;
done:
kfree(kbuf);
_leave(" = %d", ret);
return ret;
inval:
ret = -EINVAL;
printk("kAFS: Invalid Command on /proc/fs/afs/cells file\n");
goto done;
} /* end afs_proc_cells_write() */
/*****************************************************************************/
/*
* Stubs for /proc/fs/afs/rootcell
*/
static int afs_proc_rootcell_open(struct inode *inode, struct file *file)
{
return 0;
}
static int afs_proc_rootcell_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t afs_proc_rootcell_read(struct file *file, char __user *buf,
size_t size, loff_t *_pos)
{
return 0;
}
/*****************************************************************************/
/*
* handle writes to /proc/fs/afs/rootcell
* - to initialize rootcell: echo "cell.name:192.168.231.14"
*/
static ssize_t afs_proc_rootcell_write(struct file *file,
const char __user *buf,
size_t size, loff_t *_pos)
{
char *kbuf, *s;
int ret;
/* start by dragging the command into memory */
if (size <= 1 || size >= PAGE_SIZE)
return -EINVAL;
ret = -ENOMEM;
kbuf = kmalloc(size + 1, GFP_KERNEL);
if (!kbuf)
goto nomem;
ret = -EFAULT;
if (copy_from_user(kbuf, buf, size) != 0)
goto infault;
kbuf[size] = 0;
/* trim to first NL */
s = memchr(kbuf, '\n', size);
if (s)
*s = 0;
/* determine command to perform */
_debug("rootcell=%s", kbuf);
ret = afs_cell_init(kbuf);
if (ret >= 0)
ret = size; /* consume everything, always */
infault:
kfree(kbuf);
nomem:
_leave(" = %d", ret);
return ret;
} /* end afs_proc_rootcell_write() */
/*****************************************************************************/
/*
* initialise /proc/fs/afs/<cell>/
*/
int afs_proc_cell_setup(struct afs_cell *cell)
{
struct proc_dir_entry *p;
_enter("%p{%s}", cell, cell->name);
cell->proc_dir = proc_mkdir(cell->name, proc_afs);
if (!cell->proc_dir)
return -ENOMEM;
p = create_proc_entry("servers", 0, cell->proc_dir);
if (!p)
goto error_proc;
p->proc_fops = &afs_proc_cell_servers_fops;
p->owner = THIS_MODULE;
p->data = cell;
p = create_proc_entry("vlservers", 0, cell->proc_dir);
if (!p)
goto error_servers;
p->proc_fops = &afs_proc_cell_vlservers_fops;
p->owner = THIS_MODULE;
p->data = cell;
p = create_proc_entry("volumes", 0, cell->proc_dir);
if (!p)
goto error_vlservers;
p->proc_fops = &afs_proc_cell_volumes_fops;
p->owner = THIS_MODULE;
p->data = cell;
_leave(" = 0");
return 0;
error_vlservers:
remove_proc_entry("vlservers", cell->proc_dir);
error_servers:
remove_proc_entry("servers", cell->proc_dir);
error_proc:
remove_proc_entry(cell->name, proc_afs);
_leave(" = -ENOMEM");
return -ENOMEM;
} /* end afs_proc_cell_setup() */
/*****************************************************************************/
/*
* remove /proc/fs/afs/<cell>/
*/
void afs_proc_cell_remove(struct afs_cell *cell)
{
_enter("");
remove_proc_entry("volumes", cell->proc_dir);
remove_proc_entry("vlservers", cell->proc_dir);
remove_proc_entry("servers", cell->proc_dir);
remove_proc_entry(cell->name, proc_afs);
_leave("");
} /* end afs_proc_cell_remove() */
/*****************************************************************************/
/*
* open "/proc/fs/afs/<cell>/volumes" which provides a summary of extant cells
*/
static int afs_proc_cell_volumes_open(struct inode *inode, struct file *file)
{
struct afs_cell *cell;
struct seq_file *m;
int ret;
cell = afs_get_cell_maybe((struct afs_cell **) &PDE(inode)->data);
if (!cell)
return -ENOENT;
ret = seq_open(file, &afs_proc_cell_volumes_ops);
if (ret < 0)
return ret;
m = file->private_data;
m->private = cell;
return 0;
} /* end afs_proc_cell_volumes_open() */
/*****************************************************************************/
/*
* close the file and release the ref to the cell
*/
static int afs_proc_cell_volumes_release(struct inode *inode, struct file *file)
{
struct afs_cell *cell = PDE(inode)->data;
int ret;
ret = seq_release(inode,file);
afs_put_cell(cell);
return ret;
} /* end afs_proc_cell_volumes_release() */
/*****************************************************************************/
/*
* set up the iterator to start reading from the cells list and return the
* first item
*/
static void *afs_proc_cell_volumes_start(struct seq_file *m, loff_t *_pos)
{
struct list_head *_p;
struct afs_cell *cell = m->private;
loff_t pos = *_pos;
_enter("cell=%p pos=%Ld", cell, *_pos);
/* lock the list against modification */
down_read(&cell->vl_sem);
/* allow for the header line */
if (!pos)
return (void *) 1;
pos--;
/* find the n'th element in the list */
list_for_each(_p, &cell->vl_list)
if (!pos--)
break;
return _p != &cell->vl_list ? _p : NULL;
} /* end afs_proc_cell_volumes_start() */
/*****************************************************************************/
/*
* move to next cell in cells list
*/
static void *afs_proc_cell_volumes_next(struct seq_file *p, void *v,
loff_t *_pos)
{
struct list_head *_p;
struct afs_cell *cell = p->private;
_enter("cell=%p pos=%Ld", cell, *_pos);
(*_pos)++;
_p = v;
_p = v == (void *) 1 ? cell->vl_list.next : _p->next;
return _p != &cell->vl_list ? _p : NULL;
} /* end afs_proc_cell_volumes_next() */
/*****************************************************************************/
/*
* clean up after reading from the cells list
*/
static void afs_proc_cell_volumes_stop(struct seq_file *p, void *v)
{
struct afs_cell *cell = p->private;
up_read(&cell->vl_sem);
} /* end afs_proc_cell_volumes_stop() */
/*****************************************************************************/
/*
* display a header line followed by a load of volume lines
*/
static int afs_proc_cell_volumes_show(struct seq_file *m, void *v)
{
struct afs_vlocation *vlocation =
list_entry(v, struct afs_vlocation, link);
/* display header on line 1 */
if (v == (void *) 1) {
seq_puts(m, "USE VLID[0] VLID[1] VLID[2] NAME\n");
return 0;
}
/* display one cell per line on subsequent lines */
seq_printf(m, "%3d %08x %08x %08x %s\n",
atomic_read(&vlocation->usage),
vlocation->vldb.vid[0],
vlocation->vldb.vid[1],
vlocation->vldb.vid[2],
vlocation->vldb.name
);
return 0;
} /* end afs_proc_cell_volumes_show() */
/*****************************************************************************/
/*
* open "/proc/fs/afs/<cell>/vlservers" which provides a list of volume
* location server
*/
static int afs_proc_cell_vlservers_open(struct inode *inode, struct file *file)
{
struct afs_cell *cell;
struct seq_file *m;
int ret;
cell = afs_get_cell_maybe((struct afs_cell**)&PDE(inode)->data);
if (!cell)
return -ENOENT;
ret = seq_open(file,&afs_proc_cell_vlservers_ops);
if (ret<0)
return ret;
m = file->private_data;
m->private = cell;
return 0;
} /* end afs_proc_cell_vlservers_open() */
/*****************************************************************************/
/*
* close the file and release the ref to the cell
*/
static int afs_proc_cell_vlservers_release(struct inode *inode,
struct file *file)
{
struct afs_cell *cell = PDE(inode)->data;
int ret;
ret = seq_release(inode,file);
afs_put_cell(cell);
return ret;
} /* end afs_proc_cell_vlservers_release() */
/*****************************************************************************/
/*
* set up the iterator to start reading from the cells list and return the
* first item
*/
static void *afs_proc_cell_vlservers_start(struct seq_file *m, loff_t *_pos)
{
struct afs_cell *cell = m->private;
loff_t pos = *_pos;
_enter("cell=%p pos=%Ld", cell, *_pos);
/* lock the list against modification */
down_read(&cell->vl_sem);
/* allow for the header line */
if (!pos)
return (void *) 1;
pos--;
if (pos >= cell->vl_naddrs)
return NULL;
return &cell->vl_addrs[pos];
} /* end afs_proc_cell_vlservers_start() */
/*****************************************************************************/
/*
* move to next cell in cells list
*/
static void *afs_proc_cell_vlservers_next(struct seq_file *p, void *v,
loff_t *_pos)
{
struct afs_cell *cell = p->private;
loff_t pos;
_enter("cell=%p{nad=%u} pos=%Ld", cell, cell->vl_naddrs, *_pos);
pos = *_pos;
(*_pos)++;
if (pos >= cell->vl_naddrs)
return NULL;
return &cell->vl_addrs[pos];
} /* end afs_proc_cell_vlservers_next() */
/*****************************************************************************/
/*
* clean up after reading from the cells list
*/
static void afs_proc_cell_vlservers_stop(struct seq_file *p, void *v)
{
struct afs_cell *cell = p->private;
up_read(&cell->vl_sem);
} /* end afs_proc_cell_vlservers_stop() */
/*****************************************************************************/
/*
* display a header line followed by a load of volume lines
*/
static int afs_proc_cell_vlservers_show(struct seq_file *m, void *v)
{
struct in_addr *addr = v;
/* display header on line 1 */
if (v == (struct in_addr *) 1) {
seq_puts(m, "ADDRESS\n");
return 0;
}
/* display one cell per line on subsequent lines */
seq_printf(m, "%u.%u.%u.%u\n", NIPQUAD(addr->s_addr));
return 0;
} /* end afs_proc_cell_vlservers_show() */
/*****************************************************************************/
/*
* open "/proc/fs/afs/<cell>/servers" which provides a summary of active
* servers
*/
static int afs_proc_cell_servers_open(struct inode *inode, struct file *file)
{
struct afs_cell *cell;
struct seq_file *m;
int ret;
cell = afs_get_cell_maybe((struct afs_cell **) &PDE(inode)->data);
if (!cell)
return -ENOENT;
ret = seq_open(file, &afs_proc_cell_servers_ops);
if (ret < 0)
return ret;
m = file->private_data;
m->private = cell;
return 0;
} /* end afs_proc_cell_servers_open() */
/*****************************************************************************/
/*
* close the file and release the ref to the cell
*/
static int afs_proc_cell_servers_release(struct inode *inode,
struct file *file)
{
struct afs_cell *cell = PDE(inode)->data;
int ret;
ret = seq_release(inode, file);
afs_put_cell(cell);
return ret;
} /* end afs_proc_cell_servers_release() */
/*****************************************************************************/
/*
* set up the iterator to start reading from the cells list and return the
* first item
*/
static void *afs_proc_cell_servers_start(struct seq_file *m, loff_t *_pos)
{
struct list_head *_p;
struct afs_cell *cell = m->private;
loff_t pos = *_pos;
_enter("cell=%p pos=%Ld", cell, *_pos);
/* lock the list against modification */
read_lock(&cell->sv_lock);
/* allow for the header line */
if (!pos)
return (void *) 1;
pos--;
/* find the n'th element in the list */
list_for_each(_p, &cell->sv_list)
if (!pos--)
break;
return _p != &cell->sv_list ? _p : NULL;
} /* end afs_proc_cell_servers_start() */
/*****************************************************************************/
/*
* move to next cell in cells list
*/
static void *afs_proc_cell_servers_next(struct seq_file *p, void *v,
loff_t *_pos)
{
struct list_head *_p;
struct afs_cell *cell = p->private;
_enter("cell=%p pos=%Ld", cell, *_pos);
(*_pos)++;
_p = v;
_p = v == (void *) 1 ? cell->sv_list.next : _p->next;
return _p != &cell->sv_list ? _p : NULL;
} /* end afs_proc_cell_servers_next() */
/*****************************************************************************/
/*
* clean up after reading from the cells list
*/
static void afs_proc_cell_servers_stop(struct seq_file *p, void *v)
{
struct afs_cell *cell = p->private;
read_unlock(&cell->sv_lock);
} /* end afs_proc_cell_servers_stop() */
/*****************************************************************************/
/*
* display a header line followed by a load of volume lines
*/
static int afs_proc_cell_servers_show(struct seq_file *m, void *v)
{
struct afs_server *server = list_entry(v, struct afs_server, link);
char ipaddr[20];
/* display header on line 1 */
if (v == (void *) 1) {
seq_puts(m, "USE ADDR STATE\n");
return 0;
}
/* display one cell per line on subsequent lines */
sprintf(ipaddr, "%u.%u.%u.%u", NIPQUAD(server->addr));
seq_printf(m, "%3d %-15.15s %5d\n",
atomic_read(&server->usage),
ipaddr,
server->fs_state
);
return 0;
} /* end afs_proc_cell_servers_show() */

502
fs/afs/server.c Normal file
View File

@@ -0,0 +1,502 @@
/* server.c: AFS server record management
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/sched.h>
#include <linux/slab.h>
#include <rxrpc/peer.h>
#include <rxrpc/connection.h>
#include "volume.h"
#include "cell.h"
#include "server.h"
#include "transport.h"
#include "vlclient.h"
#include "kafstimod.h"
#include "internal.h"
DEFINE_SPINLOCK(afs_server_peer_lock);
#define FS_SERVICE_ID 1 /* AFS Volume Location Service ID */
#define VL_SERVICE_ID 52 /* AFS Volume Location Service ID */
static void __afs_server_timeout(struct afs_timer *timer)
{
struct afs_server *server =
list_entry(timer, struct afs_server, timeout);
_debug("SERVER TIMEOUT [%p{u=%d}]",
server, atomic_read(&server->usage));
afs_server_do_timeout(server);
}
static const struct afs_timer_ops afs_server_timer_ops = {
.timed_out = __afs_server_timeout,
};
/*****************************************************************************/
/*
* lookup a server record in a cell
* - TODO: search the cell's server list
*/
int afs_server_lookup(struct afs_cell *cell, const struct in_addr *addr,
struct afs_server **_server)
{
struct afs_server *server, *active, *zombie;
int loop;
_enter("%p,%08x,", cell, ntohl(addr->s_addr));
/* allocate and initialise a server record */
server = kmalloc(sizeof(struct afs_server), GFP_KERNEL);
if (!server) {
_leave(" = -ENOMEM");
return -ENOMEM;
}
memset(server, 0, sizeof(struct afs_server));
atomic_set(&server->usage, 1);
INIT_LIST_HEAD(&server->link);
init_rwsem(&server->sem);
INIT_LIST_HEAD(&server->fs_callq);
spin_lock_init(&server->fs_lock);
INIT_LIST_HEAD(&server->cb_promises);
spin_lock_init(&server->cb_lock);
for (loop = 0; loop < AFS_SERVER_CONN_LIST_SIZE; loop++)
server->fs_conn_cnt[loop] = 4;
memcpy(&server->addr, addr, sizeof(struct in_addr));
server->addr.s_addr = addr->s_addr;
afs_timer_init(&server->timeout, &afs_server_timer_ops);
/* add to the cell */
write_lock(&cell->sv_lock);
/* check the active list */
list_for_each_entry(active, &cell->sv_list, link) {
if (active->addr.s_addr == addr->s_addr)
goto use_active_server;
}
/* check the inactive list */
spin_lock(&cell->sv_gylock);
list_for_each_entry(zombie, &cell->sv_graveyard, link) {
if (zombie->addr.s_addr == addr->s_addr)
goto resurrect_server;
}
spin_unlock(&cell->sv_gylock);
afs_get_cell(cell);
server->cell = cell;
list_add_tail(&server->link, &cell->sv_list);
write_unlock(&cell->sv_lock);
*_server = server;
_leave(" = 0 (%p)", server);
return 0;
/* found a matching active server */
use_active_server:
_debug("active server");
afs_get_server(active);
write_unlock(&cell->sv_lock);
kfree(server);
*_server = active;
_leave(" = 0 (%p)", active);
return 0;
/* found a matching server in the graveyard, so resurrect it and
* dispose of the new record */
resurrect_server:
_debug("resurrecting server");
list_del(&zombie->link);
list_add_tail(&zombie->link, &cell->sv_list);
afs_get_server(zombie);
afs_kafstimod_del_timer(&zombie->timeout);
spin_unlock(&cell->sv_gylock);
write_unlock(&cell->sv_lock);
kfree(server);
*_server = zombie;
_leave(" = 0 (%p)", zombie);
return 0;
} /* end afs_server_lookup() */
/*****************************************************************************/
/*
* destroy a server record
* - removes from the cell list
*/
void afs_put_server(struct afs_server *server)
{
struct afs_cell *cell;
if (!server)
return;
_enter("%p", server);
cell = server->cell;
/* sanity check */
BUG_ON(atomic_read(&server->usage) <= 0);
/* to prevent a race, the decrement and the dequeue must be effectively
* atomic */
write_lock(&cell->sv_lock);
if (likely(!atomic_dec_and_test(&server->usage))) {
write_unlock(&cell->sv_lock);
_leave("");
return;
}
spin_lock(&cell->sv_gylock);
list_del(&server->link);
list_add_tail(&server->link, &cell->sv_graveyard);
/* time out in 10 secs */
afs_kafstimod_add_timer(&server->timeout, 10 * HZ);
spin_unlock(&cell->sv_gylock);
write_unlock(&cell->sv_lock);
_leave(" [killed]");
} /* end afs_put_server() */
/*****************************************************************************/
/*
* timeout server record
* - removes from the cell's graveyard if the usage count is zero
*/
void afs_server_do_timeout(struct afs_server *server)
{
struct rxrpc_peer *peer;
struct afs_cell *cell;
int loop;
_enter("%p", server);
cell = server->cell;
BUG_ON(atomic_read(&server->usage) < 0);
/* remove from graveyard if still dead */
spin_lock(&cell->vl_gylock);
if (atomic_read(&server->usage) == 0)
list_del_init(&server->link);
else
server = NULL;
spin_unlock(&cell->vl_gylock);
if (!server) {
_leave("");
return; /* resurrected */
}
/* we can now destroy it properly */
afs_put_cell(cell);
/* uncross-point the structs under a global lock */
spin_lock(&afs_server_peer_lock);
peer = server->peer;
if (peer) {
server->peer = NULL;
peer->user = NULL;
}
spin_unlock(&afs_server_peer_lock);
/* finish cleaning up the server */
for (loop = AFS_SERVER_CONN_LIST_SIZE - 1; loop >= 0; loop--)
if (server->fs_conn[loop])
rxrpc_put_connection(server->fs_conn[loop]);
if (server->vlserver)
rxrpc_put_connection(server->vlserver);
kfree(server);
_leave(" [destroyed]");
} /* end afs_server_do_timeout() */
/*****************************************************************************/
/*
* get a callslot on a connection to the fileserver on the specified server
*/
int afs_server_request_callslot(struct afs_server *server,
struct afs_server_callslot *callslot)
{
struct afs_server_callslot *pcallslot;
struct rxrpc_connection *conn;
int nconn, ret;
_enter("%p,",server);
INIT_LIST_HEAD(&callslot->link);
callslot->task = current;
callslot->conn = NULL;
callslot->nconn = -1;
callslot->ready = 0;
ret = 0;
conn = NULL;
/* get hold of a callslot first */
spin_lock(&server->fs_lock);
/* resurrect the server if it's death timeout has expired */
if (server->fs_state) {
if (time_before(jiffies, server->fs_dead_jif)) {
ret = server->fs_state;
spin_unlock(&server->fs_lock);
_leave(" = %d [still dead]", ret);
return ret;
}
server->fs_state = 0;
}
/* try and find a connection that has spare callslots */
for (nconn = 0; nconn < AFS_SERVER_CONN_LIST_SIZE; nconn++) {
if (server->fs_conn_cnt[nconn] > 0) {
server->fs_conn_cnt[nconn]--;
spin_unlock(&server->fs_lock);
callslot->nconn = nconn;
goto obtained_slot;
}
}
/* none were available - wait interruptibly for one to become
* available */
set_current_state(TASK_INTERRUPTIBLE);
list_add_tail(&callslot->link, &server->fs_callq);
spin_unlock(&server->fs_lock);
while (!callslot->ready && !signal_pending(current)) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
set_current_state(TASK_RUNNING);
/* even if we were interrupted we may still be queued */
if (!callslot->ready) {
spin_lock(&server->fs_lock);
list_del_init(&callslot->link);
spin_unlock(&server->fs_lock);
}
nconn = callslot->nconn;
/* if interrupted, we must release any slot we also got before
* returning an error */
if (signal_pending(current)) {
ret = -EINTR;
goto error_release;
}
/* if we were woken up with an error, then pass that error back to the
* called */
if (nconn < 0) {
_leave(" = %d", callslot->errno);
return callslot->errno;
}
/* were we given a connection directly? */
if (callslot->conn) {
/* yes - use it */
_leave(" = 0 (nc=%d)", nconn);
return 0;
}
/* got a callslot, but no connection */
obtained_slot:
/* need to get hold of the RxRPC connection */
down_write(&server->sem);
/* quick check to see if there's an outstanding error */
ret = server->fs_state;
if (ret)
goto error_release_upw;
if (server->fs_conn[nconn]) {
/* reuse an existing connection */
rxrpc_get_connection(server->fs_conn[nconn]);
callslot->conn = server->fs_conn[nconn];
}
else {
/* create a new connection */
ret = rxrpc_create_connection(afs_transport,
htons(7000),
server->addr.s_addr,
FS_SERVICE_ID,
NULL,
&server->fs_conn[nconn]);
if (ret < 0)
goto error_release_upw;
callslot->conn = server->fs_conn[0];
rxrpc_get_connection(callslot->conn);
}
up_write(&server->sem);
_leave(" = 0");
return 0;
/* handle an error occurring */
error_release_upw:
up_write(&server->sem);
error_release:
/* either release the callslot or pass it along to another deserving
* task */
spin_lock(&server->fs_lock);
if (nconn < 0) {
/* no callslot allocated */
}
else if (list_empty(&server->fs_callq)) {
/* no one waiting */
server->fs_conn_cnt[nconn]++;
spin_unlock(&server->fs_lock);
}
else {
/* someone's waiting - dequeue them and wake them up */
pcallslot = list_entry(server->fs_callq.next,
struct afs_server_callslot, link);
list_del_init(&pcallslot->link);
pcallslot->errno = server->fs_state;
if (!pcallslot->errno) {
/* pass them out callslot details */
callslot->conn = xchg(&pcallslot->conn,
callslot->conn);
pcallslot->nconn = nconn;
callslot->nconn = nconn = -1;
}
pcallslot->ready = 1;
wake_up_process(pcallslot->task);
spin_unlock(&server->fs_lock);
}
rxrpc_put_connection(callslot->conn);
callslot->conn = NULL;
_leave(" = %d", ret);
return ret;
} /* end afs_server_request_callslot() */
/*****************************************************************************/
/*
* release a callslot back to the server
* - transfers the RxRPC connection to the next pending callslot if possible
*/
void afs_server_release_callslot(struct afs_server *server,
struct afs_server_callslot *callslot)
{
struct afs_server_callslot *pcallslot;
_enter("{ad=%08x,cnt=%u},{%d}",
ntohl(server->addr.s_addr),
server->fs_conn_cnt[callslot->nconn],
callslot->nconn);
BUG_ON(callslot->nconn < 0);
spin_lock(&server->fs_lock);
if (list_empty(&server->fs_callq)) {
/* no one waiting */
server->fs_conn_cnt[callslot->nconn]++;
spin_unlock(&server->fs_lock);
}
else {
/* someone's waiting - dequeue them and wake them up */
pcallslot = list_entry(server->fs_callq.next,
struct afs_server_callslot, link);
list_del_init(&pcallslot->link);
pcallslot->errno = server->fs_state;
if (!pcallslot->errno) {
/* pass them out callslot details */
callslot->conn = xchg(&pcallslot->conn, callslot->conn);
pcallslot->nconn = callslot->nconn;
callslot->nconn = -1;
}
pcallslot->ready = 1;
wake_up_process(pcallslot->task);
spin_unlock(&server->fs_lock);
}
rxrpc_put_connection(callslot->conn);
_leave("");
} /* end afs_server_release_callslot() */
/*****************************************************************************/
/*
* get a handle to a connection to the vlserver (volume location) on the
* specified server
*/
int afs_server_get_vlconn(struct afs_server *server,
struct rxrpc_connection **_conn)
{
struct rxrpc_connection *conn;
int ret;
_enter("%p,", server);
ret = 0;
conn = NULL;
down_read(&server->sem);
if (server->vlserver) {
/* reuse an existing connection */
rxrpc_get_connection(server->vlserver);
conn = server->vlserver;
up_read(&server->sem);
}
else {
/* create a new connection */
up_read(&server->sem);
down_write(&server->sem);
if (!server->vlserver) {
ret = rxrpc_create_connection(afs_transport,
htons(7003),
server->addr.s_addr,
VL_SERVICE_ID,
NULL,
&server->vlserver);
}
if (ret == 0) {
rxrpc_get_connection(server->vlserver);
conn = server->vlserver;
}
up_write(&server->sem);
}
*_conn = conn;
_leave(" = %d", ret);
return ret;
} /* end afs_server_get_vlconn() */

102
fs/afs/server.h Normal file
View File

@@ -0,0 +1,102 @@
/* server.h: AFS server record
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_SERVER_H
#define _LINUX_AFS_SERVER_H
#include "types.h"
#include "kafstimod.h"
#include <rxrpc/peer.h>
#include <linux/rwsem.h>
extern spinlock_t afs_server_peer_lock;
/*****************************************************************************/
/*
* AFS server record
*/
struct afs_server
{
atomic_t usage;
struct afs_cell *cell; /* cell in which server resides */
struct list_head link; /* link in cell's server list */
struct rw_semaphore sem; /* access lock */
struct afs_timer timeout; /* graveyard timeout */
struct in_addr addr; /* server address */
struct rxrpc_peer *peer; /* peer record for this server */
struct rxrpc_connection *vlserver; /* connection to the volume location service */
/* file service access */
#define AFS_SERVER_CONN_LIST_SIZE 2
struct rxrpc_connection *fs_conn[AFS_SERVER_CONN_LIST_SIZE]; /* FS connections */
unsigned fs_conn_cnt[AFS_SERVER_CONN_LIST_SIZE]; /* per conn call count */
struct list_head fs_callq; /* queue of processes waiting to make a call */
spinlock_t fs_lock; /* access lock */
int fs_state; /* 0 or reason FS currently marked dead (-errno) */
unsigned fs_rtt; /* FS round trip time */
unsigned long fs_act_jif; /* time at which last activity occurred */
unsigned long fs_dead_jif; /* time at which no longer to be considered dead */
/* callback promise management */
struct list_head cb_promises; /* as yet unbroken promises from this server */
spinlock_t cb_lock; /* access lock */
};
extern int afs_server_lookup(struct afs_cell *cell,
const struct in_addr *addr,
struct afs_server **_server);
#define afs_get_server(S) do { atomic_inc(&(S)->usage); } while(0)
extern void afs_put_server(struct afs_server *server);
extern void afs_server_do_timeout(struct afs_server *server);
extern int afs_server_find_by_peer(const struct rxrpc_peer *peer,
struct afs_server **_server);
extern int afs_server_get_vlconn(struct afs_server *server,
struct rxrpc_connection **_conn);
static inline
struct afs_server *afs_server_get_from_peer(struct rxrpc_peer *peer)
{
struct afs_server *server;
spin_lock(&afs_server_peer_lock);
server = peer->user;
if (server)
afs_get_server(server);
spin_unlock(&afs_server_peer_lock);
return server;
}
/*****************************************************************************/
/*
* AFS server callslot grant record
*/
struct afs_server_callslot
{
struct list_head link; /* link in server's list */
struct task_struct *task; /* process waiting to make call */
struct rxrpc_connection *conn; /* connection to use (or NULL on error) */
short nconn; /* connection slot number (-1 on error) */
char ready; /* T when ready */
int errno; /* error number if nconn==-1 */
};
extern int afs_server_request_callslot(struct afs_server *server,
struct afs_server_callslot *callslot);
extern void afs_server_release_callslot(struct afs_server *server,
struct afs_server_callslot *callslot);
#endif /* _LINUX_AFS_SERVER_H */

441
fs/afs/super.c Normal file
View File

@@ -0,0 +1,441 @@
/*
* Copyright (c) 2002 Red Hat, Inc. All rights reserved.
*
* This software may be freely redistributed under the terms of the
* GNU General Public License.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Authors: David Howells <dhowells@redhat.com>
* David Woodhouse <dwmw2@cambridge.redhat.com>
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include "vnode.h"
#include "volume.h"
#include "cell.h"
#include "cmservice.h"
#include "fsclient.h"
#include "super.h"
#include "internal.h"
#define AFS_FS_MAGIC 0x6B414653 /* 'kAFS' */
struct afs_mount_params {
int rwpath;
struct afs_cell *default_cell;
struct afs_volume *volume;
};
static void afs_i_init_once(void *foo, kmem_cache_t *cachep,
unsigned long flags);
static struct super_block *afs_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name,
void *data);
static struct inode *afs_alloc_inode(struct super_block *sb);
static void afs_put_super(struct super_block *sb);
static void afs_destroy_inode(struct inode *inode);
static struct file_system_type afs_fs_type = {
.owner = THIS_MODULE,
.name = "afs",
.get_sb = afs_get_sb,
.kill_sb = kill_anon_super,
.fs_flags = FS_BINARY_MOUNTDATA,
};
static struct super_operations afs_super_ops = {
.statfs = simple_statfs,
.alloc_inode = afs_alloc_inode,
.drop_inode = generic_delete_inode,
.destroy_inode = afs_destroy_inode,
.clear_inode = afs_clear_inode,
.put_super = afs_put_super,
};
static kmem_cache_t *afs_inode_cachep;
static atomic_t afs_count_active_inodes;
/*****************************************************************************/
/*
* initialise the filesystem
*/
int __init afs_fs_init(void)
{
int ret;
_enter("");
afs_timer_init(&afs_mntpt_expiry_timer, &afs_mntpt_expiry_timer_ops);
/* create ourselves an inode cache */
atomic_set(&afs_count_active_inodes, 0);
ret = -ENOMEM;
afs_inode_cachep = kmem_cache_create("afs_inode_cache",
sizeof(struct afs_vnode),
0,
SLAB_HWCACHE_ALIGN,
afs_i_init_once,
NULL);
if (!afs_inode_cachep) {
printk(KERN_NOTICE "kAFS: Failed to allocate inode cache\n");
return ret;
}
/* now export our filesystem to lesser mortals */
ret = register_filesystem(&afs_fs_type);
if (ret < 0) {
kmem_cache_destroy(afs_inode_cachep);
kleave(" = %d", ret);
return ret;
}
kleave(" = 0");
return 0;
} /* end afs_fs_init() */
/*****************************************************************************/
/*
* clean up the filesystem
*/
void __exit afs_fs_exit(void)
{
unregister_filesystem(&afs_fs_type);
if (atomic_read(&afs_count_active_inodes) != 0) {
printk("kAFS: %d active inode objects still present\n",
atomic_read(&afs_count_active_inodes));
BUG();
}
kmem_cache_destroy(afs_inode_cachep);
} /* end afs_fs_exit() */
/*****************************************************************************/
/*
* check that an argument has a value
*/
static int want_arg(char **_value, const char *option)
{
if (!_value || !*_value || !**_value) {
printk(KERN_NOTICE "kAFS: %s: argument missing\n", option);
return 0;
}
return 1;
} /* end want_arg() */
/*****************************************************************************/
/*
* check that there's no subsequent value
*/
static int want_no_value(char *const *_value, const char *option)
{
if (*_value && **_value) {
printk(KERN_NOTICE "kAFS: %s: Invalid argument: %s\n",
option, *_value);
return 0;
}
return 1;
} /* end want_no_value() */
/*****************************************************************************/
/*
* parse the mount options
* - this function has been shamelessly adapted from the ext3 fs which
* shamelessly adapted it from the msdos fs
*/
static int afs_super_parse_options(struct afs_mount_params *params,
char *options,
const char **devname)
{
char *key, *value;
int ret;
_enter("%s", options);
options[PAGE_SIZE - 1] = 0;
ret = 0;
while ((key = strsep(&options, ",")) != 0)
{
value = strchr(key, '=');
if (value)
*value++ = 0;
printk("kAFS: KEY: %s, VAL:%s\n", key, value ?: "-");
if (strcmp(key, "rwpath") == 0) {
if (!want_no_value(&value, "rwpath"))
return -EINVAL;
params->rwpath = 1;
continue;
}
else if (strcmp(key, "vol") == 0) {
if (!want_arg(&value, "vol"))
return -EINVAL;
*devname = value;
continue;
}
else if (strcmp(key, "cell") == 0) {
if (!want_arg(&value, "cell"))
return -EINVAL;
afs_put_cell(params->default_cell);
ret = afs_cell_lookup(value,
strlen(value),
&params->default_cell);
if (ret < 0)
return -EINVAL;
continue;
}
printk("kAFS: Unknown mount option: '%s'\n", key);
ret = -EINVAL;
goto error;
}
ret = 0;
error:
_leave(" = %d", ret);
return ret;
} /* end afs_super_parse_options() */
/*****************************************************************************/
/*
* check a superblock to see if it's the one we're looking for
*/
static int afs_test_super(struct super_block *sb, void *data)
{
struct afs_mount_params *params = data;
struct afs_super_info *as = sb->s_fs_info;
return as->volume == params->volume;
} /* end afs_test_super() */
/*****************************************************************************/
/*
* fill in the superblock
*/
static int afs_fill_super(struct super_block *sb, void *data, int silent)
{
struct afs_mount_params *params = data;
struct afs_super_info *as = NULL;
struct afs_fid fid;
struct dentry *root = NULL;
struct inode *inode = NULL;
int ret;
kenter("");
/* allocate a superblock info record */
as = kmalloc(sizeof(struct afs_super_info), GFP_KERNEL);
if (!as) {
_leave(" = -ENOMEM");
return -ENOMEM;
}
memset(as, 0, sizeof(struct afs_super_info));
afs_get_volume(params->volume);
as->volume = params->volume;
/* fill in the superblock */
sb->s_blocksize = PAGE_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = AFS_FS_MAGIC;
sb->s_op = &afs_super_ops;
sb->s_fs_info = as;
/* allocate the root inode and dentry */
fid.vid = as->volume->vid;
fid.vnode = 1;
fid.unique = 1;
ret = afs_iget(sb, &fid, &inode);
if (ret < 0)
goto error;
ret = -ENOMEM;
root = d_alloc_root(inode);
if (!root)
goto error;
sb->s_root = root;
kleave(" = 0");
return 0;
error:
iput(inode);
afs_put_volume(as->volume);
kfree(as);
sb->s_fs_info = NULL;
kleave(" = %d", ret);
return ret;
} /* end afs_fill_super() */
/*****************************************************************************/
/*
* get an AFS superblock
* - TODO: don't use get_sb_nodev(), but rather call sget() directly
*/
static struct super_block *afs_get_sb(struct file_system_type *fs_type,
int flags,
const char *dev_name,
void *options)
{
struct afs_mount_params params;
struct super_block *sb;
int ret;
_enter(",,%s,%p", dev_name, options);
memset(&params, 0, sizeof(params));
/* start the cache manager */
ret = afscm_start();
if (ret < 0) {
_leave(" = %d", ret);
return ERR_PTR(ret);
}
/* parse the options */
if (options) {
ret = afs_super_parse_options(&params, options, &dev_name);
if (ret < 0)
goto error;
if (!dev_name) {
printk("kAFS: no volume name specified\n");
ret = -EINVAL;
goto error;
}
}
/* parse the device name */
ret = afs_volume_lookup(dev_name,
params.default_cell,
params.rwpath,
&params.volume);
if (ret < 0)
goto error;
/* allocate a deviceless superblock */
sb = sget(fs_type, afs_test_super, set_anon_super, &params);
if (IS_ERR(sb))
goto error;
sb->s_flags = flags;
ret = afs_fill_super(sb, &params, flags & MS_VERBOSE ? 1 : 0);
if (ret < 0) {
up_write(&sb->s_umount);
deactivate_super(sb);
goto error;
}
sb->s_flags |= MS_ACTIVE;
afs_put_volume(params.volume);
afs_put_cell(params.default_cell);
_leave(" = %p", sb);
return sb;
error:
afs_put_volume(params.volume);
afs_put_cell(params.default_cell);
afscm_stop();
_leave(" = %d", ret);
return ERR_PTR(ret);
} /* end afs_get_sb() */
/*****************************************************************************/
/*
* finish the unmounting process on the superblock
*/
static void afs_put_super(struct super_block *sb)
{
struct afs_super_info *as = sb->s_fs_info;
_enter("");
afs_put_volume(as->volume);
afscm_stop();
_leave("");
} /* end afs_put_super() */
/*****************************************************************************/
/*
* initialise an inode cache slab element prior to any use
*/
static void afs_i_init_once(void *_vnode, kmem_cache_t *cachep,
unsigned long flags)
{
struct afs_vnode *vnode = (struct afs_vnode *) _vnode;
if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
SLAB_CTOR_CONSTRUCTOR) {
memset(vnode, 0, sizeof(*vnode));
inode_init_once(&vnode->vfs_inode);
init_waitqueue_head(&vnode->update_waitq);
spin_lock_init(&vnode->lock);
INIT_LIST_HEAD(&vnode->cb_link);
INIT_LIST_HEAD(&vnode->cb_hash_link);
afs_timer_init(&vnode->cb_timeout,
&afs_vnode_cb_timed_out_ops);
}
} /* end afs_i_init_once() */
/*****************************************************************************/
/*
* allocate an AFS inode struct from our slab cache
*/
static struct inode *afs_alloc_inode(struct super_block *sb)
{
struct afs_vnode *vnode;
vnode = (struct afs_vnode *)
kmem_cache_alloc(afs_inode_cachep, SLAB_KERNEL);
if (!vnode)
return NULL;
atomic_inc(&afs_count_active_inodes);
memset(&vnode->fid, 0, sizeof(vnode->fid));
memset(&vnode->status, 0, sizeof(vnode->status));
vnode->volume = NULL;
vnode->update_cnt = 0;
vnode->flags = 0;
return &vnode->vfs_inode;
} /* end afs_alloc_inode() */
/*****************************************************************************/
/*
* destroy an AFS inode struct
*/
static void afs_destroy_inode(struct inode *inode)
{
_enter("{%lu}", inode->i_ino);
kmem_cache_free(afs_inode_cachep, AFS_FS_I(inode));
atomic_dec(&afs_count_active_inodes);
} /* end afs_destroy_inode() */

43
fs/afs/super.h Normal file
View File

@@ -0,0 +1,43 @@
/* super.h: AFS filesystem internal private data
*
* Copyright (c) 2002 Red Hat, Inc. All rights reserved.
*
* This software may be freely redistributed under the terms of the
* GNU General Public License.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Authors: David Woodhouse <dwmw2@cambridge.redhat.com>
* David Howells <dhowells@redhat.com>
*
*/
#ifndef _LINUX_AFS_SUPER_H
#define _LINUX_AFS_SUPER_H
#include <linux/fs.h>
#include "server.h"
#ifdef __KERNEL__
/*****************************************************************************/
/*
* AFS superblock private data
* - there's one superblock per volume
*/
struct afs_super_info
{
struct afs_volume *volume; /* volume record */
char rwparent; /* T if parent is R/W AFS volume */
};
static inline struct afs_super_info *AFS_FS_S(struct super_block *sb)
{
return sb->s_fs_info;
}
#endif /* __KERNEL__ */
#endif /* _LINUX_AFS_SUPER_H */

21
fs/afs/transport.h Normal file
View File

@@ -0,0 +1,21 @@
/* transport.h: AFS transport management
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_TRANSPORT_H
#define _LINUX_AFS_TRANSPORT_H
#include "types.h"
#include <rxrpc/transport.h>
/* the cache manager transport endpoint */
extern struct rxrpc_transport *afs_transport;
#endif /* _LINUX_AFS_TRANSPORT_H */

125
fs/afs/types.h Normal file
View File

@@ -0,0 +1,125 @@
/* types.h: AFS types
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_TYPES_H
#define _LINUX_AFS_TYPES_H
#ifdef __KERNEL__
#include <rxrpc/types.h>
#endif /* __KERNEL__ */
typedef unsigned afs_volid_t;
typedef unsigned afs_vnodeid_t;
typedef unsigned long long afs_dataversion_t;
typedef enum {
AFSVL_RWVOL, /* read/write volume */
AFSVL_ROVOL, /* read-only volume */
AFSVL_BACKVOL, /* backup volume */
} __attribute__((packed)) afs_voltype_t;
typedef enum {
AFS_FTYPE_INVALID = 0,
AFS_FTYPE_FILE = 1,
AFS_FTYPE_DIR = 2,
AFS_FTYPE_SYMLINK = 3,
} afs_file_type_t;
#ifdef __KERNEL__
struct afs_cell;
struct afs_vnode;
/*****************************************************************************/
/*
* AFS file identifier
*/
struct afs_fid
{
afs_volid_t vid; /* volume ID */
afs_vnodeid_t vnode; /* file index within volume */
unsigned unique; /* unique ID number (file index version) */
};
/*****************************************************************************/
/*
* AFS callback notification
*/
typedef enum {
AFSCM_CB_UNTYPED = 0, /* no type set on CB break */
AFSCM_CB_EXCLUSIVE = 1, /* CB exclusive to CM [not implemented] */
AFSCM_CB_SHARED = 2, /* CB shared by other CM's */
AFSCM_CB_DROPPED = 3, /* CB promise cancelled by file server */
} afs_callback_type_t;
struct afs_callback
{
struct afs_server *server; /* server that made the promise */
struct afs_fid fid; /* file identifier */
unsigned version; /* callback version */
unsigned expiry; /* time at which expires */
afs_callback_type_t type; /* type of callback */
};
#define AFSCBMAX 50
/*****************************************************************************/
/*
* AFS volume information
*/
struct afs_volume_info
{
afs_volid_t vid; /* volume ID */
afs_voltype_t type; /* type of this volume */
afs_volid_t type_vids[5]; /* volume ID's for possible types for this vol */
/* list of fileservers serving this volume */
size_t nservers; /* number of entries used in servers[] */
struct {
struct in_addr addr; /* fileserver address */
} servers[8];
};
/*****************************************************************************/
/*
* AFS file status information
*/
struct afs_file_status
{
unsigned if_version; /* interface version */
#define AFS_FSTATUS_VERSION 1
afs_file_type_t type; /* file type */
unsigned nlink; /* link count */
size_t size; /* file size */
afs_dataversion_t version; /* current data version */
unsigned author; /* author ID */
unsigned owner; /* owner ID */
unsigned caller_access; /* access rights for authenticated caller */
unsigned anon_access; /* access rights for unauthenticated caller */
umode_t mode; /* UNIX mode */
struct afs_fid parent; /* parent file ID */
time_t mtime_client; /* last time client changed data */
time_t mtime_server; /* last time server changed data */
};
/*****************************************************************************/
/*
* AFS volume synchronisation information
*/
struct afs_volsync
{
time_t creation; /* volume creation time */
};
#endif /* __KERNEL__ */
#endif /* _LINUX_AFS_TYPES_H */

695
fs/afs/vlclient.c Normal file
View File

@@ -0,0 +1,695 @@
/* vlclient.c: AFS Volume Location Service client
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/init.h>
#include <linux/sched.h>
#include <rxrpc/rxrpc.h>
#include <rxrpc/transport.h>
#include <rxrpc/connection.h>
#include <rxrpc/call.h>
#include "server.h"
#include "volume.h"
#include "vlclient.h"
#include "kafsasyncd.h"
#include "kafstimod.h"
#include "errors.h"
#include "internal.h"
#define VLGETENTRYBYID 503 /* AFS Get Cache Entry By ID operation ID */
#define VLGETENTRYBYNAME 504 /* AFS Get Cache Entry By Name operation ID */
#define VLPROBE 514 /* AFS Probe Volume Location Service operation ID */
static void afs_rxvl_get_entry_by_id_attn(struct rxrpc_call *call);
static void afs_rxvl_get_entry_by_id_error(struct rxrpc_call *call);
/*****************************************************************************/
/*
* map afs VL abort codes to/from Linux error codes
* - called with call->lock held
*/
static void afs_rxvl_aemap(struct rxrpc_call *call)
{
int err;
_enter("{%u,%u,%d}",
call->app_err_state, call->app_abort_code, call->app_errno);
switch (call->app_err_state) {
case RXRPC_ESTATE_LOCAL_ABORT:
call->app_abort_code = -call->app_errno;
return;
case RXRPC_ESTATE_PEER_ABORT:
switch (call->app_abort_code) {
case AFSVL_IDEXIST: err = -EEXIST; break;
case AFSVL_IO: err = -EREMOTEIO; break;
case AFSVL_NAMEEXIST: err = -EEXIST; break;
case AFSVL_CREATEFAIL: err = -EREMOTEIO; break;
case AFSVL_NOENT: err = -ENOMEDIUM; break;
case AFSVL_EMPTY: err = -ENOMEDIUM; break;
case AFSVL_ENTDELETED: err = -ENOMEDIUM; break;
case AFSVL_BADNAME: err = -EINVAL; break;
case AFSVL_BADINDEX: err = -EINVAL; break;
case AFSVL_BADVOLTYPE: err = -EINVAL; break;
case AFSVL_BADSERVER: err = -EINVAL; break;
case AFSVL_BADPARTITION: err = -EINVAL; break;
case AFSVL_REPSFULL: err = -EFBIG; break;
case AFSVL_NOREPSERVER: err = -ENOENT; break;
case AFSVL_DUPREPSERVER: err = -EEXIST; break;
case AFSVL_RWNOTFOUND: err = -ENOENT; break;
case AFSVL_BADREFCOUNT: err = -EINVAL; break;
case AFSVL_SIZEEXCEEDED: err = -EINVAL; break;
case AFSVL_BADENTRY: err = -EINVAL; break;
case AFSVL_BADVOLIDBUMP: err = -EINVAL; break;
case AFSVL_IDALREADYHASHED: err = -EINVAL; break;
case AFSVL_ENTRYLOCKED: err = -EBUSY; break;
case AFSVL_BADVOLOPER: err = -EBADRQC; break;
case AFSVL_BADRELLOCKTYPE: err = -EINVAL; break;
case AFSVL_RERELEASE: err = -EREMOTEIO; break;
case AFSVL_BADSERVERFLAG: err = -EINVAL; break;
case AFSVL_PERM: err = -EACCES; break;
case AFSVL_NOMEM: err = -EREMOTEIO; break;
default:
err = afs_abort_to_error(call->app_abort_code);
break;
}
call->app_errno = err;
return;
default:
return;
}
} /* end afs_rxvl_aemap() */
#if 0
/*****************************************************************************/
/*
* probe a volume location server to see if it is still alive -- unused
*/
static int afs_rxvl_probe(struct afs_server *server, int alloc_flags)
{
struct rxrpc_connection *conn;
struct rxrpc_call *call;
struct kvec piov[1];
size_t sent;
int ret;
__be32 param[1];
DECLARE_WAITQUEUE(myself, current);
/* get hold of the vlserver connection */
ret = afs_server_get_vlconn(server, &conn);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = VLPROBE;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq, &myself);
/* marshall the parameters */
param[0] = htonl(VLPROBE);
piov[0].iov_len = sizeof(param);
piov[0].iov_base = param;
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET,
alloc_flags, 0, &sent);
if (ret < 0)
goto abort;
/* wait for the reply to completely arrive */
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY ||
signal_pending(current))
break;
schedule();
}
set_current_state(TASK_RUNNING);
ret = -EINTR;
if (signal_pending(current))
goto abort;
switch (call->app_call_state) {
case RXRPC_CSTATE_ERROR:
ret = call->app_errno;
goto out_unwait;
case RXRPC_CSTATE_CLNT_GOT_REPLY:
ret = 0;
goto out_unwait;
default:
BUG();
}
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq, &myself);
rxrpc_put_call(call);
out_put_conn:
rxrpc_put_connection(conn);
out:
return ret;
} /* end afs_rxvl_probe() */
#endif
/*****************************************************************************/
/*
* look up a volume location database entry by name
*/
int afs_rxvl_get_entry_by_name(struct afs_server *server,
const char *volname,
unsigned volnamesz,
struct afs_cache_vlocation *entry)
{
DECLARE_WAITQUEUE(myself, current);
struct rxrpc_connection *conn;
struct rxrpc_call *call;
struct kvec piov[3];
unsigned tmp;
size_t sent;
int ret, loop;
__be32 *bp, param[2], zero;
_enter(",%*.*s,%u,", volnamesz, volnamesz, volname, volnamesz);
memset(entry, 0, sizeof(*entry));
/* get hold of the vlserver connection */
ret = afs_server_get_vlconn(server, &conn);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = VLGETENTRYBYNAME;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq, &myself);
/* marshall the parameters */
piov[1].iov_len = volnamesz;
piov[1].iov_base = (char *) volname;
zero = 0;
piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3;
piov[2].iov_base = &zero;
param[0] = htonl(VLGETENTRYBYNAME);
param[1] = htonl(piov[1].iov_len);
piov[0].iov_len = sizeof(param);
piov[0].iov_base = param;
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0)
goto abort;
/* wait for the reply to completely arrive */
bp = rxrpc_call_alloc_scratch(call, 384);
ret = rxrpc_call_read_data(call, bp, 384,
RXRPC_CALL_READ_BLOCK |
RXRPC_CALL_READ_ALL);
if (ret < 0) {
if (ret == -ECONNABORTED) {
ret = call->app_errno;
goto out_unwait;
}
goto abort;
}
/* unmarshall the reply */
for (loop = 0; loop < 64; loop++)
entry->name[loop] = ntohl(*bp++);
bp++; /* final NUL */
bp++; /* type */
entry->nservers = ntohl(*bp++);
for (loop = 0; loop < 8; loop++)
entry->servers[loop].s_addr = *bp++;
bp += 8; /* partition IDs */
for (loop = 0; loop < 8; loop++) {
tmp = ntohl(*bp++);
if (tmp & AFS_VLSF_RWVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_RW;
if (tmp & AFS_VLSF_ROVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_RO;
if (tmp & AFS_VLSF_BACKVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_BAK;
}
entry->vid[0] = ntohl(*bp++);
entry->vid[1] = ntohl(*bp++);
entry->vid[2] = ntohl(*bp++);
bp++; /* clone ID */
tmp = ntohl(*bp++); /* flags */
if (tmp & AFS_VLF_RWEXISTS)
entry->vidmask |= AFS_VOL_VTM_RW;
if (tmp & AFS_VLF_ROEXISTS)
entry->vidmask |= AFS_VOL_VTM_RO;
if (tmp & AFS_VLF_BACKEXISTS)
entry->vidmask |= AFS_VOL_VTM_BAK;
ret = -ENOMEDIUM;
if (!entry->vidmask)
goto abort;
/* success */
entry->rtime = get_seconds();
ret = 0;
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq, &myself);
rxrpc_put_call(call);
out_put_conn:
rxrpc_put_connection(conn);
out:
_leave(" = %d", ret);
return ret;
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
goto out_unwait;
} /* end afs_rxvl_get_entry_by_name() */
/*****************************************************************************/
/*
* look up a volume location database entry by ID
*/
int afs_rxvl_get_entry_by_id(struct afs_server *server,
afs_volid_t volid,
afs_voltype_t voltype,
struct afs_cache_vlocation *entry)
{
DECLARE_WAITQUEUE(myself, current);
struct rxrpc_connection *conn;
struct rxrpc_call *call;
struct kvec piov[1];
unsigned tmp;
size_t sent;
int ret, loop;
__be32 *bp, param[3];
_enter(",%x,%d,", volid, voltype);
memset(entry, 0, sizeof(*entry));
/* get hold of the vlserver connection */
ret = afs_server_get_vlconn(server, &conn);
if (ret < 0)
goto out;
/* create a call through that connection */
ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
goto out_put_conn;
}
call->app_opcode = VLGETENTRYBYID;
/* we want to get event notifications from the call */
add_wait_queue(&call->waitq, &myself);
/* marshall the parameters */
param[0] = htonl(VLGETENTRYBYID);
param[1] = htonl(volid);
param[2] = htonl(voltype);
piov[0].iov_len = sizeof(param);
piov[0].iov_base = param;
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0)
goto abort;
/* wait for the reply to completely arrive */
bp = rxrpc_call_alloc_scratch(call, 384);
ret = rxrpc_call_read_data(call, bp, 384,
RXRPC_CALL_READ_BLOCK |
RXRPC_CALL_READ_ALL);
if (ret < 0) {
if (ret == -ECONNABORTED) {
ret = call->app_errno;
goto out_unwait;
}
goto abort;
}
/* unmarshall the reply */
for (loop = 0; loop < 64; loop++)
entry->name[loop] = ntohl(*bp++);
bp++; /* final NUL */
bp++; /* type */
entry->nservers = ntohl(*bp++);
for (loop = 0; loop < 8; loop++)
entry->servers[loop].s_addr = *bp++;
bp += 8; /* partition IDs */
for (loop = 0; loop < 8; loop++) {
tmp = ntohl(*bp++);
if (tmp & AFS_VLSF_RWVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_RW;
if (tmp & AFS_VLSF_ROVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_RO;
if (tmp & AFS_VLSF_BACKVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_BAK;
}
entry->vid[0] = ntohl(*bp++);
entry->vid[1] = ntohl(*bp++);
entry->vid[2] = ntohl(*bp++);
bp++; /* clone ID */
tmp = ntohl(*bp++); /* flags */
if (tmp & AFS_VLF_RWEXISTS)
entry->vidmask |= AFS_VOL_VTM_RW;
if (tmp & AFS_VLF_ROEXISTS)
entry->vidmask |= AFS_VOL_VTM_RO;
if (tmp & AFS_VLF_BACKEXISTS)
entry->vidmask |= AFS_VOL_VTM_BAK;
ret = -ENOMEDIUM;
if (!entry->vidmask)
goto abort;
#if 0 /* TODO: remove */
entry->nservers = 3;
entry->servers[0].s_addr = htonl(0xac101249);
entry->servers[1].s_addr = htonl(0xac101243);
entry->servers[2].s_addr = htonl(0xac10125b /*0xac10125b*/);
entry->srvtmask[0] = AFS_VOL_VTM_RO;
entry->srvtmask[1] = AFS_VOL_VTM_RO;
entry->srvtmask[2] = AFS_VOL_VTM_RO | AFS_VOL_VTM_RW;
#endif
/* success */
entry->rtime = get_seconds();
ret = 0;
out_unwait:
set_current_state(TASK_RUNNING);
remove_wait_queue(&call->waitq, &myself);
rxrpc_put_call(call);
out_put_conn:
rxrpc_put_connection(conn);
out:
_leave(" = %d", ret);
return ret;
abort:
set_current_state(TASK_UNINTERRUPTIBLE);
rxrpc_call_abort(call, ret);
schedule();
goto out_unwait;
} /* end afs_rxvl_get_entry_by_id() */
/*****************************************************************************/
/*
* look up a volume location database entry by ID asynchronously
*/
int afs_rxvl_get_entry_by_id_async(struct afs_async_op *op,
afs_volid_t volid,
afs_voltype_t voltype)
{
struct rxrpc_connection *conn;
struct rxrpc_call *call;
struct kvec piov[1];
size_t sent;
int ret;
__be32 param[3];
_enter(",%x,%d,", volid, voltype);
/* get hold of the vlserver connection */
ret = afs_server_get_vlconn(op->server, &conn);
if (ret < 0) {
_leave(" = %d", ret);
return ret;
}
/* create a call through that connection */
ret = rxrpc_create_call(conn,
afs_rxvl_get_entry_by_id_attn,
afs_rxvl_get_entry_by_id_error,
afs_rxvl_aemap,
&op->call);
rxrpc_put_connection(conn);
if (ret < 0) {
printk("kAFS: Unable to create call: %d\n", ret);
_leave(" = %d", ret);
return ret;
}
op->call->app_opcode = VLGETENTRYBYID;
op->call->app_user = op;
call = op->call;
rxrpc_get_call(call);
/* send event notifications from the call to kafsasyncd */
afs_kafsasyncd_begin_op(op);
/* marshall the parameters */
param[0] = htonl(VLGETENTRYBYID);
param[1] = htonl(volid);
param[2] = htonl(voltype);
piov[0].iov_len = sizeof(param);
piov[0].iov_base = param;
/* allocate result read buffer in scratch space */
call->app_scr_ptr = rxrpc_call_alloc_scratch(op->call, 384);
/* send the parameters to the server */
ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
0, &sent);
if (ret < 0) {
rxrpc_call_abort(call, ret); /* handle from kafsasyncd */
ret = 0;
goto out;
}
/* wait for the reply to completely arrive */
ret = rxrpc_call_read_data(call, call->app_scr_ptr, 384, 0);
switch (ret) {
case 0:
case -EAGAIN:
case -ECONNABORTED:
ret = 0;
break; /* all handled by kafsasyncd */
default:
rxrpc_call_abort(call, ret); /* make kafsasyncd handle it */
ret = 0;
break;
}
out:
rxrpc_put_call(call);
_leave(" = %d", ret);
return ret;
} /* end afs_rxvl_get_entry_by_id_async() */
/*****************************************************************************/
/*
* attend to the asynchronous get VLDB entry by ID
*/
int afs_rxvl_get_entry_by_id_async2(struct afs_async_op *op,
struct afs_cache_vlocation *entry)
{
__be32 *bp;
__u32 tmp;
int loop, ret;
_enter("{op=%p cst=%u}", op, op->call->app_call_state);
memset(entry, 0, sizeof(*entry));
if (op->call->app_call_state == RXRPC_CSTATE_COMPLETE) {
/* operation finished */
afs_kafsasyncd_terminate_op(op);
bp = op->call->app_scr_ptr;
/* unmarshall the reply */
for (loop = 0; loop < 64; loop++)
entry->name[loop] = ntohl(*bp++);
bp++; /* final NUL */
bp++; /* type */
entry->nservers = ntohl(*bp++);
for (loop = 0; loop < 8; loop++)
entry->servers[loop].s_addr = *bp++;
bp += 8; /* partition IDs */
for (loop = 0; loop < 8; loop++) {
tmp = ntohl(*bp++);
if (tmp & AFS_VLSF_RWVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_RW;
if (tmp & AFS_VLSF_ROVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_RO;
if (tmp & AFS_VLSF_BACKVOL)
entry->srvtmask[loop] |= AFS_VOL_VTM_BAK;
}
entry->vid[0] = ntohl(*bp++);
entry->vid[1] = ntohl(*bp++);
entry->vid[2] = ntohl(*bp++);
bp++; /* clone ID */
tmp = ntohl(*bp++); /* flags */
if (tmp & AFS_VLF_RWEXISTS)
entry->vidmask |= AFS_VOL_VTM_RW;
if (tmp & AFS_VLF_ROEXISTS)
entry->vidmask |= AFS_VOL_VTM_RO;
if (tmp & AFS_VLF_BACKEXISTS)
entry->vidmask |= AFS_VOL_VTM_BAK;
ret = -ENOMEDIUM;
if (!entry->vidmask) {
rxrpc_call_abort(op->call, ret);
goto done;
}
#if 0 /* TODO: remove */
entry->nservers = 3;
entry->servers[0].s_addr = htonl(0xac101249);
entry->servers[1].s_addr = htonl(0xac101243);
entry->servers[2].s_addr = htonl(0xac10125b /*0xac10125b*/);
entry->srvtmask[0] = AFS_VOL_VTM_RO;
entry->srvtmask[1] = AFS_VOL_VTM_RO;
entry->srvtmask[2] = AFS_VOL_VTM_RO | AFS_VOL_VTM_RW;
#endif
/* success */
entry->rtime = get_seconds();
ret = 0;
goto done;
}
if (op->call->app_call_state == RXRPC_CSTATE_ERROR) {
/* operation error */
ret = op->call->app_errno;
goto done;
}
_leave(" = -EAGAIN");
return -EAGAIN;
done:
rxrpc_put_call(op->call);
op->call = NULL;
_leave(" = %d", ret);
return ret;
} /* end afs_rxvl_get_entry_by_id_async2() */
/*****************************************************************************/
/*
* handle attention events on an async get-entry-by-ID op
* - called from krxiod
*/
static void afs_rxvl_get_entry_by_id_attn(struct rxrpc_call *call)
{
struct afs_async_op *op = call->app_user;
_enter("{op=%p cst=%u}", op, call->app_call_state);
switch (call->app_call_state) {
case RXRPC_CSTATE_COMPLETE:
afs_kafsasyncd_attend_op(op);
break;
case RXRPC_CSTATE_CLNT_RCV_REPLY:
if (call->app_async_read)
break;
case RXRPC_CSTATE_CLNT_GOT_REPLY:
if (call->app_read_count == 0)
break;
printk("kAFS: Reply bigger than expected"
" {cst=%u asyn=%d mark=%Zu rdy=%Zu pr=%u%s}",
call->app_call_state,
call->app_async_read,
call->app_mark,
call->app_ready_qty,
call->pkt_rcv_count,
call->app_last_rcv ? " last" : "");
rxrpc_call_abort(call, -EBADMSG);
break;
default:
BUG();
}
_leave("");
} /* end afs_rxvl_get_entry_by_id_attn() */
/*****************************************************************************/
/*
* handle error events on an async get-entry-by-ID op
* - called from krxiod
*/
static void afs_rxvl_get_entry_by_id_error(struct rxrpc_call *call)
{
struct afs_async_op *op = call->app_user;
_enter("{op=%p cst=%u}", op, call->app_call_state);
afs_kafsasyncd_attend_op(op);
_leave("");
} /* end afs_rxvl_get_entry_by_id_error() */

93
fs/afs/vlclient.h Normal file
View File

@@ -0,0 +1,93 @@
/* vlclient.h: Volume Location Service client interface
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_VLCLIENT_H
#define _LINUX_AFS_VLCLIENT_H
#include "types.h"
enum AFSVL_Errors {
AFSVL_IDEXIST = 363520, /* Volume Id entry exists in vl database */
AFSVL_IO = 363521, /* I/O related error */
AFSVL_NAMEEXIST = 363522, /* Volume name entry exists in vl database */
AFSVL_CREATEFAIL = 363523, /* Internal creation failure */
AFSVL_NOENT = 363524, /* No such entry */
AFSVL_EMPTY = 363525, /* Vl database is empty */
AFSVL_ENTDELETED = 363526, /* Entry is deleted (soft delete) */
AFSVL_BADNAME = 363527, /* Volume name is illegal */
AFSVL_BADINDEX = 363528, /* Index is out of range */
AFSVL_BADVOLTYPE = 363529, /* Bad volume type */
AFSVL_BADSERVER = 363530, /* Illegal server number (out of range) */
AFSVL_BADPARTITION = 363531, /* Bad partition number */
AFSVL_REPSFULL = 363532, /* Run out of space for Replication sites */
AFSVL_NOREPSERVER = 363533, /* No such Replication server site exists */
AFSVL_DUPREPSERVER = 363534, /* Replication site already exists */
AFSVL_RWNOTFOUND = 363535, /* Parent R/W entry not found */
AFSVL_BADREFCOUNT = 363536, /* Illegal Reference Count number */
AFSVL_SIZEEXCEEDED = 363537, /* Vl size for attributes exceeded */
AFSVL_BADENTRY = 363538, /* Bad incoming vl entry */
AFSVL_BADVOLIDBUMP = 363539, /* Illegal max volid increment */
AFSVL_IDALREADYHASHED = 363540, /* RO/BACK id already hashed */
AFSVL_ENTRYLOCKED = 363541, /* Vl entry is already locked */
AFSVL_BADVOLOPER = 363542, /* Bad volume operation code */
AFSVL_BADRELLOCKTYPE = 363543, /* Bad release lock type */
AFSVL_RERELEASE = 363544, /* Status report: last release was aborted */
AFSVL_BADSERVERFLAG = 363545, /* Invalid replication site server <20>ag */
AFSVL_PERM = 363546, /* No permission access */
AFSVL_NOMEM = 363547, /* malloc/realloc failed to alloc enough memory */
};
/* maps to "struct vldbentry" in vvl-spec.pdf */
struct afs_vldbentry {
char name[65]; /* name of volume (including NUL char) */
afs_voltype_t type; /* volume type */
unsigned num_servers; /* num servers that hold instances of this vol */
unsigned clone_id; /* cloning ID */
unsigned flags;
#define AFS_VLF_RWEXISTS 0x1000 /* R/W volume exists */
#define AFS_VLF_ROEXISTS 0x2000 /* R/O volume exists */
#define AFS_VLF_BACKEXISTS 0x4000 /* backup volume exists */
afs_volid_t volume_ids[3]; /* volume IDs */
struct {
struct in_addr addr; /* server address */
unsigned partition; /* partition ID on this server */
unsigned flags; /* server specific flags */
#define AFS_VLSF_NEWREPSITE 0x0001 /* unused */
#define AFS_VLSF_ROVOL 0x0002 /* this server holds a R/O instance of the volume */
#define AFS_VLSF_RWVOL 0x0004 /* this server holds a R/W instance of the volume */
#define AFS_VLSF_BACKVOL 0x0008 /* this server holds a backup instance of the volume */
} servers[8];
};
/* look up a volume location database entry by name */
extern int afs_rxvl_get_entry_by_name(struct afs_server *server,
const char *volname,
unsigned volnamesz,
struct afs_cache_vlocation *entry);
/* look up a volume location database entry by ID */
extern int afs_rxvl_get_entry_by_id(struct afs_server *server,
afs_volid_t volid,
afs_voltype_t voltype,
struct afs_cache_vlocation *entry);
extern int afs_rxvl_get_entry_by_id_async(struct afs_async_op *op,
afs_volid_t volid,
afs_voltype_t voltype);
extern int afs_rxvl_get_entry_by_id_async2(struct afs_async_op *op,
struct afs_cache_vlocation *entry);
#endif /* _LINUX_AFS_VLCLIENT_H */

954
fs/afs/vlocation.c Normal file
View File

@@ -0,0 +1,954 @@
/* vlocation.c: volume location management
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include "volume.h"
#include "cell.h"
#include "cmservice.h"
#include "fsclient.h"
#include "vlclient.h"
#include "kafstimod.h"
#include <rxrpc/connection.h>
#include "internal.h"
#define AFS_VLDB_TIMEOUT HZ*1000
static void afs_vlocation_update_timer(struct afs_timer *timer);
static void afs_vlocation_update_attend(struct afs_async_op *op);
static void afs_vlocation_update_discard(struct afs_async_op *op);
static void __afs_put_vlocation(struct afs_vlocation *vlocation);
static void __afs_vlocation_timeout(struct afs_timer *timer)
{
struct afs_vlocation *vlocation =
list_entry(timer, struct afs_vlocation, timeout);
_debug("VL TIMEOUT [%s{u=%d}]",
vlocation->vldb.name, atomic_read(&vlocation->usage));
afs_vlocation_do_timeout(vlocation);
}
static const struct afs_timer_ops afs_vlocation_timer_ops = {
.timed_out = __afs_vlocation_timeout,
};
static const struct afs_timer_ops afs_vlocation_update_timer_ops = {
.timed_out = afs_vlocation_update_timer,
};
static const struct afs_async_op_ops afs_vlocation_update_op_ops = {
.attend = afs_vlocation_update_attend,
.discard = afs_vlocation_update_discard,
};
static LIST_HEAD(afs_vlocation_update_pendq); /* queue of VLs awaiting update */
static struct afs_vlocation *afs_vlocation_update; /* VL currently being updated */
static DEFINE_SPINLOCK(afs_vlocation_update_lock); /* lock guarding update queue */
#ifdef AFS_CACHING_SUPPORT
static cachefs_match_val_t afs_vlocation_cache_match(void *target,
const void *entry);
static void afs_vlocation_cache_update(void *source, void *entry);
struct cachefs_index_def afs_vlocation_cache_index_def = {
.name = "vldb",
.data_size = sizeof(struct afs_cache_vlocation),
.keys[0] = { CACHEFS_INDEX_KEYS_ASCIIZ, 64 },
.match = afs_vlocation_cache_match,
.update = afs_vlocation_cache_update,
};
#endif
/*****************************************************************************/
/*
* iterate through the VL servers in a cell until one of them admits knowing
* about the volume in question
* - caller must have cell->vl_sem write-locked
*/
static int afs_vlocation_access_vl_by_name(struct afs_vlocation *vlocation,
const char *name,
unsigned namesz,
struct afs_cache_vlocation *vldb)
{
struct afs_server *server = NULL;
struct afs_cell *cell = vlocation->cell;
int count, ret;
_enter("%s,%*.*s,%u", cell->name, namesz, namesz, name, namesz);
ret = -ENOMEDIUM;
for (count = cell->vl_naddrs; count > 0; count--) {
_debug("CellServ[%hu]: %08x",
cell->vl_curr_svix,
cell->vl_addrs[cell->vl_curr_svix].s_addr);
/* try and create a server */
ret = afs_server_lookup(cell,
&cell->vl_addrs[cell->vl_curr_svix],
&server);
switch (ret) {
case 0:
break;
case -ENOMEM:
case -ENONET:
goto out;
default:
goto rotate;
}
/* attempt to access the VL server */
ret = afs_rxvl_get_entry_by_name(server, name, namesz, vldb);
switch (ret) {
case 0:
afs_put_server(server);
goto out;
case -ENOMEM:
case -ENONET:
case -ENETUNREACH:
case -EHOSTUNREACH:
case -ECONNREFUSED:
down_write(&server->sem);
if (server->vlserver) {
rxrpc_put_connection(server->vlserver);
server->vlserver = NULL;
}
up_write(&server->sem);
afs_put_server(server);
if (ret == -ENOMEM || ret == -ENONET)
goto out;
goto rotate;
case -ENOMEDIUM:
afs_put_server(server);
goto out;
default:
afs_put_server(server);
ret = -ENOMEDIUM;
goto rotate;
}
/* rotate the server records upon lookup failure */
rotate:
cell->vl_curr_svix++;
cell->vl_curr_svix %= cell->vl_naddrs;
}
out:
_leave(" = %d", ret);
return ret;
} /* end afs_vlocation_access_vl_by_name() */
/*****************************************************************************/
/*
* iterate through the VL servers in a cell until one of them admits knowing
* about the volume in question
* - caller must have cell->vl_sem write-locked
*/
static int afs_vlocation_access_vl_by_id(struct afs_vlocation *vlocation,
afs_volid_t volid,
afs_voltype_t voltype,
struct afs_cache_vlocation *vldb)
{
struct afs_server *server = NULL;
struct afs_cell *cell = vlocation->cell;
int count, ret;
_enter("%s,%x,%d,", cell->name, volid, voltype);
ret = -ENOMEDIUM;
for (count = cell->vl_naddrs; count > 0; count--) {
_debug("CellServ[%hu]: %08x",
cell->vl_curr_svix,
cell->vl_addrs[cell->vl_curr_svix].s_addr);
/* try and create a server */
ret = afs_server_lookup(cell,
&cell->vl_addrs[cell->vl_curr_svix],
&server);
switch (ret) {
case 0:
break;
case -ENOMEM:
case -ENONET:
goto out;
default:
goto rotate;
}
/* attempt to access the VL server */
ret = afs_rxvl_get_entry_by_id(server, volid, voltype, vldb);
switch (ret) {
case 0:
afs_put_server(server);
goto out;
case -ENOMEM:
case -ENONET:
case -ENETUNREACH:
case -EHOSTUNREACH:
case -ECONNREFUSED:
down_write(&server->sem);
if (server->vlserver) {
rxrpc_put_connection(server->vlserver);
server->vlserver = NULL;
}
up_write(&server->sem);
afs_put_server(server);
if (ret == -ENOMEM || ret == -ENONET)
goto out;
goto rotate;
case -ENOMEDIUM:
afs_put_server(server);
goto out;
default:
afs_put_server(server);
ret = -ENOMEDIUM;
goto rotate;
}
/* rotate the server records upon lookup failure */
rotate:
cell->vl_curr_svix++;
cell->vl_curr_svix %= cell->vl_naddrs;
}
out:
_leave(" = %d", ret);
return ret;
} /* end afs_vlocation_access_vl_by_id() */
/*****************************************************************************/
/*
* lookup volume location
* - caller must have cell->vol_sem write-locked
* - iterate through the VL servers in a cell until one of them admits knowing
* about the volume in question
* - lookup in the local cache if not able to find on the VL server
* - insert/update in the local cache if did get a VL response
*/
int afs_vlocation_lookup(struct afs_cell *cell,
const char *name,
unsigned namesz,
struct afs_vlocation **_vlocation)
{
struct afs_cache_vlocation vldb;
struct afs_vlocation *vlocation;
afs_voltype_t voltype;
afs_volid_t vid;
int active = 0, ret;
_enter("{%s},%*.*s,%u,", cell->name, namesz, namesz, name, namesz);
if (namesz > sizeof(vlocation->vldb.name)) {
_leave(" = -ENAMETOOLONG");
return -ENAMETOOLONG;
}
/* search the cell's active list first */
list_for_each_entry(vlocation, &cell->vl_list, link) {
if (namesz < sizeof(vlocation->vldb.name) &&
vlocation->vldb.name[namesz] != '\0')
continue;
if (memcmp(vlocation->vldb.name, name, namesz) == 0)
goto found_in_memory;
}
/* search the cell's graveyard list second */
spin_lock(&cell->vl_gylock);
list_for_each_entry(vlocation, &cell->vl_graveyard, link) {
if (namesz < sizeof(vlocation->vldb.name) &&
vlocation->vldb.name[namesz] != '\0')
continue;
if (memcmp(vlocation->vldb.name, name, namesz) == 0)
goto found_in_graveyard;
}
spin_unlock(&cell->vl_gylock);
/* not in the cell's in-memory lists - create a new record */
vlocation = kmalloc(sizeof(struct afs_vlocation), GFP_KERNEL);
if (!vlocation)
return -ENOMEM;
memset(vlocation, 0, sizeof(struct afs_vlocation));
atomic_set(&vlocation->usage, 1);
INIT_LIST_HEAD(&vlocation->link);
rwlock_init(&vlocation->lock);
memcpy(vlocation->vldb.name, name, namesz);
afs_timer_init(&vlocation->timeout, &afs_vlocation_timer_ops);
afs_timer_init(&vlocation->upd_timer, &afs_vlocation_update_timer_ops);
afs_async_op_init(&vlocation->upd_op, &afs_vlocation_update_op_ops);
afs_get_cell(cell);
vlocation->cell = cell;
list_add_tail(&vlocation->link, &cell->vl_list);
#ifdef AFS_CACHING_SUPPORT
/* we want to store it in the cache, plus it might already be
* encached */
cachefs_acquire_cookie(cell->cache,
&afs_volume_cache_index_def,
vlocation,
&vlocation->cache);
if (vlocation->valid)
goto found_in_cache;
#endif
/* try to look up an unknown volume in the cell VL databases by name */
ret = afs_vlocation_access_vl_by_name(vlocation, name, namesz, &vldb);
if (ret < 0) {
printk("kAFS: failed to locate '%*.*s' in cell '%s'\n",
namesz, namesz, name, cell->name);
goto error;
}
goto found_on_vlserver;
found_in_graveyard:
/* found in the graveyard - resurrect */
_debug("found in graveyard");
atomic_inc(&vlocation->usage);
list_del(&vlocation->link);
list_add_tail(&vlocation->link, &cell->vl_list);
spin_unlock(&cell->vl_gylock);
afs_kafstimod_del_timer(&vlocation->timeout);
goto active;
found_in_memory:
/* found in memory - check to see if it's active */
_debug("found in memory");
atomic_inc(&vlocation->usage);
active:
active = 1;
#ifdef AFS_CACHING_SUPPORT
found_in_cache:
#endif
/* try to look up a cached volume in the cell VL databases by ID */
_debug("found in cache");
_debug("Locally Cached: %s %02x { %08x(%x) %08x(%x) %08x(%x) }",
vlocation->vldb.name,
vlocation->vldb.vidmask,
ntohl(vlocation->vldb.servers[0].s_addr),
vlocation->vldb.srvtmask[0],
ntohl(vlocation->vldb.servers[1].s_addr),
vlocation->vldb.srvtmask[1],
ntohl(vlocation->vldb.servers[2].s_addr),
vlocation->vldb.srvtmask[2]
);
_debug("Vids: %08x %08x %08x",
vlocation->vldb.vid[0],
vlocation->vldb.vid[1],
vlocation->vldb.vid[2]);
if (vlocation->vldb.vidmask & AFS_VOL_VTM_RW) {
vid = vlocation->vldb.vid[0];
voltype = AFSVL_RWVOL;
}
else if (vlocation->vldb.vidmask & AFS_VOL_VTM_RO) {
vid = vlocation->vldb.vid[1];
voltype = AFSVL_ROVOL;
}
else if (vlocation->vldb.vidmask & AFS_VOL_VTM_BAK) {
vid = vlocation->vldb.vid[2];
voltype = AFSVL_BACKVOL;
}
else {
BUG();
vid = 0;
voltype = 0;
}
ret = afs_vlocation_access_vl_by_id(vlocation, vid, voltype, &vldb);
switch (ret) {
/* net error */
default:
printk("kAFS: failed to volume '%*.*s' (%x) up in '%s': %d\n",
namesz, namesz, name, vid, cell->name, ret);
goto error;
/* pulled from local cache into memory */
case 0:
goto found_on_vlserver;
/* uh oh... looks like the volume got deleted */
case -ENOMEDIUM:
printk("kAFS: volume '%*.*s' (%x) does not exist '%s'\n",
namesz, namesz, name, vid, cell->name);
/* TODO: make existing record unavailable */
goto error;
}
found_on_vlserver:
_debug("Done VL Lookup: %*.*s %02x { %08x(%x) %08x(%x) %08x(%x) }",
namesz, namesz, name,
vldb.vidmask,
ntohl(vldb.servers[0].s_addr), vldb.srvtmask[0],
ntohl(vldb.servers[1].s_addr), vldb.srvtmask[1],
ntohl(vldb.servers[2].s_addr), vldb.srvtmask[2]
);
_debug("Vids: %08x %08x %08x", vldb.vid[0], vldb.vid[1], vldb.vid[2]);
if ((namesz < sizeof(vlocation->vldb.name) &&
vlocation->vldb.name[namesz] != '\0') ||
memcmp(vldb.name, name, namesz) != 0)
printk("kAFS: name of volume '%*.*s' changed to '%s' on server\n",
namesz, namesz, name, vldb.name);
memcpy(&vlocation->vldb, &vldb, sizeof(vlocation->vldb));
afs_kafstimod_add_timer(&vlocation->upd_timer, 10 * HZ);
#ifdef AFS_CACHING_SUPPORT
/* update volume entry in local cache */
cachefs_update_cookie(vlocation->cache);
#endif
*_vlocation = vlocation;
_leave(" = 0 (%p)",vlocation);
return 0;
error:
if (vlocation) {
if (active) {
__afs_put_vlocation(vlocation);
}
else {
list_del(&vlocation->link);
#ifdef AFS_CACHING_SUPPORT
cachefs_relinquish_cookie(vlocation->cache, 0);
#endif
afs_put_cell(vlocation->cell);
kfree(vlocation);
}
}
_leave(" = %d", ret);
return ret;
} /* end afs_vlocation_lookup() */
/*****************************************************************************/
/*
* finish using a volume location record
* - caller must have cell->vol_sem write-locked
*/
static void __afs_put_vlocation(struct afs_vlocation *vlocation)
{
struct afs_cell *cell;
if (!vlocation)
return;
_enter("%s", vlocation->vldb.name);
cell = vlocation->cell;
/* sanity check */
BUG_ON(atomic_read(&vlocation->usage) <= 0);
spin_lock(&cell->vl_gylock);
if (likely(!atomic_dec_and_test(&vlocation->usage))) {
spin_unlock(&cell->vl_gylock);
_leave("");
return;
}
/* move to graveyard queue */
list_del(&vlocation->link);
list_add_tail(&vlocation->link,&cell->vl_graveyard);
/* remove from pending timeout queue (refcounted if actually being
* updated) */
list_del_init(&vlocation->upd_op.link);
/* time out in 10 secs */
afs_kafstimod_del_timer(&vlocation->upd_timer);
afs_kafstimod_add_timer(&vlocation->timeout, 10 * HZ);
spin_unlock(&cell->vl_gylock);
_leave(" [killed]");
} /* end __afs_put_vlocation() */
/*****************************************************************************/
/*
* finish using a volume location record
*/
void afs_put_vlocation(struct afs_vlocation *vlocation)
{
if (vlocation) {
struct afs_cell *cell = vlocation->cell;
down_write(&cell->vl_sem);
__afs_put_vlocation(vlocation);
up_write(&cell->vl_sem);
}
} /* end afs_put_vlocation() */
/*****************************************************************************/
/*
* timeout vlocation record
* - removes from the cell's graveyard if the usage count is zero
*/
void afs_vlocation_do_timeout(struct afs_vlocation *vlocation)
{
struct afs_cell *cell;
_enter("%s", vlocation->vldb.name);
cell = vlocation->cell;
BUG_ON(atomic_read(&vlocation->usage) < 0);
/* remove from graveyard if still dead */
spin_lock(&cell->vl_gylock);
if (atomic_read(&vlocation->usage) == 0)
list_del_init(&vlocation->link);
else
vlocation = NULL;
spin_unlock(&cell->vl_gylock);
if (!vlocation) {
_leave("");
return; /* resurrected */
}
/* we can now destroy it properly */
#ifdef AFS_CACHING_SUPPORT
cachefs_relinquish_cookie(vlocation->cache, 0);
#endif
afs_put_cell(cell);
kfree(vlocation);
_leave(" [destroyed]");
} /* end afs_vlocation_do_timeout() */
/*****************************************************************************/
/*
* send an update operation to the currently selected server
*/
static int afs_vlocation_update_begin(struct afs_vlocation *vlocation)
{
afs_voltype_t voltype;
afs_volid_t vid;
int ret;
_enter("%s{ufs=%u ucs=%u}",
vlocation->vldb.name,
vlocation->upd_first_svix,
vlocation->upd_curr_svix);
/* try to look up a cached volume in the cell VL databases by ID */
if (vlocation->vldb.vidmask & AFS_VOL_VTM_RW) {
vid = vlocation->vldb.vid[0];
voltype = AFSVL_RWVOL;
}
else if (vlocation->vldb.vidmask & AFS_VOL_VTM_RO) {
vid = vlocation->vldb.vid[1];
voltype = AFSVL_ROVOL;
}
else if (vlocation->vldb.vidmask & AFS_VOL_VTM_BAK) {
vid = vlocation->vldb.vid[2];
voltype = AFSVL_BACKVOL;
}
else {
BUG();
vid = 0;
voltype = 0;
}
/* contact the chosen server */
ret = afs_server_lookup(
vlocation->cell,
&vlocation->cell->vl_addrs[vlocation->upd_curr_svix],
&vlocation->upd_op.server);
switch (ret) {
case 0:
break;
case -ENOMEM:
case -ENONET:
default:
_leave(" = %d", ret);
return ret;
}
/* initiate the update operation */
ret = afs_rxvl_get_entry_by_id_async(&vlocation->upd_op, vid, voltype);
if (ret < 0) {
_leave(" = %d", ret);
return ret;
}
_leave(" = %d", ret);
return ret;
} /* end afs_vlocation_update_begin() */
/*****************************************************************************/
/*
* abandon updating a VL record
* - does not restart the update timer
*/
static void afs_vlocation_update_abandon(struct afs_vlocation *vlocation,
afs_vlocation_upd_t state,
int ret)
{
_enter("%s,%u", vlocation->vldb.name, state);
if (ret < 0)
printk("kAFS: Abandoning VL update '%s': %d\n",
vlocation->vldb.name, ret);
/* discard the server record */
afs_put_server(vlocation->upd_op.server);
vlocation->upd_op.server = NULL;
spin_lock(&afs_vlocation_update_lock);
afs_vlocation_update = NULL;
vlocation->upd_state = state;
/* TODO: start updating next VL record on pending list */
spin_unlock(&afs_vlocation_update_lock);
_leave("");
} /* end afs_vlocation_update_abandon() */
/*****************************************************************************/
/*
* handle periodic update timeouts and busy retry timeouts
* - called from kafstimod
*/
static void afs_vlocation_update_timer(struct afs_timer *timer)
{
struct afs_vlocation *vlocation =
list_entry(timer, struct afs_vlocation, upd_timer);
int ret;
_enter("%s", vlocation->vldb.name);
/* only update if not in the graveyard (defend against putting too) */
spin_lock(&vlocation->cell->vl_gylock);
if (!atomic_read(&vlocation->usage))
goto out_unlock1;
spin_lock(&afs_vlocation_update_lock);
/* if we were woken up due to EBUSY sleep then restart immediately if
* possible or else jump to front of pending queue */
if (vlocation->upd_state == AFS_VLUPD_BUSYSLEEP) {
if (afs_vlocation_update) {
list_add(&vlocation->upd_op.link,
&afs_vlocation_update_pendq);
}
else {
afs_get_vlocation(vlocation);
afs_vlocation_update = vlocation;
vlocation->upd_state = AFS_VLUPD_INPROGRESS;
}
goto out_unlock2;
}
/* put on pending queue if there's already another update in progress */
if (afs_vlocation_update) {
vlocation->upd_state = AFS_VLUPD_PENDING;
list_add_tail(&vlocation->upd_op.link,
&afs_vlocation_update_pendq);
goto out_unlock2;
}
/* hold a ref on it while actually updating */
afs_get_vlocation(vlocation);
afs_vlocation_update = vlocation;
vlocation->upd_state = AFS_VLUPD_INPROGRESS;
spin_unlock(&afs_vlocation_update_lock);
spin_unlock(&vlocation->cell->vl_gylock);
/* okay... we can start the update */
_debug("BEGIN VL UPDATE [%s]", vlocation->vldb.name);
vlocation->upd_first_svix = vlocation->cell->vl_curr_svix;
vlocation->upd_curr_svix = vlocation->upd_first_svix;
vlocation->upd_rej_cnt = 0;
vlocation->upd_busy_cnt = 0;
ret = afs_vlocation_update_begin(vlocation);
if (ret < 0) {
afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, ret);
afs_kafstimod_add_timer(&vlocation->upd_timer,
AFS_VLDB_TIMEOUT);
afs_put_vlocation(vlocation);
}
_leave("");
return;
out_unlock2:
spin_unlock(&afs_vlocation_update_lock);
out_unlock1:
spin_unlock(&vlocation->cell->vl_gylock);
_leave("");
return;
} /* end afs_vlocation_update_timer() */
/*****************************************************************************/
/*
* attend to an update operation upon which an event happened
* - called in kafsasyncd context
*/
static void afs_vlocation_update_attend(struct afs_async_op *op)
{
struct afs_cache_vlocation vldb;
struct afs_vlocation *vlocation =
list_entry(op, struct afs_vlocation, upd_op);
unsigned tmp;
int ret;
_enter("%s", vlocation->vldb.name);
ret = afs_rxvl_get_entry_by_id_async2(op, &vldb);
switch (ret) {
case -EAGAIN:
_leave(" [unfinished]");
return;
case 0:
_debug("END VL UPDATE: %d\n", ret);
vlocation->valid = 1;
_debug("Done VL Lookup: %02x { %08x(%x) %08x(%x) %08x(%x) }",
vldb.vidmask,
ntohl(vldb.servers[0].s_addr), vldb.srvtmask[0],
ntohl(vldb.servers[1].s_addr), vldb.srvtmask[1],
ntohl(vldb.servers[2].s_addr), vldb.srvtmask[2]
);
_debug("Vids: %08x %08x %08x",
vldb.vid[0], vldb.vid[1], vldb.vid[2]);
afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, 0);
down_write(&vlocation->cell->vl_sem);
/* actually update the cache */
if (strncmp(vldb.name, vlocation->vldb.name,
sizeof(vlocation->vldb.name)) != 0)
printk("kAFS: name of volume '%s'"
" changed to '%s' on server\n",
vlocation->vldb.name, vldb.name);
memcpy(&vlocation->vldb, &vldb, sizeof(vlocation->vldb));
#if 0
/* TODO update volume entry in local cache */
#endif
up_write(&vlocation->cell->vl_sem);
if (ret < 0)
printk("kAFS: failed to update local cache: %d\n", ret);
afs_kafstimod_add_timer(&vlocation->upd_timer,
AFS_VLDB_TIMEOUT);
afs_put_vlocation(vlocation);
_leave(" [found]");
return;
case -ENOMEDIUM:
vlocation->upd_rej_cnt++;
goto try_next;
/* the server is locked - retry in a very short while */
case -EBUSY:
vlocation->upd_busy_cnt++;
if (vlocation->upd_busy_cnt > 3)
goto try_next; /* too many retries */
afs_vlocation_update_abandon(vlocation,
AFS_VLUPD_BUSYSLEEP, 0);
afs_kafstimod_add_timer(&vlocation->upd_timer, HZ / 2);
afs_put_vlocation(vlocation);
_leave(" [busy]");
return;
case -ENETUNREACH:
case -EHOSTUNREACH:
case -ECONNREFUSED:
case -EREMOTEIO:
/* record bad vlserver info in the cell too
* - TODO: use down_write_trylock() if available
*/
if (vlocation->upd_curr_svix == vlocation->cell->vl_curr_svix)
vlocation->cell->vl_curr_svix =
vlocation->cell->vl_curr_svix %
vlocation->cell->vl_naddrs;
case -EBADRQC:
case -EINVAL:
case -EACCES:
case -EBADMSG:
goto try_next;
default:
goto abandon;
}
/* try contacting the next server */
try_next:
vlocation->upd_busy_cnt = 0;
/* discard the server record */
afs_put_server(vlocation->upd_op.server);
vlocation->upd_op.server = NULL;
tmp = vlocation->cell->vl_naddrs;
if (tmp == 0)
goto abandon;
vlocation->upd_curr_svix++;
if (vlocation->upd_curr_svix >= tmp)
vlocation->upd_curr_svix = 0;
if (vlocation->upd_first_svix >= tmp)
vlocation->upd_first_svix = tmp - 1;
/* move to the next server */
if (vlocation->upd_curr_svix != vlocation->upd_first_svix) {
afs_vlocation_update_begin(vlocation);
_leave(" [next]");
return;
}
/* run out of servers to try - was the volume rejected? */
if (vlocation->upd_rej_cnt > 0) {
printk("kAFS: Active volume no longer valid '%s'\n",
vlocation->vldb.name);
vlocation->valid = 0;
afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, 0);
afs_kafstimod_add_timer(&vlocation->upd_timer,
AFS_VLDB_TIMEOUT);
afs_put_vlocation(vlocation);
_leave(" [invalidated]");
return;
}
/* abandon the update */
abandon:
afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, ret);
afs_kafstimod_add_timer(&vlocation->upd_timer, HZ * 10);
afs_put_vlocation(vlocation);
_leave(" [abandoned]");
} /* end afs_vlocation_update_attend() */
/*****************************************************************************/
/*
* deal with an update operation being discarded
* - called in kafsasyncd context when it's dying due to rmmod
* - the call has already been aborted and put()'d
*/
static void afs_vlocation_update_discard(struct afs_async_op *op)
{
struct afs_vlocation *vlocation =
list_entry(op, struct afs_vlocation, upd_op);
_enter("%s", vlocation->vldb.name);
afs_put_server(op->server);
op->server = NULL;
afs_put_vlocation(vlocation);
_leave("");
} /* end afs_vlocation_update_discard() */
/*****************************************************************************/
/*
* match a VLDB record stored in the cache
* - may also load target from entry
*/
#ifdef AFS_CACHING_SUPPORT
static cachefs_match_val_t afs_vlocation_cache_match(void *target,
const void *entry)
{
const struct afs_cache_vlocation *vldb = entry;
struct afs_vlocation *vlocation = target;
_enter("{%s},{%s}", vlocation->vldb.name, vldb->name);
if (strncmp(vlocation->vldb.name, vldb->name, sizeof(vldb->name)) == 0
) {
if (!vlocation->valid ||
vlocation->vldb.rtime == vldb->rtime
) {
vlocation->vldb = *vldb;
vlocation->valid = 1;
_leave(" = SUCCESS [c->m]");
return CACHEFS_MATCH_SUCCESS;
}
/* need to update cache if cached info differs */
else if (memcmp(&vlocation->vldb, vldb, sizeof(*vldb)) != 0) {
/* delete if VIDs for this name differ */
if (memcmp(&vlocation->vldb.vid,
&vldb->vid,
sizeof(vldb->vid)) != 0) {
_leave(" = DELETE");
return CACHEFS_MATCH_SUCCESS_DELETE;
}
_leave(" = UPDATE");
return CACHEFS_MATCH_SUCCESS_UPDATE;
}
else {
_leave(" = SUCCESS");
return CACHEFS_MATCH_SUCCESS;
}
}
_leave(" = FAILED");
return CACHEFS_MATCH_FAILED;
} /* end afs_vlocation_cache_match() */
#endif
/*****************************************************************************/
/*
* update a VLDB record stored in the cache
*/
#ifdef AFS_CACHING_SUPPORT
static void afs_vlocation_cache_update(void *source, void *entry)
{
struct afs_cache_vlocation *vldb = entry;
struct afs_vlocation *vlocation = source;
_enter("");
*vldb = vlocation->vldb;
} /* end afs_vlocation_cache_update() */
#endif

395
fs/afs/vnode.c Normal file
View File

@@ -0,0 +1,395 @@
/* vnode.c: AFS vnode management
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include "volume.h"
#include "cell.h"
#include "cmservice.h"
#include "fsclient.h"
#include "vlclient.h"
#include "vnode.h"
#include "internal.h"
static void afs_vnode_cb_timed_out(struct afs_timer *timer);
struct afs_timer_ops afs_vnode_cb_timed_out_ops = {
.timed_out = afs_vnode_cb_timed_out,
};
#ifdef AFS_CACHING_SUPPORT
static cachefs_match_val_t afs_vnode_cache_match(void *target,
const void *entry);
static void afs_vnode_cache_update(void *source, void *entry);
struct cachefs_index_def afs_vnode_cache_index_def = {
.name = "vnode",
.data_size = sizeof(struct afs_cache_vnode),
.keys[0] = { CACHEFS_INDEX_KEYS_BIN, 4 },
.match = afs_vnode_cache_match,
.update = afs_vnode_cache_update,
};
#endif
/*****************************************************************************/
/*
* handle a callback timing out
* TODO: retain a ref to vnode struct for an outstanding callback timeout
*/
static void afs_vnode_cb_timed_out(struct afs_timer *timer)
{
struct afs_server *oldserver;
struct afs_vnode *vnode;
vnode = list_entry(timer, struct afs_vnode, cb_timeout);
_enter("%p", vnode);
/* set the changed flag in the vnode and release the server */
spin_lock(&vnode->lock);
oldserver = xchg(&vnode->cb_server, NULL);
if (oldserver) {
vnode->flags |= AFS_VNODE_CHANGED;
spin_lock(&afs_cb_hash_lock);
list_del_init(&vnode->cb_hash_link);
spin_unlock(&afs_cb_hash_lock);
spin_lock(&oldserver->cb_lock);
list_del_init(&vnode->cb_link);
spin_unlock(&oldserver->cb_lock);
}
spin_unlock(&vnode->lock);
afs_put_server(oldserver);
_leave("");
} /* end afs_vnode_cb_timed_out() */
/*****************************************************************************/
/*
* finish off updating the recorded status of a file
* - starts callback expiry timer
* - adds to server's callback list
*/
static void afs_vnode_finalise_status_update(struct afs_vnode *vnode,
struct afs_server *server,
int ret)
{
struct afs_server *oldserver = NULL;
_enter("%p,%p,%d", vnode, server, ret);
spin_lock(&vnode->lock);
vnode->flags &= ~AFS_VNODE_CHANGED;
if (ret == 0) {
/* adjust the callback timeout appropriately */
afs_kafstimod_add_timer(&vnode->cb_timeout,
vnode->cb_expiry * HZ);
spin_lock(&afs_cb_hash_lock);
list_del(&vnode->cb_hash_link);
list_add_tail(&vnode->cb_hash_link,
&afs_cb_hash(server, &vnode->fid));
spin_unlock(&afs_cb_hash_lock);
/* swap ref to old callback server with that for new callback
* server */
oldserver = xchg(&vnode->cb_server, server);
if (oldserver != server) {
if (oldserver) {
spin_lock(&oldserver->cb_lock);
list_del_init(&vnode->cb_link);
spin_unlock(&oldserver->cb_lock);
}
afs_get_server(server);
spin_lock(&server->cb_lock);
list_add_tail(&vnode->cb_link, &server->cb_promises);
spin_unlock(&server->cb_lock);
}
else {
/* same server */
oldserver = NULL;
}
}
else if (ret == -ENOENT) {
/* the file was deleted - clear the callback timeout */
oldserver = xchg(&vnode->cb_server, NULL);
afs_kafstimod_del_timer(&vnode->cb_timeout);
_debug("got NOENT from server - marking file deleted");
vnode->flags |= AFS_VNODE_DELETED;
}
vnode->update_cnt--;
spin_unlock(&vnode->lock);
wake_up_all(&vnode->update_waitq);
afs_put_server(oldserver);
_leave("");
} /* end afs_vnode_finalise_status_update() */
/*****************************************************************************/
/*
* fetch file status from the volume
* - don't issue a fetch if:
* - the changed bit is not set and there's a valid callback
* - there are any outstanding ops that will fetch the status
* - TODO implement local caching
*/
int afs_vnode_fetch_status(struct afs_vnode *vnode)
{
struct afs_server *server;
int ret;
DECLARE_WAITQUEUE(myself, current);
_enter("%s,{%u,%u,%u}",
vnode->volume->vlocation->vldb.name,
vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique);
if (!(vnode->flags & AFS_VNODE_CHANGED) && vnode->cb_server) {
_leave(" [unchanged]");
return 0;
}
if (vnode->flags & AFS_VNODE_DELETED) {
_leave(" [deleted]");
return -ENOENT;
}
spin_lock(&vnode->lock);
if (!(vnode->flags & AFS_VNODE_CHANGED)) {
spin_unlock(&vnode->lock);
_leave(" [unchanged]");
return 0;
}
if (vnode->update_cnt > 0) {
/* someone else started a fetch */
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&vnode->update_waitq, &myself);
/* wait for the status to be updated */
for (;;) {
if (!(vnode->flags & AFS_VNODE_CHANGED))
break;
if (vnode->flags & AFS_VNODE_DELETED)
break;
/* it got updated and invalidated all before we saw
* it */
if (vnode->update_cnt == 0) {
remove_wait_queue(&vnode->update_waitq,
&myself);
set_current_state(TASK_RUNNING);
goto get_anyway;
}
spin_unlock(&vnode->lock);
schedule();
set_current_state(TASK_UNINTERRUPTIBLE);
spin_lock(&vnode->lock);
}
remove_wait_queue(&vnode->update_waitq, &myself);
spin_unlock(&vnode->lock);
set_current_state(TASK_RUNNING);
return vnode->flags & AFS_VNODE_DELETED ? -ENOENT : 0;
}
get_anyway:
/* okay... we're going to have to initiate the op */
vnode->update_cnt++;
spin_unlock(&vnode->lock);
/* merge AFS status fetches and clear outstanding callback on this
* vnode */
do {
/* pick a server to query */
ret = afs_volume_pick_fileserver(vnode->volume, &server);
if (ret<0)
return ret;
_debug("USING SERVER: %08x\n", ntohl(server->addr.s_addr));
ret = afs_rxfs_fetch_file_status(server, vnode, NULL);
} while (!afs_volume_release_fileserver(vnode->volume, server, ret));
/* adjust the flags */
afs_vnode_finalise_status_update(vnode, server, ret);
_leave(" = %d", ret);
return ret;
} /* end afs_vnode_fetch_status() */
/*****************************************************************************/
/*
* fetch file data from the volume
* - TODO implement caching and server failover
*/
int afs_vnode_fetch_data(struct afs_vnode *vnode,
struct afs_rxfs_fetch_descriptor *desc)
{
struct afs_server *server;
int ret;
_enter("%s,{%u,%u,%u}",
vnode->volume->vlocation->vldb.name,
vnode->fid.vid,
vnode->fid.vnode,
vnode->fid.unique);
/* this op will fetch the status */
spin_lock(&vnode->lock);
vnode->update_cnt++;
spin_unlock(&vnode->lock);
/* merge in AFS status fetches and clear outstanding callback on this
* vnode */
do {
/* pick a server to query */
ret = afs_volume_pick_fileserver(vnode->volume, &server);
if (ret < 0)
return ret;
_debug("USING SERVER: %08x\n", ntohl(server->addr.s_addr));
ret = afs_rxfs_fetch_file_data(server, vnode, desc, NULL);
} while (!afs_volume_release_fileserver(vnode->volume, server, ret));
/* adjust the flags */
afs_vnode_finalise_status_update(vnode, server, ret);
_leave(" = %d", ret);
return ret;
} /* end afs_vnode_fetch_data() */
/*****************************************************************************/
/*
* break any outstanding callback on a vnode
* - only relevent to server that issued it
*/
int afs_vnode_give_up_callback(struct afs_vnode *vnode)
{
struct afs_server *server;
int ret;
_enter("%s,{%u,%u,%u}",
vnode->volume->vlocation->vldb.name,
vnode->fid.vid,
vnode->fid.vnode,
vnode->fid.unique);
spin_lock(&afs_cb_hash_lock);
list_del_init(&vnode->cb_hash_link);
spin_unlock(&afs_cb_hash_lock);
/* set the changed flag in the vnode and release the server */
spin_lock(&vnode->lock);
afs_kafstimod_del_timer(&vnode->cb_timeout);
server = xchg(&vnode->cb_server, NULL);
if (server) {
vnode->flags |= AFS_VNODE_CHANGED;
spin_lock(&server->cb_lock);
list_del_init(&vnode->cb_link);
spin_unlock(&server->cb_lock);
}
spin_unlock(&vnode->lock);
ret = 0;
if (server) {
ret = afs_rxfs_give_up_callback(server, vnode);
afs_put_server(server);
}
_leave(" = %d", ret);
return ret;
} /* end afs_vnode_give_up_callback() */
/*****************************************************************************/
/*
* match a vnode record stored in the cache
*/
#ifdef AFS_CACHING_SUPPORT
static cachefs_match_val_t afs_vnode_cache_match(void *target,
const void *entry)
{
const struct afs_cache_vnode *cvnode = entry;
struct afs_vnode *vnode = target;
_enter("{%x,%x,%Lx},{%x,%x,%Lx}",
vnode->fid.vnode,
vnode->fid.unique,
vnode->status.version,
cvnode->vnode_id,
cvnode->vnode_unique,
cvnode->data_version);
if (vnode->fid.vnode != cvnode->vnode_id) {
_leave(" = FAILED");
return CACHEFS_MATCH_FAILED;
}
if (vnode->fid.unique != cvnode->vnode_unique ||
vnode->status.version != cvnode->data_version) {
_leave(" = DELETE");
return CACHEFS_MATCH_SUCCESS_DELETE;
}
_leave(" = SUCCESS");
return CACHEFS_MATCH_SUCCESS;
} /* end afs_vnode_cache_match() */
#endif
/*****************************************************************************/
/*
* update a vnode record stored in the cache
*/
#ifdef AFS_CACHING_SUPPORT
static void afs_vnode_cache_update(void *source, void *entry)
{
struct afs_cache_vnode *cvnode = entry;
struct afs_vnode *vnode = source;
_enter("");
cvnode->vnode_id = vnode->fid.vnode;
cvnode->vnode_unique = vnode->fid.unique;
cvnode->data_version = vnode->status.version;
} /* end afs_vnode_cache_update() */
#endif

94
fs/afs/vnode.h Normal file
View File

@@ -0,0 +1,94 @@
/* vnode.h: AFS vnode record
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_VNODE_H
#define _LINUX_AFS_VNODE_H
#include <linux/fs.h>
#include "server.h"
#include "kafstimod.h"
#include "cache.h"
#ifdef __KERNEL__
struct afs_rxfs_fetch_descriptor;
/*****************************************************************************/
/*
* vnode catalogue entry
*/
struct afs_cache_vnode
{
afs_vnodeid_t vnode_id; /* vnode ID */
unsigned vnode_unique; /* vnode ID uniquifier */
afs_dataversion_t data_version; /* data version */
};
#ifdef AFS_CACHING_SUPPORT
extern struct cachefs_index_def afs_vnode_cache_index_def;
#endif
/*****************************************************************************/
/*
* AFS inode private data
*/
struct afs_vnode
{
struct inode vfs_inode; /* the VFS's inode record */
struct afs_volume *volume; /* volume on which vnode resides */
struct afs_fid fid; /* the file identifier for this inode */
struct afs_file_status status; /* AFS status info for this file */
#ifdef AFS_CACHING_SUPPORT
struct cachefs_cookie *cache; /* caching cookie */
#endif
wait_queue_head_t update_waitq; /* status fetch waitqueue */
unsigned update_cnt; /* number of outstanding ops that will update the
* status */
spinlock_t lock; /* waitqueue/flags lock */
unsigned flags;
#define AFS_VNODE_CHANGED 0x00000001 /* set if vnode reported changed by callback */
#define AFS_VNODE_DELETED 0x00000002 /* set if vnode deleted on server */
#define AFS_VNODE_MOUNTPOINT 0x00000004 /* set if vnode is a mountpoint symlink */
/* outstanding callback notification on this file */
struct afs_server *cb_server; /* server that made the current promise */
struct list_head cb_link; /* link in server's promises list */
struct list_head cb_hash_link; /* link in master callback hash */
struct afs_timer cb_timeout; /* timeout on promise */
unsigned cb_version; /* callback version */
unsigned cb_expiry; /* callback expiry time */
afs_callback_type_t cb_type; /* type of callback */
};
static inline struct afs_vnode *AFS_FS_I(struct inode *inode)
{
return container_of(inode,struct afs_vnode,vfs_inode);
}
static inline struct inode *AFS_VNODE_TO_I(struct afs_vnode *vnode)
{
return &vnode->vfs_inode;
}
extern int afs_vnode_fetch_status(struct afs_vnode *vnode);
extern int afs_vnode_fetch_data(struct afs_vnode *vnode,
struct afs_rxfs_fetch_descriptor *desc);
extern int afs_vnode_give_up_callback(struct afs_vnode *vnode);
extern struct afs_timer_ops afs_vnode_cb_timed_out_ops;
#endif /* __KERNEL__ */
#endif /* _LINUX_AFS_VNODE_H */

520
fs/afs/volume.c Normal file
View File

@@ -0,0 +1,520 @@
/* volume.c: AFS volume management
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include "volume.h"
#include "vnode.h"
#include "cell.h"
#include "cache.h"
#include "cmservice.h"
#include "fsclient.h"
#include "vlclient.h"
#include "internal.h"
#ifdef __KDEBUG
static const char *afs_voltypes[] = { "R/W", "R/O", "BAK" };
#endif
#ifdef AFS_CACHING_SUPPORT
static cachefs_match_val_t afs_volume_cache_match(void *target,
const void *entry);
static void afs_volume_cache_update(void *source, void *entry);
struct cachefs_index_def afs_volume_cache_index_def = {
.name = "volume",
.data_size = sizeof(struct afs_cache_vhash),
.keys[0] = { CACHEFS_INDEX_KEYS_BIN, 1 },
.keys[1] = { CACHEFS_INDEX_KEYS_BIN, 1 },
.match = afs_volume_cache_match,
.update = afs_volume_cache_update,
};
#endif
/*****************************************************************************/
/*
* lookup a volume by name
* - this can be one of the following:
* "%[cell:]volume[.]" R/W volume
* "#[cell:]volume[.]" R/O or R/W volume (rwparent=0),
* or R/W (rwparent=1) volume
* "%[cell:]volume.readonly" R/O volume
* "#[cell:]volume.readonly" R/O volume
* "%[cell:]volume.backup" Backup volume
* "#[cell:]volume.backup" Backup volume
*
* The cell name is optional, and defaults to the current cell.
*
* See "The Rules of Mount Point Traversal" in Chapter 5 of the AFS SysAdmin
* Guide
* - Rule 1: Explicit type suffix forces access of that type or nothing
* (no suffix, then use Rule 2 & 3)
* - Rule 2: If parent volume is R/O, then mount R/O volume by preference, R/W
* if not available
* - Rule 3: If parent volume is R/W, then only mount R/W volume unless
* explicitly told otherwise
*/
int afs_volume_lookup(const char *name, struct afs_cell *cell, int rwpath,
struct afs_volume **_volume)
{
struct afs_vlocation *vlocation = NULL;
struct afs_volume *volume = NULL;
afs_voltype_t type;
const char *cellname, *volname, *suffix;
char srvtmask;
int force, ret, loop, cellnamesz, volnamesz;
_enter("%s,,%d,", name, rwpath);
if (!name || (name[0] != '%' && name[0] != '#') || !name[1]) {
printk("kAFS: unparsable volume name\n");
return -EINVAL;
}
/* determine the type of volume we're looking for */
force = 0;
type = AFSVL_ROVOL;
if (rwpath || name[0] == '%') {
type = AFSVL_RWVOL;
force = 1;
}
suffix = strrchr(name, '.');
if (suffix) {
if (strcmp(suffix, ".readonly") == 0) {
type = AFSVL_ROVOL;
force = 1;
}
else if (strcmp(suffix, ".backup") == 0) {
type = AFSVL_BACKVOL;
force = 1;
}
else if (suffix[1] == 0) {
}
else {
suffix = NULL;
}
}
/* split the cell and volume names */
name++;
volname = strchr(name, ':');
if (volname) {
cellname = name;
cellnamesz = volname - name;
volname++;
}
else {
volname = name;
cellname = NULL;
cellnamesz = 0;
}
volnamesz = suffix ? suffix - volname : strlen(volname);
_debug("CELL:%*.*s [%p] VOLUME:%*.*s SUFFIX:%s TYPE:%d%s",
cellnamesz, cellnamesz, cellname ?: "", cell,
volnamesz, volnamesz, volname, suffix ?: "-",
type,
force ? " FORCE" : "");
/* lookup the cell record */
if (cellname || !cell) {
ret = afs_cell_lookup(cellname, cellnamesz, &cell);
if (ret<0) {
printk("kAFS: unable to lookup cell '%s'\n",
cellname ?: "");
goto error;
}
}
else {
afs_get_cell(cell);
}
/* lookup the volume location record */
ret = afs_vlocation_lookup(cell, volname, volnamesz, &vlocation);
if (ret < 0)
goto error;
/* make the final decision on the type we want */
ret = -ENOMEDIUM;
if (force && !(vlocation->vldb.vidmask & (1 << type)))
goto error;
srvtmask = 0;
for (loop = 0; loop < vlocation->vldb.nservers; loop++)
srvtmask |= vlocation->vldb.srvtmask[loop];
if (force) {
if (!(srvtmask & (1 << type)))
goto error;
}
else if (srvtmask & AFS_VOL_VTM_RO) {
type = AFSVL_ROVOL;
}
else if (srvtmask & AFS_VOL_VTM_RW) {
type = AFSVL_RWVOL;
}
else {
goto error;
}
down_write(&cell->vl_sem);
/* is the volume already active? */
if (vlocation->vols[type]) {
/* yes - re-use it */
volume = vlocation->vols[type];
afs_get_volume(volume);
goto success;
}
/* create a new volume record */
_debug("creating new volume record");
ret = -ENOMEM;
volume = kmalloc(sizeof(struct afs_volume), GFP_KERNEL);
if (!volume)
goto error_up;
memset(volume, 0, sizeof(struct afs_volume));
atomic_set(&volume->usage, 1);
volume->type = type;
volume->type_force = force;
volume->cell = cell;
volume->vid = vlocation->vldb.vid[type];
init_rwsem(&volume->server_sem);
/* look up all the applicable server records */
for (loop = 0; loop < 8; loop++) {
if (vlocation->vldb.srvtmask[loop] & (1 << volume->type)) {
ret = afs_server_lookup(
volume->cell,
&vlocation->vldb.servers[loop],
&volume->servers[volume->nservers]);
if (ret < 0)
goto error_discard;
volume->nservers++;
}
}
/* attach the cache and volume location */
#ifdef AFS_CACHING_SUPPORT
cachefs_acquire_cookie(vlocation->cache,
&afs_vnode_cache_index_def,
volume,
&volume->cache);
#endif
afs_get_vlocation(vlocation);
volume->vlocation = vlocation;
vlocation->vols[type] = volume;
success:
_debug("kAFS selected %s volume %08x",
afs_voltypes[volume->type], volume->vid);
*_volume = volume;
ret = 0;
/* clean up */
error_up:
up_write(&cell->vl_sem);
error:
afs_put_vlocation(vlocation);
afs_put_cell(cell);
_leave(" = %d (%p)", ret, volume);
return ret;
error_discard:
up_write(&cell->vl_sem);
for (loop = volume->nservers - 1; loop >= 0; loop--)
afs_put_server(volume->servers[loop]);
kfree(volume);
goto error;
} /* end afs_volume_lookup() */
/*****************************************************************************/
/*
* destroy a volume record
*/
void afs_put_volume(struct afs_volume *volume)
{
struct afs_vlocation *vlocation;
int loop;
if (!volume)
return;
_enter("%p", volume);
vlocation = volume->vlocation;
/* sanity check */
BUG_ON(atomic_read(&volume->usage) <= 0);
/* to prevent a race, the decrement and the dequeue must be effectively
* atomic */
down_write(&vlocation->cell->vl_sem);
if (likely(!atomic_dec_and_test(&volume->usage))) {
up_write(&vlocation->cell->vl_sem);
_leave("");
return;
}
vlocation->vols[volume->type] = NULL;
up_write(&vlocation->cell->vl_sem);
/* finish cleaning up the volume */
#ifdef AFS_CACHING_SUPPORT
cachefs_relinquish_cookie(volume->cache, 0);
#endif
afs_put_vlocation(vlocation);
for (loop = volume->nservers - 1; loop >= 0; loop--)
afs_put_server(volume->servers[loop]);
kfree(volume);
_leave(" [destroyed]");
} /* end afs_put_volume() */
/*****************************************************************************/
/*
* pick a server to use to try accessing this volume
* - returns with an elevated usage count on the server chosen
*/
int afs_volume_pick_fileserver(struct afs_volume *volume,
struct afs_server **_server)
{
struct afs_server *server;
int ret, state, loop;
_enter("%s", volume->vlocation->vldb.name);
down_read(&volume->server_sem);
/* handle the no-server case */
if (volume->nservers == 0) {
ret = volume->rjservers ? -ENOMEDIUM : -ESTALE;
up_read(&volume->server_sem);
_leave(" = %d [no servers]", ret);
return ret;
}
/* basically, just search the list for the first live server and use
* that */
ret = 0;
for (loop = 0; loop < volume->nservers; loop++) {
server = volume->servers[loop];
state = server->fs_state;
switch (state) {
/* found an apparently healthy server */
case 0:
afs_get_server(server);
up_read(&volume->server_sem);
*_server = server;
_leave(" = 0 (picked %08x)",
ntohl(server->addr.s_addr));
return 0;
case -ENETUNREACH:
if (ret == 0)
ret = state;
break;
case -EHOSTUNREACH:
if (ret == 0 ||
ret == -ENETUNREACH)
ret = state;
break;
case -ECONNREFUSED:
if (ret == 0 ||
ret == -ENETUNREACH ||
ret == -EHOSTUNREACH)
ret = state;
break;
default:
case -EREMOTEIO:
if (ret == 0 ||
ret == -ENETUNREACH ||
ret == -EHOSTUNREACH ||
ret == -ECONNREFUSED)
ret = state;
break;
}
}
/* no available servers
* - TODO: handle the no active servers case better
*/
up_read(&volume->server_sem);
_leave(" = %d", ret);
return ret;
} /* end afs_volume_pick_fileserver() */
/*****************************************************************************/
/*
* release a server after use
* - releases the ref on the server struct that was acquired by picking
* - records result of using a particular server to access a volume
* - return 0 to try again, 1 if okay or to issue error
*/
int afs_volume_release_fileserver(struct afs_volume *volume,
struct afs_server *server,
int result)
{
unsigned loop;
_enter("%s,%08x,%d",
volume->vlocation->vldb.name, ntohl(server->addr.s_addr),
result);
switch (result) {
/* success */
case 0:
server->fs_act_jif = jiffies;
break;
/* the fileserver denied all knowledge of the volume */
case -ENOMEDIUM:
server->fs_act_jif = jiffies;
down_write(&volume->server_sem);
/* first, find where the server is in the active list (if it
* is) */
for (loop = 0; loop < volume->nservers; loop++)
if (volume->servers[loop] == server)
goto present;
/* no longer there - may have been discarded by another op */
goto try_next_server_upw;
present:
volume->nservers--;
memmove(&volume->servers[loop],
&volume->servers[loop + 1],
sizeof(volume->servers[loop]) *
(volume->nservers - loop));
volume->servers[volume->nservers] = NULL;
afs_put_server(server);
volume->rjservers++;
if (volume->nservers > 0)
/* another server might acknowledge its existence */
goto try_next_server_upw;
/* handle the case where all the fileservers have rejected the
* volume
* - TODO: try asking the fileservers for volume information
* - TODO: contact the VL server again to see if the volume is
* no longer registered
*/
up_write(&volume->server_sem);
afs_put_server(server);
_leave(" [completely rejected]");
return 1;
/* problem reaching the server */
case -ENETUNREACH:
case -EHOSTUNREACH:
case -ECONNREFUSED:
case -ETIMEDOUT:
case -EREMOTEIO:
/* mark the server as dead
* TODO: vary dead timeout depending on error
*/
spin_lock(&server->fs_lock);
if (!server->fs_state) {
server->fs_dead_jif = jiffies + HZ * 10;
server->fs_state = result;
printk("kAFS: SERVER DEAD state=%d\n", result);
}
spin_unlock(&server->fs_lock);
goto try_next_server;
/* miscellaneous error */
default:
server->fs_act_jif = jiffies;
case -ENOMEM:
case -ENONET:
break;
}
/* tell the caller to accept the result */
afs_put_server(server);
_leave("");
return 1;
/* tell the caller to loop around and try the next server */
try_next_server_upw:
up_write(&volume->server_sem);
try_next_server:
afs_put_server(server);
_leave(" [try next server]");
return 0;
} /* end afs_volume_release_fileserver() */
/*****************************************************************************/
/*
* match a volume hash record stored in the cache
*/
#ifdef AFS_CACHING_SUPPORT
static cachefs_match_val_t afs_volume_cache_match(void *target,
const void *entry)
{
const struct afs_cache_vhash *vhash = entry;
struct afs_volume *volume = target;
_enter("{%u},{%u}", volume->type, vhash->vtype);
if (volume->type == vhash->vtype) {
_leave(" = SUCCESS");
return CACHEFS_MATCH_SUCCESS;
}
_leave(" = FAILED");
return CACHEFS_MATCH_FAILED;
} /* end afs_volume_cache_match() */
#endif
/*****************************************************************************/
/*
* update a volume hash record stored in the cache
*/
#ifdef AFS_CACHING_SUPPORT
static void afs_volume_cache_update(void *source, void *entry)
{
struct afs_cache_vhash *vhash = entry;
struct afs_volume *volume = source;
_enter("");
vhash->vtype = volume->type;
} /* end afs_volume_cache_update() */
#endif

142
fs/afs/volume.h Normal file
View File

@@ -0,0 +1,142 @@
/* volume.h: AFS volume management
*
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _LINUX_AFS_VOLUME_H
#define _LINUX_AFS_VOLUME_H
#include "types.h"
#include "fsclient.h"
#include "kafstimod.h"
#include "kafsasyncd.h"
#include "cache.h"
#define __packed __attribute__((packed))
typedef enum {
AFS_VLUPD_SLEEP, /* sleeping waiting for update timer to fire */
AFS_VLUPD_PENDING, /* on pending queue */
AFS_VLUPD_INPROGRESS, /* op in progress */
AFS_VLUPD_BUSYSLEEP, /* sleeping because server returned EBUSY */
} __attribute__((packed)) afs_vlocation_upd_t;
/*****************************************************************************/
/*
* entry in the cached volume location catalogue
*/
struct afs_cache_vlocation
{
uint8_t name[64]; /* volume name (lowercase, padded with NULs) */
uint8_t nservers; /* number of entries used in servers[] */
uint8_t vidmask; /* voltype mask for vid[] */
uint8_t srvtmask[8]; /* voltype masks for servers[] */
#define AFS_VOL_VTM_RW 0x01 /* R/W version of the volume is available (on this server) */
#define AFS_VOL_VTM_RO 0x02 /* R/O version of the volume is available (on this server) */
#define AFS_VOL_VTM_BAK 0x04 /* backup version of the volume is available (on this server) */
afs_volid_t vid[3]; /* volume IDs for R/W, R/O and Bak volumes */
struct in_addr servers[8]; /* fileserver addresses */
time_t rtime; /* last retrieval time */
};
#ifdef AFS_CACHING_SUPPORT
extern struct cachefs_index_def afs_vlocation_cache_index_def;
#endif
/*****************************************************************************/
/*
* volume -> vnode hash table entry
*/
struct afs_cache_vhash
{
afs_voltype_t vtype; /* which volume variation */
uint8_t hash_bucket; /* which hash bucket this represents */
} __attribute__((packed));
#ifdef AFS_CACHING_SUPPORT
extern struct cachefs_index_def afs_volume_cache_index_def;
#endif
/*****************************************************************************/
/*
* AFS volume location record
*/
struct afs_vlocation
{
atomic_t usage;
struct list_head link; /* link in cell volume location list */
struct afs_timer timeout; /* decaching timer */
struct afs_cell *cell; /* cell to which volume belongs */
#ifdef AFS_CACHING_SUPPORT
struct cachefs_cookie *cache; /* caching cookie */
#endif
struct afs_cache_vlocation vldb; /* volume information DB record */
struct afs_volume *vols[3]; /* volume access record pointer (index by type) */
rwlock_t lock; /* access lock */
unsigned long read_jif; /* time at which last read from vlserver */
struct afs_timer upd_timer; /* update timer */
struct afs_async_op upd_op; /* update operation */
afs_vlocation_upd_t upd_state; /* update state */
unsigned short upd_first_svix; /* first server index during update */
unsigned short upd_curr_svix; /* current server index during update */
unsigned short upd_rej_cnt; /* ENOMEDIUM count during update */
unsigned short upd_busy_cnt; /* EBUSY count during update */
unsigned short valid; /* T if valid */
};
extern int afs_vlocation_lookup(struct afs_cell *cell,
const char *name,
unsigned namesz,
struct afs_vlocation **_vlocation);
#define afs_get_vlocation(V) do { atomic_inc(&(V)->usage); } while(0)
extern void afs_put_vlocation(struct afs_vlocation *vlocation);
extern void afs_vlocation_do_timeout(struct afs_vlocation *vlocation);
/*****************************************************************************/
/*
* AFS volume access record
*/
struct afs_volume
{
atomic_t usage;
struct afs_cell *cell; /* cell to which belongs (unrefd ptr) */
struct afs_vlocation *vlocation; /* volume location */
#ifdef AFS_CACHING_SUPPORT
struct cachefs_cookie *cache; /* caching cookie */
#endif
afs_volid_t vid; /* volume ID */
afs_voltype_t __packed type; /* type of volume */
char type_force; /* force volume type (suppress R/O -> R/W) */
unsigned short nservers; /* number of server slots filled */
unsigned short rjservers; /* number of servers discarded due to -ENOMEDIUM */
struct afs_server *servers[8]; /* servers on which volume resides (ordered) */
struct rw_semaphore server_sem; /* lock for accessing current server */
};
extern int afs_volume_lookup(const char *name,
struct afs_cell *cell,
int rwpath,
struct afs_volume **_volume);
#define afs_get_volume(V) do { atomic_inc(&(V)->usage); } while(0)
extern void afs_put_volume(struct afs_volume *volume);
extern int afs_volume_pick_fileserver(struct afs_volume *volume,
struct afs_server **_server);
extern int afs_volume_release_fileserver(struct afs_volume *volume,
struct afs_server *server,
int result);
#endif /* _LINUX_AFS_VOLUME_H */

1729
fs/aio.c Normal file

File diff suppressed because it is too large Load Diff

208
fs/attr.c Normal file
View File

@@ -0,0 +1,208 @@
/*
* linux/fs/attr.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
* changes by Thomas Schoebel-Theuer
*/
#include <linux/module.h>
#include <linux/time.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/smp_lock.h>
#include <linux/dnotify.h>
#include <linux/fcntl.h>
#include <linux/quotaops.h>
#include <linux/security.h>
#include <linux/time.h>
/* Taken over from the old code... */
/* POSIX UID/GID verification for setting inode attributes. */
int inode_change_ok(struct inode *inode, struct iattr *attr)
{
int retval = -EPERM;
unsigned int ia_valid = attr->ia_valid;
/* If force is set do it anyway. */
if (ia_valid & ATTR_FORCE)
goto fine;
/* Make sure a caller can chown. */
if ((ia_valid & ATTR_UID) &&
(current->fsuid != inode->i_uid ||
attr->ia_uid != inode->i_uid) && !capable(CAP_CHOWN))
goto error;
/* Make sure caller can chgrp. */
if ((ia_valid & ATTR_GID) &&
(current->fsuid != inode->i_uid ||
(!in_group_p(attr->ia_gid) && attr->ia_gid != inode->i_gid)) &&
!capable(CAP_CHOWN))
goto error;
/* Make sure a caller can chmod. */
if (ia_valid & ATTR_MODE) {
if ((current->fsuid != inode->i_uid) && !capable(CAP_FOWNER))
goto error;
/* Also check the setgid bit! */
if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
inode->i_gid) && !capable(CAP_FSETID))
attr->ia_mode &= ~S_ISGID;
}
/* Check for setting the inode time. */
if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET)) {
if (current->fsuid != inode->i_uid && !capable(CAP_FOWNER))
goto error;
}
fine:
retval = 0;
error:
return retval;
}
EXPORT_SYMBOL(inode_change_ok);
int inode_setattr(struct inode * inode, struct iattr * attr)
{
unsigned int ia_valid = attr->ia_valid;
int error = 0;
if (ia_valid & ATTR_SIZE) {
if (attr->ia_size != i_size_read(inode)) {
error = vmtruncate(inode, attr->ia_size);
if (error || (ia_valid == ATTR_SIZE))
goto out;
} else {
/*
* We skipped the truncate but must still update
* timestamps
*/
ia_valid |= ATTR_MTIME|ATTR_CTIME;
}
}
if (ia_valid & ATTR_UID)
inode->i_uid = attr->ia_uid;
if (ia_valid & ATTR_GID)
inode->i_gid = attr->ia_gid;
if (ia_valid & ATTR_ATIME)
inode->i_atime = timespec_trunc(attr->ia_atime,
inode->i_sb->s_time_gran);
if (ia_valid & ATTR_MTIME)
inode->i_mtime = timespec_trunc(attr->ia_mtime,
inode->i_sb->s_time_gran);
if (ia_valid & ATTR_CTIME)
inode->i_ctime = timespec_trunc(attr->ia_ctime,
inode->i_sb->s_time_gran);
if (ia_valid & ATTR_MODE) {
umode_t mode = attr->ia_mode;
if (!in_group_p(inode->i_gid) && !capable(CAP_FSETID))
mode &= ~S_ISGID;
inode->i_mode = mode;
}
mark_inode_dirty(inode);
out:
return error;
}
EXPORT_SYMBOL(inode_setattr);
int setattr_mask(unsigned int ia_valid)
{
unsigned long dn_mask = 0;
if (ia_valid & ATTR_UID)
dn_mask |= DN_ATTRIB;
if (ia_valid & ATTR_GID)
dn_mask |= DN_ATTRIB;
if (ia_valid & ATTR_SIZE)
dn_mask |= DN_MODIFY;
/* both times implies a utime(s) call */
if ((ia_valid & (ATTR_ATIME|ATTR_MTIME)) == (ATTR_ATIME|ATTR_MTIME))
dn_mask |= DN_ATTRIB;
else if (ia_valid & ATTR_ATIME)
dn_mask |= DN_ACCESS;
else if (ia_valid & ATTR_MTIME)
dn_mask |= DN_MODIFY;
if (ia_valid & ATTR_MODE)
dn_mask |= DN_ATTRIB;
return dn_mask;
}
int notify_change(struct dentry * dentry, struct iattr * attr)
{
struct inode *inode = dentry->d_inode;
mode_t mode;
int error;
struct timespec now;
unsigned int ia_valid = attr->ia_valid;
if (!inode)
BUG();
mode = inode->i_mode;
now = current_fs_time(inode->i_sb);
attr->ia_ctime = now;
if (!(ia_valid & ATTR_ATIME_SET))
attr->ia_atime = now;
if (!(ia_valid & ATTR_MTIME_SET))
attr->ia_mtime = now;
if (ia_valid & ATTR_KILL_SUID) {
attr->ia_valid &= ~ATTR_KILL_SUID;
if (mode & S_ISUID) {
if (!(ia_valid & ATTR_MODE)) {
ia_valid = attr->ia_valid |= ATTR_MODE;
attr->ia_mode = inode->i_mode;
}
attr->ia_mode &= ~S_ISUID;
}
}
if (ia_valid & ATTR_KILL_SGID) {
attr->ia_valid &= ~ ATTR_KILL_SGID;
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
if (!(ia_valid & ATTR_MODE)) {
ia_valid = attr->ia_valid |= ATTR_MODE;
attr->ia_mode = inode->i_mode;
}
attr->ia_mode &= ~S_ISGID;
}
}
if (!attr->ia_valid)
return 0;
if (ia_valid & ATTR_SIZE)
down_write(&dentry->d_inode->i_alloc_sem);
if (inode->i_op && inode->i_op->setattr) {
error = security_inode_setattr(dentry, attr);
if (!error)
error = inode->i_op->setattr(dentry, attr);
} else {
error = inode_change_ok(inode, attr);
if (!error)
error = security_inode_setattr(dentry, attr);
if (!error) {
if ((ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid) ||
(ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid))
error = DQUOT_TRANSFER(inode, attr) ? -EDQUOT : 0;
if (!error)
error = inode_setattr(inode, attr);
}
}
if (ia_valid & ATTR_SIZE)
up_write(&dentry->d_inode->i_alloc_sem);
if (!error) {
unsigned long dn_mask = setattr_mask(ia_valid);
if (dn_mask)
dnotify_parent(dentry, dn_mask);
}
return error;
}
EXPORT_SYMBOL(notify_change);

7
fs/autofs/Makefile Normal file
View File

@@ -0,0 +1,7 @@
#
# Makefile for the linux autofs-filesystem routines.
#
obj-$(CONFIG_AUTOFS_FS) += autofs.o
autofs-objs := dirhash.o init.o inode.o root.o symlink.o waitq.o

164
fs/autofs/autofs_i.h Normal file
View File

@@ -0,0 +1,164 @@
/* -*- linux-c -*- ------------------------------------------------------- *
*
* linux/fs/autofs/autofs_i.h
*
* Copyright 1997-1998 Transmeta Corporation - All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
/* Internal header file for autofs */
#include <linux/auto_fs.h>
/* This is the range of ioctl() numbers we claim as ours */
#define AUTOFS_IOC_FIRST AUTOFS_IOC_READY
#define AUTOFS_IOC_COUNT 32
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/dcache.h>
#include <linux/namei.h>
#include <linux/mount.h>
#include <linux/sched.h>
#include <asm/current.h>
#include <asm/uaccess.h>
#ifdef DEBUG
#define DPRINTK(D) (printk D)
#else
#define DPRINTK(D) ((void)0)
#endif
#define AUTOFS_SUPER_MAGIC 0x0187
/*
* If the daemon returns a negative response (AUTOFS_IOC_FAIL) then the
* kernel will keep the negative response cached for up to the time given
* here, although the time can be shorter if the kernel throws the dcache
* entry away. This probably should be settable from user space.
*/
#define AUTOFS_NEGATIVE_TIMEOUT (60*HZ) /* 1 minute */
/* Structures associated with the root directory hash table */
#define AUTOFS_HASH_SIZE 67
struct autofs_dir_ent {
int hash;
char *name;
int len;
ino_t ino;
struct dentry *dentry;
/* Linked list of entries */
struct autofs_dir_ent *next;
struct autofs_dir_ent **back;
/* The following entries are for the expiry system */
unsigned long last_usage;
struct list_head exp;
};
struct autofs_dirhash {
struct autofs_dir_ent *h[AUTOFS_HASH_SIZE];
struct list_head expiry_head;
};
struct autofs_wait_queue {
wait_queue_head_t queue;
struct autofs_wait_queue *next;
autofs_wqt_t wait_queue_token;
/* We use the following to see what we are waiting for */
int hash;
int len;
char *name;
/* This is for status reporting upon return */
int status;
int wait_ctr;
};
struct autofs_symlink {
char *data;
int len;
time_t mtime;
};
#define AUTOFS_MAX_SYMLINKS 256
#define AUTOFS_ROOT_INO 1
#define AUTOFS_FIRST_SYMLINK 2
#define AUTOFS_FIRST_DIR_INO (AUTOFS_FIRST_SYMLINK+AUTOFS_MAX_SYMLINKS)
#define AUTOFS_SYMLINK_BITMAP_LEN \
((AUTOFS_MAX_SYMLINKS+((sizeof(long)*1)-1))/(sizeof(long)*8))
#define AUTOFS_SBI_MAGIC 0x6d4a556d
struct autofs_sb_info {
u32 magic;
struct file *pipe;
pid_t oz_pgrp;
int catatonic;
unsigned long exp_timeout;
ino_t next_dir_ino;
struct autofs_wait_queue *queues; /* Wait queue pointer */
struct autofs_dirhash dirhash; /* Root directory hash */
struct autofs_symlink symlink[AUTOFS_MAX_SYMLINKS];
unsigned long symlink_bitmap[AUTOFS_SYMLINK_BITMAP_LEN];
};
static inline struct autofs_sb_info *autofs_sbi(struct super_block *sb)
{
return (struct autofs_sb_info *)(sb->s_fs_info);
}
/* autofs_oz_mode(): do we see the man behind the curtain? (The
processes which do manipulations for us in user space sees the raw
filesystem without "magic".) */
static inline int autofs_oz_mode(struct autofs_sb_info *sbi) {
return sbi->catatonic || process_group(current) == sbi->oz_pgrp;
}
/* Hash operations */
void autofs_initialize_hash(struct autofs_dirhash *);
struct autofs_dir_ent *autofs_hash_lookup(const struct autofs_dirhash *,struct qstr *);
void autofs_hash_insert(struct autofs_dirhash *,struct autofs_dir_ent *);
void autofs_hash_delete(struct autofs_dir_ent *);
struct autofs_dir_ent *autofs_hash_enum(const struct autofs_dirhash *,off_t *,struct autofs_dir_ent *);
void autofs_hash_dputall(struct autofs_dirhash *);
void autofs_hash_nuke(struct autofs_dirhash *);
/* Expiration-handling functions */
void autofs_update_usage(struct autofs_dirhash *,struct autofs_dir_ent *);
struct autofs_dir_ent *autofs_expire(struct super_block *,struct autofs_sb_info *, struct vfsmount *mnt);
/* Operations structures */
extern struct inode_operations autofs_root_inode_operations;
extern struct inode_operations autofs_symlink_inode_operations;
extern struct file_operations autofs_root_operations;
/* Initializing function */
int autofs_fill_super(struct super_block *, void *, int);
/* Queue management functions */
int autofs_wait(struct autofs_sb_info *,struct qstr *);
int autofs_wait_release(struct autofs_sb_info *,autofs_wqt_t,int);
void autofs_catatonic_mode(struct autofs_sb_info *);
#ifdef DEBUG
void autofs_say(const char *name, int len);
#else
#define autofs_say(n,l) ((void)0)
#endif

249
fs/autofs/dirhash.c Normal file
View File

@@ -0,0 +1,249 @@
/* -*- linux-c -*- --------------------------------------------------------- *
*
* linux/fs/autofs/dirhash.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include "autofs_i.h"
/* Functions for maintenance of expiry queue */
static void autofs_init_usage(struct autofs_dirhash *dh,
struct autofs_dir_ent *ent)
{
list_add_tail(&ent->exp, &dh->expiry_head);
ent->last_usage = jiffies;
}
static void autofs_delete_usage(struct autofs_dir_ent *ent)
{
list_del(&ent->exp);
}
void autofs_update_usage(struct autofs_dirhash *dh,
struct autofs_dir_ent *ent)
{
autofs_delete_usage(ent); /* Unlink from current position */
autofs_init_usage(dh,ent); /* Relink at queue tail */
}
struct autofs_dir_ent *autofs_expire(struct super_block *sb,
struct autofs_sb_info *sbi,
struct vfsmount *mnt)
{
struct autofs_dirhash *dh = &sbi->dirhash;
struct autofs_dir_ent *ent;
struct dentry *dentry;
unsigned long timeout = sbi->exp_timeout;
while (1) {
if ( list_empty(&dh->expiry_head) || sbi->catatonic )
return NULL; /* No entries */
/* We keep the list sorted by last_usage and want old stuff */
ent = list_entry(dh->expiry_head.next, struct autofs_dir_ent, exp);
if (jiffies - ent->last_usage < timeout)
break;
/* Move to end of list in case expiry isn't desirable */
autofs_update_usage(dh, ent);
/* Check to see that entry is expirable */
if ( ent->ino < AUTOFS_FIRST_DIR_INO )
return ent; /* Symlinks are always expirable */
/* Get the dentry for the autofs subdirectory */
dentry = ent->dentry;
if ( !dentry ) {
/* Should only happen in catatonic mode */
printk("autofs: dentry == NULL but inode range is directory, entry %s\n", ent->name);
autofs_delete_usage(ent);
continue;
}
if ( !dentry->d_inode ) {
dput(dentry);
printk("autofs: negative dentry on expiry queue: %s\n",
ent->name);
autofs_delete_usage(ent);
continue;
}
/* Make sure entry is mounted and unused; note that dentry will
point to the mounted-on-top root. */
if (!S_ISDIR(dentry->d_inode->i_mode)||!d_mountpoint(dentry)) {
DPRINTK(("autofs: not expirable (not a mounted directory): %s\n", ent->name));
continue;
}
mntget(mnt);
dget(dentry);
if (!follow_down(&mnt, &dentry)) {
dput(dentry);
mntput(mnt);
DPRINTK(("autofs: not expirable (not a mounted directory): %s\n", ent->name));
continue;
}
while (d_mountpoint(dentry) && follow_down(&mnt, &dentry))
;
dput(dentry);
if ( may_umount(mnt) == 0 ) {
mntput(mnt);
DPRINTK(("autofs: signaling expire on %s\n", ent->name));
return ent; /* Expirable! */
}
DPRINTK(("autofs: didn't expire due to may_umount: %s\n", ent->name));
mntput(mnt);
}
return NULL; /* No expirable entries */
}
void autofs_initialize_hash(struct autofs_dirhash *dh) {
memset(&dh->h, 0, AUTOFS_HASH_SIZE*sizeof(struct autofs_dir_ent *));
INIT_LIST_HEAD(&dh->expiry_head);
}
struct autofs_dir_ent *autofs_hash_lookup(const struct autofs_dirhash *dh, struct qstr *name)
{
struct autofs_dir_ent *dhn;
DPRINTK(("autofs_hash_lookup: hash = 0x%08x, name = ", name->hash));
autofs_say(name->name,name->len);
for ( dhn = dh->h[(unsigned) name->hash % AUTOFS_HASH_SIZE] ; dhn ; dhn = dhn->next ) {
if ( name->hash == dhn->hash &&
name->len == dhn->len &&
!memcmp(name->name, dhn->name, name->len) )
break;
}
return dhn;
}
void autofs_hash_insert(struct autofs_dirhash *dh, struct autofs_dir_ent *ent)
{
struct autofs_dir_ent **dhnp;
DPRINTK(("autofs_hash_insert: hash = 0x%08x, name = ", ent->hash));
autofs_say(ent->name,ent->len);
autofs_init_usage(dh,ent);
if (ent->dentry)
dget(ent->dentry);
dhnp = &dh->h[(unsigned) ent->hash % AUTOFS_HASH_SIZE];
ent->next = *dhnp;
ent->back = dhnp;
*dhnp = ent;
if ( ent->next )
ent->next->back = &(ent->next);
}
void autofs_hash_delete(struct autofs_dir_ent *ent)
{
*(ent->back) = ent->next;
if ( ent->next )
ent->next->back = ent->back;
autofs_delete_usage(ent);
if ( ent->dentry )
dput(ent->dentry);
kfree(ent->name);
kfree(ent);
}
/*
* Used by readdir(). We must validate "ptr", so we can't simply make it
* a pointer. Values below 0xffff are reserved; calling with any value
* <= 0x10000 will return the first entry found.
*
* "last" can be NULL or the value returned by the last search *if* we
* want the next sequential entry.
*/
struct autofs_dir_ent *autofs_hash_enum(const struct autofs_dirhash *dh,
off_t *ptr, struct autofs_dir_ent *last)
{
int bucket, ecount, i;
struct autofs_dir_ent *ent;
bucket = (*ptr >> 16) - 1;
ecount = *ptr & 0xffff;
if ( bucket < 0 ) {
bucket = ecount = 0;
}
DPRINTK(("autofs_hash_enum: bucket %d, entry %d\n", bucket, ecount));
ent = last ? last->next : NULL;
if ( ent ) {
ecount++;
} else {
while ( bucket < AUTOFS_HASH_SIZE ) {
ent = dh->h[bucket];
for ( i = ecount ; ent && i ; i-- )
ent = ent->next;
if (ent) {
ecount++; /* Point to *next* entry */
break;
}
bucket++; ecount = 0;
}
}
#ifdef DEBUG
if ( !ent )
printk("autofs_hash_enum: nothing found\n");
else {
printk("autofs_hash_enum: found hash %08x, name", ent->hash);
autofs_say(ent->name,ent->len);
}
#endif
*ptr = ((bucket+1) << 16) + ecount;
return ent;
}
/* Iterate over all the ents, and remove all dentry pointers. Used on
entering catatonic mode, in order to make the filesystem unmountable. */
void autofs_hash_dputall(struct autofs_dirhash *dh)
{
int i;
struct autofs_dir_ent *ent;
for ( i = 0 ; i < AUTOFS_HASH_SIZE ; i++ ) {
for ( ent = dh->h[i] ; ent ; ent = ent->next ) {
if ( ent->dentry ) {
dput(ent->dentry);
ent->dentry = NULL;
}
}
}
}
/* Delete everything. This is used on filesystem destruction, so we
make no attempt to keep the pointers valid */
void autofs_hash_nuke(struct autofs_dirhash *dh)
{
int i;
struct autofs_dir_ent *ent, *nent;
for ( i = 0 ; i < AUTOFS_HASH_SIZE ; i++ ) {
for ( ent = dh->h[i] ; ent ; ent = nent ) {
nent = ent->next;
if ( ent->dentry )
dput(ent->dentry);
kfree(ent->name);
kfree(ent);
}
}
}

52
fs/autofs/init.c Normal file
View File

@@ -0,0 +1,52 @@
/* -*- linux-c -*- --------------------------------------------------------- *
*
* linux/fs/autofs/init.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include <linux/module.h>
#include <linux/init.h>
#include "autofs_i.h"
static struct super_block *autofs_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return get_sb_nodev(fs_type, flags, data, autofs_fill_super);
}
static struct file_system_type autofs_fs_type = {
.owner = THIS_MODULE,
.name = "autofs",
.get_sb = autofs_get_sb,
.kill_sb = kill_anon_super,
};
static int __init init_autofs_fs(void)
{
return register_filesystem(&autofs_fs_type);
}
static void __exit exit_autofs_fs(void)
{
unregister_filesystem(&autofs_fs_type);
}
module_init(init_autofs_fs);
module_exit(exit_autofs_fs);
#ifdef DEBUG
void autofs_say(const char *name, int len)
{
printk("(%d: ", len);
while ( len-- )
printk("%c", *name++);
printk(")\n");
}
#endif
MODULE_LICENSE("GPL");

250
fs/autofs/inode.c Normal file
View File

@@ -0,0 +1,250 @@
/* -*- linux-c -*- --------------------------------------------------------- *
*
* linux/fs/autofs/inode.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/parser.h>
#include <linux/bitops.h>
#include "autofs_i.h"
#include <linux/module.h>
static void autofs_put_super(struct super_block *sb)
{
struct autofs_sb_info *sbi = autofs_sbi(sb);
unsigned int n;
if ( !sbi->catatonic )
autofs_catatonic_mode(sbi); /* Free wait queues, close pipe */
autofs_hash_nuke(&sbi->dirhash);
for ( n = 0 ; n < AUTOFS_MAX_SYMLINKS ; n++ ) {
if ( test_bit(n, sbi->symlink_bitmap) )
kfree(sbi->symlink[n].data);
}
kfree(sb->s_fs_info);
DPRINTK(("autofs: shutting down\n"));
}
static void autofs_read_inode(struct inode *inode);
static struct super_operations autofs_sops = {
.read_inode = autofs_read_inode,
.put_super = autofs_put_super,
.statfs = simple_statfs,
};
enum {Opt_err, Opt_fd, Opt_uid, Opt_gid, Opt_pgrp, Opt_minproto, Opt_maxproto};
static match_table_t autofs_tokens = {
{Opt_fd, "fd=%u"},
{Opt_uid, "uid=%u"},
{Opt_gid, "gid=%u"},
{Opt_pgrp, "pgrp=%u"},
{Opt_minproto, "minproto=%u"},
{Opt_maxproto, "maxproto=%u"},
{Opt_err, NULL}
};
static int parse_options(char *options, int *pipefd, uid_t *uid, gid_t *gid, pid_t *pgrp, int *minproto, int *maxproto)
{
char *p;
substring_t args[MAX_OPT_ARGS];
int option;
*uid = current->uid;
*gid = current->gid;
*pgrp = process_group(current);
*minproto = *maxproto = AUTOFS_PROTO_VERSION;
*pipefd = -1;
if (!options)
return 1;
while ((p = strsep(&options, ",")) != NULL) {
int token;
if (!*p)
continue;
token = match_token(p, autofs_tokens, args);
switch (token) {
case Opt_fd:
if (match_int(&args[0], &option))
return 1;
*pipefd = option;
break;
case Opt_uid:
if (match_int(&args[0], &option))
return 1;
*uid = option;
break;
case Opt_gid:
if (match_int(&args[0], &option))
return 1;
*gid = option;
break;
case Opt_pgrp:
if (match_int(&args[0], &option))
return 1;
*pgrp = option;
break;
case Opt_minproto:
if (match_int(&args[0], &option))
return 1;
*minproto = option;
break;
case Opt_maxproto:
if (match_int(&args[0], &option))
return 1;
*maxproto = option;
break;
default:
return 1;
}
}
return (*pipefd < 0);
}
int autofs_fill_super(struct super_block *s, void *data, int silent)
{
struct inode * root_inode;
struct dentry * root;
struct file * pipe;
int pipefd;
struct autofs_sb_info *sbi;
int minproto, maxproto;
sbi = kmalloc(sizeof(*sbi), GFP_KERNEL);
if ( !sbi )
goto fail_unlock;
memset(sbi, 0, sizeof(*sbi));
DPRINTK(("autofs: starting up, sbi = %p\n",sbi));
s->s_fs_info = sbi;
sbi->magic = AUTOFS_SBI_MAGIC;
sbi->catatonic = 0;
sbi->exp_timeout = 0;
sbi->oz_pgrp = process_group(current);
autofs_initialize_hash(&sbi->dirhash);
sbi->queues = NULL;
memset(sbi->symlink_bitmap, 0, sizeof(long)*AUTOFS_SYMLINK_BITMAP_LEN);
sbi->next_dir_ino = AUTOFS_FIRST_DIR_INO;
s->s_blocksize = 1024;
s->s_blocksize_bits = 10;
s->s_magic = AUTOFS_SUPER_MAGIC;
s->s_op = &autofs_sops;
s->s_time_gran = 1;
root_inode = iget(s, AUTOFS_ROOT_INO);
root = d_alloc_root(root_inode);
pipe = NULL;
if (!root)
goto fail_iput;
/* Can this call block? - WTF cares? s is locked. */
if ( parse_options(data,&pipefd,&root_inode->i_uid,&root_inode->i_gid,&sbi->oz_pgrp,&minproto,&maxproto) ) {
printk("autofs: called with bogus options\n");
goto fail_dput;
}
/* Couldn't this be tested earlier? */
if ( minproto > AUTOFS_PROTO_VERSION ||
maxproto < AUTOFS_PROTO_VERSION ) {
printk("autofs: kernel does not match daemon version\n");
goto fail_dput;
}
DPRINTK(("autofs: pipe fd = %d, pgrp = %u\n", pipefd, sbi->oz_pgrp));
pipe = fget(pipefd);
if ( !pipe ) {
printk("autofs: could not open pipe file descriptor\n");
goto fail_dput;
}
if ( !pipe->f_op || !pipe->f_op->write )
goto fail_fput;
sbi->pipe = pipe;
/*
* Success! Install the root dentry now to indicate completion.
*/
s->s_root = root;
return 0;
fail_fput:
printk("autofs: pipe file descriptor does not contain proper ops\n");
fput(pipe);
fail_dput:
dput(root);
goto fail_free;
fail_iput:
printk("autofs: get root dentry failed\n");
iput(root_inode);
fail_free:
kfree(sbi);
fail_unlock:
return -EINVAL;
}
static void autofs_read_inode(struct inode *inode)
{
ino_t ino = inode->i_ino;
unsigned int n;
struct autofs_sb_info *sbi = autofs_sbi(inode->i_sb);
/* Initialize to the default case (stub directory) */
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO;
inode->i_nlink = 2;
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
inode->i_blocks = 0;
inode->i_blksize = 1024;
if ( ino == AUTOFS_ROOT_INO ) {
inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR;
inode->i_op = &autofs_root_inode_operations;
inode->i_fop = &autofs_root_operations;
inode->i_uid = inode->i_gid = 0; /* Changed in read_super */
return;
}
inode->i_uid = inode->i_sb->s_root->d_inode->i_uid;
inode->i_gid = inode->i_sb->s_root->d_inode->i_gid;
if ( ino >= AUTOFS_FIRST_SYMLINK && ino < AUTOFS_FIRST_DIR_INO ) {
/* Symlink inode - should be in symlink list */
struct autofs_symlink *sl;
n = ino - AUTOFS_FIRST_SYMLINK;
if ( n >= AUTOFS_MAX_SYMLINKS || !test_bit(n,sbi->symlink_bitmap)) {
printk("autofs: Looking for bad symlink inode %u\n", (unsigned int) ino);
return;
}
inode->i_op = &autofs_symlink_inode_operations;
sl = &sbi->symlink[n];
inode->u.generic_ip = sl;
inode->i_mode = S_IFLNK | S_IRWXUGO;
inode->i_mtime.tv_sec = inode->i_ctime.tv_sec = sl->mtime;
inode->i_mtime.tv_nsec = inode->i_ctime.tv_nsec = 0;
inode->i_size = sl->len;
inode->i_nlink = 1;
}
}

564
fs/autofs/root.c Normal file
View File

@@ -0,0 +1,564 @@
/* -*- linux-c -*- --------------------------------------------------------- *
*
* linux/fs/autofs/root.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/param.h>
#include <linux/time.h>
#include <linux/smp_lock.h>
#include "autofs_i.h"
static int autofs_root_readdir(struct file *,void *,filldir_t);
static struct dentry *autofs_root_lookup(struct inode *,struct dentry *, struct nameidata *);
static int autofs_root_symlink(struct inode *,struct dentry *,const char *);
static int autofs_root_unlink(struct inode *,struct dentry *);
static int autofs_root_rmdir(struct inode *,struct dentry *);
static int autofs_root_mkdir(struct inode *,struct dentry *,int);
static int autofs_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long);
struct file_operations autofs_root_operations = {
.read = generic_read_dir,
.readdir = autofs_root_readdir,
.ioctl = autofs_root_ioctl,
};
struct inode_operations autofs_root_inode_operations = {
.lookup = autofs_root_lookup,
.unlink = autofs_root_unlink,
.symlink = autofs_root_symlink,
.mkdir = autofs_root_mkdir,
.rmdir = autofs_root_rmdir,
};
static int autofs_root_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
struct autofs_dir_ent *ent = NULL;
struct autofs_dirhash *dirhash;
struct autofs_sb_info *sbi;
struct inode * inode = filp->f_dentry->d_inode;
off_t onr, nr;
lock_kernel();
sbi = autofs_sbi(inode->i_sb);
dirhash = &sbi->dirhash;
nr = filp->f_pos;
switch(nr)
{
case 0:
if (filldir(dirent, ".", 1, nr, inode->i_ino, DT_DIR) < 0)
goto out;
filp->f_pos = ++nr;
/* fall through */
case 1:
if (filldir(dirent, "..", 2, nr, inode->i_ino, DT_DIR) < 0)
goto out;
filp->f_pos = ++nr;
/* fall through */
default:
while ( onr = nr, ent = autofs_hash_enum(dirhash,&nr,ent) ) {
if ( !ent->dentry || d_mountpoint(ent->dentry) ) {
if (filldir(dirent,ent->name,ent->len,onr,ent->ino,DT_UNKNOWN) < 0)
goto out;
filp->f_pos = nr;
}
}
break;
}
out:
unlock_kernel();
return 0;
}
static int try_to_fill_dentry(struct dentry *dentry, struct super_block *sb, struct autofs_sb_info *sbi)
{
struct inode * inode;
struct autofs_dir_ent *ent;
int status = 0;
if ( !(ent = autofs_hash_lookup(&sbi->dirhash, &dentry->d_name)) ) {
do {
if ( status && dentry->d_inode ) {
if ( status != -ENOENT )
printk("autofs warning: lookup failure on positive dentry, status = %d, name = %s\n", status, dentry->d_name.name);
return 0; /* Try to get the kernel to invalidate this dentry */
}
/* Turn this into a real negative dentry? */
if (status == -ENOENT) {
dentry->d_time = jiffies + AUTOFS_NEGATIVE_TIMEOUT;
dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
return 1;
} else if (status) {
/* Return a negative dentry, but leave it "pending" */
return 1;
}
status = autofs_wait(sbi, &dentry->d_name);
} while (!(ent = autofs_hash_lookup(&sbi->dirhash, &dentry->d_name)) );
}
/* Abuse this field as a pointer to the directory entry, used to
find the expire list pointers */
dentry->d_time = (unsigned long) ent;
if (!dentry->d_inode) {
inode = iget(sb, ent->ino);
if (!inode) {
/* Failed, but leave pending for next time */
return 1;
}
dentry->d_inode = inode;
}
/* If this is a directory that isn't a mount point, bitch at the
daemon and fix it in user space */
if ( S_ISDIR(dentry->d_inode->i_mode) && !d_mountpoint(dentry) ) {
return !autofs_wait(sbi, &dentry->d_name);
}
/* We don't update the usages for the autofs daemon itself, this
is necessary for recursive autofs mounts */
if ( !autofs_oz_mode(sbi) ) {
autofs_update_usage(&sbi->dirhash,ent);
}
dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
return 1;
}
/*
* Revalidate is called on every cache lookup. Some of those
* cache lookups may actually happen while the dentry is not
* yet completely filled in, and revalidate has to delay such
* lookups..
*/
static int autofs_revalidate(struct dentry * dentry, struct nameidata *nd)
{
struct inode * dir;
struct autofs_sb_info *sbi;
struct autofs_dir_ent *ent;
int res;
lock_kernel();
dir = dentry->d_parent->d_inode;
sbi = autofs_sbi(dir->i_sb);
/* Pending dentry */
if ( dentry->d_flags & DCACHE_AUTOFS_PENDING ) {
if (autofs_oz_mode(sbi))
res = 1;
else
res = try_to_fill_dentry(dentry, dir->i_sb, sbi);
unlock_kernel();
return res;
}
/* Negative dentry.. invalidate if "old" */
if (!dentry->d_inode) {
unlock_kernel();
return (dentry->d_time - jiffies <= AUTOFS_NEGATIVE_TIMEOUT);
}
/* Check for a non-mountpoint directory */
if ( S_ISDIR(dentry->d_inode->i_mode) && !d_mountpoint(dentry) ) {
if (autofs_oz_mode(sbi))
res = 1;
else
res = try_to_fill_dentry(dentry, dir->i_sb, sbi);
unlock_kernel();
return res;
}
/* Update the usage list */
if ( !autofs_oz_mode(sbi) ) {
ent = (struct autofs_dir_ent *) dentry->d_time;
if ( ent )
autofs_update_usage(&sbi->dirhash,ent);
}
unlock_kernel();
return 1;
}
static struct dentry_operations autofs_dentry_operations = {
.d_revalidate = autofs_revalidate,
};
static struct dentry *autofs_root_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
struct autofs_sb_info *sbi;
int oz_mode;
DPRINTK(("autofs_root_lookup: name = "));
lock_kernel();
autofs_say(dentry->d_name.name,dentry->d_name.len);
if (dentry->d_name.len > NAME_MAX) {
unlock_kernel();
return ERR_PTR(-ENAMETOOLONG);/* File name too long to exist */
}
sbi = autofs_sbi(dir->i_sb);
oz_mode = autofs_oz_mode(sbi);
DPRINTK(("autofs_lookup: pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d\n",
current->pid, process_group(current), sbi->catatonic, oz_mode));
/*
* Mark the dentry incomplete, but add it. This is needed so
* that the VFS layer knows about the dentry, and we can count
* on catching any lookups through the revalidate.
*
* Let all the hard work be done by the revalidate function that
* needs to be able to do this anyway..
*
* We need to do this before we release the directory semaphore.
*/
dentry->d_op = &autofs_dentry_operations;
dentry->d_flags |= DCACHE_AUTOFS_PENDING;
d_add(dentry, NULL);
up(&dir->i_sem);
autofs_revalidate(dentry, nd);
down(&dir->i_sem);
/*
* If we are still pending, check if we had to handle
* a signal. If so we can force a restart..
*/
if (dentry->d_flags & DCACHE_AUTOFS_PENDING) {
/* See if we were interrupted */
if (signal_pending(current)) {
sigset_t *sigset = &current->pending.signal;
if (sigismember (sigset, SIGKILL) ||
sigismember (sigset, SIGQUIT) ||
sigismember (sigset, SIGINT)) {
unlock_kernel();
return ERR_PTR(-ERESTARTNOINTR);
}
}
}
unlock_kernel();
/*
* If this dentry is unhashed, then we shouldn't honour this
* lookup even if the dentry is positive. Returning ENOENT here
* doesn't do the right thing for all system calls, but it should
* be OK for the operations we permit from an autofs.
*/
if ( dentry->d_inode && d_unhashed(dentry) )
return ERR_PTR(-ENOENT);
return NULL;
}
static int autofs_root_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
{
struct autofs_sb_info *sbi = autofs_sbi(dir->i_sb);
struct autofs_dirhash *dh = &sbi->dirhash;
struct autofs_dir_ent *ent;
unsigned int n;
int slsize;
struct autofs_symlink *sl;
DPRINTK(("autofs_root_symlink: %s <- ", symname));
autofs_say(dentry->d_name.name,dentry->d_name.len);
lock_kernel();
if ( !autofs_oz_mode(sbi) ) {
unlock_kernel();
return -EACCES;
}
if ( autofs_hash_lookup(dh, &dentry->d_name) ) {
unlock_kernel();
return -EEXIST;
}
n = find_first_zero_bit(sbi->symlink_bitmap,AUTOFS_MAX_SYMLINKS);
if ( n >= AUTOFS_MAX_SYMLINKS ) {
unlock_kernel();
return -ENOSPC;
}
set_bit(n,sbi->symlink_bitmap);
sl = &sbi->symlink[n];
sl->len = strlen(symname);
sl->data = kmalloc(slsize = sl->len+1, GFP_KERNEL);
if ( !sl->data ) {
clear_bit(n,sbi->symlink_bitmap);
unlock_kernel();
return -ENOSPC;
}
ent = kmalloc(sizeof(struct autofs_dir_ent), GFP_KERNEL);
if ( !ent ) {
kfree(sl->data);
clear_bit(n,sbi->symlink_bitmap);
unlock_kernel();
return -ENOSPC;
}
ent->name = kmalloc(dentry->d_name.len+1, GFP_KERNEL);
if ( !ent->name ) {
kfree(sl->data);
kfree(ent);
clear_bit(n,sbi->symlink_bitmap);
unlock_kernel();
return -ENOSPC;
}
memcpy(sl->data,symname,slsize);
sl->mtime = get_seconds();
ent->ino = AUTOFS_FIRST_SYMLINK + n;
ent->hash = dentry->d_name.hash;
memcpy(ent->name, dentry->d_name.name, 1+(ent->len = dentry->d_name.len));
ent->dentry = NULL; /* We don't keep the dentry for symlinks */
autofs_hash_insert(dh,ent);
d_instantiate(dentry, iget(dir->i_sb,ent->ino));
unlock_kernel();
return 0;
}
/*
* NOTE!
*
* Normal filesystems would do a "d_delete()" to tell the VFS dcache
* that the file no longer exists. However, doing that means that the
* VFS layer can turn the dentry into a negative dentry, which we
* obviously do not want (we're dropping the entry not because it
* doesn't exist, but because it has timed out).
*
* Also see autofs_root_rmdir()..
*/
static int autofs_root_unlink(struct inode *dir, struct dentry *dentry)
{
struct autofs_sb_info *sbi = autofs_sbi(dir->i_sb);
struct autofs_dirhash *dh = &sbi->dirhash;
struct autofs_dir_ent *ent;
unsigned int n;
/* This allows root to remove symlinks */
lock_kernel();
if ( !autofs_oz_mode(sbi) && !capable(CAP_SYS_ADMIN) ) {
unlock_kernel();
return -EACCES;
}
ent = autofs_hash_lookup(dh, &dentry->d_name);
if ( !ent ) {
unlock_kernel();
return -ENOENT;
}
n = ent->ino - AUTOFS_FIRST_SYMLINK;
if ( n >= AUTOFS_MAX_SYMLINKS ) {
unlock_kernel();
return -EISDIR; /* It's a directory, dummy */
}
if ( !test_bit(n,sbi->symlink_bitmap) ) {
unlock_kernel();
return -EINVAL; /* Nonexistent symlink? Shouldn't happen */
}
dentry->d_time = (unsigned long)(struct autofs_dirhash *)NULL;
autofs_hash_delete(ent);
clear_bit(n,sbi->symlink_bitmap);
kfree(sbi->symlink[n].data);
d_drop(dentry);
unlock_kernel();
return 0;
}
static int autofs_root_rmdir(struct inode *dir, struct dentry *dentry)
{
struct autofs_sb_info *sbi = autofs_sbi(dir->i_sb);
struct autofs_dirhash *dh = &sbi->dirhash;
struct autofs_dir_ent *ent;
lock_kernel();
if ( !autofs_oz_mode(sbi) ) {
unlock_kernel();
return -EACCES;
}
ent = autofs_hash_lookup(dh, &dentry->d_name);
if ( !ent ) {
unlock_kernel();
return -ENOENT;
}
if ( (unsigned int)ent->ino < AUTOFS_FIRST_DIR_INO ) {
unlock_kernel();
return -ENOTDIR; /* Not a directory */
}
if ( ent->dentry != dentry ) {
printk("autofs_rmdir: odentry != dentry for entry %s\n", dentry->d_name.name);
}
dentry->d_time = (unsigned long)(struct autofs_dir_ent *)NULL;
autofs_hash_delete(ent);
dir->i_nlink--;
d_drop(dentry);
unlock_kernel();
return 0;
}
static int autofs_root_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
struct autofs_sb_info *sbi = autofs_sbi(dir->i_sb);
struct autofs_dirhash *dh = &sbi->dirhash;
struct autofs_dir_ent *ent;
ino_t ino;
lock_kernel();
if ( !autofs_oz_mode(sbi) ) {
unlock_kernel();
return -EACCES;
}
ent = autofs_hash_lookup(dh, &dentry->d_name);
if ( ent ) {
unlock_kernel();
return -EEXIST;
}
if ( sbi->next_dir_ino < AUTOFS_FIRST_DIR_INO ) {
printk("autofs: Out of inode numbers -- what the heck did you do??\n");
unlock_kernel();
return -ENOSPC;
}
ino = sbi->next_dir_ino++;
ent = kmalloc(sizeof(struct autofs_dir_ent), GFP_KERNEL);
if ( !ent ) {
unlock_kernel();
return -ENOSPC;
}
ent->name = kmalloc(dentry->d_name.len+1, GFP_KERNEL);
if ( !ent->name ) {
kfree(ent);
unlock_kernel();
return -ENOSPC;
}
ent->hash = dentry->d_name.hash;
memcpy(ent->name, dentry->d_name.name, 1+(ent->len = dentry->d_name.len));
ent->ino = ino;
ent->dentry = dentry;
autofs_hash_insert(dh,ent);
dir->i_nlink++;
d_instantiate(dentry, iget(dir->i_sb,ino));
unlock_kernel();
return 0;
}
/* Get/set timeout ioctl() operation */
static inline int autofs_get_set_timeout(struct autofs_sb_info *sbi,
unsigned long __user *p)
{
unsigned long ntimeout;
if (get_user(ntimeout, p) ||
put_user(sbi->exp_timeout / HZ, p))
return -EFAULT;
if ( ntimeout > ULONG_MAX/HZ )
sbi->exp_timeout = 0;
else
sbi->exp_timeout = ntimeout * HZ;
return 0;
}
/* Return protocol version */
static inline int autofs_get_protover(int __user *p)
{
return put_user(AUTOFS_PROTO_VERSION, p);
}
/* Perform an expiry operation */
static inline int autofs_expire_run(struct super_block *sb,
struct autofs_sb_info *sbi,
struct vfsmount *mnt,
struct autofs_packet_expire __user *pkt_p)
{
struct autofs_dir_ent *ent;
struct autofs_packet_expire pkt;
memset(&pkt,0,sizeof pkt);
pkt.hdr.proto_version = AUTOFS_PROTO_VERSION;
pkt.hdr.type = autofs_ptype_expire;
if ( !sbi->exp_timeout ||
!(ent = autofs_expire(sb,sbi,mnt)) )
return -EAGAIN;
pkt.len = ent->len;
memcpy(pkt.name, ent->name, pkt.len);
pkt.name[pkt.len] = '\0';
if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) )
return -EFAULT;
return 0;
}
/*
* ioctl()'s on the root directory is the chief method for the daemon to
* generate kernel reactions
*/
static int autofs_root_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct autofs_sb_info *sbi = autofs_sbi(inode->i_sb);
void __user *argp = (void __user *)arg;
DPRINTK(("autofs_ioctl: cmd = 0x%08x, arg = 0x%08lx, sbi = %p, pgrp = %u\n",cmd,arg,sbi,process_group(current)));
if ( _IOC_TYPE(cmd) != _IOC_TYPE(AUTOFS_IOC_FIRST) ||
_IOC_NR(cmd) - _IOC_NR(AUTOFS_IOC_FIRST) >= AUTOFS_IOC_COUNT )
return -ENOTTY;
if ( !autofs_oz_mode(sbi) && !capable(CAP_SYS_ADMIN) )
return -EPERM;
switch(cmd) {
case AUTOFS_IOC_READY: /* Wait queue: go ahead and retry */
return autofs_wait_release(sbi,(autofs_wqt_t)arg,0);
case AUTOFS_IOC_FAIL: /* Wait queue: fail with ENOENT */
return autofs_wait_release(sbi,(autofs_wqt_t)arg,-ENOENT);
case AUTOFS_IOC_CATATONIC: /* Enter catatonic mode (daemon shutdown) */
autofs_catatonic_mode(sbi);
return 0;
case AUTOFS_IOC_PROTOVER: /* Get protocol version */
return autofs_get_protover(argp);
case AUTOFS_IOC_SETTIMEOUT:
return autofs_get_set_timeout(sbi, argp);
case AUTOFS_IOC_EXPIRE:
return autofs_expire_run(inode->i_sb, sbi, filp->f_vfsmnt,
argp);
default:
return -ENOSYS;
}
}

25
fs/autofs/symlink.c Normal file
View File

@@ -0,0 +1,25 @@
/* -*- linux-c -*- --------------------------------------------------------- *
*
* linux/fs/autofs/symlink.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include "autofs_i.h"
static int autofs_follow_link(struct dentry *dentry, struct nameidata *nd)
{
char *s=((struct autofs_symlink *)dentry->d_inode->u.generic_ip)->data;
nd_set_link(nd, s);
return 0;
}
struct inode_operations autofs_symlink_inode_operations = {
.readlink = generic_readlink,
.follow_link = autofs_follow_link
};

206
fs/autofs/waitq.c Normal file
View File

@@ -0,0 +1,206 @@
/* -*- linux-c -*- --------------------------------------------------------- *
*
* linux/fs/autofs/waitq.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/signal.h>
#include <linux/file.h>
#include "autofs_i.h"
/* We make this a static variable rather than a part of the superblock; it
is better if we don't reassign numbers easily even across filesystems */
static autofs_wqt_t autofs_next_wait_queue = 1;
/* These are the signals we allow interrupting a pending mount */
#define SHUTDOWN_SIGS (sigmask(SIGKILL) | sigmask(SIGINT) | sigmask(SIGQUIT))
void autofs_catatonic_mode(struct autofs_sb_info *sbi)
{
struct autofs_wait_queue *wq, *nwq;
DPRINTK(("autofs: entering catatonic mode\n"));
sbi->catatonic = 1;
wq = sbi->queues;
sbi->queues = NULL; /* Erase all wait queues */
while ( wq ) {
nwq = wq->next;
wq->status = -ENOENT; /* Magic is gone - report failure */
kfree(wq->name);
wq->name = NULL;
wake_up(&wq->queue);
wq = nwq;
}
fput(sbi->pipe); /* Close the pipe */
autofs_hash_dputall(&sbi->dirhash); /* Remove all dentry pointers */
}
static int autofs_write(struct file *file, const void *addr, int bytes)
{
unsigned long sigpipe, flags;
mm_segment_t fs;
const char *data = (const char *)addr;
ssize_t wr = 0;
/** WARNING: this is not safe for writing more than PIPE_BUF bytes! **/
sigpipe = sigismember(&current->pending.signal, SIGPIPE);
/* Save pointer to user space and point back to kernel space */
fs = get_fs();
set_fs(KERNEL_DS);
while (bytes &&
(wr = file->f_op->write(file,data,bytes,&file->f_pos)) > 0) {
data += wr;
bytes -= wr;
}
set_fs(fs);
/* Keep the currently executing process from receiving a
SIGPIPE unless it was already supposed to get one */
if (wr == -EPIPE && !sigpipe) {
spin_lock_irqsave(&current->sighand->siglock, flags);
sigdelset(&current->pending.signal, SIGPIPE);
recalc_sigpending();
spin_unlock_irqrestore(&current->sighand->siglock, flags);
}
return (bytes > 0);
}
static void autofs_notify_daemon(struct autofs_sb_info *sbi, struct autofs_wait_queue *wq)
{
struct autofs_packet_missing pkt;
DPRINTK(("autofs_wait: wait id = 0x%08lx, name = ", wq->wait_queue_token));
autofs_say(wq->name,wq->len);
memset(&pkt,0,sizeof pkt); /* For security reasons */
pkt.hdr.proto_version = AUTOFS_PROTO_VERSION;
pkt.hdr.type = autofs_ptype_missing;
pkt.wait_queue_token = wq->wait_queue_token;
pkt.len = wq->len;
memcpy(pkt.name, wq->name, pkt.len);
pkt.name[pkt.len] = '\0';
if ( autofs_write(sbi->pipe,&pkt,sizeof(struct autofs_packet_missing)) )
autofs_catatonic_mode(sbi);
}
int autofs_wait(struct autofs_sb_info *sbi, struct qstr *name)
{
struct autofs_wait_queue *wq;
int status;
/* In catatonic mode, we don't wait for nobody */
if ( sbi->catatonic )
return -ENOENT;
/* We shouldn't be able to get here, but just in case */
if ( name->len > NAME_MAX )
return -ENOENT;
for ( wq = sbi->queues ; wq ; wq = wq->next ) {
if ( wq->hash == name->hash &&
wq->len == name->len &&
wq->name && !memcmp(wq->name,name->name,name->len) )
break;
}
if ( !wq ) {
/* Create a new wait queue */
wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL);
if ( !wq )
return -ENOMEM;
wq->name = kmalloc(name->len,GFP_KERNEL);
if ( !wq->name ) {
kfree(wq);
return -ENOMEM;
}
wq->wait_queue_token = autofs_next_wait_queue++;
init_waitqueue_head(&wq->queue);
wq->hash = name->hash;
wq->len = name->len;
wq->status = -EINTR; /* Status return if interrupted */
memcpy(wq->name, name->name, name->len);
wq->next = sbi->queues;
sbi->queues = wq;
/* autofs_notify_daemon() may block */
wq->wait_ctr = 2;
autofs_notify_daemon(sbi,wq);
} else
wq->wait_ctr++;
/* wq->name is NULL if and only if the lock is already released */
if ( sbi->catatonic ) {
/* We might have slept, so check again for catatonic mode */
wq->status = -ENOENT;
if ( wq->name ) {
kfree(wq->name);
wq->name = NULL;
}
}
if ( wq->name ) {
/* Block all but "shutdown" signals while waiting */
sigset_t sigmask;
siginitsetinv(&sigmask, SHUTDOWN_SIGS);
sigprocmask(SIG_BLOCK, &sigmask, &sigmask);
interruptible_sleep_on(&wq->queue);
sigprocmask(SIG_SETMASK, &sigmask, NULL);
} else {
DPRINTK(("autofs_wait: skipped sleeping\n"));
}
status = wq->status;
if ( ! --wq->wait_ctr ) /* Are we the last process to need status? */
kfree(wq);
return status;
}
int autofs_wait_release(struct autofs_sb_info *sbi, autofs_wqt_t wait_queue_token, int status)
{
struct autofs_wait_queue *wq, **wql;
for ( wql = &sbi->queues ; (wq = *wql) != 0 ; wql = &wq->next ) {
if ( wq->wait_queue_token == wait_queue_token )
break;
}
if ( !wq )
return -EINVAL;
*wql = wq->next; /* Unlink from chain */
kfree(wq->name);
wq->name = NULL; /* Do not wait on this queue */
wq->status = status;
if ( ! --wq->wait_ctr ) /* Is anyone still waiting for this guy? */
kfree(wq);
else
wake_up(&wq->queue);
return 0;
}

7
fs/autofs4/Makefile Normal file
View File

@@ -0,0 +1,7 @@
#
# Makefile for the linux autofs-filesystem routines.
#
obj-$(CONFIG_AUTOFS4_FS) += autofs4.o
autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o

193
fs/autofs4/autofs_i.h Normal file
View File

@@ -0,0 +1,193 @@
/* -*- c -*- ------------------------------------------------------------- *
*
* linux/fs/autofs/autofs_i.h
*
* Copyright 1997-1998 Transmeta Corporation - All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
/* Internal header file for autofs */
#include <linux/auto_fs4.h>
#include <linux/list.h>
/* This is the range of ioctl() numbers we claim as ours */
#define AUTOFS_IOC_FIRST AUTOFS_IOC_READY
#define AUTOFS_IOC_COUNT 32
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <asm/current.h>
#include <asm/uaccess.h>
/* #define DEBUG */
#ifdef DEBUG
#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0)
#else
#define DPRINTK(fmt,args...) do {} while(0)
#endif
#define AUTOFS_SUPER_MAGIC 0x0187
/*
* If the daemon returns a negative response (AUTOFS_IOC_FAIL) then the
* kernel will keep the negative response cached for up to the time given
* here, although the time can be shorter if the kernel throws the dcache
* entry away. This probably should be settable from user space.
*/
#define AUTOFS_NEGATIVE_TIMEOUT (60*HZ) /* 1 minute */
/* Unified info structure. This is pointed to by both the dentry and
inode structures. Each file in the filesystem has an instance of this
structure. It holds a reference to the dentry, so dentries are never
flushed while the file exists. All name lookups are dealt with at the
dentry level, although the filesystem can interfere in the validation
process. Readdir is implemented by traversing the dentry lists. */
struct autofs_info {
struct dentry *dentry;
struct inode *inode;
int flags;
struct autofs_sb_info *sbi;
unsigned long last_used;
mode_t mode;
size_t size;
void (*free)(struct autofs_info *);
union {
const char *symlink;
} u;
};
#define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */
struct autofs_wait_queue {
wait_queue_head_t queue;
struct autofs_wait_queue *next;
autofs_wqt_t wait_queue_token;
/* We use the following to see what we are waiting for */
int hash;
int len;
char *name;
/* This is for status reporting upon return */
int status;
atomic_t wait_ctr;
};
#define AUTOFS_SBI_MAGIC 0x6d4a556d
struct autofs_sb_info {
u32 magic;
struct file *pipe;
pid_t oz_pgrp;
int catatonic;
int version;
int sub_version;
unsigned long exp_timeout;
int reghost_enabled;
int needs_reghost;
struct super_block *sb;
struct semaphore wq_sem;
struct autofs_wait_queue *queues; /* Wait queue pointer */
};
static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb)
{
return (struct autofs_sb_info *)(sb->s_fs_info);
}
static inline struct autofs_info *autofs4_dentry_ino(struct dentry *dentry)
{
return (struct autofs_info *)(dentry->d_fsdata);
}
/* autofs4_oz_mode(): do we see the man behind the curtain? (The
processes which do manipulations for us in user space sees the raw
filesystem without "magic".) */
static inline int autofs4_oz_mode(struct autofs_sb_info *sbi) {
return sbi->catatonic || process_group(current) == sbi->oz_pgrp;
}
/* Does a dentry have some pending activity? */
static inline int autofs4_ispending(struct dentry *dentry)
{
struct autofs_info *inf = autofs4_dentry_ino(dentry);
return (dentry->d_flags & DCACHE_AUTOFS_PENDING) ||
(inf != NULL && inf->flags & AUTOFS_INF_EXPIRING);
}
static inline void autofs4_copy_atime(struct file *src, struct file *dst)
{
dst->f_dentry->d_inode->i_atime = src->f_dentry->d_inode->i_atime;
return;
}
struct inode *autofs4_get_inode(struct super_block *, struct autofs_info *);
void autofs4_free_ino(struct autofs_info *);
/* Expiration */
int is_autofs4_dentry(struct dentry *);
int autofs4_expire_run(struct super_block *, struct vfsmount *,
struct autofs_sb_info *,
struct autofs_packet_expire __user *);
int autofs4_expire_multi(struct super_block *, struct vfsmount *,
struct autofs_sb_info *, int __user *);
/* Operations structures */
extern struct inode_operations autofs4_symlink_inode_operations;
extern struct inode_operations autofs4_dir_inode_operations;
extern struct inode_operations autofs4_root_inode_operations;
extern struct file_operations autofs4_dir_operations;
extern struct file_operations autofs4_root_operations;
/* Initializing function */
int autofs4_fill_super(struct super_block *, void *, int);
struct autofs_info *autofs4_init_ino(struct autofs_info *, struct autofs_sb_info *sbi, mode_t mode);
/* Queue management functions */
enum autofs_notify
{
NFY_NONE,
NFY_MOUNT,
NFY_EXPIRE
};
int autofs4_wait(struct autofs_sb_info *,struct dentry *, enum autofs_notify);
int autofs4_wait_release(struct autofs_sb_info *,autofs_wqt_t,int);
void autofs4_catatonic_mode(struct autofs_sb_info *);
static inline int simple_positive(struct dentry *dentry)
{
return dentry->d_inode && !d_unhashed(dentry);
}
static inline int simple_empty_nolock(struct dentry *dentry)
{
struct dentry *child;
int ret = 0;
list_for_each_entry(child, &dentry->d_subdirs, d_child)
if (simple_positive(child))
goto out;
ret = 1;
out:
return ret;
}

358
fs/autofs4/expire.c Normal file
View File

@@ -0,0 +1,358 @@
/* -*- c -*- --------------------------------------------------------------- *
*
* linux/fs/autofs/expire.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
* Copyright 1999-2000 Jeremy Fitzhardinge <jeremy@goop.org>
* Copyright 2001-2003 Ian Kent <raven@themaw.net>
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include "autofs_i.h"
static unsigned long now;
/* Check if a dentry can be expired return 1 if it can else return 0 */
static inline int autofs4_can_expire(struct dentry *dentry,
unsigned long timeout, int do_now)
{
struct autofs_info *ino = autofs4_dentry_ino(dentry);
/* dentry in the process of being deleted */
if (ino == NULL)
return 0;
/* No point expiring a pending mount */
if (dentry->d_flags & DCACHE_AUTOFS_PENDING)
return 0;
if (!do_now) {
/* Too young to die */
if (time_after(ino->last_used + timeout, now))
return 0;
/* update last_used here :-
- obviously makes sense if it is in use now
- less obviously, prevents rapid-fire expire
attempts if expire fails the first time */
ino->last_used = now;
}
return 1;
}
/* Check a mount point for busyness return 1 if not busy, otherwise */
static int autofs4_check_mount(struct vfsmount *mnt, struct dentry *dentry)
{
int status = 0;
DPRINTK("dentry %p %.*s",
dentry, (int)dentry->d_name.len, dentry->d_name.name);
mntget(mnt);
dget(dentry);
if (!follow_down(&mnt, &dentry))
goto done;
while (d_mountpoint(dentry) && follow_down(&mnt, &dentry))
;
/* This is an autofs submount, we can't expire it */
if (is_autofs4_dentry(dentry))
goto done;
/* The big question */
if (may_umount_tree(mnt) == 0)
status = 1;
done:
DPRINTK("returning = %d", status);
mntput(mnt);
dput(dentry);
return status;
}
/* Check a directory tree of mount points for busyness
* The tree is not busy iff no mountpoints are busy
* Return 1 if the tree is busy or 0 otherwise
*/
static int autofs4_check_tree(struct vfsmount *mnt,
struct dentry *top,
unsigned long timeout,
int do_now)
{
struct dentry *this_parent = top;
struct list_head *next;
DPRINTK("parent %p %.*s",
top, (int)top->d_name.len, top->d_name.name);
/* Negative dentry - give up */
if (!simple_positive(top))
return 0;
/* Timeout of a tree mount is determined by its top dentry */
if (!autofs4_can_expire(top, timeout, do_now))
return 0;
spin_lock(&dcache_lock);
repeat:
next = this_parent->d_subdirs.next;
resume:
while (next != &this_parent->d_subdirs) {
struct dentry *dentry = list_entry(next, struct dentry, d_child);
/* Negative dentry - give up */
if (!simple_positive(dentry)) {
next = next->next;
continue;
}
DPRINTK("dentry %p %.*s",
dentry, (int)dentry->d_name.len, dentry->d_name.name);
if (!simple_empty_nolock(dentry)) {
this_parent = dentry;
goto repeat;
}
dentry = dget(dentry);
spin_unlock(&dcache_lock);
if (d_mountpoint(dentry)) {
/* First busy => tree busy */
if (!autofs4_check_mount(mnt, dentry)) {
dput(dentry);
return 0;
}
}
dput(dentry);
spin_lock(&dcache_lock);
next = next->next;
}
if (this_parent != top) {
next = this_parent->d_child.next;
this_parent = this_parent->d_parent;
goto resume;
}
spin_unlock(&dcache_lock);
return 1;
}
static struct dentry *autofs4_check_leaves(struct vfsmount *mnt,
struct dentry *parent,
unsigned long timeout,
int do_now)
{
struct dentry *this_parent = parent;
struct list_head *next;
DPRINTK("parent %p %.*s",
parent, (int)parent->d_name.len, parent->d_name.name);
spin_lock(&dcache_lock);
repeat:
next = this_parent->d_subdirs.next;
resume:
while (next != &this_parent->d_subdirs) {
struct dentry *dentry = list_entry(next, struct dentry, d_child);
/* Negative dentry - give up */
if (!simple_positive(dentry)) {
next = next->next;
continue;
}
DPRINTK("dentry %p %.*s",
dentry, (int)dentry->d_name.len, dentry->d_name.name);
if (!list_empty(&dentry->d_subdirs)) {
this_parent = dentry;
goto repeat;
}
dentry = dget(dentry);
spin_unlock(&dcache_lock);
if (d_mountpoint(dentry)) {
/* Can we expire this guy */
if (!autofs4_can_expire(dentry, timeout, do_now))
goto cont;
/* Can we umount this guy */
if (autofs4_check_mount(mnt, dentry))
return dentry;
}
cont:
dput(dentry);
spin_lock(&dcache_lock);
next = next->next;
}
if (this_parent != parent) {
next = this_parent->d_child.next;
this_parent = this_parent->d_parent;
goto resume;
}
spin_unlock(&dcache_lock);
return NULL;
}
/*
* Find an eligible tree to time-out
* A tree is eligible if :-
* - it is unused by any user process
* - it has been unused for exp_timeout time
*/
static struct dentry *autofs4_expire(struct super_block *sb,
struct vfsmount *mnt,
struct autofs_sb_info *sbi,
int how)
{
unsigned long timeout;
struct dentry *root = sb->s_root;
struct dentry *expired = NULL;
struct list_head *next;
int do_now = how & AUTOFS_EXP_IMMEDIATE;
int exp_leaves = how & AUTOFS_EXP_LEAVES;
if ( !sbi->exp_timeout || !root )
return NULL;
now = jiffies;
timeout = sbi->exp_timeout;
spin_lock(&dcache_lock);
next = root->d_subdirs.next;
/* On exit from the loop expire is set to a dgot dentry
* to expire or it's NULL */
while ( next != &root->d_subdirs ) {
struct dentry *dentry = list_entry(next, struct dentry, d_child);
/* Negative dentry - give up */
if ( !simple_positive(dentry) ) {
next = next->next;
continue;
}
dentry = dget(dentry);
spin_unlock(&dcache_lock);
/* Case 1: indirect mount or top level direct mount */
if (d_mountpoint(dentry)) {
DPRINTK("checking mountpoint %p %.*s",
dentry, (int)dentry->d_name.len, dentry->d_name.name);
/* Can we expire this guy */
if (!autofs4_can_expire(dentry, timeout, do_now))
goto next;
/* Can we umount this guy */
if (autofs4_check_mount(mnt, dentry)) {
expired = dentry;
break;
}
goto next;
}
if ( simple_empty(dentry) )
goto next;
/* Case 2: tree mount, expire iff entire tree is not busy */
if (!exp_leaves) {
if (autofs4_check_tree(mnt, dentry, timeout, do_now)) {
expired = dentry;
break;
}
/* Case 3: direct mount, expire individual leaves */
} else {
expired = autofs4_check_leaves(mnt, dentry, timeout, do_now);
if (expired) {
dput(dentry);
break;
}
}
next:
dput(dentry);
spin_lock(&dcache_lock);
next = next->next;
}
if ( expired ) {
DPRINTK("returning %p %.*s",
expired, (int)expired->d_name.len, expired->d_name.name);
spin_lock(&dcache_lock);
list_del(&expired->d_parent->d_subdirs);
list_add(&expired->d_parent->d_subdirs, &expired->d_child);
spin_unlock(&dcache_lock);
return expired;
}
spin_unlock(&dcache_lock);
return NULL;
}
/* Perform an expiry operation */
int autofs4_expire_run(struct super_block *sb,
struct vfsmount *mnt,
struct autofs_sb_info *sbi,
struct autofs_packet_expire __user *pkt_p)
{
struct autofs_packet_expire pkt;
struct dentry *dentry;
memset(&pkt,0,sizeof pkt);
pkt.hdr.proto_version = sbi->version;
pkt.hdr.type = autofs_ptype_expire;
if ((dentry = autofs4_expire(sb, mnt, sbi, 0)) == NULL)
return -EAGAIN;
pkt.len = dentry->d_name.len;
memcpy(pkt.name, dentry->d_name.name, pkt.len);
pkt.name[pkt.len] = '\0';
dput(dentry);
if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) )
return -EFAULT;
return 0;
}
/* Call repeatedly until it returns -EAGAIN, meaning there's nothing
more to be done */
int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt,
struct autofs_sb_info *sbi, int __user *arg)
{
struct dentry *dentry;
int ret = -EAGAIN;
int do_now = 0;
if (arg && get_user(do_now, arg))
return -EFAULT;
if ((dentry = autofs4_expire(sb, mnt, sbi, do_now)) != NULL) {
struct autofs_info *de_info = autofs4_dentry_ino(dentry);
/* This is synchronous because it makes the daemon a
little easier */
de_info->flags |= AUTOFS_INF_EXPIRING;
ret = autofs4_wait(sbi, dentry, NFY_EXPIRE);
de_info->flags &= ~AUTOFS_INF_EXPIRING;
dput(dentry);
}
return ret;
}

42
fs/autofs4/init.c Normal file
View File

@@ -0,0 +1,42 @@
/* -*- c -*- --------------------------------------------------------------- *
*
* linux/fs/autofs/init.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include <linux/module.h>
#include <linux/init.h>
#include "autofs_i.h"
static struct super_block *autofs_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return get_sb_nodev(fs_type, flags, data, autofs4_fill_super);
}
static struct file_system_type autofs_fs_type = {
.owner = THIS_MODULE,
.name = "autofs",
.get_sb = autofs_get_sb,
.kill_sb = kill_anon_super,
};
static int __init init_autofs4_fs(void)
{
return register_filesystem(&autofs_fs_type);
}
static void __exit exit_autofs4_fs(void)
{
unregister_filesystem(&autofs_fs_type);
}
module_init(init_autofs4_fs)
module_exit(exit_autofs4_fs)
MODULE_LICENSE("GPL");

324
fs/autofs4/inode.c Normal file
View File

@@ -0,0 +1,324 @@
/* -*- c -*- --------------------------------------------------------------- *
*
* linux/fs/autofs/inode.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/pagemap.h>
#include <linux/parser.h>
#include <linux/bitops.h>
#include "autofs_i.h"
#include <linux/module.h>
static void ino_lnkfree(struct autofs_info *ino)
{
if (ino->u.symlink) {
kfree(ino->u.symlink);
ino->u.symlink = NULL;
}
}
struct autofs_info *autofs4_init_ino(struct autofs_info *ino,
struct autofs_sb_info *sbi, mode_t mode)
{
int reinit = 1;
if (ino == NULL) {
reinit = 0;
ino = kmalloc(sizeof(*ino), GFP_KERNEL);
}
if (ino == NULL)
return NULL;
ino->flags = 0;
ino->mode = mode;
ino->inode = NULL;
ino->dentry = NULL;
ino->size = 0;
ino->last_used = jiffies;
ino->sbi = sbi;
if (reinit && ino->free)
(ino->free)(ino);
memset(&ino->u, 0, sizeof(ino->u));
ino->free = NULL;
if (S_ISLNK(mode))
ino->free = ino_lnkfree;
return ino;
}
void autofs4_free_ino(struct autofs_info *ino)
{
if (ino->dentry) {
ino->dentry->d_fsdata = NULL;
if (ino->dentry->d_inode)
dput(ino->dentry);
ino->dentry = NULL;
}
if (ino->free)
(ino->free)(ino);
kfree(ino);
}
static void autofs4_put_super(struct super_block *sb)
{
struct autofs_sb_info *sbi = autofs4_sbi(sb);
sb->s_fs_info = NULL;
if ( !sbi->catatonic )
autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */
kfree(sbi);
DPRINTK("shutting down");
}
static struct super_operations autofs4_sops = {
.put_super = autofs4_put_super,
.statfs = simple_statfs,
};
enum {Opt_err, Opt_fd, Opt_uid, Opt_gid, Opt_pgrp, Opt_minproto, Opt_maxproto};
static match_table_t tokens = {
{Opt_fd, "fd=%u"},
{Opt_uid, "uid=%u"},
{Opt_gid, "gid=%u"},
{Opt_pgrp, "pgrp=%u"},
{Opt_minproto, "minproto=%u"},
{Opt_maxproto, "maxproto=%u"},
{Opt_err, NULL}
};
static int parse_options(char *options, int *pipefd, uid_t *uid, gid_t *gid,
pid_t *pgrp, int *minproto, int *maxproto)
{
char *p;
substring_t args[MAX_OPT_ARGS];
int option;
*uid = current->uid;
*gid = current->gid;
*pgrp = process_group(current);
*minproto = AUTOFS_MIN_PROTO_VERSION;
*maxproto = AUTOFS_MAX_PROTO_VERSION;
*pipefd = -1;
if (!options)
return 1;
while ((p = strsep(&options, ",")) != NULL) {
int token;
if (!*p)
continue;
token = match_token(p, tokens, args);
switch (token) {
case Opt_fd:
if (match_int(args, pipefd))
return 1;
break;
case Opt_uid:
if (match_int(args, &option))
return 1;
*uid = option;
break;
case Opt_gid:
if (match_int(args, &option))
return 1;
*gid = option;
break;
case Opt_pgrp:
if (match_int(args, &option))
return 1;
*pgrp = option;
break;
case Opt_minproto:
if (match_int(args, &option))
return 1;
*minproto = option;
break;
case Opt_maxproto:
if (match_int(args, &option))
return 1;
*maxproto = option;
break;
default:
return 1;
}
}
return (*pipefd < 0);
}
static struct autofs_info *autofs4_mkroot(struct autofs_sb_info *sbi)
{
struct autofs_info *ino;
ino = autofs4_init_ino(NULL, sbi, S_IFDIR | 0755);
if (!ino)
return NULL;
return ino;
}
int autofs4_fill_super(struct super_block *s, void *data, int silent)
{
struct inode * root_inode;
struct dentry * root;
struct file * pipe;
int pipefd;
struct autofs_sb_info *sbi;
struct autofs_info *ino;
int minproto, maxproto;
sbi = (struct autofs_sb_info *) kmalloc(sizeof(*sbi), GFP_KERNEL);
if ( !sbi )
goto fail_unlock;
DPRINTK("starting up, sbi = %p",sbi);
memset(sbi, 0, sizeof(*sbi));
s->s_fs_info = sbi;
sbi->magic = AUTOFS_SBI_MAGIC;
sbi->catatonic = 0;
sbi->exp_timeout = 0;
sbi->oz_pgrp = process_group(current);
sbi->sb = s;
sbi->version = 0;
sbi->sub_version = 0;
init_MUTEX(&sbi->wq_sem);
sbi->queues = NULL;
s->s_blocksize = 1024;
s->s_blocksize_bits = 10;
s->s_magic = AUTOFS_SUPER_MAGIC;
s->s_op = &autofs4_sops;
s->s_time_gran = 1;
/*
* Get the root inode and dentry, but defer checking for errors.
*/
ino = autofs4_mkroot(sbi);
if (!ino)
goto fail_free;
root_inode = autofs4_get_inode(s, ino);
kfree(ino);
if (!root_inode)
goto fail_free;
root_inode->i_op = &autofs4_root_inode_operations;
root_inode->i_fop = &autofs4_root_operations;
root = d_alloc_root(root_inode);
pipe = NULL;
if (!root)
goto fail_iput;
/* Can this call block? */
if (parse_options(data, &pipefd,
&root_inode->i_uid, &root_inode->i_gid,
&sbi->oz_pgrp,
&minproto, &maxproto)) {
printk("autofs: called with bogus options\n");
goto fail_dput;
}
/* Couldn't this be tested earlier? */
if (maxproto < AUTOFS_MIN_PROTO_VERSION ||
minproto > AUTOFS_MAX_PROTO_VERSION) {
printk("autofs: kernel does not match daemon version "
"daemon (%d, %d) kernel (%d, %d)\n",
minproto, maxproto,
AUTOFS_MIN_PROTO_VERSION, AUTOFS_MAX_PROTO_VERSION);
goto fail_dput;
}
sbi->version = maxproto > AUTOFS_MAX_PROTO_VERSION ? AUTOFS_MAX_PROTO_VERSION : maxproto;
sbi->sub_version = AUTOFS_PROTO_SUBVERSION;
DPRINTK("pipe fd = %d, pgrp = %u", pipefd, sbi->oz_pgrp);
pipe = fget(pipefd);
if ( !pipe ) {
printk("autofs: could not open pipe file descriptor\n");
goto fail_dput;
}
if ( !pipe->f_op || !pipe->f_op->write )
goto fail_fput;
sbi->pipe = pipe;
/*
* Success! Install the root dentry now to indicate completion.
*/
s->s_root = root;
return 0;
/*
* Failure ... clean up.
*/
fail_fput:
printk("autofs: pipe file descriptor does not contain proper ops\n");
fput(pipe);
/* fall through */
fail_dput:
dput(root);
goto fail_free;
fail_iput:
printk("autofs: get root dentry failed\n");
iput(root_inode);
fail_free:
kfree(sbi);
fail_unlock:
return -EINVAL;
}
struct inode *autofs4_get_inode(struct super_block *sb,
struct autofs_info *inf)
{
struct inode *inode = new_inode(sb);
if (inode == NULL)
return NULL;
inf->inode = inode;
inode->i_mode = inf->mode;
if (sb->s_root) {
inode->i_uid = sb->s_root->d_inode->i_uid;
inode->i_gid = sb->s_root->d_inode->i_gid;
} else {
inode->i_uid = 0;
inode->i_gid = 0;
}
inode->i_blksize = PAGE_CACHE_SIZE;
inode->i_blocks = 0;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
if (S_ISDIR(inf->mode)) {
inode->i_nlink = 2;
inode->i_op = &autofs4_dir_inode_operations;
inode->i_fop = &autofs4_dir_operations;
} else if (S_ISLNK(inf->mode)) {
inode->i_size = inf->size;
inode->i_op = &autofs4_symlink_inode_operations;
}
return inode;
}

808
fs/autofs4/root.c Normal file
View File

@@ -0,0 +1,808 @@
/* -*- c -*- --------------------------------------------------------------- *
*
* linux/fs/autofs/root.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
* Copyright 1999-2000 Jeremy Fitzhardinge <jeremy@goop.org>
* Copyright 2001-2003 Ian Kent <raven@themaw.net>
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/param.h>
#include <linux/time.h>
#include <linux/smp_lock.h>
#include "autofs_i.h"
static int autofs4_dir_symlink(struct inode *,struct dentry *,const char *);
static int autofs4_dir_unlink(struct inode *,struct dentry *);
static int autofs4_dir_rmdir(struct inode *,struct dentry *);
static int autofs4_dir_mkdir(struct inode *,struct dentry *,int);
static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long);
static int autofs4_dir_open(struct inode *inode, struct file *file);
static int autofs4_dir_close(struct inode *inode, struct file *file);
static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir);
static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir);
static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *);
static int autofs4_dcache_readdir(struct file *, void *, filldir_t);
struct file_operations autofs4_root_operations = {
.open = dcache_dir_open,
.release = dcache_dir_close,
.read = generic_read_dir,
.readdir = autofs4_root_readdir,
.ioctl = autofs4_root_ioctl,
};
struct file_operations autofs4_dir_operations = {
.open = autofs4_dir_open,
.release = autofs4_dir_close,
.read = generic_read_dir,
.readdir = autofs4_dir_readdir,
};
struct inode_operations autofs4_root_inode_operations = {
.lookup = autofs4_lookup,
.unlink = autofs4_dir_unlink,
.symlink = autofs4_dir_symlink,
.mkdir = autofs4_dir_mkdir,
.rmdir = autofs4_dir_rmdir,
};
struct inode_operations autofs4_dir_inode_operations = {
.lookup = autofs4_lookup,
.unlink = autofs4_dir_unlink,
.symlink = autofs4_dir_symlink,
.mkdir = autofs4_dir_mkdir,
.rmdir = autofs4_dir_rmdir,
};
static int autofs4_root_readdir(struct file *file, void *dirent,
filldir_t filldir)
{
struct autofs_sb_info *sbi = autofs4_sbi(file->f_dentry->d_sb);
int oz_mode = autofs4_oz_mode(sbi);
DPRINTK("called, filp->f_pos = %lld", file->f_pos);
/*
* Don't set reghost flag if:
* 1) f_pos is larger than zero -- we've already been here.
* 2) we haven't even enabled reghosting in the 1st place.
* 3) this is the daemon doing a readdir
*/
if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled)
sbi->needs_reghost = 1;
DPRINTK("needs_reghost = %d", sbi->needs_reghost);
return autofs4_dcache_readdir(file, dirent, filldir);
}
/* Update usage from here to top of tree, so that scan of
top-level directories will give a useful result */
static void autofs4_update_usage(struct dentry *dentry)
{
struct dentry *top = dentry->d_sb->s_root;
spin_lock(&dcache_lock);
for(; dentry != top; dentry = dentry->d_parent) {
struct autofs_info *ino = autofs4_dentry_ino(dentry);
if (ino) {
update_atime(dentry->d_inode);
ino->last_used = jiffies;
}
}
spin_unlock(&dcache_lock);
}
/*
* From 2.4 kernel readdir.c
*/
static int autofs4_dcache_readdir(struct file * filp, void * dirent, filldir_t filldir)
{
int i;
struct dentry *dentry = filp->f_dentry;
i = filp->f_pos;
switch (i) {
case 0:
if (filldir(dirent, ".", 1, i, dentry->d_inode->i_ino, DT_DIR) < 0)
break;
i++;
filp->f_pos++;
/* fallthrough */
case 1:
if (filldir(dirent, "..", 2, i, dentry->d_parent->d_inode->i_ino, DT_DIR) < 0)
break;
i++;
filp->f_pos++;
/* fallthrough */
default: {
struct list_head *list;
int j = i-2;
spin_lock(&dcache_lock);
list = dentry->d_subdirs.next;
for (;;) {
if (list == &dentry->d_subdirs) {
spin_unlock(&dcache_lock);
return 0;
}
if (!j)
break;
j--;
list = list->next;
}
while(1) {
struct dentry *de = list_entry(list, struct dentry, d_child);
if (!d_unhashed(de) && de->d_inode) {
spin_unlock(&dcache_lock);
if (filldir(dirent, de->d_name.name, de->d_name.len, filp->f_pos, de->d_inode->i_ino, DT_UNKNOWN) < 0)
break;
spin_lock(&dcache_lock);
}
filp->f_pos++;
list = list->next;
if (list != &dentry->d_subdirs)
continue;
spin_unlock(&dcache_lock);
break;
}
}
}
return 0;
}
static int autofs4_dir_open(struct inode *inode, struct file *file)
{
struct dentry *dentry = file->f_dentry;
struct vfsmount *mnt = file->f_vfsmnt;
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
int status;
DPRINTK("file=%p dentry=%p %.*s",
file, dentry, dentry->d_name.len, dentry->d_name.name);
if (autofs4_oz_mode(sbi))
goto out;
if (autofs4_ispending(dentry)) {
DPRINTK("dentry busy");
return -EBUSY;
}
if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) {
struct nameidata nd;
int empty;
/* In case there are stale directory dentrys from a failed mount */
spin_lock(&dcache_lock);
empty = list_empty(&dentry->d_subdirs);
spin_unlock(&dcache_lock);
if (!empty)
d_invalidate(dentry);
nd.flags = LOOKUP_DIRECTORY;
status = (dentry->d_op->d_revalidate)(dentry, &nd);
if (!status)
return -ENOENT;
}
if (d_mountpoint(dentry)) {
struct file *fp = NULL;
struct vfsmount *fp_mnt = mntget(mnt);
struct dentry *fp_dentry = dget(dentry);
while (follow_down(&fp_mnt, &fp_dentry) && d_mountpoint(fp_dentry));
fp = dentry_open(fp_dentry, fp_mnt, file->f_flags);
status = PTR_ERR(fp);
if (IS_ERR(fp)) {
file->private_data = NULL;
return status;
}
file->private_data = fp;
}
out:
return 0;
}
static int autofs4_dir_close(struct inode *inode, struct file *file)
{
struct dentry *dentry = file->f_dentry;
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
DPRINTK("file=%p dentry=%p %.*s",
file, dentry, dentry->d_name.len, dentry->d_name.name);
if (autofs4_oz_mode(sbi))
goto out;
if (autofs4_ispending(dentry)) {
DPRINTK("dentry busy");
return -EBUSY;
}
if (d_mountpoint(dentry)) {
struct file *fp = file->private_data;
if (!fp)
return -ENOENT;
filp_close(fp, current->files);
file->private_data = NULL;
}
out:
return 0;
}
static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir)
{
struct dentry *dentry = file->f_dentry;
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
int status;
DPRINTK("file=%p dentry=%p %.*s",
file, dentry, dentry->d_name.len, dentry->d_name.name);
if (autofs4_oz_mode(sbi))
goto out;
if (autofs4_ispending(dentry)) {
DPRINTK("dentry busy");
return -EBUSY;
}
if (d_mountpoint(dentry)) {
struct file *fp = file->private_data;
if (!fp)
return -ENOENT;
if (!fp->f_op || !fp->f_op->readdir)
goto out;
status = vfs_readdir(fp, filldir, dirent);
file->f_pos = fp->f_pos;
if (status)
autofs4_copy_atime(file, fp);
return status;
}
out:
return autofs4_dcache_readdir(file, dirent, filldir);
}
static int try_to_fill_dentry(struct dentry *dentry,
struct super_block *sb,
struct autofs_sb_info *sbi, int flags)
{
struct autofs_info *de_info = autofs4_dentry_ino(dentry);
int status = 0;
/* Block on any pending expiry here; invalidate the dentry
when expiration is done to trigger mount request with a new
dentry */
if (de_info && (de_info->flags & AUTOFS_INF_EXPIRING)) {
DPRINTK("waiting for expire %p name=%.*s",
dentry, dentry->d_name.len, dentry->d_name.name);
status = autofs4_wait(sbi, dentry, NFY_NONE);
DPRINTK("expire done status=%d", status);
return 0;
}
DPRINTK("dentry=%p %.*s ino=%p",
dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode);
/* Wait for a pending mount, triggering one if there isn't one already */
if (dentry->d_inode == NULL) {
DPRINTK("waiting for mount name=%.*s",
dentry->d_name.len, dentry->d_name.name);
status = autofs4_wait(sbi, dentry, NFY_MOUNT);
DPRINTK("mount done status=%d", status);
if (status && dentry->d_inode)
return 0; /* Try to get the kernel to invalidate this dentry */
/* Turn this into a real negative dentry? */
if (status == -ENOENT) {
dentry->d_time = jiffies + AUTOFS_NEGATIVE_TIMEOUT;
spin_lock(&dentry->d_lock);
dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
spin_unlock(&dentry->d_lock);
return 1;
} else if (status) {
/* Return a negative dentry, but leave it "pending" */
return 1;
}
/* Trigger mount for path component or follow link */
} else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ||
current->link_count) {
DPRINTK("waiting for mount name=%.*s",
dentry->d_name.len, dentry->d_name.name);
spin_lock(&dentry->d_lock);
dentry->d_flags |= DCACHE_AUTOFS_PENDING;
spin_unlock(&dentry->d_lock);
status = autofs4_wait(sbi, dentry, NFY_MOUNT);
DPRINTK("mount done status=%d", status);
if (status) {
spin_lock(&dentry->d_lock);
dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
spin_unlock(&dentry->d_lock);
return 0;
}
}
/* We don't update the usages for the autofs daemon itself, this
is necessary for recursive autofs mounts */
if (!autofs4_oz_mode(sbi))
autofs4_update_usage(dentry);
spin_lock(&dentry->d_lock);
dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
spin_unlock(&dentry->d_lock);
return 1;
}
/*
* Revalidate is called on every cache lookup. Some of those
* cache lookups may actually happen while the dentry is not
* yet completely filled in, and revalidate has to delay such
* lookups..
*/
static int autofs4_revalidate(struct dentry * dentry, struct nameidata *nd)
{
struct inode * dir = dentry->d_parent->d_inode;
struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
int oz_mode = autofs4_oz_mode(sbi);
int flags = nd ? nd->flags : 0;
int status = 1;
/* Pending dentry */
if (autofs4_ispending(dentry)) {
if (!oz_mode)
status = try_to_fill_dentry(dentry, dir->i_sb, sbi, flags);
return status;
}
/* Negative dentry.. invalidate if "old" */
if (dentry->d_inode == NULL)
return (dentry->d_time - jiffies <= AUTOFS_NEGATIVE_TIMEOUT);
/* Check for a non-mountpoint directory with no contents */
spin_lock(&dcache_lock);
if (S_ISDIR(dentry->d_inode->i_mode) &&
!d_mountpoint(dentry) &&
list_empty(&dentry->d_subdirs)) {
DPRINTK("dentry=%p %.*s, emptydir",
dentry, dentry->d_name.len, dentry->d_name.name);
spin_unlock(&dcache_lock);
if (!oz_mode)
status = try_to_fill_dentry(dentry, dir->i_sb, sbi, flags);
return status;
}
spin_unlock(&dcache_lock);
/* Update the usage list */
if (!oz_mode)
autofs4_update_usage(dentry);
return 1;
}
static void autofs4_dentry_release(struct dentry *de)
{
struct autofs_info *inf;
DPRINTK("releasing %p", de);
inf = autofs4_dentry_ino(de);
de->d_fsdata = NULL;
if (inf) {
inf->dentry = NULL;
inf->inode = NULL;
autofs4_free_ino(inf);
}
}
/* For dentries of directories in the root dir */
static struct dentry_operations autofs4_root_dentry_operations = {
.d_revalidate = autofs4_revalidate,
.d_release = autofs4_dentry_release,
};
/* For other dentries */
static struct dentry_operations autofs4_dentry_operations = {
.d_revalidate = autofs4_revalidate,
.d_release = autofs4_dentry_release,
};
/* Lookups in the root directory */
static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
struct autofs_sb_info *sbi;
int oz_mode;
DPRINTK("name = %.*s",
dentry->d_name.len, dentry->d_name.name);
if (dentry->d_name.len > NAME_MAX)
return ERR_PTR(-ENAMETOOLONG);/* File name too long to exist */
sbi = autofs4_sbi(dir->i_sb);
oz_mode = autofs4_oz_mode(sbi);
DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d",
current->pid, process_group(current), sbi->catatonic, oz_mode);
/*
* Mark the dentry incomplete, but add it. This is needed so
* that the VFS layer knows about the dentry, and we can count
* on catching any lookups through the revalidate.
*
* Let all the hard work be done by the revalidate function that
* needs to be able to do this anyway..
*
* We need to do this before we release the directory semaphore.
*/
dentry->d_op = &autofs4_root_dentry_operations;
if (!oz_mode) {
spin_lock(&dentry->d_lock);
dentry->d_flags |= DCACHE_AUTOFS_PENDING;
spin_unlock(&dentry->d_lock);
}
dentry->d_fsdata = NULL;
d_add(dentry, NULL);
if (dentry->d_op && dentry->d_op->d_revalidate) {
up(&dir->i_sem);
(dentry->d_op->d_revalidate)(dentry, nd);
down(&dir->i_sem);
}
/*
* If we are still pending, check if we had to handle
* a signal. If so we can force a restart..
*/
if (dentry->d_flags & DCACHE_AUTOFS_PENDING) {
/* See if we were interrupted */
if (signal_pending(current)) {
sigset_t *sigset = &current->pending.signal;
if (sigismember (sigset, SIGKILL) ||
sigismember (sigset, SIGQUIT) ||
sigismember (sigset, SIGINT)) {
return ERR_PTR(-ERESTARTNOINTR);
}
}
}
/*
* If this dentry is unhashed, then we shouldn't honour this
* lookup even if the dentry is positive. Returning ENOENT here
* doesn't do the right thing for all system calls, but it should
* be OK for the operations we permit from an autofs.
*/
if ( dentry->d_inode && d_unhashed(dentry) )
return ERR_PTR(-ENOENT);
return NULL;
}
static int autofs4_dir_symlink(struct inode *dir,
struct dentry *dentry,
const char *symname)
{
struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry);
struct inode *inode;
char *cp;
DPRINTK("%s <- %.*s", symname,
dentry->d_name.len, dentry->d_name.name);
if (!autofs4_oz_mode(sbi))
return -EACCES;
ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555);
if (ino == NULL)
return -ENOSPC;
ino->size = strlen(symname);
ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL);
if (cp == NULL) {
kfree(ino);
return -ENOSPC;
}
strcpy(cp, symname);
inode = autofs4_get_inode(dir->i_sb, ino);
d_instantiate(dentry, inode);
if (dir == dir->i_sb->s_root->d_inode)
dentry->d_op = &autofs4_root_dentry_operations;
else
dentry->d_op = &autofs4_dentry_operations;
dentry->d_fsdata = ino;
ino->dentry = dget(dentry);
ino->inode = inode;
dir->i_mtime = CURRENT_TIME;
return 0;
}
/*
* NOTE!
*
* Normal filesystems would do a "d_delete()" to tell the VFS dcache
* that the file no longer exists. However, doing that means that the
* VFS layer can turn the dentry into a negative dentry. We don't want
* this, because since the unlink is probably the result of an expire.
* We simply d_drop it, which allows the dentry lookup to remount it
* if necessary.
*
* If a process is blocked on the dentry waiting for the expire to finish,
* it will invalidate the dentry and try to mount with a new one.
*
* Also see autofs4_dir_rmdir()..
*/
static int autofs4_dir_unlink(struct inode *dir, struct dentry *dentry)
{
struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry);
/* This allows root to remove symlinks */
if ( !autofs4_oz_mode(sbi) && !capable(CAP_SYS_ADMIN) )
return -EACCES;
dput(ino->dentry);
dentry->d_inode->i_size = 0;
dentry->d_inode->i_nlink = 0;
dir->i_mtime = CURRENT_TIME;
d_drop(dentry);
return 0;
}
static int autofs4_dir_rmdir(struct inode *dir, struct dentry *dentry)
{
struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry);
if (!autofs4_oz_mode(sbi))
return -EACCES;
spin_lock(&dcache_lock);
if (!list_empty(&dentry->d_subdirs)) {
spin_unlock(&dcache_lock);
return -ENOTEMPTY;
}
spin_lock(&dentry->d_lock);
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
spin_unlock(&dcache_lock);
dput(ino->dentry);
dentry->d_inode->i_size = 0;
dentry->d_inode->i_nlink = 0;
if (dir->i_nlink)
dir->i_nlink--;
return 0;
}
static int autofs4_dir_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry);
struct inode *inode;
if ( !autofs4_oz_mode(sbi) )
return -EACCES;
DPRINTK("dentry %p, creating %.*s",
dentry, dentry->d_name.len, dentry->d_name.name);
ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555);
if (ino == NULL)
return -ENOSPC;
inode = autofs4_get_inode(dir->i_sb, ino);
d_instantiate(dentry, inode);
if (dir == dir->i_sb->s_root->d_inode)
dentry->d_op = &autofs4_root_dentry_operations;
else
dentry->d_op = &autofs4_dentry_operations;
dentry->d_fsdata = ino;
ino->dentry = dget(dentry);
ino->inode = inode;
dir->i_nlink++;
dir->i_mtime = CURRENT_TIME;
return 0;
}
/* Get/set timeout ioctl() operation */
static inline int autofs4_get_set_timeout(struct autofs_sb_info *sbi,
unsigned long __user *p)
{
int rv;
unsigned long ntimeout;
if ( (rv = get_user(ntimeout, p)) ||
(rv = put_user(sbi->exp_timeout/HZ, p)) )
return rv;
if ( ntimeout > ULONG_MAX/HZ )
sbi->exp_timeout = 0;
else
sbi->exp_timeout = ntimeout * HZ;
return 0;
}
/* Return protocol version */
static inline int autofs4_get_protover(struct autofs_sb_info *sbi, int __user *p)
{
return put_user(sbi->version, p);
}
/* Return protocol sub version */
static inline int autofs4_get_protosubver(struct autofs_sb_info *sbi, int __user *p)
{
return put_user(sbi->sub_version, p);
}
/*
* Tells the daemon whether we need to reghost or not. Also, clears
* the reghost_needed flag.
*/
static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p)
{
int status;
DPRINTK("returning %d", sbi->needs_reghost);
status = put_user(sbi->needs_reghost, p);
if ( status )
return status;
sbi->needs_reghost = 0;
return 0;
}
/*
* Enable / Disable reghosting ioctl() operation
*/
static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p)
{
int status;
int val;
status = get_user(val, p);
DPRINTK("reghost = %d", val);
if (status)
return status;
/* turn on/off reghosting, with the val */
sbi->reghost_enabled = val;
return 0;
}
/*
* Tells the daemon whether it can umount the autofs mount.
*/
static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p)
{
int status = 0;
if (may_umount(mnt) == 0)
status = 1;
DPRINTK("returning %d", status);
status = put_user(status, p);
return status;
}
/* Identify autofs4_dentries - this is so we can tell if there's
an extra dentry refcount or not. We only hold a refcount on the
dentry if its non-negative (ie, d_inode != NULL)
*/
int is_autofs4_dentry(struct dentry *dentry)
{
return dentry && dentry->d_inode &&
(dentry->d_op == &autofs4_root_dentry_operations ||
dentry->d_op == &autofs4_dentry_operations) &&
dentry->d_fsdata != NULL;
}
/*
* ioctl()'s on the root directory is the chief method for the daemon to
* generate kernel reactions
*/
static int autofs4_root_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct autofs_sb_info *sbi = autofs4_sbi(inode->i_sb);
void __user *p = (void __user *)arg;
DPRINTK("cmd = 0x%08x, arg = 0x%08lx, sbi = %p, pgrp = %u",
cmd,arg,sbi,process_group(current));
if ( _IOC_TYPE(cmd) != _IOC_TYPE(AUTOFS_IOC_FIRST) ||
_IOC_NR(cmd) - _IOC_NR(AUTOFS_IOC_FIRST) >= AUTOFS_IOC_COUNT )
return -ENOTTY;
if ( !autofs4_oz_mode(sbi) && !capable(CAP_SYS_ADMIN) )
return -EPERM;
switch(cmd) {
case AUTOFS_IOC_READY: /* Wait queue: go ahead and retry */
return autofs4_wait_release(sbi,(autofs_wqt_t)arg,0);
case AUTOFS_IOC_FAIL: /* Wait queue: fail with ENOENT */
return autofs4_wait_release(sbi,(autofs_wqt_t)arg,-ENOENT);
case AUTOFS_IOC_CATATONIC: /* Enter catatonic mode (daemon shutdown) */
autofs4_catatonic_mode(sbi);
return 0;
case AUTOFS_IOC_PROTOVER: /* Get protocol version */
return autofs4_get_protover(sbi, p);
case AUTOFS_IOC_PROTOSUBVER: /* Get protocol sub version */
return autofs4_get_protosubver(sbi, p);
case AUTOFS_IOC_SETTIMEOUT:
return autofs4_get_set_timeout(sbi, p);
case AUTOFS_IOC_TOGGLEREGHOST:
return autofs4_toggle_reghost(sbi, p);
case AUTOFS_IOC_ASKREGHOST:
return autofs4_ask_reghost(sbi, p);
case AUTOFS_IOC_ASKUMOUNT:
return autofs4_ask_umount(filp->f_vfsmnt, p);
/* return a single thing to expire */
case AUTOFS_IOC_EXPIRE:
return autofs4_expire_run(inode->i_sb,filp->f_vfsmnt,sbi, p);
/* same as above, but can send multiple expires through pipe */
case AUTOFS_IOC_EXPIRE_MULTI:
return autofs4_expire_multi(inode->i_sb,filp->f_vfsmnt,sbi, p);
default:
return -ENOSYS;
}
}

25
fs/autofs4/symlink.c Normal file
View File

@@ -0,0 +1,25 @@
/* -*- c -*- --------------------------------------------------------------- *
*
* linux/fs/autofs/symlink.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include "autofs_i.h"
static int autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
{
struct autofs_info *ino = autofs4_dentry_ino(dentry);
nd_set_link(nd, (char *)ino->u.symlink);
return 0;
}
struct inode_operations autofs4_symlink_inode_operations = {
.readlink = generic_readlink,
.follow_link = autofs4_follow_link
};

303
fs/autofs4/waitq.c Normal file
View File

@@ -0,0 +1,303 @@
/* -*- c -*- --------------------------------------------------------------- *
*
* linux/fs/autofs/waitq.c
*
* Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved
* Copyright 2001-2003 Ian Kent <raven@themaw.net>
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License, version 2, or at your
* option, any later version, incorporated herein by reference.
*
* ------------------------------------------------------------------------- */
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/signal.h>
#include <linux/file.h>
#include "autofs_i.h"
/* We make this a static variable rather than a part of the superblock; it
is better if we don't reassign numbers easily even across filesystems */
static autofs_wqt_t autofs4_next_wait_queue = 1;
/* These are the signals we allow interrupting a pending mount */
#define SHUTDOWN_SIGS (sigmask(SIGKILL) | sigmask(SIGINT) | sigmask(SIGQUIT))
void autofs4_catatonic_mode(struct autofs_sb_info *sbi)
{
struct autofs_wait_queue *wq, *nwq;
DPRINTK("entering catatonic mode");
sbi->catatonic = 1;
wq = sbi->queues;
sbi->queues = NULL; /* Erase all wait queues */
while ( wq ) {
nwq = wq->next;
wq->status = -ENOENT; /* Magic is gone - report failure */
kfree(wq->name);
wq->name = NULL;
wake_up_interruptible(&wq->queue);
wq = nwq;
}
if (sbi->pipe) {
fput(sbi->pipe); /* Close the pipe */
sbi->pipe = NULL;
}
shrink_dcache_sb(sbi->sb);
}
static int autofs4_write(struct file *file, const void *addr, int bytes)
{
unsigned long sigpipe, flags;
mm_segment_t fs;
const char *data = (const char *)addr;
ssize_t wr = 0;
/** WARNING: this is not safe for writing more than PIPE_BUF bytes! **/
sigpipe = sigismember(&current->pending.signal, SIGPIPE);
/* Save pointer to user space and point back to kernel space */
fs = get_fs();
set_fs(KERNEL_DS);
while (bytes &&
(wr = file->f_op->write(file,data,bytes,&file->f_pos)) > 0) {
data += wr;
bytes -= wr;
}
set_fs(fs);
/* Keep the currently executing process from receiving a
SIGPIPE unless it was already supposed to get one */
if (wr == -EPIPE && !sigpipe) {
spin_lock_irqsave(&current->sighand->siglock, flags);
sigdelset(&current->pending.signal, SIGPIPE);
recalc_sigpending();
spin_unlock_irqrestore(&current->sighand->siglock, flags);
}
return (bytes > 0);
}
static void autofs4_notify_daemon(struct autofs_sb_info *sbi,
struct autofs_wait_queue *wq,
int type)
{
union autofs_packet_union pkt;
size_t pktsz;
DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d",
wq->wait_queue_token, wq->len, wq->name, type);
memset(&pkt,0,sizeof pkt); /* For security reasons */
pkt.hdr.proto_version = sbi->version;
pkt.hdr.type = type;
if (type == autofs_ptype_missing) {
struct autofs_packet_missing *mp = &pkt.missing;
pktsz = sizeof(*mp);
mp->wait_queue_token = wq->wait_queue_token;
mp->len = wq->len;
memcpy(mp->name, wq->name, wq->len);
mp->name[wq->len] = '\0';
} else if (type == autofs_ptype_expire_multi) {
struct autofs_packet_expire_multi *ep = &pkt.expire_multi;
pktsz = sizeof(*ep);
ep->wait_queue_token = wq->wait_queue_token;
ep->len = wq->len;
memcpy(ep->name, wq->name, wq->len);
ep->name[wq->len] = '\0';
} else {
printk("autofs4_notify_daemon: bad type %d!\n", type);
return;
}
if (autofs4_write(sbi->pipe, &pkt, pktsz))
autofs4_catatonic_mode(sbi);
}
static int autofs4_getpath(struct autofs_sb_info *sbi,
struct dentry *dentry, char **name)
{
struct dentry *root = sbi->sb->s_root;
struct dentry *tmp;
char *buf = *name;
char *p;
int len = 0;
spin_lock(&dcache_lock);
for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent)
len += tmp->d_name.len + 1;
if (--len > NAME_MAX) {
spin_unlock(&dcache_lock);
return 0;
}
*(buf + len) = '\0';
p = buf + len - dentry->d_name.len;
strncpy(p, dentry->d_name.name, dentry->d_name.len);
for (tmp = dentry->d_parent; tmp != root ; tmp = tmp->d_parent) {
*(--p) = '/';
p -= tmp->d_name.len;
strncpy(p, tmp->d_name.name, tmp->d_name.len);
}
spin_unlock(&dcache_lock);
return len;
}
int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry,
enum autofs_notify notify)
{
struct autofs_wait_queue *wq;
char *name;
int len, status;
/* In catatonic mode, we don't wait for nobody */
if ( sbi->catatonic )
return -ENOENT;
name = kmalloc(NAME_MAX + 1, GFP_KERNEL);
if (!name)
return -ENOMEM;
len = autofs4_getpath(sbi, dentry, &name);
if (!len) {
kfree(name);
return -ENOENT;
}
if (down_interruptible(&sbi->wq_sem)) {
kfree(name);
return -EINTR;
}
for (wq = sbi->queues ; wq ; wq = wq->next) {
if (wq->hash == dentry->d_name.hash &&
wq->len == len &&
wq->name && !memcmp(wq->name, name, len))
break;
}
if ( !wq ) {
/* Create a new wait queue */
wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL);
if ( !wq ) {
kfree(name);
up(&sbi->wq_sem);
return -ENOMEM;
}
wq->wait_queue_token = autofs4_next_wait_queue;
if (++autofs4_next_wait_queue == 0)
autofs4_next_wait_queue = 1;
wq->next = sbi->queues;
sbi->queues = wq;
init_waitqueue_head(&wq->queue);
wq->hash = dentry->d_name.hash;
wq->name = name;
wq->len = len;
wq->status = -EINTR; /* Status return if interrupted */
atomic_set(&wq->wait_ctr, 2);
up(&sbi->wq_sem);
DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d",
(unsigned long) wq->wait_queue_token, wq->len, wq->name, notify);
/* autofs4_notify_daemon() may block */
if (notify != NFY_NONE) {
autofs4_notify_daemon(sbi,wq,
notify == NFY_MOUNT ?
autofs_ptype_missing :
autofs_ptype_expire_multi);
}
} else {
atomic_inc(&wq->wait_ctr);
up(&sbi->wq_sem);
kfree(name);
DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d",
(unsigned long) wq->wait_queue_token, wq->len, wq->name, notify);
}
/* wq->name is NULL if and only if the lock is already released */
if ( sbi->catatonic ) {
/* We might have slept, so check again for catatonic mode */
wq->status = -ENOENT;
if ( wq->name ) {
kfree(wq->name);
wq->name = NULL;
}
}
if ( wq->name ) {
/* Block all but "shutdown" signals while waiting */
sigset_t oldset;
unsigned long irqflags;
spin_lock_irqsave(&current->sighand->siglock, irqflags);
oldset = current->blocked;
siginitsetinv(&current->blocked, SHUTDOWN_SIGS & ~oldset.sig[0]);
recalc_sigpending();
spin_unlock_irqrestore(&current->sighand->siglock, irqflags);
wait_event_interruptible(wq->queue, wq->name == NULL);
spin_lock_irqsave(&current->sighand->siglock, irqflags);
current->blocked = oldset;
recalc_sigpending();
spin_unlock_irqrestore(&current->sighand->siglock, irqflags);
} else {
DPRINTK("skipped sleeping");
}
status = wq->status;
/* Are we the last process to need status? */
if (atomic_dec_and_test(&wq->wait_ctr))
kfree(wq);
return status;
}
int autofs4_wait_release(struct autofs_sb_info *sbi, autofs_wqt_t wait_queue_token, int status)
{
struct autofs_wait_queue *wq, **wql;
down(&sbi->wq_sem);
for ( wql = &sbi->queues ; (wq = *wql) != 0 ; wql = &wq->next ) {
if ( wq->wait_queue_token == wait_queue_token )
break;
}
if ( !wq ) {
up(&sbi->wq_sem);
return -EINVAL;
}
*wql = wq->next; /* Unlink from chain */
up(&sbi->wq_sem);
kfree(wq->name);
wq->name = NULL; /* Do not wait on this queue */
wq->status = status;
if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */
kfree(wq);
else
wake_up_interruptible(&wq->queue);
return 0;
}

123
fs/bad_inode.c Normal file
View File

@@ -0,0 +1,123 @@
/*
* linux/fs/bad_inode.c
*
* Copyright (C) 1997, Stephen Tweedie
*
* Provide stub functions for unreadable inodes
*
* Fabian Frederick : August 2003 - All file operations assigned to EIO
*/
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/time.h>
#include <linux/smp_lock.h>
#include <linux/namei.h>
static int return_EIO(void)
{
return -EIO;
}
#define EIO_ERROR ((void *) (return_EIO))
static struct file_operations bad_file_ops =
{
.llseek = EIO_ERROR,
.aio_read = EIO_ERROR,
.read = EIO_ERROR,
.write = EIO_ERROR,
.aio_write = EIO_ERROR,
.readdir = EIO_ERROR,
.poll = EIO_ERROR,
.ioctl = EIO_ERROR,
.mmap = EIO_ERROR,
.open = EIO_ERROR,
.flush = EIO_ERROR,
.release = EIO_ERROR,
.fsync = EIO_ERROR,
.aio_fsync = EIO_ERROR,
.fasync = EIO_ERROR,
.lock = EIO_ERROR,
.readv = EIO_ERROR,
.writev = EIO_ERROR,
.sendfile = EIO_ERROR,
.sendpage = EIO_ERROR,
.get_unmapped_area = EIO_ERROR,
};
struct inode_operations bad_inode_ops =
{
.create = EIO_ERROR,
.lookup = EIO_ERROR,
.link = EIO_ERROR,
.unlink = EIO_ERROR,
.symlink = EIO_ERROR,
.mkdir = EIO_ERROR,
.rmdir = EIO_ERROR,
.mknod = EIO_ERROR,
.rename = EIO_ERROR,
.readlink = EIO_ERROR,
/* follow_link must be no-op, otherwise unmounting this inode
won't work */
.truncate = EIO_ERROR,
.permission = EIO_ERROR,
.getattr = EIO_ERROR,
.setattr = EIO_ERROR,
.setxattr = EIO_ERROR,
.getxattr = EIO_ERROR,
.listxattr = EIO_ERROR,
.removexattr = EIO_ERROR,
};
/*
* When a filesystem is unable to read an inode due to an I/O error in
* its read_inode() function, it can call make_bad_inode() to return a
* set of stubs which will return EIO errors as required.
*
* We only need to do limited initialisation: all other fields are
* preinitialised to zero automatically.
*/
/**
* make_bad_inode - mark an inode bad due to an I/O error
* @inode: Inode to mark bad
*
* When an inode cannot be read due to a media or remote network
* failure this function makes the inode "bad" and causes I/O operations
* on it to fail from this point on.
*/
void make_bad_inode(struct inode * inode)
{
remove_inode_hash(inode);
inode->i_mode = S_IFREG;
inode->i_atime = inode->i_mtime = inode->i_ctime =
current_fs_time(inode->i_sb);
inode->i_op = &bad_inode_ops;
inode->i_fop = &bad_file_ops;
}
EXPORT_SYMBOL(make_bad_inode);
/*
* This tests whether an inode has been flagged as bad. The test uses
* &bad_inode_ops to cover the case of invalidated inodes as well as
* those created by make_bad_inode() above.
*/
/**
* is_bad_inode - is an inode errored
* @inode: inode to test
*
* Returns true if the inode in question has been marked as bad.
*/
int is_bad_inode(struct inode * inode)
{
return (inode->i_op == &bad_inode_ops);
}
EXPORT_SYMBOL(is_bad_inode);

417
fs/befs/ChangeLog Normal file
View File

@@ -0,0 +1,417 @@
Version 0.92 (2002-03-29)
==========
* Minor cleanup. Ran Lindent on the sources.
Version 0.92 (2002-03-27)
==========
* Fixed module makefile problem. It was not compiling all the correct
source files!
* Removed duplicated function definition
* Fixed potential null pointer dereference when reporting an error
Version 0.91 (2002-03-26)
==========
* Oy! Fixed stupid bug that would cause an unresolved symbol error.
Thanks to Laszlo Boszormenyi for pointing this out to me.
Version 0.9 (2002-03-14)
==========
* Added Sergey S. Kostyliov's patch to eliminate memcpy() overhead
from b+tree operations. Changes the befs_read_datastream() interface.
* Segregated the functions that interface directly with the linux vfs
interface into their own file called linuxvfs.c. [WD]
Version 0.64 (2002-02-07)
==========
* Did the string comparision really right this time (btree.c) [WD]
* Fixed up some places where I assumed that a long int could hold
a pointer value. (btree.c) [WD]
* Andrew Farnham <andrewfarnham@uq.net.au> pointed out that the module
wouldn't work on older (<2.4.10) kernels due to an unresolved symbol.
This is bad, since 2.4.9 is still the current RedHat kernel. I added
a workaround for this problem (compatibility.h) [WD]
* Sergey S. Kostyliov made befs_find_key() use a binary search to find
keys within btree nodes, rather than the linear search we were using
before. (btree.c) [Sergey S. Kostyliov <rathamahata@php4.ru>]
* Made a debian package of the source for use with kernel-package. [WD]
Version 0.63 (2002-01-31)
==========
* Fixed bug in befs_find_brun_indirect() that would result in the wrong
block being read. It was introduced when adding byteswapping in
0.61. (datastream.c) [WD]
* Fixed a longstanding bug in befs_find_key() that would result in it
finding the first key that is a substring of the string it is searching
for. For example, this would cause files in the same directory with
names like file1 and file2 to mysteriously be duplicates of each other
(because they have the same inode number). Many thanks to Pavel Roskin
for reporting this serious bug!!!
(btree.c) [WD]
* Added support for long symlinks, after Axel Dorfler explained up how
they work. I had forgotten all about them. (inode.c, symlink.c) [WD]
* Documentation improvements in source. [WD]
* Makefile fix for independent module when CONFIG_MODVERSION is set in
kernel config [Pavel Roskin <proski@gnu.org>]
* Compile warning fix for namei.c. [Sergey S. Kostyliov <rathamahata@php4.ru>]
Version 0.62
==========
* Fixed makefile for module install [WD]
Version 0.61 (2002-01-20)
==========
* Made functions in endian.h to do the correct byteswapping, no matter
the arch. [WD]
* Abbandoned silly checks for a NULL superblock pointer in debug.c. [WD]
* Misc code cleanups. Also cleanup of this changelog file. [WD]
* Added byteswapping to all metadata reads from disk.
Uses the functions from endian.h [WD]
* Remove the typedef of struct super_block to vfs_sb, as it offended
certain peoples' aesthetic sense. [WD]
* Ditto with the befs_read_block() interface. [WD]
Version 0.6 (2001-12-15)
==========
* Cleanup of NLS functions (util.c) [WD]
* Make directory lookup/read use the NLS if an iocharset is provided. [WD]
* Fixed stupid bug where specifying the uid or gid mount options as '0'
would result in the filesystem using the on-disk uid and gid. [WD]
* Added mount option to control debug printing.
The option is, simply enough, 'debug'.
(super.c, debug.c) [WD]
* Removed notion of btree handle from btree.c. It was unnecessary, as the
linux VFS doesn't allow us to keep any state between calls. Updated
dir.c, namei.c befs_fs.h to account for it. [WD]
* Improved handleing of overflow nodes when listing directories.
Now works for overflow nodes hanging off of nodes other than the root
node. This is the cleaner solution to Brent Miszalaski's problem. [WD]
* Added new debug/warning/error print functions in debug.c.
More flexible. Will soon be controllable at mount time
(see TODO). [WD]
* Rewrote datastream positon lookups.
(datastream.c) [WD]
* Moved the TODO list to its own file.
Version 0.50 (2001-11-13)
==========
* Added workaround for mis-understanding of the nature of the b+trees used
in directories. A cleaner solution will come after I've thought about it
for a while. Thanks to Brent Miszalaski for finding and reporting this bug.
(btree.c) [WD]
* Minor cleanups
* Added test for "impossible" condition of empty internal nodes in
seekleaf() in btree.c [WD]
* Implemented the abstracted read_block() in io.c [WD]
* Cleaned up the inode validation in inode.c [WD]
* Anton Altaparmakov figured out (by asking Linus :) ) what was causing the
hanging disk io problem. It turns out you need to have the sync_pages
callback defined in your address_space_ops, even if it just uses the
default linux-supplied implementation. Fixed. Works now.
(file.c) [WD]
* Anton Altaparmakov and Christoph Hellwig alerted me to the fact that
filesystem code should be using GFP_NOFS instead of GFP_KERNEL as the
priority parameter to kmalloc(). Fixed.
(datastream.c, btree.c super.c inode.c) [WD]
* Anton also told me that the blocksize is not allowed to be larger than
the page size in linux, which is 4k i386. Oops. Added a test for
(blocksize > PAGE_SIZE), and refuse to mount in that case. What this
practicaly means is that 8k blocksize volumes won't work without a major
restructuring of the driver (or an alpha or other 64bit hardware). [WD]
* Cleaned up the befs_count_blocks() function. Much smarter now.
And somewhat smaller too. [WD]
* Made inode allocations use a slab cache
(super.c inode.c) [WD]
* Moved the freeing of the private inode section from put_inode() to
clear_inode(). This fixes a potential free twice type bug. Put_inode()
can be called multiple times for each inode struct. [WD]
* Converted all non vfs-callback functions to use befs_sb_info as the
superblock type, rather than struct super_block. This is for
portablity. [WD]
* Fixed a couple of compile warnings due to use of malloc.h, when slab.h
is the new way. (inode.c, super.c) [WD]
* Fixed erronous includes of linux/befs_fs_i.h and linux/befs_fs_sb.h
in inode.c [WD]
Version 0.45 (2001-10-29)
==========
* Added functions to get the private superblock and inode structures from
their enclosing public structures. Switched all references to the
private portions to use them. (many files) [WD]
* Made read_super and read_inode allocate the private portions of those
structures into the generic pointer fields of the public structures
with kmalloc(). put_super and put_inode free them. This allows us not
to have to touch the definitions of the public structures in
include/linux/fs.h. Also, befs_inode_info is huge (becuase of the
symlink string). (super.c, inode.c, befs_fs.h) [WD]
* Fixed a thinko that was corrupting file reads after the first block_run
is done being read. (datastream.c) [WD]
* Removed fsync() hooks, since a read-only filesystem doesn't need them.
[Christoph Hellwig].
* Fixed befs_readlink() (symlink.c) [Christoph Hellwig].
* Removed all the Read-Write stuff. I'll redo it when it is time to add
write support (various files) [WD].
* Removed prototypes for functions who's definitions have been removed
(befs_fs.h) [WD].
Version 0.4 (2001-10-28)
==========
* Made it an option to use the old non-pagecache befs_file_read() for
testing purposes. (fs/Config.in)
* Fixed unused variable warnings when compiling without debugging.
* Fixed a bug where the inode and super_block didn't get their blockbits
fields set (inode.c and super.c).
* Release patch version 11. AKA befs-driver version 0.4.
* Thats right. New versioning scheme.
I've done some serious testing on it now (on my box anyhow), and it
seems stable and not outragously slow. Existing features are more-or-less
correct (see TODO list). But it isn't 1.0 yet. I think 0.4 gives me some
headroom before the big 1.0.
2001-10-26
==========
* Fixed date format in this file. Was I smoking crack?
* Removed old datastream code from file.c, since it is nolonger used.
* Generic_read_file() is now used to read regular file data.
It doesn't chew up the buffer cache (it does page io instead), and seems
to be about as fast (even though it has to look up each file block
indivdualy). And it knows about doing readahead, which is a major plus.
So it does i/o in much larger chunks. It is the correct linux way. It
uses befs_get_block() by way of befs_readpage() to find the disk offsets
of blocks, which in turn calls befs_fpos2brun() in datastream.c to do
the hard work of finding the disk block number.
* Changed method of checking for a dirty filesystem in befs_read_super
(super.c). Now we check to see if log_start and log_end differ. If so,
the journal needs to be replayed, and the filesystem cannot be mounted.
* Fixed an extra instance of MOD_DEC_USE_COUNT in super.c
* Fixed a problem with reading the superblock on devices with large sector
sizes (such as cdroms) on linux 2.4.10 and up.
2001-10-24
==========
* Fix nasty bug in converting block numbers to struct befs_inode_addr.
Subtle, because the old version was only sometimes wrong.
Probably responsible for lots of problems. (inode.c)
* Fix bug with reading an empty directory. (btree.c and dir.c)
* This one looks good. Release patch version 10
2001-10-23
==========
* Added btree searching function.
* Use befs_btree_find in befs_lookup (namei.c)
* Additional comments in btree.c
2001-10-22
==========
* Added B+tree reading functions (in btree.c).
Made befs_readdir() use them them instead of the cruft in index.c.
2001-09-11
==========
* Converted befs_read_file() to use the new datastream code.
* Finally updated the README file.
* Added many comments.
* Posted version 6
* Removed byte-order conversion code.
I have no intention of supporting it, and it was very ugly.
Flow control with #ifdef (ugh). Maybe I'll redo it once
native byteorder works 100%.
2001-09-10
==========
* Finished implementing read_datastream()
* made befs_read_brun() more general
Supports an offset to start at and a max bytes to read
Added a wrapper function to give the old call
2001-09-30
==========
* Discovered that the datastream handleing code in file.c is quite deficient
in several respects. For one thing, it doesn't deal with indirect blocks
* Rewrote datastream handleing.
* Created io.c, for io related functions.
Previously, the befs_bread() funtions lived in file.c
Created the befs_read_brun() function.
2001-09-07
==========
* Made a function to actually count the number of fs blocks used by a file.
And helper functions.
(fs/befs/inode.c)
2001-09-05
==========
* Fixed a misunderstanding of the inode fields.
This fixed the problmem with wrong file sizes from du and others.
The i_blocks field of the inode struct is not the number of blocks for the
inode, it is the number of blocks for the file. Also, i_blksize is not
necessarily the size of the inode, although in practice it works out.
Changed to blocksize of filesystem.
(fs/befs/inode.c)
* Permanently removed code that had been provisionally ifdefed out of befs_fs.h
* Since we don't support access time, make that field zero, instead of
copying m_time.
(fs/befs/inode.c)
* Added sanity check for inode reading
Make sure inode we got was the one we asked for.
(fs/befs/inode.c)
* Code cleanup
Local pointers to commonly used structures in inode.c.
Got rid of abominations befs_iaddr2inode() and befs_inode2ino().
Replaced with single function iaddr2blockno().
(fs/befs/super.c) (fs/befs/inode.c)
2001-09-01
==========
* Fixed the problem with statfs where it would always claim the disk was
half full, due to improper understanding of the statfs fields.
(fs/befs/super.c)
* Posted verion 4 of the patch
2001-09-01
==========
* Changed the macros in befs_fs.h to inline functions.
More readable. Typesafe. Better
(include/linux/befs_fs.h)
* Moved type definitions from befs_fs.h to a new file, befs_fs_types.h
Because befs_fs_i.h and befs_fs_sb.h were including befs_fs.h for the
typedefs, and they are inlcuded in <linux/fs.h>, which has definitions
that I want the inline functions in befs_fs.h to be able to see. Nasty
circularity.
(include/linux/befs_fs.h)
2001-08-30
==========
* Cleaned up some wording.
* Added additional consitency checks on mount
Check block_size agrees with block_shift
Check flags == BEFS_CLEAN
(fs/befs/super.c)
* Tell the kernel to only mount befs read-only.
By setting the MS_RDONLY flag in befs_read_super().
Not that it was possible to write before. But now the kernel won't even try.
(fs/befs/super.c)
* Got rid of kernel warning on mount.
The kernel doesn't like it if you call set_blocksize() on a device when
you have some of its blocks open. Moved the second set_blocksize() to the
very end of befs_read_super(), after we are done with the disk superblock.
(fs/befs/super.c)
* Fixed wrong number of args bug in befs_dump_inode
(fs/befs/debug.c)
* Solved lots of type mismatches in kprint()s
(everwhere)
2001-08-27
==========
* Cleaned up the fs/Config.in entries a bit, now slightly more descriptive.
* BeFS depends on NLS, so I made activating BeFS enable the NLS questions
(fs/nls/Config.in)
* Added Configure.help entries for CONFIG_BEFS_FS and CONFIG_DEBUG_BEFS
(Documentation/Configure.help)
2001-08-??
==========
* Removed superblock locking calls in befs_read_super(). In 2.4, the VFS
hands us a super_block struct that is already locked.
2001-08-13
==========
* Will Dyson <will_dyson@pobox.com> is now attempting to maintain this module
Makoto Kato <m_kato@ga2.so-net.ne.jp> is original author.Daniel Berlin
also did some work on it (fixing it up for the later 2.3.x kernels, IIRC).
* Fixed compile errors on 2.4.1 kernel (WD)
Resolve rejected patches
Accomodate changed NLS interface (util.h)
Needed to include <linux/slab.h> in most files
Makefile changes
fs/Config.in changes
* Tried to niceify the code using the ext2 fs as a guide
Declare befs_fs_type using the DECLARE_FSTYPE_DEV() macro
* Made it a configure option to turn on debugging (fs/Config.in)
* Compiles on 2.4.7

7
fs/befs/Makefile Normal file
View File

@@ -0,0 +1,7 @@
#
# Makefile for the linux BeOS filesystem routines.
#
obj-$(CONFIG_BEFS_FS) += befs.o
befs-objs := datastream.o btree.o super.o inode.o debug.o io.o linuxvfs.o

14
fs/befs/TODO Normal file
View File

@@ -0,0 +1,14 @@
TODO
==========
* Convert comments to the Kernel-Doc format.
* Befs_fs.h has gotten big and messy. No reason not to break it up into
smaller peices.
* See if Alexander Viro's option parser made it into the kernel tree.
Use that if we can. (include/linux/parser.h)
* See if we really need separate types for on-disk and in-memory
representations of the superblock and inode.

117
fs/befs/attribute.c Normal file
View File

@@ -0,0 +1,117 @@
/*
* linux/fs/befs/attribute.c
*
* Copyright (C) 2002 Will Dyson <will_dyson@pobox.com>
*
* Many thanks to Dominic Giampaolo, author of "Practical File System
* Design with the Be File System", for such a helpful book.
*
*/
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include "befs.h"
#include "endian.h"
#define SD_DATA(sd)\
(void*)((char*)sd + sizeof(*sd) + (sd->name_size - sizeof(sd->name)))
#define SD_NEXT(sd)\
(befs_small_data*)((char*)sd + sizeof(*sd) + (sd->name_size - \
sizeof(sd->name) + sd->data_size))
int
list_small_data(struct super_block *sb, befs_inode * inode, filldir_t filldir);
befs_small_data *
find_small_data(struct super_block *sb, befs_inode * inode,
const char *name);
int
read_small_data(struct super_block *sb, befs_inode * inode,
befs_small_data * sdata, void *buf, size_t bufsize);
/**
*
*
*
*
*
*/
befs_small_data *
find_small_data(struct super_block *sb, befs_inode * inode, const char *name)
{
befs_small_data *sdata = inode->small_data;
while (sdata->type != 0) {
if (strcmp(name, sdata->name) != 0) {
return sdata;
}
sdata = SD_NEXT(sdata);
}
return NULL;
}
/**
*
*
*
*
*
*/
int
read_small_data(struct super_block *sb, befs_inode * inode,
const char *name, void *buf, size_t bufsize)
{
befs_small_data *sdata;
sdata = find_small_data(sb, inode, name);
if (sdata == NULL)
return BEFS_ERR;
else if (sdata->data_size > bufsize)
return BEFS_ERR;
memcpy(buf, SD_DATA(sdata), sdata->data_size);
return BEFS_OK;
}
/**
*
*
*
*
*
*/
int
list_small_data(struct super_block *sb, befs_inode * inode)
{
}
/**
*
*
*
*
*
*/
int
list_attr(struct super_block *sb, befs_inode * inode)
{
}
/**
*
*
*
*
*
*/
int
read_attr(struct super_block *sb, befs_inode * inode)
{
}

154
fs/befs/befs.h Normal file
View File

@@ -0,0 +1,154 @@
/*
* befs.h
*
* Copyright (C) 2001-2002 Will Dyson <will_dyson@pobox.com>
* Copyright (C) 1999 Makoto Kato (m_kato@ga2.so-net.ne.jp)
*/
#ifndef _LINUX_BEFS_H
#define _LINUX_BEFS_H
#include "befs_fs_types.h"
/* used in debug.c */
#define BEFS_VERSION "0.9.3"
typedef u64 befs_blocknr_t;
/*
* BeFS in memory structures
*/
typedef struct befs_mount_options {
gid_t gid;
uid_t uid;
int use_gid;
int use_uid;
int debug;
char *iocharset;
} befs_mount_options;
typedef struct befs_sb_info {
u32 magic1;
u32 block_size;
u32 block_shift;
int byte_order;
befs_off_t num_blocks;
befs_off_t used_blocks;
u32 inode_size;
u32 magic2;
/* Allocation group information */
u32 blocks_per_ag;
u32 ag_shift;
u32 num_ags;
/* jornal log entry */
befs_block_run log_blocks;
befs_off_t log_start;
befs_off_t log_end;
befs_inode_addr root_dir;
befs_inode_addr indices;
u32 magic3;
befs_mount_options mount_opts;
struct nls_table *nls;
} befs_sb_info;
typedef struct befs_inode_info {
u32 i_flags;
u32 i_type;
befs_inode_addr i_inode_num;
befs_inode_addr i_parent;
befs_inode_addr i_attribute;
union {
befs_data_stream ds;
char symlink[BEFS_SYMLINK_LEN];
} i_data;
struct inode vfs_inode;
} befs_inode_info;
enum befs_err {
BEFS_OK,
BEFS_ERR,
BEFS_BAD_INODE,
BEFS_BT_END,
BEFS_BT_EMPTY,
BEFS_BT_MATCH,
BEFS_BT_PARMATCH,
BEFS_BT_NOT_FOUND
};
/****************************/
/* debug.c */
void befs_error(const struct super_block *sb, const char *fmt, ...);
void befs_warning(const struct super_block *sb, const char *fmt, ...);
void befs_debug(const struct super_block *sb, const char *fmt, ...);
void befs_dump_super_block(const struct super_block *sb, befs_super_block *);
void befs_dump_inode(const struct super_block *sb, befs_inode *);
void befs_dump_index_entry(const struct super_block *sb, befs_btree_super *);
void befs_dump_index_node(const struct super_block *sb, befs_btree_nodehead *);
/****************************/
/* Gets a pointer to the private portion of the super_block
* structure from the public part
*/
static inline befs_sb_info *
BEFS_SB(const struct super_block *super)
{
return (befs_sb_info *) super->s_fs_info;
}
static inline befs_inode_info *
BEFS_I(const struct inode *inode)
{
return list_entry(inode, struct befs_inode_info, vfs_inode);
}
static inline befs_blocknr_t
iaddr2blockno(struct super_block *sb, befs_inode_addr * iaddr)
{
return ((iaddr->allocation_group << BEFS_SB(sb)->ag_shift) +
iaddr->start);
}
static inline befs_inode_addr
blockno2iaddr(struct super_block *sb, befs_blocknr_t blockno)
{
befs_inode_addr iaddr;
iaddr.allocation_group = blockno >> BEFS_SB(sb)->ag_shift;
iaddr.start =
blockno - (iaddr.allocation_group << BEFS_SB(sb)->ag_shift);
iaddr.len = 1;
return iaddr;
}
static inline unsigned int
befs_iaddrs_per_block(struct super_block *sb)
{
return BEFS_SB(sb)->block_size / sizeof (befs_inode_addr);
}
static inline int
befs_iaddr_is_empty(befs_inode_addr * iaddr)
{
return (!iaddr->allocation_group) && (!iaddr->start) && (!iaddr->len);
}
static inline size_t
befs_brun_size(struct super_block *sb, befs_block_run run)
{
return BEFS_SB(sb)->block_size * run.len;
}
#endif /* _LINUX_BEFS_H */

213
fs/befs/befs_fs_types.h Normal file
View File

@@ -0,0 +1,213 @@
/*
* include/linux/befs_fs_types.h
*
* Copyright (C) 2001 Will Dyson (will@cs.earlham.edu)
*
*
*
* from linux/include/linux/befs_fs.h
*
* Copyright (C) 1999 Makoto Kato (m_kato@ga2.so-net.ne.jp)
*
*/
#ifndef _LINUX_BEFS_FS_TYPES
#define _LINUX_BEFS_FS_TYPES
#ifdef __KERNEL__
#include <linux/types.h>
#endif /*__KERNEL__*/
#define PACKED __attribute__ ((__packed__))
/*
* Max name lengths of BFS
*/
#define BEFS_NAME_LEN 255
#define BEFS_SYMLINK_LEN 144
#define BEFS_NUM_DIRECT_BLOCKS 12
#define B_OS_NAME_LENGTH 32
/* The datastream blocks mapped by the double-indirect
* block are always 4 fs blocks long.
* This eliminates the need for linear searches among
* the potentially huge number of indirect blocks
*
* Err. Should that be 4 fs blocks or 4k???
* It matters on large blocksize volumes
*/
#define BEFS_DBLINDIR_BRUN_LEN 4
/*
* Flags of superblock
*/
enum super_flags {
BEFS_BYTESEX_BE,
BEFS_BYTESEX_LE,
BEFS_CLEAN = 0x434c454e,
BEFS_DIRTY = 0x44495254,
BEFS_SUPER_MAGIC1 = 0x42465331, /* BFS1 */
BEFS_SUPER_MAGIC2 = 0xdd121031,
BEFS_SUPER_MAGIC3 = 0x15b6830e,
};
#define BEFS_BYTEORDER_NATIVE 0x42494745
#define BEFS_SUPER_MAGIC BEFS_SUPER_MAGIC1
/*
* Flags of inode
*/
#define BEFS_INODE_MAGIC1 0x3bbe0ad9
enum inode_flags {
BEFS_INODE_IN_USE = 0x00000001,
BEFS_ATTR_INODE = 0x00000004,
BEFS_INODE_LOGGED = 0x00000008,
BEFS_INODE_DELETED = 0x00000010,
BEFS_LONG_SYMLINK = 0x00000040,
BEFS_PERMANENT_FLAG = 0x0000ffff,
BEFS_INODE_NO_CREATE = 0x00010000,
BEFS_INODE_WAS_WRITTEN = 0x00020000,
BEFS_NO_TRANSACTION = 0x00040000,
};
/*
* On-Disk datastructures of BeFS
*/
typedef u64 befs_off_t;
typedef u64 befs_time_t;
typedef void befs_binode_etc;
/* Block runs */
typedef struct {
u32 allocation_group;
u16 start;
u16 len;
} PACKED befs_block_run;
typedef befs_block_run befs_inode_addr;
/*
* The Superblock Structure
*/
typedef struct {
char name[B_OS_NAME_LENGTH];
u32 magic1;
u32 fs_byte_order;
u32 block_size;
u32 block_shift;
befs_off_t num_blocks;
befs_off_t used_blocks;
u32 inode_size;
u32 magic2;
u32 blocks_per_ag;
u32 ag_shift;
u32 num_ags;
u32 flags;
befs_block_run log_blocks;
befs_off_t log_start;
befs_off_t log_end;
u32 magic3;
befs_inode_addr root_dir;
befs_inode_addr indices;
} PACKED befs_super_block;
/*
* Note: the indirect and dbl_indir block_runs may
* be longer than one block!
*/
typedef struct {
befs_block_run direct[BEFS_NUM_DIRECT_BLOCKS];
befs_off_t max_direct_range;
befs_block_run indirect;
befs_off_t max_indirect_range;
befs_block_run double_indirect;
befs_off_t max_double_indirect_range;
befs_off_t size;
} PACKED befs_data_stream;
/* Attribute */
typedef struct {
u32 type;
u16 name_size;
u16 data_size;
char name[1];
} PACKED befs_small_data;
/* Inode structure */
typedef struct {
u32 magic1;
befs_inode_addr inode_num;
u32 uid;
u32 gid;
u32 mode;
u32 flags;
befs_time_t create_time;
befs_time_t last_modified_time;
befs_inode_addr parent;
befs_inode_addr attributes;
u32 type;
u32 inode_size;
u32 etc; /* not use */
union {
befs_data_stream datastream;
char symlink[BEFS_SYMLINK_LEN];
} data;
u32 pad[4]; /* not use */
befs_small_data small_data[1];
} PACKED befs_inode;
/*
* B+tree superblock
*/
#define BEFS_BTREE_MAGIC 0x69f6c2e8
enum btree_types {
BTREE_STRING_TYPE = 0,
BTREE_INT32_TYPE = 1,
BTREE_UINT32_TYPE = 2,
BTREE_INT64_TYPE = 3,
BTREE_UINT64_TYPE = 4,
BTREE_FLOAT_TYPE = 5,
BTREE_DOUBLE_TYPE = 6
};
typedef struct {
u32 magic;
u32 node_size;
u32 max_depth;
u32 data_type;
befs_off_t root_node_ptr;
befs_off_t free_node_ptr;
befs_off_t max_size;
} PACKED befs_btree_super;
/*
* Header stucture of each btree node
*/
typedef struct {
befs_off_t left;
befs_off_t right;
befs_off_t overflow;
u16 all_key_count;
u16 all_key_length;
} PACKED befs_btree_nodehead;
#endif /* _LINUX_BEFS_FS_TYPES */

788
fs/befs/btree.c Normal file
View File

@@ -0,0 +1,788 @@
/*
* linux/fs/befs/btree.c
*
* Copyright (C) 2001-2002 Will Dyson <will_dyson@pobox.com>
*
* Licensed under the GNU GPL. See the file COPYING for details.
*
* 2002-02-05: Sergey S. Kostyliov added binary search withing
* btree nodes.
*
* Many thanks to:
*
* Dominic Giampaolo, author of "Practical File System
* Design with the Be File System", for such a helpful book.
*
* Marcus J. Ranum, author of the b+tree package in
* comp.sources.misc volume 10. This code is not copied from that
* work, but it is partially based on it.
*
* Makoto Kato, author of the original BeFS for linux filesystem
* driver.
*/
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/buffer_head.h>
#include "befs.h"
#include "btree.h"
#include "datastream.h"
#include "endian.h"
/*
* The btree functions in this file are built on top of the
* datastream.c interface, which is in turn built on top of the
* io.c interface.
*/
/* Befs B+tree structure:
*
* The first thing in the tree is the tree superblock. It tells you
* all kinds of useful things about the tree, like where the rootnode
* is located, and the size of the nodes (always 1024 with current version
* of BeOS).
*
* The rest of the tree consists of a series of nodes. Nodes contain a header
* (struct befs_btree_nodehead), the packed key data, an array of shorts
* containing the ending offsets for each of the keys, and an array of
* befs_off_t values. In interior nodes, the keys are the ending keys for
* the childnode they point to, and the values are offsets into the
* datastream containing the tree.
*/
/* Note:
*
* The book states 2 confusing things about befs b+trees. First,
* it states that the overflow field of node headers is used by internal nodes
* to point to another node that "effectively continues this one". Here is what
* I believe that means. Each key in internal nodes points to another node that
* contains key values less than itself. Inspection reveals that the last key
* in the internal node is not the last key in the index. Keys that are
* greater than the last key in the internal node go into the overflow node.
* I imagine there is a performance reason for this.
*
* Second, it states that the header of a btree node is sufficient to
* distinguish internal nodes from leaf nodes. Without saying exactly how.
* After figuring out the first, it becomes obvious that internal nodes have
* overflow nodes and leafnodes do not.
*/
/*
* Currently, this code is only good for directory B+trees.
* In order to be used for other BFS indexes, it needs to be extended to handle
* duplicate keys and non-string keytypes (int32, int64, float, double).
*/
/*
* In memory structure of each btree node
*/
typedef struct {
befs_btree_nodehead head; /* head of node converted to cpu byteorder */
struct buffer_head *bh;
befs_btree_nodehead *od_node; /* on disk node */
} befs_btree_node;
/* local constants */
static const befs_off_t befs_bt_inval = 0xffffffffffffffffULL;
/* local functions */
static int befs_btree_seekleaf(struct super_block *sb, befs_data_stream * ds,
befs_btree_super * bt_super,
befs_btree_node * this_node,
befs_off_t * node_off);
static int befs_bt_read_super(struct super_block *sb, befs_data_stream * ds,
befs_btree_super * sup);
static int befs_bt_read_node(struct super_block *sb, befs_data_stream * ds,
befs_btree_node * node, befs_off_t node_off);
static int befs_leafnode(befs_btree_node * node);
static u16 *befs_bt_keylen_index(befs_btree_node * node);
static befs_off_t *befs_bt_valarray(befs_btree_node * node);
static char *befs_bt_keydata(befs_btree_node * node);
static int befs_find_key(struct super_block *sb, befs_btree_node * node,
const char *findkey, befs_off_t * value);
static char *befs_bt_get_key(struct super_block *sb, befs_btree_node * node,
int index, u16 * keylen);
static int befs_compare_strings(const void *key1, int keylen1,
const void *key2, int keylen2);
/**
* befs_bt_read_super - read in btree superblock convert to cpu byteorder
* @sb: Filesystem superblock
* @ds: Datastream to read from
* @sup: Buffer in which to place the btree superblock
*
* Calls befs_read_datastream to read in the btree superblock and
* makes sure it is in cpu byteorder, byteswapping if necessary.
*
* On success, returns BEFS_OK and *@sup contains the btree superblock,
* in cpu byte order.
*
* On failure, BEFS_ERR is returned.
*/
static int
befs_bt_read_super(struct super_block *sb, befs_data_stream * ds,
befs_btree_super * sup)
{
struct buffer_head *bh = NULL;
befs_btree_super *od_sup = NULL;
befs_debug(sb, "---> befs_btree_read_super()");
bh = befs_read_datastream(sb, ds, 0, NULL);
if (!bh) {
befs_error(sb, "Couldn't read index header.");
goto error;
}
od_sup = (befs_btree_super *) bh->b_data;
befs_dump_index_entry(sb, od_sup);
sup->magic = fs32_to_cpu(sb, od_sup->magic);
sup->node_size = fs32_to_cpu(sb, od_sup->node_size);
sup->max_depth = fs32_to_cpu(sb, od_sup->max_depth);
sup->data_type = fs32_to_cpu(sb, od_sup->data_type);
sup->root_node_ptr = fs64_to_cpu(sb, od_sup->root_node_ptr);
sup->free_node_ptr = fs64_to_cpu(sb, od_sup->free_node_ptr);
sup->max_size = fs64_to_cpu(sb, od_sup->max_size);
brelse(bh);
if (sup->magic != BEFS_BTREE_MAGIC) {
befs_error(sb, "Index header has bad magic.");
goto error;
}
befs_debug(sb, "<--- befs_btree_read_super()");
return BEFS_OK;
error:
befs_debug(sb, "<--- befs_btree_read_super() ERROR");
return BEFS_ERR;
}
/**
* befs_bt_read_node - read in btree node and convert to cpu byteorder
* @sb: Filesystem superblock
* @ds: Datastream to read from
* @node: Buffer in which to place the btree node
* @node_off: Starting offset (in bytes) of the node in @ds
*
* Calls befs_read_datastream to read in the indicated btree node and
* makes sure its header fields are in cpu byteorder, byteswapping if
* necessary.
* Note: node->bh must be NULL when this function called first
* time. Don't forget brelse(node->bh) after last call.
*
* On success, returns BEFS_OK and *@node contains the btree node that
* starts at @node_off, with the node->head fields in cpu byte order.
*
* On failure, BEFS_ERR is returned.
*/
static int
befs_bt_read_node(struct super_block *sb, befs_data_stream * ds,
befs_btree_node * node, befs_off_t node_off)
{
uint off = 0;
befs_debug(sb, "---> befs_bt_read_node()");
if (node->bh)
brelse(node->bh);
node->bh = befs_read_datastream(sb, ds, node_off, &off);
if (!node->bh) {
befs_error(sb, "befs_bt_read_node() failed to read "
"node at %Lu", node_off);
befs_debug(sb, "<--- befs_bt_read_node() ERROR");
return BEFS_ERR;
}
node->od_node =
(befs_btree_nodehead *) ((void *) node->bh->b_data + off);
befs_dump_index_node(sb, node->od_node);
node->head.left = fs64_to_cpu(sb, node->od_node->left);
node->head.right = fs64_to_cpu(sb, node->od_node->right);
node->head.overflow = fs64_to_cpu(sb, node->od_node->overflow);
node->head.all_key_count =
fs16_to_cpu(sb, node->od_node->all_key_count);
node->head.all_key_length =
fs16_to_cpu(sb, node->od_node->all_key_length);
befs_debug(sb, "<--- befs_btree_read_node()");
return BEFS_OK;
}
/**
* befs_btree_find - Find a key in a befs B+tree
* @sb: Filesystem superblock
* @ds: Datastream containing btree
* @key: Key string to lookup in btree
* @value: Value stored with @key
*
* On sucess, returns BEFS_OK and sets *@value to the value stored
* with @key (usually the disk block number of an inode).
*
* On failure, returns BEFS_ERR or BEFS_BT_NOT_FOUND.
*
* Algorithm:
* Read the superblock and rootnode of the b+tree.
* Drill down through the interior nodes using befs_find_key().
* Once at the correct leaf node, use befs_find_key() again to get the
* actuall value stored with the key.
*/
int
befs_btree_find(struct super_block *sb, befs_data_stream * ds,
const char *key, befs_off_t * value)
{
befs_btree_node *this_node = NULL;
befs_btree_super bt_super;
befs_off_t node_off;
int res;
befs_debug(sb, "---> befs_btree_find() Key: %s", key);
if (befs_bt_read_super(sb, ds, &bt_super) != BEFS_OK) {
befs_error(sb,
"befs_btree_find() failed to read index superblock");
goto error;
}
this_node = (befs_btree_node *) kmalloc(sizeof (befs_btree_node),
GFP_NOFS);
if (!this_node) {
befs_error(sb, "befs_btree_find() failed to allocate %u "
"bytes of memory", sizeof (befs_btree_node));
goto error;
}
this_node->bh = NULL;
/* read in root node */
node_off = bt_super.root_node_ptr;
if (befs_bt_read_node(sb, ds, this_node, node_off) != BEFS_OK) {
befs_error(sb, "befs_btree_find() failed to read "
"node at %Lu", node_off);
goto error_alloc;
}
while (!befs_leafnode(this_node)) {
res = befs_find_key(sb, this_node, key, &node_off);
if (res == BEFS_BT_NOT_FOUND)
node_off = this_node->head.overflow;
/* if no match, go to overflow node */
if (befs_bt_read_node(sb, ds, this_node, node_off) != BEFS_OK) {
befs_error(sb, "befs_btree_find() failed to read "
"node at %Lu", node_off);
goto error_alloc;
}
}
/* at the correct leaf node now */
res = befs_find_key(sb, this_node, key, value);
brelse(this_node->bh);
kfree(this_node);
if (res != BEFS_BT_MATCH) {
befs_debug(sb, "<--- befs_btree_find() Key %s not found", key);
*value = 0;
return BEFS_BT_NOT_FOUND;
}
befs_debug(sb, "<--- befs_btree_find() Found key %s, value %Lu",
key, *value);
return BEFS_OK;
error_alloc:
kfree(this_node);
error:
*value = 0;
befs_debug(sb, "<--- befs_btree_find() ERROR");
return BEFS_ERR;
}
/**
* befs_find_key - Search for a key within a node
* @sb: Filesystem superblock
* @node: Node to find the key within
* @key: Keystring to search for
* @value: If key is found, the value stored with the key is put here
*
* finds exact match if one exists, and returns BEFS_BT_MATCH
* If no exact match, finds first key in node that is greater
* (alphabetically) than the search key and returns BEFS_BT_PARMATCH
* (for partial match, I guess). Can you think of something better to
* call it?
*
* If no key was a match or greater than the search key, return
* BEFS_BT_NOT_FOUND.
*
* Use binary search instead of a linear.
*/
static int
befs_find_key(struct super_block *sb, befs_btree_node * node,
const char *findkey, befs_off_t * value)
{
int first, last, mid;
int eq;
u16 keylen;
int findkey_len;
char *thiskey;
befs_off_t *valarray;
befs_debug(sb, "---> befs_find_key() %s", findkey);
*value = 0;
findkey_len = strlen(findkey);
/* if node can not contain key, just skeep this node */
last = node->head.all_key_count - 1;
thiskey = befs_bt_get_key(sb, node, last, &keylen);
eq = befs_compare_strings(thiskey, keylen, findkey, findkey_len);
if (eq < 0) {
befs_debug(sb, "<--- befs_find_key() %s not found", findkey);
return BEFS_BT_NOT_FOUND;
}
valarray = befs_bt_valarray(node);
/* simple binary search */
first = 0;
mid = 0;
while (last >= first) {
mid = (last + first) / 2;
befs_debug(sb, "first: %d, last: %d, mid: %d", first, last,
mid);
thiskey = befs_bt_get_key(sb, node, mid, &keylen);
eq = befs_compare_strings(thiskey, keylen, findkey,
findkey_len);
if (eq == 0) {
befs_debug(sb, "<--- befs_find_key() found %s at %d",
thiskey, mid);
*value = fs64_to_cpu(sb, valarray[mid]);
return BEFS_BT_MATCH;
}
if (eq > 0)
last = mid - 1;
else
first = mid + 1;
}
if (eq < 0)
*value = fs64_to_cpu(sb, valarray[mid + 1]);
else
*value = fs64_to_cpu(sb, valarray[mid]);
befs_debug(sb, "<--- befs_find_key() found %s at %d", thiskey, mid);
return BEFS_BT_PARMATCH;
}
/**
* befs_btree_read - Traverse leafnodes of a btree
* @sb: Filesystem superblock
* @ds: Datastream containing btree
* @key_no: Key number (alphabetical order) of key to read
* @bufsize: Size of the buffer to return key in
* @keybuf: Pointer to a buffer to put the key in
* @keysize: Length of the returned key
* @value: Value stored with the returned key
*
* Heres how it works: Key_no is the index of the key/value pair to
* return in keybuf/value.
* Bufsize is the size of keybuf (BEFS_NAME_LEN+1 is a good size). Keysize is
* the number of charecters in the key (just a convenience).
*
* Algorithm:
* Get the first leafnode of the tree. See if the requested key is in that
* node. If not, follow the node->right link to the next leafnode. Repeat
* until the (key_no)th key is found or the tree is out of keys.
*/
int
befs_btree_read(struct super_block *sb, befs_data_stream * ds,
loff_t key_no, size_t bufsize, char *keybuf, size_t * keysize,
befs_off_t * value)
{
befs_btree_node *this_node;
befs_btree_super bt_super;
befs_off_t node_off = 0;
int cur_key;
befs_off_t *valarray;
char *keystart;
u16 keylen;
int res;
uint key_sum = 0;
befs_debug(sb, "---> befs_btree_read()");
if (befs_bt_read_super(sb, ds, &bt_super) != BEFS_OK) {
befs_error(sb,
"befs_btree_read() failed to read index superblock");
goto error;
}
if ((this_node = (befs_btree_node *)
kmalloc(sizeof (befs_btree_node), GFP_NOFS)) == NULL) {
befs_error(sb, "befs_btree_read() failed to allocate %u "
"bytes of memory", sizeof (befs_btree_node));
goto error;
}
node_off = bt_super.root_node_ptr;
this_node->bh = NULL;
/* seeks down to first leafnode, reads it into this_node */
res = befs_btree_seekleaf(sb, ds, &bt_super, this_node, &node_off);
if (res == BEFS_BT_EMPTY) {
brelse(this_node->bh);
kfree(this_node);
*value = 0;
*keysize = 0;
befs_debug(sb, "<--- befs_btree_read() Tree is EMPTY");
return BEFS_BT_EMPTY;
} else if (res == BEFS_ERR) {
goto error_alloc;
}
/* find the leaf node containing the key_no key */
while (key_sum + this_node->head.all_key_count <= key_no) {
/* no more nodes to look in: key_no is too large */
if (this_node->head.right == befs_bt_inval) {
*keysize = 0;
*value = 0;
befs_debug(sb,
"<--- befs_btree_read() END of keys at %Lu",
key_sum + this_node->head.all_key_count);
brelse(this_node->bh);
kfree(this_node);
return BEFS_BT_END;
}
key_sum += this_node->head.all_key_count;
node_off = this_node->head.right;
if (befs_bt_read_node(sb, ds, this_node, node_off) != BEFS_OK) {
befs_error(sb, "befs_btree_read() failed to read "
"node at %Lu", node_off);
goto error_alloc;
}
}
/* how many keys into this_node is key_no */
cur_key = key_no - key_sum;
/* get pointers to datastructures within the node body */
valarray = befs_bt_valarray(this_node);
keystart = befs_bt_get_key(sb, this_node, cur_key, &keylen);
befs_debug(sb, "Read [%Lu,%d]: keysize %d", node_off, cur_key, keylen);
if (bufsize < keylen + 1) {
befs_error(sb, "befs_btree_read() keybuf too small (%u) "
"for key of size %d", bufsize, keylen);
brelse(this_node->bh);
goto error_alloc;
};
strncpy(keybuf, keystart, keylen);
*value = fs64_to_cpu(sb, valarray[cur_key]);
*keysize = keylen;
keybuf[keylen] = '\0';
befs_debug(sb, "Read [%Lu,%d]: Key \"%.*s\", Value %Lu", node_off,
cur_key, keylen, keybuf, *value);
brelse(this_node->bh);
kfree(this_node);
befs_debug(sb, "<--- befs_btree_read()");
return BEFS_OK;
error_alloc:
kfree(this_node);
error:
*keysize = 0;
*value = 0;
befs_debug(sb, "<--- befs_btree_read() ERROR");
return BEFS_ERR;
}
/**
* befs_btree_seekleaf - Find the first leafnode in the btree
* @sb: Filesystem superblock
* @ds: Datastream containing btree
* @bt_super: Pointer to the superblock of the btree
* @this_node: Buffer to return the leafnode in
* @node_off: Pointer to offset of current node within datastream. Modified
* by the function.
*
*
* Helper function for btree traverse. Moves the current position to the
* start of the first leaf node.
*
* Also checks for an empty tree. If there are no keys, returns BEFS_BT_EMPTY.
*/
static int
befs_btree_seekleaf(struct super_block *sb, befs_data_stream * ds,
befs_btree_super * bt_super, befs_btree_node * this_node,
befs_off_t * node_off)
{
befs_debug(sb, "---> befs_btree_seekleaf()");
if (befs_bt_read_node(sb, ds, this_node, *node_off) != BEFS_OK) {
befs_error(sb, "befs_btree_seekleaf() failed to read "
"node at %Lu", *node_off);
goto error;
}
befs_debug(sb, "Seekleaf to root node %Lu", *node_off);
if (this_node->head.all_key_count == 0 && befs_leafnode(this_node)) {
befs_debug(sb, "<--- befs_btree_seekleaf() Tree is EMPTY");
return BEFS_BT_EMPTY;
}
while (!befs_leafnode(this_node)) {
if (this_node->head.all_key_count == 0) {
befs_debug(sb, "befs_btree_seekleaf() encountered "
"an empty interior node: %Lu. Using Overflow "
"node: %Lu", *node_off,
this_node->head.overflow);
*node_off = this_node->head.overflow;
} else {
befs_off_t *valarray = befs_bt_valarray(this_node);
*node_off = fs64_to_cpu(sb, valarray[0]);
}
if (befs_bt_read_node(sb, ds, this_node, *node_off) != BEFS_OK) {
befs_error(sb, "befs_btree_seekleaf() failed to read "
"node at %Lu", *node_off);
goto error;
}
befs_debug(sb, "Seekleaf to child node %Lu", *node_off);
}
befs_debug(sb, "Node %Lu is a leaf node", *node_off);
return BEFS_OK;
error:
befs_debug(sb, "<--- befs_btree_seekleaf() ERROR");
return BEFS_ERR;
}
/**
* befs_leafnode - Determine if the btree node is a leaf node or an
* interior node
* @node: Pointer to node structure to test
*
* Return 1 if leaf, 0 if interior
*/
static int
befs_leafnode(befs_btree_node * node)
{
/* all interior nodes (and only interior nodes) have an overflow node */
if (node->head.overflow == befs_bt_inval)
return 1;
else
return 0;
}
/**
* befs_bt_keylen_index - Finds start of keylen index in a node
* @node: Pointer to the node structure to find the keylen index within
*
* Returns a pointer to the start of the key length index array
* of the B+tree node *@node
*
* "The length of all the keys in the node is added to the size of the
* header and then rounded up to a multiple of four to get the beginning
* of the key length index" (p.88, practical filesystem design).
*
* Except that rounding up to 8 works, and rounding up to 4 doesn't.
*/
static u16 *
befs_bt_keylen_index(befs_btree_node * node)
{
const int keylen_align = 8;
unsigned long int off =
(sizeof (befs_btree_nodehead) + node->head.all_key_length);
ulong tmp = off % keylen_align;
if (tmp)
off += keylen_align - tmp;
return (u16 *) ((void *) node->od_node + off);
}
/**
* befs_bt_valarray - Finds the start of value array in a node
* @node: Pointer to the node structure to find the value array within
*
* Returns a pointer to the start of the value array
* of the node pointed to by the node header
*/
static befs_off_t *
befs_bt_valarray(befs_btree_node * node)
{
void *keylen_index_start = (void *) befs_bt_keylen_index(node);
size_t keylen_index_size = node->head.all_key_count * sizeof (u16);
return (befs_off_t *) (keylen_index_start + keylen_index_size);
}
/**
* befs_bt_keydata - Finds start of keydata array in a node
* @node: Pointer to the node structure to find the keydata array within
*
* Returns a pointer to the start of the keydata array
* of the node pointed to by the node header
*/
static char *
befs_bt_keydata(befs_btree_node * node)
{
return (char *) ((void *) node->od_node + sizeof (befs_btree_nodehead));
}
/**
* befs_bt_get_key - returns a pointer to the start of a key
* @sb: filesystem superblock
* @node: node in which to look for the key
* @index: the index of the key to get
* @keylen: modified to be the length of the key at @index
*
* Returns a valid pointer into @node on success.
* Returns NULL on failure (bad input) and sets *@keylen = 0
*/
static char *
befs_bt_get_key(struct super_block *sb, befs_btree_node * node,
int index, u16 * keylen)
{
int prev_key_end;
char *keystart;
u16 *keylen_index;
if (index < 0 || index > node->head.all_key_count) {
*keylen = 0;
return NULL;
}
keystart = befs_bt_keydata(node);
keylen_index = befs_bt_keylen_index(node);
if (index == 0)
prev_key_end = 0;
else
prev_key_end = fs16_to_cpu(sb, keylen_index[index - 1]);
*keylen = fs16_to_cpu(sb, keylen_index[index]) - prev_key_end;
return keystart + prev_key_end;
}
/**
* befs_compare_strings - compare two strings
* @key1: pointer to the first key to be compared
* @keylen1: length in bytes of key1
* @key2: pointer to the second key to be compared
* @kelen2: length in bytes of key2
*
* Returns 0 if @key1 and @key2 are equal.
* Returns >0 if @key1 is greater.
* Returns <0 if @key2 is greater..
*/
static int
befs_compare_strings(const void *key1, int keylen1,
const void *key2, int keylen2)
{
int len = min_t(int, keylen1, keylen2);
int result = strncmp(key1, key2, len);
if (result == 0)
result = keylen1 - keylen2;
return result;
}
/* These will be used for non-string keyed btrees */
#if 0
static int
btree_compare_int32(cont void *key1, int keylen1, const void *key2, int keylen2)
{
return *(int32_t *) key1 - *(int32_t *) key2;
}
static int
btree_compare_uint32(cont void *key1, int keylen1,
const void *key2, int keylen2)
{
if (*(u_int32_t *) key1 == *(u_int32_t *) key2)
return 0;
else if (*(u_int32_t *) key1 > *(u_int32_t *) key2)
return 1;
return -1;
}
static int
btree_compare_int64(cont void *key1, int keylen1, const void *key2, int keylen2)
{
if (*(int64_t *) key1 == *(int64_t *) key2)
return 0;
else if (*(int64_t *) key1 > *(int64_t *) key2)
return 1;
return -1;
}
static int
btree_compare_uint64(cont void *key1, int keylen1,
const void *key2, int keylen2)
{
if (*(u_int64_t *) key1 == *(u_int64_t *) key2)
return 0;
else if (*(u_int64_t *) key1 > *(u_int64_t *) key2)
return 1;
return -1;
}
static int
btree_compare_float(cont void *key1, int keylen1, const void *key2, int keylen2)
{
float result = *(float *) key1 - *(float *) key2;
if (result == 0.0f)
return 0;
return (result < 0.0f) ? -1 : 1;
}
static int
btree_compare_double(cont void *key1, int keylen1,
const void *key2, int keylen2)
{
double result = *(double *) key1 - *(double *) key2;
if (result == 0.0)
return 0;
return (result < 0.0) ? -1 : 1;
}
#endif //0

13
fs/befs/btree.h Normal file
View File

@@ -0,0 +1,13 @@
/*
* btree.h
*
*/
int befs_btree_find(struct super_block *sb, befs_data_stream * ds,
const char *key, befs_off_t * value);
int befs_btree_read(struct super_block *sb, befs_data_stream * ds,
loff_t key_no, size_t bufsize, char *keybuf,
size_t * keysize, befs_off_t * value);

528
fs/befs/datastream.c Normal file
View File

@@ -0,0 +1,528 @@
/*
* linux/fs/befs/datastream.c
*
* Copyright (C) 2001 Will Dyson <will_dyson@pobox.com>
*
* Based on portions of file.c by Makoto Kato <m_kato@ga2.so-net.ne.jp>
*
* Many thanks to Dominic Giampaolo, author of "Practical File System
* Design with the Be File System", for such a helpful book.
*
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/buffer_head.h>
#include <linux/string.h>
#include "befs.h"
#include "datastream.h"
#include "io.h"
#include "endian.h"
const befs_inode_addr BAD_IADDR = { 0, 0, 0 };
static int befs_find_brun_direct(struct super_block *sb,
befs_data_stream * data,
befs_blocknr_t blockno, befs_block_run * run);
static int befs_find_brun_indirect(struct super_block *sb,
befs_data_stream * data,
befs_blocknr_t blockno,
befs_block_run * run);
static int befs_find_brun_dblindirect(struct super_block *sb,
befs_data_stream * data,
befs_blocknr_t blockno,
befs_block_run * run);
/**
* befs_read_datastream - get buffer_head containing data, starting from pos.
* @sb: Filesystem superblock
* @ds: datastrem to find data with
* @pos: start of data
* @off: offset of data in buffer_head->b_data
*
* Returns pointer to buffer_head containing data starting with offset @off,
* if you don't need to know offset just set @off = NULL.
*/
struct buffer_head *
befs_read_datastream(struct super_block *sb, befs_data_stream * ds,
befs_off_t pos, uint * off)
{
struct buffer_head *bh = NULL;
befs_block_run run;
befs_blocknr_t block; /* block coresponding to pos */
befs_debug(sb, "---> befs_read_datastream() %Lu", pos);
block = pos >> BEFS_SB(sb)->block_shift;
if (off)
*off = pos - (block << BEFS_SB(sb)->block_shift);
if (befs_fblock2brun(sb, ds, block, &run) != BEFS_OK) {
befs_error(sb, "BeFS: Error finding disk addr of block %lu",
block);
befs_debug(sb, "<--- befs_read_datastream() ERROR");
return NULL;
}
bh = befs_bread_iaddr(sb, run);
if (!bh) {
befs_error(sb, "BeFS: Error reading block %lu from datastream",
block);
return NULL;
}
befs_debug(sb, "<--- befs_read_datastream() read data, starting at %Lu",
pos);
return bh;
}
/*
* Takes a file position and gives back a brun who's starting block
* is block number fblock of the file.
*
* Returns BEFS_OK or BEFS_ERR.
*
* Calls specialized functions for each of the three possible
* datastream regions.
*
* 2001-11-15 Will Dyson
*/
int
befs_fblock2brun(struct super_block *sb, befs_data_stream * data,
befs_blocknr_t fblock, befs_block_run * run)
{
int err;
befs_off_t pos = fblock << BEFS_SB(sb)->block_shift;
if (pos < data->max_direct_range) {
err = befs_find_brun_direct(sb, data, fblock, run);
} else if (pos < data->max_indirect_range) {
err = befs_find_brun_indirect(sb, data, fblock, run);
} else if (pos < data->max_double_indirect_range) {
err = befs_find_brun_dblindirect(sb, data, fblock, run);
} else {
befs_error(sb,
"befs_fblock2brun() was asked to find block %lu, "
"which is not mapped by the datastream\n", fblock);
err = BEFS_ERR;
}
return err;
}
/**
* befs_read_lsmylink - read long symlink from datastream.
* @sb: Filesystem superblock
* @ds: Datastrem to read from
* @buf: Buffer in wich to place long symlink data
* @len: Length of the long symlink in bytes
*
* Returns the number of bytes read
*/
size_t
befs_read_lsymlink(struct super_block * sb, befs_data_stream * ds, void *buff,
befs_off_t len)
{
befs_off_t bytes_read = 0; /* bytes readed */
u16 plen;
struct buffer_head *bh = NULL;
befs_debug(sb, "---> befs_read_lsymlink() length: %Lu", len);
while (bytes_read < len) {
bh = befs_read_datastream(sb, ds, bytes_read, NULL);
if (!bh) {
befs_error(sb, "BeFS: Error reading datastream block "
"starting from %Lu", bytes_read);
befs_debug(sb, "<--- befs_read_lsymlink() ERROR");
return bytes_read;
}
plen = ((bytes_read + BEFS_SB(sb)->block_size) < len) ?
BEFS_SB(sb)->block_size : len - bytes_read;
memcpy(buff + bytes_read, bh->b_data, plen);
brelse(bh);
bytes_read += plen;
}
befs_debug(sb, "<--- befs_read_lsymlink() read %u bytes", bytes_read);
return bytes_read;
}
/**
* befs_count_blocks - blocks used by a file
* @sb: Filesystem superblock
* @ds: Datastream of the file
*
* Counts the number of fs blocks that the file represented by
* inode occupies on the filesystem, counting both regular file
* data and filesystem metadata (and eventually attribute data
* when we support attributes)
*/
befs_blocknr_t
befs_count_blocks(struct super_block * sb, befs_data_stream * ds)
{
befs_blocknr_t blocks;
befs_blocknr_t datablocks; /* File data blocks */
befs_blocknr_t metablocks; /* FS metadata blocks */
befs_sb_info *befs_sb = BEFS_SB(sb);
befs_debug(sb, "---> befs_count_blocks()");
datablocks = ds->size >> befs_sb->block_shift;
if (ds->size & (befs_sb->block_size - 1))
datablocks += 1;
metablocks = 1; /* Start with 1 block for inode */
/* Size of indirect block */
if (ds->size > ds->max_direct_range)
metablocks += ds->indirect.len;
/*
Double indir block, plus all the indirect blocks it mapps
In the double-indirect range, all block runs of data are
BEFS_DBLINDIR_BRUN_LEN blocks long. Therefore, we know
how many data block runs are in the double-indirect region,
and from that we know how many indirect blocks it takes to
map them. We assume that the indirect blocks are also
BEFS_DBLINDIR_BRUN_LEN blocks long.
*/
if (ds->size > ds->max_indirect_range && ds->max_indirect_range != 0) {
uint dbl_bytes;
uint dbl_bruns;
uint indirblocks;
dbl_bytes =
ds->max_double_indirect_range - ds->max_indirect_range;
dbl_bruns =
dbl_bytes / (befs_sb->block_size * BEFS_DBLINDIR_BRUN_LEN);
indirblocks = dbl_bruns / befs_iaddrs_per_block(sb);
metablocks += ds->double_indirect.len;
metablocks += indirblocks;
}
blocks = datablocks + metablocks;
befs_debug(sb, "<--- befs_count_blocks() %u blocks", blocks);
return blocks;
}
/*
Finds the block run that starts at file block number blockno
in the file represented by the datastream data, if that
blockno is in the direct region of the datastream.
sb: the superblock
data: the datastream
blockno: the blocknumber to find
run: The found run is passed back through this pointer
Return value is BEFS_OK if the blockrun is found, BEFS_ERR
otherwise.
Algorithm:
Linear search. Checks each element of array[] to see if it
contains the blockno-th filesystem block. This is necessary
because the block runs map variable amounts of data. Simply
keeps a count of the number of blocks searched so far (sum),
incrementing this by the length of each block run as we come
across it. Adds sum to *count before returning (this is so
you can search multiple arrays that are logicaly one array,
as in the indirect region code).
When/if blockno is found, if blockno is inside of a block
run as stored on disk, we offset the start and lenght members
of the block run, so that blockno is the start and len is
still valid (the run ends in the same place).
2001-11-15 Will Dyson
*/
static int
befs_find_brun_direct(struct super_block *sb, befs_data_stream * data,
befs_blocknr_t blockno, befs_block_run * run)
{
int i;
befs_block_run *array = data->direct;
befs_blocknr_t sum;
befs_blocknr_t max_block =
data->max_direct_range >> BEFS_SB(sb)->block_shift;
befs_debug(sb, "---> befs_find_brun_direct(), find %lu", blockno);
if (blockno > max_block) {
befs_error(sb, "befs_find_brun_direct() passed block outside of"
"direct region");
return BEFS_ERR;
}
for (i = 0, sum = 0; i < BEFS_NUM_DIRECT_BLOCKS;
sum += array[i].len, i++) {
if (blockno >= sum && blockno < sum + (array[i].len)) {
int offset = blockno - sum;
run->allocation_group = array[i].allocation_group;
run->start = array[i].start + offset;
run->len = array[i].len - offset;
befs_debug(sb, "---> befs_find_brun_direct(), "
"found %lu at direct[%d]", blockno, i);
return BEFS_OK;
}
}
befs_debug(sb, "---> befs_find_brun_direct() ERROR");
return BEFS_ERR;
}
/*
Finds the block run that starts at file block number blockno
in the file represented by the datastream data, if that
blockno is in the indirect region of the datastream.
sb: the superblock
data: the datastream
blockno: the blocknumber to find
run: The found run is passed back through this pointer
Return value is BEFS_OK if the blockrun is found, BEFS_ERR
otherwise.
Algorithm:
For each block in the indirect run of the datastream, read
it in and search through it for search_blk.
XXX:
Really should check to make sure blockno is inside indirect
region.
2001-11-15 Will Dyson
*/
static int
befs_find_brun_indirect(struct super_block *sb,
befs_data_stream * data, befs_blocknr_t blockno,
befs_block_run * run)
{
int i, j;
befs_blocknr_t sum = 0;
befs_blocknr_t indir_start_blk;
befs_blocknr_t search_blk;
struct buffer_head *indirblock;
befs_block_run *array;
befs_block_run indirect = data->indirect;
befs_blocknr_t indirblockno = iaddr2blockno(sb, &indirect);
int arraylen = befs_iaddrs_per_block(sb);
befs_debug(sb, "---> befs_find_brun_indirect(), find %lu", blockno);
indir_start_blk = data->max_direct_range >> BEFS_SB(sb)->block_shift;
search_blk = blockno - indir_start_blk;
/* Examine blocks of the indirect run one at a time */
for (i = 0; i < indirect.len; i++) {
indirblock = befs_bread(sb, indirblockno + i);
if (indirblock == NULL) {
befs_debug(sb,
"---> befs_find_brun_indirect() failed to "
"read disk block %lu from the indirect brun",
indirblockno + i);
return BEFS_ERR;
}
array = (befs_block_run *) indirblock->b_data;
for (j = 0; j < arraylen; ++j) {
int len = fs16_to_cpu(sb, array[j].len);
if (search_blk >= sum && search_blk < sum + len) {
int offset = search_blk - sum;
run->allocation_group =
fs32_to_cpu(sb, array[j].allocation_group);
run->start =
fs16_to_cpu(sb, array[j].start) + offset;
run->len =
fs16_to_cpu(sb, array[j].len) - offset;
brelse(indirblock);
befs_debug(sb,
"<--- befs_find_brun_indirect() found "
"file block %lu at indirect[%d]",
blockno, j + (i * arraylen));
return BEFS_OK;
}
sum += len;
}
brelse(indirblock);
}
/* Only fallthrough is an error */
befs_error(sb, "BeFS: befs_find_brun_indirect() failed to find "
"file block %lu", blockno);
befs_debug(sb, "<--- befs_find_brun_indirect() ERROR");
return BEFS_ERR;
}
/*
Finds the block run that starts at file block number blockno
in the file represented by the datastream data, if that
blockno is in the double-indirect region of the datastream.
sb: the superblock
data: the datastream
blockno: the blocknumber to find
run: The found run is passed back through this pointer
Return value is BEFS_OK if the blockrun is found, BEFS_ERR
otherwise.
Algorithm:
The block runs in the double-indirect region are different.
They are always allocated 4 fs blocks at a time, so each
block run maps a constant amount of file data. This means
that we can directly calculate how many block runs into the
double-indirect region we need to go to get to the one that
maps a particular filesystem block.
We do this in two stages. First we calculate which of the
inode addresses in the double-indirect block will point us
to the indirect block that contains the mapping for the data,
then we calculate which of the inode addresses in that
indirect block maps the data block we are after.
Oh, and once we've done that, we actually read in the blocks
that contain the inode addresses we calculated above. Even
though the double-indirect run may be several blocks long,
we can calculate which of those blocks will contain the index
we are after and only read that one. We then follow it to
the indirect block and perform a similar process to find
the actual block run that maps the data block we are interested
in.
Then we offset the run as in befs_find_brun_array() and we are
done.
2001-11-15 Will Dyson
*/
static int
befs_find_brun_dblindirect(struct super_block *sb,
befs_data_stream * data, befs_blocknr_t blockno,
befs_block_run * run)
{
int dblindir_indx;
int indir_indx;
int offset;
int dbl_which_block;
int which_block;
int dbl_block_indx;
int block_indx;
off_t dblindir_leftover;
befs_blocknr_t blockno_at_run_start;
struct buffer_head *dbl_indir_block;
struct buffer_head *indir_block;
befs_block_run indir_run;
befs_inode_addr *iaddr_array = NULL;
befs_sb_info *befs_sb = BEFS_SB(sb);
befs_blocknr_t indir_start_blk =
data->max_indirect_range >> befs_sb->block_shift;
off_t dbl_indir_off = blockno - indir_start_blk;
/* number of data blocks mapped by each of the iaddrs in
* the indirect block pointed to by the double indirect block
*/
size_t iblklen = BEFS_DBLINDIR_BRUN_LEN;
/* number of data blocks mapped by each of the iaddrs in
* the double indirect block
*/
size_t diblklen = iblklen * befs_iaddrs_per_block(sb)
* BEFS_DBLINDIR_BRUN_LEN;
befs_debug(sb, "---> befs_find_brun_dblindirect() find %lu", blockno);
/* First, discover which of the double_indir->indir blocks
* contains pos. Then figure out how much of pos that
* accounted for. Then discover which of the iaddrs in
* the indirect block contains pos.
*/
dblindir_indx = dbl_indir_off / diblklen;
dblindir_leftover = dbl_indir_off % diblklen;
indir_indx = dblindir_leftover / diblklen;
/* Read double indirect block */
dbl_which_block = dblindir_indx / befs_iaddrs_per_block(sb);
if (dbl_which_block > data->double_indirect.len) {
befs_error(sb, "The double-indirect index calculated by "
"befs_read_brun_dblindirect(), %d, is outside the range "
"of the double-indirect block", dblindir_indx);
return BEFS_ERR;
}
dbl_indir_block =
befs_bread(sb, iaddr2blockno(sb, &data->double_indirect) +
dbl_which_block);
if (dbl_indir_block == NULL) {
befs_error(sb, "befs_read_brun_dblindirect() couldn't read the "
"double-indirect block at blockno %lu",
iaddr2blockno(sb,
&data->double_indirect) +
dbl_which_block);
brelse(dbl_indir_block);
return BEFS_ERR;
}
dbl_block_indx =
dblindir_indx - (dbl_which_block * befs_iaddrs_per_block(sb));
iaddr_array = (befs_inode_addr *) dbl_indir_block->b_data;
indir_run = fsrun_to_cpu(sb, iaddr_array[dbl_block_indx]);
brelse(dbl_indir_block);
iaddr_array = NULL;
/* Read indirect block */
which_block = indir_indx / befs_iaddrs_per_block(sb);
if (which_block > indir_run.len) {
befs_error(sb, "The indirect index calculated by "
"befs_read_brun_dblindirect(), %d, is outside the range "
"of the indirect block", indir_indx);
return BEFS_ERR;
}
indir_block =
befs_bread(sb, iaddr2blockno(sb, &indir_run) + which_block);
if (indir_block == NULL) {
befs_error(sb, "befs_read_brun_dblindirect() couldn't read the "
"indirect block at blockno %lu",
iaddr2blockno(sb, &indir_run) + which_block);
brelse(indir_block);
return BEFS_ERR;
}
block_indx = indir_indx - (which_block * befs_iaddrs_per_block(sb));
iaddr_array = (befs_inode_addr *) indir_block->b_data;
*run = fsrun_to_cpu(sb, iaddr_array[block_indx]);
brelse(indir_block);
iaddr_array = NULL;
blockno_at_run_start = indir_start_blk;
blockno_at_run_start += diblklen * dblindir_indx;
blockno_at_run_start += iblklen * indir_indx;
offset = blockno - blockno_at_run_start;
run->start += offset;
run->len -= offset;
befs_debug(sb, "Found file block %lu in double_indirect[%d][%d],"
" double_indirect_leftover = %lu",
blockno, dblindir_indx, indir_indx, dblindir_leftover);
return BEFS_OK;
}

19
fs/befs/datastream.h Normal file
View File

@@ -0,0 +1,19 @@
/*
* datastream.h
*
*/
struct buffer_head *befs_read_datastream(struct super_block *sb,
befs_data_stream * ds, befs_off_t pos,
uint * off);
int befs_fblock2brun(struct super_block *sb, befs_data_stream * data,
befs_blocknr_t fblock, befs_block_run * run);
size_t befs_read_lsymlink(struct super_block *sb, befs_data_stream * data,
void *buff, befs_off_t len);
befs_blocknr_t befs_count_blocks(struct super_block *sb, befs_data_stream * ds);
extern const befs_inode_addr BAD_IADDR;

283
fs/befs/debug.c Normal file
View File

@@ -0,0 +1,283 @@
/*
* linux/fs/befs/debug.c
*
* Copyright (C) 2001 Will Dyson (will_dyson at pobox.com)
*
* With help from the ntfs-tng driver by Anton Altparmakov
*
* Copyright (C) 1999 Makoto Kato (m_kato@ga2.so-net.ne.jp)
*
* debug functions
*/
#ifdef __KERNEL__
#include <stdarg.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#endif /* __KERNEL__ */
#include "befs.h"
#include "endian.h"
#define ERRBUFSIZE 1024
void
befs_error(const struct super_block *sb, const char *fmt, ...)
{
va_list args;
char *err_buf = (char *) kmalloc(ERRBUFSIZE, GFP_KERNEL);
if (err_buf == NULL) {
printk(KERN_ERR "could not allocate %d bytes\n", ERRBUFSIZE);
return;
}
va_start(args, fmt);
vsnprintf(err_buf, ERRBUFSIZE, fmt, args);
va_end(args);
printk(KERN_ERR "BeFS(%s): %s\n", sb->s_id, err_buf);
kfree(err_buf);
}
void
befs_warning(const struct super_block *sb, const char *fmt, ...)
{
va_list args;
char *err_buf = (char *) kmalloc(ERRBUFSIZE, GFP_KERNEL);
if (err_buf == NULL) {
printk(KERN_ERR "could not allocate %d bytes\n", ERRBUFSIZE);
return;
}
va_start(args, fmt);
vsnprintf(err_buf, ERRBUFSIZE, fmt, args);
va_end(args);
printk(KERN_WARNING "BeFS(%s): %s\n", sb->s_id, err_buf);
kfree(err_buf);
}
void
befs_debug(const struct super_block *sb, const char *fmt, ...)
{
#ifdef CONFIG_BEFS_DEBUG
va_list args;
char *err_buf = NULL;
if (BEFS_SB(sb)->mount_opts.debug) {
err_buf = (char *) kmalloc(ERRBUFSIZE, GFP_KERNEL);
if (err_buf == NULL) {
printk(KERN_ERR "could not allocate %d bytes\n",
ERRBUFSIZE);
return;
}
va_start(args, fmt);
vsnprintf(err_buf, ERRBUFSIZE, fmt, args);
va_end(args);
printk(KERN_DEBUG "BeFS(%s): %s\n", sb->s_id, err_buf);
kfree(err_buf);
}
#endif //CONFIG_BEFS_DEBUG
}
void
befs_dump_inode(const struct super_block *sb, befs_inode * inode)
{
#ifdef CONFIG_BEFS_DEBUG
befs_block_run tmp_run;
befs_debug(sb, "befs_inode information");
befs_debug(sb, " magic1 %08x", fs32_to_cpu(sb, inode->magic1));
tmp_run = fsrun_to_cpu(sb, inode->inode_num);
befs_debug(sb, " inode_num %u, %hu, %hu",
tmp_run.allocation_group, tmp_run.start, tmp_run.len);
befs_debug(sb, " uid %u", fs32_to_cpu(sb, inode->uid));
befs_debug(sb, " gid %u", fs32_to_cpu(sb, inode->gid));
befs_debug(sb, " mode %08x", fs32_to_cpu(sb, inode->mode));
befs_debug(sb, " flags %08x", fs32_to_cpu(sb, inode->flags));
befs_debug(sb, " create_time %Lu",
fs64_to_cpu(sb, inode->create_time));
befs_debug(sb, " last_modified_time %Lu",
fs64_to_cpu(sb, inode->last_modified_time));
tmp_run = fsrun_to_cpu(sb, inode->parent);
befs_debug(sb, " parent [%u, %hu, %hu]",
tmp_run.allocation_group, tmp_run.start, tmp_run.len);
tmp_run = fsrun_to_cpu(sb, inode->attributes);
befs_debug(sb, " attributes [%u, %hu, %hu]",
tmp_run.allocation_group, tmp_run.start, tmp_run.len);
befs_debug(sb, " type %08x", fs32_to_cpu(sb, inode->type));
befs_debug(sb, " inode_size %u", fs32_to_cpu(sb, inode->inode_size));
if (S_ISLNK(inode->mode)) {
befs_debug(sb, " Symbolic link [%s]", inode->data.symlink);
} else {
int i;
for (i = 0; i < BEFS_NUM_DIRECT_BLOCKS; i++) {
tmp_run =
fsrun_to_cpu(sb, inode->data.datastream.direct[i]);
befs_debug(sb, " direct %d [%u, %hu, %hu]", i,
tmp_run.allocation_group, tmp_run.start,
tmp_run.len);
}
befs_debug(sb, " max_direct_range %Lu",
fs64_to_cpu(sb,
inode->data.datastream.
max_direct_range));
tmp_run = fsrun_to_cpu(sb, inode->data.datastream.indirect);
befs_debug(sb, " indirect [%u, %hu, %hu]",
tmp_run.allocation_group,
tmp_run.start, tmp_run.len);
befs_debug(sb, " max_indirect_range %Lu",
fs64_to_cpu(sb,
inode->data.datastream.
max_indirect_range));
tmp_run =
fsrun_to_cpu(sb, inode->data.datastream.double_indirect);
befs_debug(sb, " double indirect [%u, %hu, %hu]",
tmp_run.allocation_group, tmp_run.start,
tmp_run.len);
befs_debug(sb, " max_double_indirect_range %Lu",
fs64_to_cpu(sb,
inode->data.datastream.
max_double_indirect_range));
befs_debug(sb, " size %Lu",
fs64_to_cpu(sb, inode->data.datastream.size));
}
#endif //CONFIG_BEFS_DEBUG
}
/*
* Display super block structure for debug.
*/
void
befs_dump_super_block(const struct super_block *sb, befs_super_block * sup)
{
#ifdef CONFIG_BEFS_DEBUG
befs_block_run tmp_run;
befs_debug(sb, "befs_super_block information");
befs_debug(sb, " name %s", sup->name);
befs_debug(sb, " magic1 %08x", fs32_to_cpu(sb, sup->magic1));
befs_debug(sb, " fs_byte_order %08x",
fs32_to_cpu(sb, sup->fs_byte_order));
befs_debug(sb, " block_size %u", fs32_to_cpu(sb, sup->block_size));
befs_debug(sb, " block_shift %u", fs32_to_cpu(sb, sup->block_shift));
befs_debug(sb, " num_blocks %Lu", fs64_to_cpu(sb, sup->num_blocks));
befs_debug(sb, " used_blocks %Lu", fs64_to_cpu(sb, sup->used_blocks));
befs_debug(sb, " magic2 %08x", fs32_to_cpu(sb, sup->magic2));
befs_debug(sb, " blocks_per_ag %u",
fs32_to_cpu(sb, sup->blocks_per_ag));
befs_debug(sb, " ag_shift %u", fs32_to_cpu(sb, sup->ag_shift));
befs_debug(sb, " num_ags %u", fs32_to_cpu(sb, sup->num_ags));
befs_debug(sb, " flags %08x", fs32_to_cpu(sb, sup->flags));
tmp_run = fsrun_to_cpu(sb, sup->log_blocks);
befs_debug(sb, " log_blocks %u, %hu, %hu",
tmp_run.allocation_group, tmp_run.start, tmp_run.len);
befs_debug(sb, " log_start %Ld", fs64_to_cpu(sb, sup->log_start));
befs_debug(sb, " log_end %Ld", fs64_to_cpu(sb, sup->log_end));
befs_debug(sb, " magic3 %08x", fs32_to_cpu(sb, sup->magic3));
tmp_run = fsrun_to_cpu(sb, sup->root_dir);
befs_debug(sb, " root_dir %u, %hu, %hu",
tmp_run.allocation_group, tmp_run.start, tmp_run.len);
tmp_run = fsrun_to_cpu(sb, sup->indices);
befs_debug(sb, " indices %u, %hu, %hu",
tmp_run.allocation_group, tmp_run.start, tmp_run.len);
#endif //CONFIG_BEFS_DEBUG
}
#if 0
/* unused */
void
befs_dump_small_data(const struct super_block *sb, befs_small_data * sd)
{
}
/* unused */
void
befs_dump_run(const struct super_block *sb, befs_block_run run)
{
#ifdef CONFIG_BEFS_DEBUG
run = fsrun_to_cpu(sb, run);
befs_debug(sb, "[%u, %hu, %hu]",
run.allocation_group, run.start, run.len);
#endif //CONFIG_BEFS_DEBUG
}
#endif /* 0 */
void
befs_dump_index_entry(const struct super_block *sb, befs_btree_super * super)
{
#ifdef CONFIG_BEFS_DEBUG
befs_debug(sb, "Btree super structure");
befs_debug(sb, " magic %08x", fs32_to_cpu(sb, super->magic));
befs_debug(sb, " node_size %u", fs32_to_cpu(sb, super->node_size));
befs_debug(sb, " max_depth %08x", fs32_to_cpu(sb, super->max_depth));
befs_debug(sb, " data_type %08x", fs32_to_cpu(sb, super->data_type));
befs_debug(sb, " root_node_pointer %016LX",
fs64_to_cpu(sb, super->root_node_ptr));
befs_debug(sb, " free_node_pointer %016LX",
fs64_to_cpu(sb, super->free_node_ptr));
befs_debug(sb, " maximum size %016LX",
fs64_to_cpu(sb, super->max_size));
#endif //CONFIG_BEFS_DEBUG
}
void
befs_dump_index_node(const struct super_block *sb, befs_btree_nodehead * node)
{
#ifdef CONFIG_BEFS_DEBUG
befs_debug(sb, "Btree node structure");
befs_debug(sb, " left %016LX", fs64_to_cpu(sb, node->left));
befs_debug(sb, " right %016LX", fs64_to_cpu(sb, node->right));
befs_debug(sb, " overflow %016LX", fs64_to_cpu(sb, node->overflow));
befs_debug(sb, " all_key_count %hu",
fs16_to_cpu(sb, node->all_key_count));
befs_debug(sb, " all_key_length %hu",
fs16_to_cpu(sb, node->all_key_length));
#endif //CONFIG_BEFS_DEBUG
}

126
fs/befs/endian.h Normal file
View File

@@ -0,0 +1,126 @@
/*
* linux/fs/befs/endian.h
*
* Copyright (C) 2001 Will Dyson <will_dyson@pobox.com>
*
* Partially based on similar funtions in the sysv driver.
*/
#ifndef LINUX_BEFS_ENDIAN
#define LINUX_BEFS_ENDIAN
#include <linux/byteorder/generic.h>
#include "befs.h"
static inline u64
fs64_to_cpu(const struct super_block *sb, u64 n)
{
if (BEFS_SB(sb)->byte_order == BEFS_BYTESEX_LE)
return le64_to_cpu(n);
else
return be64_to_cpu(n);
}
static inline u64
cpu_to_fs64(const struct super_block *sb, u64 n)
{
if (BEFS_SB(sb)->byte_order == BEFS_BYTESEX_LE)
return cpu_to_le64(n);
else
return cpu_to_be64(n);
}
static inline u32
fs32_to_cpu(const struct super_block *sb, u32 n)
{
if (BEFS_SB(sb)->byte_order == BEFS_BYTESEX_LE)
return le32_to_cpu(n);
else
return be32_to_cpu(n);
}
static inline u32
cpu_to_fs32(const struct super_block *sb, u32 n)
{
if (BEFS_SB(sb)->byte_order == BEFS_BYTESEX_LE)
return cpu_to_le32(n);
else
return cpu_to_be32(n);
}
static inline u16
fs16_to_cpu(const struct super_block *sb, u16 n)
{
if (BEFS_SB(sb)->byte_order == BEFS_BYTESEX_LE)
return le16_to_cpu(n);
else
return be16_to_cpu(n);
}
static inline u16
cpu_to_fs16(const struct super_block *sb, u16 n)
{
if (BEFS_SB(sb)->byte_order == BEFS_BYTESEX_LE)
return cpu_to_le16(n);
else
return cpu_to_be16(n);
}
/* Composite types below here */
static inline befs_block_run
fsrun_to_cpu(const struct super_block *sb, befs_block_run n)
{
befs_block_run run;
if (BEFS_SB(sb)->byte_order == BEFS_BYTESEX_LE) {
run.allocation_group = le32_to_cpu(n.allocation_group);
run.start = le16_to_cpu(n.start);
run.len = le16_to_cpu(n.len);
} else {
run.allocation_group = be32_to_cpu(n.allocation_group);
run.start = be16_to_cpu(n.start);
run.len = be16_to_cpu(n.len);
}
return run;
}
static inline befs_block_run
cpu_to_fsrun(const struct super_block *sb, befs_block_run n)
{
befs_block_run run;
if (BEFS_SB(sb)->byte_order == BEFS_BYTESEX_LE) {
run.allocation_group = cpu_to_le32(n.allocation_group);
run.start = cpu_to_le16(n.start);
run.len = cpu_to_le16(n.len);
} else {
run.allocation_group = cpu_to_be32(n.allocation_group);
run.start = cpu_to_be16(n.start);
run.len = cpu_to_be16(n.len);
}
return run;
}
static inline befs_data_stream
fsds_to_cpu(const struct super_block *sb, befs_data_stream n)
{
befs_data_stream data;
int i;
for (i = 0; i < BEFS_NUM_DIRECT_BLOCKS; ++i)
data.direct[i] = fsrun_to_cpu(sb, n.direct[i]);
data.max_direct_range = fs64_to_cpu(sb, n.max_direct_range);
data.indirect = fsrun_to_cpu(sb, n.indirect);
data.max_indirect_range = fs64_to_cpu(sb, n.max_indirect_range);
data.double_indirect = fsrun_to_cpu(sb, n.double_indirect);
data.max_double_indirect_range = fs64_to_cpu(sb,
n.
max_double_indirect_range);
data.size = fs64_to_cpu(sb, n.size);
return data;
}
#endif //LINUX_BEFS_ENDIAN

53
fs/befs/inode.c Normal file
View File

@@ -0,0 +1,53 @@
/*
* inode.c
*
* Copyright (C) 2001 Will Dyson <will_dyson@pobox.com>
*/
#include <linux/fs.h>
#include "befs.h"
#include "inode.h"
#include "endian.h"
/*
Validates the correctness of the befs inode
Returns BEFS_OK if the inode should be used, otherwise
returns BEFS_BAD_INODE
*/
int
befs_check_inode(struct super_block *sb, befs_inode * raw_inode,
befs_blocknr_t inode)
{
u32 magic1 = fs32_to_cpu(sb, raw_inode->magic1);
befs_inode_addr ino_num = fsrun_to_cpu(sb, raw_inode->inode_num);
u32 flags = fs32_to_cpu(sb, raw_inode->flags);
/* check magic header. */
if (magic1 != BEFS_INODE_MAGIC1) {
befs_error(sb,
"Inode has a bad magic header - inode = %lu", inode);
return BEFS_BAD_INODE;
}
/*
* Sanity check2: inodes store their own block address. Check it.
*/
if (inode != iaddr2blockno(sb, &ino_num)) {
befs_error(sb, "inode blocknr field disagrees with vfs "
"VFS: %lu, Inode %lu",
inode, iaddr2blockno(sb, &ino_num));
return BEFS_BAD_INODE;
}
/*
* check flag
*/
if (!(flags & BEFS_INODE_IN_USE)) {
befs_error(sb, "inode is not used - inode = %lu", inode);
return BEFS_BAD_INODE;
}
return BEFS_OK;
}

8
fs/befs/inode.h Normal file
View File

@@ -0,0 +1,8 @@
/*
* inode.h
*
*/
int befs_check_inode(struct super_block *sb, befs_inode * raw_inode,
befs_blocknr_t inode);

83
fs/befs/io.c Normal file
View File

@@ -0,0 +1,83 @@
/*
* linux/fs/befs/io.c
*
* Copyright (C) 2001 Will Dyson <will_dyson@pobox.com
*
* Based on portions of file.c and inode.c
* by Makoto Kato (m_kato@ga2.so-net.ne.jp)
*
* Many thanks to Dominic Giampaolo, author of Practical File System
* Design with the Be File System, for such a helpful book.
*
*/
#include <linux/buffer_head.h>
#include "befs.h"
#include "io.h"
/*
* Converts befs notion of disk addr to a disk offset and uses
* linux kernel function sb_bread() to get the buffer containing
* the offset. -Will Dyson
*
*/
struct buffer_head *
befs_bread_iaddr(struct super_block *sb, befs_inode_addr iaddr)
{
struct buffer_head *bh = NULL;
befs_blocknr_t block = 0;
befs_sb_info *befs_sb = BEFS_SB(sb);
befs_debug(sb, "---> Enter befs_read_iaddr() "
"[%u, %hu, %hu]",
iaddr.allocation_group, iaddr.start, iaddr.len);
if (iaddr.allocation_group > befs_sb->num_ags) {
befs_error(sb, "BEFS: Invalid allocation group %u, max is %u",
iaddr.allocation_group, befs_sb->num_ags);
goto error;
}
block = iaddr2blockno(sb, &iaddr);
befs_debug(sb, "befs_read_iaddr: offset = %lu", block);
bh = sb_bread(sb, block);
if (bh == NULL) {
befs_error(sb, "Failed to read block %lu", block);
goto error;
}
befs_debug(sb, "<--- befs_read_iaddr()");
return bh;
error:
befs_debug(sb, "<--- befs_read_iaddr() ERROR");
return NULL;
}
struct buffer_head *
befs_bread(struct super_block *sb, befs_blocknr_t block)
{
struct buffer_head *bh = NULL;
befs_debug(sb, "---> Enter befs_read() %Lu", block);
bh = sb_bread(sb, block);
if (bh == NULL) {
befs_error(sb, "Failed to read block %lu", block);
goto error;
}
befs_debug(sb, "<--- befs_read()");
return bh;
error:
befs_debug(sb, "<--- befs_read() ERROR");
return NULL;
}

9
fs/befs/io.h Normal file
View File

@@ -0,0 +1,9 @@
/*
* io.h
*/
struct buffer_head *befs_bread_iaddr(struct super_block *sb,
befs_inode_addr iaddr);
struct buffer_head *befs_bread(struct super_block *sb, befs_blocknr_t block);

964
fs/befs/linuxvfs.c Normal file
View File

@@ -0,0 +1,964 @@
/*
* linux/fs/befs/linuxvfs.c
*
* Copyright (C) 2001 Will Dyson <will_dyson@pobox.com
*
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/nls.h>
#include <linux/buffer_head.h>
#include <linux/vfs.h>
#include <linux/parser.h>
#include <linux/namei.h>
#include "befs.h"
#include "btree.h"
#include "inode.h"
#include "datastream.h"
#include "super.h"
#include "io.h"
#include "endian.h"
MODULE_DESCRIPTION("BeOS File System (BeFS) driver");
MODULE_AUTHOR("Will Dyson");
MODULE_LICENSE("GPL");
/* The units the vfs expects inode->i_blocks to be in */
#define VFS_BLOCK_SIZE 512
static int befs_readdir(struct file *, void *, filldir_t);
static int befs_get_block(struct inode *, sector_t, struct buffer_head *, int);
static int befs_readpage(struct file *file, struct page *page);
static sector_t befs_bmap(struct address_space *mapping, sector_t block);
static struct dentry *befs_lookup(struct inode *, struct dentry *, struct nameidata *);
static void befs_read_inode(struct inode *ino);
static struct inode *befs_alloc_inode(struct super_block *sb);
static void befs_destroy_inode(struct inode *inode);
static int befs_init_inodecache(void);
static void befs_destroy_inodecache(void);
static int befs_follow_link(struct dentry *, struct nameidata *);
static void befs_put_link(struct dentry *, struct nameidata *);
static int befs_utf2nls(struct super_block *sb, const char *in, int in_len,
char **out, int *out_len);
static int befs_nls2utf(struct super_block *sb, const char *in, int in_len,
char **out, int *out_len);
static void befs_put_super(struct super_block *);
static int befs_remount(struct super_block *, int *, char *);
static int befs_statfs(struct super_block *, struct kstatfs *);
static int parse_options(char *, befs_mount_options *);
static const struct super_operations befs_sops = {
.read_inode = befs_read_inode, /* initialize & read inode */
.alloc_inode = befs_alloc_inode, /* allocate a new inode */
.destroy_inode = befs_destroy_inode, /* deallocate an inode */
.put_super = befs_put_super, /* uninit super */
.statfs = befs_statfs, /* statfs */
.remount_fs = befs_remount,
};
/* slab cache for befs_inode_info objects */
static kmem_cache_t *befs_inode_cachep;
static struct file_operations befs_dir_operations = {
.read = generic_read_dir,
.readdir = befs_readdir,
};
static struct inode_operations befs_dir_inode_operations = {
.lookup = befs_lookup,
};
static struct file_operations befs_file_operations = {
.llseek = default_llseek,
.read = generic_file_read,
.mmap = generic_file_readonly_mmap,
};
static struct address_space_operations befs_aops = {
.readpage = befs_readpage,
.sync_page = block_sync_page,
.bmap = befs_bmap,
};
static struct inode_operations befs_symlink_inode_operations = {
.readlink = generic_readlink,
.follow_link = befs_follow_link,
.put_link = befs_put_link,
};
/*
* Called by generic_file_read() to read a page of data
*
* In turn, simply calls a generic block read function and
* passes it the address of befs_get_block, for mapping file
* positions to disk blocks.
*/
static int
befs_readpage(struct file *file, struct page *page)
{
return block_read_full_page(page, befs_get_block);
}
static sector_t
befs_bmap(struct address_space *mapping, sector_t block)
{
return generic_block_bmap(mapping, block, befs_get_block);
}
/*
* Generic function to map a file position (block) to a
* disk offset (passed back in bh_result).
*
* Used by many higher level functions.
*
* Calls befs_fblock2brun() in datastream.c to do the real work.
*
* -WD 10-26-01
*/
static int
befs_get_block(struct inode *inode, sector_t block,
struct buffer_head *bh_result, int create)
{
struct super_block *sb = inode->i_sb;
befs_data_stream *ds = &BEFS_I(inode)->i_data.ds;
befs_block_run run = BAD_IADDR;
int res = 0;
ulong disk_off;
befs_debug(sb, "---> befs_get_block() for inode %lu, block %ld",
inode->i_ino, block);
if (block < 0) {
befs_error(sb, "befs_get_block() was asked for a block "
"number less than zero: block %ld in inode %lu",
block, inode->i_ino);
return -EIO;
}
if (create) {
befs_error(sb, "befs_get_block() was asked to write to "
"block %ld in inode %lu", block, inode->i_ino);
return -EPERM;
}
res = befs_fblock2brun(sb, ds, block, &run);
if (res != BEFS_OK) {
befs_error(sb,
"<--- befs_get_block() for inode %lu, block "
"%ld ERROR", inode->i_ino, block);
return -EFBIG;
}
disk_off = (ulong) iaddr2blockno(sb, &run);
map_bh(bh_result, inode->i_sb, disk_off);
befs_debug(sb, "<--- befs_get_block() for inode %lu, block %ld, "
"disk address %lu", inode->i_ino, block, disk_off);
return 0;
}
static struct dentry *
befs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
struct inode *inode = NULL;
struct super_block *sb = dir->i_sb;
befs_data_stream *ds = &BEFS_I(dir)->i_data.ds;
befs_off_t offset;
int ret;
int utfnamelen;
char *utfname;
const char *name = dentry->d_name.name;
befs_debug(sb, "---> befs_lookup() "
"name %s inode %ld", dentry->d_name.name, dir->i_ino);
/* Convert to UTF-8 */
if (BEFS_SB(sb)->nls) {
ret =
befs_nls2utf(sb, name, strlen(name), &utfname, &utfnamelen);
if (ret < 0) {
befs_debug(sb, "<--- befs_lookup() ERROR");
return ERR_PTR(ret);
}
ret = befs_btree_find(sb, ds, utfname, &offset);
kfree(utfname);
} else {
ret = befs_btree_find(sb, ds, dentry->d_name.name, &offset);
}
if (ret == BEFS_BT_NOT_FOUND) {
befs_debug(sb, "<--- befs_lookup() %s not found",
dentry->d_name.name);
return ERR_PTR(-ENOENT);
} else if (ret != BEFS_OK || offset == 0) {
befs_warning(sb, "<--- befs_lookup() Error");
return ERR_PTR(-ENODATA);
}
inode = iget(dir->i_sb, (ino_t) offset);
if (!inode)
return ERR_PTR(-EACCES);
d_add(dentry, inode);
befs_debug(sb, "<--- befs_lookup()");
return NULL;
}
static int
befs_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
struct inode *inode = filp->f_dentry->d_inode;
struct super_block *sb = inode->i_sb;
befs_data_stream *ds = &BEFS_I(inode)->i_data.ds;
befs_off_t value;
int result;
size_t keysize;
unsigned char d_type;
char keybuf[BEFS_NAME_LEN + 1];
char *nlsname;
int nlsnamelen;
const char *dirname = filp->f_dentry->d_name.name;
befs_debug(sb, "---> befs_readdir() "
"name %s, inode %ld, filp->f_pos %Ld",
dirname, inode->i_ino, filp->f_pos);
result = befs_btree_read(sb, ds, filp->f_pos, BEFS_NAME_LEN + 1,
keybuf, &keysize, &value);
if (result == BEFS_ERR) {
befs_debug(sb, "<--- befs_readdir() ERROR");
befs_error(sb, "IO error reading %s (inode %lu)",
dirname, inode->i_ino);
return -EIO;
} else if (result == BEFS_BT_END) {
befs_debug(sb, "<--- befs_readdir() END");
return 0;
} else if (result == BEFS_BT_EMPTY) {
befs_debug(sb, "<--- befs_readdir() Empty directory");
return 0;
}
d_type = DT_UNKNOWN;
/* Convert to NLS */
if (BEFS_SB(sb)->nls) {
result =
befs_utf2nls(sb, keybuf, keysize, &nlsname, &nlsnamelen);
if (result < 0) {
befs_debug(sb, "<--- befs_readdir() ERROR");
return result;
}
result = filldir(dirent, nlsname, nlsnamelen, filp->f_pos,
(ino_t) value, d_type);
kfree(nlsname);
} else {
result = filldir(dirent, keybuf, keysize, filp->f_pos,
(ino_t) value, d_type);
}
filp->f_pos++;
befs_debug(sb, "<--- befs_readdir() filp->f_pos %Ld", filp->f_pos);
return 0;
}
static struct inode *
befs_alloc_inode(struct super_block *sb)
{
struct befs_inode_info *bi;
bi = (struct befs_inode_info *)kmem_cache_alloc(befs_inode_cachep,
SLAB_KERNEL);
if (!bi)
return NULL;
return &bi->vfs_inode;
}
static void
befs_destroy_inode(struct inode *inode)
{
kmem_cache_free(befs_inode_cachep, BEFS_I(inode));
}
static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
{
struct befs_inode_info *bi = (struct befs_inode_info *) foo;
if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
SLAB_CTOR_CONSTRUCTOR) {
inode_init_once(&bi->vfs_inode);
}
}
static void
befs_read_inode(struct inode *inode)
{
struct buffer_head *bh = NULL;
befs_inode *raw_inode = NULL;
struct super_block *sb = inode->i_sb;
befs_sb_info *befs_sb = BEFS_SB(sb);
befs_inode_info *befs_ino = NULL;
befs_debug(sb, "---> befs_read_inode() " "inode = %lu", inode->i_ino);
befs_ino = BEFS_I(inode);
/* convert from vfs's inode number to befs's inode number */
befs_ino->i_inode_num = blockno2iaddr(sb, inode->i_ino);
befs_debug(sb, " real inode number [%u, %hu, %hu]",
befs_ino->i_inode_num.allocation_group,
befs_ino->i_inode_num.start, befs_ino->i_inode_num.len);
bh = befs_bread(sb, inode->i_ino);
if (!bh) {
befs_error(sb, "unable to read inode block - "
"inode = %lu", inode->i_ino);
goto unaquire_none;
}
raw_inode = (befs_inode *) bh->b_data;
befs_dump_inode(sb, raw_inode);
if (befs_check_inode(sb, raw_inode, inode->i_ino) != BEFS_OK) {
befs_error(sb, "Bad inode: %lu", inode->i_ino);
goto unaquire_bh;
}
inode->i_mode = (umode_t) fs32_to_cpu(sb, raw_inode->mode);
/*
* set uid and gid. But since current BeOS is single user OS, so
* you can change by "uid" or "gid" options.
*/
inode->i_uid = befs_sb->mount_opts.use_uid ?
befs_sb->mount_opts.uid : (uid_t) fs32_to_cpu(sb, raw_inode->uid);
inode->i_gid = befs_sb->mount_opts.use_gid ?
befs_sb->mount_opts.gid : (gid_t) fs32_to_cpu(sb, raw_inode->gid);
inode->i_nlink = 1;
/*
* BEFS's time is 64 bits, but current VFS is 32 bits...
* BEFS don't have access time. Nor inode change time. VFS
* doesn't have creation time.
* Also, the lower 16 bits of the last_modified_time and
* create_time are just a counter to help ensure uniqueness
* for indexing purposes. (PFD, page 54)
*/
inode->i_mtime.tv_sec =
fs64_to_cpu(sb, raw_inode->last_modified_time) >> 16;
inode->i_mtime.tv_nsec = 0; /* lower 16 bits are not a time */
inode->i_ctime = inode->i_mtime;
inode->i_atime = inode->i_mtime;
inode->i_blksize = befs_sb->block_size;
befs_ino->i_inode_num = fsrun_to_cpu(sb, raw_inode->inode_num);
befs_ino->i_parent = fsrun_to_cpu(sb, raw_inode->parent);
befs_ino->i_attribute = fsrun_to_cpu(sb, raw_inode->attributes);
befs_ino->i_flags = fs32_to_cpu(sb, raw_inode->flags);
if (S_ISLNK(inode->i_mode) && !(befs_ino->i_flags & BEFS_LONG_SYMLINK)){
inode->i_size = 0;
inode->i_blocks = befs_sb->block_size / VFS_BLOCK_SIZE;
strncpy(befs_ino->i_data.symlink, raw_inode->data.symlink,
BEFS_SYMLINK_LEN);
} else {
int num_blks;
befs_ino->i_data.ds =
fsds_to_cpu(sb, raw_inode->data.datastream);
num_blks = befs_count_blocks(sb, &befs_ino->i_data.ds);
inode->i_blocks =
num_blks * (befs_sb->block_size / VFS_BLOCK_SIZE);
inode->i_size = befs_ino->i_data.ds.size;
}
inode->i_mapping->a_ops = &befs_aops;
if (S_ISREG(inode->i_mode)) {
inode->i_fop = &befs_file_operations;
} else if (S_ISDIR(inode->i_mode)) {
inode->i_op = &befs_dir_inode_operations;
inode->i_fop = &befs_dir_operations;
} else if (S_ISLNK(inode->i_mode)) {
inode->i_op = &befs_symlink_inode_operations;
} else {
befs_error(sb, "Inode %lu is not a regular file, "
"directory or symlink. THAT IS WRONG! BeFS has no "
"on disk special files", inode->i_ino);
goto unaquire_bh;
}
brelse(bh);
befs_debug(sb, "<--- befs_read_inode()");
return;
unaquire_bh:
brelse(bh);
unaquire_none:
make_bad_inode(inode);
befs_debug(sb, "<--- befs_read_inode() - Bad inode");
return;
}
/* Initialize the inode cache. Called at fs setup.
*
* Taken from NFS implementation by Al Viro.
*/
static int
befs_init_inodecache(void)
{
befs_inode_cachep = kmem_cache_create("befs_inode_cache",
sizeof (struct befs_inode_info),
0, SLAB_RECLAIM_ACCOUNT,
init_once, NULL);
if (befs_inode_cachep == NULL) {
printk(KERN_ERR "befs_init_inodecache: "
"Couldn't initalize inode slabcache\n");
return -ENOMEM;
}
return 0;
}
/* Called at fs teardown.
*
* Taken from NFS implementation by Al Viro.
*/
static void
befs_destroy_inodecache(void)
{
if (kmem_cache_destroy(befs_inode_cachep))
printk(KERN_ERR "befs_destroy_inodecache: "
"not all structures were freed\n");
}
/*
* The inode of symbolic link is different to data stream.
* The data stream become link name. Unless the LONG_SYMLINK
* flag is set.
*/
static int
befs_follow_link(struct dentry *dentry, struct nameidata *nd)
{
befs_inode_info *befs_ino = BEFS_I(dentry->d_inode);
char *link;
if (befs_ino->i_flags & BEFS_LONG_SYMLINK) {
struct super_block *sb = dentry->d_sb;
befs_data_stream *data = &befs_ino->i_data.ds;
befs_off_t len = data->size;
befs_debug(sb, "Follow long symlink");
link = kmalloc(len, GFP_NOFS);
if (!link) {
link = ERR_PTR(-ENOMEM);
} else if (befs_read_lsymlink(sb, data, link, len) != len) {
kfree(link);
befs_error(sb, "Failed to read entire long symlink");
link = ERR_PTR(-EIO);
}
} else {
link = befs_ino->i_data.symlink;
}
nd_set_link(nd, link);
return 0;
}
static void befs_put_link(struct dentry *dentry, struct nameidata *nd)
{
befs_inode_info *befs_ino = BEFS_I(dentry->d_inode);
if (befs_ino->i_flags & BEFS_LONG_SYMLINK) {
char *p = nd_get_link(nd);
if (!IS_ERR(p))
kfree(p);
}
}
/*
* UTF-8 to NLS charset convert routine
*
*
* Changed 8/10/01 by Will Dyson. Now use uni2char() / char2uni() rather than
* the nls tables directly
*/
static int
befs_utf2nls(struct super_block *sb, const char *in,
int in_len, char **out, int *out_len)
{
struct nls_table *nls = BEFS_SB(sb)->nls;
int i, o;
wchar_t uni;
int unilen, utflen;
char *result;
int maxlen = in_len; /* The utf8->nls conversion can't make more chars */
befs_debug(sb, "---> utf2nls()");
if (!nls) {
befs_error(sb, "befs_utf2nls called with no NLS table loaded");
return -EINVAL;
}
*out = result = kmalloc(maxlen, GFP_NOFS);
if (!*out) {
befs_error(sb, "befs_utf2nls() cannot allocate memory");
*out_len = 0;
return -ENOMEM;
}
for (i = o = 0; i < in_len; i += utflen, o += unilen) {
/* convert from UTF-8 to Unicode */
utflen = utf8_mbtowc(&uni, &in[i], in_len - i);
if (utflen < 0) {
goto conv_err;
}
/* convert from Unicode to nls */
unilen = nls->uni2char(uni, &result[o], in_len - o);
if (unilen < 0) {
goto conv_err;
}
}
result[o] = '\0';
*out_len = o;
befs_debug(sb, "<--- utf2nls()");
return o;
conv_err:
befs_error(sb, "Name using character set %s contains a character that "
"cannot be converted to unicode.", nls->charset);
befs_debug(sb, "<--- utf2nls()");
kfree(result);
return -EILSEQ;
}
/**
* befs_nls2utf - Convert NLS string to utf8 encodeing
* @sb: Superblock
* @src: Input string buffer in NLS format
* @srclen: Length of input string in bytes
* @dest: The output string in UTF8 format
* @destlen: Length of the output buffer
*
* Converts input string @src, which is in the format of the loaded NLS map,
* into a utf8 string.
*
* The destination string @dest is allocated by this function and the caller is
* responsible for freeing it with kfree()
*
* On return, *@destlen is the length of @dest in bytes.
*
* On success, the return value is the number of utf8 characters written to
* the output buffer @dest.
*
* On Failure, a negative number coresponding to the error code is returned.
*/
static int
befs_nls2utf(struct super_block *sb, const char *in,
int in_len, char **out, int *out_len)
{
struct nls_table *nls = BEFS_SB(sb)->nls;
int i, o;
wchar_t uni;
int unilen, utflen;
char *result;
int maxlen = 3 * in_len;
befs_debug(sb, "---> nls2utf()\n");
if (!nls) {
befs_error(sb, "befs_nls2utf called with no NLS table loaded.");
return -EINVAL;
}
*out = result = kmalloc(maxlen, GFP_NOFS);
if (!*out) {
befs_error(sb, "befs_nls2utf() cannot allocate memory");
*out_len = 0;
return -ENOMEM;
}
for (i = o = 0; i < in_len; i += unilen, o += utflen) {
/* convert from nls to unicode */
unilen = nls->char2uni(&in[i], in_len - i, &uni);
if (unilen < 0) {
goto conv_err;
}
/* convert from unicode to UTF-8 */
utflen = utf8_wctomb(&result[o], uni, 3);
if (utflen <= 0) {
goto conv_err;
}
}
result[o] = '\0';
*out_len = o;
befs_debug(sb, "<--- nls2utf()");
return i;
conv_err:
befs_error(sb, "Name using charecter set %s contains a charecter that "
"cannot be converted to unicode.", nls->charset);
befs_debug(sb, "<--- nls2utf()");
kfree(result);
return -EILSEQ;
}
/**
* Use the
*
*/
enum {
Opt_uid, Opt_gid, Opt_charset, Opt_debug, Opt_err,
};
static match_table_t befs_tokens = {
{Opt_uid, "uid=%d"},
{Opt_gid, "gid=%d"},
{Opt_charset, "iocharset=%s"},
{Opt_debug, "debug"},
{Opt_err, NULL}
};
static int
parse_options(char *options, befs_mount_options * opts)
{
char *p;
substring_t args[MAX_OPT_ARGS];
int option;
/* Initialize options */
opts->uid = 0;
opts->gid = 0;
opts->use_uid = 0;
opts->use_gid = 0;
opts->iocharset = NULL;
opts->debug = 0;
if (!options)
return 1;
while ((p = strsep(&options, ",")) != NULL) {
int token;
if (!*p)
continue;
token = match_token(p, befs_tokens, args);
switch (token) {
case Opt_uid:
if (match_int(&args[0], &option))
return 0;
if (option < 0) {
printk(KERN_ERR "BeFS: Invalid uid %d, "
"using default\n", option);
break;
}
opts->uid = option;
opts->use_uid = 1;
break;
case Opt_gid:
if (match_int(&args[0], &option))
return 0;
if (option < 0) {
printk(KERN_ERR "BeFS: Invalid gid %d, "
"using default\n", option);
break;
}
opts->gid = option;
opts->use_gid = 1;
break;
case Opt_charset:
kfree(opts->iocharset);
opts->iocharset = match_strdup(&args[0]);
if (!opts->iocharset) {
printk(KERN_ERR "BeFS: allocation failure for "
"iocharset string\n");
return 0;
}
break;
case Opt_debug:
opts->debug = 1;
break;
default:
printk(KERN_ERR "BeFS: Unrecognized mount option \"%s\" "
"or missing value\n", p);
return 0;
}
}
return 1;
}
/* This function has the responsibiltiy of getting the
* filesystem ready for unmounting.
* Basicly, we free everything that we allocated in
* befs_read_inode
*/
static void
befs_put_super(struct super_block *sb)
{
if (BEFS_SB(sb)->mount_opts.iocharset) {
kfree(BEFS_SB(sb)->mount_opts.iocharset);
BEFS_SB(sb)->mount_opts.iocharset = NULL;
}
if (BEFS_SB(sb)->nls) {
unload_nls(BEFS_SB(sb)->nls);
BEFS_SB(sb)->nls = NULL;
}
if (sb->s_fs_info) {
kfree(sb->s_fs_info);
sb->s_fs_info = NULL;
}
return;
}
/* Allocate private field of the superblock, fill it.
*
* Finish filling the public superblock fields
* Make the root directory
* Load a set of NLS translations if needed.
*/
static int
befs_fill_super(struct super_block *sb, void *data, int silent)
{
struct buffer_head *bh;
befs_sb_info *befs_sb;
befs_super_block *disk_sb;
struct inode *root;
const unsigned long sb_block = 0;
const off_t x86_sb_off = 512;
sb->s_fs_info = kmalloc(sizeof (*befs_sb), GFP_KERNEL);
if (sb->s_fs_info == NULL) {
printk(KERN_ERR
"BeFS(%s): Unable to allocate memory for private "
"portion of superblock. Bailing.\n", sb->s_id);
goto unaquire_none;
}
befs_sb = BEFS_SB(sb);
memset(befs_sb, 0, sizeof(befs_sb_info));
if (!parse_options((char *) data, &befs_sb->mount_opts)) {
befs_error(sb, "cannot parse mount options");
goto unaquire_priv_sbp;
}
befs_debug(sb, "---> befs_fill_super()");
#ifndef CONFIG_BEFS_RW
if (!(sb->s_flags & MS_RDONLY)) {
befs_warning(sb,
"No write support. Marking filesystem read-only");
sb->s_flags |= MS_RDONLY;
}
#endif /* CONFIG_BEFS_RW */
/*
* Set dummy blocksize to read super block.
* Will be set to real fs blocksize later.
*
* Linux 2.4.10 and later refuse to read blocks smaller than
* the hardsect size for the device. But we also need to read at
* least 1k to get the second 512 bytes of the volume.
* -WD 10-26-01
*/
sb_min_blocksize(sb, 1024);
if (!(bh = sb_bread(sb, sb_block))) {
befs_error(sb, "unable to read superblock");
goto unaquire_priv_sbp;
}
/* account for offset of super block on x86 */
disk_sb = (befs_super_block *) bh->b_data;
if ((le32_to_cpu(disk_sb->magic1) == BEFS_SUPER_MAGIC1) ||
(be32_to_cpu(disk_sb->magic1) == BEFS_SUPER_MAGIC1)) {
befs_debug(sb, "Using PPC superblock location");
} else {
befs_debug(sb, "Using x86 superblock location");
disk_sb =
(befs_super_block *) ((void *) bh->b_data + x86_sb_off);
}
if (befs_load_sb(sb, disk_sb) != BEFS_OK)
goto unaquire_bh;
befs_dump_super_block(sb, disk_sb);
brelse(bh);
if (befs_check_sb(sb) != BEFS_OK)
goto unaquire_priv_sbp;
if( befs_sb->num_blocks > ~((sector_t)0) ) {
befs_error(sb, "blocks count: %Lu "
"is larger than the host can use",
befs_sb->num_blocks);
goto unaquire_priv_sbp;
}
/*
* set up enough so that it can read an inode
* Fill in kernel superblock fields from private sb
*/
sb->s_magic = BEFS_SUPER_MAGIC;
/* Set real blocksize of fs */
sb_set_blocksize(sb, (ulong) befs_sb->block_size);
sb->s_op = (struct super_operations *) &befs_sops;
root = iget(sb, iaddr2blockno(sb, &(befs_sb->root_dir)));
sb->s_root = d_alloc_root(root);
if (!sb->s_root) {
iput(root);
befs_error(sb, "get root inode failed");
goto unaquire_priv_sbp;
}
/* load nls library */
if (befs_sb->mount_opts.iocharset) {
befs_debug(sb, "Loading nls: %s",
befs_sb->mount_opts.iocharset);
befs_sb->nls = load_nls(befs_sb->mount_opts.iocharset);
if (!befs_sb->nls) {
befs_warning(sb, "Cannot load nls %s"
" loading default nls",
befs_sb->mount_opts.iocharset);
befs_sb->nls = load_nls_default();
}
/* load default nls if none is specified in mount options */
} else {
befs_debug(sb, "Loading default nls");
befs_sb->nls = load_nls_default();
}
return 0;
/*****************/
unaquire_bh:
brelse(bh);
unaquire_priv_sbp:
kfree(sb->s_fs_info);
unaquire_none:
sb->s_fs_info = NULL;
return -EINVAL;
}
static int
befs_remount(struct super_block *sb, int *flags, char *data)
{
if (!(*flags & MS_RDONLY))
return -EINVAL;
return 0;
}
static int
befs_statfs(struct super_block *sb, struct kstatfs *buf)
{
befs_debug(sb, "---> befs_statfs()");
buf->f_type = BEFS_SUPER_MAGIC;
buf->f_bsize = sb->s_blocksize;
buf->f_blocks = BEFS_SB(sb)->num_blocks;
buf->f_bfree = BEFS_SB(sb)->num_blocks - BEFS_SB(sb)->used_blocks;
buf->f_bavail = buf->f_bfree;
buf->f_files = 0; /* UNKNOWN */
buf->f_ffree = 0; /* UNKNOWN */
buf->f_namelen = BEFS_NAME_LEN;
befs_debug(sb, "<--- befs_statfs()");
return 0;
}
static struct super_block *
befs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name,
void *data)
{
return get_sb_bdev(fs_type, flags, dev_name, data, befs_fill_super);
}
static struct file_system_type befs_fs_type = {
.owner = THIS_MODULE,
.name = "befs",
.get_sb = befs_get_sb,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
static int __init
init_befs_fs(void)
{
int err;
printk(KERN_INFO "BeFS version: %s\n", BEFS_VERSION);
err = befs_init_inodecache();
if (err)
goto unaquire_none;
err = register_filesystem(&befs_fs_type);
if (err)
goto unaquire_inodecache;
return 0;
unaquire_inodecache:
befs_destroy_inodecache();
unaquire_none:
return err;
}
static void __exit
exit_befs_fs(void)
{
befs_destroy_inodecache();
unregister_filesystem(&befs_fs_type);
}
/*
Macros that typecheck the init and exit functions,
ensures that they are called at init and cleanup,
and eliminates warnings about unused functions.
*/
module_init(init_befs_fs)
module_exit(exit_befs_fs)

112
fs/befs/super.c Normal file
View File

@@ -0,0 +1,112 @@
/*
* super.c
*
* Copyright (C) 2001-2002 Will Dyson <will_dyson@pobox.com>
*
* Licensed under the GNU GPL. See the file COPYING for details.
*
*/
#include <linux/fs.h>
#include "befs.h"
#include "super.h"
#include "endian.h"
/**
* load_befs_sb -- Read from disk and properly byteswap all the fields
* of the befs superblock
*
*
*
*
*/
int
befs_load_sb(struct super_block *sb, befs_super_block * disk_sb)
{
befs_sb_info *befs_sb = BEFS_SB(sb);
/* Check the byte order of the filesystem */
if (le32_to_cpu(disk_sb->fs_byte_order) == BEFS_BYTEORDER_NATIVE)
befs_sb->byte_order = BEFS_BYTESEX_LE;
else if (be32_to_cpu(disk_sb->fs_byte_order) == BEFS_BYTEORDER_NATIVE)
befs_sb->byte_order = BEFS_BYTESEX_BE;
befs_sb->magic1 = fs32_to_cpu(sb, disk_sb->magic1);
befs_sb->magic2 = fs32_to_cpu(sb, disk_sb->magic2);
befs_sb->magic3 = fs32_to_cpu(sb, disk_sb->magic3);
befs_sb->block_size = fs32_to_cpu(sb, disk_sb->block_size);
befs_sb->block_shift = fs32_to_cpu(sb, disk_sb->block_shift);
befs_sb->num_blocks = fs64_to_cpu(sb, disk_sb->num_blocks);
befs_sb->used_blocks = fs64_to_cpu(sb, disk_sb->used_blocks);
befs_sb->inode_size = fs32_to_cpu(sb, disk_sb->inode_size);
befs_sb->blocks_per_ag = fs32_to_cpu(sb, disk_sb->blocks_per_ag);
befs_sb->ag_shift = fs32_to_cpu(sb, disk_sb->ag_shift);
befs_sb->num_ags = fs32_to_cpu(sb, disk_sb->num_ags);
befs_sb->log_blocks = fsrun_to_cpu(sb, disk_sb->log_blocks);
befs_sb->log_start = fs64_to_cpu(sb, disk_sb->log_start);
befs_sb->log_end = fs64_to_cpu(sb, disk_sb->log_end);
befs_sb->root_dir = fsrun_to_cpu(sb, disk_sb->root_dir);
befs_sb->indices = fsrun_to_cpu(sb, disk_sb->indices);
befs_sb->nls = NULL;
return BEFS_OK;
}
int
befs_check_sb(struct super_block *sb)
{
befs_sb_info *befs_sb = BEFS_SB(sb);
/* Check magic headers of super block */
if ((befs_sb->magic1 != BEFS_SUPER_MAGIC1)
|| (befs_sb->magic2 != BEFS_SUPER_MAGIC2)
|| (befs_sb->magic3 != BEFS_SUPER_MAGIC3)) {
befs_error(sb, "invalid magic header");
return BEFS_ERR;
}
/*
* Check blocksize of BEFS.
*
* Blocksize of BEFS is 1024, 2048, 4096 or 8192.
*/
if ((befs_sb->block_size != 1024)
&& (befs_sb->block_size != 2048)
&& (befs_sb->block_size != 4096)
&& (befs_sb->block_size != 8192)) {
befs_error(sb, "invalid blocksize: %u", befs_sb->block_size);
return BEFS_ERR;
}
if (befs_sb->block_size > PAGE_SIZE) {
befs_error(sb, "blocksize(%u) cannot be larger"
"than system pagesize(%lu)", befs_sb->block_size,
PAGE_SIZE);
return BEFS_ERR;
}
/*
* block_shift and block_size encode the same information
* in different ways as a consistency check.
*/
if ((1 << befs_sb->block_shift) != befs_sb->block_size) {
befs_error(sb, "block_shift disagrees with block_size. "
"Corruption likely.");
return BEFS_ERR;
}
if (befs_sb->log_start != befs_sb->log_end) {
befs_error(sb, "Filesystem not clean! There are blocks in the "
"journal. You must boot into BeOS and mount this volume "
"to make it clean.");
return BEFS_ERR;
}
return BEFS_OK;
}

8
fs/befs/super.h Normal file
View File

@@ -0,0 +1,8 @@
/*
* super.h
*/
int befs_load_sb(struct super_block *sb, befs_super_block * disk_sb);
int befs_check_sb(struct super_block *sb);

7
fs/bfs/Makefile Normal file
View File

@@ -0,0 +1,7 @@
#
# Makefile for BFS filesystem.
#
obj-$(CONFIG_BFS_FS) += bfs.o
bfs-objs := inode.o file.o dir.o

Some files were not shown because too many files have changed in this diff Show More