![Linus Torvalds](/assets/img/avatar_default.png)
This is a late set of changes from Deepa Dinamani doing an automated treewide conversion of the inode and iattr structures from 'timespec' to 'timespec64', to push the conversion from the VFS layer into the individual file systems. There were no conflicts between this and the contents of linux-next until just before the merge window, when we saw multiple problems: - A minor conflict with my own y2038 fixes, which I could address by adding another patch on top here. - One semantic conflict with late changes to the NFS tree. I addressed this by merging Deepa's original branch on top of the changes that now got merged into mainline and making sure the merge commit includes the necessary changes as produced by coccinelle. - A trivial conflict against the removal of staging/lustre. - Multiple conflicts against the VFS changes in the overlayfs tree. These are still part of linux-next, but apparently this is no longer intended for 4.18 [1], so I am ignoring that part. As Deepa writes: The series aims to switch vfs timestamps to use struct timespec64. Currently vfs uses struct timespec, which is not y2038 safe. The series involves the following: 1. Add vfs helper functions for supporting struct timepec64 timestamps. 2. Cast prints of vfs timestamps to avoid warnings after the switch. 3. Simplify code using vfs timestamps so that the actual replacement becomes easy. 4. Convert vfs timestamps to use struct timespec64 using a script. This is a flag day patch. Next steps: 1. Convert APIs that can handle timespec64, instead of converting timestamps at the boundaries. 2. Update internal data structures to avoid timestamp conversions. Thomas Gleixner adds: I think there is no point to drag that out for the next merge window. The whole thing needs to be done in one go for the core changes which means that you're going to play that catchup game forever. Let's get over with it towards the end of the merge window. [1] https://www.spinics.net/lists/linux-fsdevel/msg128294.html -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJbInZAAAoJEGCrR//JCVInReoQAIlVIIMt5ZX6wmaKbrjy9Itf MfgbFihQ/djLnuSPVQ3nztcxF0d66BKHZ9puVjz6+mIHqfDvJTRwZs9nU+sOF/T1 g78fRkM1cxq6ZCkGYAbzyjyo5aC4PnSMP/NQLmwqvi0MXqqrbDoq5ZdP9DHJw39h L9lD8FM/P7T29Fgp9tq/pT5l9X8VU8+s5KQG1uhB5hii4VL6pD6JyLElDita7rg+ Z7/V7jkxIGEUWF7vGaiR1QTFzEtpUA/exDf9cnsf51OGtK/LJfQ0oiZPPuq3oA/E LSbt8YQQObc+dvfnGxwgxEg1k5WP5ekj/Wdibv/+rQKgGyLOTz6Q4xK6r8F2ahxs nyZQBdXqHhJYyKr1H1reUH3mrSgQbE5U5R1i3My0xV2dSn+vtK5vgF21v2Ku3A1G wJratdtF/kVBzSEQUhsYTw14Un+xhBLRWzcq0cELonqxaKvRQK9r92KHLIWNE7/v c0TmhFbkZA+zR8HdsaL3iYf1+0W/eYy8PcvepyldKNeW2pVk3CyvdTfY2Z87G2XK tIkK+BUWbG3drEGG3hxZ3757Ln3a9qWyC5ruD3mBVkuug/wekbI8PykYJS7Mx4s/ WNXl0dAL0Eeu1M8uEJejRAe1Q3eXoMWZbvCYZc+wAm92pATfHVcKwPOh8P7NHlfy A3HkjIBrKW5AgQDxfgvm =CZX2 -----END PGP SIGNATURE----- Merge tag 'vfs-timespec64' of git://git.kernel.org/pub/scm/linux/kernel/git/arnd/playground Pull inode timestamps conversion to timespec64 from Arnd Bergmann: "This is a late set of changes from Deepa Dinamani doing an automated treewide conversion of the inode and iattr structures from 'timespec' to 'timespec64', to push the conversion from the VFS layer into the individual file systems. As Deepa writes: 'The series aims to switch vfs timestamps to use struct timespec64. Currently vfs uses struct timespec, which is not y2038 safe. The series involves the following: 1. Add vfs helper functions for supporting struct timepec64 timestamps. 2. Cast prints of vfs timestamps to avoid warnings after the switch. 3. Simplify code using vfs timestamps so that the actual replacement becomes easy. 4. Convert vfs timestamps to use struct timespec64 using a script. This is a flag day patch. Next steps: 1. Convert APIs that can handle timespec64, instead of converting timestamps at the boundaries. 2. Update internal data structures to avoid timestamp conversions' Thomas Gleixner adds: 'I think there is no point to drag that out for the next merge window. The whole thing needs to be done in one go for the core changes which means that you're going to play that catchup game forever. Let's get over with it towards the end of the merge window'" * tag 'vfs-timespec64' of git://git.kernel.org/pub/scm/linux/kernel/git/arnd/playground: pstore: Remove bogus format string definition vfs: change inode times to use struct timespec64 pstore: Convert internal records to timespec64 udf: Simplify calls to udf_disk_stamp_to_time fs: nfs: get rid of memcpys for inode times ceph: make inode time prints to be long long lustre: Use long long type to print inode time fs: add timespec64_truncate()
486 lines
12 KiB
C
486 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* (C) 2001 Clemson University and The University of Chicago
|
|
*
|
|
* See COPYING in top-level directory.
|
|
*/
|
|
|
|
/*
|
|
* Linux VFS inode operations.
|
|
*/
|
|
|
|
#include <linux/bvec.h>
|
|
#include "protocol.h"
|
|
#include "orangefs-kernel.h"
|
|
#include "orangefs-bufmap.h"
|
|
|
|
static int read_one_page(struct page *page)
|
|
{
|
|
int ret;
|
|
int max_block;
|
|
ssize_t bytes_read = 0;
|
|
struct inode *inode = page->mapping->host;
|
|
const __u32 blocksize = PAGE_SIZE;
|
|
const __u32 blockbits = PAGE_SHIFT;
|
|
struct iov_iter to;
|
|
struct bio_vec bv = {.bv_page = page, .bv_len = PAGE_SIZE};
|
|
|
|
iov_iter_bvec(&to, ITER_BVEC | READ, &bv, 1, PAGE_SIZE);
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"orangefs_readpage called with page %p\n",
|
|
page);
|
|
|
|
max_block = ((inode->i_size / blocksize) + 1);
|
|
|
|
if (page->index < max_block) {
|
|
loff_t blockptr_offset = (((loff_t) page->index) << blockbits);
|
|
|
|
bytes_read = orangefs_inode_read(inode,
|
|
&to,
|
|
&blockptr_offset,
|
|
inode->i_size);
|
|
}
|
|
/* this will only zero remaining unread portions of the page data */
|
|
iov_iter_zero(~0U, &to);
|
|
/* takes care of potential aliasing */
|
|
flush_dcache_page(page);
|
|
if (bytes_read < 0) {
|
|
ret = bytes_read;
|
|
SetPageError(page);
|
|
} else {
|
|
SetPageUptodate(page);
|
|
if (PageError(page))
|
|
ClearPageError(page);
|
|
ret = 0;
|
|
}
|
|
/* unlock the page after the ->readpage() routine completes */
|
|
unlock_page(page);
|
|
return ret;
|
|
}
|
|
|
|
static int orangefs_readpage(struct file *file, struct page *page)
|
|
{
|
|
return read_one_page(page);
|
|
}
|
|
|
|
static int orangefs_readpages(struct file *file,
|
|
struct address_space *mapping,
|
|
struct list_head *pages,
|
|
unsigned nr_pages)
|
|
{
|
|
int page_idx;
|
|
int ret;
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG, "orangefs_readpages called\n");
|
|
|
|
for (page_idx = 0; page_idx < nr_pages; page_idx++) {
|
|
struct page *page;
|
|
|
|
page = list_entry(pages->prev, struct page, lru);
|
|
list_del(&page->lru);
|
|
if (!add_to_page_cache(page,
|
|
mapping,
|
|
page->index,
|
|
readahead_gfp_mask(mapping))) {
|
|
ret = read_one_page(page);
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"failure adding page to cache, read_one_page returned: %d\n",
|
|
ret);
|
|
} else {
|
|
put_page(page);
|
|
}
|
|
}
|
|
BUG_ON(!list_empty(pages));
|
|
return 0;
|
|
}
|
|
|
|
static void orangefs_invalidatepage(struct page *page,
|
|
unsigned int offset,
|
|
unsigned int length)
|
|
{
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"orangefs_invalidatepage called on page %p "
|
|
"(offset is %u)\n",
|
|
page,
|
|
offset);
|
|
|
|
ClearPageUptodate(page);
|
|
ClearPageMappedToDisk(page);
|
|
return;
|
|
|
|
}
|
|
|
|
static int orangefs_releasepage(struct page *page, gfp_t foo)
|
|
{
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"orangefs_releasepage called on page %p\n",
|
|
page);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Having a direct_IO entry point in the address_space_operations
|
|
* struct causes the kernel to allows us to use O_DIRECT on
|
|
* open. Nothing will ever call this thing, but in the future we
|
|
* will need to be able to use O_DIRECT on open in order to support
|
|
* AIO. Modeled after NFS, they do this too.
|
|
*/
|
|
|
|
static ssize_t orangefs_direct_IO(struct kiocb *iocb,
|
|
struct iov_iter *iter)
|
|
{
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"orangefs_direct_IO: %pD\n",
|
|
iocb->ki_filp);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/** ORANGEFS2 implementation of address space operations */
|
|
static const struct address_space_operations orangefs_address_operations = {
|
|
.readpage = orangefs_readpage,
|
|
.readpages = orangefs_readpages,
|
|
.invalidatepage = orangefs_invalidatepage,
|
|
.releasepage = orangefs_releasepage,
|
|
.direct_IO = orangefs_direct_IO,
|
|
};
|
|
|
|
static int orangefs_setattr_size(struct inode *inode, struct iattr *iattr)
|
|
{
|
|
struct orangefs_inode_s *orangefs_inode = ORANGEFS_I(inode);
|
|
struct orangefs_kernel_op_s *new_op;
|
|
loff_t orig_size;
|
|
int ret = -EINVAL;
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"%s: %pU: Handle is %pU | fs_id %d | size is %llu\n",
|
|
__func__,
|
|
get_khandle_from_ino(inode),
|
|
&orangefs_inode->refn.khandle,
|
|
orangefs_inode->refn.fs_id,
|
|
iattr->ia_size);
|
|
|
|
/* Ensure that we have a up to date size, so we know if it changed. */
|
|
ret = orangefs_inode_getattr(inode, 0, 1, STATX_SIZE);
|
|
if (ret == -ESTALE)
|
|
ret = -EIO;
|
|
if (ret) {
|
|
gossip_err("%s: orangefs_inode_getattr failed, ret:%d:.\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
orig_size = i_size_read(inode);
|
|
|
|
truncate_setsize(inode, iattr->ia_size);
|
|
|
|
new_op = op_alloc(ORANGEFS_VFS_OP_TRUNCATE);
|
|
if (!new_op)
|
|
return -ENOMEM;
|
|
|
|
new_op->upcall.req.truncate.refn = orangefs_inode->refn;
|
|
new_op->upcall.req.truncate.size = (__s64) iattr->ia_size;
|
|
|
|
ret = service_operation(new_op,
|
|
__func__,
|
|
get_interruptible_flag(inode));
|
|
|
|
/*
|
|
* the truncate has no downcall members to retrieve, but
|
|
* the status value tells us if it went through ok or not
|
|
*/
|
|
gossip_debug(GOSSIP_INODE_DEBUG, "%s: ret:%d:\n", __func__, ret);
|
|
|
|
op_release(new_op);
|
|
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
if (orig_size != i_size_read(inode))
|
|
iattr->ia_valid |= ATTR_CTIME | ATTR_MTIME;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Change attributes of an object referenced by dentry.
|
|
*/
|
|
int orangefs_setattr(struct dentry *dentry, struct iattr *iattr)
|
|
{
|
|
int ret = -EINVAL;
|
|
struct inode *inode = dentry->d_inode;
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"%s: called on %pd\n",
|
|
__func__,
|
|
dentry);
|
|
|
|
ret = setattr_prepare(dentry, iattr);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (iattr->ia_valid & ATTR_SIZE) {
|
|
ret = orangefs_setattr_size(inode, iattr);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
setattr_copy(inode, iattr);
|
|
mark_inode_dirty(inode);
|
|
|
|
ret = orangefs_inode_setattr(inode, iattr);
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"%s: orangefs_inode_setattr returned %d\n",
|
|
__func__,
|
|
ret);
|
|
|
|
if (!ret && (iattr->ia_valid & ATTR_MODE))
|
|
/* change mod on a file that has ACLs */
|
|
ret = posix_acl_chmod(inode, inode->i_mode);
|
|
|
|
out:
|
|
gossip_debug(GOSSIP_INODE_DEBUG, "%s: ret:%d:\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Obtain attributes of an object given a dentry
|
|
*/
|
|
int orangefs_getattr(const struct path *path, struct kstat *stat,
|
|
u32 request_mask, unsigned int flags)
|
|
{
|
|
int ret = -ENOENT;
|
|
struct inode *inode = path->dentry->d_inode;
|
|
struct orangefs_inode_s *orangefs_inode = NULL;
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"orangefs_getattr: called on %pd\n",
|
|
path->dentry);
|
|
|
|
ret = orangefs_inode_getattr(inode, 0, 0, request_mask);
|
|
if (ret == 0) {
|
|
generic_fillattr(inode, stat);
|
|
|
|
/* override block size reported to stat */
|
|
orangefs_inode = ORANGEFS_I(inode);
|
|
|
|
if (request_mask & STATX_SIZE)
|
|
stat->result_mask = STATX_BASIC_STATS;
|
|
else
|
|
stat->result_mask = STATX_BASIC_STATS &
|
|
~STATX_SIZE;
|
|
|
|
stat->attributes_mask = STATX_ATTR_IMMUTABLE |
|
|
STATX_ATTR_APPEND;
|
|
if (inode->i_flags & S_IMMUTABLE)
|
|
stat->attributes |= STATX_ATTR_IMMUTABLE;
|
|
if (inode->i_flags & S_APPEND)
|
|
stat->attributes |= STATX_ATTR_APPEND;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int orangefs_permission(struct inode *inode, int mask)
|
|
{
|
|
int ret;
|
|
|
|
if (mask & MAY_NOT_BLOCK)
|
|
return -ECHILD;
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG, "%s: refreshing\n", __func__);
|
|
|
|
/* Make sure the permission (and other common attrs) are up to date. */
|
|
ret = orangefs_inode_getattr(inode, 0, 0, STATX_MODE);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return generic_permission(inode, mask);
|
|
}
|
|
|
|
int orangefs_update_time(struct inode *inode, struct timespec64 *time, int flags)
|
|
{
|
|
struct iattr iattr;
|
|
gossip_debug(GOSSIP_INODE_DEBUG, "orangefs_update_time: %pU\n",
|
|
get_khandle_from_ino(inode));
|
|
generic_update_time(inode, time, flags);
|
|
memset(&iattr, 0, sizeof iattr);
|
|
if (flags & S_ATIME)
|
|
iattr.ia_valid |= ATTR_ATIME;
|
|
if (flags & S_CTIME)
|
|
iattr.ia_valid |= ATTR_CTIME;
|
|
if (flags & S_MTIME)
|
|
iattr.ia_valid |= ATTR_MTIME;
|
|
return orangefs_inode_setattr(inode, &iattr);
|
|
}
|
|
|
|
/* ORANGEFS2 implementation of VFS inode operations for files */
|
|
static const struct inode_operations orangefs_file_inode_operations = {
|
|
.get_acl = orangefs_get_acl,
|
|
.set_acl = orangefs_set_acl,
|
|
.setattr = orangefs_setattr,
|
|
.getattr = orangefs_getattr,
|
|
.listxattr = orangefs_listxattr,
|
|
.permission = orangefs_permission,
|
|
.update_time = orangefs_update_time,
|
|
};
|
|
|
|
static int orangefs_init_iops(struct inode *inode)
|
|
{
|
|
inode->i_mapping->a_ops = &orangefs_address_operations;
|
|
|
|
switch (inode->i_mode & S_IFMT) {
|
|
case S_IFREG:
|
|
inode->i_op = &orangefs_file_inode_operations;
|
|
inode->i_fop = &orangefs_file_operations;
|
|
break;
|
|
case S_IFLNK:
|
|
inode->i_op = &orangefs_symlink_inode_operations;
|
|
break;
|
|
case S_IFDIR:
|
|
inode->i_op = &orangefs_dir_inode_operations;
|
|
inode->i_fop = &orangefs_dir_operations;
|
|
break;
|
|
default:
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"%s: unsupported mode\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Given an ORANGEFS object identifier (fsid, handle), convert it into
|
|
* a ino_t type that will be used as a hash-index from where the handle will
|
|
* be searched for in the VFS hash table of inodes.
|
|
*/
|
|
static inline ino_t orangefs_handle_hash(struct orangefs_object_kref *ref)
|
|
{
|
|
if (!ref)
|
|
return 0;
|
|
return orangefs_khandle_to_ino(&(ref->khandle));
|
|
}
|
|
|
|
/*
|
|
* Called to set up an inode from iget5_locked.
|
|
*/
|
|
static int orangefs_set_inode(struct inode *inode, void *data)
|
|
{
|
|
struct orangefs_object_kref *ref = (struct orangefs_object_kref *) data;
|
|
ORANGEFS_I(inode)->refn.fs_id = ref->fs_id;
|
|
ORANGEFS_I(inode)->refn.khandle = ref->khandle;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Called to determine if handles match.
|
|
*/
|
|
static int orangefs_test_inode(struct inode *inode, void *data)
|
|
{
|
|
struct orangefs_object_kref *ref = (struct orangefs_object_kref *) data;
|
|
struct orangefs_inode_s *orangefs_inode = NULL;
|
|
|
|
orangefs_inode = ORANGEFS_I(inode);
|
|
/* test handles and fs_ids... */
|
|
return (!ORANGEFS_khandle_cmp(&(orangefs_inode->refn.khandle),
|
|
&(ref->khandle)) &&
|
|
orangefs_inode->refn.fs_id == ref->fs_id);
|
|
}
|
|
|
|
/*
|
|
* Front-end to lookup the inode-cache maintained by the VFS using the ORANGEFS
|
|
* file handle.
|
|
*
|
|
* @sb: the file system super block instance.
|
|
* @ref: The ORANGEFS object for which we are trying to locate an inode.
|
|
*/
|
|
struct inode *orangefs_iget(struct super_block *sb,
|
|
struct orangefs_object_kref *ref)
|
|
{
|
|
struct inode *inode = NULL;
|
|
unsigned long hash;
|
|
int error;
|
|
|
|
hash = orangefs_handle_hash(ref);
|
|
inode = iget5_locked(sb,
|
|
hash,
|
|
orangefs_test_inode,
|
|
orangefs_set_inode,
|
|
ref);
|
|
if (!inode || !(inode->i_state & I_NEW))
|
|
return inode;
|
|
|
|
error = orangefs_inode_getattr(inode, 1, 1, STATX_ALL);
|
|
if (error) {
|
|
iget_failed(inode);
|
|
return ERR_PTR(error);
|
|
}
|
|
|
|
inode->i_ino = hash; /* needed for stat etc */
|
|
orangefs_init_iops(inode);
|
|
unlock_new_inode(inode);
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"iget handle %pU, fsid %d hash %ld i_ino %lu\n",
|
|
&ref->khandle,
|
|
ref->fs_id,
|
|
hash,
|
|
inode->i_ino);
|
|
|
|
return inode;
|
|
}
|
|
|
|
/*
|
|
* Allocate an inode for a newly created file and insert it into the inode hash.
|
|
*/
|
|
struct inode *orangefs_new_inode(struct super_block *sb, struct inode *dir,
|
|
int mode, dev_t dev, struct orangefs_object_kref *ref)
|
|
{
|
|
unsigned long hash = orangefs_handle_hash(ref);
|
|
struct inode *inode;
|
|
int error;
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"%s:(sb is %p | MAJOR(dev)=%u | MINOR(dev)=%u mode=%o)\n",
|
|
__func__,
|
|
sb,
|
|
MAJOR(dev),
|
|
MINOR(dev),
|
|
mode);
|
|
|
|
inode = new_inode(sb);
|
|
if (!inode)
|
|
return NULL;
|
|
|
|
orangefs_set_inode(inode, ref);
|
|
inode->i_ino = hash; /* needed for stat etc */
|
|
|
|
error = orangefs_inode_getattr(inode, 1, 1, STATX_ALL);
|
|
if (error)
|
|
goto out_iput;
|
|
|
|
orangefs_init_iops(inode);
|
|
|
|
inode->i_mode = mode;
|
|
inode->i_uid = current_fsuid();
|
|
inode->i_gid = current_fsgid();
|
|
inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
|
|
inode->i_size = PAGE_SIZE;
|
|
inode->i_rdev = dev;
|
|
|
|
error = insert_inode_locked4(inode, hash, orangefs_test_inode, ref);
|
|
if (error < 0)
|
|
goto out_iput;
|
|
|
|
gossip_debug(GOSSIP_INODE_DEBUG,
|
|
"Initializing ACL's for inode %pU\n",
|
|
get_khandle_from_ino(inode));
|
|
orangefs_init_acl(inode, dir);
|
|
return inode;
|
|
|
|
out_iput:
|
|
iput(inode);
|
|
return ERR_PTR(error);
|
|
}
|