提交 c2cc49a2 编写于 作者: L Linus Torvalds

Merge git://git.kernel.org/pub/scm/linux/kernel/git/sfrench/cifs-2.6

* git://git.kernel.org/pub/scm/linux/kernel/git/sfrench/cifs-2.6:
  cifs: when ATTR_READONLY is set, only clear write bits on non-directories
  cifs: remove cifsInodeInfo->inUse counter
  cifs: convert cifs_get_inode_info and non-posix readdir to use cifs_iget
  [CIFS] update cifs version number
  cifs: add and use CIFSSMBUnixSetFileInfo for setattr calls
  cifs: make a separate function for filling out FILE_UNIX_BASIC_INFO
  cifs: rename CIFSSMBUnixSetInfo to CIFSSMBUnixSetPathInfo
  cifs: add pid of initiating process to spnego upcall info
  cifs: fix regression with O_EXCL creates and optimize away lookup
  cifs: add new cifs_iget function and convert unix codepath to use it
......@@ -5,7 +5,11 @@ client generated ones by default (mount option "serverino" turned
on by default if server supports it). Add forceuid and forcegid
mount options (so that when negotiating unix extensions specifying
which uid mounted does not immediately force the server's reported
uids to be overridden). Add support for scope moutn parm.
uids to be overridden). Add support for scope mount parm. Improve
hard link detection to use same inode for both. Do not set
read-only dos attribute on directories (for chmod) since Windows
explorer special cases this attribute bit for directories for
a different purpose.
Version 1.58
------------
......
......@@ -86,6 +86,9 @@ struct key_type cifs_spnego_key_type = {
/* strlen of ";user=" */
#define USER_KEY_LEN 6
/* strlen of ";pid=0x" */
#define PID_KEY_LEN 7
/* get a key struct with a SPNEGO security blob, suitable for session setup */
struct key *
cifs_get_spnego_key(struct cifsSesInfo *sesInfo)
......@@ -103,7 +106,8 @@ cifs_get_spnego_key(struct cifsSesInfo *sesInfo)
IP_KEY_LEN + INET6_ADDRSTRLEN +
MAX_MECH_STR_LEN +
UID_KEY_LEN + (sizeof(uid_t) * 2) +
USER_KEY_LEN + strlen(sesInfo->userName) + 1;
USER_KEY_LEN + strlen(sesInfo->userName) +
PID_KEY_LEN + (sizeof(pid_t) * 2) + 1;
spnego_key = ERR_PTR(-ENOMEM);
description = kzalloc(desc_len, GFP_KERNEL);
......@@ -141,6 +145,9 @@ cifs_get_spnego_key(struct cifsSesInfo *sesInfo)
dp = description + strlen(description);
sprintf(dp, ";user=%s", sesInfo->userName);
dp = description + strlen(description);
sprintf(dp, ";pid=0x%x", current->pid);
cFYI(1, ("key description = %s", description));
spnego_key = request_key(&cifs_spnego_key_type, description, "");
......
......@@ -327,7 +327,7 @@ static void dump_ace(struct cifs_ace *pace, char *end_of_acl)
static void parse_dacl(struct cifs_acl *pdacl, char *end_of_acl,
struct cifs_sid *pownersid, struct cifs_sid *pgrpsid,
struct inode *inode)
struct cifs_fattr *fattr)
{
int i;
int num_aces = 0;
......@@ -340,7 +340,7 @@ static void parse_dacl(struct cifs_acl *pdacl, char *end_of_acl,
if (!pdacl) {
/* no DACL in the security descriptor, set
all the permissions for user/group/other */
inode->i_mode |= S_IRWXUGO;
fattr->cf_mode |= S_IRWXUGO;
return;
}
......@@ -357,7 +357,7 @@ static void parse_dacl(struct cifs_acl *pdacl, char *end_of_acl,
/* reset rwx permissions for user/group/other.
Also, if num_aces is 0 i.e. DACL has no ACEs,
user/group/other have no permissions */
inode->i_mode &= ~(S_IRWXUGO);
fattr->cf_mode &= ~(S_IRWXUGO);
acl_base = (char *)pdacl;
acl_size = sizeof(struct cifs_acl);
......@@ -379,17 +379,17 @@ static void parse_dacl(struct cifs_acl *pdacl, char *end_of_acl,
if (compare_sids(&(ppace[i]->sid), pownersid))
access_flags_to_mode(ppace[i]->access_req,
ppace[i]->type,
&(inode->i_mode),
&fattr->cf_mode,
&user_mask);
if (compare_sids(&(ppace[i]->sid), pgrpsid))
access_flags_to_mode(ppace[i]->access_req,
ppace[i]->type,
&(inode->i_mode),
&fattr->cf_mode,
&group_mask);
if (compare_sids(&(ppace[i]->sid), &sid_everyone))
access_flags_to_mode(ppace[i]->access_req,
ppace[i]->type,
&(inode->i_mode),
&fattr->cf_mode,
&other_mask);
/* memcpy((void *)(&(cifscred->aces[i])),
......@@ -464,7 +464,7 @@ static int parse_sid(struct cifs_sid *psid, char *end_of_acl)
/* Convert CIFS ACL to POSIX form */
static int parse_sec_desc(struct cifs_ntsd *pntsd, int acl_len,
struct inode *inode)
struct cifs_fattr *fattr)
{
int rc;
struct cifs_sid *owner_sid_ptr, *group_sid_ptr;
......@@ -472,7 +472,7 @@ static int parse_sec_desc(struct cifs_ntsd *pntsd, int acl_len,
char *end_of_acl = ((char *)pntsd) + acl_len;
__u32 dacloffset;
if ((inode == NULL) || (pntsd == NULL))
if (pntsd == NULL)
return -EIO;
owner_sid_ptr = (struct cifs_sid *)((char *)pntsd +
......@@ -497,7 +497,7 @@ static int parse_sec_desc(struct cifs_ntsd *pntsd, int acl_len,
if (dacloffset)
parse_dacl(dacl_ptr, end_of_acl, owner_sid_ptr,
group_sid_ptr, inode);
group_sid_ptr, fattr);
else
cFYI(1, ("no ACL")); /* BB grant all or default perms? */
......@@ -508,7 +508,6 @@ static int parse_sec_desc(struct cifs_ntsd *pntsd, int acl_len,
memcpy((void *)(&(cifscred->gsid)), (void *)group_sid_ptr,
sizeof(struct cifs_sid)); */
return 0;
}
......@@ -671,8 +670,9 @@ static int set_cifs_acl(struct cifs_ntsd *pnntsd, __u32 acllen,
}
/* Translate the CIFS ACL (simlar to NTFS ACL) for a file into mode bits */
void acl_to_uid_mode(struct cifs_sb_info *cifs_sb, struct inode *inode,
const char *path, const __u16 *pfid)
void
cifs_acl_to_fattr(struct cifs_sb_info *cifs_sb, struct cifs_fattr *fattr,
struct inode *inode, const char *path, const __u16 *pfid)
{
struct cifs_ntsd *pntsd = NULL;
u32 acllen = 0;
......@@ -687,7 +687,7 @@ void acl_to_uid_mode(struct cifs_sb_info *cifs_sb, struct inode *inode,
/* if we can retrieve the ACL, now parse Access Control Entries, ACEs */
if (pntsd)
rc = parse_sec_desc(pntsd, acllen, inode);
rc = parse_sec_desc(pntsd, acllen, fattr);
if (rc)
cFYI(1, ("parse sec desc failed rc = %d", rc));
......
......@@ -308,7 +308,6 @@ cifs_alloc_inode(struct super_block *sb)
if (!cifs_inode)
return NULL;
cifs_inode->cifsAttrs = 0x20; /* default */
atomic_set(&cifs_inode->inUse, 0);
cifs_inode->time = 0;
cifs_inode->write_behind_rc = 0;
/* Until the file is open and we have gotten oplock
......
......@@ -24,6 +24,19 @@
#define ROOT_I 2
/*
* ino_t is 32-bits on 32-bit arch. We have to squash the 64-bit value down
* so that it will fit.
*/
static inline ino_t
cifs_uniqueid_to_ino_t(u64 fileid)
{
ino_t ino = (ino_t) fileid;
if (sizeof(ino_t) < sizeof(u64))
ino ^= fileid >> (sizeof(u64)-sizeof(ino_t)) * 8;
return ino;
}
extern struct file_system_type cifs_fs_type;
extern const struct address_space_operations cifs_addr_ops;
extern const struct address_space_operations cifs_addr_ops_smallbuf;
......@@ -100,5 +113,5 @@ extern long cifs_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
extern const struct export_operations cifs_export_ops;
#endif /* EXPERIMENTAL */
#define CIFS_VERSION "1.59"
#define CIFS_VERSION "1.60"
#endif /* _CIFSFS_H */
......@@ -364,13 +364,13 @@ struct cifsInodeInfo {
struct list_head openFileList;
int write_behind_rc;
__u32 cifsAttrs; /* e.g. DOS archive bit, sparse, compressed, system */
atomic_t inUse; /* num concurrent users (local openers cifs) of file*/
unsigned long time; /* jiffies of last update/check of inode */
bool clientCanCacheRead:1; /* read oplock */
bool clientCanCacheAll:1; /* read and writebehind oplock */
bool oplockPending:1;
bool delete_pending:1; /* DELETE_ON_CLOSE is set */
u64 server_eof; /* current file size on server */
u64 uniqueid; /* server inode number */
struct inode vfs_inode;
};
......@@ -472,6 +472,32 @@ struct dfs_info3_param {
char *node_name;
};
/*
* common struct for holding inode info when searching for or updating an
* inode with new info
*/
#define CIFS_FATTR_DFS_REFERRAL 0x1
#define CIFS_FATTR_DELETE_PENDING 0x2
#define CIFS_FATTR_NEED_REVAL 0x4
struct cifs_fattr {
u32 cf_flags;
u32 cf_cifsattrs;
u64 cf_uniqueid;
u64 cf_eof;
u64 cf_bytes;
uid_t cf_uid;
gid_t cf_gid;
umode_t cf_mode;
dev_t cf_rdev;
unsigned int cf_nlink;
unsigned int cf_dtype;
struct timespec cf_atime;
struct timespec cf_mtime;
struct timespec cf_ctime;
};
static inline void free_dfs_info_param(struct dfs_info3_param *param)
{
if (param) {
......
......@@ -2328,19 +2328,7 @@ struct file_attrib_tag {
typedef struct {
__le32 NextEntryOffset;
__u32 ResumeKey; /* as with FileIndex - no need to convert */
__le64 EndOfFile;
__le64 NumOfBytes;
__le64 LastStatusChange; /*SNIA specs DCE time for the 3 time fields */
__le64 LastAccessTime;
__le64 LastModificationTime;
__le64 Uid;
__le64 Gid;
__le32 Type;
__le64 DevMajor;
__le64 DevMinor;
__le64 UniqueId;
__le64 Permissions;
__le64 Nlinks;
FILE_UNIX_BASIC_INFO basic;
char FileName[1];
} __attribute__((packed)) FILE_UNIX_INFO; /* level 0x202 */
......
......@@ -98,9 +98,13 @@ extern struct timespec cnvrtDosUnixTm(__le16 le_date, __le16 le_time,
extern int cifs_posix_open(char *full_path, struct inode **pinode,
struct super_block *sb, int mode, int oflags,
int *poplock, __u16 *pnetfid, int xid);
extern void posix_fill_in_inode(struct inode *tmp_inode,
FILE_UNIX_BASIC_INFO *pData, int isNewInode);
extern struct inode *cifs_new_inode(struct super_block *sb, __u64 *inum);
extern void cifs_unix_basic_to_fattr(struct cifs_fattr *fattr,
FILE_UNIX_BASIC_INFO *info,
struct cifs_sb_info *cifs_sb);
extern void cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr);
extern struct inode *cifs_iget(struct super_block *sb,
struct cifs_fattr *fattr);
extern int cifs_get_inode_info(struct inode **pinode,
const unsigned char *search_path,
FILE_ALL_INFO *pfile_info,
......@@ -108,8 +112,9 @@ extern int cifs_get_inode_info(struct inode **pinode,
extern int cifs_get_inode_info_unix(struct inode **pinode,
const unsigned char *search_path,
struct super_block *sb, int xid);
extern void acl_to_uid_mode(struct cifs_sb_info *cifs_sb, struct inode *inode,
const char *path, const __u16 *pfid);
extern void cifs_acl_to_fattr(struct cifs_sb_info *cifs_sb,
struct cifs_fattr *fattr, struct inode *inode,
const char *path, const __u16 *pfid);
extern int mode_to_acl(struct inode *inode, const char *path, __u64);
extern int cifs_mount(struct super_block *, struct cifs_sb_info *, char *,
......@@ -215,7 +220,11 @@ struct cifs_unix_set_info_args {
dev_t device;
};
extern int CIFSSMBUnixSetInfo(const int xid, struct cifsTconInfo *pTcon,
extern int CIFSSMBUnixSetFileInfo(const int xid, struct cifsTconInfo *tcon,
const struct cifs_unix_set_info_args *args,
u16 fid, u32 pid_of_opener);
extern int CIFSSMBUnixSetPathInfo(const int xid, struct cifsTconInfo *pTcon,
char *fileName,
const struct cifs_unix_set_info_args *args,
const struct nls_table *nls_codepage,
......
......@@ -5074,10 +5074,114 @@ CIFSSMBSetAttrLegacy(int xid, struct cifsTconInfo *tcon, char *fileName,
}
#endif /* temporarily unneeded SetAttr legacy function */
static void
cifs_fill_unix_set_info(FILE_UNIX_BASIC_INFO *data_offset,
const struct cifs_unix_set_info_args *args)
{
u64 mode = args->mode;
/*
* Samba server ignores set of file size to zero due to bugs in some
* older clients, but we should be precise - we use SetFileSize to
* set file size and do not want to truncate file size to zero
* accidently as happened on one Samba server beta by putting
* zero instead of -1 here
*/
data_offset->EndOfFile = cpu_to_le64(NO_CHANGE_64);
data_offset->NumOfBytes = cpu_to_le64(NO_CHANGE_64);
data_offset->LastStatusChange = cpu_to_le64(args->ctime);
data_offset->LastAccessTime = cpu_to_le64(args->atime);
data_offset->LastModificationTime = cpu_to_le64(args->mtime);
data_offset->Uid = cpu_to_le64(args->uid);
data_offset->Gid = cpu_to_le64(args->gid);
/* better to leave device as zero when it is */
data_offset->DevMajor = cpu_to_le64(MAJOR(args->device));
data_offset->DevMinor = cpu_to_le64(MINOR(args->device));
data_offset->Permissions = cpu_to_le64(mode);
if (S_ISREG(mode))
data_offset->Type = cpu_to_le32(UNIX_FILE);
else if (S_ISDIR(mode))
data_offset->Type = cpu_to_le32(UNIX_DIR);
else if (S_ISLNK(mode))
data_offset->Type = cpu_to_le32(UNIX_SYMLINK);
else if (S_ISCHR(mode))
data_offset->Type = cpu_to_le32(UNIX_CHARDEV);
else if (S_ISBLK(mode))
data_offset->Type = cpu_to_le32(UNIX_BLOCKDEV);
else if (S_ISFIFO(mode))
data_offset->Type = cpu_to_le32(UNIX_FIFO);
else if (S_ISSOCK(mode))
data_offset->Type = cpu_to_le32(UNIX_SOCKET);
}
int
CIFSSMBUnixSetInfo(const int xid, struct cifsTconInfo *tcon, char *fileName,
const struct cifs_unix_set_info_args *args,
const struct nls_table *nls_codepage, int remap)
CIFSSMBUnixSetFileInfo(const int xid, struct cifsTconInfo *tcon,
const struct cifs_unix_set_info_args *args,
u16 fid, u32 pid_of_opener)
{
struct smb_com_transaction2_sfi_req *pSMB = NULL;
FILE_UNIX_BASIC_INFO *data_offset;
int rc = 0;
u16 params, param_offset, offset, byte_count, count;
cFYI(1, ("Set Unix Info (via SetFileInfo)"));
rc = small_smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB);
if (rc)
return rc;
pSMB->hdr.Pid = cpu_to_le16((__u16)pid_of_opener);
pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid_of_opener >> 16));
params = 6;
pSMB->MaxSetupCount = 0;
pSMB->Reserved = 0;
pSMB->Flags = 0;
pSMB->Timeout = 0;
pSMB->Reserved2 = 0;
param_offset = offsetof(struct smb_com_transaction2_sfi_req, Fid) - 4;
offset = param_offset + params;
data_offset = (FILE_UNIX_BASIC_INFO *)
((char *)(&pSMB->hdr.Protocol) + offset);
count = sizeof(FILE_UNIX_BASIC_INFO);
pSMB->MaxParameterCount = cpu_to_le16(2);
/* BB find max SMB PDU from sess */
pSMB->MaxDataCount = cpu_to_le16(1000);
pSMB->SetupCount = 1;
pSMB->Reserved3 = 0;
pSMB->SubCommand = cpu_to_le16(TRANS2_SET_FILE_INFORMATION);
byte_count = 3 /* pad */ + params + count;
pSMB->DataCount = cpu_to_le16(count);
pSMB->ParameterCount = cpu_to_le16(params);
pSMB->TotalDataCount = pSMB->DataCount;
pSMB->TotalParameterCount = pSMB->ParameterCount;
pSMB->ParameterOffset = cpu_to_le16(param_offset);
pSMB->DataOffset = cpu_to_le16(offset);
pSMB->Fid = fid;
pSMB->InformationLevel = cpu_to_le16(SMB_SET_FILE_UNIX_BASIC);
pSMB->Reserved4 = 0;
pSMB->hdr.smb_buf_length += byte_count;
pSMB->ByteCount = cpu_to_le16(byte_count);
cifs_fill_unix_set_info(data_offset, args);
rc = SendReceiveNoRsp(xid, tcon->ses, (struct smb_hdr *) pSMB, 0);
if (rc)
cFYI(1, ("Send error in Set Time (SetFileInfo) = %d", rc));
/* Note: On -EAGAIN error only caller can retry on handle based calls
since file handle passed in no longer valid */
return rc;
}
int
CIFSSMBUnixSetPathInfo(const int xid, struct cifsTconInfo *tcon, char *fileName,
const struct cifs_unix_set_info_args *args,
const struct nls_table *nls_codepage, int remap)
{
TRANSACTION2_SPI_REQ *pSMB = NULL;
TRANSACTION2_SPI_RSP *pSMBr = NULL;
......@@ -5086,7 +5190,6 @@ CIFSSMBUnixSetInfo(const int xid, struct cifsTconInfo *tcon, char *fileName,
int bytes_returned = 0;
FILE_UNIX_BASIC_INFO *data_offset;
__u16 params, param_offset, offset, count, byte_count;
__u64 mode = args->mode;
cFYI(1, ("In SetUID/GID/Mode"));
setPermsRetry:
......@@ -5137,38 +5240,8 @@ CIFSSMBUnixSetInfo(const int xid, struct cifsTconInfo *tcon, char *fileName,
pSMB->InformationLevel = cpu_to_le16(SMB_SET_FILE_UNIX_BASIC);
pSMB->Reserved4 = 0;
pSMB->hdr.smb_buf_length += byte_count;
/* Samba server ignores set of file size to zero due to bugs in some
older clients, but we should be precise - we use SetFileSize to
set file size and do not want to truncate file size to zero
accidently as happened on one Samba server beta by putting
zero instead of -1 here */
data_offset->EndOfFile = cpu_to_le64(NO_CHANGE_64);
data_offset->NumOfBytes = cpu_to_le64(NO_CHANGE_64);
data_offset->LastStatusChange = cpu_to_le64(args->ctime);
data_offset->LastAccessTime = cpu_to_le64(args->atime);
data_offset->LastModificationTime = cpu_to_le64(args->mtime);
data_offset->Uid = cpu_to_le64(args->uid);
data_offset->Gid = cpu_to_le64(args->gid);
/* better to leave device as zero when it is */
data_offset->DevMajor = cpu_to_le64(MAJOR(args->device));
data_offset->DevMinor = cpu_to_le64(MINOR(args->device));
data_offset->Permissions = cpu_to_le64(mode);
if (S_ISREG(mode))
data_offset->Type = cpu_to_le32(UNIX_FILE);
else if (S_ISDIR(mode))
data_offset->Type = cpu_to_le32(UNIX_DIR);
else if (S_ISLNK(mode))
data_offset->Type = cpu_to_le32(UNIX_SYMLINK);
else if (S_ISCHR(mode))
data_offset->Type = cpu_to_le32(UNIX_CHARDEV);
else if (S_ISBLK(mode))
data_offset->Type = cpu_to_le32(UNIX_BLOCKDEV);
else if (S_ISFIFO(mode))
data_offset->Type = cpu_to_le32(UNIX_FIFO);
else if (S_ISSOCK(mode))
data_offset->Type = cpu_to_le32(UNIX_SOCKET);
cifs_fill_unix_set_info(data_offset, args);
pSMB->ByteCount = cpu_to_le16(byte_count);
rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB,
......
......@@ -188,6 +188,7 @@ int cifs_posix_open(char *full_path, struct inode **pinode,
FILE_UNIX_BASIC_INFO *presp_data;
__u32 posix_flags = 0;
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
struct cifs_fattr fattr;
cFYI(1, ("posix open %s", full_path));
......@@ -236,22 +237,21 @@ int cifs_posix_open(char *full_path, struct inode **pinode,
if (presp_data->Type == cpu_to_le32(-1))
goto posix_open_ret; /* open ok, caller does qpathinfo */
/* get new inode and set it up */
if (!pinode)
goto posix_open_ret; /* caller does not need info */
cifs_unix_basic_to_fattr(&fattr, presp_data, cifs_sb);
/* get new inode and set it up */
if (*pinode == NULL) {
__u64 unique_id = le64_to_cpu(presp_data->UniqueId);
*pinode = cifs_new_inode(sb, &unique_id);
*pinode = cifs_iget(sb, &fattr);
if (!*pinode) {
rc = -ENOMEM;
goto posix_open_ret;
}
} else {
cifs_fattr_to_inode(*pinode, &fattr);
}
/* else an inode was passed in. Update its info, don't create one */
/* We do not need to close the file if new_inode fails since
the caller will retry qpathinfo as long as inode is null */
if (*pinode == NULL)
goto posix_open_ret;
posix_fill_in_inode(*pinode, presp_data, 1);
cifs_fill_fileinfo(*pinode, *pnetfid, cifs_sb->tcon, write_only);
......@@ -425,9 +425,10 @@ cifs_create(struct inode *inode, struct dentry *direntry, int mode,
args.uid = NO_CHANGE_64;
args.gid = NO_CHANGE_64;
}
CIFSSMBUnixSetInfo(xid, tcon, full_path, &args,
cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
CIFSSMBUnixSetPathInfo(xid, tcon, full_path, &args,
cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags &
CIFS_MOUNT_MAP_SPECIAL_CHR);
} else {
/* BB implement mode setting via Windows security
descriptors e.g. */
......@@ -515,10 +516,10 @@ int cifs_mknod(struct inode *inode, struct dentry *direntry, int mode,
args.uid = NO_CHANGE_64;
args.gid = NO_CHANGE_64;
}
rc = CIFSSMBUnixSetInfo(xid, pTcon, full_path,
&args, cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags &
CIFS_MOUNT_MAP_SPECIAL_CHR);
rc = CIFSSMBUnixSetPathInfo(xid, pTcon, full_path, &args,
cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags &
CIFS_MOUNT_MAP_SPECIAL_CHR);
if (!rc) {
rc = cifs_get_inode_info_unix(&newinode, full_path,
......@@ -643,6 +644,15 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry,
}
}
/*
* O_EXCL: optimize away the lookup, but don't hash the dentry. Let
* the VFS handle the create.
*/
if (nd->flags & LOOKUP_EXCL) {
d_instantiate(direntry, NULL);
return 0;
}
/* can not grab the rename sem here since it would
deadlock in the cases (beginning of sys_rename itself)
in which we already have the sb rename sem */
......
......@@ -448,9 +448,9 @@ int cifs_open(struct inode *inode, struct file *file)
.mtime = NO_CHANGE_64,
.device = 0,
};
CIFSSMBUnixSetInfo(xid, tcon, full_path, &args,
cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags &
CIFSSMBUnixSetPathInfo(xid, tcon, full_path, &args,
cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags &
CIFS_MOUNT_MAP_SPECIAL_CHR);
}
}
......
此差异已折叠。
......@@ -63,374 +63,123 @@ static inline void dump_cifs_file_struct(struct file *file, char *label)
}
#endif /* DEBUG2 */
/* Returns 1 if new inode created, 2 if both dentry and inode were */
/* Might check in the future if inode number changed so we can rehash inode */
static int
construct_dentry(struct qstr *qstring, struct file *file,
struct inode **ptmp_inode, struct dentry **pnew_dentry,
__u64 *inum)
/*
* Find the dentry that matches "name". If there isn't one, create one. If it's
* a negative dentry or the uniqueid changed, then drop it and recreate it.
*/
static struct dentry *
cifs_readdir_lookup(struct dentry *parent, struct qstr *name,
struct cifs_fattr *fattr)
{
struct dentry *tmp_dentry = NULL;
struct super_block *sb = file->f_path.dentry->d_sb;
int rc = 0;
struct dentry *dentry, *alias;
struct inode *inode;
struct super_block *sb = parent->d_inode->i_sb;
cFYI(1, ("For %s", name->name));
dentry = d_lookup(parent, name);
if (dentry) {
/* FIXME: check for inode number changes? */
if (dentry->d_inode != NULL)
return dentry;
d_drop(dentry);
dput(dentry);
}
cFYI(1, ("For %s", qstring->name));
qstring->hash = full_name_hash(qstring->name, qstring->len);
tmp_dentry = d_lookup(file->f_path.dentry, qstring);
if (tmp_dentry) {
/* BB: overwrite old name? i.e. tmp_dentry->d_name and
* tmp_dentry->d_name.len??
*/
cFYI(0, ("existing dentry with inode 0x%p",
tmp_dentry->d_inode));
*ptmp_inode = tmp_dentry->d_inode;
if (*ptmp_inode == NULL) {
*ptmp_inode = cifs_new_inode(sb, inum);
if (*ptmp_inode == NULL)
return rc;
rc = 1;
}
} else {
tmp_dentry = d_alloc(file->f_path.dentry, qstring);
if (tmp_dentry == NULL) {
cERROR(1, ("Failed allocating dentry"));
*ptmp_inode = NULL;
return rc;
}
dentry = d_alloc(parent, name);
if (dentry == NULL)
return NULL;
if (CIFS_SB(sb)->tcon->nocase)
tmp_dentry->d_op = &cifs_ci_dentry_ops;
else
tmp_dentry->d_op = &cifs_dentry_ops;
inode = cifs_iget(sb, fattr);
if (!inode) {
dput(dentry);
return NULL;
}
*ptmp_inode = cifs_new_inode(sb, inum);
if (*ptmp_inode == NULL)
return rc;
rc = 2;
if (CIFS_SB(sb)->tcon->nocase)
dentry->d_op = &cifs_ci_dentry_ops;
else
dentry->d_op = &cifs_dentry_ops;
alias = d_materialise_unique(dentry, inode);
if (alias != NULL) {
dput(dentry);
if (IS_ERR(alias))
return NULL;
dentry = alias;
}
tmp_dentry->d_time = jiffies;
*pnew_dentry = tmp_dentry;
return rc;
return dentry;
}
static void fill_in_inode(struct inode *tmp_inode, int new_buf_type,
char *buf, unsigned int *pobject_type, int isNewInode)
static void
cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
{
loff_t local_size;
struct timespec local_mtime;
struct cifsInodeInfo *cifsInfo = CIFS_I(tmp_inode);
struct cifs_sb_info *cifs_sb = CIFS_SB(tmp_inode->i_sb);
__u32 attr;
__u64 allocation_size;
__u64 end_of_file;
umode_t default_mode;
/* save mtime and size */
local_mtime = tmp_inode->i_mtime;
local_size = tmp_inode->i_size;
if (new_buf_type) {
FILE_DIRECTORY_INFO *pfindData = (FILE_DIRECTORY_INFO *)buf;
attr = le32_to_cpu(pfindData->ExtFileAttributes);
allocation_size = le64_to_cpu(pfindData->AllocationSize);
end_of_file = le64_to_cpu(pfindData->EndOfFile);
tmp_inode->i_atime =
cifs_NTtimeToUnix(pfindData->LastAccessTime);
tmp_inode->i_mtime =
cifs_NTtimeToUnix(pfindData->LastWriteTime);
tmp_inode->i_ctime =
cifs_NTtimeToUnix(pfindData->ChangeTime);
} else { /* legacy, OS2 and DOS style */
int offset = cifs_sb->tcon->ses->server->timeAdj;
FIND_FILE_STANDARD_INFO *pfindData =
(FIND_FILE_STANDARD_INFO *)buf;
tmp_inode->i_mtime = cnvrtDosUnixTm(pfindData->LastWriteDate,
pfindData->LastWriteTime,
offset);
tmp_inode->i_atime = cnvrtDosUnixTm(pfindData->LastAccessDate,
pfindData->LastAccessTime,
offset);
tmp_inode->i_ctime = cnvrtDosUnixTm(pfindData->LastWriteDate,
pfindData->LastWriteTime,
offset);
attr = le16_to_cpu(pfindData->Attributes);
allocation_size = le32_to_cpu(pfindData->AllocationSize);
end_of_file = le32_to_cpu(pfindData->DataSize);
}
fattr->cf_uid = cifs_sb->mnt_uid;
fattr->cf_gid = cifs_sb->mnt_gid;
/* Linux can not store file creation time unfortunately so ignore it */
cifsInfo->cifsAttrs = attr;
#ifdef CONFIG_CIFS_EXPERIMENTAL
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_CIFS_ACL) {
/* get more accurate mode via ACL - so force inode refresh */
cifsInfo->time = 0;
} else
#endif /* CONFIG_CIFS_EXPERIMENTAL */
cifsInfo->time = jiffies;
/* treat dos attribute of read-only as read-only mode bit e.g. 555? */
/* 2767 perms - indicate mandatory locking */
/* BB fill in uid and gid here? with help from winbind?
or retrieve from NTFS stream extended attribute */
if (atomic_read(&cifsInfo->inUse) == 0) {
tmp_inode->i_uid = cifs_sb->mnt_uid;
tmp_inode->i_gid = cifs_sb->mnt_gid;
}
if (attr & ATTR_DIRECTORY)
default_mode = cifs_sb->mnt_dir_mode;
else
default_mode = cifs_sb->mnt_file_mode;
/* set initial permissions */
if ((atomic_read(&cifsInfo->inUse) == 0) ||
(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_DYNPERM) == 0)
tmp_inode->i_mode = default_mode;
else {
/* just reenable write bits if !ATTR_READONLY */
if ((tmp_inode->i_mode & S_IWUGO) == 0 &&
(attr & ATTR_READONLY) == 0)
tmp_inode->i_mode |= (S_IWUGO & default_mode);
tmp_inode->i_mode &= ~S_IFMT;
if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
fattr->cf_dtype = DT_DIR;
} else {
fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
fattr->cf_dtype = DT_REG;
}
/* clear write bits if ATTR_READONLY is set */
if (attr & ATTR_READONLY)
tmp_inode->i_mode &= ~S_IWUGO;
if (fattr->cf_cifsattrs & ATTR_READONLY)
fattr->cf_mode &= ~S_IWUGO;
/* set inode type */
if ((attr & ATTR_SYSTEM) &&
(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL)) {
if (end_of_file == 0) {
tmp_inode->i_mode |= S_IFIFO;
*pobject_type = DT_FIFO;
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL &&
fattr->cf_cifsattrs & ATTR_SYSTEM) {
if (fattr->cf_eof == 0) {
fattr->cf_mode &= ~S_IFMT;
fattr->cf_mode |= S_IFIFO;
fattr->cf_dtype = DT_FIFO;
} else {
/*
* trying to get the type can be slow, so just call
* this a regular file for now, and mark for reval
* trying to get the type and mode via SFU can be slow,
* so just call those regular files for now, and mark
* for reval
*/
tmp_inode->i_mode |= S_IFREG;
*pobject_type = DT_REG;
cifsInfo->time = 0;
}
} else {
if (attr & ATTR_DIRECTORY) {
tmp_inode->i_mode |= S_IFDIR;
*pobject_type = DT_DIR;
} else {
tmp_inode->i_mode |= S_IFREG;
*pobject_type = DT_REG;
fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
}
}
}
/* can not fill in nlink here as in qpathinfo version and Unx search */
if (atomic_read(&cifsInfo->inUse) == 0)
atomic_set(&cifsInfo->inUse, 1);
cifsInfo->server_eof = end_of_file;
spin_lock(&tmp_inode->i_lock);
if (is_size_safe_to_change(cifsInfo, end_of_file)) {
/* can not safely change the file size here if the
client is writing to it due to potential races */
i_size_write(tmp_inode, end_of_file);
/* 512 bytes (2**9) is the fake blocksize that must be used */
/* for this calculation, even though the reported blocksize is larger */
tmp_inode->i_blocks = (512 - 1 + allocation_size) >> 9;
}
spin_unlock(&tmp_inode->i_lock);
if (allocation_size < end_of_file)
cFYI(1, ("May be sparse file, allocation less than file size"));
cFYI(1, ("File Size %ld and blocks %llu",
(unsigned long)tmp_inode->i_size,
(unsigned long long)tmp_inode->i_blocks));
if (S_ISREG(tmp_inode->i_mode)) {
cFYI(1, ("File inode"));
tmp_inode->i_op = &cifs_file_inode_ops;
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_DIRECT_IO) {
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_BRL)
tmp_inode->i_fop = &cifs_file_direct_nobrl_ops;
else
tmp_inode->i_fop = &cifs_file_direct_ops;
} else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_BRL)
tmp_inode->i_fop = &cifs_file_nobrl_ops;
else
tmp_inode->i_fop = &cifs_file_ops;
if ((cifs_sb->tcon) && (cifs_sb->tcon->ses) &&
(cifs_sb->tcon->ses->server->maxBuf <
PAGE_CACHE_SIZE + MAX_CIFS_HDR_SIZE))
tmp_inode->i_data.a_ops = &cifs_addr_ops_smallbuf;
else
tmp_inode->i_data.a_ops = &cifs_addr_ops;
if (isNewInode)
return; /* No sense invalidating pages for new inode
since have not started caching readahead file
data yet */
if (timespec_equal(&tmp_inode->i_mtime, &local_mtime) &&
(local_size == tmp_inode->i_size)) {
cFYI(1, ("inode exists but unchanged"));
} else {
/* file may have changed on server */
cFYI(1, ("invalidate inode, readdir detected change"));
invalidate_remote_inode(tmp_inode);
}
} else if (S_ISDIR(tmp_inode->i_mode)) {
cFYI(1, ("Directory inode"));
tmp_inode->i_op = &cifs_dir_inode_ops;
tmp_inode->i_fop = &cifs_dir_ops;
} else if (S_ISLNK(tmp_inode->i_mode)) {
cFYI(1, ("Symbolic Link inode"));
tmp_inode->i_op = &cifs_symlink_inode_ops;
} else {
cFYI(1, ("Init special inode"));
init_special_inode(tmp_inode, tmp_inode->i_mode,
tmp_inode->i_rdev);
}
void
cifs_dir_info_to_fattr(struct cifs_fattr *fattr, FILE_DIRECTORY_INFO *info,
struct cifs_sb_info *cifs_sb)
{
memset(fattr, 0, sizeof(*fattr));
fattr->cf_cifsattrs = le32_to_cpu(info->ExtFileAttributes);
fattr->cf_eof = le64_to_cpu(info->EndOfFile);
fattr->cf_bytes = le64_to_cpu(info->AllocationSize);
fattr->cf_atime = cifs_NTtimeToUnix(info->LastAccessTime);
fattr->cf_ctime = cifs_NTtimeToUnix(info->ChangeTime);
fattr->cf_mtime = cifs_NTtimeToUnix(info->LastWriteTime);
cifs_fill_common_info(fattr, cifs_sb);
}
static void unix_fill_in_inode(struct inode *tmp_inode,
FILE_UNIX_INFO *pfindData, unsigned int *pobject_type, int isNewInode)
void
cifs_std_info_to_fattr(struct cifs_fattr *fattr, FIND_FILE_STANDARD_INFO *info,
struct cifs_sb_info *cifs_sb)
{
loff_t local_size;
struct timespec local_mtime;
struct cifsInodeInfo *cifsInfo = CIFS_I(tmp_inode);
struct cifs_sb_info *cifs_sb = CIFS_SB(tmp_inode->i_sb);
__u32 type = le32_to_cpu(pfindData->Type);
__u64 num_of_bytes = le64_to_cpu(pfindData->NumOfBytes);
__u64 end_of_file = le64_to_cpu(pfindData->EndOfFile);
cifsInfo->time = jiffies;
atomic_inc(&cifsInfo->inUse);
/* save mtime and size */
local_mtime = tmp_inode->i_mtime;
local_size = tmp_inode->i_size;
tmp_inode->i_atime =
cifs_NTtimeToUnix(pfindData->LastAccessTime);
tmp_inode->i_mtime =
cifs_NTtimeToUnix(pfindData->LastModificationTime);
tmp_inode->i_ctime =
cifs_NTtimeToUnix(pfindData->LastStatusChange);
tmp_inode->i_mode = le64_to_cpu(pfindData->Permissions);
/* since we set the inode type below we need to mask off type
to avoid strange results if bits above were corrupt */
tmp_inode->i_mode &= ~S_IFMT;
if (type == UNIX_FILE) {
*pobject_type = DT_REG;
tmp_inode->i_mode |= S_IFREG;
} else if (type == UNIX_SYMLINK) {
*pobject_type = DT_LNK;
tmp_inode->i_mode |= S_IFLNK;
} else if (type == UNIX_DIR) {
*pobject_type = DT_DIR;
tmp_inode->i_mode |= S_IFDIR;
} else if (type == UNIX_CHARDEV) {
*pobject_type = DT_CHR;
tmp_inode->i_mode |= S_IFCHR;
tmp_inode->i_rdev = MKDEV(le64_to_cpu(pfindData->DevMajor),
le64_to_cpu(pfindData->DevMinor) & MINORMASK);
} else if (type == UNIX_BLOCKDEV) {
*pobject_type = DT_BLK;
tmp_inode->i_mode |= S_IFBLK;
tmp_inode->i_rdev = MKDEV(le64_to_cpu(pfindData->DevMajor),
le64_to_cpu(pfindData->DevMinor) & MINORMASK);
} else if (type == UNIX_FIFO) {
*pobject_type = DT_FIFO;
tmp_inode->i_mode |= S_IFIFO;
} else if (type == UNIX_SOCKET) {
*pobject_type = DT_SOCK;
tmp_inode->i_mode |= S_IFSOCK;
} else {
/* safest to just call it a file */
*pobject_type = DT_REG;
tmp_inode->i_mode |= S_IFREG;
cFYI(1, ("unknown inode type %d", type));
}
int offset = cifs_sb->tcon->ses->server->timeAdj;
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_OVERR_UID)
tmp_inode->i_uid = cifs_sb->mnt_uid;
else
tmp_inode->i_uid = le64_to_cpu(pfindData->Uid);
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_OVERR_GID)
tmp_inode->i_gid = cifs_sb->mnt_gid;
else
tmp_inode->i_gid = le64_to_cpu(pfindData->Gid);
tmp_inode->i_nlink = le64_to_cpu(pfindData->Nlinks);
cifsInfo->server_eof = end_of_file;
spin_lock(&tmp_inode->i_lock);
if (is_size_safe_to_change(cifsInfo, end_of_file)) {
/* can not safely change the file size here if the
client is writing to it due to potential races */
i_size_write(tmp_inode, end_of_file);
/* 512 bytes (2**9) is the fake blocksize that must be used */
/* for this calculation, not the real blocksize */
tmp_inode->i_blocks = (512 - 1 + num_of_bytes) >> 9;
}
spin_unlock(&tmp_inode->i_lock);
memset(fattr, 0, sizeof(*fattr));
fattr->cf_atime = cnvrtDosUnixTm(info->LastAccessDate,
info->LastAccessTime, offset);
fattr->cf_ctime = cnvrtDosUnixTm(info->LastWriteDate,
info->LastWriteTime, offset);
fattr->cf_mtime = cnvrtDosUnixTm(info->LastWriteDate,
info->LastWriteTime, offset);
if (S_ISREG(tmp_inode->i_mode)) {
cFYI(1, ("File inode"));
tmp_inode->i_op = &cifs_file_inode_ops;
fattr->cf_cifsattrs = le16_to_cpu(info->Attributes);
fattr->cf_bytes = le32_to_cpu(info->AllocationSize);
fattr->cf_eof = le32_to_cpu(info->DataSize);
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_DIRECT_IO) {
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_BRL)
tmp_inode->i_fop = &cifs_file_direct_nobrl_ops;
else
tmp_inode->i_fop = &cifs_file_direct_ops;
} else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_BRL)
tmp_inode->i_fop = &cifs_file_nobrl_ops;
else
tmp_inode->i_fop = &cifs_file_ops;
if ((cifs_sb->tcon) && (cifs_sb->tcon->ses) &&
(cifs_sb->tcon->ses->server->maxBuf <
PAGE_CACHE_SIZE + MAX_CIFS_HDR_SIZE))
tmp_inode->i_data.a_ops = &cifs_addr_ops_smallbuf;
else
tmp_inode->i_data.a_ops = &cifs_addr_ops;
if (isNewInode)
return; /* No sense invalidating pages for new inode
since we have not started caching readahead
file data for it yet */
if (timespec_equal(&tmp_inode->i_mtime, &local_mtime) &&
(local_size == tmp_inode->i_size)) {
cFYI(1, ("inode exists but unchanged"));
} else {
/* file may have changed on server */
cFYI(1, ("invalidate inode, readdir detected change"));
invalidate_remote_inode(tmp_inode);
}
} else if (S_ISDIR(tmp_inode->i_mode)) {
cFYI(1, ("Directory inode"));
tmp_inode->i_op = &cifs_dir_inode_ops;
tmp_inode->i_fop = &cifs_dir_ops;
} else if (S_ISLNK(tmp_inode->i_mode)) {
cFYI(1, ("Symbolic Link inode"));
tmp_inode->i_op = &cifs_symlink_inode_ops;
/* tmp_inode->i_fop = *//* do not need to set to anything */
} else {
cFYI(1, ("Special inode"));
init_special_inode(tmp_inode, tmp_inode->i_mode,
tmp_inode->i_rdev);
}
cifs_fill_common_info(fattr, cifs_sb);
}
/* BB eventually need to add the following helper function to
......@@ -872,7 +621,7 @@ static int cifs_get_name_from_search_buf(struct qstr *pqst,
len = strnlen(filename, PATH_MAX);
}
*pinum = le64_to_cpu(pFindData->UniqueId);
*pinum = le64_to_cpu(pFindData->basic.UniqueId);
} else if (level == SMB_FIND_FILE_DIRECTORY_INFO) {
FILE_DIRECTORY_INFO *pFindData =
(FILE_DIRECTORY_INFO *)current_entry;
......@@ -932,11 +681,12 @@ static int cifs_filldir(char *pfindEntry, struct file *file, filldir_t filldir,
int rc = 0;
struct qstr qstring;
struct cifsFileInfo *pCifsF;
unsigned int obj_type;
__u64 inum;
u64 inum;
ino_t ino;
struct super_block *sb;
struct cifs_sb_info *cifs_sb;
struct inode *tmp_inode;
struct dentry *tmp_dentry;
struct cifs_fattr fattr;
/* get filename and len into qstring */
/* get dentry */
......@@ -954,60 +704,53 @@ static int cifs_filldir(char *pfindEntry, struct file *file, filldir_t filldir,
if (rc != 0)
return 0;
cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
sb = file->f_path.dentry->d_sb;
cifs_sb = CIFS_SB(sb);
qstring.name = scratch_buf;
rc = cifs_get_name_from_search_buf(&qstring, pfindEntry,
pCifsF->srch_inf.info_level,
pCifsF->srch_inf.unicode, cifs_sb,
max_len,
&inum /* returned */);
max_len, &inum /* returned */);
if (rc)
return rc;
/* only these two infolevels return valid inode numbers */
if (pCifsF->srch_inf.info_level == SMB_FIND_FILE_UNIX ||
pCifsF->srch_inf.info_level == SMB_FIND_FILE_ID_FULL_DIR_INFO)
rc = construct_dentry(&qstring, file, &tmp_inode, &tmp_dentry,
&inum);
else
rc = construct_dentry(&qstring, file, &tmp_inode, &tmp_dentry,
NULL);
if ((tmp_inode == NULL) || (tmp_dentry == NULL))
return -ENOMEM;
/* we pass in rc below, indicating whether it is a new inode,
so we can figure out whether to invalidate the inode cached
data if the file has changed */
if (pCifsF->srch_inf.info_level == SMB_FIND_FILE_UNIX)
unix_fill_in_inode(tmp_inode,
(FILE_UNIX_INFO *)pfindEntry,
&obj_type, rc);
cifs_unix_basic_to_fattr(&fattr,
&((FILE_UNIX_INFO *) pfindEntry)->basic,
cifs_sb);
else if (pCifsF->srch_inf.info_level == SMB_FIND_FILE_INFO_STANDARD)
fill_in_inode(tmp_inode, 0 /* old level 1 buffer type */,
pfindEntry, &obj_type, rc);
cifs_std_info_to_fattr(&fattr, (FIND_FILE_STANDARD_INFO *)
pfindEntry, cifs_sb);
else
fill_in_inode(tmp_inode, 1 /* NT */, pfindEntry, &obj_type, rc);
cifs_dir_info_to_fattr(&fattr, (FILE_DIRECTORY_INFO *)
pfindEntry, cifs_sb);
if (rc) /* new inode - needs to be tied to dentry */ {
d_instantiate(tmp_dentry, tmp_inode);
if (rc == 2)
d_rehash(tmp_dentry);
}
/* FIXME: make _to_fattr functions fill this out */
if (pCifsF->srch_inf.info_level == SMB_FIND_FILE_ID_FULL_DIR_INFO)
fattr.cf_uniqueid = inum;
else
fattr.cf_uniqueid = iunique(sb, ROOT_I);
ino = cifs_uniqueid_to_ino_t(fattr.cf_uniqueid);
tmp_dentry = cifs_readdir_lookup(file->f_dentry, &qstring, &fattr);
rc = filldir(direntry, qstring.name, qstring.len, file->f_pos,
tmp_inode->i_ino, obj_type);
ino, fattr.cf_dtype);
/*
* we can not return filldir errors to the caller since they are
* "normal" when the stat blocksize is too small - we return remapped
* error instead
*
* FIXME: This looks bogus. filldir returns -EOVERFLOW in the above
* case already. Why should we be clobbering other errors from it?
*/
if (rc) {
cFYI(1, ("filldir rc = %d", rc));
/* we can not return filldir errors to the caller
since they are "normal" when the stat blocksize
is too small - we return remapped error instead */
rc = -EOVERFLOW;
}
dput(tmp_dentry);
return rc;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册