summaryrefslogtreecommitdiffstats
path: root/fs/affs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
commit4352b8aee98005853aa63f57d6377282de17a33f (patch)
tree27614d7a9ef05c32d089004e104364ce42324837 /fs/affs
downloadlinux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz
linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ...
Diffstat (limited to 'fs/affs')
-rw-r--r--fs/affs/Changes343
-rw-r--r--fs/affs/Kconfig24
-rw-r--r--fs/affs/Makefile10
-rw-r--r--fs/affs/affs.h322
-rw-r--r--fs/affs/amigaffs.c543
-rw-r--r--fs/affs/amigaffs.h149
-rw-r--r--fs/affs/bitmap.c365
-rw-r--r--fs/affs/dir.c180
-rw-r--r--fs/affs/file.c1017
-rw-r--r--fs/affs/inode.c417
-rw-r--r--fs/affs/namei.c582
-rw-r--r--fs/affs/super.c684
-rw-r--r--fs/affs/symlink.c75
13 files changed, 4711 insertions, 0 deletions
diff --git a/fs/affs/Changes b/fs/affs/Changes
new file mode 100644
index 000000000..b41c2c979
--- /dev/null
+++ b/fs/affs/Changes
@@ -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'.
diff --git a/fs/affs/Kconfig b/fs/affs/Kconfig
new file mode 100644
index 000000000..1ae432d26
--- /dev/null
+++ b/fs/affs/Kconfig
@@ -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.
diff --git a/fs/affs/Makefile b/fs/affs/Makefile
new file mode 100644
index 000000000..f2c811429
--- /dev/null
+++ b/fs/affs/Makefile
@@ -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
diff --git a/fs/affs/affs.h b/fs/affs/affs.h
new file mode 100644
index 000000000..d1c506c1f
--- /dev/null
+++ b/fs/affs/affs.h
@@ -0,0 +1,322 @@
+/* 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
+ 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);
+
+/* 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);
+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);
+}
diff --git a/fs/affs/amigaffs.c b/fs/affs/amigaffs.c
new file mode 100644
index 000000000..6cc0fc9a4
--- /dev/null
+++ b/fs/affs/amigaffs.c
@@ -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);
+ mark_buffer_dirty(dir_bh);
+ 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);
+ mark_buffer_dirty(bh);
+ 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;
+ }
+ mark_buffer_dirty(link_bh);
+
+ 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;
+ }
+ mark_buffer_dirty(bh);
+
+ 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(bh);
+ 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;
+ mark_buffer_dirty(bh);
+
+ 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;
+}
diff --git a/fs/affs/amigaffs.h b/fs/affs/amigaffs.h
new file mode 100644
index 000000000..da3217ab6
--- /dev/null
+++ b/fs/affs/amigaffs.h
@@ -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
diff --git a/fs/affs/bitmap.c b/fs/affs/bitmap.c
new file mode 100644
index 000000000..40bc4ce6a
--- /dev/null
+++ b/fs/affs/bitmap.c
@@ -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;
+}
diff --git a/fs/affs/dir.c b/fs/affs/dir.c
new file mode 100644
index 000000000..d2b897ebb
--- /dev/null
+++ b/fs/affs/dir.c
@@ -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;
+}
diff --git a/fs/affs/file.c b/fs/affs/file.c
new file mode 100644
index 000000000..23e088a7e
--- /dev/null
+++ b/fs/affs/file.c
@@ -0,0 +1,1017 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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 <linux/uio.h>
+#include <linux/blkdev.h>
+#include <linux/filelock.h>
+#include <linux/mpage.h>
+#include "affs.h"
+
+static struct buffer_head *affs_get_extblock_slow(struct inode *inode, u32 ext);
+
+static int
+affs_file_open(struct inode *inode, struct file *filp)
+{
+ pr_debug("open(%llu,%d)\n",
+ inode->i_ino, atomic_read(&AFFS_I(inode)->i_opencnt));
+ atomic_inc(&AFFS_I(inode)->i_opencnt);
+ return 0;
+}
+
+static int
+affs_file_release(struct inode *inode, struct file *filp)
+{
+ pr_debug("release(%llu, %d)\n",
+ inode->i_ino, atomic_read(&AFFS_I(inode)->i_opencnt));
+
+ if (atomic_dec_and_test(&AFFS_I(inode)->i_opencnt)) {
+ inode_lock(inode);
+ if (inode->i_size != AFFS_I(inode)->mmu_private)
+ affs_truncate(inode);
+ affs_free_prealloc(inode);
+ inode_unlock(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(new_bh);
+
+ 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(bh);
+
+ 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)
+ get_bh(bh);
+ 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;
+ BUG_ON(ext > AFFS_I(inode)->i_extcnt);
+ 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 */
+ BUG_ON(ext > AFFS_I(inode)->i_extcnt);
+
+ /* 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;
+ get_bh(bh);
+
+ 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("%s(%llu, %llu)\n", __func__, inode->i_ino,
+ (unsigned long long)block);
+
+ BUG_ON(block > (sector_t)0x7fffffffUL);
+
+ 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 (%llx)",
+ (unsigned long long)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 %llu",
+ (unsigned long long)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_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ return mpage_writepages(mapping, wbc, affs_get_block);
+}
+
+static int affs_read_folio(struct file *file, struct folio *folio)
+{
+ return block_read_full_folio(folio, affs_get_block);
+}
+
+static void affs_write_failed(struct address_space *mapping, loff_t to)
+{
+ struct inode *inode = mapping->host;
+
+ if (to > inode->i_size) {
+ truncate_pagecache(inode, inode->i_size);
+ affs_truncate(inode);
+ }
+}
+
+static ssize_t
+affs_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
+{
+ struct file *file = iocb->ki_filp;
+ struct address_space *mapping = file->f_mapping;
+ struct inode *inode = mapping->host;
+ size_t count = iov_iter_count(iter);
+ loff_t offset = iocb->ki_pos;
+ ssize_t ret;
+
+ if (iov_iter_rw(iter) == WRITE) {
+ loff_t size = offset + count;
+
+ if (AFFS_I(inode)->mmu_private < size)
+ return 0;
+ }
+
+ ret = blockdev_direct_IO(iocb, inode, iter, affs_get_block);
+ if (ret < 0 && iov_iter_rw(iter) == WRITE)
+ affs_write_failed(mapping, offset + count);
+ return ret;
+}
+
+static int affs_write_begin(const struct kiocb *iocb,
+ struct address_space *mapping,
+ loff_t pos, unsigned len,
+ struct folio **foliop, void **fsdata)
+{
+ int ret;
+
+ ret = cont_write_begin(iocb, mapping, pos, len, foliop, fsdata,
+ affs_get_block,
+ &AFFS_I(mapping->host)->mmu_private);
+ if (unlikely(ret))
+ affs_write_failed(mapping, pos + len);
+
+ return ret;
+}
+
+static int affs_write_end(const struct kiocb *iocb,
+ struct address_space *mapping, loff_t pos,
+ unsigned int len, unsigned int copied,
+ struct folio *folio, void *fsdata)
+{
+ struct inode *inode = mapping->host;
+ int ret;
+
+ ret = generic_write_end(iocb, mapping, pos, len, copied, folio, fsdata);
+
+ /* Clear Archived bit on file writes, as AmigaOS would do */
+ if (AFFS_I(inode)->i_protect & FIBF_ARCHIVED) {
+ AFFS_I(inode)->i_protect &= ~FIBF_ARCHIVED;
+ mark_inode_dirty(inode);
+ }
+
+ return ret;
+}
+
+static sector_t _affs_bmap(struct address_space *mapping, sector_t block)
+{
+ return generic_block_bmap(mapping,block,affs_get_block);
+}
+
+const struct address_space_operations affs_aops = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .read_folio = affs_read_folio,
+ .writepages = affs_writepages,
+ .write_begin = affs_write_begin,
+ .write_end = affs_write_end,
+ .direct_IO = affs_direct_IO,
+ .migrate_folio = buffer_migrate_folio,
+ .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 int affs_do_read_folio_ofs(struct folio *folio, size_t to, int create)
+{
+ struct inode *inode = folio->mapping->host;
+ struct super_block *sb = inode->i_sb;
+ struct buffer_head *bh;
+ size_t pos = 0;
+ size_t bidx, boff, bsize;
+ u32 tmp;
+
+ pr_debug("%s(%llu, %ld, 0, %zu)\n", __func__, inode->i_ino,
+ folio->index, to);
+ BUG_ON(to > folio_size(folio));
+ bsize = AFFS_SB(sb)->s_data_blksize;
+ tmp = folio_pos(folio);
+ bidx = tmp / bsize;
+ boff = tmp % bsize;
+
+ while (pos < to) {
+ bh = affs_bread_ino(inode, bidx, create);
+ if (IS_ERR(bh))
+ return PTR_ERR(bh);
+ tmp = min(bsize - boff, to - pos);
+ BUG_ON(pos + tmp > to || tmp > bsize);
+ memcpy_to_folio(folio, pos, AFFS_DATA(bh) + boff, tmp);
+ affs_brelse(bh);
+ bidx++;
+ pos += tmp;
+ boff = 0;
+ }
+ 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("%s(%llu, %d)\n", __func__, 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);
+ BUG_ON(boff + tmp > bsize || tmp > bsize);
+ memset(AFFS_DATA(bh) + boff, 0, tmp);
+ be32_add_cpu(&AFFS_DATA_HEAD(bh)->size, tmp);
+ affs_fix_checksum(sb, bh);
+ mark_buffer_dirty(bh);
+ 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);
+ BUG_ON(tmp > bsize);
+ 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 + 1);
+ AFFS_DATA_HEAD(bh)->size = cpu_to_be32(tmp);
+ affs_fix_checksum(sb, bh);
+ bh->b_state &= ~(1UL << BH_New);
+ mark_buffer_dirty(bh);
+ if (prev_bh) {
+ u32 tmp_next = be32_to_cpu(AFFS_DATA_HEAD(prev_bh)->next);
+
+ if (tmp_next)
+ affs_warning(sb, "extent_file_ofs",
+ "next block already set for %d (%d)",
+ bidx, tmp_next);
+ AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
+ affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp_next);
+ mark_buffer_dirty(prev_bh);
+ 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_read_folio_ofs(struct file *file, struct folio *folio)
+{
+ struct inode *inode = folio->mapping->host;
+ size_t to;
+ int err;
+
+ pr_debug("%s(%llu, %ld)\n", __func__, inode->i_ino, folio->index);
+ to = folio_size(folio);
+ if (folio_pos(folio) + to > inode->i_size) {
+ to = inode->i_size - folio_pos(folio);
+ folio_zero_segment(folio, to, folio_size(folio));
+ }
+
+ err = affs_do_read_folio_ofs(folio, to, 0);
+ if (!err)
+ folio_mark_uptodate(folio);
+ folio_unlock(folio);
+ return err;
+}
+
+static int affs_write_begin_ofs(const struct kiocb *iocb,
+ struct address_space *mapping,
+ loff_t pos, unsigned len,
+ struct folio **foliop, void **fsdata)
+{
+ struct inode *inode = mapping->host;
+ struct folio *folio;
+ pgoff_t index;
+ int err = 0;
+
+ pr_debug("%s(%llu, %llu, %llu)\n", __func__, inode->i_ino, pos,
+ pos + len);
+ if (pos > AFFS_I(inode)->mmu_private) {
+ /* XXX: this probably leaves a too-big i_size in case of
+ * failure. Should really be updating i_size at write_end time
+ */
+ err = affs_extent_file_ofs(inode, pos);
+ if (err)
+ return err;
+ }
+
+ index = pos >> PAGE_SHIFT;
+ folio = __filemap_get_folio(mapping, index, FGP_WRITEBEGIN,
+ mapping_gfp_mask(mapping));
+ if (IS_ERR(folio))
+ return PTR_ERR(folio);
+ *foliop = folio;
+
+ if (folio_test_uptodate(folio))
+ return 0;
+
+ /* XXX: inefficient but safe in the face of short writes */
+ err = affs_do_read_folio_ofs(folio, folio_size(folio), 1);
+ if (err) {
+ folio_unlock(folio);
+ folio_put(folio);
+ }
+ return err;
+}
+
+static int affs_write_end_ofs(const struct kiocb *iocb,
+ struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned copied,
+ struct folio *folio, void *fsdata)
+{
+ struct inode *inode = mapping->host;
+ struct super_block *sb = inode->i_sb;
+ struct buffer_head *bh, *prev_bh;
+ char *data;
+ u32 bidx, boff, bsize;
+ unsigned from, to;
+ u32 tmp;
+ int written;
+
+ from = pos & (PAGE_SIZE - 1);
+ to = from + len;
+ /*
+ * XXX: not sure if this can handle short copies (len < copied), but
+ * we don't have to, because the folio should always be uptodate here,
+ * due to write_begin.
+ */
+
+ pr_debug("%s(%llu, %llu, %llu)\n", __func__, inode->i_ino, pos,
+ pos + len);
+ bsize = AFFS_SB(sb)->s_data_blksize;
+ data = folio_address(folio);
+
+ bh = NULL;
+ written = 0;
+ tmp = (folio->index << PAGE_SHIFT) + from;
+ bidx = tmp / bsize;
+ boff = tmp % bsize;
+ if (boff) {
+ bh = affs_bread_ino(inode, bidx, 0);
+ if (IS_ERR(bh)) {
+ written = PTR_ERR(bh);
+ goto err_first_bh;
+ }
+ tmp = min(bsize - boff, to - from);
+ BUG_ON(boff + tmp > bsize || tmp > bsize);
+ memcpy(AFFS_DATA(bh) + boff, data + from, tmp);
+ AFFS_DATA_HEAD(bh)->size = cpu_to_be32(
+ max(boff + tmp, be32_to_cpu(AFFS_DATA_HEAD(bh)->size)));
+ affs_fix_checksum(sb, bh);
+ mark_buffer_dirty(bh);
+ written += tmp;
+ from += tmp;
+ bidx++;
+ } else if (bidx) {
+ bh = affs_bread_ino(inode, bidx - 1, 0);
+ if (IS_ERR(bh)) {
+ written = PTR_ERR(bh);
+ goto err_first_bh;
+ }
+ }
+ while (from + bsize <= to) {
+ prev_bh = bh;
+ bh = affs_getemptyblk_ino(inode, bidx);
+ if (IS_ERR(bh))
+ goto err_bh;
+ 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 + 1);
+ 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_next = be32_to_cpu(AFFS_DATA_HEAD(prev_bh)->next);
+
+ if (tmp_next)
+ affs_warning(sb, "commit_write_ofs",
+ "next block already set for %d (%d)",
+ bidx, tmp_next);
+ AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
+ affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp_next);
+ mark_buffer_dirty(prev_bh);
+ }
+ }
+ affs_brelse(prev_bh);
+ affs_fix_checksum(sb, bh);
+ mark_buffer_dirty(bh);
+ written += bsize;
+ from += bsize;
+ bidx++;
+ }
+ if (from < to) {
+ prev_bh = bh;
+ bh = affs_bread_ino(inode, bidx, 1);
+ if (IS_ERR(bh))
+ goto err_bh;
+ tmp = min(bsize, to - from);
+ BUG_ON(tmp > bsize);
+ 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 + 1);
+ 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_next = be32_to_cpu(AFFS_DATA_HEAD(prev_bh)->next);
+
+ if (tmp_next)
+ affs_warning(sb, "commit_write_ofs",
+ "next block already set for %d (%d)",
+ bidx, tmp_next);
+ AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
+ affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp_next);
+ mark_buffer_dirty(prev_bh);
+ }
+ } 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(bh);
+ written += tmp;
+ from += tmp;
+ bidx++;
+ }
+ folio_mark_uptodate(folio);
+
+done:
+ affs_brelse(bh);
+ tmp = (folio->index << PAGE_SHIFT) + from;
+ if (tmp > inode->i_size)
+ inode->i_size = AFFS_I(inode)->mmu_private = tmp;
+
+ /* Clear Archived bit on file writes, as AmigaOS would do */
+ if (AFFS_I(inode)->i_protect & FIBF_ARCHIVED) {
+ AFFS_I(inode)->i_protect &= ~FIBF_ARCHIVED;
+ mark_inode_dirty(inode);
+ }
+
+err_first_bh:
+ folio_unlock(folio);
+ folio_put(folio);
+
+ return written;
+
+err_bh:
+ bh = prev_bh;
+ if (!written)
+ written = PTR_ERR(bh);
+ goto done;
+}
+
+const struct address_space_operations affs_aops_ofs = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .read_folio = affs_read_folio_ofs,
+ //.writepages = affs_writepages_ofs,
+ .write_begin = affs_write_begin_ofs,
+ .write_end = affs_write_end_ofs,
+ .migrate_folio = filemap_migrate_folio,
+};
+
+/* Free any preallocated blocks. */
+
+void
+affs_free_prealloc(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+
+ pr_debug("free_prealloc(ino=%llu)\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("truncate(inode=%llu, oldsize=%llu, newsize=%llu)\n",
+ inode->i_ino, AFFS_I(inode)->mmu_private, 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 folio *folio;
+ void *fsdata = NULL;
+ loff_t isize = inode->i_size;
+ int res;
+
+ res = mapping->a_ops->write_begin(NULL, mapping, isize, 0, &folio, &fsdata);
+ if (!res)
+ res = mapping->a_ops->write_end(NULL, mapping, isize, 0, 0, folio, fsdata);
+ else
+ inode->i_size = AFFS_I(inode)->mmu_private;
+ 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 (%ld)",
+ 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;
+ AFFS_HEAD(ext_bh)->block_count = cpu_to_be32(i);
+ 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(ext_bh);
+ 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_test_opt(AFFS_SB(sb)->s_flags, SF_OFS)) {
+ struct buffer_head *bh = affs_bread_ino(inode, last_blk, 0);
+ u32 tmp;
+ if (IS_ERR(bh)) {
+ affs_warning(sb, "truncate",
+ "unexpected read error for last block %u (%ld)",
+ ext, PTR_ERR(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);
+}
+
+int affs_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
+{
+ struct inode *inode = filp->f_mapping->host;
+ int ret, err;
+
+ err = file_write_and_wait_range(filp, start, end);
+ if (err)
+ return err;
+
+ inode_lock(inode);
+ ret = write_inode_now(inode, 0);
+ err = sync_blockdev(inode->i_sb->s_bdev);
+ if (!ret)
+ ret = err;
+ inode_unlock(inode);
+ return ret;
+}
+const struct file_operations affs_file_operations = {
+ .llseek = generic_file_llseek,
+ .read_iter = generic_file_read_iter,
+ .write_iter = generic_file_write_iter,
+ .mmap_prepare = generic_file_mmap_prepare,
+ .open = affs_file_open,
+ .release = affs_file_release,
+ .fsync = affs_file_fsync,
+ .splice_read = filemap_splice_read,
+ .setlease = generic_setlease,
+};
+
+const struct inode_operations affs_file_inode_operations = {
+ .setattr = affs_setattr,
+};
diff --git a/fs/affs/inode.c b/fs/affs/inode.c
new file mode 100644
index 000000000..d4a3f381c
--- /dev/null
+++ b/fs/affs/inode.c
@@ -0,0 +1,417 @@
+// 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);
+ mark_buffer_dirty(bh);
+ 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);
+ }
+
+ 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;
+ mark_buffer_dirty(bh);
+ 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));
+ mark_buffer_dirty(inode_bh);
+ set_nlink(inode, 2);
+ ihold(inode);
+ }
+ affs_fix_checksum(sb, bh);
+ mark_buffer_dirty(bh);
+ dentry->d_fsdata = (void *)(long)bh->b_blocknr;
+
+ affs_lock_dir(dir);
+ retval = affs_insert_hash(dir, bh);
+ mark_buffer_dirty(bh);
+ 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;
+}
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
new file mode 100644
index 000000000..6cb52efaf
--- /dev/null
+++ b/fs/affs/namei.c
@@ -0,0 +1,582 @@
+// 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)
+{
+ 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 = 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;
+ mark_buffer_dirty(bh);
+ 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:
+ mark_buffer_dirty(bh);
+ 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:
+ mark_buffer_dirty(bh_old);
+ mark_buffer_dirty(bh_new);
+ 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,
+};
diff --git a/fs/affs/super.c b/fs/affs/super.c
new file mode 100644
index 000000000..ed8225b3c
--- /dev/null
+++ b/fs/affs/super.c
@@ -0,0 +1,684 @@
+// 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_dfl_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;
+
+ 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)
diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c
new file mode 100644
index 000000000..4994abb97
--- /dev/null
+++ b/fs/affs/symlink.c
@@ -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,
+};