summaryrefslogtreecommitdiffstats
path: root/fs/ufs/dir.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /fs/ufs/dir.c
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'fs/ufs/dir.c')
-rw-r--r--fs/ufs/dir.c658
1 files changed, 658 insertions, 0 deletions
diff --git a/fs/ufs/dir.c b/fs/ufs/dir.c
new file mode 100644
index 000000000..e62fe5667
--- /dev/null
+++ b/fs/ufs/dir.c
@@ -0,0 +1,658 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/ufs/ufs_dir.c
+ *
+ * Copyright (C) 1996
+ * Adrian Rodriguez (adrian@franklins-tower.rutgers.edu)
+ * Laboratory for Computer Science Research Computing Facility
+ * Rutgers, The State University of New Jersey
+ *
+ * swab support by Francois-Rene Rideau <fare@tunes.org> 19970406
+ *
+ * 4.4BSD (FreeBSD) support added on February 1st 1998 by
+ * Niels Kristian Bech Jensen <nkbj@image.dk> partially based
+ * on code by Martin von Loewis <martin@mira.isdn.cs.tu-berlin.de>.
+ *
+ * Migration to usage of "page cache" on May 2006 by
+ * Evgeniy Dushistov <dushistov@mail.ru> based on ext2 code base.
+ */
+
+#include <linux/time.h>
+#include <linux/fs.h>
+#include <linux/filelock.h>
+#include <linux/swap.h>
+#include <linux/iversion.h>
+
+#include "ufs_fs.h"
+#include "ufs.h"
+#include "swab.h"
+#include "util.h"
+
+/*
+ * NOTE! unlike strncmp, ufs_match returns 1 for success, 0 for failure.
+ *
+ * len <= UFS_MAXNAMLEN and de != NULL are guaranteed by caller.
+ */
+static inline int ufs_match(struct super_block *sb, int len,
+ const unsigned char *name, struct ufs_dir_entry *de)
+{
+ if (len != ufs_get_de_namlen(sb, de))
+ return 0;
+ if (!de->d_ino)
+ return 0;
+ return !memcmp(name, de->d_name, len);
+}
+
+static void ufs_commit_chunk(struct folio *folio, loff_t pos, unsigned len)
+{
+ struct address_space *mapping = folio->mapping;
+ struct inode *dir = mapping->host;
+
+ inode_inc_iversion(dir);
+ block_write_end(pos, len, len, folio);
+ if (pos+len > dir->i_size) {
+ i_size_write(dir, pos+len);
+ mark_inode_dirty(dir);
+ }
+ folio_unlock(folio);
+}
+
+static int ufs_handle_dirsync(struct inode *dir)
+{
+ int err;
+
+ err = filemap_write_and_wait(dir->i_mapping);
+ if (!err)
+ err = sync_inode_metadata(dir, 1);
+ return err;
+}
+
+ino_t ufs_inode_by_name(struct inode *dir, const struct qstr *qstr)
+{
+ ino_t res = 0;
+ struct ufs_dir_entry *de;
+ struct folio *folio;
+
+ de = ufs_find_entry(dir, qstr, &folio);
+ if (de) {
+ res = fs32_to_cpu(dir->i_sb, de->d_ino);
+ folio_release_kmap(folio, de);
+ }
+ return res;
+}
+
+
+int ufs_set_link(struct inode *dir, struct ufs_dir_entry *de,
+ struct folio *folio, struct inode *inode,
+ bool update_times)
+{
+ loff_t pos = folio_pos(folio) + offset_in_folio(folio, de);
+ unsigned len = fs16_to_cpu(dir->i_sb, de->d_reclen);
+ int err;
+
+ folio_lock(folio);
+ err = ufs_prepare_chunk(folio, pos, len);
+ if (unlikely(err)) {
+ folio_unlock(folio);
+ return err;
+ }
+
+ de->d_ino = cpu_to_fs32(dir->i_sb, inode->i_ino);
+ ufs_set_de_type(dir->i_sb, de, inode->i_mode);
+
+ ufs_commit_chunk(folio, pos, len);
+ if (update_times)
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ mark_inode_dirty(dir);
+ return ufs_handle_dirsync(dir);
+}
+
+static bool ufs_check_folio(struct folio *folio, char *kaddr)
+{
+ struct inode *dir = folio->mapping->host;
+ struct super_block *sb = dir->i_sb;
+ unsigned offs, rec_len;
+ unsigned limit = folio_size(folio);
+ const unsigned chunk_mask = UFS_SB(sb)->s_uspi->s_dirblksize - 1;
+ struct ufs_dir_entry *p;
+ char *error;
+
+ if (dir->i_size < folio_pos(folio) + limit) {
+ limit = offset_in_folio(folio, dir->i_size);
+ if (limit & chunk_mask)
+ goto Ebadsize;
+ if (!limit)
+ goto out;
+ }
+ for (offs = 0; offs <= limit - UFS_DIR_REC_LEN(1); offs += rec_len) {
+ p = (struct ufs_dir_entry *)(kaddr + offs);
+ rec_len = fs16_to_cpu(sb, p->d_reclen);
+
+ if (rec_len < UFS_DIR_REC_LEN(1))
+ goto Eshort;
+ if (rec_len & 3)
+ goto Ealign;
+ if (rec_len < UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, p)))
+ goto Enamelen;
+ if (((offs + rec_len - 1) ^ offs) & ~chunk_mask)
+ goto Espan;
+ if (fs32_to_cpu(sb, p->d_ino) > (UFS_SB(sb)->s_uspi->s_ipg *
+ UFS_SB(sb)->s_uspi->s_ncg))
+ goto Einumber;
+ }
+ if (offs != limit)
+ goto Eend;
+out:
+ folio_set_checked(folio);
+ return true;
+
+ /* Too bad, we had an error */
+
+Ebadsize:
+ ufs_error(sb, __func__,
+ "size of directory #%llu is not a multiple of chunk size",
+ dir->i_ino
+ );
+ goto fail;
+Eshort:
+ error = "rec_len is smaller than minimal";
+ goto bad_entry;
+Ealign:
+ error = "unaligned directory entry";
+ goto bad_entry;
+Enamelen:
+ error = "rec_len is too small for name_len";
+ goto bad_entry;
+Espan:
+ error = "directory entry across blocks";
+ goto bad_entry;
+Einumber:
+ error = "inode out of bounds";
+bad_entry:
+ ufs_error(sb, __func__, "bad entry in directory #%llu: %s - "
+ "offset=%llu, rec_len=%d, name_len=%d",
+ dir->i_ino, error, folio_pos(folio) + offs,
+ rec_len, ufs_get_de_namlen(sb, p));
+ goto fail;
+Eend:
+ p = (struct ufs_dir_entry *)(kaddr + offs);
+ ufs_error(sb, __func__,
+ "entry in directory #%llu spans the page boundary"
+ "offset=%llu",
+ dir->i_ino, folio_pos(folio) + offs);
+fail:
+ return false;
+}
+
+static void *ufs_get_folio(struct inode *dir, unsigned long n,
+ struct folio **foliop)
+{
+ struct address_space *mapping = dir->i_mapping;
+ struct folio *folio = read_mapping_folio(mapping, n, NULL);
+ void *kaddr;
+
+ if (IS_ERR(folio))
+ return ERR_CAST(folio);
+ kaddr = kmap_local_folio(folio, 0);
+ if (unlikely(!folio_test_checked(folio))) {
+ if (!ufs_check_folio(folio, kaddr))
+ goto fail;
+ }
+ *foliop = folio;
+ return kaddr;
+
+fail:
+ folio_release_kmap(folio, kaddr);
+ return ERR_PTR(-EIO);
+}
+
+/*
+ * Return the offset into page `page_nr' of the last valid
+ * byte in that page, plus one.
+ */
+static unsigned
+ufs_last_byte(struct inode *inode, unsigned long page_nr)
+{
+ unsigned last_byte = inode->i_size;
+
+ last_byte -= page_nr << PAGE_SHIFT;
+ if (last_byte > PAGE_SIZE)
+ last_byte = PAGE_SIZE;
+ return last_byte;
+}
+
+static inline struct ufs_dir_entry *
+ufs_next_entry(struct super_block *sb, struct ufs_dir_entry *p)
+{
+ return (struct ufs_dir_entry *)((char *)p +
+ fs16_to_cpu(sb, p->d_reclen));
+}
+
+struct ufs_dir_entry *ufs_dotdot(struct inode *dir, struct folio **foliop)
+{
+ struct ufs_dir_entry *de = ufs_get_folio(dir, 0, foliop);
+
+ if (!IS_ERR(de))
+ return ufs_next_entry(dir->i_sb, de);
+
+ return NULL;
+}
+
+/*
+ * ufs_find_entry()
+ *
+ * finds an entry in the specified directory with the wanted name. It
+ * returns the page in which the entry was found, and the entry itself
+ * (as a parameter - res_dir). Page is returned mapped and unlocked.
+ * Entry is guaranteed to be valid.
+ */
+struct ufs_dir_entry *ufs_find_entry(struct inode *dir, const struct qstr *qstr,
+ struct folio **foliop)
+{
+ struct super_block *sb = dir->i_sb;
+ const unsigned char *name = qstr->name;
+ int namelen = qstr->len;
+ unsigned reclen = UFS_DIR_REC_LEN(namelen);
+ unsigned long start, n;
+ unsigned long npages = dir_pages(dir);
+ struct ufs_inode_info *ui = UFS_I(dir);
+ struct ufs_dir_entry *de;
+
+ UFSD("ENTER, dir_ino %llu, name %s, namlen %u\n", dir->i_ino, name, namelen);
+
+ if (npages == 0 || namelen > UFS_MAXNAMLEN)
+ goto out;
+
+ start = ui->i_dir_start_lookup;
+
+ if (start >= npages)
+ start = 0;
+ n = start;
+ do {
+ char *kaddr = ufs_get_folio(dir, n, foliop);
+
+ if (!IS_ERR(kaddr)) {
+ de = (struct ufs_dir_entry *)kaddr;
+ kaddr += ufs_last_byte(dir, n) - reclen;
+ while ((char *) de <= kaddr) {
+ if (ufs_match(sb, namelen, name, de))
+ goto found;
+ de = ufs_next_entry(sb, de);
+ }
+ folio_release_kmap(*foliop, kaddr);
+ }
+ if (++n >= npages)
+ n = 0;
+ } while (n != start);
+out:
+ return NULL;
+
+found:
+ ui->i_dir_start_lookup = n;
+ return de;
+}
+
+/*
+ * Parent is locked.
+ */
+int ufs_add_link(struct dentry *dentry, struct inode *inode)
+{
+ struct inode *dir = d_inode(dentry->d_parent);
+ const unsigned char *name = dentry->d_name.name;
+ int namelen = dentry->d_name.len;
+ struct super_block *sb = dir->i_sb;
+ unsigned reclen = UFS_DIR_REC_LEN(namelen);
+ const unsigned int chunk_size = UFS_SB(sb)->s_uspi->s_dirblksize;
+ unsigned short rec_len, name_len;
+ struct folio *folio = NULL;
+ struct ufs_dir_entry *de;
+ unsigned long npages = dir_pages(dir);
+ unsigned long n;
+ loff_t pos;
+ int err;
+
+ UFSD("ENTER, name %s, namelen %u\n", name, namelen);
+
+ /*
+ * We take care of directory expansion in the same loop.
+ * This code plays outside i_size, so it locks the folio
+ * to protect that region.
+ */
+ for (n = 0; n <= npages; n++) {
+ char *kaddr = ufs_get_folio(dir, n, &folio);
+ char *dir_end;
+
+ if (IS_ERR(kaddr))
+ return PTR_ERR(kaddr);
+ folio_lock(folio);
+ dir_end = kaddr + ufs_last_byte(dir, n);
+ de = (struct ufs_dir_entry *)kaddr;
+ kaddr += folio_size(folio) - reclen;
+ while ((char *)de <= kaddr) {
+ if ((char *)de == dir_end) {
+ /* We hit i_size */
+ name_len = 0;
+ rec_len = chunk_size;
+ de->d_reclen = cpu_to_fs16(sb, chunk_size);
+ de->d_ino = 0;
+ goto got_it;
+ }
+ if (de->d_reclen == 0) {
+ ufs_error(dir->i_sb, __func__,
+ "zero-length directory entry");
+ err = -EIO;
+ goto out_unlock;
+ }
+ err = -EEXIST;
+ if (ufs_match(sb, namelen, name, de))
+ goto out_unlock;
+ name_len = UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, de));
+ rec_len = fs16_to_cpu(sb, de->d_reclen);
+ if (!de->d_ino && rec_len >= reclen)
+ goto got_it;
+ if (rec_len >= name_len + reclen)
+ goto got_it;
+ de = (struct ufs_dir_entry *) ((char *) de + rec_len);
+ }
+ folio_unlock(folio);
+ folio_release_kmap(folio, kaddr);
+ }
+ BUG();
+ return -EINVAL;
+
+got_it:
+ pos = folio_pos(folio) + offset_in_folio(folio, de);
+ err = ufs_prepare_chunk(folio, pos, rec_len);
+ if (err)
+ goto out_unlock;
+ if (de->d_ino) {
+ struct ufs_dir_entry *de1 =
+ (struct ufs_dir_entry *) ((char *) de + name_len);
+ de1->d_reclen = cpu_to_fs16(sb, rec_len - name_len);
+ de->d_reclen = cpu_to_fs16(sb, name_len);
+
+ de = de1;
+ }
+
+ ufs_set_de_namlen(sb, de, namelen);
+ memcpy(de->d_name, name, namelen + 1);
+ de->d_ino = cpu_to_fs32(sb, inode->i_ino);
+ ufs_set_de_type(sb, de, inode->i_mode);
+
+ ufs_commit_chunk(folio, pos, rec_len);
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+
+ mark_inode_dirty(dir);
+ err = ufs_handle_dirsync(dir);
+ /* OFFSET_CACHE */
+out_put:
+ folio_release_kmap(folio, de);
+ return err;
+out_unlock:
+ folio_unlock(folio);
+ goto out_put;
+}
+
+static inline unsigned
+ufs_validate_entry(struct super_block *sb, char *base,
+ unsigned offset, unsigned mask)
+{
+ struct ufs_dir_entry *de = (struct ufs_dir_entry*)(base + offset);
+ struct ufs_dir_entry *p = (struct ufs_dir_entry*)(base + (offset&mask));
+ while ((char*)p < (char*)de)
+ p = ufs_next_entry(sb, p);
+ return (char *)p - base;
+}
+
+
+/*
+ * This is blatantly stolen from ext2fs
+ */
+static int
+ufs_readdir(struct file *file, struct dir_context *ctx)
+{
+ loff_t pos = ctx->pos;
+ struct inode *inode = file_inode(file);
+ struct super_block *sb = inode->i_sb;
+ unsigned int offset = pos & ~PAGE_MASK;
+ unsigned long n = pos >> PAGE_SHIFT;
+ unsigned long npages = dir_pages(inode);
+ unsigned chunk_mask = ~(UFS_SB(sb)->s_uspi->s_dirblksize - 1);
+ bool need_revalidate = !inode_eq_iversion(inode, *(u64 *)file->private_data);
+ unsigned flags = UFS_SB(sb)->s_flags;
+
+ UFSD("BEGIN\n");
+
+ if (pos > inode->i_size - UFS_DIR_REC_LEN(1))
+ return 0;
+
+ for ( ; n < npages; n++, offset = 0) {
+ struct ufs_dir_entry *de;
+ struct folio *folio;
+ char *kaddr = ufs_get_folio(inode, n, &folio);
+ char *limit;
+
+ if (IS_ERR(kaddr)) {
+ ufs_error(sb, __func__,
+ "bad page in #%llu",
+ inode->i_ino);
+ ctx->pos += PAGE_SIZE - offset;
+ return PTR_ERR(kaddr);
+ }
+ if (unlikely(need_revalidate)) {
+ if (offset) {
+ offset = ufs_validate_entry(sb, kaddr, offset, chunk_mask);
+ ctx->pos = (n<<PAGE_SHIFT) + offset;
+ }
+ *(u64 *)file->private_data = inode_query_iversion(inode);
+ need_revalidate = false;
+ }
+ de = (struct ufs_dir_entry *)(kaddr+offset);
+ limit = kaddr + ufs_last_byte(inode, n) - UFS_DIR_REC_LEN(1);
+ for ( ;(char*)de <= limit; de = ufs_next_entry(sb, de)) {
+ if (de->d_ino) {
+ unsigned char d_type = DT_UNKNOWN;
+
+ UFSD("filldir(%s,%u)\n", de->d_name,
+ fs32_to_cpu(sb, de->d_ino));
+ UFSD("namlen %u\n", ufs_get_de_namlen(sb, de));
+
+ if ((flags & UFS_DE_MASK) == UFS_DE_44BSD)
+ d_type = de->d_u.d_44.d_type;
+
+ if (!dir_emit(ctx, de->d_name,
+ ufs_get_de_namlen(sb, de),
+ fs32_to_cpu(sb, de->d_ino),
+ d_type)) {
+ folio_release_kmap(folio, de);
+ return 0;
+ }
+ }
+ ctx->pos += fs16_to_cpu(sb, de->d_reclen);
+ }
+ folio_release_kmap(folio, kaddr);
+ }
+ return 0;
+}
+
+
+/*
+ * ufs_delete_entry deletes a directory entry by merging it with the
+ * previous entry.
+ */
+int ufs_delete_entry(struct inode *inode, struct ufs_dir_entry *dir,
+ struct folio *folio)
+{
+ struct super_block *sb = inode->i_sb;
+ size_t from, to;
+ char *kaddr;
+ loff_t pos;
+ struct ufs_dir_entry *de, *pde = NULL;
+ int err;
+
+ UFSD("ENTER\n");
+
+ from = offset_in_folio(folio, dir);
+ to = from + fs16_to_cpu(sb, dir->d_reclen);
+ kaddr = (char *)dir - from;
+ from &= ~(UFS_SB(sb)->s_uspi->s_dirblksize - 1);
+ de = (struct ufs_dir_entry *) (kaddr + from);
+
+ UFSD("ino %u, reclen %u, namlen %u, name %s\n",
+ fs32_to_cpu(sb, de->d_ino),
+ fs16_to_cpu(sb, de->d_reclen),
+ ufs_get_de_namlen(sb, de), de->d_name);
+
+ while ((char*)de < (char*)dir) {
+ if (de->d_reclen == 0) {
+ ufs_error(inode->i_sb, __func__,
+ "zero-length directory entry");
+ return -EIO;
+ }
+ pde = de;
+ de = ufs_next_entry(sb, de);
+ }
+ if (pde)
+ from = offset_in_folio(folio, pde);
+ pos = folio_pos(folio) + from;
+ folio_lock(folio);
+ err = ufs_prepare_chunk(folio, pos, to - from);
+ if (unlikely(err)) {
+ folio_unlock(folio);
+ return err;
+ }
+ if (pde)
+ pde->d_reclen = cpu_to_fs16(sb, to - from);
+ dir->d_ino = 0;
+ ufs_commit_chunk(folio, pos, to - from);
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ mark_inode_dirty(inode);
+ return ufs_handle_dirsync(inode);
+}
+
+int ufs_make_empty(struct inode * inode, struct inode *dir)
+{
+ struct super_block * sb = dir->i_sb;
+ struct address_space *mapping = inode->i_mapping;
+ struct folio *folio = filemap_grab_folio(mapping, 0);
+ const unsigned int chunk_size = UFS_SB(sb)->s_uspi->s_dirblksize;
+ struct ufs_dir_entry * de;
+ int err;
+ char *kaddr;
+
+ if (IS_ERR(folio))
+ return PTR_ERR(folio);
+
+ err = ufs_prepare_chunk(folio, 0, chunk_size);
+ if (err) {
+ folio_unlock(folio);
+ goto fail;
+ }
+
+ kaddr = kmap_local_folio(folio, 0);
+ memset(kaddr, 0, folio_size(folio));
+
+ de = (struct ufs_dir_entry *)kaddr;
+
+ de->d_ino = cpu_to_fs32(sb, inode->i_ino);
+ ufs_set_de_type(sb, de, inode->i_mode);
+ ufs_set_de_namlen(sb, de, 1);
+ de->d_reclen = cpu_to_fs16(sb, UFS_DIR_REC_LEN(1));
+ strcpy (de->d_name, ".");
+ de = (struct ufs_dir_entry *)
+ ((char *)de + fs16_to_cpu(sb, de->d_reclen));
+ de->d_ino = cpu_to_fs32(sb, dir->i_ino);
+ ufs_set_de_type(sb, de, dir->i_mode);
+ de->d_reclen = cpu_to_fs16(sb, chunk_size - UFS_DIR_REC_LEN(1));
+ ufs_set_de_namlen(sb, de, 2);
+ strcpy (de->d_name, "..");
+ kunmap_local(kaddr);
+
+ ufs_commit_chunk(folio, 0, chunk_size);
+ err = ufs_handle_dirsync(inode);
+fail:
+ folio_put(folio);
+ return err;
+}
+
+/*
+ * routine to check that the specified directory is empty (for rmdir)
+ */
+int ufs_empty_dir(struct inode * inode)
+{
+ struct super_block *sb = inode->i_sb;
+ struct folio *folio;
+ char *kaddr;
+ unsigned long i, npages = dir_pages(inode);
+
+ for (i = 0; i < npages; i++) {
+ struct ufs_dir_entry *de;
+
+ kaddr = ufs_get_folio(inode, i, &folio);
+ if (IS_ERR(kaddr))
+ continue;
+
+ de = (struct ufs_dir_entry *)kaddr;
+ kaddr += ufs_last_byte(inode, i) - UFS_DIR_REC_LEN(1);
+
+ while ((char *)de <= kaddr) {
+ if (de->d_reclen == 0) {
+ ufs_error(inode->i_sb, __func__,
+ "zero-length directory entry: "
+ "kaddr=%p, de=%p\n", kaddr, de);
+ goto not_empty;
+ }
+ if (de->d_ino) {
+ u16 namelen=ufs_get_de_namlen(sb, de);
+ /* check for . and .. */
+ if (de->d_name[0] != '.')
+ goto not_empty;
+ if (namelen > 2)
+ goto not_empty;
+ if (namelen < 2) {
+ if (inode->i_ino !=
+ fs32_to_cpu(sb, de->d_ino))
+ goto not_empty;
+ } else if (de->d_name[1] != '.')
+ goto not_empty;
+ }
+ de = ufs_next_entry(sb, de);
+ }
+ folio_release_kmap(folio, kaddr);
+ }
+ return 1;
+
+not_empty:
+ folio_release_kmap(folio, kaddr);
+ return 0;
+}
+
+static int ufs_dir_open(struct inode *inode, struct file *file)
+{
+ file->private_data = kzalloc(sizeof(u64), GFP_KERNEL);
+ if (!file->private_data)
+ return -ENOMEM;
+ return 0;
+}
+
+static int ufs_dir_release(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ return 0;
+}
+
+static loff_t ufs_dir_llseek(struct file *file, loff_t offset, int whence)
+{
+ return generic_llseek_cookie(file, offset, whence,
+ (u64 *)file->private_data);
+}
+
+const struct file_operations ufs_dir_operations = {
+ .open = ufs_dir_open,
+ .release = ufs_dir_release,
+ .read = generic_read_dir,
+ .iterate_shared = ufs_readdir,
+ .fsync = simple_fsync,
+ .llseek = ufs_dir_llseek,
+ .setlease = generic_setlease,
+};