Whamcloud - gitweb
LU-10810 clio: SEEK_HOLE/SEEK_DATA on client side 08/39708/16
authorMikhail Pershin <mpershin@whamcloud.com>
Fri, 21 Aug 2020 16:22:38 +0000 (19:22 +0300)
committerOleg Drokin <green@whamcloud.com>
Sat, 7 Nov 2020 08:12:37 +0000 (08:12 +0000)
Patch introduces basic support for lseek SEEK_HOLE/SEEK_DATA
parameters in lustre client.

- introduce new IO type CIT_LSEEK in CLIO stack
- LOV splits request to all stripes involved and merges
  results back.
- OSC sends OST LSEEK RPC asynchronously
- if target doesn't support LSEEK RPC then OSC assumes
  whole related object is data with virtual hole at the end
- lseek restores released files assuming it is done prior
  the file copying.
- tool is added to request needed lseek on file
- basic tests are added in sanity, sanityn and sanity-hsm

Signed-off-by: Mikhail Pershin <mpershin@whamcloud.com>
Change-Id: I0728329d4bce71c441de581a439cde1aa873fd46
Reviewed-on: https://review.whamcloud.com/39708
Reviewed-by: Oleg Drokin <green@whamcloud.com>
Tested-by: Oleg Drokin <green@whamcloud.com>
16 files changed:
lustre/include/cl_object.h
lustre/include/lustre_export.h
lustre/include/lustre_osc.h
lustre/llite/file.c
lustre/llite/llite_lib.c
lustre/llite/vvp_io.c
lustre/lov/lov_io.c
lustre/mdc/mdc_dev.c
lustre/obdclass/cl_io.c
lustre/osc/osc_io.c
lustre/tests/.gitignore
lustre/tests/Makefile.am
lustre/tests/lseek_test.c [new file with mode: 0644]
lustre/tests/sanity-hsm.sh
lustre/tests/sanity.sh
lustre/tests/sanityn.sh

index 6c9a212..ad3512c 100644 (file)
@@ -1411,6 +1411,11 @@ enum cl_io_type {
         * To give advice about access of a file
         */
        CIT_LADVISE,
         * To give advice about access of a file
         */
        CIT_LADVISE,
+       /**
+        * SEEK_HOLE/SEEK_DATA handling to search holes or data
+        * across all file objects
+        */
+       CIT_LSEEK,
         CIT_OP_NR
 };
 
         CIT_OP_NR
 };
 
@@ -1882,6 +1887,11 @@ struct cl_io {
                        enum lu_ladvise_type     li_advice;
                        __u64                    li_flags;
                } ci_ladvise;
                        enum lu_ladvise_type     li_advice;
                        __u64                    li_flags;
                } ci_ladvise;
+               struct cl_lseek_io {
+                       loff_t                   ls_start;
+                       loff_t                   ls_result;
+                       int                      ls_whence;
+               } ci_lseek;
         } u;
         struct cl_2queue     ci_queue;
         size_t               ci_nob;
         } u;
         struct cl_2queue     ci_queue;
         size_t               ci_nob;
index 7d29455..f7ee8ec 100644 (file)
@@ -480,6 +480,11 @@ static inline int exp_connect_encrypt(struct obd_export *exp)
        return !!(exp_connect_flags2(exp) & OBD_CONNECT2_ENCRYPT);
 }
 
        return !!(exp_connect_flags2(exp) & OBD_CONNECT2_ENCRYPT);
 }
 
