Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+343
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]
- 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'.
+24
View File
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0-only
config AFFS_FS
tristate "Amiga FFS file system support"
depends on BLOCK
select BUFFER_HEAD
select LEGACY_DIRECT_IO
help
The Fast File System (FFS) is the common file system used on hard
disks by Amiga(tm) systems since AmigaOS Version 1.3 (34.20). Say Y
if you want to be able to read and write files from and to an Amiga
FFS partition on your hard drive. Amiga floppies however cannot be
read with this driver due to an incompatibility of the floppy
controller used in an Amiga and the standard floppy controller in
PCs and workstations. Read <file:Documentation/filesystems/affs.rst>
and <file:fs/affs/Changes>.
With this driver you can also mount disk files used by Bernd
Schmidt's Un*X Amiga Emulator
(<http://www.freiburg.linux.de/~uae/>).
If you want to do this, you will also need to say Y or M to "Loop
device support", above.
To compile this file system support as a module, choose M here: the
module will be called affs. If unsure, say N.
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the Linux affs filesystem routines.
#
#ccflags-y := -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
+324
View File
@@ -0,0 +1,324 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/buffer_head.h>
#include "amigaffs.h"
#include <linux/mutex.h>
#include <linux/workqueue.h>
/* Ugly macros make the code more pretty. */
#define AFFS_BLOCK(sb, bh, blk) (AFFS_HEAD(bh)->table[AFFS_SB(sb)->s_hashsize-1-(blk)])
#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_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)
#define AFFSNAMEMAX 30U
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 {
atomic_t i_opencnt;
struct mutex i_link_lock; /* Protects internal inode access. */
struct mutex i_ext_lock; /* Protects internal inode access. */
#define i_hash_lock i_ext_lock
struct mapping_metadata_bhs i_metadata_bhs;
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 container_of(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. */
kuid_t s_uid; /* uid to override */
kgid_t s_gid; /* gid to override */
umode_t s_mode; /* mode to override */
struct buffer_head *s_root_bh; /* Cached root block. */
struct mutex 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. */
char s_volume[32]; /* Volume prefix for absolute symlinks. */
spinlock_t symlink_lock; /* protects the previous two */
struct super_block *sb; /* the VFS superblock object */
int work_queued; /* non-zero delayed work is queued */
struct delayed_work sb_work; /* superblock flush delayed work */
spinlock_t work_lock; /* protects sb_work and work_queued */
struct rcu_head rcu;
};
#define AFFS_MOUNT_SF_INTL 0x0001 /* International filesystem. */
#define AFFS_MOUNT_SF_BM_VALID 0x0002 /* Bitmap is valid. */
#define AFFS_MOUNT_SF_IMMUTABLE 0x0004 /* Protection bits cannot be changed */
#define AFFS_MOUNT_SF_QUIET 0x0008 /* chmod errors will be not reported */
#define AFFS_MOUNT_SF_SETUID 0x0010 /* Ignore Amiga uid */
#define AFFS_MOUNT_SF_SETGID 0x0020 /* Ignore Amiga gid */
#define AFFS_MOUNT_SF_SETMODE 0x0040 /* Ignore Amiga protection bits */
#define AFFS_MOUNT_SF_MUFS 0x0100 /* Use MUFS uid/gid mapping */
#define AFFS_MOUNT_SF_OFS 0x0200 /* Old filesystem */
#define AFFS_MOUNT_SF_PREFIX 0x0400 /* Buffer for prefix is allocated */
#define AFFS_MOUNT_SF_VERBOSE 0x0800 /* Talk about fs when mounting */
#define AFFS_MOUNT_SF_NO_TRUNCATE 0x1000 /* Don't truncate filenames */
#define affs_clear_opt(o, opt) (o &= ~AFFS_MOUNT_##opt)
#define affs_set_opt(o, opt) (o |= AFFS_MOUNT_##opt)
#define affs_test_opt(o, opt) ((o) & AFFS_MOUNT_##opt)
/* 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;
}
void affs_mark_sb_dirty(struct super_block *sb);
/* 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 affs_secs_to_datestamp(time64_t secs, struct affs_date *ds);
extern umode_t affs_prot_to_mode(u32 prot);
extern void affs_mode_to_prot(struct inode *inode);
__printf(3, 4)
extern void affs_error(struct super_block *sb, const char *function,
const char *fmt, ...);
__printf(3, 4)
extern void affs_warning(struct super_block *sb, const char *function,
const char *fmt, ...);
extern bool affs_nofilenametruncate(const struct dentry *dentry);
extern int affs_check_name(const unsigned char *name, int len,
bool notruncate);
extern int affs_copy_name(unsigned char *bstr, struct dentry *dentry);
struct mapping_metadata_bhs *affs_get_metadata_bhs(struct inode *inode);
/* 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 const struct export_operations affs_export_ops;
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, unsigned int);
extern int affs_unlink(struct inode *dir, struct dentry *dentry);
extern int affs_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool);
extern struct dentry *affs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t 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 mnt_idmap *idmap,
struct inode *dir, struct dentry *dentry,
const char *symname);
extern int affs_rename2(struct mnt_idmap *idmap,
struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags);
/* inode.c */
extern struct inode *affs_new_inode(struct inode *dir);
extern int affs_setattr(struct mnt_idmap *idmap,
struct dentry *dentry, struct iattr *attr);
extern void affs_evict_inode(struct inode *inode);
extern struct inode *affs_iget(struct super_block *sb,
unsigned long ino);
extern int affs_write_inode(struct inode *inode,
struct writeback_control *wbc);
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 *);
int affs_file_fsync(struct file *, loff_t, loff_t, int);
/* dir.c */
extern void affs_dir_truncate(struct inode *);
/* jump tables */
extern const struct inode_operations affs_file_inode_operations;
extern const struct inode_operations affs_dir_inode_operations;
extern const struct inode_operations affs_symlink_inode_operations;
extern const struct file_operations affs_file_operations;
extern const struct file_operations affs_file_operations_ofs;
extern const struct file_operations affs_dir_operations;
extern const struct address_space_operations affs_symlink_aops;
extern const struct address_space_operations affs_aops;
extern const struct address_space_operations affs_aops_ofs;
extern const struct dentry_operations affs_dentry_operations;
extern const struct dentry_operations affs_intl_dentry_operations;
static inline bool affs_validblock(struct super_block *sb, int block)
{
return(block >= AFFS_SB(sb)->s_reserved &&
block < AFFS_SB(sb)->s_partition_size);
}
static inline struct buffer_head *
affs_bread(struct super_block *sb, int block)
{
pr_debug("%s: %d\n", __func__, block);
if (affs_validblock(sb, block))
return sb_bread(sb, block);
return NULL;
}
static inline struct buffer_head *
affs_getblk(struct super_block *sb, int block)
{
pr_debug("%s: %d\n", __func__, block);
if (affs_validblock(sb, block))
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("%s: %d\n", __func__, block);
if (affs_validblock(sb, block)) {
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("%s: %d\n", __func__, block);
if (affs_validblock(sb, block)) {
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("%s: %lld\n", __func__, (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)
{
mutex_lock(&AFFS_I(inode)->i_link_lock);
}
static inline void
affs_unlock_link(struct inode *inode)
{
mutex_unlock(&AFFS_I(inode)->i_link_lock);
}
static inline void
affs_lock_dir(struct inode *inode)
{
mutex_lock_nested(&AFFS_I(inode)->i_hash_lock, SINGLE_DEPTH_NESTING);
}
static inline void
affs_unlock_dir(struct inode *inode)
{
mutex_unlock(&AFFS_I(inode)->i_hash_lock);
}
static inline void
affs_lock_ext(struct inode *inode)
{
mutex_lock(&AFFS_I(inode)->i_ext_lock);
}
static inline void
affs_unlock_ext(struct inode *inode)
{
mutex_unlock(&AFFS_I(inode)->i_ext_lock);
}
+543
View File
@@ -0,0 +1,543 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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 <linux/math64.h>
#include <linux/iversion.h>
#include "affs.h"
/*
* 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("%s(dir=%llu, ino=%d)\n", __func__, 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);
mmb_mark_buffer_dirty(dir_bh, &AFFS_I(dir)->i_metadata_bhs);
affs_brelse(dir_bh);
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
inode_inc_iversion(dir);
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("%s(dir=%llu, ino=%d, hashval=%d)\n", __func__, 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);
mmb_mark_buffer_dirty(bh, &AFFS_I(dir)->i_metadata_bhs);
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);
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
inode_inc_iversion(dir);
mark_inode_dirty(dir);
return retval;
}
static void
affs_fix_dcache(struct inode *inode, u32 entry_ino)
{
struct dentry *dentry;
spin_lock(&inode->i_lock);
for_each_alias(dentry, inode) {
if (entry_ino == (u32)(long)dentry->d_fsdata) {
dentry->d_fsdata = (void *)(unsigned long)inode->i_ino;
break;
}
}
spin_unlock(&inode->i_lock);
}
/* Remove header from link chain */
static int
affs_remove_link(struct dentry *dentry)
{
struct inode *dir, *inode = d_inode(dentry);
struct super_block *sb = inode->i_sb;
struct buffer_head *bh, *link_bh = NULL;
u32 link_ino, ino;
int retval;
pr_debug("%s(key=%llu)\n", __func__, 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 = affs_iget(sb, be32_to_cpu(AFFS_TAIL(sb, link_bh)->parent));
if (IS_ERR(dir)) {
retval = PTR_ERR(dir);
goto done;
}
affs_lock_dir(dir);
/*
* if there's a dentry for that block, make it
* refer to inode itself.
*/
affs_fix_dcache(inode, link_ino);
retval = affs_remove_hash(dir, link_bh);
if (retval) {
affs_unlock_dir(dir);
goto done;
}
mmb_mark_buffer_dirty(link_bh, &AFFS_I(inode)->i_metadata_bhs);
memcpy(AFFS_TAIL(sb, bh)->name, AFFS_TAIL(sb, link_bh)->name, 32);
retval = affs_insert_hash(dir, bh);
if (retval) {
affs_unlock_dir(dir);
goto done;
}
mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
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);
mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
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)
set_nlink(inode, 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 = d_inode(dentry->d_parent);
sb = dir->i_sb;
retval = -ENOENT;
inode = d_inode(dentry);
if (!inode)
goto done;
pr_debug("%s(key=%llu)\n", __func__, 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;
mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
affs_unlock_dir(dir);
if (inode->i_nlink > 1)
retval = affs_remove_link(dentry);
else
clear_nlink(inode);
affs_unlock_link(inode);
inode_set_ctime_current(inode);
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
affs_secs_to_datestamp(time64_t secs, struct affs_date *ds)
{
u32 days;
u32 minute;
s32 rem;
secs -= sys_tz.tz_minuteswest * 60 + AFFS_EPOCH_DELTA;
if (secs < 0)
secs = 0;
days = div_s64_rem(secs, 86400, &rem);
minute = rem / 60;
rem -= minute * 60;
ds->days = cpu_to_be32(days);
ds->mins = cpu_to_be32(minute);
ds->ticks = cpu_to_be32(rem * 50);
}
umode_t
affs_prot_to_mode(u32 prot)
{
umode_t mode = 0;
if (!(prot & FIBF_NOWRITE))
mode |= 0200;
if (!(prot & FIBF_NOREAD))
mode |= 0400;
if (!(prot & FIBF_NOEXECUTE))
mode |= 0100;
if (prot & FIBF_GRP_WRITE)
mode |= 0020;
if (prot & FIBF_GRP_READ)
mode |= 0040;
if (prot & FIBF_GRP_EXECUTE)
mode |= 0010;
if (prot & FIBF_OTR_WRITE)
mode |= 0002;
if (prot & FIBF_OTR_READ)
mode |= 0004;
if (prot & FIBF_OTR_EXECUTE)
mode |= 0001;
return mode;
}
void
affs_mode_to_prot(struct inode *inode)
{
u32 prot = AFFS_I(inode)->i_protect;
umode_t mode = inode->i_mode;
/*
* First, clear all RWED bits for owner, group, other.
* Then, recalculate them afresh.
*
* We'll always clear the delete-inhibit bit for the owner, as that is
* the classic single-user mode AmigaOS protection bit and we need to
* stay compatible with all scenarios.
*
* Since multi-user AmigaOS is an extension, we'll only set the
* delete-allow bit if any of the other bits in the same user class
* (group/other) are used.
*/
prot &= ~(FIBF_NOEXECUTE | FIBF_NOREAD
| FIBF_NOWRITE | FIBF_NODELETE
| FIBF_GRP_EXECUTE | FIBF_GRP_READ
| FIBF_GRP_WRITE | FIBF_GRP_DELETE
| FIBF_OTR_EXECUTE | FIBF_OTR_READ
| FIBF_OTR_WRITE | FIBF_OTR_DELETE);
/* Classic single-user AmigaOS flags. These are inverted. */
if (!(mode & 0100))
prot |= FIBF_NOEXECUTE;
if (!(mode & 0400))
prot |= FIBF_NOREAD;
if (!(mode & 0200))
prot |= FIBF_NOWRITE;
/* Multi-user extended flags. Not inverted. */
if (mode & 0010)
prot |= FIBF_GRP_EXECUTE;
if (mode & 0040)
prot |= FIBF_GRP_READ;
if (mode & 0020)
prot |= FIBF_GRP_WRITE;
if (mode & 0070)
prot |= FIBF_GRP_DELETE;
if (mode & 0001)
prot |= FIBF_OTR_EXECUTE;
if (mode & 0004)
prot |= FIBF_OTR_READ;
if (mode & 0002)
prot |= FIBF_OTR_WRITE;
if (mode & 0007)
prot |= FIBF_OTR_DELETE;
AFFS_I(inode)->i_protect = prot;
}
void
affs_error(struct super_block *sb, const char *function, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
pr_crit("error (device %s): %s(): %pV\n", sb->s_id, function, &vaf);
if (!sb_rdonly(sb))
pr_warn("Remounting filesystem read-only\n");
sb->s_flags |= SB_RDONLY;
va_end(args);
}
void
affs_warning(struct super_block *sb, const char *function, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
pr_warn("(device %s): %s(): %pV\n", sb->s_id, function, &vaf);
va_end(args);
}
bool
affs_nofilenametruncate(const struct dentry *dentry)
{
return affs_test_opt(AFFS_SB(dentry->d_sb)->s_flags, SF_NO_TRUNCATE);
}
/* Check if the name is valid for a affs object. */
int
affs_check_name(const unsigned char *name, int len, bool notruncate)
{
int i;
if (len > AFFSNAMEMAX) {
if (notruncate)
return -ENAMETOOLONG;
len = AFFSNAMEMAX;
}
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)
{
u32 len = min(dentry->d_name.len, AFFSNAMEMAX);
*bstr++ = len;
memcpy(bstr, dentry->d_name.name, len);
return len;
}
+149
View File
@@ -0,0 +1,149 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef AMIGAFFS_H
#define AMIGAFFS_H
#include <linux/types.h>
#include <asm/byteorder.h>
#define FS_OFS 0x444F5300
#define FS_FFS 0x444F5301
#define FS_INTLOFS 0x444F5302
#define FS_INTLFFS 0x444F5303
#define FS_DCOFS 0x444F5304
#define FS_DCFFS 0x444F5305
#define MUFS_FS 0x6d754653 /* 'muFS' */
#define MUFS_OFS 0x6d754600 /* 'muF\0' */
#define MUFS_FFS 0x6d754601 /* 'muF\1' */
#define MUFS_INTLOFS 0x6d754602 /* 'muF\2' */
#define MUFS_INTLFFS 0x6d754603 /* 'muF\3' */
#define MUFS_DCOFS 0x6d754604 /* 'muF\4' */
#define MUFS_DCFFS 0x6d754605 /* 'muF\5' */
#define T_SHORT 2
#define T_LIST 16
#define T_DATA 8
#define ST_LINKFILE -4
#define ST_FILE -3
#define ST_ROOT 1
#define ST_USERDIR 2
#define ST_SOFTLINK 3
#define ST_LINKDIR 4
#define AFFS_ROOT_BMAPS 25
/* Seconds since Amiga epoch of 1978/01/01 to UNIX */
#define AFFS_EPOCH_DELTA ((8 * 365 + 2) * 86400LL)
struct affs_date {
__be32 days;
__be32 mins;
__be32 ticks;
};
struct affs_short_date {
__be16 days;
__be16 mins;
__be16 ticks;
};
struct affs_root_head {
__be32 ptype;
/* The following fields are not used, but kept as documentation. */
__be32 spare1;
__be32 spare2;
__be32 hash_size;
__be32 spare3;
__be32 checksum;
__be32 hashtable[];
};
struct affs_root_tail {
__be32 bm_flag;
__be32 bm_blk[AFFS_ROOT_BMAPS];
__be32 bm_ext;
struct affs_date root_change;
u8 disk_name[32];
__be32 spare1;
__be32 spare2;
struct affs_date disk_change;
struct affs_date disk_create;
__be32 spare3;
__be32 spare4;
__be32 dcache;
__be32 stype;
};
struct affs_head {
__be32 ptype;
__be32 key;
__be32 block_count;
__be32 spare1;
__be32 first_data;
__be32 checksum;
__be32 table[];
};
struct affs_tail {
__be32 spare1;
__be16 uid;
__be16 gid;
__be32 protect;
__be32 size;
u8 comment[92];
struct affs_date change;
u8 name[32];
__be32 spare2;
__be32 original;
__be32 link_chain;
__be32 spare[5];
__be32 hash_chain;
__be32 parent;
__be32 extension;
__be32 stype;
};
struct slink_front
{
__be32 ptype;
__be32 key;
__be32 spare1[3];
__be32 checksum;
u8 symname[]; /* depends on block size */
};
struct affs_data_head
{
__be32 ptype;
__be32 key;
__be32 sequence;
__be32 size;
__be32 next;
__be32 checksum;
u8 data[]; /* depends on block size */
};
/* Permission bits */
#define FIBF_OTR_READ 0x8000
#define FIBF_OTR_WRITE 0x4000
#define FIBF_OTR_EXECUTE 0x2000
#define FIBF_OTR_DELETE 0x1000
#define FIBF_GRP_READ 0x0800
#define FIBF_GRP_WRITE 0x0400
#define FIBF_GRP_EXECUTE 0x0200
#define FIBF_GRP_DELETE 0x0100
#define FIBF_HIDDEN 0x0080
#define FIBF_SCRIPT 0x0040
#define FIBF_PURE 0x0020 /* no use under linux */
#define FIBF_ARCHIVED 0x0010 /* never set, always cleared on write */
#define FIBF_NOREAD 0x0008 /* 0 means allowed */
#define FIBF_NOWRITE 0x0004 /* 0 means allowed */
#define FIBF_NOEXECUTE 0x0002 /* 0 means allowed, ignored under linux */
#define FIBF_NODELETE 0x0001 /* 0 means allowed */
#define FIBF_OWNER 0x000F /* Bits pertaining to owner */
#define FIBF_MASK 0xEE0E /* Bits modified by Linux */
#endif
+365
View File
@@ -0,0 +1,365 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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 <linux/slab.h>
#include "affs.h"
u32
affs_count_free_blocks(struct super_block *sb)
{
struct affs_bm_info *bm;
u32 free;
int i;
pr_debug("%s()\n", __func__);
if (sb_rdonly(sb))
return 0;
mutex_lock(&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;
mutex_unlock(&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("%s(%u)\n", __func__, 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];
mutex_lock(&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);
affs_mark_sb_dirty(sb);
bm->bm_free++;
mutex_unlock(&sbi->s_bmlock);
return;
err_free:
affs_warning(sb,"affs_free_block","Trying to free block %u which is already free", block);
mutex_unlock(&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;
mutex_unlock(&sbi->s_bmlock);
return;
err_range:
affs_error(sb, "affs_free_block","Block %u outside partition", block);
}
/*
* Allocate a block in the given allocation zone.
* Since we have to byte-swap the bitmap on little-endian
* machines, this is rather expensive. Therefore 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("balloc(inode=%llu,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];
mutex_lock(&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);
affs_mark_sb_dirty(sb);
mutex_unlock(&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:
mutex_unlock(&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 & SB_RDONLY)
return 0;
if (!AFFS_ROOT_TAIL(sb, sbi->s_root_bh)->bm_flag) {
pr_notice("Bitmap invalid - mounting %s read only\n", sb->s_id);
*flags |= SB_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 = kzalloc(size, GFP_KERNEL);
if (!sbi->s_bitmap) {
pr_err("Bitmap allocation failed\n");
return -ENOMEM;
}
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) {
pr_err("Cannot read bitmap\n");
res = -EIO;
goto out;
}
if (affs_checksum_block(sb, bh)) {
pr_warn("Bitmap %u invalid - mounting %s read only.\n",
bm->bm_key, sb->s_id);
*flags |= SB_RDONLY;
goto out;
}
pr_debug("read bitmap block %d: %d\n", blk, bm->bm_key);
bm->bm_free = memweight(bh->b_data + 4, sb->s_blocksize - 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) {
pr_err("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 = memweight(bh->b_data + 4, sb->s_blocksize - 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;
}
+180
View File
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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 <linux/iversion.h>
#include <linux/filelock.h>
#include "affs.h"
struct affs_dir_data {
unsigned long ino;
u64 cookie;
};
static int affs_readdir(struct file *, struct dir_context *);
static loff_t affs_dir_llseek(struct file *file, loff_t offset, int whence)
{
struct affs_dir_data *data = file->private_data;
return generic_llseek_cookie(file, offset, whence, &data->cookie);
}
static int affs_dir_open(struct inode *inode, struct file *file)
{
struct affs_dir_data *data;
data = kzalloc_obj(struct affs_dir_data);
if (!data)
return -ENOMEM;
file->private_data = data;
return 0;
}
static int affs_dir_release(struct inode *inode, struct file *file)
{
kfree(file->private_data);
return 0;
}
const struct file_operations affs_dir_operations = {
.open = affs_dir_open,
.read = generic_read_dir,
.llseek = affs_dir_llseek,
.iterate_shared = affs_readdir,
.fsync = affs_file_fsync,
.release = affs_dir_release,
.setlease = generic_setlease,
};
/*
* directories can handle most operations...
*/
const 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_rename2,
.setattr = affs_setattr,
};
static int
affs_readdir(struct file *file, struct dir_context *ctx)
{
struct inode *inode = file_inode(file);
struct affs_dir_data *data = file->private_data;
struct super_block *sb = inode->i_sb;
struct buffer_head *dir_bh = NULL;
struct buffer_head *fh_bh = NULL;
unsigned char *name;
int namelen;
u32 i;
int hash_pos;
int chain_pos;
u32 ino;
int error = 0;
pr_debug("%s(ino=%llu,f_pos=%llx)\n", __func__, inode->i_ino, ctx->pos);
if (ctx->pos < 2) {
data->ino = 0;
if (!dir_emit_dots(file, ctx))
return 0;
}
affs_lock_dir(inode);
chain_pos = (ctx->pos - 2) & 0xffff;
hash_pos = (ctx->pos - 2) >> 16;
if (chain_pos == 0xffff) {
affs_warning(sb, "readdir", "More than 65535 entries in chain");
chain_pos = 0;
hash_pos++;
ctx->pos = ((hash_pos << 16) | chain_pos) + 2;
}
dir_bh = affs_bread(sb, inode->i_ino);
if (!dir_bh)
goto out_unlock_dir;
/* If the directory hasn't changed since the last call to readdir(),
* we can jump directly to where we left off.
*/
ino = data->ino;
if (ino && inode_eq_iversion(inode, data->cookie)) {
pr_debug("readdir() left off=%d\n", ino);
goto inside;
}
if (hash_pos >= AFFS_SB(sb)->s_hashsize)
goto done;
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);
error = -EIO;
goto out_brelse_dir;
}
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;
ctx->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);
break;
}
namelen = min(AFFS_TAIL(sb, fh_bh)->name[0],
(u8)AFFSNAMEMAX);
name = AFFS_TAIL(sb, fh_bh)->name + 1;
pr_debug("readdir(): dir_emit(\"%.*s\", ino=%u), hash=%d, f_pos=%llx\n",
namelen, name, ino, hash_pos, ctx->pos);
if (!dir_emit(ctx, name, namelen, ino, DT_UNKNOWN))
goto done;
ctx->pos++;
ino = be32_to_cpu(AFFS_TAIL(sb, fh_bh)->hash_chain);
affs_brelse(fh_bh);
fh_bh = NULL;
} while (ino);
}
done:
data->cookie = inode_query_iversion(inode);
data->ino = ino;
affs_brelse(fh_bh);
out_brelse_dir:
affs_brelse(dir_bh);
out_unlock_dir:
affs_unlock_dir(inode);
return error;
}
File diff suppressed because it is too large Load Diff
+420
View File
@@ -0,0 +1,420 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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 <linux/sched.h>
#include <linux/cred.h>
#include <linux/gfp.h>
#include "affs.h"
struct inode *affs_iget(struct super_block *sb, unsigned long ino)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
struct buffer_head *bh;
struct affs_tail *tail;
struct inode *inode;
u32 block;
u32 size;
u32 prot;
u16 id;
inode = iget_locked(sb, ino);
if (!inode)
return ERR_PTR(-ENOMEM);
if (!(inode_state_read_once(inode) & I_NEW))
return inode;
pr_debug("affs_iget(%llu)\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;
}
tail = AFFS_TAIL(sb, bh);
prot = be32_to_cpu(tail->protect);
inode->i_size = 0;
set_nlink(inode, 1);
inode->i_mode = 0;
AFFS_I(inode)->i_extcnt = 1;
AFFS_I(inode)->i_ext_last = ~1;
AFFS_I(inode)->i_protect = prot;
atomic_set(&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 (affs_test_opt(sbi->s_flags, SF_SETMODE))
inode->i_mode = sbi->s_mode;
else
inode->i_mode = affs_prot_to_mode(prot);
id = be16_to_cpu(tail->uid);
if (id == 0 || affs_test_opt(sbi->s_flags, SF_SETUID))
inode->i_uid = sbi->s_uid;
else if (id == 0xFFFF && affs_test_opt(sbi->s_flags, SF_MUFS))
i_uid_write(inode, 0);
else
i_uid_write(inode, id);
id = be16_to_cpu(tail->gid);
if (id == 0 || affs_test_opt(sbi->s_flags, SF_SETGID))
inode->i_gid = sbi->s_gid;
else if (id == 0xFFFF && affs_test_opt(sbi->s_flags, SF_MUFS))
i_gid_write(inode, 0);
else
i_gid_write(inode, id);
switch (be32_to_cpu(tail->stype)) {
case ST_ROOT:
inode->i_uid = sbi->s_uid;
inode->i_gid = sbi->s_gid;
fallthrough;
case ST_USERDIR:
if (be32_to_cpu(tail->stype) == ST_USERDIR ||
affs_test_opt(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;
/* 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;
/* ... and leave ->i_op and ->i_fop pointing to empty */
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)
set_nlink(inode, 2);
inode->i_mapping->a_ops = affs_test_opt(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_size = strlen((char *)AFFS_HEAD(bh)->table);
inode->i_mode |= S_IFLNK;
inode_nohighmem(inode);
inode->i_op = &affs_symlink_inode_operations;
inode->i_data.a_ops = &affs_symlink_aops;
break;
}
inode_set_mtime(inode,
inode_set_atime(inode, inode_set_ctime(inode, (be32_to_cpu(tail->change.days) * 86400LL + be32_to_cpu(tail->change.mins) * 60 + be32_to_cpu(tail->change.ticks) / 50 + AFFS_EPOCH_DELTA) + sys_tz.tz_minuteswest * 60, 0).tv_sec, 0).tv_sec,
0);
affs_brelse(bh);
unlock_new_inode(inode);
return inode;
bad_inode:
affs_brelse(bh);
iget_failed(inode);
return ERR_PTR(-EIO);
}
int
affs_write_inode(struct inode *inode, struct writeback_control *wbc)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *bh;
struct affs_tail *tail;
uid_t uid;
gid_t gid;
pr_debug("write_inode(%llu)\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 %llu", inode->i_ino);
return -EIO;
}
tail = AFFS_TAIL(sb, bh);
if (tail->stype == cpu_to_be32(ST_ROOT)) {
affs_secs_to_datestamp(inode_get_mtime_sec(inode),
&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);
affs_secs_to_datestamp(inode_get_mtime_sec(inode),
&tail->change);
if (!(inode->i_ino == AFFS_SB(sb)->s_root_block)) {
uid = i_uid_read(inode);
gid = i_gid_read(inode);
if (affs_test_opt(AFFS_SB(sb)->s_flags, SF_MUFS)) {
if (uid == 0 || uid == 0xFFFF)
uid = uid ^ ~0;
if (gid == 0 || gid == 0xFFFF)
gid = gid ^ ~0;
}
if (!affs_test_opt(AFFS_SB(sb)->s_flags, SF_SETUID))
tail->uid = cpu_to_be16(uid);
if (!affs_test_opt(AFFS_SB(sb)->s_flags, SF_SETGID))
tail->gid = cpu_to_be16(gid);
}
}
affs_fix_checksum(sb, bh);
mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
affs_brelse(bh);
affs_free_prealloc(inode);
return 0;
}
int
affs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr)
{
struct inode *inode = d_inode(dentry);
int error;
pr_debug("setattr(%llu,0x%x)\n", inode->i_ino, attr->ia_valid);
error = setattr_prepare(&nop_mnt_idmap, dentry, attr);
if (error)
goto out;
if (((attr->ia_valid & ATTR_UID) &&
affs_test_opt(AFFS_SB(inode->i_sb)->s_flags, SF_SETUID)) ||
((attr->ia_valid & ATTR_GID) &&
affs_test_opt(AFFS_SB(inode->i_sb)->s_flags, SF_SETGID)) ||
((attr->ia_valid & ATTR_MODE) &&
(AFFS_SB(inode->i_sb)->s_flags &
(AFFS_MOUNT_SF_SETMODE | AFFS_MOUNT_SF_IMMUTABLE)))) {
if (!affs_test_opt(AFFS_SB(inode->i_sb)->s_flags, SF_QUIET))
error = -EPERM;
goto out;
}
if ((attr->ia_valid & ATTR_SIZE) &&
attr->ia_size != i_size_read(inode)) {
error = inode_newsize_ok(inode, attr->ia_size);
if (error)
return error;
truncate_setsize(inode, attr->ia_size);
affs_truncate(inode);
}
setattr_copy(&nop_mnt_idmap, inode, attr);
mark_inode_dirty(inode);
if (attr->ia_valid & ATTR_MODE)
affs_mode_to_prot(inode);
out:
return error;
}
void
affs_evict_inode(struct inode *inode)
{
unsigned long cache_page;
pr_debug("evict_inode(ino=%llu, nlink=%u)\n",
inode->i_ino, inode->i_nlink);
truncate_inode_pages_final(&inode->i_data);
if (!inode->i_nlink) {
inode->i_size = 0;
affs_truncate(inode);
} else {
mmb_sync(&AFFS_I(inode)->i_metadata_bhs);
}
mmb_invalidate(&AFFS_I(inode)->i_metadata_bhs);
clear_inode(inode);
affs_free_prealloc(inode);
cache_page = (unsigned long)AFFS_I(inode)->i_lc;
if (cache_page) {
pr_debug("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;
if (!inode->i_nlink)
affs_free_block(inode->i_sb, inode->i_ino);
}
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;
mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
affs_brelse(bh);
inode->i_uid = current_fsuid();
inode->i_gid = current_fsgid();
inode->i_ino = block;
set_nlink(inode, 1);
simple_inode_init_ts(inode);
atomic_set(&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;
u32 block = 0;
int retval;
pr_debug("%s(dir=%llu, inode=%llu, \"%pd\", type=%d)\n", __func__,
dir->i_ino, inode->i_ino, dentry, 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:
retval = -ENOSPC;
block = affs_alloc_block(dir, dir->i_ino);
if (!block)
goto err;
retval = -EIO;
inode_bh = bh;
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));
mmb_mark_buffer_dirty(inode_bh, &AFFS_I(inode)->i_metadata_bhs);
set_nlink(inode, 2);
ihold(inode);
}
affs_fix_checksum(sb, bh);
mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
dentry->d_fsdata = (void *)(long)bh->b_blocknr;
affs_lock_dir(dir);
retval = affs_insert_hash(dir, bh);
mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
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;
}
+583
View File
@@ -0,0 +1,583 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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"
#include <linux/exportfs.h>
typedef int (*toupper_t)(int);
/* 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_test_opt(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(const struct dentry *dentry, struct qstr *qstr, toupper_t fn, bool notruncate)
{
const u8 *name = qstr->name;
unsigned long hash;
int retval;
u32 len;
retval = affs_check_name(qstr->name, qstr->len, notruncate);
if (retval)
return retval;
hash = init_name_hash(dentry);
len = min(qstr->len, AFFSNAMEMAX);
for (; len > 0; name++, len--)
hash = partial_name_hash(fn(*name), hash);
qstr->hash = end_name_hash(hash);
return 0;
}
static int
affs_hash_dentry(const struct dentry *dentry, struct qstr *qstr)
{
return __affs_hash_dentry(dentry, qstr, affs_toupper,
affs_nofilenametruncate(dentry));
}
static int
affs_intl_hash_dentry(const struct dentry *dentry, struct qstr *qstr)
{
return __affs_hash_dentry(dentry, qstr, affs_intl_toupper,
affs_nofilenametruncate(dentry));
}
static inline int __affs_compare_dentry(unsigned int len,
const char *str, const struct qstr *name, toupper_t fn,
bool notruncate)
{
const u8 *aname = str;
const u8 *bname = name->name;
/*
* 'str' is the name of an already existing dentry, so the name
* must be valid. 'name' must be validated first.
*/
if (affs_check_name(name->name, name->len, notruncate))
return 1;
/*
* If the names are longer than the allowed 30 chars,
* the excess is ignored, so their length may differ.
*/
if (len >= AFFSNAMEMAX) {
if (name->len < AFFSNAMEMAX)
return 1;
len = AFFSNAMEMAX;
} else if (len != name->len)
return 1;
for (; len > 0; len--)
if (fn(*aname++) != fn(*bname++))
return 1;
return 0;
}
static int
affs_compare_dentry(const struct dentry *dentry,
unsigned int len, const char *str, const struct qstr *name)
{
return __affs_compare_dentry(len, str, name, affs_toupper,
affs_nofilenametruncate(dentry));
}
static int
affs_intl_compare_dentry(const struct dentry *dentry,
unsigned int len, const char *str, const struct qstr *name)
{
return __affs_compare_dentry(len, str, name, affs_intl_toupper,
affs_nofilenametruncate(dentry));
}
/*
* 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 fn)
{
const u8 *name = dentry->d_name.name;
int len = dentry->d_name.len;
if (len >= AFFSNAMEMAX) {
if (*name2 < AFFSNAMEMAX)
return 0;
len = AFFSNAMEMAX;
} else if (len != *name2)
return 0;
for (name2++; len > 0; len--)
if (fn(*name++) != fn(*name2++))
return 0;
return 1;
}
int
affs_hash_name(struct super_block *sb, const u8 *name, unsigned int len)
{
toupper_t fn = affs_get_toupper(sb);
u32 hash;
hash = len = min(len, AFFSNAMEMAX);
for (; len > 0; len--)
hash = (hash * 13 + fn(*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 fn = affs_get_toupper(sb);
u32 key;
pr_debug("%s(\"%pd\")\n", __func__, dentry);
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, fn))
return bh;
key = be32_to_cpu(AFFS_TAIL(sb, bh)->hash_chain);
}
}
struct dentry *
affs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
{
struct super_block *sb = dir->i_sb;
struct buffer_head *bh;
struct inode *inode = NULL;
struct dentry *res;
pr_debug("%s(\"%pd\")\n", __func__, dentry);
affs_lock_dir(dir);
bh = affs_find_entry(dir, dentry);
if (IS_ERR(bh)) {
affs_unlock_dir(dir);
return ERR_CAST(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 = affs_iget(sb, ino);
}
res = d_splice_alias(inode, dentry);
if (!IS_ERR_OR_NULL(res))
res->d_fsdata = dentry->d_fsdata;
affs_unlock_dir(dir);
return res;
}
int
affs_unlink(struct inode *dir, struct dentry *dentry)
{
pr_debug("%s(dir=%llu, %llu \"%pd\")\n", __func__, dir->i_ino,
d_inode(dentry)->i_ino, dentry);
return affs_remove_header(dentry);
}
int
affs_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl)
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
int error;
pr_debug("%s(%llu,\"%pd\",0%ho)\n",
__func__, dir->i_ino, dentry, mode);
inode = affs_new_inode(dir);
if (!inode)
return -ENOSPC;
inode->i_mode = mode;
affs_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_test_opt(AFFS_SB(sb)->s_flags, SF_OFS) ?
&affs_aops_ofs : &affs_aops;
error = affs_add_entry(dir, inode, dentry, ST_FILE);
if (error) {
clear_nlink(inode);
iput(inode);
return error;
}
return 0;
}
struct dentry *
affs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
struct inode *inode;
int error;
pr_debug("%s(%llu,\"%pd\",0%ho)\n",
__func__, dir->i_ino, dentry, mode);
inode = affs_new_inode(dir);
if (!inode)
return ERR_PTR(-ENOSPC);
inode->i_mode = S_IFDIR | mode;
affs_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) {
clear_nlink(inode);
mark_inode_dirty(inode);
iput(inode);
return ERR_PTR(error);
}
return NULL;
}
int
affs_rmdir(struct inode *dir, struct dentry *dentry)
{
pr_debug("%s(dir=%llu, %llu \"%pd\")\n", __func__, dir->i_ino,
d_inode(dentry)->i_ino, dentry);
return affs_remove_header(dentry);
}
int
affs_symlink(struct mnt_idmap *idmap, 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("%s(%llu,\"%pd\" -> \"%s\")\n",
__func__, dir->i_ino, dentry, 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_nohighmem(inode);
inode->i_data.a_ops = &affs_symlink_aops;
inode->i_mode = S_IFLNK | 0777;
affs_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 == '/') {
struct affs_sb_info *sbi = AFFS_SB(sb);
while (*symname == '/')
symname++;
spin_lock(&sbi->symlink_lock);
while (sbi->s_volume[i]) /* Cannot overflow */
*p++ = sbi->s_volume[i++];
spin_unlock(&sbi->symlink_lock);
}
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;
inode->i_size = i + 1;
mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
affs_brelse(bh);
mark_inode_dirty(inode);
error = affs_add_entry(dir, inode, dentry, ST_SOFTLINK);
if (error)
goto err;
return 0;
err:
clear_nlink(inode);
mark_inode_dirty(inode);
iput(inode);
return error;
}
int
affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
{
struct inode *inode = d_inode(old_dentry);
pr_debug("%s(%llu, %llu, \"%pd\")\n", __func__, inode->i_ino, dir->i_ino,
dentry);
return affs_add_entry(dir, inode, dentry, ST_LINKFILE);
}
static 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;
retval = affs_check_name(new_dentry->d_name.name,
new_dentry->d_name.len,
affs_nofilenametruncate(old_dentry));
if (retval)
return retval;
/* Unlink destination if it already exists */
if (d_really_is_positive(new_dentry)) {
retval = affs_remove_header(new_dentry);
if (retval)
return retval;
}
bh = affs_bread(sb, d_inode(old_dentry)->i_ino);
if (!bh)
return -EIO;
/* 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:
mmb_mark_buffer_dirty(bh,
&AFFS_I(retval ? old_dir : new_dir)->i_metadata_bhs);
affs_brelse(bh);
return retval;
}
static int
affs_xrename(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_old = NULL;
struct buffer_head *bh_new = NULL;
int retval;
bh_old = affs_bread(sb, d_inode(old_dentry)->i_ino);
if (!bh_old)
return -EIO;
bh_new = affs_bread(sb, d_inode(new_dentry)->i_ino);
if (!bh_new) {
affs_brelse(bh_old);
return -EIO;
}
/* Remove old header from its parent directory. */
affs_lock_dir(old_dir);
retval = affs_remove_hash(old_dir, bh_old);
affs_unlock_dir(old_dir);
if (retval)
goto done;
/* Remove new header from its parent directory. */
affs_lock_dir(new_dir);
retval = affs_remove_hash(new_dir, bh_new);
affs_unlock_dir(new_dir);
if (retval)
goto done;
/* Insert old into the new directory with the new name. */
affs_copy_name(AFFS_TAIL(sb, bh_old)->name, new_dentry);
affs_fix_checksum(sb, bh_old);
affs_lock_dir(new_dir);
retval = affs_insert_hash(new_dir, bh_old);
affs_unlock_dir(new_dir);
/* Insert new into the old directory with the old name. */
affs_copy_name(AFFS_TAIL(sb, bh_new)->name, old_dentry);
affs_fix_checksum(sb, bh_new);
affs_lock_dir(old_dir);
retval = affs_insert_hash(old_dir, bh_new);
affs_unlock_dir(old_dir);
done:
mmb_mark_buffer_dirty(bh_old, &AFFS_I(new_dir)->i_metadata_bhs);
mmb_mark_buffer_dirty(bh_new, &AFFS_I(old_dir)->i_metadata_bhs);
affs_brelse(bh_old);
affs_brelse(bh_new);
return retval;
}
int affs_rename2(struct mnt_idmap *idmap, struct inode *old_dir,
struct dentry *old_dentry, struct inode *new_dir,
struct dentry *new_dentry, unsigned int flags)
{
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
return -EINVAL;
pr_debug("%s(old=%llu,\"%pd\" to new=%llu,\"%pd\")\n", __func__,
old_dir->i_ino, old_dentry, new_dir->i_ino, new_dentry);
if (flags & RENAME_EXCHANGE)
return affs_xrename(old_dir, old_dentry, new_dir, new_dentry);
return affs_rename(old_dir, old_dentry, new_dir, new_dentry);
}
static struct dentry *affs_get_parent(struct dentry *child)
{
struct inode *parent;
struct buffer_head *bh;
bh = affs_bread(child->d_sb, d_inode(child)->i_ino);
if (!bh)
return ERR_PTR(-EIO);
parent = affs_iget(child->d_sb,
be32_to_cpu(AFFS_TAIL(child->d_sb, bh)->parent));
brelse(bh);
return d_obtain_alias(parent);
}
static struct inode *affs_nfs_get_inode(struct super_block *sb, u64 ino,
u32 generation)
{
struct inode *inode;
if (!affs_validblock(sb, ino))
return ERR_PTR(-ESTALE);
inode = affs_iget(sb, ino);
if (IS_ERR(inode))
return ERR_CAST(inode);
return inode;
}
static struct dentry *affs_fh_to_dentry(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type)
{
return generic_fh_to_dentry(sb, fid, fh_len, fh_type,
affs_nfs_get_inode);
}
static struct dentry *affs_fh_to_parent(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type)
{
return generic_fh_to_parent(sb, fid, fh_len, fh_type,
affs_nfs_get_inode);
}
const struct export_operations affs_export_ops = {
.encode_fh = generic_encode_ino32_fh,
.fh_to_dentry = affs_fh_to_dentry,
.fh_to_parent = affs_fh_to_parent,
.get_parent = affs_get_parent,
};
const struct dentry_operations affs_dentry_operations = {
.d_hash = affs_hash_dentry,
.d_compare = affs_compare_dentry,
};
const struct dentry_operations affs_intl_dentry_operations = {
.d_hash = affs_intl_hash_dentry,
.d_compare = affs_intl_compare_dentry,
};
+685
View File
@@ -0,0 +1,685 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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/fs_parser.h>
#include <linux/fs_context.h>
#include <linux/magic.h>
#include <linux/sched.h>
#include <linux/cred.h>
#include <linux/slab.h>
#include <linux/writeback.h>
#include <linux/blkdev.h>
#include <linux/seq_file.h>
#include <linux/iversion.h>
#include "affs.h"
static int affs_statfs(struct dentry *dentry, struct kstatfs *buf);
static int affs_show_options(struct seq_file *m, struct dentry *root);
static void
affs_commit_super(struct super_block *sb, int wait)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
struct buffer_head *bh = sbi->s_root_bh;
struct affs_root_tail *tail = AFFS_ROOT_TAIL(sb, bh);
lock_buffer(bh);
affs_secs_to_datestamp(ktime_get_real_seconds(), &tail->disk_change);
affs_fix_checksum(sb, bh);
unlock_buffer(bh);
mark_buffer_dirty(bh);
if (wait)
sync_dirty_buffer(bh);
}
static void
affs_put_super(struct super_block *sb)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
pr_debug("%s()\n", __func__);
cancel_delayed_work_sync(&sbi->sb_work);
}
static int
affs_sync_fs(struct super_block *sb, int wait)
{
affs_commit_super(sb, wait);
return 0;
}
static void flush_superblock(struct work_struct *work)
{
struct affs_sb_info *sbi;
struct super_block *sb;
sbi = container_of(work, struct affs_sb_info, sb_work.work);
sb = sbi->sb;
spin_lock(&sbi->work_lock);
sbi->work_queued = 0;
spin_unlock(&sbi->work_lock);
affs_commit_super(sb, 1);
}
void affs_mark_sb_dirty(struct super_block *sb)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
unsigned long delay;
if (sb_rdonly(sb))
return;
spin_lock(&sbi->work_lock);
if (!sbi->work_queued) {
delay = msecs_to_jiffies(dirty_writeback_interval * 10);
queue_delayed_work(system_long_wq, &sbi->sb_work, delay);
sbi->work_queued = 1;
}
spin_unlock(&sbi->work_lock);
}
static struct kmem_cache * affs_inode_cachep;
static struct inode *affs_alloc_inode(struct super_block *sb)
{
struct affs_inode_info *i;
i = alloc_inode_sb(sb, affs_inode_cachep, GFP_KERNEL);
if (!i)
return NULL;
inode_set_iversion(&i->vfs_inode, 1);
i->i_lc = NULL;
i->i_ext_bh = NULL;
i->i_pa_cnt = 0;
mmb_init(&i->i_metadata_bhs, &i->vfs_inode.i_data);
return &i->vfs_inode;
}
static void affs_free_inode(struct inode *inode)
{
kmem_cache_free(affs_inode_cachep, AFFS_I(inode));
}
static void init_once(void *foo)
{
struct affs_inode_info *ei = (struct affs_inode_info *) foo;
mutex_init(&ei->i_link_lock);
mutex_init(&ei->i_ext_lock);
inode_init_once(&ei->vfs_inode);
}
static int __init init_inodecache(void)
{
affs_inode_cachep = kmem_cache_create("affs_inode_cache",
sizeof(struct affs_inode_info),
0, (SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT),
init_once);
if (affs_inode_cachep == NULL)
return -ENOMEM;
return 0;
}
static void destroy_inodecache(void)
{
/*
* Make sure all delayed rcu free inodes are flushed before we
* destroy cache.
*/
rcu_barrier();
kmem_cache_destroy(affs_inode_cachep);
}
static const struct super_operations affs_sops = {
.alloc_inode = affs_alloc_inode,
.free_inode = affs_free_inode,
.write_inode = affs_write_inode,
.evict_inode = affs_evict_inode,
.put_super = affs_put_super,
.sync_fs = affs_sync_fs,
.statfs = affs_statfs,
.show_options = affs_show_options,
};
enum {
Opt_bs, Opt_mode, Opt_mufs, Opt_notruncate, Opt_prefix, Opt_protect,
Opt_reserved, Opt_root, Opt_setgid, Opt_setuid,
Opt_verbose, Opt_volume, Opt_ignore,
};
struct affs_context {
kuid_t uid; /* uid to override */
kgid_t gid; /* gid to override */
unsigned int mode; /* mode to override */
unsigned int reserved; /* Number of reserved blocks */
int root_block; /* FFS root block number */
int blocksize; /* Initial device blksize */
char *prefix; /* Prefix for volumes and assigns */
char volume[32]; /* Vol. prefix for absolute symlinks */
unsigned long mount_flags; /* Options */
};
static const struct fs_parameter_spec affs_param_spec[] = {
fsparam_u32 ("bs", Opt_bs),
fsparam_u32oct ("mode", Opt_mode),
fsparam_flag ("mufs", Opt_mufs),
fsparam_flag ("nofilenametruncate", Opt_notruncate),
fsparam_string ("prefix", Opt_prefix),
fsparam_flag ("protect", Opt_protect),
fsparam_u32 ("reserved", Opt_reserved),
fsparam_u32 ("root", Opt_root),
fsparam_gid ("setgid", Opt_setgid),
fsparam_uid ("setuid", Opt_setuid),
fsparam_flag ("verbose", Opt_verbose),
fsparam_string ("volume", Opt_volume),
fsparam_flag ("grpquota", Opt_ignore),
fsparam_flag ("noquota", Opt_ignore),
fsparam_flag ("quota", Opt_ignore),
fsparam_flag ("usrquota", Opt_ignore),
{},
};
static int affs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct affs_context *ctx = fc->fs_private;
struct fs_parse_result result;
int n;
int opt;
opt = fs_parse(fc, affs_param_spec, param, &result);
if (opt < 0)
return opt;
switch (opt) {
case Opt_bs:
n = result.uint_32;
if (n != 512 && n != 1024 && n != 2048
&& n != 4096) {
pr_warn("Invalid blocksize (512, 1024, 2048, 4096 allowed)\n");
return -EINVAL;
}
ctx->blocksize = n;
break;
case Opt_mode:
ctx->mode = result.uint_32 & 0777;
affs_set_opt(ctx->mount_flags, SF_SETMODE);
break;
case Opt_mufs:
affs_set_opt(ctx->mount_flags, SF_MUFS);
break;
case Opt_notruncate:
affs_set_opt(ctx->mount_flags, SF_NO_TRUNCATE);
break;
case Opt_prefix:
kfree(ctx->prefix);
ctx->prefix = param->string;
param->string = NULL;
affs_set_opt(ctx->mount_flags, SF_PREFIX);
break;
case Opt_protect:
affs_set_opt(ctx->mount_flags, SF_IMMUTABLE);
break;
case Opt_reserved:
ctx->reserved = result.uint_32;
break;
case Opt_root:
ctx->root_block = result.uint_32;
break;
case Opt_setgid:
ctx->gid = result.gid;
affs_set_opt(ctx->mount_flags, SF_SETGID);
break;
case Opt_setuid:
ctx->uid = result.uid;
affs_set_opt(ctx->mount_flags, SF_SETUID);
break;
case Opt_verbose:
affs_set_opt(ctx->mount_flags, SF_VERBOSE);
break;
case Opt_volume:
strscpy(ctx->volume, param->string, 32);
break;
case Opt_ignore:
/* Silently ignore the quota options */
break;
default:
return -EINVAL;
}
return 0;
}
static int affs_show_options(struct seq_file *m, struct dentry *root)
{
struct super_block *sb = root->d_sb;
struct affs_sb_info *sbi = AFFS_SB(sb);
if (sb->s_blocksize)
seq_printf(m, ",bs=%lu", sb->s_blocksize);
if (affs_test_opt(sbi->s_flags, SF_SETMODE))
seq_printf(m, ",mode=%o", sbi->s_mode);
if (affs_test_opt(sbi->s_flags, SF_MUFS))
seq_puts(m, ",mufs");
if (affs_test_opt(sbi->s_flags, SF_NO_TRUNCATE))
seq_puts(m, ",nofilenametruncate");
if (affs_test_opt(sbi->s_flags, SF_PREFIX))
seq_printf(m, ",prefix=%s", sbi->s_prefix);
if (affs_test_opt(sbi->s_flags, SF_IMMUTABLE))
seq_puts(m, ",protect");
if (sbi->s_reserved != 2)
seq_printf(m, ",reserved=%u", sbi->s_reserved);
if (sbi->s_root_block != (sbi->s_reserved + sbi->s_partition_size - 1) / 2)
seq_printf(m, ",root=%u", sbi->s_root_block);
if (affs_test_opt(sbi->s_flags, SF_SETGID))
seq_printf(m, ",setgid=%u",
from_kgid_munged(&init_user_ns, sbi->s_gid));
if (affs_test_opt(sbi->s_flags, SF_SETUID))
seq_printf(m, ",setuid=%u",
from_kuid_munged(&init_user_ns, sbi->s_uid));
if (affs_test_opt(sbi->s_flags, SF_VERBOSE))
seq_puts(m, ",verbose");
if (sbi->s_volume[0])
seq_printf(m, ",volume=%s", sbi->s_volume);
return 0;
}
/* 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, struct fs_context *fc)
{
struct affs_sb_info *sbi;
struct affs_context *ctx = fc->fs_private;
struct buffer_head *root_bh = NULL;
struct buffer_head *boot_bh;
struct inode *root_inode = NULL;
int silent = fc->sb_flags & SB_SILENT;
int size, blocksize;
u32 chksum;
int num_bm;
int i, j;
int tmp_flags; /* fix remount prototype... */
u8 sig[4];
int ret;
sb->s_magic = AFFS_SUPER_MAGIC;
sb->s_op = &affs_sops;
sb->s_flags |= SB_NODIRATIME;
sb->s_time_gran = NSEC_PER_SEC;
sb->s_time_min = sys_tz.tz_minuteswest * 60 + AFFS_EPOCH_DELTA;
sb->s_time_max = 86400LL * U32_MAX + 86400 + sb->s_time_min;
sbi = kzalloc_obj(struct affs_sb_info);
if (!sbi)
return -ENOMEM;
sb->s_fs_info = sbi;
sbi->sb = sb;
mutex_init(&sbi->s_bmlock);
spin_lock_init(&sbi->symlink_lock);
spin_lock_init(&sbi->work_lock);
INIT_DELAYED_WORK(&sbi->sb_work, flush_superblock);
sbi->s_flags = ctx->mount_flags;
sbi->s_mode = ctx->mode;
sbi->s_uid = ctx->uid;
sbi->s_gid = ctx->gid;
sbi->s_reserved = ctx->reserved;
sbi->s_prefix = ctx->prefix;
ctx->prefix = NULL;
memcpy(sbi->s_volume, ctx->volume, 32);
/* N.B. after this point s_prefix must be released */
/* 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 = bdev_nr_sectors(sb->s_bdev);
pr_debug("initial blocksize=%d, #blocks=%d\n", 512, size);
if (!sb_set_blocksize(sb, PAGE_SIZE))
return -EINVAL;
/* Try to find root block. Its location depends on the block size. */
i = bdev_logical_block_size(sb->s_bdev);
j = PAGE_SIZE;
blocksize = ctx->blocksize;
if (blocksize > 0) {
i = j = blocksize;
size = size / (blocksize / 512);
}
for (blocksize = i; blocksize <= j; blocksize <<= 1, size >>= 1) {
sbi->s_root_block = ctx->root_block;
if (ctx->root_block < 0)
sbi->s_root_block = (ctx->reserved + size - 1) / 2;
pr_debug("setting blocksize to %d\n", blocksize);
if (!sb_set_blocksize(sb, blocksize))
return -EINVAL;
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("Dev %s, trying root=%u, bs=%d, "
"size=%d, reserved=%d\n",
sb->s_id,
sbi->s_root_block + num_bm,
ctx->blocksize, size, ctx->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;
goto got_root;
}
affs_brelse(root_bh);
root_bh = NULL;
}
}
if (!silent)
pr_err("No valid root block on device %s\n", sb->s_id);
return -EINVAL;
/* N.B. after this point bh must be released */
got_root:
/* Keep super block in cache */
sbi->s_root_bh = root_bh;
ctx->root_block = sbi->s_root_block;
/* Find out which kind of FS we have */
boot_bh = sb_bread(sb, 0);
if (!boot_bh) {
pr_err("Cannot read boot block\n");
return -EINVAL;
}
memcpy(sig, boot_bh->b_data, 4);
brelse(boot_bh);
chksum = be32_to_cpu(*(__be32 *)sig);
/* 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_rdonly(sb)) {
pr_notice("Dircache FS - mounting %s read only\n", sb->s_id);
sb->s_flags |= SB_RDONLY;
}
switch (chksum) {
case MUFS_FS:
case MUFS_INTLFFS:
case MUFS_DCFFS:
affs_set_opt(sbi->s_flags, SF_MUFS);
fallthrough;
case FS_INTLFFS:
case FS_DCFFS:
affs_set_opt(sbi->s_flags, SF_INTL);
break;
case MUFS_FFS:
affs_set_opt(sbi->s_flags, SF_MUFS);
break;
case FS_FFS:
break;
case MUFS_OFS:
affs_set_opt(sbi->s_flags, SF_MUFS);
fallthrough;
case FS_OFS:
affs_set_opt(sbi->s_flags, SF_OFS);
sb->s_flags |= SB_NOEXEC;
break;
case MUFS_DCOFS:
case MUFS_INTLOFS:
affs_set_opt(sbi->s_flags, SF_MUFS);
fallthrough;
case FS_DCOFS:
case FS_INTLOFS:
affs_set_opt(sbi->s_flags, SF_INTL);
affs_set_opt(sbi->s_flags, SF_OFS);
sb->s_flags |= SB_NOEXEC;
break;
default:
pr_err("Unknown filesystem on device %s: %08X\n",
sb->s_id, chksum);
return -EINVAL;
}
if (affs_test_opt(ctx->mount_flags, SF_VERBOSE)) {
u8 len = AFFS_ROOT_TAIL(sb, root_bh)->disk_name[0];
pr_notice("Mounting volume \"%.*s\": Type=%.3s\\%c, Blocksize=%d\n",
len > 31 ? 31 : len,
AFFS_ROOT_TAIL(sb, root_bh)->disk_name + 1,
sig, sig[3] + '0', blocksize);
}
sb->s_flags |= SB_NODEV | SB_NOSUID;
sbi->s_data_blksize = sb->s_blocksize;
if (affs_test_opt(sbi->s_flags, SF_OFS))
sbi->s_data_blksize -= 24;
tmp_flags = sb->s_flags;
ret = affs_init_bitmap(sb, &tmp_flags);
if (ret)
return ret;
sb->s_flags = tmp_flags;
/* set up enough so that it can read an inode */
root_inode = affs_iget(sb, ctx->root_block);
if (IS_ERR(root_inode))
return PTR_ERR(root_inode);
if (affs_test_opt(AFFS_SB(sb)->s_flags, SF_INTL))
set_default_d_op(sb, &affs_intl_dentry_operations);
else
set_default_d_op(sb, &affs_dentry_operations);
sb->s_root = d_make_root(root_inode);
if (!sb->s_root) {
pr_err("AFFS: Get root inode failed\n");
return -ENOMEM;
}
sb->s_export_op = &affs_export_ops;
pr_debug("s_flags=%lX\n", sb->s_flags);
return 0;
}
static int affs_reconfigure(struct fs_context *fc)
{
struct super_block *sb = fc->root->d_sb;
struct affs_context *ctx = fc->fs_private;
struct affs_sb_info *sbi = AFFS_SB(sb);
int res = 0;
sync_filesystem(sb);
fc->sb_flags |= SB_NODIRATIME;
flush_delayed_work(&sbi->sb_work);
/*
* NB: Historically, only mount_flags, mode, uid, gic, prefix,
* and volume are accepted during remount.
*/
sbi->s_flags = ctx->mount_flags;
sbi->s_mode = ctx->mode;
sbi->s_uid = ctx->uid;
sbi->s_gid = ctx->gid;
/* protect against readers */
spin_lock(&sbi->symlink_lock);
if (ctx->prefix) {
kfree(sbi->s_prefix);
sbi->s_prefix = ctx->prefix;
ctx->prefix = NULL;
}
memcpy(sbi->s_volume, ctx->volume, 32);
spin_unlock(&sbi->symlink_lock);
if ((bool)(fc->sb_flags & SB_RDONLY) == sb_rdonly(sb))
return 0;
if (fc->sb_flags & SB_RDONLY)
affs_free_bitmap(sb);
else
res = affs_init_bitmap(sb, &fc->sb_flags);
return res;
}
static int
affs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct super_block *sb = dentry->d_sb;
int free;
u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
pr_debug("%s() partsize=%d, reserved=%d\n",
__func__, 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;
buf->f_fsid = u64_to_fsid(id);
buf->f_namelen = AFFSNAMEMAX;
return 0;
}
static int affs_get_tree(struct fs_context *fc)
{
return get_tree_bdev(fc, affs_fill_super);
}
static void affs_kill_sb(struct super_block *sb)
{
struct affs_sb_info *sbi = AFFS_SB(sb);
kill_block_super(sb);
if (sbi) {
affs_free_bitmap(sb);
affs_brelse(sbi->s_root_bh);
kfree(sbi->s_prefix);
mutex_destroy(&sbi->s_bmlock);
kfree_rcu(sbi, rcu);
}
}
static void affs_free_fc(struct fs_context *fc)
{
struct affs_context *ctx = fc->fs_private;
kfree(ctx->prefix);
kfree(ctx);
}
static const struct fs_context_operations affs_context_ops = {
.parse_param = affs_parse_param,
.get_tree = affs_get_tree,
.reconfigure = affs_reconfigure,
.free = affs_free_fc,
};
static int affs_init_fs_context(struct fs_context *fc)
{
struct affs_context *ctx;
ctx = kzalloc_obj(struct affs_context);
if (!ctx)
return -ENOMEM;
if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) {
struct super_block *sb = fc->root->d_sb;
struct affs_sb_info *sbi = AFFS_SB(sb);
/*
* NB: historically, no options other than volume were
* preserved across a remount unless they were explicitly
* passed in.
*/
memcpy(ctx->volume, sbi->s_volume, 32);
} else {
ctx->uid = current_uid();
ctx->gid = current_gid();
ctx->reserved = 2;
ctx->root_block = -1;
ctx->blocksize = -1;
ctx->volume[0] = ':';
}
fc->ops = &affs_context_ops;
fc->fs_private = ctx;
return 0;
}
static struct file_system_type affs_fs_type = {
.owner = THIS_MODULE,
.name = "affs",
.kill_sb = affs_kill_sb,
.fs_flags = FS_REQUIRES_DEV,
.init_fs_context = affs_init_fs_context,
.parameters = affs_param_spec,
};
MODULE_ALIAS_FS("affs");
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)
+75
View File
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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_read_folio(struct file *file, struct folio *folio)
{
struct buffer_head *bh;
struct inode *inode = folio->mapping->host;
char *link = folio_address(folio);
struct slink_front *lf;
int i, j;
char c;
char lc;
pr_debug("get_link(ino=%llu)\n", inode->i_ino);
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;
if (strchr(lf->symname,':')) { /* Handle assign or volume name */
struct affs_sb_info *sbi = AFFS_SB(inode->i_sb);
char *pf;
spin_lock(&sbi->symlink_lock);
pf = sbi->s_prefix ? sbi->s_prefix : "/";
while (i < 1023 && (c = pf[i]))
link[i++] = c;
spin_unlock(&sbi->symlink_lock);
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);
folio_mark_uptodate(folio);
folio_unlock(folio);
return 0;
fail:
folio_unlock(folio);
return -EIO;
}
const struct address_space_operations affs_symlink_aops = {
.read_folio = affs_symlink_read_folio,
};
const struct inode_operations affs_symlink_inode_operations = {
.get_link = page_get_link,
.setattr = affs_setattr,
};