// SPDX-License-Identifier: GPL-2.0-only /* * linux/fs/vfat/namei.c * * Written 1992,1993 by Werner Almesberger * * Windows95/Windows NT compatible extended MSDOS filesystem * by Gordon Chaffee Copyright (C) 1995. Send bug reports for the * VFAT filesystem to <chaffee@cs.berkeley.edu>. Specify * what file operation caused you trouble and if you can duplicate * the problem, send a script that demonstrates it. * * Short name translation 1999, 2001 by Wolfram Pienkoss <wp@bszh.de> * * Support Multibyte characters and cleanup by * OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
*/
/* * If new entry was created in the parent, it could create the 8.3 * alias (the shortname of logname). So, the parent may have the * negative-dentry which matches the created 8.3 alias. * * If it happened, the negative dentry isn't actually negative * anymore. So, drop it.
*/ staticbool vfat_revalidate_shortname(struct dentry *dentry, struct inode *dir)
{ return inode_eq_iversion(dir, vfat_d_version(dentry));
}
/* * This is not negative dentry. Always valid. * * Note, rename() to existing directory entry will have ->d_inode, * and will use existing name which isn't specified name by user. * * We may be able to drop this positive dentry here. But dropping * positive dentry isn't good idea. So it's unsupported like * rename("filename", "FILENAME") for now.
*/ if (d_really_is_positive(dentry)) return 1;
/* * This may be nfsd (or something), anyway, we can't see the * intent of this. So, since this can be for creation, drop it.
*/ if (!flags) return 0;
/* * Drop the negative dentry, in order to make sure to use the * case sensitive name which is specified by user if this is * for creation.
*/ if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET)) return 0;
return vfat_revalidate_shortname(dentry, dir);
}
/* returns the length of a struct qstr, ignoring trailing dots */ staticunsignedint __vfat_striptail_len(unsignedint len, constchar *name)
{ while (len && name[len - 1] == '.')
len--; return len;
}
/* * Compute the hash for the vfat name corresponding to the dentry. * Note: if the name is invalid, we leave the hash code unchanged so * that the existing dentry can be used. The vfat fs routines will * return ENOENT or EINVAL as appropriate.
*/ staticint vfat_hash(conststruct dentry *dentry, struct qstr *qstr)
{
qstr->hash = full_name_hash(dentry, qstr->name, vfat_striptail_len(qstr)); return 0;
}
/* * Compute the hash for the vfat name corresponding to the dentry. * Note: if the name is invalid, we leave the hash code unchanged so * that the existing dentry can be used. The vfat fs routines will * return ENOENT or EINVAL as appropriate.
*/ staticint vfat_hashi(conststruct dentry *dentry, struct qstr *qstr)
{ struct nls_table *t = MSDOS_SB(dentry->d_sb)->nls_io; constunsignedchar *name; unsignedint len; unsignedlong hash;
name = qstr->name;
len = vfat_striptail_len(qstr);
/* * Case insensitive compare of two vfat names.
*/ staticint vfat_cmpi(conststruct dentry *dentry, unsignedint len, constchar *str, conststruct qstr *name)
{ struct nls_table *t = MSDOS_SB(dentry->d_sb)->nls_io; unsignedint alen, blen;
/* A filename cannot end in '.' or we treat it like it has none */
alen = vfat_striptail_len(name);
blen = __vfat_striptail_len(len, str); if (alen == blen) { if (nls_strnicmp(t, name->name, str, alen) == 0) return 0;
} return 1;
}
/* * Case sensitive compare of two vfat names.
*/ staticint vfat_cmp(conststruct dentry *dentry, unsignedint len, constchar *str, conststruct qstr *name)
{ unsignedint alen, blen;
/* A filename cannot end in '.' or we treat it like it has none */
alen = vfat_striptail_len(name);
blen = __vfat_striptail_len(len, str); if (alen == blen) { if (strncmp(name->name, str, alen) == 0) return 0;
} return 1;
}
/* Characters that are undesirable in an MS-DOS file name */
staticinlinebool vfat_bad_char(wchar_t w)
{ return (w < 0x0020)
|| (w == '*') || (w == '?') || (w == '<') || (w == '>')
|| (w == '|') || (w == '"') || (w == ':') || (w == '/')
|| (w == '\\');
}
staticinlinebool vfat_replace_char(wchar_t w)
{ return (w == '[') || (w == ']') || (w == ';') || (w == ',')
|| (w == '+') || (w == '=');
}
staticwchar_t vfat_skip_char(wchar_t w)
{ return (w == '.') || (w == ' ');
}
staticinlineint vfat_is_used_badchars(constwchar_t *s, int len)
{ int i;
for (i = 0; i < len; i++) if (vfat_bad_char(s[i])) return -EINVAL;
if (s[i - 1] == ' ') /* last character cannot be space */ return -EINVAL;
/* * 1) Valid characters for the 8.3 format alias are any combination of * letters, uppercase alphabets, digits, any of the * following special characters: * $ % ' ` - @ { } ~ ! # ( ) & _ ^ * In this case Longfilename is not stored in disk. * * WinNT's Extension: * File name and extension name is contain uppercase/lowercase * only. And it is expressed by CASE_LOWER_BASE and CASE_LOWER_EXT. * * 2) File name is 8.3 format, but it contain the uppercase and * lowercase char, muliti bytes char, etc. In this case numtail is not * added, but Longfilename is stored. * * 3) When the one except for the above, or the following special * character are contained: * . [ ] ; , + = * numtail is added, and Longfilename must be stored in disk .
*/ struct shortname_info { unsignedchar lower:1,
upper:1,
valid:1;
}; #define INIT_SHORTNAME_INFO(x) do { \
(x)->lower = 1; \
(x)->upper = 1; \
(x)->valid = 1; \
} while (0)
staticinlineint to_shortname_char(struct nls_table *nls, unsignedchar *buf, int buf_size, wchar_t *src, struct shortname_info *info)
{ int len;
/* * Given a valid longname, create a unique shortname. Make sure the * shortname does not exist * Returns negative number on error, 0 for a normal * return, and 1 for valid shortname
*/ staticint vfat_create_shortname(struct inode *dir, struct nls_table *nls, wchar_t *uname, int ulen, unsignedchar *name_res, unsignedchar *lcase)
{ struct fat_mount_options *opts = &MSDOS_SB(dir->i_sb)->options; wchar_t *ip, *ext_start, *end, *name_start; unsignedchar base[9], ext[4], buf[5], *p; unsignedchar charbuf[NLS_MAX_CHARSET_SIZE]; int chl, chi; int sz = 0, extlen, baselen, i, numtail_baselen, numtail2_baselen; int is_shortname; struct shortname_info base_info, ext_info;
/* Now, we need to create a shortname from the long name */
ext_start = end = &uname[ulen]; while (--ext_start >= uname) { if (*ext_start == 0x002E) { /* is `.' */ if (ext_start == end - 1) {
sz = ulen;
ext_start = NULL;
} break;
}
}
if (ext_start == uname - 1) {
sz = ulen;
ext_start = NULL;
} elseif (ext_start) { /* * Names which start with a dot could be just * an extension eg. "...test". In this case Win95 * uses the extension as the name and sets no extension.
*/
name_start = &uname[0]; while (name_start < ext_start) { if (!vfat_skip_char(*name_start)) break;
name_start++;
} if (name_start != ext_start) {
sz = ext_start - uname;
ext_start++;
} else {
sz = ulen;
ext_start = NULL;
}
}
numtail_baselen = 6;
numtail2_baselen = 2; for (baselen = i = 0, p = base, ip = uname; i < sz; i++, ip++) {
chl = to_shortname_char(nls, charbuf, sizeof(charbuf),
ip, &base_info); if (chl == 0) continue;
if (baselen < 2 && (baselen + chl) > 2)
numtail2_baselen = baselen; if (baselen < 6 && (baselen + chl) > 6)
numtail_baselen = baselen; for (chi = 0; chi < chl; chi++) {
*p++ = charbuf[chi];
baselen++; if (baselen >= 8) break;
} if (baselen >= 8) { if ((chi < chl - 1) || (ip + 1) - uname < sz)
is_shortname = 0; break;
}
} if (baselen == 0) { return -EINVAL;
}
extlen = 0; if (ext_start) { for (p = ext, ip = ext_start; extlen < 3 && ip < end; ip++) {
chl = to_shortname_char(nls, charbuf, sizeof(charbuf),
ip, &ext_info); if (chl == 0) continue;
if ((extlen + chl) > 3) {
is_shortname = 0; break;
} for (chi = 0; chi < chl; chi++) {
*p++ = charbuf[chi];
extlen++;
} if (extlen >= 3) { if (ip + 1 != end)
is_shortname = 0; break;
}
}
}
ext[extlen] = '\0';
base[baselen] = '\0';
/* Yes, it can happen. ".\xe5" would do it. */ if (base[0] == DELETED_FLAG)
base[0] = 0x05;
/* OK, at this point we know that base is not longer than 8 symbols, * ext is not longer than 3, base is nonempty, both don't contain * any bad symbols (lowercase transformed to uppercase).
*/
if (opts->numtail == 0) if (vfat_find_form(dir, name_res) < 0) return 0;
/* * Try to find a unique extension. This used to * iterate through all possibilities sequentially, * but that gave extremely bad performance. Windows * only tries a few cases before using random * values for part of the base.
*/
if (baselen > 6) {
baselen = numtail_baselen;
name_res[7] = ' ';
}
name_res[baselen] = '~'; for (i = 1; i < 10; i++) {
name_res[baselen + 1] = i + '0'; if (vfat_find_form(dir, name_res) < 0) return 0;
}
i = jiffies;
sz = (jiffies >> 16) & 0x7; if (baselen > 2) {
baselen = numtail2_baselen;
name_res[7] = ' ';
}
name_res[baselen + 4] = '~';
name_res[baselen + 5] = '1' + sz; while (1) {
snprintf(buf, sizeof(buf), "%04X", i & 0xffff);
memcpy(&name_res[baselen], buf, 4); if (vfat_find_form(dir, name_res) < 0) break;
i -= 11;
} return 0;
}
/* Translate a string, including coded sequences into Unicode */ staticint
xlate_to_uni(constunsignedchar *name, int len, unsignedchar *outname, int *longlen, int *outlen, int escape, int utf8, struct nls_table *nls)
{ constunsignedchar *ip; unsignedchar *op; int i, fill; int charlen;
op = &outname[*outlen * sizeof(wchar_t)];
} else { for (i = 0, ip = name, op = outname, *outlen = 0;
i < len && *outlen < FAT_LFN_LEN;
*outlen += 1) { if (escape && (*ip == ':')) {
u8 uc[2];
if (i > len - 5) return -EINVAL;
if (hex2bin(uc, ip + 1, 2) < 0) return -EINVAL;
*(wchar_t *)op = uc[0] << 8 | uc[1];
op += 2;
ip += 5;
i += 5;
} else {
charlen = nls->char2uni(ip, len - i,
(wchar_t *)op); if (charlen < 0) return -EINVAL;
ip += charlen;
i += charlen;
op += 2;
}
} if (i < len) return -ENAMETOOLONG;
}
*longlen = *outlen; if (*outlen % 13) {
*op++ = 0;
*op++ = 0;
*outlen += 1; if (*outlen % 13) {
fill = 13 - (*outlen % 13); for (i = 0; i < fill; i++) {
*op++ = 0xff;
*op++ = 0xff;
}
*outlen += fill;
}
}
return 0;
}
staticint vfat_build_slots(struct inode *dir, constunsignedchar *name, int len, int is_dir, int cluster, struct timespec64 *ts, struct msdos_dir_slot *slots, int *nr_slots)
{ struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb); struct fat_mount_options *opts = &sbi->options; struct msdos_dir_slot *ps; struct msdos_dir_entry *de; unsignedchar cksum, lcase; unsignedchar msdos_name[MSDOS_NAME]; wchar_t *uname;
__le16 time, date;
u8 time_cs; int err, ulen, usize, i;
loff_t offset;
alias = d_find_alias(inode); /* * Checking "alias->d_parent == dentry->d_parent" to make sure * FS is not corrupted (especially double linked dir).
*/ if (alias && alias->d_parent == dentry->d_parent) { /* * This inode has non anonymous-DCACHE_DISCONNECTED * dentry. This means, the user did ->lookup() by an * another name (longname vs 8.3 alias of it) in past. * * Switch to new one for reason of locality if possible.
*/ if (!S_ISDIR(inode->i_mode))
d_move(alias, dentry);
iput(inode);
mutex_unlock(&MSDOS_SB(sb)->s_lock); return alias;
} else
dput(alias);
/* Acquire super block lock for the operation to be atomic */
mutex_lock(&MSDOS_SB(sb)->s_lock);
/* if directories are not the same, get ".." info to update */ if (old_dir != new_dir) {
err = vfat_get_dotdot_de(old_inode, &old_dotdot_bh,
&old_dotdot_de); if (err) goto out;
err = vfat_get_dotdot_de(new_inode, &new_dotdot_bh,
&new_dotdot_de); if (err) goto out;
}
err = vfat_sync_ipos(old_dir, new_inode); if (err) goto error_exchange;
err = vfat_sync_ipos(new_dir, old_inode); if (err) goto error_exchange;
/* update ".." directory entry info */ if (old_dotdot_de) {
err = vfat_update_dotdot_de(new_dir, old_inode, old_dotdot_bh,
old_dotdot_de); if (err) goto error_old_dotdot;
} if (new_dotdot_de) {
err = vfat_update_dotdot_de(old_dir, new_inode, new_dotdot_bh,
new_dotdot_de); if (err) goto error_new_dotdot;
}
/* if cross directory and only one is a directory, adjust nlink */ if (!old_dotdot_de != !new_dotdot_de) { if (old_dotdot_de)
vfat_move_nlink(old_dir, new_dir); else
vfat_move_nlink(new_dir, old_dir);
}
vfat_update_dir_metadata(old_dir, &ts); /* if directories are not the same, update new_dir as well */ if (old_dir != new_dir)
vfat_update_dir_metadata(new_dir, &ts);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.