From cda353e6efae5013a26aedbe49d8aa6fb8fe456e Mon Sep 17 00:00:00 2001 From: Mikhail Pershin Date: Fri, 21 Aug 2020 19:22:38 +0300 Subject: [PATCH 1/1] LU-10810 clio: SEEK_HOLE/SEEK_DATA on client side 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 Change-Id: I0728329d4bce71c441de581a439cde1aa873fd46 Reviewed-on: https://review.whamcloud.com/39708 Reviewed-by: Oleg Drokin Tested-by: Oleg Drokin --- lustre/include/cl_object.h | 10 +++ lustre/include/lustre_export.h | 5 ++ lustre/include/lustre_osc.h | 4 + lustre/llite/file.c | 63 +++++++++++-- lustre/llite/llite_lib.c | 4 +- lustre/llite/vvp_io.c | 51 +++++++++++ lustre/lov/lov_io.c | 100 +++++++++++++++++++++ lustre/mdc/mdc_dev.c | 4 + lustre/obdclass/cl_io.c | 1 + lustre/osc/osc_io.c | 129 +++++++++++++++++++++++++++ lustre/tests/.gitignore | 1 + lustre/tests/Makefile.am | 3 +- lustre/tests/lseek_test.c | 102 +++++++++++++++++++++ lustre/tests/sanity-hsm.sh | 20 +++++ lustre/tests/sanity.sh | 198 +++++++++++++++++++++++++++++++++++++++++ lustre/tests/sanityn.sh | 27 ++++++ 16 files changed, 713 insertions(+), 9 deletions(-) create mode 100644 lustre/tests/lseek_test.c diff --git a/lustre/include/cl_object.h b/lustre/include/cl_object.h index 6c9a212..ad3512c 100644 --- a/lustre/include/cl_object.h +++ b/lustre/include/cl_object.h @@ -1411,6 +1411,11 @@ enum cl_io_type { * 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 }; @@ -1882,6 +1887,11 @@ struct cl_io { 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; diff --git a/lustre/include/lustre_export.h b/lustre/include/lustre_export.h index 7d29455..f7ee8ec 100644 --- a/lustre/include/lustre_export.h +++ b/lustre/include/lustre_export.h @@ -480,6 +480,11 @@ static inline int exp_connect_encrypt(struct obd_export *exp) 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, diff --git a/lustre/include/lustre_osc.h b/lustre/include/lustre_osc.h index 95edabc..f41ecb7 100644 --- a/lustre/include/lustre_osc.h +++ b/lustre/include/lustre_osc.h @@ -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); +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, diff --git a/lustre/llite/file.c b/lustre/llite/file.c index abcbb6d..675a5a1 100644 --- a/lustre/llite/file.c +++ b/lustre/llite/file.c @@ -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); - loff_t retval, eof = 0; + loff_t retval = offset, eof = 0; 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); - 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 = 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)); diff --git a/lustre/llite/llite_lib.c b/lustre/llite/llite_lib.c index 4aa1515..5e6bfd3 100644 --- a/lustre/llite/llite_lib.c +++ b/lustre/llite/llite_lib.c @@ -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_CRUSH | + OBD_CONNECT2_CRUSH | OBD_CONNECT2_LSEEK | 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_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; diff --git a/lustre/llite/vvp_io.c b/lustre/llite/vvp_io.c index b8793f6..ba6f63a 100644 --- a/lustre/llite/vvp_io.c +++ b/lustre/llite/vvp_io.c @@ -1596,6 +1596,51 @@ static int vvp_io_read_ahead(const struct lu_env *env, 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] = { @@ -1642,6 +1687,12 @@ static const struct cl_io_operations vvp_io_ops = { [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 }; diff --git a/lustre/lov/lov_io.c b/lustre/lov/lov_io.c index ce87872..152984e 100644 --- a/lustre/lov/lov_io.c +++ b/lustre/lov/lov_io.c @@ -540,6 +540,12 @@ static int lov_io_slice_init(struct lov_io *lio, 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; @@ -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; } + 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: @@ -1298,6 +1310,83 @@ static void lov_io_fsync_end(const struct lu_env *env, 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] = { @@ -1363,6 +1452,15 @@ static const struct cl_io_operations lov_io_ops = { .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, }, @@ -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: + case CIT_LSEEK: 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_LSEEK: io->ci_restore_needed = 1; result = -ENODATA; break; diff --git a/lustre/mdc/mdc_dev.c b/lustre/mdc/mdc_dev.c index eddfdda..cbe0201 100644 --- a/lustre/mdc/mdc_dev.c +++ b/lustre/mdc/mdc_dev.c @@ -1326,6 +1326,10 @@ static struct cl_io_operations mdc_io_ops = { .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, diff --git a/lustre/obdclass/cl_io.c b/lustre/obdclass/cl_io.c index dc4f58f..21fa939 100644 --- a/lustre/obdclass/cl_io.c +++ b/lustre/obdclass/cl_io.c @@ -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_LSEEK: break; default: LBUG(); diff --git a/lustre/osc/osc_io.c b/lustre/osc/osc_io.c index 389363f..3bddf18 100644 --- a/lustre/osc/osc_io.c +++ b/lustre/osc/osc_io.c @@ -1053,6 +1053,130 @@ void osc_io_end(const struct lu_env *env, const struct cl_io_slice *slice) } 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] = { @@ -1095,6 +1219,11 @@ static const struct cl_io_operations osc_io_ops = { .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 } diff --git a/lustre/tests/.gitignore b/lustre/tests/.gitignore index f06c5e9..7861a5b 100644 --- a/lustre/tests/.gitignore +++ b/lustre/tests/.gitignore @@ -42,6 +42,7 @@ /lockahead_test /logs /lovstripe +/lseek_test /mcreate /memhog /mirror_io diff --git a/lustre/tests/Makefile.am b/lustre/tests/Makefile.am index 46fd861..8fe8bf9 100644 --- a/lustre/tests/Makefile.am +++ b/lustre/tests/Makefile.am @@ -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 += check_fallocate splice-test +THETESTS += check_fallocate splice-test lseek_test + if LIBAIO THETESTS += aiocp endif diff --git a/lustre/tests/lseek_test.c b/lustre/tests/lseek_test.c new file mode 100644 index 0000000..0a73293 --- /dev/null +++ b/lustre/tests/lseek_test.c @@ -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 + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +char usage[] = +"Usage: %s [option] \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; +} diff --git a/lustre/tests/sanity-hsm.sh b/lustre/tests/sanity-hsm.sh index f6e0ed4..8bbdc50 100755 --- a/lustre/tests/sanity-hsm.sh +++ b/lustre/tests/sanity-hsm.sh @@ -1340,6 +1340,26 @@ test_12q() { } 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 diff --git a/lustre/tests/sanity.sh b/lustre/tests/sanity.sh index 127a396..7b7f513 100755 --- a/lustre/tests/sanity.sh +++ b/lustre/tests/sanity.sh @@ -23591,6 +23591,204 @@ test_426() { } 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) ]] && diff --git a/lustre/tests/sanityn.sh b/lustre/tests/sanityn.sh index 58081b4..442b6e2 100755 --- a/lustre/tests/sanityn.sh +++ b/lustre/tests/sanityn.sh @@ -5367,6 +5367,33 @@ test_107b() { } 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 -- 1.8.3.1