3310 lines · 87.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0#include <linux/ceph/ceph_debug.h>#include <linux/ceph/striper.h> #include <linux/module.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/file.h>#include <linux/mount.h>#include <linux/namei.h>#include <linux/writeback.h>#include <linux/falloc.h>#include <linux/iversion.h>#include <linux/ktime.h>#include <linux/splice.h> #include "super.h"#include "mds_client.h"#include "cache.h"#include "io.h"#include "metric.h"#include "subvolume_metrics.h" /* * Record I/O for subvolume metrics tracking. * * Callers must ensure bytes > 0 for reads (ret > 0 check) to avoid counting * EOF as an I/O operation. For writes, the condition is (ret >= 0 && len > 0). */static inline void ceph_record_subvolume_io(struct inode *inode, bool is_write,					    ktime_t start, ktime_t end,					    size_t bytes){	if (!bytes)		return; 	ceph_subvolume_metrics_record_io(ceph_sb_to_mdsc(inode->i_sb),					 ceph_inode(inode),					 is_write, bytes, start, end);} static __le32 ceph_flags_sys2wire(struct ceph_mds_client *mdsc, u32 flags){	struct ceph_client *cl = mdsc->fsc->client;	u32 wire_flags = 0; 	switch (flags & O_ACCMODE) {	case O_RDONLY:		wire_flags |= CEPH_O_RDONLY;		break;	case O_WRONLY:		wire_flags |= CEPH_O_WRONLY;		break;	case O_RDWR:		wire_flags |= CEPH_O_RDWR;		break;	} 	flags &= ~O_ACCMODE; #define ceph_sys2wire(a) if (flags & a) { wire_flags |= CEPH_##a; flags &= ~a; } 	ceph_sys2wire(O_CREAT);	ceph_sys2wire(O_EXCL);	ceph_sys2wire(O_TRUNC);	ceph_sys2wire(O_DIRECTORY);	ceph_sys2wire(O_NOFOLLOW); #undef ceph_sys2wire 	if (flags)		doutc(cl, "unused open flags: %x\n", flags); 	return cpu_to_le32(wire_flags);} /* * Ceph file operations * * Implement basic open/close functionality, and implement * read/write. * * We implement three modes of file I/O: *  - buffered uses the generic_file_aio_{read,write} helpers * *  - synchronous is used when there is multi-client read/write *    sharing, avoids the page cache, and synchronously waits for an *    ack from the OSD. * *  - direct io takes the variant of the sync path that references *    user pages directly. * * fsync() flushes and waits on dirty pages, but just queues metadata * for writeback: since the MDS can recover size and mtime there is no * need to wait for MDS acknowledgement. */ /* * How many pages to get in one call to iov_iter_get_pages().  This * determines the size of the on-stack array used as a buffer. */#define ITER_GET_BVECS_PAGES	64 static ssize_t __iter_get_bvecs(struct iov_iter *iter, size_t maxsize,				struct bio_vec *bvecs){	size_t size = 0;	int bvec_idx = 0; 	if (maxsize > iov_iter_count(iter))		maxsize = iov_iter_count(iter); 	while (size < maxsize) {		struct page *pages[ITER_GET_BVECS_PAGES];		ssize_t bytes;		size_t start;		int idx = 0; 		bytes = iov_iter_get_pages2(iter, pages, maxsize - size,					   ITER_GET_BVECS_PAGES, &start);		if (bytes < 0)			return size ?: bytes; 		size += bytes; 		for ( ; bytes; idx++, bvec_idx++) {			int len = min_t(int, bytes, PAGE_SIZE - start); 			bvec_set_page(&bvecs[bvec_idx], pages[idx], len, start);			bytes -= len;			start = 0;		}	} 	return size;} /* * iov_iter_get_pages() only considers one iov_iter segment, no matter * what maxsize or maxpages are given.  For ITER_BVEC that is a single * page. * * Attempt to get up to @maxsize bytes worth of pages from @iter. * Return the number of bytes in the created bio_vec array, or an error. */static ssize_t iter_get_bvecs_alloc(struct iov_iter *iter, size_t maxsize,				    struct bio_vec **bvecs, int *num_bvecs){	struct bio_vec *bv;	size_t orig_count = iov_iter_count(iter);	ssize_t bytes;	int npages; 	iov_iter_truncate(iter, maxsize);	npages = iov_iter_npages(iter, INT_MAX);	iov_iter_reexpand(iter, orig_count); 	/*	 * __iter_get_bvecs() may populate only part of the array -- zero it	 * out.	 */	bv = kvmalloc_objs(*bv, npages, GFP_KERNEL | __GFP_ZERO);	if (!bv)		return -ENOMEM; 	bytes = __iter_get_bvecs(iter, maxsize, bv);	if (bytes < 0) {		/*		 * No pages were pinned -- just free the array.		 */		kvfree(bv);		return bytes;	} 	*bvecs = bv;	*num_bvecs = npages;	return bytes;} static void put_bvecs(struct bio_vec *bvecs, int num_bvecs, bool should_dirty){	int i; 	for (i = 0; i < num_bvecs; i++) {		if (bvecs[i].bv_page) {			if (should_dirty)				set_page_dirty_lock(bvecs[i].bv_page);			put_page(bvecs[i].bv_page);		}	}	kvfree(bvecs);} /* * Prepare an open request.  Preallocate ceph_cap to avoid an * inopportune ENOMEM later. */static struct ceph_mds_request *prepare_open_request(struct super_block *sb, int flags, int create_mode){	struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(sb);	struct ceph_mds_request *req;	int want_auth = USE_ANY_MDS;	int op = (flags & O_CREAT) ? CEPH_MDS_OP_CREATE : CEPH_MDS_OP_OPEN; 	if (flags & (O_WRONLY|O_RDWR|O_CREAT|O_TRUNC))		want_auth = USE_AUTH_MDS; 	req = ceph_mdsc_create_request(mdsc, op, want_auth);	if (IS_ERR(req))		goto out;	req->r_fmode = ceph_flags_to_mode(flags);	req->r_args.open.flags = ceph_flags_sys2wire(mdsc, flags);	req->r_args.open.mode = cpu_to_le32(create_mode);out:	return req;} static int ceph_init_file_info(struct inode *inode, struct file *file,					int fmode, bool isdir){	struct ceph_inode_info *ci = ceph_inode(inode);	struct ceph_mount_options *opt =		ceph_inode_to_fs_client(&ci->netfs.inode)->mount_options;	struct ceph_client *cl = ceph_inode_to_client(inode);	struct ceph_file_info *fi;	int ret; 	doutc(cl, "%p %llx.%llx %p 0%o (%s)\n", inode, ceph_vinop(inode),	      file, inode->i_mode, isdir ? "dir" : "regular");	BUG_ON(inode->i_fop->release != ceph_release); 	if (isdir) {		struct ceph_dir_file_info *dfi =			kmem_cache_zalloc(ceph_dir_file_cachep, GFP_KERNEL);		if (!dfi)			return -ENOMEM; 		file->private_data = dfi;		fi = &dfi->file_info;		dfi->next_offset = 2;		dfi->readdir_cache_idx = -1;	} else {		fi = kmem_cache_zalloc(ceph_file_cachep, GFP_KERNEL);		if (!fi)			return -ENOMEM; 		if (opt->flags & CEPH_MOUNT_OPT_NOPAGECACHE)			fi->flags |= CEPH_F_SYNC; 		file->private_data = fi;	} 	ceph_get_fmode(ci, fmode, 1);	fi->fmode = fmode; 	spin_lock_init(&fi->rw_contexts_lock);	INIT_LIST_HEAD(&fi->rw_contexts);	fi->filp_gen = READ_ONCE(ceph_inode_to_fs_client(inode)->filp_gen); 	if ((file->f_mode & FMODE_WRITE) && ceph_has_inline_data(ci)) {		ret = ceph_uninline_data(file);		if (ret < 0)			goto error;	} 	return 0; error:	ceph_fscache_unuse_cookie(inode, file->f_mode & FMODE_WRITE);	ceph_put_fmode(ci, fi->fmode, 1);	kmem_cache_free(ceph_file_cachep, fi);	/* wake up anyone waiting for caps on this inode */	wake_up_all(&ci->i_cap_wq);	return ret;} /* * initialize private struct file data. * if we fail, clean up by dropping fmode reference on the ceph_inode */static int ceph_init_file(struct inode *inode, struct file *file, int fmode){	struct ceph_client *cl = ceph_inode_to_client(inode);	int ret = 0; 	switch (inode->i_mode & S_IFMT) {	case S_IFREG:		ceph_fscache_use_cookie(inode, file->f_mode & FMODE_WRITE);		fallthrough;	case S_IFDIR:		ret = ceph_init_file_info(inode, file, fmode,						S_ISDIR(inode->i_mode));		break; 	case S_IFLNK:		doutc(cl, "%p %llx.%llx %p 0%o (symlink)\n", inode,		      ceph_vinop(inode), file, inode->i_mode);		break; 	default:		doutc(cl, "%p %llx.%llx %p 0%o (special)\n", inode,		      ceph_vinop(inode), file, inode->i_mode);		/*		 * we need to drop the open ref now, since we don't		 * have .release set to ceph_release.		 */		BUG_ON(inode->i_fop->release == ceph_release); 		/* call the proper open fop */		ret = inode->i_fop->open(inode, file);	}	return ret;} /* * Retry cap acquisition after a stale session or a lost cap update. */int ceph_renew_caps(struct inode *inode, int fmode){	struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);	struct ceph_client *cl = mdsc->fsc->client;	struct ceph_inode_info *ci = ceph_inode(inode);	struct ceph_mds_request *req;	int err, flags, wanted, issued; 	spin_lock(&ci->i_ceph_lock);	__ceph_touch_fmode(ci, mdsc, fmode);	wanted = __ceph_caps_file_wanted(ci);	issued = __ceph_caps_issued(ci, NULL);	if (__ceph_is_any_real_caps(ci) &&	    (!(wanted & CEPH_CAP_ANY_WR) || ci->i_auth_cap) &&	    (issued & wanted) == wanted) {		spin_unlock(&ci->i_ceph_lock);		doutc(cl, "%p %llx.%llx want %s issued %s updating mds_wanted\n",		      inode, ceph_vinop(inode), ceph_cap_string(wanted),		      ceph_cap_string(issued));		ceph_check_caps(ci, 0);		return 0;	}	spin_unlock(&ci->i_ceph_lock); 	flags = 0;	if ((wanted & CEPH_CAP_FILE_RD) && (wanted & CEPH_CAP_FILE_WR))		flags = O_RDWR;	else if (wanted & CEPH_CAP_FILE_RD)		flags = O_RDONLY;	else if (wanted & CEPH_CAP_FILE_WR)		flags = O_WRONLY;#ifdef O_LAZY	if (wanted & CEPH_CAP_FILE_LAZYIO)		flags |= O_LAZY;#endif 	req = prepare_open_request(inode->i_sb, flags, 0);	if (IS_ERR(req)) {		err = PTR_ERR(req);		goto out;	} 	req->r_inode = inode;	ihold(inode);	req->r_num_caps = 1; 	err = ceph_mdsc_do_request(mdsc, NULL, req);	ceph_mdsc_put_request(req);out:	doutc(cl, "%p %llx.%llx open result=%d\n", inode, ceph_vinop(inode),	      err);	return err < 0 ? err : 0;} /* * If we already have the requisite capabilities, we can satisfy * the open request locally (no need to request new caps from the * MDS).  We do, however, need to inform the MDS (asynchronously) * if our wanted caps set expands. */int ceph_open(struct inode *inode, struct file *file){	struct ceph_inode_info *ci = ceph_inode(inode);	struct ceph_fs_client *fsc = ceph_sb_to_fs_client(inode->i_sb);	struct ceph_client *cl = fsc->client;	struct ceph_mds_client *mdsc = fsc->mdsc;	struct ceph_mds_request *req;	struct ceph_file_info *fi = file->private_data;	int err;	int flags, fmode, wanted;	struct dentry *dentry;	char *path;	bool do_sync = false;	int mask = MAY_READ; 	if (fi) {		doutc(cl, "file %p is already opened\n", file);		return 0;	} 	/* filter out O_CREAT|O_EXCL; vfs did that already.  yuck. */	flags = file->f_flags & ~(O_CREAT|O_EXCL);	if (S_ISDIR(inode->i_mode)) {		flags = O_DIRECTORY;  /* mds likes to know */	} else if (S_ISREG(inode->i_mode)) {		err = fscrypt_file_open(inode, file);		if (err)			return err;	} 	doutc(cl, "%p %llx.%llx file %p flags %d (%d)\n", inode,	      ceph_vinop(inode), file, flags, file->f_flags);	fmode = ceph_flags_to_mode(flags);	wanted = ceph_caps_for_mode(fmode); 	if (fmode & CEPH_FILE_MODE_WR)		mask |= MAY_WRITE;	dentry = d_find_alias(inode);	if (!dentry) {		do_sync = true;	} else {		struct ceph_path_info path_info = {0};		path = ceph_mdsc_build_path(mdsc, dentry, &path_info, 0);		if (IS_ERR(path)) {			do_sync = true;			err = 0;		} else {			err = ceph_mds_check_access(mdsc, path, mask);		}		ceph_mdsc_free_path_info(&path_info);		dput(dentry); 		/* For none EACCES cases will let the MDS do the mds auth check */		if (err == -EACCES) {			return err;		} else if (err < 0) {			do_sync = true;			err = 0;		}	} 	/* snapped files are read-only */	if (ceph_snap(inode) != CEPH_NOSNAP && (file->f_mode & FMODE_WRITE))		return -EROFS; 	/* trivially open snapdir */	if (ceph_snap(inode) == CEPH_SNAPDIR) {		return ceph_init_file(inode, file, fmode);	} 	/*	 * No need to block if we have caps on the auth MDS (for	 * write) or any MDS (for read).  Update wanted set	 * asynchronously.	 */	spin_lock(&ci->i_ceph_lock);	if (!do_sync && __ceph_is_any_real_caps(ci) &&	    (((fmode & CEPH_FILE_MODE_WR) == 0) || ci->i_auth_cap)) {		int mds_wanted = __ceph_caps_mds_wanted(ci, true);		int issued = __ceph_caps_issued(ci, NULL); 		doutc(cl, "open %p fmode %d want %s issued %s using existing\n",		      inode, fmode, ceph_cap_string(wanted),		      ceph_cap_string(issued));		__ceph_touch_fmode(ci, mdsc, fmode);		spin_unlock(&ci->i_ceph_lock); 		/* adjust wanted? */		if ((issued & wanted) != wanted &&		    (mds_wanted & wanted) != wanted &&		    ceph_snap(inode) != CEPH_SNAPDIR)			ceph_check_caps(ci, 0); 		return ceph_init_file(inode, file, fmode);	} else if (!do_sync && ceph_snap(inode) != CEPH_NOSNAP &&		   (ci->i_snap_caps & wanted) == wanted) {		__ceph_touch_fmode(ci, mdsc, fmode);		spin_unlock(&ci->i_ceph_lock);		return ceph_init_file(inode, file, fmode);	} 	spin_unlock(&ci->i_ceph_lock); 	doutc(cl, "open fmode %d wants %s\n", fmode, ceph_cap_string(wanted));	req = prepare_open_request(inode->i_sb, flags, 0);	if (IS_ERR(req)) {		err = PTR_ERR(req);		goto out;	}	req->r_inode = inode;	ihold(inode); 	req->r_num_caps = 1;	err = ceph_mdsc_do_request(mdsc, NULL, req);	if (!err)		err = ceph_init_file(inode, file, req->r_fmode);	ceph_mdsc_put_request(req);	doutc(cl, "open result=%d on %llx.%llx\n", err, ceph_vinop(inode));out:	return err;} 

Lines 1–500 of 3310.