1467 lines · 36.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* *  linux/fs/fat/dir.c * *  directory handling functions for fat-based filesystems * *  Written 1992,1993 by Werner Almesberger * *  Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu> * *  VFAT extensions by Gordon Chaffee <chaffee@plateau.cs.berkeley.edu> *  Merged with msdos fs by Henrik Storner <storner@osiris.ping.dk> *  Rewritten for constant inumbers. Plugged buffer overrun in readdir(). AV *  Short name translation 1999, 2001 by Wolfram Pienkoss <wp@bszh.de> */ #include <linux/slab.h>#include <linux/compat.h>#include <linux/filelock.h>#include <linux/hex.h>#include <linux/uaccess.h>#include <linux/iversion.h>#include "fat.h" /* * Maximum buffer size of short name. * [(MSDOS_NAME + '.') * max one char + nul] * For msdos style, ['.' (hidden) + MSDOS_NAME + '.' + nul] */#define FAT_MAX_SHORT_SIZE	((MSDOS_NAME + 1) * NLS_MAX_CHARSET_SIZE + 1)/* * Maximum buffer size of unicode chars from slots. * [(max longname slots * 13 (size in a slot) + nul) * sizeof(wchar_t)] */#define FAT_MAX_UNI_CHARS	((MSDOS_SLOTS - 1) * 13 + 1)#define FAT_MAX_UNI_SIZE	(FAT_MAX_UNI_CHARS * sizeof(wchar_t)) static inline unsigned char fat_tolower(unsigned char c){	return ((c >= 'A') && (c <= 'Z')) ? c+32 : c;} static inline loff_t fat_make_i_pos(struct super_block *sb,				    struct buffer_head *bh,				    struct msdos_dir_entry *de){	return ((loff_t)bh->b_blocknr << MSDOS_SB(sb)->dir_per_block_bits)		| (de - (struct msdos_dir_entry *)bh->b_data);} static inline void fat_dir_readahead(struct inode *dir, sector_t iblock,				     sector_t phys){	struct super_block *sb = dir->i_sb;	struct msdos_sb_info *sbi = MSDOS_SB(sb);	struct buffer_head *bh;	int sec; 	/* This is not a first sector of cluster, or sec_per_clus == 1 */	if ((iblock & (sbi->sec_per_clus - 1)) || sbi->sec_per_clus == 1)		return;	/* root dir of FAT12/FAT16 */	if (!is_fat32(sbi) && (dir->i_ino == MSDOS_ROOT_INO))		return; 	bh = sb_find_get_block(sb, phys);	if (bh == NULL || !buffer_uptodate(bh)) {		for (sec = 0; sec < sbi->sec_per_clus; sec++)			sb_breadahead(sb, phys + sec);	}	brelse(bh);} /* Returns the inode number of the directory entry at offset pos. If bh is   non-NULL, it is brelse'd before. Pos is incremented. The buffer header is   returned in bh.   AV. Most often we do it item-by-item. Makes sense to optimize.   AV. OK, there we go: if both bh and de are non-NULL we assume that we just   AV. want the next entry (took one explicit de=NULL in vfat/namei.c).   AV. It's done in fat_get_entry() (inlined), here the slow case lives.   AV. Additionally, when we return -1 (i.e. reached the end of directory)   AV. we make bh NULL. */static int fat__get_entry(struct inode *dir, loff_t *pos,			  struct buffer_head **bh, struct msdos_dir_entry **de){	struct super_block *sb = dir->i_sb;	sector_t phys, iblock;	unsigned long mapped_blocks;	int err, offset; next:	brelse(*bh);	*bh = NULL;	iblock = *pos >> sb->s_blocksize_bits;	err = fat_bmap(dir, iblock, &phys, &mapped_blocks, 0, false);	if (err || !phys)		return -1;	/* beyond EOF or error */ 	fat_dir_readahead(dir, iblock, phys); 	*bh = sb_bread(sb, phys);	if (*bh == NULL) {		fat_msg_ratelimit(sb, KERN_ERR,			"Directory bread(block %llu) failed", (llu)phys);		/* skip this block */		*pos = (iblock + 1) << sb->s_blocksize_bits;		goto next;	} 	offset = *pos & (sb->s_blocksize - 1);	*pos += sizeof(struct msdos_dir_entry);	*de = (struct msdos_dir_entry *)((*bh)->b_data + offset); 	return 0;} static inline int fat_get_entry(struct inode *dir, loff_t *pos,				struct buffer_head **bh,				struct msdos_dir_entry **de){	/* Fast stuff first */	if (*bh && *de &&	   (*de - (struct msdos_dir_entry *)(*bh)->b_data) <				MSDOS_SB(dir->i_sb)->dir_per_block - 1) {		*pos += sizeof(struct msdos_dir_entry);		(*de)++;		return 0;	}	return fat__get_entry(dir, pos, bh, de);} /* * Like fat_get_entry(), but honour the FAT end-of-directory marker: * a dirent whose first name byte is NUL terminates iteration per the * spec, which also guarantees that every following slot is zeroed. * Skip straight to the end of the directory so the next call returns * -1 from fat_bmap() without re-reading the trailing zero slots, and * so callers that persist *pos across invocations (e.g. readdir's * ctx->pos) keep reporting EOD.  Release *bh and set it to NULL to * match fat_get_entry()'s contract that *bh is NULL on the -1 return. */static int fat_get_entry_eod(struct inode *dir, loff_t *pos,			     struct buffer_head **bh,			     struct msdos_dir_entry **de){	int err = fat_get_entry(dir, pos, bh, de); 	if (err == 0 && (*de)->name[0] == 0) {		brelse(*bh);		*bh = NULL;		*pos = dir->i_size;		return -1;	}	return err;} /* * Convert Unicode 16 to UTF-8, translated Unicode, or ASCII. * If uni_xlate is enabled and we can't get a 1:1 conversion, use a * colon as an escape character since it is normally invalid on the vfat * filesystem. The following four characters are the hexadecimal digits * of Unicode value. This lets us do a full dump and restore of Unicode * filenames. We could get into some trouble with long Unicode names, * but ignore that right now. * Ahem... Stack smashing in ring 0 isn't fun. Fixed. */static int uni16_to_x8(struct super_block *sb, unsigned char *ascii,		       const wchar_t *uni, int len, struct nls_table *nls){	int uni_xlate = MSDOS_SB(sb)->options.unicode_xlate;	const wchar_t *ip;	wchar_t ec;	unsigned char *op;	int charlen; 	ip = uni;	op = ascii; 	while (*ip && ((len - NLS_MAX_CHARSET_SIZE) > 0)) {		ec = *ip++;		charlen = nls->uni2char(ec, op, NLS_MAX_CHARSET_SIZE);		if (charlen > 0) {			op += charlen;			len -= charlen;		} else {			if (uni_xlate == 1) {				*op++ = ':';				op = hex_byte_pack(op, ec >> 8);				op = hex_byte_pack(op, ec);				len -= 5;			} else {				*op++ = '?';				len--;			}		}	} 	if (unlikely(*ip)) {		fat_msg(sb, KERN_WARNING,			"filename was truncated while converting.");	} 	*op = 0;	return op - ascii;} static inline int fat_uni_to_x8(struct super_block *sb, const wchar_t *uni,				unsigned char *buf, int size){	struct msdos_sb_info *sbi = MSDOS_SB(sb);	if (sbi->options.utf8)		return utf16s_to_utf8s(uni, FAT_MAX_UNI_CHARS,				UTF16_HOST_ENDIAN, buf, size);	else		return uni16_to_x8(sb, buf, uni, size, sbi->nls_io);} static inline intfat_short2uni(struct nls_table *t, unsigned char *c, int clen, wchar_t *uni){	int charlen; 	charlen = t->char2uni(c, clen, uni);	if (charlen < 0) {		*uni = 0x003f;	/* a question mark */		charlen = 1;	}	return charlen;} static inline intfat_short2lower_uni(struct nls_table *t, unsigned char *c,		    int clen, wchar_t *uni){	int charlen;	wchar_t wc; 	charlen = t->char2uni(c, clen, &wc);	if (charlen < 0) {		*uni = 0x003f;	/* a question mark */		charlen = 1;	} else if (charlen <= 1) {		unsigned char nc = t->charset2lower[*c]; 		if (!nc)			nc = *c; 		charlen = t->char2uni(&nc, 1, uni);		if (charlen < 0) {			*uni = 0x003f;	/* a question mark */			charlen = 1;		}	} else		*uni = wc; 	return charlen;} static inline intfat_shortname2uni(struct nls_table *nls, unsigned char *buf, int buf_size,		  wchar_t *uni_buf, unsigned short opt, int lower){	int len = 0; 	if (opt & VFAT_SFN_DISPLAY_LOWER)		len =  fat_short2lower_uni(nls, buf, buf_size, uni_buf);	else if (opt & VFAT_SFN_DISPLAY_WIN95)		len = fat_short2uni(nls, buf, buf_size, uni_buf);	else if (opt & VFAT_SFN_DISPLAY_WINNT) {		if (lower)			len = fat_short2lower_uni(nls, buf, buf_size, uni_buf);		else			len = fat_short2uni(nls, buf, buf_size, uni_buf);	} else		len = fat_short2uni(nls, buf, buf_size, uni_buf); 	return len;} static inline int fat_name_match(struct msdos_sb_info *sbi,				 const unsigned char *a, int a_len,				 const unsigned char *b, int b_len){	if (a_len != b_len)		return 0; 	if (sbi->options.name_check != 's')		return !nls_strnicmp(sbi->nls_io, a, b, a_len);	else		return !memcmp(a, b, a_len);} enum { PARSE_INVALID = 1, PARSE_NOT_LONGNAME, PARSE_EOF, }; /** * fat_parse_long - Parse extended directory entry. * * @dir: Pointer to the inode that represents the directory. * @pos: On input, contains the starting position to read from. *       On output, updated with the new position. * @bh: Pointer to the buffer head that may be used for reading directory *	 entries. May be updated. * @de: On input, points to the current directory entry. *      On output, points to the next directory entry. * @unicode: Pointer to a buffer where the parsed Unicode long filename will be *	      stored. * @nr_slots: Pointer to a variable that will store the number of longname *	       slots found. * * This function returns zero on success, negative value on error, or one of * the following: * * %PARSE_INVALID - Directory entry is invalid. * %PARSE_NOT_LONGNAME - Directory entry does not contain longname. * %PARSE_EOF - Directory has no more entries. */static int fat_parse_long(struct inode *dir, loff_t *pos,			  struct buffer_head **bh, struct msdos_dir_entry **de,			  wchar_t **unicode, unsigned char *nr_slots){	struct msdos_dir_slot *ds;	unsigned char id, slot, slots, alias_checksum; 	if (!*unicode) {		*unicode = __getname();		if (!*unicode) {			brelse(*bh);			return -ENOMEM;		}	}parse_long:	ds = (struct msdos_dir_slot *)*de;	id = ds->id;	if (!(id & 0x40))		return PARSE_INVALID;	slots = id & ~0x40;	if (slots > 20 || !slots)	/* ceil(256 * 2 / 26) */		return PARSE_INVALID;	*nr_slots = slots;	alias_checksum = ds->alias_checksum; 	slot = slots;	while (1) {		int offset; 		slot--;		offset = slot * 13;		fat16_towchar(*unicode + offset, ds->name0_4, 5);		fat16_towchar(*unicode + offset + 5, ds->name5_10, 6);		fat16_towchar(*unicode + offset + 11, ds->name11_12, 2); 		if (ds->id & 0x40)			(*unicode)[offset + 13] = 0;		if (fat_get_entry_eod(dir, pos, bh, de) < 0)			return PARSE_EOF;		if (slot == 0)			break;		ds = (struct msdos_dir_slot *)*de;		if (ds->attr != ATTR_EXT)			return PARSE_NOT_LONGNAME;		if ((ds->id & ~0x40) != slot)			goto parse_long;		if (ds->alias_checksum != alias_checksum)			goto parse_long;	}	if ((*de)->name[0] == DELETED_FLAG)		return PARSE_INVALID;	if ((*de)->attr == ATTR_EXT)		goto parse_long;	if (IS_FREE((*de)->name) || ((*de)->attr & ATTR_VOLUME))		return PARSE_INVALID;	if (fat_checksum((*de)->name) != alias_checksum)		*nr_slots = 0; 	return 0;} /** * fat_parse_short - Parse MS-DOS (short) directory entry. * @sb:		superblock * @de:		directory entry to parse * @name:	FAT_MAX_SHORT_SIZE array in which to place extracted name * @dot_hidden:	Nonzero == prepend '.' to names with ATTR_HIDDEN * * Returns the number of characters extracted into 'name'. */static int fat_parse_short(struct super_block *sb,			   const struct msdos_dir_entry *de,			   unsigned char *name, int dot_hidden){	const struct msdos_sb_info *sbi = MSDOS_SB(sb);	int isvfat = sbi->options.isvfat;	int nocase = sbi->options.nocase;	unsigned short opt_shortname = sbi->options.shortname;	struct nls_table *nls_disk = sbi->nls_disk;	wchar_t uni_name[14];	unsigned char c, work[MSDOS_NAME];	unsigned char *ptname = name;	int chi, chl, i, j, k;	int dotoffset = 0;	int name_len = 0, uni_len = 0; 	if (!isvfat && dot_hidden && (de->attr & ATTR_HIDDEN)) {		*ptname++ = '.';		dotoffset = 1;	} 	memcpy(work, de->name, sizeof(work));	/* For an explanation of the special treatment of 0x05 in	 * filenames, see msdos_format_name in namei_msdos.c	 */	if (work[0] == 0x05)		work[0] = 0xE5; 	/* Filename */	for (i = 0, j = 0; i < 8;) {		c = work[i];		if (!c)			break;		chl = fat_shortname2uni(nls_disk, &work[i], 8 - i,					&uni_name[j++], opt_shortname,					de->lcase & CASE_LOWER_BASE);		if (chl <= 1) {			if (!isvfat)				ptname[i] = nocase ? c : fat_tolower(c);			i++;			if (c != ' ') {				name_len = i;				uni_len  = j;			}		} else {			uni_len = j;			if (isvfat)				i += min(chl, 8-i);			else {				for (chi = 0; chi < chl && i < 8; chi++, i++)					ptname[i] = work[i];			}			if (chl)				name_len = i;		}	} 	i = name_len;	j = uni_len;	fat_short2uni(nls_disk, ".", 1, &uni_name[j++]);	if (!isvfat)		ptname[i] = '.';	i++; 	/* Extension */	for (k = 8; k < MSDOS_NAME;) {		c = work[k];		if (!c)			break;		chl = fat_shortname2uni(nls_disk, &work[k], MSDOS_NAME - k,					&uni_name[j++], opt_shortname,					de->lcase & CASE_LOWER_EXT);		if (chl <= 1) {			k++;			if (!isvfat)				ptname[i] = nocase ? c : fat_tolower(c);			i++;			if (c != ' ') {				name_len = i;				uni_len  = j;			}		} else {			uni_len = j;			if (isvfat) {				int offset = min(chl, MSDOS_NAME-k);				k += offset;				i += offset;			} else {				for (chi = 0; chi < chl && k < MSDOS_NAME;				     chi++, i++, k++) {						ptname[i] = work[k];				}			}			if (chl)				name_len = i;		}	} 	if (name_len > 0) {		name_len += dotoffset; 		if (sbi->options.isvfat) {			uni_name[uni_len] = 0x0000;			name_len = fat_uni_to_x8(sb, uni_name, name,						 FAT_MAX_SHORT_SIZE);		}	} 	return name_len;} /* * Return values: negative -> error/not found, 0 -> found.

Lines 1–500 of 1467.