// SPDX-License-Identifier: GPL-2.0-only
/*
* linux / fs / msdos / namei . c
*
* Written 1992 , 1993 by Werner Almesberger
* Hidden files 1995 by Albert Cahalan < albert @ ccs . neu . edu > < adc @ coe . neu . edu >
* Rewritten for constant inumbers 1999 by Al Viro
*/
#include <linux/module.h>
#include <linux/iversion.h>
#include "fat.h"
/* Characters that are undesirable in an MS-DOS file name */
static unsigned char bad_chars[] = "*?<>|\" ";
static unsigned char bad_if_strict[] = "+=,; " ;
/***** Formats an MS-DOS file name. Rejects invalid names. */
static int msdos_format_name(const unsigned char *name, int len,
unsigned char *res, struct fat_mount_options *opts)
/*
* name is the proposed name , len is its length , res is
* the resulting name , opts - > name_check is either ( r ) elaxed ,
* ( n ) ormal or ( s ) trict , opts - > dotsOK allows dots at the
* beginning of name ( for hidden files )
*/
{
unsigned char *walk;
unsigned char c;
int space;
if (name[0 ] == '.' ) { /* dotfile because . and .. already done */
if (opts->dotsOK) {
/* Get rid of dot - test for it elsewhere */
name++;
len--;
} else
return -EINVAL;
}
/*
* disallow names that _ really_ start with a dot
*/
space = 1 ;
c = 0 ;
for (walk = res; len && walk - res < 8 ; walk++) {
c = *name++;
len--;
if (opts->name_check != 'r' && strchr(bad_chars, c))
return -EINVAL;
if (opts->name_check == 's' && strchr(bad_if_strict, c))
return -EINVAL;
if (c >= 'A' && c <= 'Z' && opts->name_check == 's' )
return -EINVAL;
if (c < ' ' || c == ':' || c == '\\' )
return -EINVAL;
/*
* 0 xE5 is legal as a first character , but we must substitute
* 0 x05 because 0 xE5 marks deleted files . Yes , DOS really
* does this .
* It seems that Microsoft hacked DOS to support non - US
* characters after the 0 xE5 character was already in use to
* mark deleted files .
*/
if ((res == walk) && (c == 0 xE5))
c = 0 x05;
if (c == '.' )
break ;
space = (c == ' ' );
*walk = (!opts->nocase && c >= 'a' && c <= 'z' ) ? c - 32 : c;
}
if (space)
return -EINVAL;
if (opts->name_check == 's' && len && c != '.' ) {
c = *name++;
len--;
if (c != '.' )
return -EINVAL;
}
while (c != '.' && len--)
c = *name++;
if (c == '.' ) {
while (walk - res < 8 )
*walk++ = ' ' ;
while (len > 0 && walk - res < MSDOS_NAME) {
c = *name++;
len--;
if (opts->name_check != 'r' && strchr(bad_chars, c))
return -EINVAL;
if (opts->name_check == 's' &&
strchr(bad_if_strict, c))
return -EINVAL;
if (c < ' ' || c == ':' || c == '\\' )
return -EINVAL;
if (c == '.' ) {
if (opts->name_check == 's' )
return -EINVAL;
break ;
}
if (c >= 'A' && c <= 'Z' && opts->name_check == 's' )
return -EINVAL;
space = c == ' ' ;
if (!opts->nocase && c >= 'a' && c <= 'z' )
*walk++ = c - 32 ;
else
*walk++ = c;
}
if (space)
return -EINVAL;
if (opts->name_check == 's' && len)
return -EINVAL;
}
while (walk - res < MSDOS_NAME)
*walk++ = ' ' ;
return 0 ;
}
/***** Locates a directory entry. Uses unformatted name. */
static int msdos_find(struct inode *dir, const unsigned char *name, int len,
struct fat_slot_info *sinfo)
{
struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);
unsigned char msdos_name[MSDOS_NAME];
int err;
err = msdos_format_name(name, len, msdos_name, &sbi->options);
if (err)
return -ENOENT;
err = fat_scan(dir, msdos_name, sinfo);
if (!err && sbi->options.dotsOK) {
if (name[0 ] == '.' ) {
if (!(sinfo->de->attr & ATTR_HIDDEN))
err = -ENOENT;
} else {
if (sinfo->de->attr & ATTR_HIDDEN)
err = -ENOENT;
}
if (err)
brelse(sinfo->bh);
}
return err;
}
/*
* Compute the hash for the msdos 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 msdos fs routines will
* return ENOENT or EINVAL as appropriate .
*/
static int msdos_hash(const struct dentry *dentry, struct qstr *qstr)
{
struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;
unsigned char msdos_name[MSDOS_NAME];
int error;
error = msdos_format_name(qstr->name, qstr->len, msdos_name, options);
if (!error)
qstr->hash = full_name_hash(dentry, msdos_name, MSDOS_NAME);
return 0 ;
}
/*
* Compare two msdos names . If either of the names are invalid ,
* we fall back to doing the standard name comparison .
*/
static int msdos_cmp(const struct dentry *dentry,
unsigned int len, const char *str, const struct qstr *name)
{
struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;
unsigned char a_msdos_name[MSDOS_NAME], b_msdos_name[MSDOS_NAME];
int error;
error = msdos_format_name(name->name, name->len, a_msdos_name, options);
if (error)
goto old_compare;
error = msdos_format_name(str, len, b_msdos_name, options);
if (error)
goto old_compare;
error = memcmp(a_msdos_name, b_msdos_name, MSDOS_NAME);
out:
return error;
old_compare:
error = 1 ;
if (name->len == len)
error = memcmp(name->name, str, len);
goto out;
}
static const struct dentry_operations msdos_dentry_operations = {
.d_hash = msdos_hash,
.d_compare = msdos_cmp,
};
/*
* AV . Wrappers for FAT sb operations . Is it wise ?
*/
/***** Get inode using directory and name */
static struct dentry *msdos_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct super_block *sb = dir->i_sb;
struct fat_slot_info sinfo;
struct inode *inode;
int err;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
switch (err) {
case -ENOENT:
inode = NULL;
break ;
case 0 :
inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
brelse(sinfo.bh);
break ;
default :
inode = ERR_PTR(err);
}
mutex_unlock(&MSDOS_SB(sb)->s_lock);
return d_splice_alias(inode, dentry);
}
/***** Creates a directory entry (name is already formatted). */
static int msdos_add_entry(struct inode *dir, const unsigned char *name,
int is_dir, int is_hid, int cluster,
struct timespec64 *ts, struct fat_slot_info *sinfo)
{
struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);
struct msdos_dir_entry de;
__le16 time, date;
int err;
memcpy(de.name, name, MSDOS_NAME);
de.attr = is_dir ? ATTR_DIR : ATTR_ARCH;
if (is_hid)
de.attr |= ATTR_HIDDEN;
de.lcase = 0 ;
fat_time_unix2fat(sbi, ts, &time, &date, NULL);
de.cdate = de.adate = 0 ;
de.ctime = 0 ;
de.ctime_cs = 0 ;
de.time = time;
de.date = date;
fat_set_start(&de, cluster);
de.size = 0 ;
err = fat_add_entries(dir, &de, 1 , sinfo);
if (err)
return err;
fat_truncate_time(dir, ts, S_CTIME|S_MTIME);
if (IS_DIRSYNC(dir))
(void )fat_sync_inode(dir);
else
mark_inode_dirty(dir);
return 0 ;
}
/***** Create a file */
static int msdos_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl)
{
struct super_block *sb = dir->i_sb;
struct inode *inode = NULL;
struct fat_slot_info sinfo;
struct timespec64 ts;
unsigned char msdos_name[MSDOS_NAME];
int err, is_hid;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
msdos_name, &MSDOS_SB(sb)->options);
if (err)
goto out;
is_hid = (dentry->d_name.name[0 ] == '.' ) && (msdos_name[0 ] != '.' );
/* Have to do it due to foo vs. .foo conflicts */
if (!fat_scan(dir, msdos_name, &sinfo)) {
brelse(sinfo.bh);
err = -EINVAL;
goto out;
}
ts = current_time(dir);
err = msdos_add_entry(dir, msdos_name, 0 , is_hid, 0 , &ts, &sinfo);
if (err)
goto out;
inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
brelse(sinfo.bh);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto out;
}
fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME);
/* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, inode);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
if (!err)
err = fat_flush_inodes(sb, dir, inode);
return err;
}
/***** Remove a directory */
static int msdos_rmdir(struct inode *dir, struct dentry *dentry)
{
struct super_block *sb = dir->i_sb;
struct inode *inode = d_inode(dentry);
struct fat_slot_info sinfo;
int err;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = fat_dir_empty(inode);
if (err)
goto out;
err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
if (err)
goto out;
err = fat_remove_entries(dir, &sinfo); /* and releases bh */
if (err)
goto out;
drop_nlink(dir);
clear_nlink(inode);
fat_truncate_time(inode, NULL, S_CTIME);
fat_detach(inode);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
if (!err)
err = fat_flush_inodes(sb, dir, inode);
return err;
}
/***** Make a directory */
static struct dentry *msdos_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
struct super_block *sb = dir->i_sb;
struct fat_slot_info sinfo;
struct inode *inode;
unsigned char msdos_name[MSDOS_NAME];
struct timespec64 ts;
int err, is_hid, cluster;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
msdos_name, &MSDOS_SB(sb)->options);
if (err)
goto out;
is_hid = (dentry->d_name.name[0 ] == '.' ) && (msdos_name[0 ] != '.' );
/* foo vs .foo situation */
if (!fat_scan(dir, msdos_name, &sinfo)) {
brelse(sinfo.bh);
err = -EINVAL;
goto out;
}
ts = current_time(dir);
cluster = fat_alloc_new_dir(dir, &ts);
if (cluster < 0 ) {
err = cluster;
goto out;
}
err = msdos_add_entry(dir, msdos_name, 1 , is_hid, cluster, &ts, &sinfo);
if (err)
goto out_free;
inc_nlink(dir);
inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
brelse(sinfo.bh);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
/* the directory was completed, just return a error */
goto out;
}
set_nlink(inode, 2 );
fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME);
/* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, inode);
mutex_unlock(&MSDOS_SB(sb)->s_lock);
fat_flush_inodes(sb, dir, inode);
return NULL;
out_free:
fat_free_clusters(dir, cluster);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
return ERR_PTR(err);
}
/***** Unlink a file */
static int msdos_unlink(struct inode *dir, struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
struct super_block *sb = inode->i_sb;
struct fat_slot_info sinfo;
int err;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
if (err)
goto out;
err = fat_remove_entries(dir, &sinfo); /* and releases bh */
if (err)
goto out;
clear_nlink(inode);
fat_truncate_time(inode, NULL, S_CTIME);
fat_detach(inode);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
if (!err)
err = fat_flush_inodes(sb, dir, inode);
return err;
}
static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
struct dentry *old_dentry,
struct inode *new_dir, unsigned char *new_name,
struct dentry *new_dentry, int is_hid)
{
struct buffer_head *dotdot_bh;
struct msdos_dir_entry *dotdot_de;
struct inode *old_inode, *new_inode;
struct fat_slot_info old_sinfo, sinfo;
struct timespec64 ts;
loff_t new_i_pos;
int err, old_attrs, is_dir, update_dotdot, corrupt = 0 ;
old_sinfo.bh = sinfo.bh = dotdot_bh = NULL;
old_inode = d_inode(old_dentry);
new_inode = d_inode(new_dentry);
err = fat_scan(old_dir, old_name, &old_sinfo);
if (err) {
err = -EIO;
goto out;
}
is_dir = S_ISDIR(old_inode->i_mode);
update_dotdot = (is_dir && old_dir != new_dir);
if (update_dotdot) {
if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de)) {
err = -EIO;
goto out;
}
}
old_attrs = MSDOS_I(old_inode)->i_attrs;
err = fat_scan(new_dir, new_name, &sinfo);
if (!err) {
if (!new_inode) {
/* "foo" -> ".foo" case. just change the ATTR_HIDDEN */
if (sinfo.de != old_sinfo.de) {
err = -EINVAL;
goto out;
}
if (is_hid)
MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
else
MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
if (IS_DIRSYNC(old_dir)) {
err = fat_sync_inode(old_inode);
if (err) {
MSDOS_I(old_inode)->i_attrs = old_attrs;
goto out;
}
} else
mark_inode_dirty(old_inode);
inode_inc_iversion(old_dir);
fat_truncate_time(old_dir, NULL, S_CTIME|S_MTIME);
if (IS_DIRSYNC(old_dir))
(void )fat_sync_inode(old_dir);
else
mark_inode_dirty(old_dir);
goto out;
}
}
ts = current_time(old_inode);
if (new_inode) {
if (err)
goto out;
if (is_dir) {
err = fat_dir_empty(new_inode);
if (err)
goto out;
}
new_i_pos = MSDOS_I(new_inode)->i_pos;
fat_detach(new_inode);
} else {
err = msdos_add_entry(new_dir, new_name, is_dir, is_hid, 0 ,
&ts, &sinfo);
if (err)
goto out;
new_i_pos = sinfo.i_pos;
}
inode_inc_iversion(new_dir);
fat_detach(old_inode);
fat_attach(old_inode, new_i_pos);
if (is_hid)
MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
else
MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
if (IS_DIRSYNC(new_dir)) {
err = fat_sync_inode(old_inode);
if (err)
goto error_inode;
} else
mark_inode_dirty(old_inode);
if (update_dotdot) {
fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart);
mark_buffer_dirty_inode(dotdot_bh, old_inode);
if (IS_DIRSYNC(new_dir)) {
err = sync_dirty_buffer(dotdot_bh);
if (err)
goto error_dotdot;
}
drop_nlink(old_dir);
if (!new_inode)
inc_nlink(new_dir);
}
err = fat_remove_entries(old_dir, &old_sinfo); /* and releases bh */
old_sinfo.bh = NULL;
if (err)
goto error_dotdot;
inode_inc_iversion(old_dir);
fat_truncate_time(old_dir, &ts, S_CTIME|S_MTIME);
if (IS_DIRSYNC(old_dir))
(void )fat_sync_inode(old_dir);
else
mark_inode_dirty(old_dir);
if (new_inode) {
drop_nlink(new_inode);
if (is_dir)
drop_nlink(new_inode);
fat_truncate_time(new_inode, &ts, S_CTIME);
}
out:
brelse(sinfo.bh);
brelse(dotdot_bh);
brelse(old_sinfo.bh);
return err;
error_dotdot:
/* data cluster is shared, serious corruption */
corrupt = 1 ;
if (update_dotdot) {
fat_set_start(dotdot_de, MSDOS_I(old_dir)->i_logstart);
mark_buffer_dirty_inode(dotdot_bh, old_inode);
corrupt |= sync_dirty_buffer(dotdot_bh);
}
error_inode:
fat_detach(old_inode);
fat_attach(old_inode, old_sinfo.i_pos);
MSDOS_I(old_inode)->i_attrs = old_attrs;
if (new_inode) {
fat_attach(new_inode, new_i_pos);
if (corrupt)
corrupt |= fat_sync_inode(new_inode);
} else {
/*
* If new entry was not sharing the data cluster , it
* shouldn ' t be serious corruption .
*/
int err2 = fat_remove_entries(new_dir, &sinfo);
if (corrupt)
corrupt |= err2;
sinfo.bh = NULL;
}
if (corrupt < 0 ) {
fat_fs_error(new_dir->i_sb,
"%s: Filesystem corrupted (i_pos %lld)" ,
__func__, sinfo.i_pos);
}
goto out;
}
/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */
static int msdos_rename(struct mnt_idmap *idmap,
struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags)
{
struct super_block *sb = old_dir->i_sb;
unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];
int err, is_hid;
if (flags & ~RENAME_NOREPLACE)
return -EINVAL;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_format_name(old_dentry->d_name.name,
old_dentry->d_name.len, old_msdos_name,
&MSDOS_SB(old_dir->i_sb)->options);
if (err)
goto out;
err = msdos_format_name(new_dentry->d_name.name,
new_dentry->d_name.len, new_msdos_name,
&MSDOS_SB(new_dir->i_sb)->options);
if (err)
goto out;
is_hid =
(new_dentry->d_name.name[0 ] == '.' ) && (new_msdos_name[0 ] != '.' );
err = do_msdos_rename(old_dir, old_msdos_name, old_dentry,
new_dir, new_msdos_name, new_dentry, is_hid);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
if (!err)
err = fat_flush_inodes(sb, old_dir, new_dir);
return err;
}
static const struct inode_operations msdos_dir_inode_operations = {
.create = msdos_create,
.lookup = msdos_lookup,
.unlink = msdos_unlink,
.mkdir = msdos_mkdir,
.rmdir = msdos_rmdir,
.rename = msdos_rename,
.setattr = fat_setattr,
.getattr = fat_getattr,
.update_time = fat_update_time,
};
static void setup(struct super_block *sb)
{
MSDOS_SB(sb)->dir_ops = &msdos_dir_inode_operations;
set_default_d_op(sb, &msdos_dentry_operations);
sb->s_flags |= SB_NOATIME;
}
static int msdos_fill_super(struct super_block *sb, struct fs_context *fc)
{
return fat_fill_super(sb, fc, setup);
}
static int msdos_get_tree(struct fs_context *fc)
{
return get_tree_bdev(fc, msdos_fill_super);
}
static int msdos_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
return fat_parse_param(fc, param, false );
}
static const struct fs_context_operations msdos_context_ops = {
.parse_param = msdos_parse_param,
.get_tree = msdos_get_tree,
.reconfigure = fat_reconfigure,
.free = fat_free_fc,
};
static int msdos_init_fs_context(struct fs_context *fc)
{
int err;
/* Initialize with is_vfat == false */
err = fat_init_fs_context(fc, false );
if (err)
return err;
fc->ops = &msdos_context_ops;
return 0 ;
}
static struct file_system_type msdos_fs_type = {
.owner = THIS_MODULE,
.name = "msdos" ,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
.init_fs_context = msdos_init_fs_context,
.parameters = fat_param_spec,
};
MODULE_ALIAS_FS("msdos" );
static int __init init_msdos_fs(void )
{
return register_filesystem(&msdos_fs_type);
}
static void __exit exit_msdos_fs(void )
{
unregister_filesystem(&msdos_fs_type);
}
MODULE_LICENSE("GPL" );
MODULE_AUTHOR("Werner Almesberger" );
MODULE_DESCRIPTION("MS-DOS filesystem support" );
module_init(init_msdos_fs)
module_exit(exit_msdos_fs)
Messung V0.5 in Prozent C=96 H=81 G=88
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland