// 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. Load more
// 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; }