namei.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,127 行 · 第 1/2 页

C
1,127
字号
/* *  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 character and cleanup by *  				OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> */#include <linux/module.h>#include <linux/jiffies.h>#include <linux/msdos_fs.h>#include <linux/ctype.h>#include <linux/slab.h>#include <linux/smp_lock.h>#include <linux/buffer_head.h>#include <linux/namei.h>static int vfat_hashi(struct dentry *parent, struct qstr *qstr);static int vfat_hash(struct dentry *parent, struct qstr *qstr);static int vfat_cmpi(struct dentry *dentry, struct qstr *a, struct qstr *b);static int vfat_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b);static int vfat_revalidate(struct dentry *dentry, struct nameidata *nd);static struct dentry_operations vfat_dentry_ops[4] = {	{		.d_hash		= vfat_hashi,		.d_compare	= vfat_cmpi,	},	{		.d_revalidate	= vfat_revalidate,		.d_hash		= vfat_hashi,		.d_compare	= vfat_cmpi,	},	{		.d_hash		= vfat_hash,		.d_compare	= vfat_cmp,	},	{		.d_revalidate	= vfat_revalidate,		.d_hash		= vfat_hash,		.d_compare	= vfat_cmp,	}};static int vfat_revalidate(struct dentry *dentry, struct nameidata *nd){	int ret = 1;	if (!dentry->d_inode &&	    nd && !(nd->flags & LOOKUP_CONTINUE) && (nd->flags & LOOKUP_CREATE))		/*		 * negative dentry is dropped, in order to make sure		 * to use the name which a user desires if this is		 * create path.		 */		ret = 0;	else {		spin_lock(&dentry->d_lock);		if (dentry->d_time != dentry->d_parent->d_inode->i_version)			ret = 0;		spin_unlock(&dentry->d_lock);	}	return ret;}/* returns the length of a struct qstr, ignoring trailing dots */static unsigned int vfat_striptail_len(struct qstr *qstr){	unsigned int len = qstr->len;	while (len && qstr->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. */static int vfat_hash(struct dentry *dentry, struct qstr *qstr){	qstr->hash = full_name_hash(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. */static int vfat_hashi(struct dentry *dentry, struct qstr *qstr){	struct nls_table *t = MSDOS_SB(dentry->d_inode->i_sb)->nls_io;	const unsigned char *name;	unsigned int len;	unsigned long hash;	name = qstr->name;	len = vfat_striptail_len(qstr);	hash = init_name_hash();	while (len--)		hash = partial_name_hash(nls_tolower(t, *name++), hash);	qstr->hash = end_name_hash(hash);	return 0;}/* * Case insensitive compare of two vfat names. */static int vfat_cmpi(struct dentry *dentry, struct qstr *a, struct qstr *b){	struct nls_table *t = MSDOS_SB(dentry->d_inode->i_sb)->nls_io;	unsigned int alen, blen;	/* A filename cannot end in '.' or we treat it like it has none */	alen = vfat_striptail_len(a);	blen = vfat_striptail_len(b);	if (alen == blen) {		if (nls_strnicmp(t, a->name, b->name, alen) == 0)			return 0;	}	return 1;}/* * Case sensitive compare of two vfat names. */static int vfat_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b){	unsigned int alen, blen;	/* A filename cannot end in '.' or we treat it like it has none */	alen = vfat_striptail_len(a);	blen = vfat_striptail_len(b);	if (alen == blen) {		if (strncmp(a->name, b->name, alen) == 0)			return 0;	}	return 1;}/* Characters that are undesirable in an MS-DOS file name */static wchar_t bad_chars[] = {	/*  `*'     `?'     `<'    `>'      `|'     `"'     `:'     `/' */	0x002A, 0x003F, 0x003C, 0x003E, 0x007C, 0x0022, 0x003A, 0x002F,	/*  `\' */	0x005C, 0,};#define IS_BADCHAR(uni)	(vfat_unistrchr(bad_chars, (uni)) != NULL)static wchar_t replace_chars[] = {	/*  `['     `]'    `;'     `,'     `+'      `=' */	0x005B, 0x005D, 0x003B, 0x002C, 0x002B, 0x003D, 0,};#define IS_REPLACECHAR(uni)	(vfat_unistrchr(replace_chars, (uni)) != NULL)static wchar_t skip_chars[] = {	/*  `.'     ` ' */	0x002E, 0x0020, 0,};#define IS_SKIPCHAR(uni) \	((wchar_t)(uni) == skip_chars[0] || (wchar_t)(uni) == skip_chars[1])static inline wchar_t *vfat_unistrchr(const wchar_t *s, const wchar_t c){	for(; *s != c; ++s)		if (*s == 0)			return NULL;	return (wchar_t *) s;}static inline int vfat_is_used_badchars(const wchar_t *s, int len){	int i;		for (i = 0; i < len; i++)		if (s[i] < 0x0020 || IS_BADCHAR(s[i]))			return -EINVAL;	return 0;}static int vfat_valid_longname(const unsigned char *name, unsigned int len){	if (len && name[len-1] == ' ')		return 0;	if (len >= 256)		return 0;	/* MS-DOS "device special files" */	if (len == 3 || (len > 3 && name[3] == '.')) {	/* basename == 3 */		if (!strnicmp(name, "aux", 3) ||		    !strnicmp(name, "con", 3) ||		    !strnicmp(name, "nul", 3) ||		    !strnicmp(name, "prn", 3))			return 0;	}	if (len == 4 || (len > 4 && name[4] == '.')) {	/* basename == 4 */		/* "com1", "com2", ... */		if ('1' <= name[3] && name[3] <= '9') {			if (!strnicmp(name, "com", 3) ||			    !strnicmp(name, "lpt", 3))				return 0;		}	}	return 1;}static int vfat_find_form(struct inode *dir, unsigned char *name){	struct msdos_dir_entry *de;	struct buffer_head *bh = NULL;	loff_t i_pos;	int res;	res = fat_scan(dir, name, &bh, &de, &i_pos);	brelse(bh);	if (res < 0)		return -ENOENT;	return 0;}/*  * 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 {	unsigned char lower:1,		      upper:1,		      valid:1;};#define INIT_SHORTNAME_INFO(x)	do {		\	(x)->lower = 1;				\	(x)->upper = 1;				\	(x)->valid = 1;				\} while (0)static inline unsigned charshortname_info_to_lcase(struct shortname_info *base,			struct shortname_info *ext){	unsigned char lcase = 0;	if (base->valid && ext->valid) {		if (!base->upper && base->lower && (ext->lower || ext->upper))			lcase |= CASE_LOWER_BASE;		if (!ext->upper && ext->lower && (base->lower || base->upper))			lcase |= CASE_LOWER_EXT;	}	return lcase;}static inline int to_shortname_char(struct nls_table *nls,				    unsigned char *buf, int buf_size, wchar_t *src,				    struct shortname_info *info){	int len;	if (IS_SKIPCHAR(*src)) {		info->valid = 0;		return 0;	}	if (IS_REPLACECHAR(*src)) {		info->valid = 0;		buf[0] = '_';		return 1;	}		len = nls->uni2char(*src, buf, buf_size);	if (len <= 0) {		info->valid = 0;		buf[0] = '_';		len = 1;	} else if (len == 1) {		unsigned char prev = buf[0];		if (buf[0] >= 0x7F) {			info->lower = 0;			info->upper = 0;		}		buf[0] = nls_toupper(nls, buf[0]);		if (isalpha(buf[0])) {			if (buf[0] == prev)				info->lower = 0;			else				info->upper = 0;		}	} else {		info->lower = 0;		info->upper = 0;	}		return 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 */static int vfat_create_shortname(struct inode *dir, struct nls_table *nls,				 wchar_t *uname, int ulen,				 unsigned char *name_res, unsigned char *lcase){	wchar_t *ip, *ext_start, *end, *name_start;	unsigned char base[9], ext[4], buf[8], *p;	unsigned char 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;	unsigned short opt_shortname = MSDOS_SB(dir->i_sb)->options.shortname;	is_shortname = 1;	INIT_SHORTNAME_INFO(&base_info);	INIT_SHORTNAME_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;	} else if (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 (!IS_SKIPCHAR(*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).	 */	memset(name_res, ' ', MSDOS_NAME);	memcpy(name_res, base, baselen);	memcpy(name_res + 8, ext, extlen);	*lcase = 0;	if (is_shortname && base_info.valid && ext_info.valid) {		if (vfat_find_form(dir, name_res) == 0)			return -EEXIST;		if (opt_shortname & VFAT_SFN_CREATE_WIN95) {			return (base_info.upper && ext_info.upper);		} else if (opt_shortname & VFAT_SFN_CREATE_WINNT) {			if ((base_info.upper || base_info.lower)			    && (ext_info.upper || ext_info.lower)) {				*lcase = shortname_info_to_lcase(&base_info,								 &ext_info);				return 1;			}			return 0;		} else {			BUG();		}	}		if (MSDOS_SB(dir->i_sb)->options.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 & 0xffff;	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) {		sprintf(buf, "%04X", i);		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 */static intxlate_to_uni(const unsigned char *name, int len, unsigned char *outname,	     int *longlen, int *outlen, int escape, int utf8,	     struct nls_table *nls){	const unsigned char *ip;	unsigned char nc;	unsigned char *op;	unsigned int ec;	int i, k, fill;	int charlen;	if (utf8) {		int name_len = strlen(name);		*outlen = utf8_mbstowcs((wchar_t *)outname, name, PAGE_SIZE);		/*		 * We stripped '.'s before and set len appropriately,		 * but utf8_mbstowcs doesn't care about len		 */		*outlen -= (name_len-len);		op = &outname[*outlen * sizeof(wchar_t)];	} else {		if (nls) {			for (i = 0, ip = name, op = outname, *outlen = 0;			     i < len && *outlen <= 260; *outlen += 1)			{				if (escape && (*ip == ':')) {					if (i > len - 5)						return -EINVAL;					ec = 0;					for (k = 1; k < 5; k++) {						nc = ip[k];						ec <<= 4;						if (nc >= '0' && nc <= '9') {							ec |= nc - '0';							continue;						}						if (nc >= 'a' && nc <= 'f') {							ec |= nc - ('a' - 10);							continue;						}						if (nc >= 'A' && nc <= 'F') {							ec |= nc - ('A' - 10);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?