[CIFS] New CIFS POSIX mkdir performance improvement
Signed-off-by: Steve French <sfrench@us.ibm.com>
diff --git a/fs/cifs/CHANGES b/fs/cifs/CHANGES
index f75bdc6..713fec6 100644
--- a/fs/cifs/CHANGES
+++ b/fs/cifs/CHANGES
@@ -12,7 +12,10 @@
on server which does not support the Unix Extensions). Remove read only dos
attribute on chmod when adding any write permission (ie on any of
user/group/other (not all of user/group/other ie 0222) when
-mounted to windows.
+mounted to windows. Add support for POSIX MkDir (slight performance
+enhancement and eliminates the network race between the mkdir and set
+path info of the mode).
+
Version 1.47
------------
diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
index 4d8948e..d619ca7 100644
--- a/fs/cifs/cifspdu.h
+++ b/fs/cifs/cifspdu.h
@@ -1388,7 +1388,7 @@
#define SMB_SET_POSIX_LOCK 0x208
#define SMB_POSIX_OPEN 0x209
#define SMB_POSIX_UNLINK 0x20a
-#define SMB_SET_FILE_UNIX_INFO2
+#define SMB_SET_FILE_UNIX_INFO2 0x20b
#define SMB_SET_FILE_BASIC_INFO2 0x3ec
#define SMB_SET_FILE_RENAME_INFORMATION 0x3f2 /* BB check if qpathinfo too */
#define SMB_FILE_ALL_INFO2 0x3fa
@@ -2109,22 +2109,40 @@
/* end of POSIX ACL definitions */
+/* POSIX Open Flags */
+#define SMB_O_RDONLY 0x1
+#define SMB_O_WRONLY 0x2
+#define SMB_O_RDWR 0x4
+#define SMB_O_CREAT 0x10
+#define SMB_O_EXCL 0x20
+#define SMB_O_TRUNC 0x40
+#define SMB_O_APPEND 0x80
+#define SMB_O_SYNC 0x100
+#define SMB_O_DIRECTORY 0x200
+#define SMB_O_NOFOLLOW 0x400
+#define SMB_O_DIRECT 0x800
+
typedef struct {
- __u32 OpenFlags; /* same as NT CreateX */
- __u32 PosixOpenFlags;
- __u32 Mode;
- __u16 Level; /* reply level requested (see QPathInfo levels) */
- __u16 Pad; /* reserved - MBZ */
+ __le32 OpenFlags; /* same as NT CreateX */
+ __le32 PosixOpenFlags;
+ __le64 Permissions;
+ __le16 Level; /* reply level requested (see QPathInfo levels) */
} __attribute__((packed)) OPEN_PSX_REQ; /* level 0x209 SetPathInfo data */
typedef struct {
- /* reply varies based on requested level */
+ __le16 OplockFlags;
+ __u16 Fid;
+ __le32 CreateAction;
+ __le16 ReturnedLevel;
+ __le16 Pad;
+ /* struct following varies based on requested level */
} __attribute__((packed)) OPEN_PSX_RSP; /* level 0x209 SetPathInfo data */
struct file_internal_info {
__u64 UniqueId; /* inode number */
} __attribute__((packed)); /* level 0x3ee */
+
struct file_mode_info {
__le32 Mode;
} __attribute__((packed)); /* level 0x3f8 */
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index 32eb1ac..5d163e2 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -1,7 +1,7 @@
/*
* fs/cifs/cifsproto.h
*
- * Copyright (c) International Business Machines Corp., 2002,2006
+ * Copyright (c) International Business Machines Corp., 2002,2007
* Author(s): Steve French (sfrench@us.ibm.com)
*
* This library is free software; you can redistribute it and/or modify
@@ -244,6 +244,11 @@
const int access_flags, const int omode,
__u16 * netfid, int *pOplock, FILE_ALL_INFO *,
const struct nls_table *nls_codepage, int remap);
+extern int CIFSPOSIXCreate(const int xid, struct cifsTconInfo *tcon,
+ u32 posix_flags, __u64 mode, __u16 * netfid,
+ FILE_UNIX_BASIC_INFO *pRetData,
+ __u32 *pOplock, const char *name,
+ const struct nls_table *nls_codepage, int remap);
extern int CIFSSMBClose(const int xid, struct cifsTconInfo *tcon,
const int smb_file_id);
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index 48fc0c2..a7d3d8e 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -1,7 +1,7 @@
/*
* fs/cifs/cifssmb.c
*
- * Copyright (C) International Business Machines Corp., 2002,2006
+ * Copyright (C) International Business Machines Corp., 2002,2007
* Author(s): Steve French (sfrench@us.ibm.com)
*
* Contains the routines for constructing the SMB PDUs themselves
@@ -24,8 +24,8 @@
/* SMB/CIFS PDU handling routines here - except for leftovers in connect.c */
/* These are mostly routines that operate on a pathname, or on a tree id */
/* (mounted volume), but there are eight handle based routines which must be */
- /* treated slightly different for reconnection purposes since we never want */
- /* to reuse a stale file handle and the caller knows the file handle */
+ /* treated slightly differently for reconnection purposes since we never */
+ /* want to reuse a stale file handle and only the caller knows the file info */
#include <linux/fs.h>
#include <linux/kernel.h>
@@ -913,6 +913,127 @@
return rc;
}
+int
+CIFSPOSIXCreate(const int xid, struct cifsTconInfo *tcon, __u32 posix_flags,
+ __u64 mode, __u16 * netfid, FILE_UNIX_BASIC_INFO *pRetData,
+ __u32 *pOplock, const char *name,
+ const struct nls_table *nls_codepage, int remap)
+{
+ TRANSACTION2_SPI_REQ *pSMB = NULL;
+ TRANSACTION2_SPI_RSP *pSMBr = NULL;
+ int name_len;
+ int rc = 0;
+ int bytes_returned = 0;
+ char *data_offset;
+ __u16 params, param_offset, offset, byte_count, count;
+ OPEN_PSX_REQ * pdata;
+ OPEN_PSX_RSP * psx_rsp;
+
+ cFYI(1, ("In POSIX Create"));
+PsxCreat:
+ rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB,
+ (void **) &pSMBr);
+ if (rc)
+ return rc;
+
+ if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) {
+ name_len =
+ cifsConvertToUCS((__le16 *) pSMB->FileName, name,
+ PATH_MAX, nls_codepage, remap);
+ name_len++; /* trailing null */
+ name_len *= 2;
+ } else { /* BB improve the check for buffer overruns BB */
+ name_len = strnlen(name, PATH_MAX);
+ name_len++; /* trailing null */
+ strncpy(pSMB->FileName, name, name_len);
+ }
+
+ params = 6 + name_len;
+ count = sizeof(OPEN_PSX_REQ);
+ pSMB->MaxParameterCount = cpu_to_le16(2);
+ pSMB->MaxDataCount = cpu_to_le16(1000); /* large enough */
+ pSMB->MaxSetupCount = 0;
+ pSMB->Reserved = 0;
+ pSMB->Flags = 0;
+ pSMB->Timeout = 0;
+ pSMB->Reserved2 = 0;
+ param_offset = offsetof(struct smb_com_transaction2_spi_req,
+ InformationLevel) - 4;
+ offset = param_offset + params;
+ data_offset = (char *) (&pSMB->hdr.Protocol) + offset;
+ pdata = (OPEN_PSX_REQ *)(((char *)&pSMB->hdr.Protocol) + offset);
+ pdata->Level = SMB_QUERY_FILE_UNIX_BASIC;
+ pdata->Permissions = cpu_to_le64(mode);
+ pdata->PosixOpenFlags = cpu_to_le32(posix_flags);
+ pdata->OpenFlags = cpu_to_le32(*pOplock);
+ pSMB->ParameterOffset = cpu_to_le16(param_offset);
+ pSMB->DataOffset = cpu_to_le16(offset);
+ pSMB->SetupCount = 1;
+ pSMB->Reserved3 = 0;
+ pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_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->InformationLevel = cpu_to_le16(SMB_POSIX_OPEN);
+ pSMB->Reserved4 = 0;
+ pSMB->hdr.smb_buf_length += byte_count;
+ pSMB->ByteCount = cpu_to_le16(byte_count);
+ rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB,
+ (struct smb_hdr *) pSMBr, &bytes_returned, 0);
+ if (rc) {
+ cFYI(1, ("Posix create returned %d", rc));
+ goto psx_create_err;
+ }
+
+ cFYI(1,("copying inode info"));
+ rc = validate_t2((struct smb_t2_rsp *)pSMBr);
+
+ if (rc || (pSMBr->ByteCount < sizeof(OPEN_PSX_RSP))) {
+ rc = -EIO; /* bad smb */
+ goto psx_create_err;
+ }
+
+ /* copy return information to pRetData */
+ psx_rsp = (OPEN_PSX_RSP *)((char *) &pSMBr->hdr.Protocol
+ + le16_to_cpu(pSMBr->t2.DataOffset));
+
+ *pOplock = le16_to_cpu(psx_rsp->OplockFlags);
+ if(netfid)
+ *netfid = psx_rsp->Fid; /* cifs fid stays in le */
+ /* Let caller know file was created so we can set the mode. */
+ /* Do we care about the CreateAction in any other cases? */
+ if(cpu_to_le32(FILE_CREATE) == psx_rsp->CreateAction)
+ *pOplock |= CIFS_CREATE_ACTION;
+ /* check to make sure response data is there */
+ if(psx_rsp->ReturnedLevel != SMB_QUERY_FILE_UNIX_BASIC)
+ pRetData->Type = -1; /* unknown */
+ else {
+ if(pSMBr->ByteCount < sizeof(OPEN_PSX_RSP)
+ + sizeof(FILE_UNIX_BASIC_INFO)) {
+ cERROR(1,("Open response data too small"));
+ pRetData->Type = -1;
+ goto psx_create_err;
+ }
+ memcpy((char *) pRetData,
+ (char *)&psx_rsp + sizeof(OPEN_PSX_RSP),
+ sizeof (FILE_UNIX_BASIC_INFO));
+ }
+
+
+psx_create_err:
+ cifs_buf_release(pSMB);
+
+ cifs_stats_inc(&tcon->num_mkdirs);
+
+ if (rc == -EAGAIN)
+ goto PsxCreat;
+
+ return rc;
+}
+
static __u16 convert_disposition(int disposition)
{
__u16 ofun = 0;
diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
index eeea337..9b7e0db 100644
--- a/fs/cifs/inode.c
+++ b/fs/cifs/inode.c
@@ -1,7 +1,7 @@
/*
* fs/cifs/inode.c
*
- * Copyright (C) International Business Machines Corp., 2002,2005
+ * Copyright (C) International Business Machines Corp., 2002,2007
* Author(s): Steve French (sfrench@us.ibm.com)
*
* This library is free software; you can redistribute it and/or modify
@@ -734,6 +734,133 @@
return rc;
}
+static void posix_fill_in_inode(struct inode *tmp_inode,
+ FILE_UNIX_BASIC_INFO *pData, int *pobject_type, int isNewInode)
+{
+ 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(pData->Type);
+ __u64 num_of_bytes = le64_to_cpu(pData->NumOfBytes);
+ __u64 end_of_file = le64_to_cpu(pData->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(le64_to_cpu(pData->LastAccessTime));
+ tmp_inode->i_mtime =
+ cifs_NTtimeToUnix(le64_to_cpu(pData->LastModificationTime));
+ tmp_inode->i_ctime =
+ cifs_NTtimeToUnix(le64_to_cpu(pData->LastStatusChange));
+
+ tmp_inode->i_mode = le64_to_cpu(pData->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(pData->DevMajor),
+ le64_to_cpu(pData->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(pData->DevMajor),
+ le64_to_cpu(pData->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));
+ }
+
+ tmp_inode->i_uid = le64_to_cpu(pData->Uid);
+ tmp_inode->i_gid = le64_to_cpu(pData->Gid);
+ tmp_inode->i_nlink = le64_to_cpu(pData->Nlinks);
+
+ 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);
+
+ 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 we
+ 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;
+/* 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);
+ }
+}
+
int cifs_mkdir(struct inode *inode, struct dentry *direntry, int mode)
{
int rc = 0;
@@ -755,6 +882,53 @@
FreeXid(xid);
return -ENOMEM;
}
+
+ if((pTcon->ses->capabilities & CAP_UNIX) &&
+ (CIFS_UNIX_POSIX_PATH_OPS_CAP &
+ le64_to_cpu(pTcon->fsUnixInfo.Capability))) {
+ u32 oplock = 0;
+ FILE_UNIX_BASIC_INFO * pInfo =
+ kzalloc(sizeof(FILE_UNIX_BASIC_INFO), GFP_KERNEL);
+ if(pInfo == NULL) {
+ rc = -ENOMEM;
+ goto mkdir_out;
+ }
+
+ rc = CIFSPOSIXCreate(xid, pTcon, SMB_O_DIRECTORY | SMB_O_CREAT,
+ mode, NULL /* netfid */, pInfo, &oplock,
+ full_path, cifs_sb->local_nls,
+ cifs_sb->mnt_cifs_flags &
+ CIFS_MOUNT_MAP_SPECIAL_CHR);
+ if (rc) {
+ cFYI(1, ("posix mkdir returned 0x%x", rc));
+ d_drop(direntry);
+ } else {
+ if (pInfo->Type == -1) /* no return info - go query */
+ goto mkdir_get_info;
+/*BB check (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID ) to see if need to set uid/gid */
+ inc_nlink(inode);
+ if (pTcon->nocase)
+ direntry->d_op = &cifs_ci_dentry_ops;
+ else
+ direntry->d_op = &cifs_dentry_ops;
+ d_instantiate(direntry, newinode);
+ if (direntry->d_inode) {
+ int obj_type;
+ direntry->d_inode->i_nlink = 2;
+ /* already checked in POSIXCreate whether
+ frame was long enough */
+ posix_fill_in_inode(direntry->d_inode,
+ pInfo, &obj_type, 1 /* NewInode */);
+ /* could double check that we actually
+ * created what we thought we did ie
+ * a directory
+ */
+ }
+ }
+ kfree(pInfo);
+ goto mkdir_out;
+ }
+
/* BB add setting the equivalent of mode via CreateX w/ACLs */
rc = CIFSSMBMkDir(xid, pTcon, full_path, cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
@@ -762,6 +936,7 @@
cFYI(1, ("cifs_mkdir returned 0x%x", rc));
d_drop(direntry);
} else {
+mkdir_get_info:
inc_nlink(inode);
if (pTcon->ses->capabilities & CAP_UNIX)
rc = cifs_get_inode_info_unix(&newinode, full_path,
@@ -775,8 +950,10 @@
else
direntry->d_op = &cifs_dentry_ops;
d_instantiate(direntry, newinode);
- if (direntry->d_inode)
- direntry->d_inode->i_nlink = 2;
+ /* setting nlink not necessary except in cases where we
+ * failed to get it from the server or was set bogus */
+ if ((direntry->d_inode) && (direntry->d_inode->i_nlink < 2))
+ direntry->d_inode->i_nlink = 2;
if (cifs_sb->tcon->ses->capabilities & CAP_UNIX)
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) {
CIFSSMBUnixSetPerms(xid, pTcon, full_path,
@@ -812,6 +989,7 @@
}
}
}
+mkdir_out:
kfree(full_path);
FreeXid(xid);
return rc;