diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-03-21 20:22:37 +0100 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-03-21 20:22:37 +0100 |
commit | 77d913178c248d436a15151be5214ef2bf06a465 (patch) | |
tree | 92c16e294858eab0042abc77389e4bd2f4770264 /fs | |
parent | Merge tag 'xfs-for-linus-4.6-rc1' of git://git.kernel.org/pub/scm/linux/kerne... (diff) | |
parent | quota: Fix possible GPF due to uninitialised pointers (diff) | |
download | linux-77d913178c248d436a15151be5214ef2bf06a465.tar.xz linux-77d913178c248d436a15151be5214ef2bf06a465.zip |
Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs
Pull UDF and quota updates from Jan Kara:
"This contains a rewrite of UDF handling of filename encoding to fix
remaining overflow issues from Andrew Gabbasov and quota changes to
support new Q_[X]GETNEXTQUOTA quotactl for VFS quota formats"
* 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs:
quota: Fix possible GPF due to uninitialised pointers
ext4: Make Q_GETNEXTQUOTA work for quota in hidden inodes
quota: Forbid Q_GETQUOTA and Q_GETNEXTQUOTA for frozen filesystem
quota: Fix possible races during quota loading
ocfs2: Implement get_next_id()
quota_v2: Implement get_next_id() for V2 quota format
quota: Add support for ->get_nextdqblk() for VFS quota
udf: Merge linux specific translation into CS0 conversion function
udf: Remove struct ustr as non-needed intermediate storage
udf: Use separate buffer for copying split names
udf: Adjust UDF_NAME_LEN to better reflect actual restrictions
udf: Join functions for UTF8 and NLS conversions
udf: Parameterize output length in udf_put_filename
quota: Allow Q_GETQUOTA for frozen filesystem
quota: Fixup comments about return value of Q_[X]GETNEXTQUOTA
Diffstat (limited to 'fs')
-rw-r--r-- | fs/ext4/super.c | 4 | ||||
-rw-r--r-- | fs/ocfs2/ocfs2_trace.h | 2 | ||||
-rw-r--r-- | fs/ocfs2/quota_global.c | 25 | ||||
-rw-r--r-- | fs/quota/dquot.c | 54 | ||||
-rw-r--r-- | fs/quota/quota.c | 10 | ||||
-rw-r--r-- | fs/quota/quota_tree.c | 67 | ||||
-rw-r--r-- | fs/quota/quota_v2.c | 6 | ||||
-rw-r--r-- | fs/reiserfs/super.c | 1 | ||||
-rw-r--r-- | fs/udf/dir.c | 13 | ||||
-rw-r--r-- | fs/udf/namei.c | 29 | ||||
-rw-r--r-- | fs/udf/super.c | 38 | ||||
-rw-r--r-- | fs/udf/udfdecl.h | 21 | ||||
-rw-r--r-- | fs/udf/unicode.c | 630 |
13 files changed, 463 insertions, 437 deletions
diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 99996e9a8f57..539297515896 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -1129,6 +1129,7 @@ static const struct dquot_operations ext4_quota_operations = { .alloc_dquot = dquot_alloc, .destroy_dquot = dquot_destroy, .get_projid = ext4_get_projid, + .get_next_id = dquot_get_next_id, }; static const struct quotactl_ops ext4_qctl_operations = { @@ -1138,7 +1139,8 @@ static const struct quotactl_ops ext4_qctl_operations = { .get_state = dquot_get_state, .set_info = dquot_set_dqinfo, .get_dqblk = dquot_get_dqblk, - .set_dqblk = dquot_set_dqblk + .set_dqblk = dquot_set_dqblk, + .get_nextdqblk = dquot_get_next_dqblk, }; #endif diff --git a/fs/ocfs2/ocfs2_trace.h b/fs/ocfs2/ocfs2_trace.h index 6cb019b7c6a8..a52a2dbc064e 100644 --- a/fs/ocfs2/ocfs2_trace.h +++ b/fs/ocfs2/ocfs2_trace.h @@ -2035,6 +2035,8 @@ DEFINE_OCFS2_UINT_INT_EVENT(ocfs2_release_dquot); DEFINE_OCFS2_UINT_INT_EVENT(ocfs2_acquire_dquot); +DEFINE_OCFS2_UINT_INT_EVENT(ocfs2_get_next_id); + DEFINE_OCFS2_UINT_INT_EVENT(ocfs2_mark_dquot_dirty); /* End of trace events for fs/ocfs2/quota_global.c. */ diff --git a/fs/ocfs2/quota_global.c b/fs/ocfs2/quota_global.c index 9c9dd30bc945..91bc674203ed 100644 --- a/fs/ocfs2/quota_global.c +++ b/fs/ocfs2/quota_global.c @@ -860,6 +860,30 @@ out: return status; } +static int ocfs2_get_next_id(struct super_block *sb, struct kqid *qid) +{ + int type = qid->type; + struct ocfs2_mem_dqinfo *info = sb_dqinfo(sb, type)->dqi_priv; + int status = 0; + + trace_ocfs2_get_next_id(from_kqid(&init_user_ns, *qid), type); + status = ocfs2_lock_global_qf(info, 0); + if (status < 0) + goto out; + status = ocfs2_qinfo_lock(info, 0); + if (status < 0) + goto out_global; + status = qtree_get_next_id(&info->dqi_gi, qid); + ocfs2_qinfo_unlock(info, 0); +out_global: + ocfs2_unlock_global_qf(info, 0); +out: + /* Avoid logging ENOENT since it just means there isn't next ID */ + if (status && status != -ENOENT) + mlog_errno(status); + return status; +} + static int ocfs2_mark_dquot_dirty(struct dquot *dquot) { unsigned long mask = (1 << (DQ_LASTSET_B + QIF_ILIMITS_B)) | @@ -968,4 +992,5 @@ const struct dquot_operations ocfs2_quota_operations = { .write_info = ocfs2_write_info, .alloc_dquot = ocfs2_alloc_dquot, .destroy_dquot = ocfs2_destroy_dquot, + .get_next_id = ocfs2_get_next_id, }; diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 04ca0cc6d065..ba827daea5a0 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -411,6 +411,8 @@ int dquot_acquire(struct dquot *dquot) ret = dqopt->ops[dquot->dq_id.type]->read_dqblk(dquot); if (ret < 0) goto out_iolock; + /* Make sure flags update is visible after dquot has been filled */ + smp_mb__before_atomic(); set_bit(DQ_READ_B, &dquot->dq_flags); /* Instantiate dquot if needed */ if (!test_bit(DQ_ACTIVE_B, &dquot->dq_flags) && !dquot->dq_off) { @@ -427,6 +429,11 @@ int dquot_acquire(struct dquot *dquot) goto out_iolock; } } + /* + * Make sure flags update is visible after on-disk struct has been + * allocated. Paired with smp_rmb() in dqget(). + */ + smp_mb__before_atomic(); set_bit(DQ_ACTIVE_B, &dquot->dq_flags); out_iolock: mutex_unlock(&dqopt->dqio_mutex); @@ -887,6 +894,11 @@ we_slept: goto out; } } + /* + * Make sure following reads see filled structure - paired with + * smp_mb__before_atomic() in dquot_acquire(). + */ + smp_rmb(); #ifdef CONFIG_QUOTA_DEBUG BUG_ON(!dquot->dq_sb); /* Has somebody invalidated entry under us? */ #endif @@ -1398,7 +1410,7 @@ static int dquot_active(const struct inode *inode) static int __dquot_initialize(struct inode *inode, int type) { int cnt, init_needed = 0; - struct dquot **dquots, *got[MAXQUOTAS]; + struct dquot **dquots, *got[MAXQUOTAS] = {}; struct super_block *sb = inode->i_sb; qsize_t rsv; int ret = 0; @@ -1415,7 +1427,6 @@ static int __dquot_initialize(struct inode *inode, int type) int rc; struct dquot *dquot; - got[cnt] = NULL; if (type != -1 && cnt != type) continue; /* @@ -2031,6 +2042,21 @@ int dquot_commit_info(struct super_block *sb, int type) } EXPORT_SYMBOL(dquot_commit_info); +int dquot_get_next_id(struct super_block *sb, struct kqid *qid) +{ + struct quota_info *dqopt = sb_dqopt(sb); + int err; + + if (!dqopt->ops[qid->type]->get_next_id) + return -ENOSYS; + mutex_lock(&dqopt->dqio_mutex); + err = dqopt->ops[qid->type]->get_next_id(sb, qid); + mutex_unlock(&dqopt->dqio_mutex); + + return err; +} +EXPORT_SYMBOL(dquot_get_next_id); + /* * Definitions of diskquota operations. */ @@ -2042,6 +2068,7 @@ const struct dquot_operations dquot_operations = { .write_info = dquot_commit_info, .alloc_dquot = dquot_alloc, .destroy_dquot = dquot_destroy, + .get_next_id = dquot_get_next_id, }; EXPORT_SYMBOL(dquot_operations); @@ -2563,6 +2590,27 @@ int dquot_get_dqblk(struct super_block *sb, struct kqid qid, } EXPORT_SYMBOL(dquot_get_dqblk); +int dquot_get_next_dqblk(struct super_block *sb, struct kqid *qid, + struct qc_dqblk *di) +{ + struct dquot *dquot; + int err; + + if (!sb->dq_op->get_next_id) + return -ENOSYS; + err = sb->dq_op->get_next_id(sb, qid); + if (err < 0) + return err; + dquot = dqget(sb, *qid); + if (IS_ERR(dquot)) + return PTR_ERR(dquot); + do_get_dqblk(dquot, di); + dqput(dquot); + + return 0; +} +EXPORT_SYMBOL(dquot_get_next_dqblk); + #define VFS_QC_MASK \ (QC_SPACE | QC_SPC_SOFT | QC_SPC_HARD | \ QC_INO_COUNT | QC_INO_SOFT | QC_INO_HARD | \ @@ -2763,6 +2811,7 @@ const struct quotactl_ops dquot_quotactl_ops = { .get_state = dquot_get_state, .set_info = dquot_set_dqinfo, .get_dqblk = dquot_get_dqblk, + .get_nextdqblk = dquot_get_next_dqblk, .set_dqblk = dquot_set_dqblk }; EXPORT_SYMBOL(dquot_quotactl_ops); @@ -2774,6 +2823,7 @@ const struct quotactl_ops dquot_quotactl_sysfile_ops = { .get_state = dquot_get_state, .set_info = dquot_set_dqinfo, .get_dqblk = dquot_get_dqblk, + .get_nextdqblk = dquot_get_next_dqblk, .set_dqblk = dquot_set_dqblk }; EXPORT_SYMBOL(dquot_quotactl_sysfile_ops); diff --git a/fs/quota/quota.c b/fs/quota/quota.c index 0ebc90496525..0f10ee9892ce 100644 --- a/fs/quota/quota.c +++ b/fs/quota/quota.c @@ -224,7 +224,7 @@ static int quota_getquota(struct super_block *sb, int type, qid_t id, /* * Return quota for next active quota >= this id, if any exists, - * otherwise return -ESRCH via ->get_nextdqblk + * otherwise return -ENOENT via ->get_nextdqblk */ static int quota_getnextquota(struct super_block *sb, int type, qid_t id, void __user *addr) @@ -655,7 +655,7 @@ static int quota_getxquota(struct super_block *sb, int type, qid_t id, /* * Return quota for next active quota >= this id, if any exists, - * otherwise return -ESRCH via ->get_nextdqblk. + * otherwise return -ENOENT via ->get_nextdqblk. */ static int quota_getnextxquota(struct super_block *sb, int type, qid_t id, void __user *addr) @@ -765,10 +765,14 @@ static int do_quotactl(struct super_block *sb, int type, int cmd, qid_t id, /* Return 1 if 'cmd' will block on frozen filesystem */ static int quotactl_cmd_write(int cmd) { + /* + * We cannot allow Q_GETQUOTA and Q_GETNEXTQUOTA without write access + * as dquot_acquire() may allocate space for new structure and OCFS2 + * needs to increment on-disk use count. + */ switch (cmd) { case Q_GETFMT: case Q_GETINFO: - case Q_GETNEXTQUOTA: case Q_SYNC: case Q_XGETQSTAT: case Q_XGETQSTATV: diff --git a/fs/quota/quota_tree.c b/fs/quota/quota_tree.c index 58efb83dec1c..0738972e8d3f 100644 --- a/fs/quota/quota_tree.c +++ b/fs/quota/quota_tree.c @@ -22,10 +22,9 @@ MODULE_LICENSE("GPL"); #define __QUOTA_QT_PARANOIA -static int get_index(struct qtree_mem_dqinfo *info, struct kqid qid, int depth) +static int __get_index(struct qtree_mem_dqinfo *info, qid_t id, int depth) { unsigned int epb = info->dqi_usable_bs >> 2; - qid_t id = from_kqid(&init_user_ns, qid); depth = info->dqi_qtree_depth - depth - 1; while (depth--) @@ -33,6 +32,13 @@ static int get_index(struct qtree_mem_dqinfo *info, struct kqid qid, int depth) return id % epb; } +static int get_index(struct qtree_mem_dqinfo *info, struct kqid qid, int depth) +{ + qid_t id = from_kqid(&init_user_ns, qid); + + return __get_index(info, id, depth); +} + /* Number of entries in one blocks */ static int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info) { @@ -668,3 +674,60 @@ int qtree_release_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot) return 0; } EXPORT_SYMBOL(qtree_release_dquot); + +static int find_next_id(struct qtree_mem_dqinfo *info, qid_t *id, + unsigned int blk, int depth) +{ + char *buf = getdqbuf(info->dqi_usable_bs); + __le32 *ref = (__le32 *)buf; + ssize_t ret; + unsigned int epb = info->dqi_usable_bs >> 2; + unsigned int level_inc = 1; + int i; + + if (!buf) + return -ENOMEM; + + for (i = depth; i < info->dqi_qtree_depth - 1; i++) + level_inc *= epb; + + ret = read_blk(info, blk, buf); + if (ret < 0) { + quota_error(info->dqi_sb, + "Can't read quota tree block %u", blk); + goto out_buf; + } + for (i = __get_index(info, *id, depth); i < epb; i++) { + if (ref[i] == cpu_to_le32(0)) { + *id += level_inc; + continue; + } + if (depth == info->dqi_qtree_depth - 1) { + ret = 0; + goto out_buf; + } + ret = find_next_id(info, id, le32_to_cpu(ref[i]), depth + 1); + if (ret != -ENOENT) + break; + } + if (i == epb) { + ret = -ENOENT; + goto out_buf; + } +out_buf: + kfree(buf); + return ret; +} + +int qtree_get_next_id(struct qtree_mem_dqinfo *info, struct kqid *qid) +{ + qid_t id = from_kqid(&init_user_ns, *qid); + int ret; + + ret = find_next_id(info, &id, QT_TREEOFF, 0); + if (ret < 0) + return ret; + *qid = make_kqid(&init_user_ns, qid->type, id); + return 0; +} +EXPORT_SYMBOL(qtree_get_next_id); diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index ed85d4f35c04..ca71bf881ad1 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -304,6 +304,11 @@ static int v2_free_file_info(struct super_block *sb, int type) return 0; } +static int v2_get_next_id(struct super_block *sb, struct kqid *qid) +{ + return qtree_get_next_id(sb_dqinfo(sb, qid->type)->dqi_priv, qid); +} + static const struct quota_format_ops v2_format_ops = { .check_quota_file = v2_check_quota_file, .read_file_info = v2_read_file_info, @@ -312,6 +317,7 @@ static const struct quota_format_ops v2_format_ops = { .read_dqblk = v2_read_dquot, .commit_dqblk = v2_write_dquot, .release_dqblk = v2_release_dquot, + .get_next_id = v2_get_next_id, }; static struct quota_format_type v2r0_quota_format = { diff --git a/fs/reiserfs/super.c b/fs/reiserfs/super.c index c0306ec8ed7b..b8f2d1e8c645 100644 --- a/fs/reiserfs/super.c +++ b/fs/reiserfs/super.c @@ -802,6 +802,7 @@ static const struct dquot_operations reiserfs_quota_operations = { .write_info = reiserfs_write_info, .alloc_dquot = dquot_alloc, .destroy_dquot = dquot_destroy, + .get_next_id = dquot_get_next_id, }; static const struct quotactl_ops reiserfs_qctl_operations = { diff --git a/fs/udf/dir.c b/fs/udf/dir.c index 541d9c65014d..b51b371b874a 100644 --- a/fs/udf/dir.c +++ b/fs/udf/dir.c @@ -45,7 +45,7 @@ static int udf_readdir(struct file *file, struct dir_context *ctx) int block, iblock; loff_t nf_pos; int flen; - unsigned char *fname = NULL; + unsigned char *fname = NULL, *copy_name = NULL; unsigned char *nameptr; uint16_t liu; uint8_t lfi; @@ -143,7 +143,15 @@ static int udf_readdir(struct file *file, struct dir_context *ctx) if (poffset >= lfi) { nameptr = (char *)(fibh.ebh->b_data + poffset - lfi); } else { - nameptr = fname; + if (!copy_name) { + copy_name = kmalloc(UDF_NAME_LEN, + GFP_NOFS); + if (!copy_name) { + ret = -ENOMEM; + goto out; + } + } + nameptr = copy_name; memcpy(nameptr, fi->fileIdent + liu, lfi - poffset); memcpy(nameptr + lfi - poffset, @@ -185,6 +193,7 @@ out: brelse(fibh.sbh); brelse(epos.bh); kfree(fname); + kfree(copy_name); return ret; } diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 42eafb91f7ff..a2ba11eca995 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -165,7 +165,7 @@ static struct fileIdentDesc *udf_find_entry(struct inode *dir, struct fileIdentDesc *fi = NULL; loff_t f_pos; int block, flen; - unsigned char *fname = NULL; + unsigned char *fname = NULL, *copy_name = NULL; unsigned char *nameptr; uint8_t lfi; uint16_t liu; @@ -236,7 +236,15 @@ static struct fileIdentDesc *udf_find_entry(struct inode *dir, nameptr = (uint8_t *)(fibh->ebh->b_data + poffset - lfi); else { - nameptr = fname; + if (!copy_name) { + copy_name = kmalloc(UDF_NAME_LEN, + GFP_NOFS); + if (!copy_name) { + fi = ERR_PTR(-ENOMEM); + goto out_err; + } + } + nameptr = copy_name; memcpy(nameptr, fi->fileIdent + liu, lfi - poffset); memcpy(nameptr + lfi - poffset, @@ -279,6 +287,7 @@ out_err: out_ok: brelse(epos.bh); kfree(fname); + kfree(copy_name); return fi; } @@ -291,7 +300,7 @@ static struct dentry *udf_lookup(struct inode *dir, struct dentry *dentry, struct udf_fileident_bh fibh; struct fileIdentDesc *fi; - if (dentry->d_name.len > UDF_NAME_LEN - 2) + if (dentry->d_name.len > UDF_NAME_LEN) return ERR_PTR(-ENAMETOOLONG); #ifdef UDF_RECOVERY @@ -351,7 +360,7 @@ static struct fileIdentDesc *udf_add_entry(struct inode *dir, struct udf_inode_info *dinfo; fibh->sbh = fibh->ebh = NULL; - name = kmalloc(UDF_NAME_LEN, GFP_NOFS); + name = kmalloc(UDF_NAME_LEN_CS0, GFP_NOFS); if (!name) { *err = -ENOMEM; goto out_err; @@ -362,8 +371,9 @@ static struct fileIdentDesc *udf_add_entry(struct inode *dir, *err = -EINVAL; goto out_err; } - namelen = udf_put_filename(sb, dentry->d_name.name, name, - dentry->d_name.len); + namelen = udf_put_filename(sb, dentry->d_name.name, + dentry->d_name.len, + name, UDF_NAME_LEN_CS0); if (!namelen) { *err = -ENAMETOOLONG; goto out_err; @@ -914,7 +924,7 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry, iinfo = UDF_I(inode); down_write(&iinfo->i_data_sem); - name = kmalloc(UDF_NAME_LEN, GFP_NOFS); + name = kmalloc(UDF_NAME_LEN_CS0, GFP_NOFS); if (!name) { err = -ENOMEM; goto out_no_entry; @@ -997,8 +1007,9 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry, } if (pc->componentType == 5) { - namelen = udf_put_filename(sb, compstart, name, - symname - compstart); + namelen = udf_put_filename(sb, compstart, + symname - compstart, + name, UDF_NAME_LEN_CS0); if (!namelen) goto out_no_entry; diff --git a/fs/udf/super.c b/fs/udf/super.c index a522c15a0bfd..fa92fe839fda 100644 --- a/fs/udf/super.c +++ b/fs/udf/super.c @@ -887,18 +887,14 @@ static int udf_find_fileset(struct super_block *sb, static int udf_load_pvoldesc(struct super_block *sb, sector_t block) { struct primaryVolDesc *pvoldesc; - struct ustr *instr, *outstr; + uint8_t *outstr; struct buffer_head *bh; uint16_t ident; int ret = -ENOMEM; - instr = kmalloc(sizeof(struct ustr), GFP_NOFS); - if (!instr) - return -ENOMEM; - - outstr = kmalloc(sizeof(struct ustr), GFP_NOFS); + outstr = kmalloc(128, GFP_NOFS); if (!outstr) - goto out1; + return -ENOMEM; bh = udf_read_tagged(sb, block, block, &ident); if (!bh) { @@ -923,31 +919,25 @@ static int udf_load_pvoldesc(struct super_block *sb, sector_t block) #endif } - if (!udf_build_ustr(instr, pvoldesc->volIdent, 32)) { - ret = udf_CS0toUTF8(outstr, instr); - if (ret < 0) - goto out_bh; + ret = udf_CS0toUTF8(outstr, 31, pvoldesc->volIdent, 32); + if (ret < 0) + goto out_bh; - strncpy(UDF_SB(sb)->s_volume_ident, outstr->u_name, - outstr->u_len > 31 ? 31 : outstr->u_len); - udf_debug("volIdent[] = '%s'\n", UDF_SB(sb)->s_volume_ident); - } + strncpy(UDF_SB(sb)->s_volume_ident, outstr, ret); + udf_debug("volIdent[] = '%s'\n", UDF_SB(sb)->s_volume_ident); - if (!udf_build_ustr(instr, pvoldesc->volSetIdent, 128)) { - ret = udf_CS0toUTF8(outstr, instr); - if (ret < 0) - goto out_bh; + ret = udf_CS0toUTF8(outstr, 127, pvoldesc->volSetIdent, 128); + if (ret < 0) + goto out_bh; - udf_debug("volSetIdent[] = '%s'\n", outstr->u_name); - } + outstr[ret] = 0; + udf_debug("volSetIdent[] = '%s'\n", outstr); ret = 0; out_bh: brelse(bh); out2: kfree(outstr); -out1: - kfree(instr); return ret; } @@ -2358,7 +2348,7 @@ static int udf_statfs(struct dentry *dentry, struct kstatfs *buf) le32_to_cpu(lvidiu->numDirs)) : 0) + buf->f_bfree; buf->f_ffree = buf->f_bfree; - buf->f_namelen = UDF_NAME_LEN - 2; + buf->f_namelen = UDF_NAME_LEN; buf->f_fsid.val[0] = (u32)id; buf->f_fsid.val[1] = (u32)(id >> 32); diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index fa0044b6b81d..972b70625614 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -49,8 +49,8 @@ extern __printf(3, 4) void _udf_warn(struct super_block *sb, #define UDF_EXTENT_FLAG_MASK 0xC0000000 #define UDF_NAME_PAD 4 -#define UDF_NAME_LEN 256 -#define UDF_PATH_LEN 1023 +#define UDF_NAME_LEN 254 +#define UDF_NAME_LEN_CS0 255 static inline size_t udf_file_entry_alloc_offset(struct inode *inode) { @@ -106,12 +106,6 @@ struct generic_desc { __le32 volDescSeqNum; }; -struct ustr { - uint8_t u_cmpID; - uint8_t u_name[UDF_NAME_LEN - 2]; - uint8_t u_len; -}; - /* super.c */ @@ -214,12 +208,11 @@ udf_get_lb_pblock(struct super_block *sb, struct kernel_lb_addr *loc, } /* unicode.c */ -extern int udf_get_filename(struct super_block *, uint8_t *, int, uint8_t *, - int); -extern int udf_put_filename(struct super_block *, const uint8_t *, uint8_t *, - int); -extern int udf_build_ustr(struct ustr *, dstring *, int); -extern int udf_CS0toUTF8(struct ustr *, const struct ustr *); +extern int udf_get_filename(struct super_block *, const uint8_t *, int, + uint8_t *, int); +extern int udf_put_filename(struct super_block *, const uint8_t *, int, + uint8_t *, int); +extern int udf_CS0toUTF8(uint8_t *, int, const uint8_t *, int); /* ialloc.c */ extern void udf_free_inode(struct inode *); diff --git a/fs/udf/unicode.c b/fs/udf/unicode.c index e788a05aab83..3ff42f4437f3 100644 --- a/fs/udf/unicode.c +++ b/fs/udf/unicode.c @@ -28,199 +28,72 @@ #include "udf_sb.h" -static int udf_translate_to_linux(uint8_t *, int, uint8_t *, int, uint8_t *, - int); - -static int udf_char_to_ustr(struct ustr *dest, const uint8_t *src, int strlen) -{ - if ((!dest) || (!src) || (!strlen) || (strlen > UDF_NAME_LEN - 2)) - return 0; - - memset(dest, 0, sizeof(struct ustr)); - memcpy(dest->u_name, src, strlen); - dest->u_cmpID = 0x08; - dest->u_len = strlen; - - return strlen; -} - -/* - * udf_build_ustr - */ -int udf_build_ustr(struct ustr *dest, dstring *ptr, int size) -{ - int usesize; - - if (!dest || !ptr || !size) - return -1; - BUG_ON(size < 2); - - usesize = min_t(size_t, ptr[size - 1], sizeof(dest->u_name)); - usesize = min(usesize, size - 2); - dest->u_cmpID = ptr[0]; - dest->u_len = usesize; - memcpy(dest->u_name, ptr + 1, usesize); - memset(dest->u_name + usesize, 0, sizeof(dest->u_name) - usesize); - - return 0; -} - -/* - * udf_build_ustr_exact - */ -static void udf_build_ustr_exact(struct ustr *dest, dstring *ptr, int exactsize) -{ - memset(dest, 0, sizeof(struct ustr)); - dest->u_cmpID = ptr[0]; - dest->u_len = exactsize - 1; - memcpy(dest->u_name, ptr + 1, exactsize - 1); -} - -/* - * udf_CS0toUTF8 - * - * PURPOSE - * Convert OSTA Compressed Unicode to the UTF-8 equivalent. - * - * PRE-CONDITIONS - * utf Pointer to UTF-8 output buffer. - * ocu Pointer to OSTA Compressed Unicode input buffer - * of size UDF_NAME_LEN bytes. - * both of type "struct ustr *" - * - * POST-CONDITIONS - * <return> >= 0 on success. - * - * HISTORY - * November 12, 1997 - Andrew E. Mileski - * Written, tested, and released. - */ -int udf_CS0toUTF8(struct ustr *utf_o, const struct ustr *ocu_i) +static int udf_uni2char_utf8(wchar_t uni, + unsigned char *out, + int boundlen) { - const uint8_t *ocu; - uint8_t cmp_id, ocu_len; - int i; - - ocu_len = ocu_i->u_len; - if (ocu_len == 0) { - memset(utf_o, 0, sizeof(struct ustr)); - return 0; - } - - cmp_id = ocu_i->u_cmpID; - if (cmp_id != 8 && cmp_id != 16) { - memset(utf_o, 0, sizeof(struct ustr)); - pr_err("unknown compression code (%d) stri=%s\n", - cmp_id, ocu_i->u_name); - return -EINVAL; - } - - ocu = ocu_i->u_name; - utf_o->u_len = 0; - for (i = 0; (i < ocu_len) && (utf_o->u_len <= (UDF_NAME_LEN - 3));) { - - /* Expand OSTA compressed Unicode to Unicode */ - uint32_t c = ocu[i++]; - if (cmp_id == 16) - c = (c << 8) | ocu[i++]; - - /* Compress Unicode to UTF-8 */ - if (c < 0x80U) - utf_o->u_name[utf_o->u_len++] = (uint8_t)c; - else if (c < 0x800U) { - if (utf_o->u_len > (UDF_NAME_LEN - 4)) - break; - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0xc0 | (c >> 6)); - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0x80 | (c & 0x3f)); - } else { - if (utf_o->u_len > (UDF_NAME_LEN - 5)) - break; - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0xe0 | (c >> 12)); - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0x80 | - ((c >> 6) & 0x3f)); - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0x80 | (c & 0x3f)); - } + int u_len = 0; + + if (boundlen <= 0) + return -ENAMETOOLONG; + + if (uni < 0x80) { + out[u_len++] = (unsigned char)uni; + } else if (uni < 0x800) { + if (boundlen < 2) + return -ENAMETOOLONG; + out[u_len++] = (unsigned char)(0xc0 | (uni >> 6)); + out[u_len++] = (unsigned char)(0x80 | (uni & 0x3f)); + } else { + if (boundlen < 3) + return -ENAMETOOLONG; + out[u_len++] = (unsigned char)(0xe0 | (uni >> 12)); + out[u_len++] = (unsigned char)(0x80 | ((uni >> 6) & 0x3f)); + out[u_len++] = (unsigned char)(0x80 | (uni & 0x3f)); } - utf_o->u_cmpID = 8; - - return utf_o->u_len; + return u_len; } -/* - * - * udf_UTF8toCS0 - * - * PURPOSE - * Convert UTF-8 to the OSTA Compressed Unicode equivalent. - * - * DESCRIPTION - * This routine is only called by udf_lookup(). - * - * PRE-CONDITIONS - * ocu Pointer to OSTA Compressed Unicode output - * buffer of size UDF_NAME_LEN bytes. - * utf Pointer to UTF-8 input buffer. - * utf_len Length of UTF-8 input buffer in bytes. - * - * POST-CONDITIONS - * <return> Zero on success. - * - * HISTORY - * November 12, 1997 - Andrew E. Mileski - * Written, tested, and released. - */ -static int udf_UTF8toCS0(dstring *ocu, struct ustr *utf, int length) +static int udf_char2uni_utf8(const unsigned char *in, + int boundlen, + wchar_t *uni) { - unsigned c, i, max_val, utf_char; - int utf_cnt, u_len, u_ch; - - memset(ocu, 0, sizeof(dstring) * length); - ocu[0] = 8; - max_val = 0xffU; - u_ch = 1; + unsigned int utf_char; + unsigned char c; + int utf_cnt, u_len; -try_again: - u_len = 0U; - utf_char = 0U; - utf_cnt = 0U; - for (i = 0U; i < utf->u_len; i++) { - /* Name didn't fit? */ - if (u_len + 1 + u_ch >= length) - return 0; - - c = (uint8_t)utf->u_name[i]; + utf_char = 0; + utf_cnt = 0; + for (u_len = 0; u_len < boundlen;) { + c = in[u_len++]; /* Complete a multi-byte UTF-8 character */ if (utf_cnt) { - utf_char = (utf_char << 6) | (c & 0x3fU); + utf_char = (utf_char << 6) | (c & 0x3f); if (--utf_cnt) continue; } else { /* Check for a multi-byte UTF-8 character */ - if (c & 0x80U) { + if (c & 0x80) { /* Start a multi-byte UTF-8 character */ - if ((c & 0xe0U) == 0xc0U) { - utf_char = c & 0x1fU; + if ((c & 0xe0) == 0xc0) { + utf_char = c & 0x1f; utf_cnt = 1; - } else if ((c & 0xf0U) == 0xe0U) { - utf_char = c & 0x0fU; + } else if ((c & 0xf0) == 0xe0) { + utf_char = c & 0x0f; utf_cnt = 2; - } else if ((c & 0xf8U) == 0xf0U) { - utf_char = c & 0x07U; + } else if ((c & 0xf8) == 0xf0) { + utf_char = c & 0x07; utf_cnt = 3; - } else if ((c & 0xfcU) == 0xf8U) { - utf_char = c & 0x03U; + } else if ((c & 0xfc) == 0xf8) { + utf_char = c & 0x03; utf_cnt = 4; - } else if ((c & 0xfeU) == 0xfcU) { - utf_char = c & 0x01U; + } else if ((c & 0xfe) == 0xfc) { + utf_char = c & 0x01; utf_cnt = 5; } else { - goto error_out; + utf_cnt = -1; + break; } continue; } else { @@ -228,97 +101,216 @@ try_again: utf_char = c; } } - - /* Choose no compression if necessary */ - if (utf_char > max_val) { - if (max_val == 0xffU) { - max_val = 0xffffU; - ocu[0] = (uint8_t)0x10U; - u_ch = 2; - goto try_again; - } - goto error_out; - } - - if (max_val == 0xffffU) - ocu[++u_len] = (uint8_t)(utf_char >> 8); - ocu[++u_len] = (uint8_t)(utf_char & 0xffU); + *uni = utf_char; + break; } - if (utf_cnt) { -error_out: - ocu[++u_len] = '?'; - printk(KERN_DEBUG pr_fmt("bad UTF-8 character\n")); + *uni = '?'; + return -EINVAL; } + return u_len; +} - ocu[length - 1] = (uint8_t)u_len + 1; +#define ILLEGAL_CHAR_MARK '_' +#define EXT_MARK '.' +#define CRC_MARK '#' +#define EXT_SIZE 5 +/* Number of chars we need to store generated CRC to make filename unique */ +#define CRC_LEN 5 + +static int udf_name_conv_char(uint8_t *str_o, int str_o_max_len, + int *str_o_idx, + const uint8_t *str_i, int str_i_max_len, + int *str_i_idx, + int u_ch, int *needsCRC, + int (*conv_f)(wchar_t, unsigned char *, int), + int translate) +{ + uint32_t c; + int illChar = 0; + int len, gotch = 0; + + for (; (!gotch) && (*str_i_idx < str_i_max_len); *str_i_idx += u_ch) { + if (*str_o_idx >= str_o_max_len) { + *needsCRC = 1; + return gotch; + } - return u_len + 1; + /* Expand OSTA compressed Unicode to Unicode */ + c = str_i[*str_i_idx]; + if (u_ch > 1) + c = (c << 8) | str_i[*str_i_idx + 1]; + + if (translate && (c == '/' || c == 0)) + illChar = 1; + else if (illChar) + break; + else + gotch = 1; + } + if (illChar) { + *needsCRC = 1; + c = ILLEGAL_CHAR_MARK; + gotch = 1; + } + if (gotch) { + len = conv_f(c, &str_o[*str_o_idx], str_o_max_len - *str_o_idx); + /* Valid character? */ + if (len >= 0) + *str_o_idx += len; + else if (len == -ENAMETOOLONG) { + *needsCRC = 1; + gotch = 0; + } else { + str_o[(*str_o_idx)++] = '?'; + *needsCRC = 1; + } + } + return gotch; } -static int udf_CS0toNLS(struct nls_table *nls, struct ustr *utf_o, - const struct ustr *ocu_i) +static int udf_name_from_CS0(uint8_t *str_o, int str_max_len, + const uint8_t *ocu, int ocu_len, + int (*conv_f)(wchar_t, unsigned char *, int), + int translate) { - const uint8_t *ocu; - uint8_t cmp_id, ocu_len; - int i, len; + uint32_t c; + uint8_t cmp_id; + int idx, len; + int u_ch; + int needsCRC = 0; + int ext_i_len, ext_max_len; + int str_o_len = 0; /* Length of resulting output */ + int ext_o_len = 0; /* Extension output length */ + int ext_crc_len = 0; /* Extension output length if used with CRC */ + int i_ext = -1; /* Extension position in input buffer */ + int o_crc = 0; /* Rightmost possible output pos for CRC+ext */ + unsigned short valueCRC; + uint8_t ext[EXT_SIZE * NLS_MAX_CHARSET_SIZE + 1]; + uint8_t crc[CRC_LEN]; + if (str_max_len <= 0) + return 0; - ocu_len = ocu_i->u_len; if (ocu_len == 0) { - memset(utf_o, 0, sizeof(struct ustr)); + memset(str_o, 0, str_max_len); return 0; } - cmp_id = ocu_i->u_cmpID; + cmp_id = ocu[0]; if (cmp_id != 8 && cmp_id != 16) { - memset(utf_o, 0, sizeof(struct ustr)); - pr_err("unknown compression code (%d) stri=%s\n", - cmp_id, ocu_i->u_name); + memset(str_o, 0, str_max_len); + pr_err("unknown compression code (%d)\n", cmp_id); return -EINVAL; } + u_ch = cmp_id >> 3; - ocu = ocu_i->u_name; - utf_o->u_len = 0; - for (i = 0; (i < ocu_len) && (utf_o->u_len <= (UDF_NAME_LEN - 3));) { - /* Expand OSTA compressed Unicode to Unicode */ - uint32_t c = ocu[i++]; - if (cmp_id == 16) - c = (c << 8) | ocu[i++]; + ocu++; + ocu_len--; - len = nls->uni2char(c, &utf_o->u_name[utf_o->u_len], - UDF_NAME_LEN - 2 - utf_o->u_len); - /* Valid character? */ - if (len >= 0) - utf_o->u_len += len; - else - utf_o->u_name[utf_o->u_len++] = '?'; + if (ocu_len % u_ch) { + pr_err("incorrect filename length (%d)\n", ocu_len + 1); + return -EINVAL; + } + + if (translate) { + /* Look for extension */ + for (idx = ocu_len - u_ch, ext_i_len = 0; + (idx >= 0) && (ext_i_len < EXT_SIZE); + idx -= u_ch, ext_i_len++) { + c = ocu[idx]; + if (u_ch > 1) + c = (c << 8) | ocu[idx + 1]; + + if (c == EXT_MARK) { + if (ext_i_len) + i_ext = idx; + break; + } + } + if (i_ext >= 0) { + /* Convert extension */ + ext_max_len = min_t(int, sizeof(ext), str_max_len); + ext[ext_o_len++] = EXT_MARK; + idx = i_ext + u_ch; + while (udf_name_conv_char(ext, ext_max_len, &ext_o_len, + ocu, ocu_len, &idx, + u_ch, &needsCRC, + conv_f, translate)) { + if ((ext_o_len + CRC_LEN) < str_max_len) + ext_crc_len = ext_o_len; + } + } } - utf_o->u_cmpID = 8; - return utf_o->u_len; + idx = 0; + while (1) { + if (translate && (idx == i_ext)) { + if (str_o_len > (str_max_len - ext_o_len)) + needsCRC = 1; + break; + } + + if (!udf_name_conv_char(str_o, str_max_len, &str_o_len, + ocu, ocu_len, &idx, + u_ch, &needsCRC, conv_f, translate)) + break; + + if (translate && + (str_o_len <= (str_max_len - ext_o_len - CRC_LEN))) + o_crc = str_o_len; + } + + if (translate) { + if (str_o_len <= 2 && str_o[0] == '.' && + (str_o_len == 1 || str_o[1] == '.')) + needsCRC = 1; + if (needsCRC) { + str_o_len = o_crc; + valueCRC = crc_itu_t(0, ocu, ocu_len); + crc[0] = CRC_MARK; + crc[1] = hex_asc_upper_hi(valueCRC >> 8); + crc[2] = hex_asc_upper_lo(valueCRC >> 8); + crc[3] = hex_asc_upper_hi(valueCRC); + crc[4] = hex_asc_upper_lo(valueCRC); + len = min_t(int, CRC_LEN, str_max_len - str_o_len); + memcpy(&str_o[str_o_len], crc, len); + str_o_len += len; + ext_o_len = ext_crc_len; + } + if (ext_o_len > 0) { + memcpy(&str_o[str_o_len], ext, ext_o_len); + str_o_len += ext_o_len; + } + } + + return str_o_len; } -static int udf_NLStoCS0(struct nls_table *nls, dstring *ocu, struct ustr *uni, - int length) +static int udf_name_to_CS0(uint8_t *ocu, int ocu_max_len, + const uint8_t *str_i, int str_len, + int (*conv_f)(const unsigned char *, int, wchar_t *)) { - int len; - unsigned i, max_val; - uint16_t uni_char; + int i, len; + unsigned int max_val; + wchar_t uni_char; int u_len, u_ch; - memset(ocu, 0, sizeof(dstring) * length); + if (ocu_max_len <= 0) + return 0; + + memset(ocu, 0, ocu_max_len); ocu[0] = 8; - max_val = 0xffU; + max_val = 0xff; u_ch = 1; try_again: - u_len = 0U; - for (i = 0U; i < uni->u_len; i++) { + u_len = 1; + for (i = 0; i < str_len; i++) { /* Name didn't fit? */ - if (u_len + 1 + u_ch >= length) + if (u_len + u_ch > ocu_max_len) return 0; - len = nls->char2uni(&uni->u_name[i], uni->u_len - i, &uni_char); + len = conv_f(&str_i[i], str_len - i, &uni_char); if (!len) continue; /* Invalid character, deal with it */ @@ -328,187 +320,65 @@ try_again: } if (uni_char > max_val) { - max_val = 0xffffU; - ocu[0] = (uint8_t)0x10U; + max_val = 0xffff; + ocu[0] = 0x10; u_ch = 2; goto try_again; } - if (max_val == 0xffffU) - ocu[++u_len] = (uint8_t)(uni_char >> 8); - ocu[++u_len] = (uint8_t)(uni_char & 0xffU); + if (max_val == 0xffff) + ocu[u_len++] = (uint8_t)(uni_char >> 8); + ocu[u_len++] = (uint8_t)(uni_char & 0xff); i += len - 1; } - ocu[length - 1] = (uint8_t)u_len + 1; - return u_len + 1; + return u_len; } -int udf_get_filename(struct super_block *sb, uint8_t *sname, int slen, +int udf_CS0toUTF8(uint8_t *utf_o, int o_len, const uint8_t *ocu_i, int i_len) +{ + return udf_name_from_CS0(utf_o, o_len, ocu_i, i_len, + udf_uni2char_utf8, 0); +} + +int udf_get_filename(struct super_block *sb, const uint8_t *sname, int slen, uint8_t *dname, int dlen) { - struct ustr *filename, *unifilename; + int (*conv_f)(wchar_t, unsigned char *, int); int ret; if (!slen) return -EIO; - filename = kmalloc(sizeof(struct ustr), GFP_NOFS); - if (!filename) - return -ENOMEM; - - unifilename = kmalloc(sizeof(struct ustr), GFP_NOFS); - if (!unifilename) { - ret = -ENOMEM; - goto out1; - } + if (dlen <= 0) + return 0; - udf_build_ustr_exact(unifilename, sname, slen); if (UDF_QUERY_FLAG(sb, UDF_FLAG_UTF8)) { - ret = udf_CS0toUTF8(filename, unifilename); - if (ret < 0) { - udf_debug("Failed in udf_get_filename: sname = %s\n", - sname); - goto out2; - } + conv_f = udf_uni2char_utf8; } else if (UDF_QUERY_FLAG(sb, UDF_FLAG_NLS_MAP)) { - ret = udf_CS0toNLS(UDF_SB(sb)->s_nls_map, filename, - unifilename); - if (ret < 0) { - udf_debug("Failed in udf_get_filename: sname = %s\n", - sname); - goto out2; - } + conv_f = UDF_SB(sb)->s_nls_map->uni2char; } else BUG(); - ret = udf_translate_to_linux(dname, dlen, - filename->u_name, filename->u_len, - unifilename->u_name, unifilename->u_len); + ret = udf_name_from_CS0(dname, dlen, sname, slen, conv_f, 1); /* Zero length filename isn't valid... */ if (ret == 0) ret = -EINVAL; -out2: - kfree(unifilename); -out1: - kfree(filename); return ret; } -int udf_put_filename(struct super_block *sb, const uint8_t *sname, - uint8_t *dname, int flen) +int udf_put_filename(struct super_block *sb, const uint8_t *sname, int slen, + uint8_t *dname, int dlen) { - struct ustr unifilename; - int namelen; - - if (!udf_char_to_ustr(&unifilename, sname, flen)) - return 0; + int (*conv_f)(const unsigned char *, int, wchar_t *); if (UDF_QUERY_FLAG(sb, UDF_FLAG_UTF8)) { - namelen = udf_UTF8toCS0(dname, &unifilename, UDF_NAME_LEN); - if (!namelen) - return 0; + conv_f = udf_char2uni_utf8; } else if (UDF_QUERY_FLAG(sb, UDF_FLAG_NLS_MAP)) { - namelen = udf_NLStoCS0(UDF_SB(sb)->s_nls_map, dname, - &unifilename, UDF_NAME_LEN); - if (!namelen) - return 0; + conv_f = UDF_SB(sb)->s_nls_map->char2uni; } else - return 0; + BUG(); - return namelen; + return udf_name_to_CS0(dname, dlen, sname, slen, conv_f); } -#define ILLEGAL_CHAR_MARK '_' -#define EXT_MARK '.' -#define CRC_MARK '#' -#define EXT_SIZE 5 -/* Number of chars we need to store generated CRC to make filename unique */ -#define CRC_LEN 5 - -static int udf_translate_to_linux(uint8_t *newName, int newLen, - uint8_t *udfName, int udfLen, - uint8_t *fidName, int fidNameLen) -{ - int index, newIndex = 0, needsCRC = 0; - int extIndex = 0, newExtIndex = 0, hasExt = 0; - unsigned short valueCRC; - uint8_t curr; - - if (udfName[0] == '.' && - (udfLen == 1 || (udfLen == 2 && udfName[1] == '.'))) { - needsCRC = 1; - newIndex = udfLen; - memcpy(newName, udfName, udfLen); - } else { - for (index = 0; index < udfLen; index++) { - curr = udfName[index]; - if (curr == '/' || curr == 0) { - needsCRC = 1; - curr = ILLEGAL_CHAR_MARK; - while (index + 1 < udfLen && - (udfName[index + 1] == '/' || - udfName[index + 1] == 0)) - index++; - } - if (curr == EXT_MARK && - (udfLen - index - 1) <= EXT_SIZE) { - if (udfLen == index + 1) - hasExt = 0; - else { - hasExt = 1; - extIndex = index; - newExtIndex = newIndex; - } - } - if (newIndex < newLen) - newName[newIndex++] = curr; - else - needsCRC = 1; - } - } - if (needsCRC) { - uint8_t ext[EXT_SIZE]; - int localExtIndex = 0; - - if (hasExt) { - int maxFilenameLen; - for (index = 0; - index < EXT_SIZE && extIndex + index + 1 < udfLen; - index++) { - curr = udfName[extIndex + index + 1]; - - if (curr == '/' || curr == 0) { - needsCRC = 1; - curr = ILLEGAL_CHAR_MARK; - while (extIndex + index + 2 < udfLen && - (index + 1 < EXT_SIZE && - (udfName[extIndex + index + 2] == '/' || - udfName[extIndex + index + 2] == 0))) - index++; - } - ext[localExtIndex++] = curr; - } - maxFilenameLen = newLen - CRC_LEN - localExtIndex; - if (newIndex > maxFilenameLen) - newIndex = maxFilenameLen; - else - newIndex = newExtIndex; - } else if (newIndex > newLen - CRC_LEN) - newIndex = newLen - CRC_LEN; - newName[newIndex++] = CRC_MARK; - valueCRC = crc_itu_t(0, fidName, fidNameLen); - newName[newIndex++] = hex_asc_upper_hi(valueCRC >> 8); - newName[newIndex++] = hex_asc_upper_lo(valueCRC >> 8); - newName[newIndex++] = hex_asc_upper_hi(valueCRC); - newName[newIndex++] = hex_asc_upper_lo(valueCRC); - - if (hasExt) { - newName[newIndex++] = EXT_MARK; - for (index = 0; index < localExtIndex; index++) - newName[newIndex++] = ext[index]; - } - } - - return newIndex; -} |