+static inline int exp_connect_lseek(struct obd_export *exp)
+{
+       return !!(exp_connect_flags2(exp) & OBD_CONNECT2_LSEEK);
+}
+
 enum {
        /* archive_ids in array format */
        KKUC_CT_DATA_ARRAY_MAGIC        = 0x092013cea,
 enum {
        /* archive_ids in array format */
        KKUC_CT_DATA_ARRAY_MAGIC        = 0x092013cea,
index 95edabc..f41ecb7 100644 (file)
@@ -694,6 +694,10 @@ int osc_fsync_ost(const struct lu_env *env, struct osc_object *obj,
 void osc_io_fsync_end(const struct lu_env *env,
                      const struct cl_io_slice *slice);
 void osc_read_ahead_release(const struct lu_env *env, void *cbdata);
 void osc_io_fsync_end(const struct lu_env *env,
                      const struct cl_io_slice *slice);
 void osc_read_ahead_release(const struct lu_env *env, void *cbdata);
+int osc_io_lseek_start(const struct lu_env *env,
+                      const struct cl_io_slice *slice);
+void osc_io_lseek_end(const struct lu_env *env,
+                     const struct cl_io_slice *slice);
 
 /* osc_lock.c */
 void osc_lock_to_lockless(const struct lu_env *env, struct osc_lock *ols,
 
 /* osc_lock.c */
 void osc_lock_to_lockless(const struct lu_env *env, struct osc_lock *ols,
index abcbb6d..675a5a1 100644 (file)
@@ -4080,28 +4080,79 @@ out_state:
        }
 }
 
        }
 }
 
+loff_t ll_lseek(struct inode *inode, loff_t offset, int whence)
+{
+       struct lu_env *env;
+       struct cl_io *io;
+       struct cl_lseek_io *lsio;
+       __u16 refcheck;
+       int rc;
+       loff_t retval;
+
+       ENTRY;
+
+       env = cl_env_get(&refcheck);
+       if (IS_ERR(env))
+               RETURN(PTR_ERR(env));
+
+       io = vvp_env_thread_io(env);
+       io->ci_obj = ll_i2info(inode)->lli_clob;
+
+       lsio = &io->u.ci_lseek;
+       lsio->ls_start = offset;
+       lsio->ls_whence = whence;
+       lsio->ls_result = -ENXIO;
+
+       do {
+               rc = cl_io_init(env, io, CIT_LSEEK, io->ci_obj);
+               if (!rc)
+                       rc = cl_io_loop(env, io);
+               else
+                       rc = io->ci_result;
+               retval = rc ? : lsio->ls_result;
+               cl_io_fini(env, io);
+       } while (unlikely(io->ci_need_restart));
+
+       cl_env_put(env, &refcheck);
+
+       RETURN(retval);
+}
+
 static loff_t ll_file_seek(struct file *file, loff_t offset, int origin)
 {
        struct inode *inode = file_inode(file);
 static loff_t ll_file_seek(struct file *file, loff_t offset, int origin)
 {
        struct inode *inode = file_inode(file);
-       loff_t retval, eof = 0;
+       loff_t retval = offset, eof = 0;
        ktime_t kstart = ktime_get();
 
        ENTRY;
        ktime_t kstart = ktime_get();
 
        ENTRY;
-       retval = offset + ((origin == SEEK_END) ? i_size_read(inode) :
-                          (origin == SEEK_CUR) ? file->f_pos : 0);
+
        CDEBUG(D_VFSTRACE, "VFS Op:inode="DFID"(%p), to=%llu=%#llx(%d)\n",
               PFID(ll_inode2fid(inode)), inode, retval, retval,
               origin);
 
        CDEBUG(D_VFSTRACE, "VFS Op:inode="DFID"(%p), to=%llu=%#llx(%d)\n",
               PFID(ll_inode2fid(inode)), inode, retval, retval,
               origin);
 
-       if (origin == SEEK_END || origin == SEEK_HOLE || origin == SEEK_DATA) {
+       if (origin == SEEK_END) {
                retval = ll_glimpse_size(inode);
                if (retval != 0)
                        RETURN(retval);
                eof = i_size_read(inode);
        }
 
                retval = ll_glimpse_size(inode);
                if (retval != 0)
                        RETURN(retval);
                eof = i_size_read(inode);
        }
 
-       retval = generic_file_llseek_size(file, offset, origin,
-                                         ll_file_maxbytes(inode), eof);
+       if (origin == SEEK_HOLE || origin == SEEK_DATA) {
+               if (offset < 0)
+                       return -ENXIO;
+
+               /* flush local cache first if any */
+               cl_sync_file_range(inode, offset, OBD_OBJECT_EOF,
+                                  CL_FSYNC_LOCAL, 0);
+
+               retval = ll_lseek(inode, offset, origin);
+               if (retval < 0)
+                       return retval;
+               retval = vfs_setpos(file, retval, ll_file_maxbytes(inode));
+       } else {
+               retval = generic_file_llseek_size(file, offset, origin,
+                                                 ll_file_maxbytes(inode), eof);
+       }
        if (retval >= 0)
                ll_stats_ops_tally(ll_i2sbi(inode), LPROC_LL_LLSEEK,
                                   ktime_us_delta(ktime_get(), kstart));
        if (retval >= 0)
                ll_stats_ops_tally(ll_i2sbi(inode), LPROC_LL_LLSEEK,
                                   ktime_us_delta(ktime_get(), kstart));
index 4aa1515..5e6bfd3 100644 (file)
@@ -278,7 +278,7 @@ static int client_common_fill_super(struct super_block *sb, char *md, char *dt)
                                   OBD_CONNECT2_LSOM |
                                   OBD_CONNECT2_ASYNC_DISCARD |
                                   OBD_CONNECT2_PCC |
                                   OBD_CONNECT2_LSOM |
                                   OBD_CONNECT2_ASYNC_DISCARD |
                                   OBD_CONNECT2_PCC |
-                                  OBD_CONNECT2_CRUSH |
+                                  OBD_CONNECT2_CRUSH | OBD_CONNECT2_LSEEK |
                                   OBD_CONNECT2_GETATTR_PFID;
 
 #ifdef HAVE_LRU_RESIZE_SUPPORT
                                   OBD_CONNECT2_GETATTR_PFID;
 
 #ifdef HAVE_LRU_RESIZE_SUPPORT
@@ -477,7 +477,7 @@ static int client_common_fill_super(struct super_block *sb, char *md, char *dt)
                                  OBD_CONNECT_BULK_MBITS | OBD_CONNECT_SHORTIO |
                                  OBD_CONNECT_FLAGS2 | OBD_CONNECT_GRANT_SHRINK;
        data->ocd_connect_flags2 = OBD_CONNECT2_LOCKAHEAD |
                                  OBD_CONNECT_BULK_MBITS | OBD_CONNECT_SHORTIO |
                                  OBD_CONNECT_FLAGS2 | OBD_CONNECT_GRANT_SHRINK;
        data->ocd_connect_flags2 = OBD_CONNECT2_LOCKAHEAD |
-                                  OBD_CONNECT2_INC_XID;
+                                  OBD_CONNECT2_INC_XID | OBD_CONNECT2_LSEEK;
 
        if (!OBD_FAIL_CHECK(OBD_FAIL_OSC_CONNECT_GRANT_PARAM))
                data->ocd_connect_flags |= OBD_CONNECT_GRANT_PARAM;
 
        if (!OBD_FAIL_CHECK(OBD_FAIL_OSC_CONNECT_GRANT_PARAM))
                data->ocd_connect_flags |= OBD_CONNECT_GRANT_PARAM;
index b8793f6..ba6f63a 100644 (file)
@@ -1596,6 +1596,51 @@ static int vvp_io_read_ahead(const struct lu_env *env,
        RETURN(result);
 }
 
        RETURN(result);
 }
 
+static int vvp_io_lseek_lock(const struct lu_env *env,
+                            const struct cl_io_slice *ios)
+{
+       struct cl_io *io = ios->cis_io;
+       __u64 lock_start = io->u.ci_lseek.ls_start;
+       __u64 lock_end = OBD_OBJECT_EOF;
+       __u32 enqflags = CEF_MUST; /* always take client lock */
+
+       return vvp_io_one_lock(env, io, enqflags, CLM_READ,
+                              lock_start, lock_end);
+}
+
+static int vvp_io_lseek_start(const struct lu_env *env,
+                             const struct cl_io_slice *ios)
+{
+       struct cl_io *io = ios->cis_io;
+       struct inode *inode = vvp_object_inode(io->ci_obj);
+       __u64 start = io->u.ci_lseek.ls_start;
+
+       inode_lock(inode);
+       inode_dio_wait(inode);
+
+       /* At the moment we have DLM lock so just update inode
+        * to know the file size.
+        */
+       ll_merge_attr(env, inode);
+       if (start >= i_size_read(inode)) {
+               io->u.ci_lseek.ls_result = -ENXIO;
+               return -ENXIO;
+       }
+       return 0;
+}
+
+static void vvp_io_lseek_end(const struct lu_env *env,
+                            const struct cl_io_slice *ios)
+{
+       struct cl_io *io = ios->cis_io;
+       struct inode *inode = vvp_object_inode(io->ci_obj);
+
+       if (io->u.ci_lseek.ls_result > i_size_read(inode))
+               io->u.ci_lseek.ls_result = -ENXIO;
+
+       inode_unlock(inode);
+}
+
 static const struct cl_io_operations vvp_io_ops = {
        .op = {
                [CIT_READ] = {
 static const struct cl_io_operations vvp_io_ops = {
        .op = {
                [CIT_READ] = {
@@ -1642,6 +1687,12 @@ static const struct cl_io_operations vvp_io_ops = {
                [CIT_LADVISE] = {
                        .cio_fini       = vvp_io_fini
                },
                [CIT_LADVISE] = {
                        .cio_fini       = vvp_io_fini
                },
+               [CIT_LSEEK] = {
+                       .cio_fini      = vvp_io_fini,
+                       .cio_lock      = vvp_io_lseek_lock,
+                       .cio_start     = vvp_io_lseek_start,
+                       .cio_end       = vvp_io_lseek_end,
+               },
        },
        .cio_read_ahead = vvp_io_read_ahead
 };
        },
        .cio_read_ahead = vvp_io_read_ahead
 };
index ce87872..152984e 100644 (file)
@@ -540,6 +540,12 @@ static int lov_io_slice_init(struct lov_io *lio,
                break;
        }
 
                break;
        }
 
+       case CIT_LSEEK: {
+               lio->lis_pos = io->u.ci_lseek.ls_start;
+               lio->lis_endpos = OBD_OBJECT_EOF;
+               break;
+       }
+
        case CIT_GLIMPSE:
                lio->lis_pos = 0;
                lio->lis_endpos = OBD_OBJECT_EOF;
        case CIT_GLIMPSE:
                lio->lis_pos = 0;
                lio->lis_endpos = OBD_OBJECT_EOF;
@@ -729,6 +735,12 @@ static void lov_io_sub_inherit(struct lov_io_sub *sub, struct lov_io *lio,
                io->u.ci_ladvise.li_flags = parent->u.ci_ladvise.li_flags;
                break;
        }
                io->u.ci_ladvise.li_flags = parent->u.ci_ladvise.li_flags;
                break;
        }
+       case CIT_LSEEK: {
+               io->u.ci_lseek.ls_start = start;
+               io->u.ci_lseek.ls_whence = parent->u.ci_lseek.ls_whence;
+               io->u.ci_lseek.ls_result = parent->u.ci_lseek.ls_result;
+               break;
+       }
        case CIT_GLIMPSE:
        case CIT_MISC:
        default:
        case CIT_GLIMPSE:
        case CIT_MISC:
        default:
@@ -1298,6 +1310,83 @@ static void lov_io_fsync_end(const struct lu_env *env,
        RETURN_EXIT;
 }
 
        RETURN_EXIT;
 }
 
+static void lov_io_lseek_end(const struct lu_env *env,
+                            const struct cl_io_slice *ios)
+{
+       struct lov_io *lio = cl2lov_io(env, ios);
+       struct cl_io *io = lio->lis_cl.cis_io;
+       struct lov_stripe_md *lsm = lio->lis_object->lo_lsm;
+       struct lov_io_sub *sub;
+       loff_t offset = -ENXIO;
+       bool seek_hole = io->u.ci_lseek.ls_whence == SEEK_HOLE;
+
+       ENTRY;
+
+       list_for_each_entry(sub, &lio->lis_active, sub_linkage) {
+               struct cl_io *subio = &sub->sub_io;
+               int index = lov_comp_entry(sub->sub_subio_index);
+               int stripe = lov_comp_stripe(sub->sub_subio_index);
+               loff_t sub_off, lov_off;
+
+               lov_io_end_wrapper(sub->sub_env, subio);
+
+               if (io->ci_result == 0)
+                       io->ci_result = sub->sub_io.ci_result;
+
+               if (io->ci_result)
+                       continue;
+
+               CDEBUG(D_INFO, DFID": entry %x stripe %u: SEEK_%s from %lld\n",
+                      PFID(lu_object_fid(lov2lu(lio->lis_object))),
+                      index, stripe, seek_hole ? "HOLE" : "DATA",
+                      subio->u.ci_lseek.ls_start);
+
+               /* first subio with positive result is what we need */
+               sub_off = subio->u.ci_lseek.ls_result;
+               /* Expected error, offset is out of stripe file size */
+               if (sub_off == -ENXIO)
+                       continue;
+               /* Any other errors are not expected with ci_result == 0 */
+               if (sub_off < 0) {
+                       CDEBUG(D_INFO, "unexpected error: rc = %lld\n",
+                              sub_off);
+                       io->ci_result = sub_off;
+                       continue;
+               }
+               lov_off = lov_stripe_size(lsm, index, sub_off + 1, stripe) - 1;
+               if (lov_off < 0) {
+                       /* the only way to get negatove lov_off here is too big
+                        * result. Return -EOVERFLOW then.
+                        */
+                       io->ci_result = -EOVERFLOW;
+                       CDEBUG(D_INFO, "offset %llu is too big: rc = %d\n",
+                              (u64)lov_off, io->ci_result);
+                       continue;
+               }
+               if (lov_off < io->u.ci_lseek.ls_start) {
+                       io->ci_result = -EINVAL;
+                       CDEBUG(D_INFO, "offset %lld < start %lld: rc = %d\n",
+                              sub_off, io->u.ci_lseek.ls_start, io->ci_result);
+                       continue;
+               }
+               /* resulting offset can be out of component range if stripe
+                * object is full and its file size was returned as virtual
+                * hole start. Skip this result, the next component will give
+                * us correct lseek result.
+                */
+               if (lov_off >= lsm->lsm_entries[index]->lsme_extent.e_end)
+                       continue;
+
+               CDEBUG(D_INFO, "SEEK_%s: %lld->%lld/%lld: rc = %d\n",
+                      seek_hole ? "HOLE" : "DATA",
+                      subio->u.ci_lseek.ls_start, sub_off, lov_off,
+                      sub->sub_io.ci_result);
+               offset = min_t(__u64, offset, lov_off);
+       }
+       io->u.ci_lseek.ls_result = offset;
+       RETURN_EXIT;
+}
+
 static const struct cl_io_operations lov_io_ops = {
        .op = {
                [CIT_READ] = {
 static const struct cl_io_operations lov_io_ops = {
        .op = {
                [CIT_READ] = {
@@ -1363,6 +1452,15 @@ static const struct cl_io_operations lov_io_ops = {
                        .cio_start     = lov_io_start,
                        .cio_end       = lov_io_end
                },
                        .cio_start     = lov_io_start,
                        .cio_end       = lov_io_end
                },
+               [CIT_LSEEK] = {
+                       .cio_fini      = lov_io_fini,
+                       .cio_iter_init = lov_io_iter_init,
+                       .cio_iter_fini = lov_io_iter_fini,
+                       .cio_lock      = lov_io_lock,
+                       .cio_unlock    = lov_io_unlock,
+                       .cio_start     = lov_io_start,
+                       .cio_end       = lov_io_lseek_end
+               },
                [CIT_GLIMPSE] = {
                        .cio_fini      = lov_io_fini,
                },
                [CIT_GLIMPSE] = {
                        .cio_fini      = lov_io_fini,
                },
@@ -1503,6 +1601,7 @@ int lov_io_init_empty(const struct lu_env *env, struct cl_object *obj,
                break;
        case CIT_FSYNC:
        case CIT_LADVISE:
                break;
        case CIT_FSYNC:
        case CIT_LADVISE:
+       case CIT_LSEEK:
        case CIT_SETATTR:
        case CIT_DATA_VERSION:
                result = +1;
        case CIT_SETATTR:
        case CIT_DATA_VERSION:
                result = +1;
@@ -1567,6 +1666,7 @@ int lov_io_init_released(const struct lu_env *env, struct cl_object *obj,
        case CIT_READ:
        case CIT_WRITE:
        case CIT_FAULT:
        case CIT_READ:
        case CIT_WRITE:
        case CIT_FAULT:
+       case CIT_LSEEK:
                io->ci_restore_needed = 1;
                result = -ENODATA;
                break;
                io->ci_restore_needed = 1;
                result = -ENODATA;
                break;
index eddfdda..cbe0201 100644 (file)
@@ -1326,6 +1326,10 @@ static struct cl_io_operations mdc_io_ops = {
                        .cio_start = mdc_io_fsync_start,
                        .cio_end   = osc_io_fsync_end,
                },
                        .cio_start = mdc_io_fsync_start,
                        .cio_end   = osc_io_fsync_end,
                },
+               [CIT_LSEEK] = {
+                       .cio_start  = osc_io_lseek_start,
+                       .cio_end    = osc_io_lseek_end,
+               },
        },
        .cio_read_ahead   = mdc_io_read_ahead,
        .cio_submit       = osc_io_submit,
        },
        .cio_read_ahead   = mdc_io_read_ahead,
        .cio_submit       = osc_io_submit,
index dc4f58f..21fa939 100644 (file)
@@ -126,6 +126,7 @@ void cl_io_fini(const struct lu_env *env, struct cl_io *io)
        case CIT_GLIMPSE:
                break;
        case CIT_LADVISE:
        case CIT_GLIMPSE:
                break;
        case CIT_LADVISE:
+       case CIT_LSEEK:
                break;
        default:
                LBUG();
                break;
        default:
                LBUG();
index 389363f..3bddf18 100644 (file)
@@ -1053,6 +1053,130 @@ void osc_io_end(const struct lu_env *env, const struct cl_io_slice *slice)
 }
 EXPORT_SYMBOL(osc_io_end);
 
 }
 EXPORT_SYMBOL(osc_io_end);
 
+struct osc_lseek_args {
+       struct osc_io *lsa_oio;
+};
+
+static int osc_lseek_interpret(const struct lu_env *env,
+                              struct ptlrpc_request *req,
+                              void *arg, int rc)
+{
+       struct ost_body *reply;
+       struct osc_lseek_args *lsa = arg;
+       struct osc_io *oio = lsa->lsa_oio;
+       struct cl_io *io = oio->oi_cl.cis_io;
+       struct cl_lseek_io *lsio = &io->u.ci_lseek;
+
+       ENTRY;
+
+       if (rc != 0)
+               GOTO(out, rc);
+
+       reply = req_capsule_server_get(&req->rq_pill, &RMF_OST_BODY);
+       if (reply == NULL)
+               GOTO(out, rc = -EPROTO);
+
+       lsio->ls_result = reply->oa.o_size;
+out:
+       osc_async_upcall(&oio->oi_cbarg, rc);
+       RETURN(rc);
+}
+
+int osc_io_lseek_start(const struct lu_env *env,
+                      const struct cl_io_slice *slice)
+{
+       struct cl_io *io = slice->cis_io;
+       struct osc_io *oio = cl2osc_io(env, slice);
+       struct cl_object *obj = slice->cis_obj;
+       struct lov_oinfo *loi = cl2osc(obj)->oo_oinfo;
+       struct cl_lseek_io *lsio = &io->u.ci_lseek;
+       struct obdo *oa = &oio->oi_oa;
+       struct osc_async_cbargs *cbargs = &oio->oi_cbarg;
+       struct obd_export *exp = osc_export(cl2osc(obj));
+       struct ptlrpc_request *req;
+       struct ost_body *body;
+       struct osc_lseek_args *lsa;
+       int rc = 0;
+
+       ENTRY;
+
+       /* No negative values at this point */
+       LASSERT(lsio->ls_start >= 0);
+       LASSERT(lsio->ls_whence == SEEK_HOLE || lsio->ls_whence == SEEK_DATA);
+
+       /* with IO lock taken we have object size in LVB and can check
+        * boundaries prior sending LSEEK RPC
+        */
+       if (lsio->ls_start >= loi->loi_lvb.lvb_size) {
+               /* consider area beyond end of object as hole */
+               if (lsio->ls_whence == SEEK_HOLE)
+                       lsio->ls_result = lsio->ls_start;
+               else
+                       lsio->ls_result = -ENXIO;
+               RETURN(0);
+       }
+
+       /* if LSEEK RPC is not supported by server, consider whole stripe
+        * object is data with hole after end of object
+        */
+       if (!exp_connect_lseek(exp)) {
+               if (lsio->ls_whence == SEEK_HOLE)
+                       lsio->ls_result = loi->loi_lvb.lvb_size;
+               else
+                       lsio->ls_result = lsio->ls_start;
+               RETURN(0);
+       }
+
+       memset(oa, 0, sizeof(*oa));
+       oa->o_oi = loi->loi_oi;
+       oa->o_valid = OBD_MD_FLID | OBD_MD_FLGROUP;
+       oa->o_size = lsio->ls_start;
+       oa->o_mode = lsio->ls_whence;
+       if (oio->oi_lockless) {
+               oa->o_flags = OBD_FL_SRVLOCK;
+               oa->o_valid |= OBD_MD_FLFLAGS;
+       }
+
+       init_completion(&cbargs->opc_sync);
+       req = ptlrpc_request_alloc(class_exp2cliimp(exp), &RQF_OST_SEEK);
+       if (req == NULL)
+               RETURN(-ENOMEM);
+
+       rc = ptlrpc_request_pack(req, LUSTRE_OST_VERSION, OST_SEEK);
+       if (rc < 0) {
+               ptlrpc_request_free(req);
+               RETURN(rc);
+       }
+
+       body = req_capsule_client_get(&req->rq_pill, &RMF_OST_BODY);
+       lustre_set_wire_obdo(&req->rq_import->imp_connect_data, &body->oa, oa);
+       ptlrpc_request_set_replen(req);
+       req->rq_interpret_reply = osc_lseek_interpret;
+       lsa = ptlrpc_req_async_args(lsa, req);
+       lsa->lsa_oio = oio;
+
+       ptlrpcd_add_req(req);
+       cbargs->opc_rpc_sent = 1;
+
+       RETURN(0);
+}
+EXPORT_SYMBOL(osc_io_lseek_start);
+
+void osc_io_lseek_end(const struct lu_env *env,
+                     const struct cl_io_slice *slice)
+{
+       struct osc_io *oio = cl2osc_io(env, slice);
+       struct osc_async_cbargs *cbargs = &oio->oi_cbarg;
+       int rc = 0;
+
+       if (cbargs->opc_rpc_sent) {
+               wait_for_completion(&cbargs->opc_sync);
+               rc = cbargs->opc_rc;
+       }
+       slice->cis_io->ci_result = rc;
+}
+EXPORT_SYMBOL(osc_io_lseek_end);
+
 static const struct cl_io_operations osc_io_ops = {
        .op = {
                [CIT_READ] = {
 static const struct cl_io_operations osc_io_ops = {
        .op = {
                [CIT_READ] = {
@@ -1095,6 +1219,11 @@ static const struct cl_io_operations osc_io_ops = {
                        .cio_end    = osc_io_ladvise_end,
                        .cio_fini   = osc_io_fini
                },
                        .cio_end    = osc_io_ladvise_end,
                        .cio_fini   = osc_io_fini
                },
+               [CIT_LSEEK] = {
+                       .cio_start  = osc_io_lseek_start,
+                       .cio_end    = osc_io_lseek_end,
+                       .cio_fini   = osc_io_fini
+               },
                [CIT_MISC] = {
                        .cio_fini   = osc_io_fini
                }
                [CIT_MISC] = {
                        .cio_fini   = osc_io_fini
                }
index f06c5e9..7861a5b 100644 (file)
@@ -42,6 +42,7 @@
 /lockahead_test
 /logs
 /lovstripe
 /lockahead_test
 /logs
 /lovstripe
+/lseek_test
 /mcreate
 /memhog
 /mirror_io
 /mcreate
 /memhog
 /mirror_io
index 46fd861..8fe8bf9 100644 (file)
@@ -76,7 +76,8 @@ THETESTS += group_lock_test llapi_fid_test sendfile_grouplock mmap_cat
 THETESTS += swap_lock_test lockahead_test mirror_io mmap_mknod_test
 THETESTS += create_foreign_file parse_foreign_file
 THETESTS += create_foreign_dir parse_foreign_dir
 THETESTS += swap_lock_test lockahead_test mirror_io mmap_mknod_test
 THETESTS += create_foreign_file parse_foreign_file
 THETESTS += create_foreign_dir parse_foreign_dir
-THETESTS += check_fallocate splice-test
+THETESTS += check_fallocate splice-test lseek_test
+
 if LIBAIO
 THETESTS += aiocp
 endif
 if LIBAIO
 THETESTS += aiocp
 endif
diff --git a/lustre/tests/lseek_test.c b/lustre/tests/lseek_test.c
new file mode 100644 (file)
index 0000000..0a73293
--- /dev/null
@@ -0,0 +1,102 @@
+/* GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+
+/*
+ * Copyright (c) 2020, Whamcloud.
+ * Author: Mikhail Pershin <mpershin@whamcloud.com>
+ */
+
+/*
+ * Test does lseek with SEEK_DATA/SEEK_HOLE options on a file and prints result.
+ *
+ * Two input options are '-d|--data' for SEEK_DATA and '-l|--hole' for hole
+ */
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+char usage[] =
+"Usage: %s [option] <start> <filename>\n"
+"      where options are:\n"
+"      --hole|-l seek first hole offset after given offset\n"
+"      --data|-d seek first data offset after given offset\n";
+
+int main(int argc, char **argv)
+{
+       int c;
+       struct option long_opts[] = {
+               { .name = "hole", .has_arg = no_argument, .val = 'l' },
+               { .name = "data", .has_arg = no_argument, .val = 'd' },
+               { .name = NULL },
+       };
+       int opt = SEEK_HOLE;
+       int fd;
+       off_t cur_off;
+       off_t ret_off;
+
+       optind = 0;
+       while ((c = getopt_long(argc, argv, "ld", long_opts, NULL)) != -1) {
+               switch (c) {
+               case 'l':
+                       opt = SEEK_HOLE;
+                       break;
+               case 'd':
+                       opt = SEEK_DATA;
+                       break;
+               default:
+                       fprintf(stderr, "error: %s: unknown option '%s'\n",
+                               argv[0], argv[optind - 1]);
+               return -1;
+               }
+       }
+
+       if (argc - optind < 2) {
+               fprintf(stderr, usage, argv[0]);
+               return -1;
+       }
+
+       cur_off = atoll(argv[optind]);
+
+       fd = open(argv[optind + 1], O_RDONLY);
+       if (fd < 0) {
+               fprintf(stderr, "cannot open %s for reading, error %d",
+                       argv[optind + 1], errno);
+               return -1;
+       }
+
+       ret_off = lseek(fd, cur_off, opt);
+       close(fd);
+
+       if (ret_off < 0) {
+               fprintf(stderr, "lseek to %jd failed with %d\n",
+                       cur_off, errno);
+               return ret_off;
+       }
+       printf("%jd\n", ret_off);
+       return 0;
+}
index f6e0ed4..8bbdc50 100755 (executable)
@@ -1340,6 +1340,26 @@ test_12q() {
 }
 run_test 12q "file attributes are refreshed after restore"
 
 }
 run_test 12q "file attributes are refreshed after restore"
 
+test_12r() {
+       # test needs a running copytool
+       copytool setup
+
+       mkdir -p $DIR/$tdir
+       local f=$DIR/$tdir/$tfile
+       local fid=$(copy_file /etc/hosts $f)
+
+       $LFS hsm_archive $f || error "archive of $f failed"
+       wait_request_state $fid ARCHIVE SUCCEED
+       $LFS hsm_release $f || error "release of $f failed"
+
+       offset=$(lseek_test -d 7 $f)
+
+       # we check we had a restore done
+       wait_request_state $fid RESTORE SUCCEED
+       [[ $offset == 7 ]] || error "offset $offset != 7"
+}
+run_test 12r "lseek restores released file"
+
 test_13() {
        local -i i j k=0
        for i in {1..10}; do
 test_13() {
        local -i i j k=0
        for i in {1..10}; do
index 127a396..7b7f513 100755 (executable)
@@ -23591,6 +23591,204 @@ test_426() {
 }
 run_test 426 "splice test on Lustre"
 
 }
 run_test 426 "splice test on Lustre"
 
+lseek_test_430() {
+       local offset
+       local file=$1
+
+       # data at [200K, 400K)
+       dd if=/dev/urandom of=$file bs=256K count=1 seek=1 ||
+               error "256K->512K dd fails"
+       # data at [2M, 3M)
+       dd if=/dev/urandom of=$file bs=1M count=1 seek=2 ||
+               error "2M->3M dd fails"
+       # data at [4M, 5M)
+       dd if=/dev/urandom of=$file bs=1M count=1 seek=4 ||
+               error "4M->5M dd fails"
+       echo "Data at 256K...512K, 2M...3M and 4M...5M"
+       # start at first component hole #1
+       printf "Seeking hole from 1000 ... "
+       offset=$(lseek_test -l 1000 $file)
+       echo $offset
+       [[ $offset == 1000 ]] || error "offset $offset != 1000"
+       printf "Seeking data from 1000 ... "
+       offset=$(lseek_test -d 1000 $file)
+       echo $offset
+       [[ $offset == 262144 ]] || error "offset $offset != 262144"
+
+       # start at first component data block
+       printf "Seeking hole from 300000 ... "
+       offset=$(lseek_test -l 300000 $file)
+       echo $offset
+       [[ $offset == 524288 ]] || error "offset $offset != 524288"
+       printf "Seeking data from 300000 ... "
+       offset=$(lseek_test -d 300000 $file)
+       echo $offset
+       [[ $offset == 300000 ]] || error "offset $offset != 300000"
+
+       # start at the first component but beyond end of object size
+       printf "Seeking hole from 1000000 ... "
+       offset=$(lseek_test -l 1000000 $file)
+       echo $offset
+       [[ $offset == 1000000 ]] || error "offset $offset != 1000000"
+       printf "Seeking data from 1000000 ... "
+       offset=$(lseek_test -d 1000000 $file)
+       echo $offset
+       [[ $offset == 2097152 ]] || error "offset $offset != 2097152"
+
+       # start at second component stripe 2 (empty file)
+       printf "Seeking hole from 1500000 ... "
+       offset=$(lseek_test -l 1500000 $file)
+       echo $offset
+       [[ $offset == 1500000 ]] || error "offset $offset != 1500000"
+       printf "Seeking data from 1500000 ... "
+       offset=$(lseek_test -d 1500000 $file)
+       echo $offset
+       [[ $offset == 2097152 ]] || error "offset $offset != 2097152"
+
+       # start at second component stripe 1 (all data)
+       printf "Seeking hole from 3000000 ... "
+       offset=$(lseek_test -l 3000000 $file)
+       echo $offset
+       [[ $offset == 3145728 ]] || error "offset $offset != 3145728"
+       printf "Seeking data from 3000000 ... "
+       offset=$(lseek_test -d 3000000 $file)
+       echo $offset
+       [[ $offset == 3000000 ]] || error "offset $offset != 3000000"
+
+       dd if=/dev/urandom of=$file bs=640K count=1 seek=1 ||
+               error "2nd dd fails"
+       echo "Add data block at 640K...1280K"
+
+       # start at before new data block, in hole
+       printf "Seeking hole from 600000 ... "
+       offset=$(lseek_test -l 600000 $file)
+       echo $offset
+       [[ $offset == 600000 ]] || error "offset $offset != 600000"
+       printf "Seeking data from 600000 ... "
+       offset=$(lseek_test -d 600000 $file)
+       echo $offset
+       [[ $offset == 655360 ]] || error "offset $offset != 655360"
+
+       # start at the first component new data block
+       printf "Seeking hole from 1000000 ... "
+       offset=$(lseek_test -l 1000000 $file)
+       echo $offset
+       [[ $offset == 1310720 ]] || error "offset $offset != 1310720"
+       printf "Seeking data from 1000000 ... "
+       offset=$(lseek_test -d 1000000 $file)
+       echo $offset
+       [[ $offset == 1000000 ]] || error "offset $offset != 1000000"
+
+       # start at second component stripe 2, new data
+       printf "Seeking hole from 1200000 ... "
+       offset=$(lseek_test -l 1200000 $file)
+       echo $offset
+       [[ $offset == 1310720 ]] || error "offset $offset != 1310720"
+       printf "Seeking data from 1200000 ... "
+       offset=$(lseek_test -d 1200000 $file)
+       echo $offset
+       [[ $offset == 1200000 ]] || error "offset $offset != 1200000"
+
+       # start beyond file end
+       printf "Using offset > filesize ... "
+       lseek_test -l 4000000 $file && error "lseek should fail"
+       printf "Using offset > filesize ... "
+       lseek_test -d 4000000 $file && error "lseek should fail"
+
+       printf "Done\n\n"
+}
+
+test_430a() {
+       $LCTL get_param mdc.*.import | grep -q 'connect_flags:.*seek' ||
+               skip "MDT does not support SEEK_HOLE"
+
+       $LCTL get_param osc.*.import | grep -q 'connect_flags:.*seek' ||
+               skip "OST does not support SEEK_HOLE"
+
+       local file=$DIR/$tdir/$tfile
+
+       mkdir -p $DIR/$tdir
+
+       $LFS setstripe -E 1M -L mdt -E eof -c2 $file
+       # OST stripe #1 will have continuous data at [1M, 3M)
+       # OST stripe #2 is empty
+       echo "Component #1: 1M DoM, component #2: EOF, 2 stripes 1M"
+       lseek_test_430 $file
+       rm $file
+       $LFS setstripe -E 1M -c2 -S 64K -E 10M -c2 -S 1M $file
+       echo "Component #1: 1M, 2 stripes 64K, component #2: EOF, 2 stripes 1M"
+       lseek_test_430 $file
+       rm $file
+       $LFS setstripe -c2 -S 512K $file
+       echo "Two stripes, stripe size 512K"
+       lseek_test_430 $file
+       rm $file
+       # FLR with stale mirror
+       $LFS setstripe -N -E 512K -c1 -S 64K -E eof -c2 -S 512K \
+                      -N -c2 -S 1M $file
+       echo "Mirrored file:"
+       echo "Component #1: 512K, stripe 64K, component #2: EOF, 2 stripes 512K"
+       echo "Plain 2 stripes 1M"
+       lseek_test_430 $file
+       rm $file
+}
+run_test 430a "lseek: SEEK_DATA/SEEK_HOLE basic functionality"
+
+test_430b() {
+       $LCTL get_param osc.*.import | grep -q 'connect_flags:.*seek' ||
+               skip "OST does not support SEEK_HOLE"
+
+       local offset
+       local file=$DIR/$tdir/$tfile
+
+       mkdir -p $DIR/$tdir
+       # Empty layout lseek should fail
+       $MCREATE $file
+       # seek from 0
+       printf "Seeking hole from 0 ... "
+       lseek_test -l 0 $file && error "lseek should fail"
+       printf "Seeking data from 0 ... "
+       lseek_test -d 0 $file && error "lseek should fail"
+       rm $file
+
+       # 1M-hole file
+       $LFS setstripe -E 1M -c2 -E eof $file
+       $TRUNCATE $file 1048576
+       printf "Seeking hole from 1000000 ... "
+       offset=$(lseek_test -l 1000000 $file)
+       echo $offset
+       [[ $offset == 1000000 ]] || error "offset $offset != 1000000"
+       printf "Seeking data from 1000000 ... "
+       lseek_test -d 1000000 $file && error "lseek should fail"
+       # full first component, non-inited second one
+       dd if=/dev/urandom of=$file bs=1M count=1
+       printf "Seeking hole from 1000000 ... "
+       offset=$(lseek_test -l 1000000 $file)
+       echo $offset
+       [[ $offset == 1048576 ]] || error "offset $offset != 1048576"
+       printf "Seeking hole from 1048576 ... "
+       lseek_test -l 1048576 $file && error "lseek should fail"
+       # init second component and truncate back
+       echo "123" >> $file
+       $TRUNCATE $file 1048576
+       ls -lia $file
+       printf "Seeking hole from 1000000 ... "
+       offset=$(lseek_test -l 1000000 $file)
+       echo $offset
+       [[ $offset == 1048576 ]] || error "offset $offset != 1048576"
+       printf "Seeking hole from 1048576 ... "
+       lseek_test -l 1048576 $file && error "lseek should fail"
+       # boundary checks for big values
+       dd if=/dev/urandom of=$file.10g bs=1 count=1 seek=10G
+       offset=$(lseek_test -d 0 $file.10g)
+       [[ $offset == 10737418240 ]] || error "offset $offset != 10737418240"
+       dd if=/dev/urandom of=$file.100g bs=1 count=1 seek=100G
+       offset=$(lseek_test -d 0 $file.100g)
+       [[ $offset == 107374182400 ]] || error "offset $offset != 107374182400"
+       return 0
+}
+run_test 430b "lseek: SEEK_DATA/SEEK_HOLE special cases"
+
 prep_801() {
        [[ $MDS1_VERSION -lt $(version_code 2.9.55) ]] ||
        [[ $OST1_VERSION -lt $(version_code 2.9.55) ]] &&
 prep_801() {
        [[ $MDS1_VERSION -lt $(version_code 2.9.55) ]] ||
        [[ $OST1_VERSION -lt $(version_code 2.9.55) ]] &&
index 58081b4..442b6e2 100755 (executable)
@@ -5367,6 +5367,33 @@ test_107b() {
 }
 run_test 107b "Grouplock is added to the head of waiting list"
 
 }
 run_test 107b "Grouplock is added to the head of waiting list"
 
+test_108a() {
+       local offset
+
+       $LFS setstripe -E 1M -c 1 -E -1 $DIR1/$tfile ||
+               error "Create $DIR1/$tfile failed"
+
+       dd if=/dev/zero of=$DIR1/$tfile bs=10000 count=1 ||
+               error "dd $DIR1/$tfile failed"
+       offset=$(lseek_test -d 5000 $DIR2/$tfile)
+       [[ $offset == 5000 ]] || error "offset $offset != 5000"
+
+       $TRUNCATE $DIR1/$tfile 2000
+       offset=$(lseek_test -l 1000 $DIR2/$tfile)
+       [[ $offset == 2000 ]] || error "offset $offset != 2000"
+
+       #define OBD_FAIL_OSC_DELAY_IO 0x414
+       $LCTL set_param fail_val=4 fail_loc=0x80000414
+       dd if=/dev/zero of=$DIR1/$tfile count=1 bs=8M conv=notrunc oflag=dsync &
+       local pid=$!
+       sleep 2
+
+       offset=$(lseek_test -l 8000 $DIR2/$tfile)
+       wait $pid
+       [[ $offset == 8388608 ]] || error "offset $offset != 8388608"
+}
+run_test 108a "lseek: parallel updates"
+
 log "cleanup: ======================================================"
 
 # kill and wait in each test only guarentee script finish, but command in script
 log "cleanup: ======================================================"
 
 # kill and wait in each test only guarentee script finish, but command in script