Commit c48c9f7f authored by Valdis Klētnieks's avatar Valdis Klētnieks Committed by Greg Kroah-Hartman

staging: exfat: add exfat filesystem code to staging

The exfat code needs a lot of work to get it into "real" shape for
the fs/ part of the kernel, so put it into drivers/staging/ for now so
that it can be worked on by everyone in the community.

The full specification of the filesystem can be found at:
  https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specificationSigned-off-by: default avatarValdis Kletnieks <valdis.kletnieks@vt.edu>
Signed-off-by: default avatarSasha Levin <alexander.levin@microsoft.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Link: https://lore.kernel.org/r/20190828160817.6250-1-gregkh@linuxfoundation.orgSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 3982f1df
...@@ -6097,6 +6097,11 @@ F: include/trace/events/mdio.h ...@@ -6097,6 +6097,11 @@ F: include/trace/events/mdio.h
F: include/uapi/linux/mdio.h F: include/uapi/linux/mdio.h
F: include/uapi/linux/mii.h F: include/uapi/linux/mii.h
EXFAT FILE SYSTEM
M: Valdis Kletnieks <valdis.kletnieks@vt.edu>
S: Maintained
F: fs/exfat/
EXT2 FILE SYSTEM EXT2 FILE SYSTEM
M: Jan Kara <jack@suse.com> M: Jan Kara <jack@suse.com>
L: linux-ext4@vger.kernel.org L: linux-ext4@vger.kernel.org
......
...@@ -118,4 +118,6 @@ source "drivers/staging/kpc2000/Kconfig" ...@@ -118,4 +118,6 @@ source "drivers/staging/kpc2000/Kconfig"
source "drivers/staging/isdn/Kconfig" source "drivers/staging/isdn/Kconfig"
source "drivers/staging/exfat/Kconfig"
endif # STAGING endif # STAGING
...@@ -49,3 +49,4 @@ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/ ...@@ -49,3 +49,4 @@ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/
obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/ obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/
obj-$(CONFIG_KPC2000) += kpc2000/ obj-$(CONFIG_KPC2000) += kpc2000/
obj-$(CONFIG_ISDN_CAPI) += isdn/ obj-$(CONFIG_ISDN_CAPI) += isdn/
obj-$(CONFIG_EXFAT_FS) += exfat/
config EXFAT_FS
tristate "exFAT fs support"
select NLS
help
This adds support for the exFAT file system.
config EXFAT_DISCARD
bool "enable discard support"
depends on EXFAT_FS
default y
config EXFAT_DELAYED_SYNC
bool "enable delayed sync"
depends on EXFAT_FS
default n
config EXFAT_KERNEL_DEBUG
bool "enable kernel debug features via ioctl"
depends on EXFAT_FS
default n
config EXFAT_DEBUG_MSG
bool "print debug messages"
depends on EXFAT_FS
default n
config EXFAT_DEFAULT_CODEPAGE
int "Default codepage for exFAT"
default 437
depends on EXFAT_FS
help
This option should be set to the codepage of your exFAT filesystems.
config EXFAT_DEFAULT_IOCHARSET
string "Default iocharset for exFAT"
default "utf8"
depends on EXFAT_FS
help
Set this to the default input/output character set you'd like exFAT to use.
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_EXFAT_FS) += exfat.o
exfat-y := exfat_core.o \
exfat_super.o \
exfat_blkdev.o \
exfat_cache.o \
exfat_nls.o \
exfat_upcase.o
exfat_core.c - ffsReadFile - the goto err_out seem to leak a brelse().
same for ffsWriteFile.
exfat_core.c - fs_sync(sb,0) all over the place looks fishy as hell.
There's only one place that calls it with a non-zero argument.
ffsTruncateFile - if (old_size <= new_size) {
That doesn't look right. How did it ever work? Are they relying on lazy
block allocation when actual writes happen? If nothing else, it never
does the 'fid->size = new_size' and do the inode update....
ffsSetAttr() is just dangling in the breeze, not wired up at all...
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*/
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include <linux/fs.h>
#include "exfat.h"
void bdev_open(struct super_block *sb)
{
struct bd_info_t *p_bd = &(EXFAT_SB(sb)->bd_info);
if (p_bd->opened)
return;
p_bd->sector_size = bdev_logical_block_size(sb->s_bdev);
p_bd->sector_size_bits = ilog2(p_bd->sector_size);
p_bd->sector_size_mask = p_bd->sector_size - 1;
p_bd->num_sectors = i_size_read(sb->s_bdev->bd_inode) >>
p_bd->sector_size_bits;
p_bd->opened = true;
}
void bdev_close(struct super_block *sb)
{
struct bd_info_t *p_bd = &(EXFAT_SB(sb)->bd_info);
p_bd->opened = false;
}
int bdev_read(struct super_block *sb, sector_t secno, struct buffer_head **bh,
u32 num_secs, bool read)
{
struct bd_info_t *p_bd = &(EXFAT_SB(sb)->bd_info);
struct fs_info_t *p_fs = &(EXFAT_SB(sb)->fs_info);
#ifdef CONFIG_EXFAT_KERNEL_DEBUG
struct exfat_sb_info *sbi = EXFAT_SB(sb);
long flags = sbi->debug_flags;
if (flags & EXFAT_DEBUGFLAGS_ERROR_RW)
return FFS_MEDIAERR;
#endif /* CONFIG_EXFAT_KERNEL_DEBUG */
if (!p_bd->opened)
return FFS_MEDIAERR;
if (*bh)
__brelse(*bh);
if (read)
*bh = __bread(sb->s_bdev, secno,
num_secs << p_bd->sector_size_bits);
else
*bh = __getblk(sb->s_bdev, secno,
num_secs << p_bd->sector_size_bits);
if (*bh)
return 0;
WARN(!p_fs->dev_ejected,
"[EXFAT] No bh, device seems wrong or to be ejected.\n");
return FFS_MEDIAERR;
}
int bdev_write(struct super_block *sb, sector_t secno, struct buffer_head *bh,
u32 num_secs, bool sync)
{
s32 count;
struct buffer_head *bh2;
struct bd_info_t *p_bd = &(EXFAT_SB(sb)->bd_info);
struct fs_info_t *p_fs = &(EXFAT_SB(sb)->fs_info);
#ifdef CONFIG_EXFAT_KERNEL_DEBUG
struct exfat_sb_info *sbi = EXFAT_SB(sb);
long flags = sbi->debug_flags;
if (flags & EXFAT_DEBUGFLAGS_ERROR_RW)
return FFS_MEDIAERR;
#endif /* CONFIG_EXFAT_KERNEL_DEBUG */
if (!p_bd->opened)
return FFS_MEDIAERR;
if (secno == bh->b_blocknr) {
lock_buffer(bh);
set_buffer_uptodate(bh);
mark_buffer_dirty(bh);
unlock_buffer(bh);
if (sync && (sync_dirty_buffer(bh) != 0))
return FFS_MEDIAERR;
} else {
count = num_secs << p_bd->sector_size_bits;
bh2 = __getblk(sb->s_bdev, secno, count);
if (!bh2)
goto no_bh;
lock_buffer(bh2);
memcpy(bh2->b_data, bh->b_data, count);
set_buffer_uptodate(bh2);
mark_buffer_dirty(bh2);
unlock_buffer(bh2);
if (sync && (sync_dirty_buffer(bh2) != 0)) {
__brelse(bh2);
goto no_bh;
}
__brelse(bh2);
}
return 0;
no_bh:
WARN(!p_fs->dev_ejected,
"[EXFAT] No bh, device seems wrong or to be ejected.\n");
return FFS_MEDIAERR;
}
int bdev_sync(struct super_block *sb)
{
struct bd_info_t *p_bd = &(EXFAT_SB(sb)->bd_info);
#ifdef CONFIG_EXFAT_KERNEL_DEBUG
struct exfat_sb_info *sbi = EXFAT_SB(sb);
long flags = sbi->debug_flags;
if (flags & EXFAT_DEBUGFLAGS_ERROR_RW)
return FFS_MEDIAERR;
#endif /* CONFIG_EXFAT_KERNEL_DEBUG */
if (!p_bd->opened)
return FFS_MEDIAERR;
return sync_blockdev(sb->s_bdev);
}
This diff is collapsed.
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*/
#include <linux/string.h>
#include <linux/nls.h>
#include "exfat.h"
static u16 bad_dos_chars[] = {
/* + , ; = [ ] */
0x002B, 0x002C, 0x003B, 0x003D, 0x005B, 0x005D,
0xFF0B, 0xFF0C, 0xFF1B, 0xFF1D, 0xFF3B, 0xFF3D,
0
};
static u16 bad_uni_chars[] = {
/* " * / : < > ? \ | */
0x0022, 0x002A, 0x002F, 0x003A,
0x003C, 0x003E, 0x003F, 0x005C, 0x007C,
0
};
static int convert_ch_to_uni(struct nls_table *nls, u16 *uni, u8 *ch,
bool *lossy)
{
int len;
*uni = 0x0;
if (ch[0] < 0x80) {
*uni = (u16)ch[0];
return 1;
}
len = nls->char2uni(ch, NLS_MAX_CHARSET_SIZE, uni);
if (len < 0) {
/* conversion failed */
pr_info("%s: fail to use nls\n", __func__);
if (lossy)
*lossy = true;
*uni = (u16)'_';
if (!strcmp(nls->charset, "utf8"))
return 1;
else
return 2;
}
return len;
}
static int convert_uni_to_ch(struct nls_table *nls, u8 *ch, u16 uni,
bool *lossy)
{
int len;
ch[0] = 0x0;
if (uni < 0x0080) {
ch[0] = (u8)uni;
return 1;
}
len = nls->uni2char(uni, ch, NLS_MAX_CHARSET_SIZE);
if (len < 0) {
/* conversion failed */
pr_info("%s: fail to use nls\n", __func__);
if (lossy)
*lossy = true;
ch[0] = '_';
return 1;
}
return len;
}
u16 nls_upper(struct super_block *sb, u16 a)
{
struct fs_info_t *p_fs = &(EXFAT_SB(sb)->fs_info);
if (EXFAT_SB(sb)->options.casesensitive)
return a;
if (p_fs->vol_utbl && p_fs->vol_utbl[get_col_index(a)])
return p_fs->vol_utbl[get_col_index(a)][get_row_index(a)];
else
return a;
}
static u16 *nls_wstrchr(u16 *str, u16 wchar)
{
while (*str) {
if (*(str++) == wchar)
return str;
}
return NULL;
}
int nls_dosname_cmp(struct super_block *sb, u8 *a, u8 *b)
{
return strncmp(a, b, DOS_NAME_LENGTH);
}
int nls_uniname_cmp(struct super_block *sb, u16 *a, u16 *b)
{
int i;
for (i = 0; i < MAX_NAME_LENGTH; i++, a++, b++) {
if (nls_upper(sb, *a) != nls_upper(sb, *b))
return 1;
if (*a == 0x0)
return 0;
}
return 0;
}
void nls_uniname_to_dosname(struct super_block *sb,
struct dos_name_t *p_dosname,
struct uni_name_t *p_uniname, bool *p_lossy)
{
int i, j, len;
bool lossy = false;
u8 buf[MAX_CHARSET_SIZE];
u8 lower = 0, upper = 0;
u8 *dosname = p_dosname->name;
u16 *uniname = p_uniname->name;
u16 *p, *last_period;
struct nls_table *nls = EXFAT_SB(sb)->nls_disk;
for (i = 0; i < DOS_NAME_LENGTH; i++)
*(dosname + i) = ' ';
if (!nls_uniname_cmp(sb, uniname, (u16 *)UNI_CUR_DIR_NAME)) {
*(dosname) = '.';
p_dosname->name_case = 0x0;
if (p_lossy)
*p_lossy = false;
return;
}
if (!nls_uniname_cmp(sb, uniname, (u16 *)UNI_PAR_DIR_NAME)) {
*(dosname) = '.';
*(dosname + 1) = '.';
p_dosname->name_case = 0x0;
if (p_lossy)
*p_lossy = false;
return;
}
/* search for the last embedded period */
last_period = NULL;
for (p = uniname; *p; p++) {
if (*p == (u16)'.')
last_period = p;
}
i = 0;
while (i < DOS_NAME_LENGTH) {
if (i == 8) {
if (!last_period)
break;
if (uniname <= last_period) {
if (uniname < last_period)
lossy = true;
uniname = last_period + 1;
}
}
if (*uniname == (u16)'\0') {
break;
} else if (*uniname == (u16)' ') {
lossy = true;
} else if (*uniname == (u16)'.') {
if (uniname < last_period)
lossy = true;
else
i = 8;
} else if (nls_wstrchr(bad_dos_chars, *uniname)) {
lossy = true;
*(dosname + i) = '_';
i++;
} else {
len = convert_uni_to_ch(nls, buf, *uniname, &lossy);
if (len > 1) {
if ((i >= 8) && ((i + len) > DOS_NAME_LENGTH))
break;
if ((i < 8) && ((i + len) > 8)) {
i = 8;
continue;
}
lower = 0xFF;
for (j = 0; j < len; j++, i++)
*(dosname + i) = *(buf + j);
} else { /* len == 1 */
if ((*buf >= 'a') && (*buf <= 'z')) {
*(dosname + i) = *buf - ('a' - 'A');
if (i < 8)
lower |= 0x08;
else
lower |= 0x10;
} else if ((*buf >= 'A') && (*buf <= 'Z')) {
*(dosname + i) = *buf;
if (i < 8)
upper |= 0x08;
else
upper |= 0x10;
} else {
*(dosname + i) = *buf;
}
i++;
}
}
uniname++;
}
if (*dosname == 0xE5)
*dosname = 0x05;
if (*uniname != 0x0)
lossy = TRUE;
if (upper & lower)
p_dosname->name_case = 0xFF;
else
p_dosname->name_case = lower;
if (p_lossy)
*p_lossy = lossy;
}
void nls_dosname_to_uniname(struct super_block *sb,
struct uni_name_t *p_uniname,
struct dos_name_t *p_dosname)
{
int i = 0, j, n = 0;
u8 buf[DOS_NAME_LENGTH + 2];
u8 *dosname = p_dosname->name;
u16 *uniname = p_uniname->name;
struct nls_table *nls = EXFAT_SB(sb)->nls_disk;
if (*dosname == 0x05) {
*buf = 0xE5;
i++;
n++;
}
for (; i < 8; i++, n++) {
if (*(dosname + i) == ' ')
break;
if ((*(dosname + i) >= 'A') && (*(dosname + i) <= 'Z') &&
(p_dosname->name_case & 0x08))
*(buf + n) = *(dosname + i) + ('a' - 'A');
else
*(buf + n) = *(dosname + i);
}
if (*(dosname + 8) != ' ') {
*(buf + n) = '.';
n++;
}
for (i = 8; i < DOS_NAME_LENGTH; i++, n++) {
if (*(dosname + i) == ' ')
break;
if ((*(dosname + i) >= 'A') && (*(dosname + i) <= 'Z') &&
(p_dosname->name_case & 0x10))
*(buf + n) = *(dosname + i) + ('a' - 'A');
else
*(buf + n) = *(dosname + i);
}
*(buf + n) = '\0';
i = 0;
j = 0;
while (j < (MAX_NAME_LENGTH - 1)) {
if (*(buf + i) == '\0')
break;
i += convert_ch_to_uni(nls, uniname, (buf + i), NULL);
uniname++;
j++;
}
*uniname = (u16)'\0';
}
void nls_uniname_to_cstring(struct super_block *sb, u8 *p_cstring,
struct uni_name_t *p_uniname)
{
int i, j, len;
u8 buf[MAX_CHARSET_SIZE];
u16 *uniname = p_uniname->name;
struct nls_table *nls = EXFAT_SB(sb)->nls_io;
if (!nls) {
len = utf16s_to_utf8s(uniname, MAX_NAME_LENGTH,
UTF16_HOST_ENDIAN, p_cstring,
MAX_NAME_LENGTH);
p_cstring[len] = 0;
return;
}
i = 0;
while (i < (MAX_NAME_LENGTH - 1)) {
if (*uniname == (u16)'\0')
break;
len = convert_uni_to_ch(nls, buf, *uniname, NULL);
if (len > 1) {
for (j = 0; j < len; j++)
*p_cstring++ = (char)*(buf + j);
} else { /* len == 1 */
*p_cstring++ = (char)*buf;
}
uniname++;
i++;
}
*p_cstring = '\0';
}
void nls_cstring_to_uniname(struct super_block *sb,
struct uni_name_t *p_uniname, u8 *p_cstring,
bool *p_lossy)
{
int i, j;
bool lossy = false;
u8 *end_of_name;
u8 upname[MAX_NAME_LENGTH * 2];
u16 *uniname = p_uniname->name;
struct nls_table *nls = EXFAT_SB(sb)->nls_io;
/* strip all trailing spaces */
end_of_name = p_cstring + strlen(p_cstring);
while (*(--end_of_name) == ' ') {
if (end_of_name < p_cstring)
break;
}
*(++end_of_name) = '\0';
if (strcmp(p_cstring, ".") && strcmp(p_cstring, "..")) {
/* strip all trailing periods */
while (*(--end_of_name) == '.') {
if (end_of_name < p_cstring)
break;
}
*(++end_of_name) = '\0';
}
if (*p_cstring == '\0')
lossy = true;
if (!nls) {
i = utf8s_to_utf16s(p_cstring, MAX_NAME_LENGTH,
UTF16_HOST_ENDIAN, uniname,
MAX_NAME_LENGTH);
for (j = 0; j < i; j++)
SET16_A(upname + j * 2, nls_upper(sb, uniname[j]));
uniname[i] = '\0';
} else {
i = 0;
j = 0;
while (j < (MAX_NAME_LENGTH - 1)) {
if (*(p_cstring + i) == '\0')
break;
i += convert_ch_to_uni(nls, uniname,
(u8 *)(p_cstring + i), &lossy);
if ((*uniname < 0x0020) ||
nls_wstrchr(bad_uni_chars, *uniname))
lossy = true;
SET16_A(upname + j * 2, nls_upper(sb, *uniname));
uniname++;
j++;
}
if (*(p_cstring + i) != '\0')
lossy = true;
*uniname = (u16)'\0';
}
p_uniname->name_len = j;
p_uniname->name_hash = calc_checksum_2byte(upname, j << 1, 0,
CS_DEFAULT);
if (p_lossy)
*p_lossy = lossy;
}
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment