diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 3a3a97718846..781887050df2 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -1086,7 +1086,7 @@ dump_zap(objset_t *os, uint64_t object, void *data, size_t size) { (void) data, (void) size; zap_cursor_t zc; - zap_attribute_t *attrp = zap_attribute_alloc(); + zap_attribute_t *attrp = zap_attribute_long_alloc(); void *prop; unsigned i; @@ -1313,7 +1313,7 @@ dump_zpldir(objset_t *os, uint64_t object, void *data, size_t size) { (void) data, (void) size; zap_cursor_t zc; - zap_attribute_t *attrp = zap_attribute_alloc(); + zap_attribute_t *attrp = zap_attribute_long_alloc(); const char *typenames[] = { /* 0 */ "not specified", /* 1 */ "FIFO", diff --git a/cmd/zhack.c b/cmd/zhack.c index 844f57d98989..7b87431e5284 100644 --- a/cmd/zhack.c +++ b/cmd/zhack.c @@ -203,7 +203,7 @@ static void dump_obj(objset_t *os, uint64_t obj, const char *name) { zap_cursor_t zc; - zap_attribute_t *za = zap_attribute_alloc(); + zap_attribute_t *za = zap_attribute_long_alloc(); (void) printf("%s_obj:\n", name); diff --git a/include/os/linux/zfs/sys/zfs_vfsops_os.h b/include/os/linux/zfs/sys/zfs_vfsops_os.h index b4d5db21f5e5..e742e8dc392e 100644 --- a/include/os/linux/zfs/sys/zfs_vfsops_os.h +++ b/include/os/linux/zfs/sys/zfs_vfsops_os.h @@ -118,6 +118,7 @@ struct zfsvfs { boolean_t z_xattr_sa; /* allow xattrs to be stores as SA */ boolean_t z_draining; /* is true when drain is active */ boolean_t z_drain_cancel; /* signal the unlinked drain to stop */ + boolean_t z_longname; /* Dataset supports long names */ uint64_t z_version; /* ZPL version */ uint64_t z_shares_dir; /* hidden shares dir */ dataset_kstats_t z_kstat; /* fs kstats */ diff --git a/include/os/linux/zfs/sys/zfs_vnops_os.h b/include/os/linux/zfs/sys/zfs_vnops_os.h index 830c76e5743a..7298e9d3a15f 100644 --- a/include/os/linux/zfs/sys/zfs_vnops_os.h +++ b/include/os/linux/zfs/sys/zfs_vnops_os.h @@ -44,6 +44,7 @@ extern int zfs_write_simple(znode_t *zp, const void *data, size_t len, loff_t pos, size_t *resid); extern int zfs_lookup(znode_t *dzp, char *nm, znode_t **zpp, int flags, cred_t *cr, int *direntflags, pathname_t *realpnp); +extern int zfs_get_name(znode_t *dzp, char *name, znode_t *zp); extern int zfs_create(znode_t *dzp, char *name, vattr_t *vap, int excl, int mode, znode_t **zpp, cred_t *cr, int flag, vsecattr_t *vsecp, zidmap_t *mnt_ns); diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 025567e2183f..cda6c4afcc9e 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -81,6 +81,7 @@ typedef enum dmu_objset_type { * All of these include the terminating NUL byte. */ #define ZAP_MAXNAMELEN 256 +#define ZAP_MAXNAMELEN_NEW 1024 #define ZAP_MAXVALUELEN (1024 * 8) #define ZAP_OLDMAXVALUELEN 1024 #define ZFS_MAX_DATASET_NAME_LEN 256 @@ -193,6 +194,7 @@ typedef enum { ZFS_PROP_SNAPSHOTS_CHANGED, ZFS_PROP_PREFETCH, ZFS_PROP_VOLTHREADING, + ZFS_PROP_LONGNAME, ZFS_NUM_PROPS } zfs_prop_t; diff --git a/include/sys/zap.h b/include/sys/zap.h index 0452f4967440..79521eeb3e8a 100644 --- a/include/sys/zap.h +++ b/include/sys/zap.h @@ -306,7 +306,7 @@ int zap_count(objset_t *ds, uint64_t zapobj, uint64_t *count); * match must be exact (ie, same as mask=-1ULL). */ int zap_value_search(objset_t *os, uint64_t zapobj, - uint64_t value, uint64_t mask, char *name); + uint64_t value, uint64_t mask, char *name, uint64_t namelen); /* * Transfer all the entries from fromobj into intoobj. Only works on @@ -378,6 +378,7 @@ void zap_fini(void); * Alloc and free zap_attribute_t. */ zap_attribute_t *zap_attribute_alloc(void); +zap_attribute_t *zap_attribute_long_alloc(void); void zap_attribute_free(zap_attribute_t *attrp); /* diff --git a/include/sys/zfs_ioctl.h b/include/sys/zfs_ioctl.h index 26dfe97604de..d9cfed1f2891 100644 --- a/include/sys/zfs_ioctl.h +++ b/include/sys/zfs_ioctl.h @@ -124,7 +124,7 @@ typedef enum drr_headertype { * default use of "zfs send" won't encounter the bug mentioned above. */ #define DMU_BACKUP_FEATURE_SWITCH_TO_LARGE_BLOCKS (1 << 27) -/* flag #28 is reserved for a Nutanix feature */ +#define DMU_BACKUP_FEATURE_LONGNAME (1 << 28) /* * flag #29 is the last unused bit. It is reserved to indicate a to-be-designed * extension to the stream format which will accomodate more feature flags. @@ -141,7 +141,7 @@ typedef enum drr_headertype { DMU_BACKUP_FEATURE_COMPRESSED | DMU_BACKUP_FEATURE_LARGE_DNODE | \ DMU_BACKUP_FEATURE_RAW | DMU_BACKUP_FEATURE_HOLDS | \ DMU_BACKUP_FEATURE_REDACTED | DMU_BACKUP_FEATURE_SWITCH_TO_LARGE_BLOCKS | \ - DMU_BACKUP_FEATURE_ZSTD) + DMU_BACKUP_FEATURE_ZSTD | DMU_BACKUP_FEATURE_LONGNAME) /* Are all features in the given flag word currently supported? */ #define DMU_STREAM_SUPPORTED(x) (!((x) & ~DMU_BACKUP_FEATURE_MASK)) diff --git a/include/zfeature_common.h b/include/zfeature_common.h index 2515ba321759..ebe3cf5e1e1b 100644 --- a/include/zfeature_common.h +++ b/include/zfeature_common.h @@ -82,6 +82,7 @@ typedef enum spa_feature { SPA_FEATURE_AVZ_V2, SPA_FEATURE_REDACTION_LIST_SPILL, SPA_FEATURE_RAIDZ_EXPANSION, + SPA_FEATURE_LONGNAME, SPA_FEATURES } spa_feature_t; diff --git a/lib/libnvpair/libnvpair.abi b/lib/libnvpair/libnvpair.abi index ef92f3e9bda6..b99a0d6a3373 100644 --- a/lib/libnvpair/libnvpair.abi +++ b/lib/libnvpair/libnvpair.abi @@ -1156,6 +1156,11 @@ + + + + + @@ -2536,11 +2541,6 @@ - - - - - diff --git a/lib/libuutil/libuutil.abi b/lib/libuutil/libuutil.abi index e942d24c6531..620f384d8f5b 100644 --- a/lib/libuutil/libuutil.abi +++ b/lib/libuutil/libuutil.abi @@ -596,14 +596,11 @@ - + - - - - + @@ -800,9 +797,16 @@ + + + + + + + @@ -912,6 +916,25 @@ + + + + + + + + + + + + + + + + + + + @@ -920,12 +943,23 @@ + + + + + + + + + + + @@ -937,8 +971,9 @@ - + + diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index cdd2f04c2629..875ff12d6d0e 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -606,7 +606,7 @@ - + @@ -654,6 +654,10 @@ + + + + @@ -1112,14 +1116,11 @@ - + - - - - + @@ -1824,7 +1825,8 @@ - + + @@ -2832,6 +2834,9 @@ + + + @@ -2844,6 +2849,10 @@ + + + + @@ -2964,6 +2973,24 @@ + + + + + + + + + + + + + + + + + + @@ -2979,6 +3006,7 @@ + @@ -3214,9 +3242,13 @@ + + + + @@ -3249,6 +3281,7 @@ + @@ -3340,18 +3373,9 @@ - - - - - - - - - - - - + + + @@ -3394,11 +3418,6 @@ - - - - - @@ -3411,10 +3430,6 @@ - - - - @@ -3432,12 +3447,9 @@ - - - - - + + @@ -3462,10 +3474,6 @@ - - - - @@ -3478,13 +3486,6 @@ - - - - - - - @@ -3794,12 +3795,18 @@ + + + + + + @@ -4232,9 +4239,13 @@ - - - + + + + + + + @@ -4258,9 +4269,13 @@ - - - + + + + + + + @@ -4277,6 +4292,10 @@ + + + + @@ -4316,6 +4335,13 @@ + + + + + + + @@ -4846,6 +4872,11 @@ + + + + + @@ -5889,7 +5920,8 @@ - + + @@ -6778,7 +6810,7 @@ - + @@ -6786,7 +6818,7 @@ - + @@ -7895,10 +7927,6 @@ - - - - @@ -8939,8 +8967,8 @@ - - + + @@ -9017,7 +9045,7 @@ - + diff --git a/lib/libzfs_core/libzfs_core.abi b/lib/libzfs_core/libzfs_core.abi index 5b95c8f779db..ab7231971c26 100644 --- a/lib/libzfs_core/libzfs_core.abi +++ b/lib/libzfs_core/libzfs_core.abi @@ -594,14 +594,11 @@ - + - - - - + @@ -770,6 +767,13 @@ + + + + + + + @@ -873,12 +877,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -895,8 +929,9 @@ - + + @@ -1119,7 +1154,7 @@ - + @@ -1127,7 +1162,7 @@ - + diff --git a/module/os/freebsd/zfs/zfs_vnops_os.c b/module/os/freebsd/zfs/zfs_vnops_os.c index 9ba44689bad6..c6b6ec5ec00e 100644 --- a/module/os/freebsd/zfs/zfs_vnops_os.c +++ b/module/os/freebsd/zfs/zfs_vnops_os.c @@ -1819,7 +1819,7 @@ zfs_readdir(vnode_t *vp, zfs_uio_t *uio, cred_t *cr, int *eofp, /* * Grab next entry. */ - if ((error = zap_cursor_retrieve(&zc, &zap))) { + if ((error = zap_cursor_retrieve(&zc, zap))) { if ((*eofp = (error == ENOENT)) != 0) break; else diff --git a/module/os/freebsd/zfs/zfs_znode.c b/module/os/freebsd/zfs/zfs_znode.c index 0d4c94555c6b..b7d1d798dc97 100644 --- a/module/os/freebsd/zfs/zfs_znode.c +++ b/module/os/freebsd/zfs/zfs_znode.c @@ -1979,7 +1979,7 @@ zfs_obj_to_path_impl(objset_t *osp, uint64_t obj, sa_handle_t *hdl, (void) sprintf(component + 1, ""); } else { error = zap_value_search(osp, pobj, obj, - ZFS_DIRENT_OBJ(-1ULL), component + 1); + ZFS_DIRENT_OBJ(-1ULL), component + 1, MAXNAMELEN); if (error != 0) break; } @@ -2192,7 +2192,7 @@ zfs_znode_parent_and_name(znode_t *zp, znode_t **dzpp, char *buf) return (SET_ERROR(EINVAL)); err = zap_value_search(zfsvfs->z_os, parent, zp->z_id, - ZFS_DIRENT_OBJ(-1ULL), buf); + ZFS_DIRENT_OBJ(-1ULL), buf, MAXNAMELEN); if (err != 0) return (err); err = zfs_zget(zfsvfs, parent, dzpp); diff --git a/module/os/linux/zfs/zfs_dir.c b/module/os/linux/zfs/zfs_dir.c index ad2ca15e297a..dc03aab2ef96 100644 --- a/module/os/linux/zfs/zfs_dir.c +++ b/module/os/linux/zfs/zfs_dir.c @@ -802,6 +802,7 @@ zfs_link_create(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag) { znode_t *dzp = dl->dl_dzp; zfsvfs_t *zfsvfs = ZTOZSB(zp); + dsl_dataset_t *ds = dmu_objset_ds(zfsvfs->z_os); uint64_t value; int zp_is_dir = S_ISDIR(ZTOI(zp)->i_mode); sa_bulk_attr_t bulk[5]; @@ -847,6 +848,14 @@ zfs_link_create(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag) return (error); } + /* + * If we added a longname activate the SPA_FEATURE_LONGNAME. + */ + if (strlen(dl->dl_name) >= ZAP_MAXNAMELEN) { + ds->ds_feature_activation[SPA_FEATURE_LONGNAME] = + (void *)B_TRUE; + } + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PARENT(zfsvfs), NULL, &dzp->z_id, sizeof (dzp->z_id)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index 2015c20d7340..55da2d71e8c5 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include "zfs_comutil.h" @@ -448,6 +449,12 @@ acl_inherit_changed_cb(void *arg, uint64_t newval) ((zfsvfs_t *)arg)->z_acl_inherit = newval; } +static void +longname_changed_cb(void *arg, uint64_t newval) +{ + ((zfsvfs_t *)arg)->z_longname = newval; +} + static int zfs_register_callbacks(vfs_t *vfsp) { @@ -508,6 +515,8 @@ zfs_register_callbacks(vfs_t *vfsp) zfsvfs); error = error ? error : dsl_prop_register(ds, zfs_prop_to_name(ZFS_PROP_NBMAND), nbmand_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_LONGNAME), longname_changed_cb, zfsvfs); dsl_pool_config_exit(dmu_objset_pool(os), FTAG); if (error) goto unregister; @@ -1139,7 +1148,8 @@ zfs_statvfs(struct inode *ip, struct kstatfs *statp) statp->f_fsid.val[0] = (uint32_t)fsid; statp->f_fsid.val[1] = (uint32_t)(fsid >> 32); statp->f_type = ZFS_SUPER_MAGIC; - statp->f_namelen = MAXNAMELEN - 1; + statp->f_namelen = + zfsvfs->z_longname ? (ZAP_MAXNAMELEN_NEW - 1) : (MAXNAMELEN - 1); /* * We have all of 40 characters to stuff a string here. diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index 88ff8c7e7df4..9b4f4ceb4013 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -531,6 +531,46 @@ zfs_lookup(znode_t *zdp, char *nm, znode_t **zpp, int flags, cred_t *cr, return (error); } +/* + * Perform a linear search in directory for the name of specific inode. + * Note we don't pass in the buffer size of name because it's hardcoded to + * NAME_MAX+1(256) in Linux. + * + * IN: dzp - znode of directory to search. + * zp - znode of the target + * + * OUT: name - dentry name of the target + * + * RETURN: 0 on success, error code on failure. + */ +int +zfs_get_name(znode_t *dzp, char *name, znode_t *zp) +{ + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + int error = 0; + + if ((error = zfs_enter_verify_zp(zfsvfs, dzp, FTAG)) != 0) + return (error); + + if ((error = zfs_verify_zp(zp)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* ctldir should have got their name in zfs_vget */ + if (dzp->z_is_ctldir || zp->z_is_ctldir) { + zfs_exit(zfsvfs, FTAG); + return (ENOENT); + } + + /* buffer len is hardcoded to 256 in Linux kernel */ + error = zap_value_search(zfsvfs->z_os, dzp->z_id, zp->z_id, + ZFS_DIRENT_OBJ(-1ULL), name, ZAP_MAXNAMELEN); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + /* * Attempt to create a new entry in a directory. If the entry * already exists, truncate the file if permissible, else return @@ -1537,7 +1577,7 @@ zfs_readdir(struct inode *ip, zpl_dir_context_t *ctx, cred_t *cr) os = zfsvfs->z_os; offset = ctx->pos; prefetch = zp->z_zn_prefetch; - zap = zap_attribute_alloc(); + zap = zap_attribute_long_alloc(); /* * Initialize the iterator cursor. diff --git a/module/os/linux/zfs/zfs_znode.c b/module/os/linux/zfs/zfs_znode.c index b99df188c64b..10a23449edf9 100644 --- a/module/os/linux/zfs/zfs_znode.c +++ b/module/os/linux/zfs/zfs_znode.c @@ -2144,6 +2144,7 @@ zfs_obj_to_path_impl(objset_t *osp, uint64_t obj, sa_handle_t *hdl, dmu_buf_t *prevdb = NULL; dmu_buf_t *sa_db = NULL; char *path = buf + len - 1; + char *comp_buf; int error; *path = '\0'; @@ -2159,9 +2160,10 @@ zfs_obj_to_path_impl(objset_t *osp, uint64_t obj, sa_handle_t *hdl, return (error); } + comp_buf = kmem_alloc(ZAP_MAXNAMELEN_NEW + 2, KM_SLEEP); for (;;) { uint64_t pobj = 0; - char component[MAXNAMELEN + 2]; + char *component = comp_buf; size_t complen; int is_xattrdir = 0; @@ -2185,7 +2187,8 @@ zfs_obj_to_path_impl(objset_t *osp, uint64_t obj, sa_handle_t *hdl, strcpy(component + 1, ""); } else { error = zap_value_search(osp, pobj, obj, - ZFS_DIRENT_OBJ(-1ULL), component + 1); + ZFS_DIRENT_OBJ(-1ULL), component + 1, + ZAP_MAXNAMELEN_NEW); if (error != 0) break; } @@ -2216,6 +2219,7 @@ zfs_obj_to_path_impl(objset_t *osp, uint64_t obj, sa_handle_t *hdl, if (error == 0) (void) memmove(buf, path, buf + len - path); + kmem_free(comp_buf, ZAP_MAXNAMELEN_NEW +2); return (error); } diff --git a/module/os/linux/zfs/zpl_export.c b/module/os/linux/zfs/zpl_export.c index aa80b72e2d7a..e9ca7b13490a 100644 --- a/module/os/linux/zfs/zpl_export.c +++ b/module/os/linux/zfs/zpl_export.c @@ -24,6 +24,7 @@ */ +#include #include #include #include @@ -109,6 +110,35 @@ zpl_fh_to_dentry(struct super_block *sb, struct fid *fh, return (d_obtain_alias(ip)); } +/* + * In case the filesystem contains name longer than 255, we need to override + * the default get_name so we don't get buffer overflow. Unfortunately, since + * the buffer size is hardcoded in Linux, we will get ESTALE error in this + * case. + */ +static int +zpl_get_name(struct dentry *parent, char *name, struct dentry *child) +{ + cred_t *cr = CRED(); + fstrans_cookie_t cookie; + struct inode *dir = parent->d_inode; + struct inode *ip = child->d_inode; + int error; + + if (!dir || !S_ISDIR(dir->i_mode)) + return (-ENOTDIR); + + crhold(cr); + cookie = spl_fstrans_mark(); + spl_inode_lock_shared(dir); + error = -zfs_get_name(ITOZ(dir), name, ITOZ(ip)); + spl_inode_unlock_shared(dir); + spl_fstrans_unmark(cookie); + crfree(cr); + + return (error); +} + static struct dentry * zpl_get_parent(struct dentry *child) { @@ -153,6 +183,7 @@ zpl_commit_metadata(struct inode *inode) const struct export_operations zpl_export_operations = { .encode_fh = zpl_encode_fh, .fh_to_dentry = zpl_fh_to_dentry, + .get_name = zpl_get_name, .get_parent = zpl_get_parent, .commit_metadata = zpl_commit_metadata, }; diff --git a/module/os/linux/zfs/zpl_inode.c b/module/os/linux/zfs/zpl_inode.c index ad1753f7a071..6d737d9fc6cb 100644 --- a/module/os/linux/zfs/zpl_inode.c +++ b/module/os/linux/zfs/zpl_inode.c @@ -46,9 +46,29 @@ zpl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) pathname_t pn; int zfs_flags = 0; zfsvfs_t *zfsvfs = dentry->d_sb->s_fs_info; + dsl_dataset_t *ds = dmu_objset_ds(zfsvfs->z_os); + size_t dlen = dlen(dentry); - if (dlen(dentry) >= ZAP_MAXNAMELEN) + /* + * If z_longname is disabled, disallow create or rename of names + * longer than ZAP_MAXNAMELEN. + * + * This is needed in cases where longname was enabled first and some + * files/dirs with names > ZAP_MAXNAMELEN were created. And later + * longname was disabled. In such a case allow access to existing + * longnames. But disallow creation newer longnamed entities. + */ + if (!zfsvfs->z_longname && (dlen >= ZAP_MAXNAMELEN)) { + /* + * If this is for create or rename fail it. + */ + if (!dsl_dataset_feature_is_active(ds, SPA_FEATURE_LONGNAME) || + (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET))) + return (ERR_PTR(-ENAMETOOLONG)); + } + if (dlen >= ZAP_MAXNAMELEN_NEW) { return (ERR_PTR(-ENAMETOOLONG)); + } crhold(cr); cookie = spl_fstrans_mark(); @@ -131,6 +151,16 @@ zpl_vap_init(vattr_t *vap, struct inode *dir, umode_t mode, cred_t *cr, } } +static inline bool +is_nametoolong(struct dentry *dentry) +{ + zfsvfs_t *zfsvfs = dentry->d_sb->s_fs_info; + size_t dlen = dlen(dentry); + + return ((!zfsvfs->z_longname && dlen >= ZAP_MAXNAMELEN) || + dlen >= ZAP_MAXNAMELEN_NEW); +} + static int #ifdef HAVE_IOPS_CREATE_USERNS zpl_create(struct user_namespace *user_ns, struct inode *dir, @@ -151,6 +181,10 @@ zpl_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool flag) zidmap_t *user_ns = kcred->user_ns; #endif + if (is_nametoolong(dentry)) { + return (-ENAMETOOLONG); + } + crhold(cr); vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP); zpl_vap_init(vap, dir, mode, cr, user_ns); @@ -201,6 +235,10 @@ zpl_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, zidmap_t *user_ns = kcred->user_ns; #endif + if (is_nametoolong(dentry)) { + return (-ENAMETOOLONG); + } + /* * We currently expect Linux to supply rdev=0 for all sockets * and fifos, but we want to know if this behavior ever changes. @@ -355,6 +393,10 @@ zpl_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) zidmap_t *user_ns = kcred->user_ns; #endif + if (is_nametoolong(dentry)) { + return (-ENAMETOOLONG); + } + crhold(cr); vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP); zpl_vap_init(vap, dir, mode | S_IFDIR, cr, user_ns); @@ -570,6 +612,10 @@ zpl_rename2(struct inode *sdip, struct dentry *sdentry, zidmap_t *user_ns = kcred->user_ns; #endif + if (is_nametoolong(tdentry)) { + return (-ENAMETOOLONG); + } + crhold(cr); if (rflags & RENAME_WHITEOUT) { wo_vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP); @@ -621,6 +667,10 @@ zpl_symlink(struct inode *dir, struct dentry *dentry, const char *name) zidmap_t *user_ns = kcred->user_ns; #endif + if (is_nametoolong(dentry)) { + return (-ENAMETOOLONG); + } + crhold(cr); vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP); zpl_vap_init(vap, dir, S_IFLNK | S_IRWXUGO, cr, user_ns); @@ -773,6 +823,10 @@ zpl_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) int error; fstrans_cookie_t cookie; + if (is_nametoolong(dentry)) { + return (-ENAMETOOLONG); + } + if (ip->i_nlink >= ZFS_LINK_MAX) return (-EMLINK); diff --git a/module/zcommon/zfeature_common.c b/module/zcommon/zfeature_common.c index 309d9bf14cd4..d9c95b21e72f 100644 --- a/module/zcommon/zfeature_common.c +++ b/module/zcommon/zfeature_common.c @@ -754,6 +754,19 @@ zpool_feature_init(void) "Support for raidz expansion", ZFEATURE_FLAG_MOS, ZFEATURE_TYPE_BOOLEAN, NULL, sfeatures); + { + static const spa_feature_t longname_deps[] = { + SPA_FEATURE_EXTENSIBLE_DATASET, + SPA_FEATURE_ENABLED_TXG, + SPA_FEATURE_NONE + }; + zfeature_register(SPA_FEATURE_LONGNAME, + "org.zfsonlinux:longname", "longname", + "support filename upto 1024 bytes", + ZFEATURE_FLAG_READONLY_COMPAT | ZFEATURE_FLAG_PER_DATASET, + ZFEATURE_TYPE_BOOLEAN, longname_deps, sfeatures); + } + zfs_mod_list_supported_free(sfeatures); } diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 764993b45e7c..886414de6148 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -760,6 +760,10 @@ zfs_prop_init(void) ZFS_TYPE_VOLUME, "", "SNAPSHOTS_CHANGED", B_FALSE, B_TRUE, B_TRUE, NULL, sfeatures); + zprop_register_index(ZFS_PROP_LONGNAME, "longname", 0, PROP_INHERIT, + ZFS_TYPE_FILESYSTEM, "on | off", "LONGNAME", boolean_table, + sfeatures); + zfs_mod_list_supported_free(sfeatures); } diff --git a/module/zfs/dmu_recv.c b/module/zfs/dmu_recv.c index 54aa60259ea1..eccfed0eb78f 100644 --- a/module/zfs/dmu_recv.c +++ b/module/zfs/dmu_recv.c @@ -602,6 +602,13 @@ recv_begin_check_feature_flags_impl(uint64_t featureflags, spa_t *spa) !spa_feature_is_enabled(spa, SPA_FEATURE_REDACTED_DATASETS)) return (SET_ERROR(ENOTSUP)); + /* + * If the LONGNAME is not enabled on the target, fail that request. + */ + if ((featureflags & DMU_BACKUP_FEATURE_LONGNAME) && + !spa_feature_is_enabled(spa, SPA_FEATURE_LONGNAME)) + return (SET_ERROR(ENOTSUP)); + return (0); } @@ -990,6 +997,16 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx) dmu_buf_will_dirty(newds->ds_dbuf, tx); dsl_dataset_phys(newds)->ds_flags |= DS_FLAG_INCONSISTENT; + /* + * Activate longname feature if received + */ + if (featureflags & DMU_BACKUP_FEATURE_LONGNAME && + !dsl_dataset_feature_is_active(newds, SPA_FEATURE_LONGNAME)) { + dsl_dataset_activate_feature(newds->ds_object, + SPA_FEATURE_LONGNAME, (void *)B_TRUE, tx); + newds->ds_feature[SPA_FEATURE_LONGNAME] = (void *)B_TRUE; + } + /* * If we actually created a non-clone, we need to create the objset * in our new dataset. If this is a raw send we postpone this until diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index 37c68528bf95..771718fa53df 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -2011,6 +2011,10 @@ setup_featureflags(struct dmu_send_params *dspp, objset_t *os, if (dsl_dataset_feature_is_active(to_ds, SPA_FEATURE_LARGE_DNODE)) { *featureflags |= DMU_BACKUP_FEATURE_LARGE_DNODE; } + + if (dsl_dataset_feature_is_active(to_ds, SPA_FEATURE_LONGNAME)) { + *featureflags |= DMU_BACKUP_FEATURE_LONGNAME; + } return (0); } diff --git a/module/zfs/dsl_dataset.c b/module/zfs/dsl_dataset.c index 09e4583d6c1a..063c71891a98 100644 --- a/module/zfs/dsl_dataset.c +++ b/module/zfs/dsl_dataset.c @@ -493,7 +493,8 @@ dsl_dataset_get_snapname(dsl_dataset_t *ds) return (err); headphys = headdbuf->db_data; err = zap_value_search(dp->dp_meta_objset, - headphys->ds_snapnames_zapobj, ds->ds_object, 0, ds->ds_snapname); + headphys->ds_snapnames_zapobj, ds->ds_object, 0, ds->ds_snapname, + sizeof (ds->ds_snapname)); if (err != 0 && zfs_recover == B_TRUE) { err = 0; (void) snprintf(ds->ds_snapname, sizeof (ds->ds_snapname), diff --git a/module/zfs/dsl_dir.c b/module/zfs/dsl_dir.c index 5aeff86b948f..1b60fa620b8d 100644 --- a/module/zfs/dsl_dir.c +++ b/module/zfs/dsl_dir.c @@ -239,7 +239,8 @@ dsl_dir_hold_obj(dsl_pool_t *dp, uint64_t ddobj, err = zap_value_search(dp->dp_meta_objset, dsl_dir_phys(dd->dd_parent)-> dd_child_dir_zapobj, - ddobj, 0, dd->dd_myname); + ddobj, 0, dd->dd_myname, + sizeof (dd->dd_myname)); } if (err != 0) goto errout; diff --git a/module/zfs/zap.c b/module/zfs/zap.c index 7b3be5ecabc8..6af7141f57c7 100644 --- a/module/zfs/zap.c +++ b/module/zfs/zap.c @@ -750,7 +750,8 @@ zap_put_leaf_maybe_grow_ptrtbl(zap_name_t *zn, zap_leaf_t *l, static int fzap_checkname(zap_name_t *zn) { - if (zn->zn_key_orig_numints * zn->zn_key_intlen > ZAP_MAXNAMELEN) + uint32_t maxnamelen = zn->zn_normbuf_len; + if (zn->zn_key_orig_numints * zn->zn_key_intlen > maxnamelen) return (SET_ERROR(ENAMETOOLONG)); return (0); } @@ -1016,7 +1017,7 @@ zap_create_link_dnsize(objset_t *os, dmu_object_type_t ot, uint64_t parent_obj, int zap_value_search(objset_t *os, uint64_t zapobj, uint64_t value, uint64_t mask, - char *name) + char *name, uint64_t namelen) { zap_cursor_t zc; int err; @@ -1024,12 +1025,13 @@ zap_value_search(objset_t *os, uint64_t zapobj, uint64_t value, uint64_t mask, if (mask == 0) mask = -1ULL; - zap_attribute_t *za = zap_attribute_alloc(); + zap_attribute_t *za = zap_attribute_long_alloc(); for (zap_cursor_init(&zc, os, zapobj); (err = zap_cursor_retrieve(&zc, za)) == 0; zap_cursor_advance(&zc)) { if ((za->za_first_integer & mask) == (value & mask)) { - (void) strlcpy(name, za->za_name, MAXNAMELEN); + if (strlcpy(name, za->za_name, namelen) >= namelen) + err = SET_ERROR(ENAMETOOLONG); break; } } @@ -1044,7 +1046,7 @@ zap_join(objset_t *os, uint64_t fromobj, uint64_t intoobj, dmu_tx_t *tx) zap_cursor_t zc; int err = 0; - zap_attribute_t *za = zap_attribute_alloc(); + zap_attribute_t *za = zap_attribute_long_alloc(); for (zap_cursor_init(&zc, os, fromobj); zap_cursor_retrieve(&zc, za) == 0; (void) zap_cursor_advance(&zc)) { @@ -1069,7 +1071,7 @@ zap_join_key(objset_t *os, uint64_t fromobj, uint64_t intoobj, zap_cursor_t zc; int err = 0; - zap_attribute_t *za = zap_attribute_alloc(); + zap_attribute_t *za = zap_attribute_long_alloc(); for (zap_cursor_init(&zc, os, fromobj); zap_cursor_retrieve(&zc, za) == 0; (void) zap_cursor_advance(&zc)) { @@ -1094,7 +1096,7 @@ zap_join_increment(objset_t *os, uint64_t fromobj, uint64_t intoobj, zap_cursor_t zc; int err = 0; - zap_attribute_t *za = zap_attribute_alloc(); + zap_attribute_t *za = zap_attribute_long_alloc(); for (zap_cursor_init(&zc, os, fromobj); zap_cursor_retrieve(&zc, za) == 0; (void) zap_cursor_advance(&zc)) { diff --git a/module/zfs/zap_micro.c b/module/zfs/zap_micro.c index 4d472a60b0fd..47104f7e3fc2 100644 --- a/module/zfs/zap_micro.c +++ b/module/zfs/zap_micro.c @@ -131,12 +131,12 @@ zap_hash(zap_name_t *zn) } static int -zap_normalize(zap_t *zap, const char *name, char *namenorm, int normflags) +zap_normalize(zap_t *zap, const char *name, char *namenorm, int normflags, + size_t outlen) { ASSERT(!(zap_getflags(zap) & ZAP_FLAG_UINT64_KEY)); size_t inlen = strlen(name) + 1; - size_t outlen = ZAP_MAXNAMELEN; int err = 0; (void) u8_textprep_str((char *)name, &inlen, namenorm, &outlen, @@ -149,23 +149,39 @@ zap_normalize(zap_t *zap, const char *name, char *namenorm, int normflags) boolean_t zap_match(zap_name_t *zn, const char *matchname) { + boolean_t res = B_FALSE; ASSERT(!(zap_getflags(zn->zn_zap) & ZAP_FLAG_UINT64_KEY)); if (zn->zn_matchtype & MT_NORMALIZE) { - char norm[ZAP_MAXNAMELEN]; + size_t namelen = zn->zn_normbuf_len; + char normbuf[ZAP_MAXNAMELEN]; + char *norm = normbuf; - if (zap_normalize(zn->zn_zap, matchname, norm, - zn->zn_normflags) != 0) - return (B_FALSE); + /* + * Cannot allocate this on-stack as it exceed the stack-limit of + * 1024. + */ + if (namelen > ZAP_MAXNAMELEN) + norm = kmem_alloc(namelen, KM_SLEEP); - return (strcmp(zn->zn_key_norm, norm) == 0); + if (zap_normalize(zn->zn_zap, matchname, norm, + zn->zn_normflags, namelen) != 0) { + res = B_FALSE; + } else { + res = (strcmp(zn->zn_key_norm, norm) == 0); + } + if (norm != normbuf) + kmem_free(norm, namelen); } else { - return (strcmp(zn->zn_key_orig, matchname) == 0); + res = (strcmp(zn->zn_key_orig, matchname) == 0); } + return (res); } static kmem_cache_t *zap_name_cache; static kmem_cache_t *zap_attr_cache; +static kmem_cache_t *zap_name_long_cache; +static kmem_cache_t *zap_attr_long_cache; void zap_init() @@ -177,6 +193,14 @@ zap_init() zap_attr_cache = kmem_cache_create("zap_attr_cache", sizeof (zap_attribute_t) + ZAP_MAXNAMELEN, 0, NULL, NULL, NULL, NULL, NULL, 0); + + zap_name_long_cache = kmem_cache_create("zap_name_long", + sizeof (zap_name_t) + ZAP_MAXNAMELEN_NEW, 0, NULL, NULL, + NULL, NULL, NULL, 0); + + zap_attr_long_cache = kmem_cache_create("zap_attr_long_cache", + sizeof (zap_attribute_t) + ZAP_MAXNAMELEN_NEW, 0, NULL, + NULL, NULL, NULL, NULL, 0); } void @@ -184,33 +208,47 @@ zap_fini() { kmem_cache_destroy(zap_name_cache); kmem_cache_destroy(zap_attr_cache); + kmem_cache_destroy(zap_name_long_cache); + kmem_cache_destroy(zap_attr_long_cache); } static zap_name_t * -zap_name_alloc(zap_t *zap) +zap_name_alloc(zap_t *zap, boolean_t longname) { - zap_name_t *zn = kmem_cache_alloc(zap_name_cache, KM_SLEEP); + kmem_cache_t *cache = longname ? zap_name_long_cache : zap_name_cache; + zap_name_t *zn = kmem_cache_alloc(cache, KM_SLEEP); + zn->zn_zap = zap; + zn->zn_normbuf_len = longname ? ZAP_MAXNAMELEN_NEW : ZAP_MAXNAMELEN; return (zn); } void zap_name_free(zap_name_t *zn) { - kmem_cache_free(zap_name_cache, zn); + if (zn->zn_normbuf_len == ZAP_MAXNAMELEN) { + kmem_cache_free(zap_name_cache, zn); + } else { + ASSERT3U(zn->zn_normbuf_len, ==, ZAP_MAXNAMELEN_NEW); + kmem_cache_free(zap_name_long_cache, zn); + } } static int zap_name_init_str(zap_name_t *zn, const char *key, matchtype_t mt) { zap_t *zap = zn->zn_zap; + size_t key_len = strlen(key) + 1; + + /* Make sure zn is allocated for longname if key is long */ + IMPLY(key_len > ZAP_MAXNAMELEN, + zn->zn_normbuf_len == ZAP_MAXNAMELEN_NEW); zn->zn_key_intlen = sizeof (*key); zn->zn_key_orig = key; - zn->zn_key_orig_numints = strlen(zn->zn_key_orig) + 1; + zn->zn_key_orig_numints = key_len; zn->zn_matchtype = mt; zn->zn_normflags = zap->zap_normflags; - zn->zn_normbuf_len = ZAP_MAXNAMELEN; /* * If we're dealing with a case sensitive lookup on a mixed or @@ -226,7 +264,7 @@ zap_name_init_str(zap_name_t *zn, const char *key, matchtype_t mt) * what the hash is computed from. */ if (zap_normalize(zap, key, zn->zn_normbuf, - zap->zap_normflags) != 0) + zap->zap_normflags, zn->zn_normbuf_len) != 0) return (SET_ERROR(ENOTSUP)); zn->zn_key_norm = zn->zn_normbuf; zn->zn_key_norm_numints = strlen(zn->zn_key_norm) + 1; @@ -245,7 +283,7 @@ zap_name_init_str(zap_name_t *zn, const char *key, matchtype_t mt) * what the matching is based on. (Not the hash!) */ if (zap_normalize(zap, key, zn->zn_normbuf, - zn->zn_normflags) != 0) + zn->zn_normflags, zn->zn_normbuf_len) != 0) return (SET_ERROR(ENOTSUP)); zn->zn_key_norm_numints = strlen(zn->zn_key_norm) + 1; } @@ -256,7 +294,8 @@ zap_name_init_str(zap_name_t *zn, const char *key, matchtype_t mt) zap_name_t * zap_name_alloc_str(zap_t *zap, const char *key, matchtype_t mt) { - zap_name_t *zn = zap_name_alloc(zap); + size_t key_len = strlen(key) + 1; + zap_name_t *zn = zap_name_alloc(zap, (key_len > ZAP_MAXNAMELEN)); if (zap_name_init_str(zn, key, mt) != 0) { zap_name_free(zn); return (NULL); @@ -491,7 +530,7 @@ mzap_open(objset_t *os, uint64_t obj, dmu_buf_t *db) zfs_btree_create_custom(&zap->zap_m.zap_tree, mze_compare, mze_find_in_buf, sizeof (mzap_ent_t), 512); - zap_name_t *zn = zap_name_alloc(zap); + zap_name_t *zn = zap_name_alloc(zap, B_FALSE); for (uint16_t i = 0; i < zap->zap_m.zap_num_chunks; i++) { mzap_ent_phys_t *mze = &zap_m_phys(zap)->mz_chunk[i]; @@ -701,7 +740,7 @@ mzap_upgrade(zap_t **zapp, const void *tag, dmu_tx_t *tx, zap_flags_t flags) fzap_upgrade(zap, tx, flags); - zap_name_t *zn = zap_name_alloc(zap); + zap_name_t *zn = zap_name_alloc(zap, B_FALSE); for (int i = 0; i < nchunks; i++) { mzap_ent_phys_t *mze = &mzp->mz_chunk[i]; if (mze->mze_name[0] == 0) @@ -1527,21 +1566,38 @@ zap_remove_uint64(objset_t *os, uint64_t zapobj, const uint64_t *key, } -zap_attribute_t * -zap_attribute_alloc(void) +static zap_attribute_t * +zap_attribute_alloc_impl(boolean_t longname) { - uint32_t len = ZAP_MAXNAMELEN; zap_attribute_t *za; - za = kmem_cache_alloc(zap_attr_cache, KM_SLEEP); - za->za_name_len = len; + za = kmem_cache_alloc((longname)? zap_attr_long_cache : zap_attr_cache, + KM_SLEEP); + za->za_name_len = (longname)? ZAP_MAXNAMELEN_NEW : ZAP_MAXNAMELEN; return (za); } +zap_attribute_t * +zap_attribute_alloc() +{ + return (zap_attribute_alloc_impl(B_FALSE)); +} + +zap_attribute_t * +zap_attribute_long_alloc() +{ + return (zap_attribute_alloc_impl(B_TRUE)); +} + void zap_attribute_free(zap_attribute_t *za) { - kmem_cache_free(zap_attr_cache, za); + if (za->za_name_len == ZAP_MAXNAMELEN) { + kmem_cache_free(zap_attr_cache, za); + } else { + ASSERT3U(za->za_name_len, ==, ZAP_MAXNAMELEN_NEW); + kmem_cache_free(zap_attr_long_cache, za); + } } /* diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index b2b06881bdd4..bbdd78dd75b0 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -2566,6 +2566,44 @@ zfs_prop_set_special(const char *dsname, zprop_source_t source, } break; } + case ZFS_PROP_LONGNAME: + { + zfsvfs_t *zfsvfs; + dsl_dataset_t *ds; + + /* + * Ignore the checks if the property is being applied as part of + * 'zfs receive'. Because, we already check if the local pool + * has SPA_FEATURE_LONGNAME enabled in dmu_recv_begin_check(). + */ + if (source == ZPROP_SRC_RECEIVED) { + cmn_err(CE_NOTE, "Skipping ZFS_PROP_LONGNAME checks " + "for dsname=%s\n", dsname); + err = -1; + break; + } + + if ((err = zfsvfs_hold(dsname, FTAG, &zfsvfs, B_FALSE)) != 0) { + cmn_err(CE_WARN, "%s:%d Failed to hold for dsname=%s " + "err=%d\n", __FILE__, __LINE__, dsname, err); + break; + } + + ds = dmu_objset_ds(zfsvfs->z_os); + + if (!spa_feature_is_enabled(zfsvfs->z_os->os_spa, + SPA_FEATURE_LONGNAME)) { + err = ENOTSUP; + } else { + /* + * Set err to -1 to force the zfs_set_prop_nvlist code + * down the default path to set the value in the nvlist. + */ + err = -1; + } + zfsvfs_rele(zfsvfs, FTAG); + break; + } default: err = -1; } diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index a0b74ef4a8c6..ddaeaaf3b20e 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -138,6 +138,10 @@ pre = post = tags = ['functional', 'largest_pool'] +[tests/functional/longname:Linux] +tests = ['longname_001_pos', 'longname_002_pos', 'longname_003_pos'] +tags = ['functional', 'longname'] + [tests/functional/mmap:Linux] tests = ['mmap_libaio_001_pos', 'mmap_sync_001_pos'] tags = ['functional', 'mmap'] @@ -182,7 +186,7 @@ tests = ['renameat2_noreplace', 'renameat2_exchange', 'renameat2_whiteout'] tags = ['functional', 'renameat2'] [tests/functional/rsend:Linux] -tests = ['send_realloc_dnode_size', 'send_encrypted_files'] +tests = ['send_realloc_dnode_size', 'send_encrypted_files', 'send-c_longname'] tags = ['functional', 'rsend'] [tests/functional/simd:Linux] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index fe9c92108725..24d81147e5b8 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1552,6 +1552,11 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/link_count/link_count_001.ksh \ functional/link_count/link_count_root_inode.ksh \ functional/link_count/setup.ksh \ + functional/longname/cleanup.ksh \ + functional/longname/longname_001_pos.ksh \ + functional/longname/longname_002_pos.ksh \ + functional/longname/longname_003_pos.ksh \ + functional/longname/setup.ksh \ functional/log_spacemap/log_spacemap_import_logs.ksh \ functional/migration/cleanup.ksh \ functional/migration/migration_001_pos.ksh \ @@ -1884,6 +1889,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/rsend/rsend_031_pos.ksh \ functional/rsend/send-c_embedded_blocks.ksh \ functional/rsend/send-c_incremental.ksh \ + functional/rsend/send-c_longname.ksh \ functional/rsend/send-c_lz4_disabled.ksh \ functional/rsend/send-c_mixed_compression.ksh \ functional/rsend/send-c_props.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg b/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg index 6ebce9459190..bb1896a72825 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg @@ -107,5 +107,6 @@ if is_linux || is_freebsd; then "feature@block_cloning" "feature@vdev_zaps_v2" "feature@raidz_expansion" + "feature@longname" ) fi diff --git a/tests/zfs-tests/tests/functional/longname/cleanup.ksh b/tests/zfs-tests/tests/functional/longname/cleanup.ksh new file mode 100755 index 000000000000..aac8062ec408 --- /dev/null +++ b/tests/zfs-tests/tests/functional/longname/cleanup.ksh @@ -0,0 +1,34 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2021 by Nutanix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +default_cleanup diff --git a/tests/zfs-tests/tests/functional/longname/longname_001_pos.ksh b/tests/zfs-tests/tests/functional/longname/longname_001_pos.ksh new file mode 100755 index 000000000000..e7ecd2351ce4 --- /dev/null +++ b/tests/zfs-tests/tests/functional/longname/longname_001_pos.ksh @@ -0,0 +1,139 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2021 by Nutanix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Verify the support for long filenames. +# +# STRATEGY: +# 0. On a fresh dataset ensure property "longname" is enabled by default. +# 1. Disable the longname. +# 2. Try to create a filename whose length is > 256 bytes. This should fail. +# 3. Enable "longname" property on the dataset. +# 4. Try to create files and dirs whose names are > 256 bytes. +# 5. Ensure that "ls" is able to list the file. +# 6. Ensure stat(1) is able to stat the file/directory. +# 7. Try to rename the files and directories +# 8. Try to delete the files and directories + +verify_runnable "global" + +WORKDIR=$TESTDIR/workdir +MOVEDIR=$TESTDIR/movedir/level2/level3/level4/level5/level6 + +function cleanup +{ + log_must rm -rf $WORKDIR + log_must rm -rf $TESTDIR/movedir +} + +LONGNAME=$(printf 'a%.0s' {1..512}) +LONGFNAME="file-$LONGNAME" +LONGDNAME="dir-$LONGNAME" +LONGPNAME="mypipe-$LONGNAME" +LONGCNAME="char_dev-$LONGNAME" +LONGLNAME="link-$LONGNAME" +LONGNAME_255=$(printf 'a%.0s' {1..255}) +LONGNAME_1023=$(printf 'a%.0s' {1..1023}) + + +log_assert "Check longname support for directories/files" + +log_onexit cleanup + +log_must mkdir $WORKDIR +log_must mkdir -p $MOVEDIR + +# Disable longname support +log_must zfs set longname=off $TESTPOOL/$TESTFS + +#Ensure a file of length 255bytes can be created +log_must touch $WORKDIR/$LONGNAME_255 + +#Where as file of length 256bytes should fail +long_mustnot touch $WORKDIR/${LONGNAME_255}b + +# Try to create a file with long name with property "longname=off" +log_mustnot touch $WORKDIR/$LONGFNAME +log_mustnot mkdir $WORKDIR/$LONGDNAME + +# Enable longname support +log_must zfs set longname=on $TESTPOOL/$TESTFS + +# Retry the longname creates and that should succeed +log_must mkdir $WORKDIR/$LONGDNAME +log_must touch $WORKDIR/$LONGFNAME + +# Should be able to create a file with name of 1023 chars +log_must touch $WORKDIR/$LONGNAME_1023 + +# And names longer that 1023 should fail +log_mustnot touch $WORKDIR/${LONGNAME_1023}b + +# Ensure the longnamed dir/file can be listed. +name=$(ls $WORKDIR/$LONGFNAME) +if [[ "${name}" != "$WORKDIR/$LONGFNAME" ]]; then + log_fail "Unable to list: $WORKDIR/$LONGFNAME ret:$name" +fi + +name=$(ls -d $WORKDIR/$LONGDNAME) +if [[ "${name}" != "$WORKDIR/$LONGDNAME" ]]; then + log_fail "Unable to list: $WORKDIR/$LONGDNAME ret:$name" +fi + +# Ensure stat works +log_must stat $WORKDIR/$LONGFNAME +log_must stat $WORKDIR/$LONGDNAME + +# Ensure softlinks can be created from a longname to +# another longnamed file. +log_must ln -s $WORKDIR/$LONGFNAME $WORKDIR/$LONGLNAME + +# Ensure a longnamed pipe and character device file +# can be created +log_must mknod $WORKDIR/$LONGPNAME p +log_must mknod $WORKDIR/$LONGCNAME c 92 1 + +# Fetch the inode numbers of the objects created. +INODE_NO_FILE=$(stat -c %i $WORKDIR/$LONGFNAME) +INODE_NO_DIR=$(stat -c %i $WORKDIR/$LONGDNAME) +INODE_NO_LINK=$(stat -c %i $WORKDIR/$LONGLNAME) +INODE_NO_PIPE=$(stat -c %i $WORKDIR/$LONGPNAME) +INODE_NO_CHAR=$(stat -c %i $WORKDIR/$LONGCNAME) + +# Ensure we can rename the longname file +log_must mv $WORKDIR/$LONGFNAME $WORKDIR/file2 + +# Delete the long named dir/file +log_must rmdir $WORKDIR/$LONGDNAME +log_must rm $WORKDIR/file2 +log_must rm $WORKDIR/$LONGPNAME +log_must rm $WORKDIR/$LONGCNAME +log_must rm $WORKDIR/$LONGLNAME + +log_pass diff --git a/tests/zfs-tests/tests/functional/longname/longname_002_pos.ksh b/tests/zfs-tests/tests/functional/longname/longname_002_pos.ksh new file mode 100755 index 000000000000..dd2acabb3e02 --- /dev/null +++ b/tests/zfs-tests/tests/functional/longname/longname_002_pos.ksh @@ -0,0 +1,115 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2021 by Nutanix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Check if longname feature is disabled by default and can be enabled. +# +# STRATEGY: +# 1. Create a zpool with longname feature disabled +# 2. Attempt to enable 'longname' property should fail. +# 3. Attempt to create a longnamed (>255) file should fail. +# 4. Enable the feature@longname +# 5. Enable 'longname' property on the dataset. +# 6. Should be able to create long named files and directories. +# 7. Should be able to disable longname property. +# 8. This should disallow creating new longnamed file/dirs. But, should be +# able to access existing longnamed files/dirs. +verify_runnable "global" + +function cleanup +{ + log_must rm -rf $WORKDIR + poolexists $TESTPOOL && zpool destroy $TESTPOOL +} + +log_assert "Check feature@longname and 'longname' dataset propery work correctly" + +log_onexit cleanup + +log_must zpool destroy $TESTPOOL + +log_must zpool create -o feature@longname=disabled $TESTPOOL $DISKS + +log_must zfs create $TESTPOOL/$TESTFS2 + +log_must zfs set mountpoint=$TESTDIR2 $TESTPOOL/$TESTFS2 + +log_mustnot zfs set longname=on $TESTPOOL/$TESTFS2 + +LONGNAME=$(printf 'a%.0s' {1..512}) +LONGFNAME="file-$LONGNAME" +LONGDNAME="dir-$LONGNAME" +SHORTDNAME="dir-short" +SHORTFNAME="file-short" +WORKDIR=$TESTDIR2/workdir + +log_must mkdir $WORKDIR +log_mustnot touch $WORKDIR/$LONGFNAME +log_mustnot mkdir $WORKDIR/$LONGDNAME + +log_must zpool set feature@longname=enabled $TESTPOOL +log_must zfs set longname=on $TESTPOOL/$TESTFS2 + +log_must mkdir $WORKDIR/$LONGDNAME +log_must touch $WORKDIR/$LONGFNAME + +# Ensure the above changes are synced out. +log_must zpool sync $TESTPOOL + +# Ensure that the feature is activated once longnamed files are created. +state=$(zpool get feature@longname -H -o value $TESTPOOL) +log_note "feature@longname on pool: $TESTPOOL : $state" + +if [[ "$state" != "active" ]]; then + log_fail "feature@longname has state $state (expected active)" +fi + +# Set longname=off. +log_must zfs set longname=off $TESTPOOL/$TESTFS2 + +# Ensure no new file/directory with longnames can be created or can be renamed +# to. +log_mustnot mkdir $WORKDIR/${LONGDNAME}.1 +log_mustnot touch $WORKDIR/${LONGFNAME}.1 +log_must mkdir $WORKDIR/$SHORTDNAME +log_mustnot mv $WORKDIR/$SHORTDNAME $WORKDIR/${LONGDNAME}.1 +log_must touch $WORKDIR/$SHORTFNAME +log_mustnot mv $WORKDIR/$SHORTFNAME $WORKDIR/${LONGFNAME}.1 + +#Cleanup shortnames +log_must rmdir $WORKDIR/$SHORTDNAME +log_must rm $WORKDIR/$SHORTFNAME + +# But, should be able to stat and rename existing files +log_must stat $WORKDIR/$LONGDNAME +log_must stat $WORKDIR/$LONGFNAME +log_must mv $WORKDIR/$LONGDNAME $WORKDIR/$SHORTDNAME +log_must mv $WORKDIR/$LONGFNAME $WORKDIR/$SHORTFNAME + +log_pass diff --git a/tests/zfs-tests/tests/functional/longname/longname_003_pos.ksh b/tests/zfs-tests/tests/functional/longname/longname_003_pos.ksh new file mode 100755 index 000000000000..f684b514399d --- /dev/null +++ b/tests/zfs-tests/tests/functional/longname/longname_003_pos.ksh @@ -0,0 +1,113 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2021 by Nutanix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Check if longnames are handled correctly by ZIL replay and feature is activated. +# +# STRATEGY: +# 1. Create a zpool with longname feature disabled +# 2. Enable the feature@longname +# 3. Enable 'longname' property on the dataset. +# 4. Freeze the zpool +# 5. Create a longname +# 6. Export and import the zpool. +# 7. Replaying of longname create should activate the feature@longname +verify_runnable "global" + +function cleanup +{ + log_must rm -rf $WORKDIR + poolexists $TESTPOOL && zpool destroy $TESTPOOL +} + +log_assert "Check feature@longname and 'longname' dataset propery work correctly" + +log_onexit cleanup + +poolexists $TESTPOOL && zpool destroy $TESTPOOL + +log_must zpool create -o feature@longname=disabled $TESTPOOL $DISKS + +log_must zfs create $TESTPOOL/$TESTFS + +log_must zfs set mountpoint=$TESTDIR $TESTPOOL/$TESTFS + +LONGNAME=$(printf 'a%.0s' {1..512}) +LONGFNAME="file-$LONGNAME" +LONGDNAME="dir-$LONGNAME" +SHORTDNAME="dir-short" +SHORTFNAME="file-short" +WORKDIR=$TESTDIR/workdir + +log_must mkdir $WORKDIR + +log_must zpool set feature@longname=enabled $TESTPOOL +log_must zfs set longname=on $TESTPOOL/$TESTFS + +# Ensure that the feature is NOT activated yet as no longnamed file is created. +state=$(zpool get feature@longname -H -o value $TESTPOOL) +log_note "feature@longname on pool: $TESTPOOL : $state" + +if [[ "$state" != "enabled" ]]; then + log_fail "feature@longname has state $state (expected enabled)" +fi + +# +# This dd command works around an issue where ZIL records aren't created +# after freezing the pool unless a ZIL header already exists. Create a file +# synchronously to force ZFS to write one out. +# +log_must dd if=/dev/zero of=/$WORKDIR/sync conv=fdatasync,fsync bs=1 count=1 + +log_must zpool freeze $TESTPOOL + +log_must mkdir $WORKDIR/$LONGDNAME +log_must touch $WORKDIR/$LONGFNAME + +# Export and re-import the zpool +log_must zpool export $TESTPOOL +log_must zpool import $TESTPOOL + +# Ensure that the feature is activated once longnamed files are created. +state=$(zpool get feature@longname -H -o value $TESTPOOL) +log_note "feature@longname on pool: $TESTPOOL : $state" +if [[ "$state" != "active" ]]; then + log_fail "feature@longname has state $state (expected active)" +fi + +# Destroying the dataset where the feature is activated should put the feature +# back to 'enabled' state +log_must zfs destroy -r $TESTPOOL/$TESTFS +state=$(zpool get feature@longname -H -o value $TESTPOOL) +log_note "feature@longname on pool: $TESTPOOL : $state" +if [[ "$state" != "enabled" ]]; then + log_fail "feature@longname has state $state (expected active)" +fi + +log_pass diff --git a/tests/zfs-tests/tests/functional/longname/setup.ksh b/tests/zfs-tests/tests/functional/longname/setup.ksh new file mode 100755 index 000000000000..3f6759469b7b --- /dev/null +++ b/tests/zfs-tests/tests/functional/longname/setup.ksh @@ -0,0 +1,35 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2021 by Nutanix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +DISK=${DISKS%% *} +default_setup $DISK diff --git a/tests/zfs-tests/tests/functional/rsend/rsend.kshlib b/tests/zfs-tests/tests/functional/rsend/rsend.kshlib index 26e7c2cc25bc..0b2984c59d4e 100644 --- a/tests/zfs-tests/tests/functional/rsend/rsend.kshlib +++ b/tests/zfs-tests/tests/functional/rsend/rsend.kshlib @@ -720,6 +720,7 @@ function stream_has_features feature[resuming]="100000" feature[redacted]="200000" feature[compressed]="400000" + feature[longname]="10000000" typeset flag known derived=0 for flag in "$@"; do diff --git a/tests/zfs-tests/tests/functional/rsend/send-c_longname.ksh b/tests/zfs-tests/tests/functional/rsend/send-c_longname.ksh new file mode 100755 index 000000000000..3f7edc6810dd --- /dev/null +++ b/tests/zfs-tests/tests/functional/rsend/send-c_longname.ksh @@ -0,0 +1,98 @@ +#!/bin/ksh -p + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2021 by Nutanix. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/include/properties.shlib + +# +# Description: +# Verify that longname featureflag is present in the stream. +# +# Strategy: +# 1. Create a filesystem with longnamed files/directories. +# 2. Verify that the sendstream has the longname featureflag is present in the +# send stream. +# 3. Verify the created streams can be received correctly. +# 4. Verify that the longnamed files/directories are present in the received +# filesystem. +# + +verify_runnable "both" + +log_assert "Verify that longnames are handled correctly in send stream." +log_onexit cleanup_pool $POOL $POOL2 $POOL3 + +typeset sendfs=$POOL/sendfs +typeset recvfs=$POOL2/recvfs +typeset recvfs3=$POOL3/recvfs +typeset stream=$BACKDIR/stream +typeset dump=$BACKDIR/dump + +log_must zfs create -o longname=on $sendfs +typeset dir=$(get_prop mountpoint $sendfs) + +# Create a longnamed dir and a file in the send dataset +LONGNAME=$(printf 'a%.0s' {1..512}) +LONGFNAME="file-$LONGNAME" +LONGDNAME="dir-$LONGNAME" +log_must mkdir $dir/$LONGDNAME +log_must touch $dir/$LONGFNAME + +# When POOL3 is created by rsend.kshlib feature@longname is 'enabled'. +# Recreate the POOL3 with feature@longname disabled. +datasetexists $POOL3 && log_must zpool destroy $POOL3 +log_must zpool create -o feature@longname=disabled $POOL3 $DISK3 + +# Generate the streams and zstreamdump output. +log_must zfs snapshot $sendfs@now +log_must eval "zfs send -p $sendfs@now >$stream" +log_must eval "zstream dump -v <$stream >$dump" +log_must eval "zfs recv $recvfs <$stream" +cmp_ds_cont $sendfs $recvfs +log_must stream_has_features $stream longname + +# Ensure the the receiving pool has feature@longname activated after receiving. +feat_val=$(zpool get -H -o value feature@longname $POOL2) +log_note "Zpool $POOL2 feature@longname=$feat_val" +if [[ "$feat_val" != "active" ]]; then + log_fail "pool $POOL2 feature@longname=$feat_val (expected 'active')" +fi + +# Receiving of the stream on $POOL3 should fail as longname is not enabled +log_mustnot eval "zfs recv $recvfs3 <$stream" + +# Enable feature@longname and retry the receiving the stream. +# It should succeed this time. +log_must eval "zpool set feature@longname=enabled $POOL3" +log_must eval "zfs recv $recvfs3 <$stream" + +log_must zfs get longname $recvfs3 +prop_val=$(zfs get -H -o value longname $recvfs3) +log_note "dataset $recvfs3 has longname=$prop_val" +if [[ "$prop_val" != "on" ]]; then + log_fail "$recvfs3 has longname=$prop_val (expected 'on')" +fi + +# +# TODO: +# - Add a testcase to cover the case where send-stream does not contain +# properties (generated without "-p"). +# In this case the target dataset would have longname files/directories which +# cannot be accessed if the dataset property 'longname=off'. +# + +log_pass "Longnames are handled correctly in send/recv"