summaryrefslogtreecommitdiff
path: root/fs/fat
diff options
context:
space:
mode:
Diffstat (limited to 'fs/fat')
-rw-r--r--fs/fat/dir.c52
-rw-r--r--fs/fat/fatent.c9
-rw-r--r--fs/fat/file.c216
-rw-r--r--fs/fat/inode.c38
4 files changed, 172 insertions, 143 deletions
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
index 72cbcd61bd95..486725ee99ae 100644
--- a/fs/fat/dir.c
+++ b/fs/fat/dir.c
@@ -124,8 +124,8 @@ static inline int fat_get_entry(struct inode *dir, loff_t *pos,
* but ignore that right now.
* Ahem... Stack smashing in ring 0 isn't fun. Fixed.
*/
-static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int uni_xlate,
- struct nls_table *nls)
+static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int len,
+ int uni_xlate, struct nls_table *nls)
{
wchar_t *ip, ec;
unsigned char *op, nc;
@@ -135,10 +135,11 @@ static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int uni_xlate,
ip = uni;
op = ascii;
- while (*ip) {
+ while (*ip && ((len - NLS_MAX_CHARSET_SIZE) > 0)) {
ec = *ip++;
if ( (charlen = nls->uni2char(ec, op, NLS_MAX_CHARSET_SIZE)) > 0) {
op += charlen;
+ len -= charlen;
} else {
if (uni_xlate == 1) {
*op = ':';
@@ -149,16 +150,19 @@ static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int uni_xlate,
ec >>= 4;
}
op += 5;
+ len -= 5;
} else {
*op++ = '?';
+ len--;
}
}
- /* We have some slack there, so it's OK */
- if (op>ascii+256) {
- op = ascii + 256;
- break;
- }
}
+
+ if (unlikely(*ip)) {
+ printk(KERN_WARNING "FAT: filename was truncated while "
+ "converting.");
+ }
+
*op = 0;
return (op - ascii);
}
@@ -243,7 +247,7 @@ static int fat_parse_long(struct inode *dir, loff_t *pos,
unsigned char id, slot, slots, alias_checksum;
if (!*unicode) {
- *unicode = (wchar_t *)__get_free_page(GFP_KERNEL);
+ *unicode = __getname();
if (!*unicode) {
brelse(*bh);
return -ENOMEM;
@@ -311,9 +315,11 @@ int fat_search_long(struct inode *inode, const unsigned char *name,
struct nls_table *nls_io = sbi->nls_io;
struct nls_table *nls_disk = sbi->nls_disk;
wchar_t bufuname[14];
- unsigned char xlate_len, nr_slots;
+ unsigned char nr_slots;
+ int xlate_len;
wchar_t *unicode = NULL;
- unsigned char work[MSDOS_NAME], bufname[260]; /* 256 + 4 */
+ unsigned char work[MSDOS_NAME];
+ unsigned char *bufname = NULL;
int uni_xlate = sbi->options.unicode_xlate;
int utf8 = sbi->options.utf8;
int anycase = (sbi->options.name_check != 's');
@@ -321,6 +327,10 @@ int fat_search_long(struct inode *inode, const unsigned char *name,
loff_t cpos = 0;
int chl, i, j, last_u, err;
+ bufname = __getname();
+ if (!bufname)
+ return -ENOMEM;
+
err = -ENOENT;
while(1) {
if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
@@ -386,8 +396,8 @@ parse_record:
bufuname[last_u] = 0x0000;
xlate_len = utf8
- ?utf8_wcstombs(bufname, bufuname, sizeof(bufname))
- :uni16_to_x8(bufname, bufuname, uni_xlate, nls_io);
+ ?utf8_wcstombs(bufname, bufuname, PATH_MAX)
+ :uni16_to_x8(bufname, bufuname, PATH_MAX, uni_xlate, nls_io);
if (xlate_len == name_len)
if ((!anycase && !memcmp(name, bufname, xlate_len)) ||
(anycase && !nls_strnicmp(nls_io, name, bufname,
@@ -396,8 +406,8 @@ parse_record:
if (nr_slots) {
xlate_len = utf8
- ?utf8_wcstombs(bufname, unicode, sizeof(bufname))
- :uni16_to_x8(bufname, unicode, uni_xlate, nls_io);
+ ?utf8_wcstombs(bufname, unicode, PATH_MAX)
+ :uni16_to_x8(bufname, unicode, PATH_MAX, uni_xlate, nls_io);
if (xlate_len != name_len)
continue;
if ((!anycase && !memcmp(name, bufname, xlate_len)) ||
@@ -416,8 +426,10 @@ Found:
sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
err = 0;
EODir:
+ if (bufname)
+ __putname(bufname);
if (unicode)
- free_page((unsigned long)unicode);
+ __putname(unicode);
return err;
}
@@ -598,7 +610,7 @@ parse_record:
if (isvfat) {
bufuname[j] = 0x0000;
i = utf8 ? utf8_wcstombs(bufname, bufuname, sizeof(bufname))
- : uni16_to_x8(bufname, bufuname, uni_xlate, nls_io);
+ : uni16_to_x8(bufname, bufuname, sizeof(bufname), uni_xlate, nls_io);
}
fill_name = bufname;
@@ -607,10 +619,10 @@ parse_record:
/* convert the unicode long name. 261 is maximum size
* of unicode buffer. (13 * slots + nul) */
void *longname = unicode + 261;
- int buf_size = PAGE_SIZE - (261 * sizeof(unicode[0]));
+ int buf_size = PATH_MAX - (261 * sizeof(unicode[0]));
int long_len = utf8
? utf8_wcstombs(longname, unicode, buf_size)
- : uni16_to_x8(longname, unicode, uni_xlate, nls_io);
+ : uni16_to_x8(longname, unicode, buf_size, uni_xlate, nls_io);
if (!both) {
fill_name = longname;
@@ -640,7 +652,7 @@ EODir:
FillFailed:
brelse(bh);
if (unicode)
- free_page((unsigned long)unicode);
+ __putname(unicode);
out:
unlock_kernel();
return ret;
diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c
index 5fb366992b73..13ab763cc510 100644
--- a/fs/fat/fatent.c
+++ b/fs/fat/fatent.c
@@ -450,7 +450,8 @@ int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster)
BUG_ON(nr_cluster > (MAX_BUF_PER_PAGE / 2)); /* fixed limit */
lock_fat(sbi);
- if (sbi->free_clusters != -1 && sbi->free_clusters < nr_cluster) {
+ if (sbi->free_clusters != -1 && sbi->free_clus_valid &&
+ sbi->free_clusters < nr_cluster) {
unlock_fat(sbi);
return -ENOSPC;
}
@@ -504,6 +505,7 @@ int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster)
/* Couldn't allocate the free entries */
sbi->free_clusters = 0;
+ sbi->free_clus_valid = 1;
sb->s_dirt = 1;
err = -ENOSPC;
@@ -583,8 +585,6 @@ error:
brelse(bhs[i]);
unlock_fat(sbi);
- fat_clusters_flush(sb);
-
return err;
}
@@ -615,7 +615,7 @@ int fat_count_free_clusters(struct super_block *sb)
int err = 0, free;
lock_fat(sbi);
- if (sbi->free_clusters != -1)
+ if (sbi->free_clusters != -1 && sbi->free_clus_valid)
goto out;
reada_blocks = FAT_READA_SIZE >> sb->s_blocksize_bits;
@@ -643,6 +643,7 @@ int fat_count_free_clusters(struct super_block *sb)
} while (fat_ent_next(sbi, &fatent));
}
sbi->free_clusters = free;
+ sbi->free_clus_valid = 1;
sb->s_dirt = 1;
fatent_brelse(&fatent);
out:
diff --git a/fs/fat/file.c b/fs/fat/file.c
index c614175876e0..d604bb132422 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -8,6 +8,7 @@
#include <linux/capability.h>
#include <linux/module.h>
+#include <linux/mount.h>
#include <linux/time.h>
#include <linux/msdos_fs.h>
#include <linux/smp_lock.h>
@@ -46,10 +47,9 @@ int fat_generic_ioctl(struct inode *inode, struct file *filp,
mutex_lock(&inode->i_mutex);
- if (IS_RDONLY(inode)) {
- err = -EROFS;
- goto up;
- }
+ err = mnt_want_write(filp->f_path.mnt);
+ if (err)
+ goto up_no_drop_write;
/*
* ATTR_VOLUME and ATTR_DIR cannot be changed; this also
@@ -105,7 +105,9 @@ int fat_generic_ioctl(struct inode *inode, struct file *filp,
MSDOS_I(inode)->i_attrs = attr & ATTR_UNUSED;
mark_inode_dirty(inode);
- up:
+up:
+ mnt_drop_write(filp->f_path.mnt);
+up_no_drop_write:
mutex_unlock(&inode->i_mutex);
return err;
}
@@ -155,104 +157,6 @@ out:
return err;
}
-static int check_mode(const struct msdos_sb_info *sbi, mode_t mode)
-{
- mode_t req = mode & ~S_IFMT;
-
- /*
- * Of the r and x bits, all (subject to umask) must be present. Of the
- * w bits, either all (subject to umask) or none must be present.
- */
-
- if (S_ISREG(mode)) {
- req &= ~sbi->options.fs_fmask;
-
- if ((req & (S_IRUGO | S_IXUGO)) !=
- ((S_IRUGO | S_IXUGO) & ~sbi->options.fs_fmask))
- return -EPERM;
-
- if ((req & S_IWUGO) != 0 &&
- (req & S_IWUGO) != (S_IWUGO & ~sbi->options.fs_fmask))
- return -EPERM;
- } else if (S_ISDIR(mode)) {
- req &= ~sbi->options.fs_dmask;
-
- if ((req & (S_IRUGO | S_IXUGO)) !=
- ((S_IRUGO | S_IXUGO) & ~sbi->options.fs_dmask))
- return -EPERM;
-
- if ((req & S_IWUGO) != 0 &&
- (req & S_IWUGO) != (S_IWUGO & ~sbi->options.fs_dmask))
- return -EPERM;
- } else {
- return -EPERM;
- }
-
- return 0;
-}
-
-int fat_notify_change(struct dentry *dentry, struct iattr *attr)
-{
- struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);
- struct inode *inode = dentry->d_inode;
- int mask, error = 0;
-
- lock_kernel();
-
- /*
- * Expand the file. Since inode_setattr() updates ->i_size
- * before calling the ->truncate(), but FAT needs to fill the
- * hole before it.
- */
- if (attr->ia_valid & ATTR_SIZE) {
- if (attr->ia_size > inode->i_size) {
- error = fat_cont_expand(inode, attr->ia_size);
- if (error || attr->ia_valid == ATTR_SIZE)
- goto out;
- attr->ia_valid &= ~ATTR_SIZE;
- }
- }
-
- error = inode_change_ok(inode, attr);
- if (error) {
- if (sbi->options.quiet)
- error = 0;
- goto out;
- }
- if (((attr->ia_valid & ATTR_UID) &&
- (attr->ia_uid != sbi->options.fs_uid)) ||
- ((attr->ia_valid & ATTR_GID) &&
- (attr->ia_gid != sbi->options.fs_gid)))
- error = -EPERM;
-
- if (error) {
- if (sbi->options.quiet)
- error = 0;
- goto out;
- }
-
- if (attr->ia_valid & ATTR_MODE) {
- error = check_mode(sbi, attr->ia_mode);
- if (error != 0 && !sbi->options.quiet)
- goto out;
- }
-
- error = inode_setattr(inode, attr);
- if (error)
- goto out;
-
- if (S_ISDIR(inode->i_mode))
- mask = sbi->options.fs_dmask;
- else
- mask = sbi->options.fs_fmask;
- inode->i_mode &= S_IFMT | (S_IRWXUGO & ~mask);
-out:
- unlock_kernel();
- return error;
-}
-
-EXPORT_SYMBOL_GPL(fat_notify_change);
-
/* Free all clusters after the skip'th cluster. */
static int fat_free(struct inode *inode, int skip)
{
@@ -353,8 +257,112 @@ int fat_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
}
EXPORT_SYMBOL_GPL(fat_getattr);
+static int fat_check_mode(const struct msdos_sb_info *sbi, struct inode *inode,
+ mode_t mode)
+{
+ mode_t mask, req = mode & ~S_IFMT;
+
+ if (S_ISREG(mode))
+ mask = sbi->options.fs_fmask;
+ else
+ mask = sbi->options.fs_dmask;
+
+ /*
+ * Of the r and x bits, all (subject to umask) must be present. Of the
+ * w bits, either all (subject to umask) or none must be present.
+ */
+ req &= ~mask;
+ if ((req & (S_IRUGO | S_IXUGO)) != (inode->i_mode & (S_IRUGO|S_IXUGO)))
+ return -EPERM;
+ if ((req & S_IWUGO) && ((req & S_IWUGO) != (S_IWUGO & ~mask)))
+ return -EPERM;
+
+ return 0;
+}
+
+static int fat_allow_set_time(struct msdos_sb_info *sbi, struct inode *inode)
+{
+ mode_t allow_utime = sbi->options.allow_utime;
+
+ if (current->fsuid != inode->i_uid) {
+ if (in_group_p(inode->i_gid))
+ allow_utime >>= 3;
+ if (allow_utime & MAY_WRITE)
+ return 1;
+ }
+
+ /* use a default check */
+ return 0;
+}
+
+int fat_setattr(struct dentry *dentry, struct iattr *attr)
+{
+ struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);
+ struct inode *inode = dentry->d_inode;
+ int mask, error = 0;
+ unsigned int ia_valid;
+
+ lock_kernel();
+
+ /*
+ * Expand the file. Since inode_setattr() updates ->i_size
+ * before calling the ->truncate(), but FAT needs to fill the
+ * hole before it.
+ */
+ if (attr->ia_valid & ATTR_SIZE) {
+ if (attr->ia_size > inode->i_size) {
+ error = fat_cont_expand(inode, attr->ia_size);
+ if (error || attr->ia_valid == ATTR_SIZE)
+ goto out;
+ attr->ia_valid &= ~ATTR_SIZE;
+ }
+ }
+
+ /* Check for setting the inode time. */
+ ia_valid = attr->ia_valid;
+ if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET)) {
+ if (fat_allow_set_time(sbi, inode))
+ attr->ia_valid &= ~(ATTR_MTIME_SET | ATTR_ATIME_SET);
+ }
+
+ error = inode_change_ok(inode, attr);
+ attr->ia_valid = ia_valid;
+ if (error) {
+ if (sbi->options.quiet)
+ error = 0;
+ goto out;
+ }
+ if (((attr->ia_valid & ATTR_UID) &&
+ (attr->ia_uid != sbi->options.fs_uid)) ||
+ ((attr->ia_valid & ATTR_GID) &&
+ (attr->ia_gid != sbi->options.fs_gid)) ||
+ ((attr->ia_valid & ATTR_MODE) &&
+ fat_check_mode(sbi, inode, attr->ia_mode) < 0))
+ error = -EPERM;
+
+ if (error) {
+ if (sbi->options.quiet)
+ error = 0;
+ goto out;
+ }
+
+ error = inode_setattr(inode, attr);
+ if (error)
+ goto out;
+
+ if (S_ISDIR(inode->i_mode))
+ mask = sbi->options.fs_dmask;
+ else
+ mask = sbi->options.fs_fmask;
+ inode->i_mode &= S_IFMT | (S_IRWXUGO & ~mask);
+out:
+ unlock_kernel();
+ return error;
+}
+EXPORT_SYMBOL_GPL(fat_setattr);
+
const struct inode_operations fat_file_inode_operations = {
.truncate = fat_truncate,
- .setattr = fat_notify_change,
+ .setattr = fat_setattr,
.getattr = fat_getattr,
};
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 53f3cf62b7c1..5f522a55b596 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -433,11 +433,8 @@ EXPORT_SYMBOL_GPL(fat_build_inode);
static void fat_delete_inode(struct inode *inode)
{
truncate_inode_pages(&inode->i_data, 0);
-
- if (!is_bad_inode(inode)) {
- inode->i_size = 0;
- fat_truncate(inode);
- }
+ inode->i_size = 0;
+ fat_truncate(inode);
clear_inode(inode);
}
@@ -445,8 +442,6 @@ static void fat_clear_inode(struct inode *inode)
{
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
- if (is_bad_inode(inode))
- return;
lock_kernel();
spin_lock(&sbi->inode_hash_lock);
fat_cache_inval_inode(inode);
@@ -542,7 +537,7 @@ static int fat_statfs(struct dentry *dentry, struct kstatfs *buf)
struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);
/* If the count of free cluster is still unknown, counts it here. */
- if (sbi->free_clusters == -1) {
+ if (sbi->free_clusters == -1 || !sbi->free_clus_valid) {
int err = fat_count_free_clusters(dentry->d_sb);
if (err)
return err;
@@ -790,6 +785,8 @@ static int fat_show_options(struct seq_file *m, struct vfsmount *mnt)
seq_printf(m, ",gid=%u", opts->fs_gid);
seq_printf(m, ",fmask=%04o", opts->fs_fmask);
seq_printf(m, ",dmask=%04o", opts->fs_dmask);
+ if (opts->allow_utime)
+ seq_printf(m, ",allow_utime=%04o", opts->allow_utime);
if (sbi->nls_disk)
seq_printf(m, ",codepage=%s", sbi->nls_disk->charset);
if (isvfat) {
@@ -845,9 +842,9 @@ static int fat_show_options(struct seq_file *m, struct vfsmount *mnt)
enum {
Opt_check_n, Opt_check_r, Opt_check_s, Opt_uid, Opt_gid,
- Opt_umask, Opt_dmask, Opt_fmask, Opt_codepage, Opt_usefree, Opt_nocase,
- Opt_quiet, Opt_showexec, Opt_debug, Opt_immutable,
- Opt_dots, Opt_nodots,
+ Opt_umask, Opt_dmask, Opt_fmask, Opt_allow_utime, Opt_codepage,
+ Opt_usefree, Opt_nocase, Opt_quiet, Opt_showexec, Opt_debug,
+ Opt_immutable, Opt_dots, Opt_nodots,
Opt_charset, Opt_shortname_lower, Opt_shortname_win95,
Opt_shortname_winnt, Opt_shortname_mixed, Opt_utf8_no, Opt_utf8_yes,
Opt_uni_xl_no, Opt_uni_xl_yes, Opt_nonumtail_no, Opt_nonumtail_yes,
@@ -866,6 +863,7 @@ static match_table_t fat_tokens = {
{Opt_umask, "umask=%o"},
{Opt_dmask, "dmask=%o"},
{Opt_fmask, "fmask=%o"},
+ {Opt_allow_utime, "allow_utime=%o"},
{Opt_codepage, "codepage=%u"},
{Opt_usefree, "usefree"},
{Opt_nocase, "nocase"},
@@ -937,6 +935,7 @@ static int parse_options(char *options, int is_vfat, int silent, int *debug,
opts->fs_uid = current->uid;
opts->fs_gid = current->gid;
opts->fs_fmask = opts->fs_dmask = current->fs->umask;
+ opts->allow_utime = -1;
opts->codepage = fat_default_codepage;
opts->iocharset = fat_default_iocharset;
if (is_vfat)
@@ -1024,6 +1023,11 @@ static int parse_options(char *options, int is_vfat, int silent, int *debug,
return 0;
opts->fs_fmask = option;
break;
+ case Opt_allow_utime:
+ if (match_octal(&args[0], &option))
+ return 0;
+ opts->allow_utime = option & (S_IWGRP | S_IWOTH);
+ break;
case Opt_codepage:
if (match_int(&args[0], &option))
return 0;
@@ -1106,6 +1110,9 @@ static int parse_options(char *options, int is_vfat, int silent, int *debug,
" for FAT filesystems, filesystem will be case sensitive!\n");
}
+ /* If user doesn't specify allow_utime, it's initialized from dmask. */
+ if (opts->allow_utime == (unsigned short)-1)
+ opts->allow_utime = ~opts->fs_dmask & (S_IWGRP | S_IWOTH);
if (opts->unicode_xlate)
opts->utf8 = 0;
@@ -1208,7 +1215,7 @@ int fat_fill_super(struct super_block *sb, void *data, int silent,
*/
media = b->media;
- if (!FAT_VALID_MEDIA(media)) {
+ if (!fat_valid_media(media)) {
if (!silent)
printk(KERN_ERR "FAT: invalid media value (0x%02x)\n",
media);
@@ -1219,7 +1226,7 @@ int fat_fill_super(struct super_block *sb, void *data, int silent,
le16_to_cpu(get_unaligned((__le16 *)&b->sector_size));
if (!is_power_of_2(logical_sector_size)
|| (logical_sector_size < 512)
- || (PAGE_CACHE_SIZE < logical_sector_size)) {
+ || (logical_sector_size > 4096)) {
if (!silent)
printk(KERN_ERR "FAT: bogus logical sector size %u\n",
logical_sector_size);
@@ -1267,6 +1274,7 @@ int fat_fill_super(struct super_block *sb, void *data, int silent,
sbi->fat_length = le16_to_cpu(b->fat_length);
sbi->root_cluster = 0;
sbi->free_clusters = -1; /* Don't know yet */
+ sbi->free_clus_valid = 0;
sbi->prev_free = FAT_START_ENT;
if (!sbi->fat_length && b->fat32_length) {
@@ -1302,8 +1310,8 @@ int fat_fill_super(struct super_block *sb, void *data, int silent,
sbi->fsinfo_sector);
} else {
if (sbi->options.usefree)
- sbi->free_clusters =
- le32_to_cpu(fsinfo->free_clusters);
+ sbi->free_clus_valid = 1;
+ sbi->free_clusters = le32_to_cpu(fsinfo->free_clusters);
sbi->prev_free = le32_to_cpu(fsinfo->next_cluster);
}