Merge commit 'v2.6.30' into for-2.6.31
[safe/jmp/linux-2.6] / fs / cifs / dir.c
index c619d45..3758965 100644 (file)
@@ -2,8 +2,8 @@
  *   fs/cifs/dir.c
  *
  *   vfs operations that deal with dentries
- * 
- *   Copyright (C) International Business Machines  Corp., 2002,2003
+ *
+ *   Copyright (C) International Business Machines  Corp., 2002,2009
  *   Author(s): Steve French (sfrench@us.ibm.com)
  *
  *   This library is free software; you can redistribute it and/or modify
 #include "cifs_debug.h"
 #include "cifs_fs_sb.h"
 
-void
+static void
 renew_parental_timestamps(struct dentry *direntry)
 {
-       /* BB check if there is a way to get the kernel to do this or if we really need this */
+       /* BB check if there is a way to get the kernel to do this or if we
+          really need this */
        do {
                direntry->d_time = jiffies;
                direntry = direntry->d_parent;
-       } while (!IS_ROOT(direntry));   
+       } while (!IS_ROOT(direntry));
 }
 
 /* Note: caller must free return buffer */
 char *
-build_path_from_dentry(struct dentry *direntry, const struct cifs_sb_info *cifs_sb)
+build_path_from_dentry(struct dentry *direntry)
 {
        struct dentry *temp;
-       int namelen = 0;
+       int namelen;
+       int pplen;
+       int dfsplen;
        char *full_path;
+       char dirsep;
+       struct cifs_sb_info *cifs_sb;
 
-       if(direntry == NULL)
+       if (direntry == NULL)
                return NULL;  /* not much we can do if dentry is freed and
                we need to reopen the file after it was closed implicitly
                when the server crashed */
 
+       cifs_sb = CIFS_SB(direntry->d_sb);
+       dirsep = CIFS_DIR_SEP(cifs_sb);
+       pplen = cifs_sb->prepathlen;
+       if (cifs_sb->tcon && (cifs_sb->tcon->Flags & SMB_SHARE_IS_IN_DFS))
+               dfsplen = strnlen(cifs_sb->tcon->treeName, MAX_TREE_SIZE + 1);
+       else
+               dfsplen = 0;
 cifs_bp_rename_retry:
+       namelen = pplen + dfsplen;
        for (temp = direntry; !IS_ROOT(temp);) {
                namelen += (1 + temp->d_name.len);
                temp = temp->d_parent;
-               if(temp == NULL) {
-                       cERROR(1,("corrupt dentry"));
+               if (temp == NULL) {
+                       cERROR(1, ("corrupt dentry"));
                        return NULL;
                }
        }
 
        full_path = kmalloc(namelen+1, GFP_KERNEL);
-       if(full_path == NULL)
+       if (full_path == NULL)
                return full_path;
        full_path[namelen] = 0; /* trailing null */
-
        for (temp = direntry; !IS_ROOT(temp);) {
                namelen -= 1 + temp->d_name.len;
                if (namelen < 0) {
                        break;
                } else {
-                       full_path[namelen] = CIFS_DIR_SEP(cifs_sb);
+                       full_path[namelen] = dirsep;
                        strncpy(full_path + namelen + 1, temp->d_name.name,
                                temp->d_name.len);
-                       cFYI(0, (" name: %s ", full_path + namelen));
+                       cFYI(0, ("name: %s", full_path + namelen));
                }
                temp = temp->d_parent;
-               if(temp == NULL) {
-                       cERROR(1,("corrupt dentry"));
+               if (temp == NULL) {
+                       cERROR(1, ("corrupt dentry"));
                        kfree(full_path);
                        return NULL;
                }
        }
-       if (namelen != 0) {
+       if (namelen != pplen + dfsplen) {
                cERROR(1,
-                      ("We did not end path lookup where we expected namelen is %d",
+                      ("did not end path lookup where expected namelen is %d",
                        namelen));
-               /* presumably this is only possible if we were racing with a rename 
+               /* presumably this is only possible if racing with a rename
                of one of the parent directories  (we can not lock the dentries
                above us to prevent this, but retrying should be harmless) */
                kfree(full_path);
-               namelen = 0;
                goto cifs_bp_rename_retry;
        }
-
+       /* DIR_SEP already set for byte  0 / vs \ but not for
+          subsequent slashes in prepath which currently must
+          be entered the right way - not sure if there is an alternative
+          since the '\' is a valid posix character so we can not switch
+          those safely to '/' if any are found in the middle of the prepath */
+       /* BB test paths to Windows with '/' in the midst of prepath */
+
+       if (dfsplen) {
+               strncpy(full_path, cifs_sb->tcon->treeName, dfsplen);
+               if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) {
+                       int i;
+                       for (i = 0; i < dfsplen; i++) {
+                               if (full_path[i] == '\\')
+                                       full_path[i] = '/';
+                       }
+               }
+       }
+       strncpy(full_path + dfsplen, CIFS_SB(direntry->d_sb)->prepath, pplen);
        return full_path;
 }
 
-/* char * build_wildcard_path_from_dentry(struct dentry *direntry)
+static void
+cifs_fill_fileinfo(struct inode *newinode, __u16 fileHandle,
+                       struct cifsTconInfo *tcon, bool write_only)
 {
-       if(full_path == NULL)
-               return full_path;
+       int oplock = 0;
+       struct cifsFileInfo *pCifsFile;
+       struct cifsInodeInfo *pCifsInode;
+
+       pCifsFile = kzalloc(sizeof(struct cifsFileInfo), GFP_KERNEL);
+
+       if (pCifsFile == NULL)
+               return;
+
+       if (oplockEnabled)
+               oplock = REQ_OPLOCK;
+
+       pCifsFile->netfid = fileHandle;
+       pCifsFile->pid = current->tgid;
+       pCifsFile->pInode = newinode;
+       pCifsFile->invalidHandle = false;
+       pCifsFile->closePend = false;
+       mutex_init(&pCifsFile->fh_mutex);
+       mutex_init(&pCifsFile->lock_mutex);
+       INIT_LIST_HEAD(&pCifsFile->llist);
+       atomic_set(&pCifsFile->wrtPending, 0);
+
+       /* set the following in open now
+                       pCifsFile->pfile = file; */
+       write_lock(&GlobalSMBSeslock);
+       list_add(&pCifsFile->tlist, &tcon->openFileList);
+       pCifsInode = CIFS_I(newinode);
+       if (pCifsInode) {
+               /* if readable file instance put first in list*/
+               if (write_only)
+                       list_add_tail(&pCifsFile->flist,
+                                     &pCifsInode->openFileList);
+               else
+                       list_add(&pCifsFile->flist, &pCifsInode->openFileList);
+
+               if ((oplock & 0xF) == OPLOCK_EXCLUSIVE) {
+                       pCifsInode->clientCanCacheAll = true;
+                       pCifsInode->clientCanCacheRead = true;
+                       cFYI(1, ("Exclusive Oplock inode %p", newinode));
+               } else if ((oplock & 0xF) == OPLOCK_READ)
+                               pCifsInode->clientCanCacheRead = true;
+       }
+       write_unlock(&GlobalSMBSeslock);
+}
+
+int cifs_posix_open(char *full_path, struct inode **pinode,
+                   struct super_block *sb, int mode, int oflags,
+                   int *poplock, __u16 *pnetfid, int xid)
+{
+       int rc;
+       __u32 oplock;
+       bool write_only = false;
+       FILE_UNIX_BASIC_INFO *presp_data;
+       __u32 posix_flags = 0;
+       struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+
+       cFYI(1, ("posix open %s", full_path));
+
+       presp_data = kzalloc(sizeof(FILE_UNIX_BASIC_INFO), GFP_KERNEL);
+       if (presp_data == NULL)
+               return -ENOMEM;
+
+/* So far cifs posix extensions can only map the following flags.
+   There are other valid fmode oflags such as FMODE_LSEEK, FMODE_PREAD, but
+   so far we do not seem to need them, and we can treat them as local only */
+       if ((oflags & (FMODE_READ | FMODE_WRITE)) ==
+               (FMODE_READ | FMODE_WRITE))
+               posix_flags = SMB_O_RDWR;
+       else if (oflags & FMODE_READ)
+               posix_flags = SMB_O_RDONLY;
+       else if (oflags & FMODE_WRITE)
+               posix_flags = SMB_O_WRONLY;
+       if (oflags & O_CREAT)
+               posix_flags |= SMB_O_CREAT;
+       if (oflags & O_EXCL)
+               posix_flags |= SMB_O_EXCL;
+       if (oflags & O_TRUNC)
+               posix_flags |= SMB_O_TRUNC;
+       if (oflags & O_APPEND)
+               posix_flags |= SMB_O_APPEND;
+       if (oflags & O_SYNC)
+               posix_flags |= SMB_O_SYNC;
+       if (oflags & O_DIRECTORY)
+               posix_flags |= SMB_O_DIRECTORY;
+       if (oflags & O_NOFOLLOW)
+               posix_flags |= SMB_O_NOFOLLOW;
+       if (oflags & O_DIRECT)
+               posix_flags |= SMB_O_DIRECT;
+
+       if (!(oflags & FMODE_READ))
+               write_only = true;
+
+       mode &= ~current_umask();
+       rc = CIFSPOSIXCreate(xid, cifs_sb->tcon, posix_flags, mode,
+                       pnetfid, presp_data, &oplock, full_path,
+                       cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
+                                       CIFS_MOUNT_MAP_SPECIAL_CHR);
+       if (rc)
+               goto posix_open_ret;
+
+       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 */
 
-       full_path[namelen] = '\\';
-       full_path[namelen+1] = '*';
-       full_path[namelen+2] = 0;
-BB remove above eight lines BB */
+       if (*pinode == NULL) {
+               __u64 unique_id = le64_to_cpu(presp_data->UniqueId);
+               *pinode = cifs_new_inode(sb, &unique_id);
+       }
+       /* 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);
+
+posix_open_ret:
+       kfree(presp_data);
+       return rc;
+}
 
-/* Inode operations in similar order to how they appear in the Linux file fs.h */
+static void setup_cifs_dentry(struct cifsTconInfo *tcon,
+                             struct dentry *direntry,
+                             struct inode *newinode)
+{
+       if (tcon->nocase)
+               direntry->d_op = &cifs_ci_dentry_ops;
+       else
+               direntry->d_op = &cifs_dentry_ops;
+       d_instantiate(direntry, newinode);
+}
+
+/* Inode operations in similar order to how they appear in Linux file fs.h */
 
 int
 cifs_create(struct inode *inode, struct dentry *direntry, int mode,
@@ -119,162 +279,198 @@ cifs_create(struct inode *inode, struct dentry *direntry, int mode,
 {
        int rc = -ENOENT;
        int xid;
+       int create_options = CREATE_NOT_DIR;
        int oplock = 0;
+       int oflags;
+       bool posix_create = false;
+       /*
+        * BB below access is probably too much for mknod to request
+        *    but we have to do query and setpathinfo so requesting
+        *    less could fail (unless we want to request getatr and setatr
+        *    permissions (only).  At least for POSIX we do not have to
+        *    request so much.
+        */
        int desiredAccess = GENERIC_READ | GENERIC_WRITE;
        __u16 fileHandle;
        struct cifs_sb_info *cifs_sb;
-       struct cifsTconInfo *pTcon;
+       struct cifsTconInfo *tcon;
        char *full_path = NULL;
-       FILE_ALL_INFO * buf = NULL;
+       FILE_ALL_INFO *buf = NULL;
        struct inode *newinode = NULL;
-       struct cifsFileInfo * pCifsFile = NULL;
-       struct cifsInodeInfo * pCifsInode;
        int disposition = FILE_OVERWRITE_IF;
-       int write_only = FALSE;
+       bool write_only = false;
 
        xid = GetXid();
 
        cifs_sb = CIFS_SB(inode->i_sb);
-       pTcon = cifs_sb->tcon;
+       tcon = cifs_sb->tcon;
 
-       down(&direntry->d_sb->s_vfs_rename_sem);
-       full_path = build_path_from_dentry(direntry, cifs_sb);
-       up(&direntry->d_sb->s_vfs_rename_sem);
-       if(full_path == NULL) {
+       full_path = build_path_from_dentry(direntry);
+       if (full_path == NULL) {
                FreeXid(xid);
                return -ENOMEM;
        }
 
-       if(nd) {
-               if ((nd->intent.open.flags & O_ACCMODE) == O_RDONLY)
-                       desiredAccess = GENERIC_READ;
-               else if ((nd->intent.open.flags & O_ACCMODE) == O_WRONLY) {
-                       desiredAccess = GENERIC_WRITE;
-                       write_only = TRUE;
-               } else if ((nd->intent.open.flags & O_ACCMODE) == O_RDWR) {
-                       /* GENERIC_ALL is too much permission to request */
-                       /* can cause unnecessary access denied on create */
-                       /* desiredAccess = GENERIC_ALL; */
-                       desiredAccess = GENERIC_READ | GENERIC_WRITE;
+       if (oplockEnabled)
+               oplock = REQ_OPLOCK;
+
+       if (nd && (nd->flags & LOOKUP_OPEN))
+               oflags = nd->intent.open.flags;
+       else
+               oflags = FMODE_READ;
+
+       if (tcon->unix_ext && (tcon->ses->capabilities & CAP_UNIX) &&
+           (CIFS_UNIX_POSIX_PATH_OPS_CAP &
+                       le64_to_cpu(tcon->fsUnixInfo.Capability))) {
+               rc = cifs_posix_open(full_path, &newinode, inode->i_sb,
+                                    mode, oflags, &oplock, &fileHandle, xid);
+               /* EIO could indicate that (posix open) operation is not
+                  supported, despite what server claimed in capability
+                  negotation.  EREMOTE indicates DFS junction, which is not
+                  handled in posix open */
+
+               if (rc == 0) {
+                       posix_create = true;
+                       if (newinode == NULL) /* query inode info */
+                               goto cifs_create_get_file_info;
+                       else /* success, no need to query */
+                               goto cifs_create_set_dentry;
+               } else if ((rc != -EIO) && (rc != -EREMOTE) &&
+                        (rc != -EOPNOTSUPP) && (rc != -EINVAL))
+                       goto cifs_create_out;
+               /* else fallthrough to retry, using older open call, this is
+                  case where server does not support this SMB level, and
+                  falsely claims capability (also get here for DFS case
+                  which should be rare for path not covered on files) */
+       }
+
+       if (nd && (nd->flags & LOOKUP_OPEN)) {
+               /* if the file is going to stay open, then we
+                  need to set the desired access properly */
+               desiredAccess = 0;
+               if (oflags & FMODE_READ)
+                       desiredAccess |= GENERIC_READ; /* is this too little? */
+               if (oflags & FMODE_WRITE) {
+                       desiredAccess |= GENERIC_WRITE;
+                       if (!(oflags & FMODE_READ))
+                               write_only = true;
                }
 
-               if((nd->intent.open.flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+               if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
                        disposition = FILE_CREATE;
-               else if((nd->intent.open.flags & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC))
+               else if ((oflags & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC))
                        disposition = FILE_OVERWRITE_IF;
-               else if((nd->intent.open.flags & O_CREAT) == O_CREAT)
+               else if ((oflags & O_CREAT) == O_CREAT)
                        disposition = FILE_OPEN_IF;
-               else {
-                       cFYI(1,("Create flag not set in create function"));
-               }
+               else
+                       cFYI(1, ("Create flag not set in create function"));
        }
 
-       /* BB add processing to set equivalent of mode - e.g. via CreateX with ACLs */
-       if (oplockEnabled)
-               oplock = REQ_OPLOCK;
+       /* BB add processing to set equivalent of mode - e.g. via CreateX with
+          ACLs */
 
-       buf = kmalloc(sizeof(FILE_ALL_INFO),GFP_KERNEL);
-       if(buf == NULL) {
+       buf = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
+       if (buf == NULL) {
                kfree(full_path);
                FreeXid(xid);
                return -ENOMEM;
        }
 
-       rc = CIFSSMBOpen(xid, pTcon, full_path, disposition,
-                        desiredAccess, CREATE_NOT_DIR,
+       /*
+        * if we're not using unix extensions, see if we need to set
+        * ATTR_READONLY on the create call
+        */
+       if (!tcon->unix_ext && (mode & S_IWUGO) == 0)
+               create_options |= CREATE_OPTION_READONLY;
+
+       if (cifs_sb->tcon->ses->capabilities & CAP_NT_SMBS)
+               rc = CIFSSMBOpen(xid, tcon, full_path, disposition,
+                        desiredAccess, create_options,
                         &fileHandle, &oplock, buf, cifs_sb->local_nls,
                         cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
+       else
+               rc = -EIO; /* no NT SMB support fall into legacy open below */
+
+       if (rc == -EIO) {
+               /* old server, retry the open legacy style */
+               rc = SMBLegacyOpen(xid, tcon, full_path, disposition,
+                       desiredAccess, create_options,
+                       &fileHandle, &oplock, buf, cifs_sb->local_nls,
+                       cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
+       }
        if (rc) {
-               cFYI(1, ("cifs_create returned 0x%x ", rc));
-       } else {
-               /* If Open reported that we actually created a file
-               then we now have to set the mode if possible */
-               if ((cifs_sb->tcon->ses->capabilities & CAP_UNIX) &&
-                       (oplock & CIFS_CREATE_ACTION))
-                       if(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) {
-                               CIFSSMBUnixSetPerms(xid, pTcon, full_path, mode,
-                                       (__u64)current->euid,
-                                       (__u64)current->egid,
-                                       0 /* dev */,
-                                       cifs_sb->local_nls, 
-                                       cifs_sb->mnt_cifs_flags & 
-                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
-                       } else {
-                               CIFSSMBUnixSetPerms(xid, pTcon, full_path, mode,
-                                       (__u64)-1,
-                                       (__u64)-1,
-                                       0 /* dev */,
-                                       cifs_sb->local_nls,
-                                       cifs_sb->mnt_cifs_flags & 
-                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
-                       }
-               else {
-                       /* BB implement mode setting via Windows security descriptors */
-                       /* eg CIFSSMBWinSetPerms(xid,pTcon,full_path,mode,-1,-1,local_nls);*/
-                       /* could set r/o dos attribute if mode & 0222 == 0 */
-               }
-
-       /* BB server might mask mode so we have to query for Unix case*/
-               if (pTcon->ses->capabilities & CAP_UNIX)
-                       rc = cifs_get_inode_info_unix(&newinode, full_path,
-                                                inode->i_sb,xid);
-               else {
-                       rc = cifs_get_inode_info(&newinode, full_path,
-                                                buf, inode->i_sb,xid);
-                       if(newinode)
-                               newinode->i_mode = mode;
-               }
+               cFYI(1, ("cifs_create returned 0x%x", rc));
+               goto cifs_create_out;
+       }
 
-               if (rc != 0) {
-                       cFYI(1,
-                            ("Create worked but get_inode_info failed rc = %d",
-                             rc));
+       /* If Open reported that we actually created a file
+          then we now have to set the mode if possible */
+       if ((tcon->unix_ext) && (oplock & CIFS_CREATE_ACTION)) {
+               struct cifs_unix_set_info_args args = {
+                               .mode   = mode,
+                               .ctime  = NO_CHANGE_64,
+                               .atime  = NO_CHANGE_64,
+                               .mtime  = NO_CHANGE_64,
+                               .device = 0,
+               };
+
+               if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) {
+                       args.uid = (__u64) current_fsuid();
+                       if (inode->i_mode & S_ISGID)
+                               args.gid = (__u64) inode->i_gid;
+                       else
+                               args.gid = (__u64) current_fsgid();
                } else {
-                       direntry->d_op = &cifs_dentry_ops;
-                       d_instantiate(direntry, newinode);
+                       args.uid = NO_CHANGE_64;
+                       args.gid = NO_CHANGE_64;
                }
-               if((nd->flags & LOOKUP_OPEN) == FALSE) {
-                       /* mknod case - do not leave file open */
-                       CIFSSMBClose(xid, pTcon, fileHandle);
-               } else if(newinode) {
-                       pCifsFile =
-                          kmalloc(sizeof (struct cifsFileInfo), GFP_KERNEL);
-                       
-                       if(pCifsFile == NULL)
-                               goto cifs_create_out;
-                       memset((char *)pCifsFile, 0,
-                              sizeof (struct cifsFileInfo));
-                       pCifsFile->netfid = fileHandle;
-                       pCifsFile->pid = current->tgid;
-                       pCifsFile->pInode = newinode;
-                       pCifsFile->invalidHandle = FALSE;
-                       pCifsFile->closePend     = FALSE;
-                       init_MUTEX(&pCifsFile->fh_sem);
-                       /* set the following in open now 
-                               pCifsFile->pfile = file; */
-                       write_lock(&GlobalSMBSeslock);
-                       list_add(&pCifsFile->tlist,&pTcon->openFileList);
-                       pCifsInode = CIFS_I(newinode);
-                       if(pCifsInode) {
-                               /* if readable file instance put first in list*/
-                               if (write_only == TRUE) {
-                                               list_add_tail(&pCifsFile->flist,
-                                               &pCifsInode->openFileList);
-                               } else {
-                                       list_add(&pCifsFile->flist,
-                                               &pCifsInode->openFileList);
-                               }
-                               if((oplock & 0xF) == OPLOCK_EXCLUSIVE) {
-                                       pCifsInode->clientCanCacheAll = TRUE;
-                                       pCifsInode->clientCanCacheRead = TRUE;
-                                       cFYI(1,("Exclusive Oplock for inode %p",
-                                               newinode));
-                               } else if((oplock & 0xF) == OPLOCK_READ)
-                                       pCifsInode->clientCanCacheRead = TRUE;
+               CIFSSMBUnixSetInfo(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. */
+               /* CIFSSMBWinSetPerms(xid,tcon,path,mode,-1,-1,nls);*/
+
+               /* Could set r/o dos attribute if mode & 0222 == 0 */
+       }
+
+cifs_create_get_file_info:
+       /* server might mask mode so we have to query for it */
+       if (tcon->unix_ext)
+               rc = cifs_get_inode_info_unix(&newinode, full_path,
+                                             inode->i_sb, xid);
+       else {
+               rc = cifs_get_inode_info(&newinode, full_path, buf,
+                                        inode->i_sb, xid, &fileHandle);
+               if (newinode) {
+                       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_DYNPERM)
+                               newinode->i_mode = mode;
+                       if ((oplock & CIFS_CREATE_ACTION) &&
+                           (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID)) {
+                               newinode->i_uid = current_fsuid();
+                               if (inode->i_mode & S_ISGID)
+                                       newinode->i_gid = inode->i_gid;
+                               else
+                                       newinode->i_gid = current_fsgid();
                        }
-                       write_unlock(&GlobalSMBSeslock);
                }
-       } 
+       }
+
+cifs_create_set_dentry:
+       if (rc == 0)
+               setup_cifs_dentry(tcon, direntry, newinode);
+       else
+               cFYI(1, ("Create worked, get_inode_info failed rc = %d", rc));
+
+       /* nfsd case - nfs srv does not set nd */
+       if ((nd == NULL) || (!(nd->flags & LOOKUP_OPEN))) {
+               /* mknod case - do not leave file open */
+               CIFSSMBClose(xid, tcon, fileHandle);
+       } else if (!(posix_create) && (newinode)) {
+                       cifs_fill_fileinfo(newinode, fileHandle,
+                                       cifs_sb->tcon, write_only);
+       }
 cifs_create_out:
        kfree(buf);
        kfree(full_path);
@@ -282,14 +478,15 @@ cifs_create_out:
        return rc;
 }
 
-int cifs_mknod(struct inode *inode, struct dentry *direntry, int mode, dev_t device_number) 
+int cifs_mknod(struct inode *inode, struct dentry *direntry, int mode,
+               dev_t device_number)
 {
        int rc = -EPERM;
        int xid;
        struct cifs_sb_info *cifs_sb;
        struct cifsTconInfo *pTcon;
        char *full_path = NULL;
-       struct inode * newinode = NULL;
+       struct inode *newinode = NULL;
 
        if (!old_valid_dev(device_number))
                return -EINVAL;
@@ -299,43 +496,49 @@ int cifs_mknod(struct inode *inode, struct dentry *direntry, int mode, dev_t dev
        cifs_sb = CIFS_SB(inode->i_sb);
        pTcon = cifs_sb->tcon;
 
-       down(&direntry->d_sb->s_vfs_rename_sem);
-       full_path = build_path_from_dentry(direntry, cifs_sb);
-       up(&direntry->d_sb->s_vfs_rename_sem);
-       if(full_path == NULL)
+       full_path = build_path_from_dentry(direntry);
+       if (full_path == NULL)
                rc = -ENOMEM;
-       else if (pTcon->ses->capabilities & CAP_UNIX) {
-               if(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) {
-                       rc = CIFSSMBUnixSetPerms(xid, pTcon, full_path,
-                               mode,(__u64)current->euid,(__u64)current->egid,
-                               device_number, cifs_sb->local_nls,
-                               cifs_sb->mnt_cifs_flags & 
-                                       CIFS_MOUNT_MAP_SPECIAL_CHR);
+       else if (pTcon->unix_ext) {
+               struct cifs_unix_set_info_args args = {
+                       .mode   = mode & ~current_umask(),
+                       .ctime  = NO_CHANGE_64,
+                       .atime  = NO_CHANGE_64,
+                       .mtime  = NO_CHANGE_64,
+                       .device = device_number,
+               };
+               if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) {
+                       args.uid = (__u64) current_fsuid();
+                       args.gid = (__u64) current_fsgid();
                } else {
-                       rc = CIFSSMBUnixSetPerms(xid, pTcon,
-                               full_path, mode, (__u64)-1, (__u64)-1,
-                               device_number, cifs_sb->local_nls,
-                               cifs_sb->mnt_cifs_flags & 
-                                       CIFS_MOUNT_MAP_SPECIAL_CHR);
+                       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);
 
-               if(!rc) {
+               if (!rc) {
                        rc = cifs_get_inode_info_unix(&newinode, full_path,
-                                               inode->i_sb,xid);
-                       direntry->d_op = &cifs_dentry_ops;
-                       if(rc == 0)
+                                               inode->i_sb, xid);
+                       if (pTcon->nocase)
+                               direntry->d_op = &cifs_ci_dentry_ops;
+                       else
+                               direntry->d_op = &cifs_dentry_ops;
+                       if (rc == 0)
                                d_instantiate(direntry, newinode);
                }
        } else {
-               if(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL) {
+               if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL) {
                        int oplock = 0;
                        u16 fileHandle;
-                       FILE_ALL_INFO * buf;
+                       FILE_ALL_INFO *buf;
 
-                       cFYI(1,("sfu compat create special file"));
+                       cFYI(1, ("sfu compat create special file"));
 
-                       buf = kmalloc(sizeof(FILE_ALL_INFO),GFP_KERNEL);
-                       if(buf == NULL) {
+                       buf = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
+                       if (buf == NULL) {
                                kfree(full_path);
                                FreeXid(xid);
                                return -ENOMEM;
@@ -343,19 +546,49 @@ int cifs_mknod(struct inode *inode, struct dentry *direntry, int mode, dev_t dev
 
                        rc = CIFSSMBOpen(xid, pTcon, full_path,
                                         FILE_CREATE, /* fail if exists */
-                                        GENERIC_WRITE /* BB would 
+                                        GENERIC_WRITE /* BB would
                                          WRITE_OWNER | WRITE_DAC be better? */,
                                         /* Create a file and set the
                                            file attribute to SYSTEM */
                                         CREATE_NOT_DIR | CREATE_OPTION_SPECIAL,
                                         &fileHandle, &oplock, buf,
                                         cifs_sb->local_nls,
-                                        cifs_sb->mnt_cifs_flags & 
+                                        cifs_sb->mnt_cifs_flags &
                                            CIFS_MOUNT_MAP_SPECIAL_CHR);
 
-                       if(!rc) {
+                       /* BB FIXME - add handling for backlevel servers
+                          which need legacy open and check for all
+                          calls to SMBOpen for fallback to SMBLeagcyOpen */
+                       if (!rc) {
                                /* BB Do not bother to decode buf since no
-                                  local inode yet to put timestamps in */
+                                  local inode yet to put timestamps in,
+                                  but we can reuse it safely */
+                               unsigned int bytes_written;
+                               struct win_dev *pdev;
+                               pdev = (struct win_dev *)buf;
+                               if (S_ISCHR(mode)) {
+                                       memcpy(pdev->type, "IntxCHR", 8);
+                                       pdev->major =
+                                             cpu_to_le64(MAJOR(device_number));
+                                       pdev->minor =
+                                             cpu_to_le64(MINOR(device_number));
+                                       rc = CIFSSMBWrite(xid, pTcon,
+                                               fileHandle,
+                                               sizeof(struct win_dev),
+                                               0, &bytes_written, (char *)pdev,
+                                               NULL, 0);
+                               } else if (S_ISBLK(mode)) {
+                                       memcpy(pdev->type, "IntxBLK", 8);
+                                       pdev->major =
+                                             cpu_to_le64(MAJOR(device_number));
+                                       pdev->minor =
+                                             cpu_to_le64(MINOR(device_number));
+                                       rc = CIFSSMBWrite(xid, pTcon,
+                                               fileHandle,
+                                               sizeof(struct win_dev),
+                                               0, &bytes_written, (char *)pdev,
+                                               NULL, 0);
+                               } /* else if(S_ISFIFO */
                                CIFSSMBClose(xid, pTcon, fileHandle);
                                d_drop(direntry);
                        }
@@ -369,70 +602,125 @@ int cifs_mknod(struct inode *inode, struct dentry *direntry, int mode, dev_t dev
        return rc;
 }
 
-
 struct dentry *
-cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, struct nameidata *nd)
+cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry,
+           struct nameidata *nd)
 {
        int xid;
        int rc = 0; /* to get around spurious gcc warning, set to zero here */
+       int oplock = 0;
+       __u16 fileHandle = 0;
+       bool posix_open = false;
        struct cifs_sb_info *cifs_sb;
        struct cifsTconInfo *pTcon;
        struct inode *newInode = NULL;
        char *full_path = NULL;
+       struct file *filp;
 
        xid = GetXid();
 
-       cFYI(1,
-            (" parent inode = 0x%p name is: %s and dentry = 0x%p",
+       cFYI(1, ("parent inode = 0x%p name is: %s and dentry = 0x%p",
              parent_dir_inode, direntry->d_name.name, direntry));
 
-       /* BB Add check of incoming data - e.g. frame not longer than maximum SMB - let server check the namelen BB */
-
        /* check whether path exists */
 
        cifs_sb = CIFS_SB(parent_dir_inode->i_sb);
        pTcon = cifs_sb->tcon;
 
+       /*
+        * Don't allow the separator character in a path component.
+        * The VFS will not allow "/", but "\" is allowed by posix.
+        */
+       if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS)) {
+               int i;
+               for (i = 0; i < direntry->d_name.len; i++)
+                       if (direntry->d_name.name[i] == '\\') {
+                               cFYI(1, ("Invalid file name"));
+                               FreeXid(xid);
+                               return ERR_PTR(-EINVAL);
+                       }
+       }
+
        /* 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 */
-       full_path = build_path_from_dentry(direntry, cifs_sb);
-       if(full_path == NULL) {
+       full_path = build_path_from_dentry(direntry);
+       if (full_path == NULL) {
                FreeXid(xid);
                return ERR_PTR(-ENOMEM);
        }
 
        if (direntry->d_inode != NULL) {
-               cFYI(1, (" non-NULL inode in lookup"));
+               cFYI(1, ("non-NULL inode in lookup"));
        } else {
-               cFYI(1, (" NULL inode in lookup"));
+               cFYI(1, ("NULL inode in lookup"));
        }
-       cFYI(1,
-            (" Full path: %s inode = 0x%p", full_path, direntry->d_inode));
-
-       if (pTcon->ses->capabilities & CAP_UNIX)
-               rc = cifs_get_inode_info_unix(&newInode, full_path,
-                                             parent_dir_inode->i_sb,xid);
-       else
+       cFYI(1, ("Full path: %s inode = 0x%p", full_path, direntry->d_inode));
+
+       /* Posix open is only called (at lookup time) for file create now.
+        * For opens (rather than creates), because we do not know if it
+        * is a file or directory yet, and current Samba no longer allows
+        * us to do posix open on dirs, we could end up wasting an open call
+        * on what turns out to be a dir. For file opens, we wait to call posix
+        * open till cifs_open.  It could be added here (lookup) in the future
+        * but the performance tradeoff of the extra network request when EISDIR
+        * or EACCES is returned would have to be weighed against the 50%
+        * reduction in network traffic in the other paths.
+        */
+       if (pTcon->unix_ext) {
+               if (!(nd->flags & (LOOKUP_PARENT | LOOKUP_DIRECTORY)) &&
+                    (nd->flags & LOOKUP_OPEN) && !pTcon->broken_posix_open &&
+                    (nd->intent.open.flags & O_CREAT)) {
+                       rc = cifs_posix_open(full_path, &newInode,
+                                       parent_dir_inode->i_sb,
+                                       nd->intent.open.create_mode,
+                                       nd->intent.open.flags, &oplock,
+                                       &fileHandle, xid);
+                       /*
+                        * The check below works around a bug in POSIX
+                        * open in samba versions 3.3.1 and earlier where
+                        * open could incorrectly fail with invalid parameter.
+                        * If either that or op not supported returned, follow
+                        * the normal lookup.
+                        */
+                       if ((rc == 0) || (rc == -ENOENT))
+                               posix_open = true;
+                       else if ((rc == -EINVAL) || (rc != -EOPNOTSUPP))
+                               pTcon->broken_posix_open = true;
+               }
+               if (!posix_open)
+                       rc = cifs_get_inode_info_unix(&newInode, full_path,
+                                               parent_dir_inode->i_sb, xid);
+       } else
                rc = cifs_get_inode_info(&newInode, full_path, NULL,
-                                        parent_dir_inode->i_sb,xid);
+                               parent_dir_inode->i_sb, xid, NULL);
 
        if ((rc == 0) && (newInode != NULL)) {
-               direntry->d_op = &cifs_dentry_ops;
+               if (pTcon->nocase)
+                       direntry->d_op = &cifs_ci_dentry_ops;
+               else
+                       direntry->d_op = &cifs_dentry_ops;
                d_add(direntry, newInode);
-
-               /* since paths are not looked up by component - the parent directories are presumed to be good here */
+               if (posix_open)
+                       filp = lookup_instantiate_filp(nd, direntry, NULL);
+               /* since paths are not looked up by component - the parent
+                  directories are presumed to be good here */
                renew_parental_timestamps(direntry);
 
        } else if (rc == -ENOENT) {
                rc = 0;
+               direntry->d_time = jiffies;
+               if (pTcon->nocase)
+                       direntry->d_op = &cifs_ci_dentry_ops;
+               else
+                       direntry->d_op = &cifs_dentry_ops;
                d_add(direntry, NULL);
-       } else {
-               cERROR(1,("Error 0x%x on cifs_get_inode_info in lookup of %s",
-                          rc,full_path));
-               /* BB special case check for Access Denied - watch security 
-               exposure of returning dir info implicitly via different rc 
-               if file exists or not but no access BB */
+       /*      if it was once a directory (but how can we tell?) we could do
+               shrink_dcache_parent(direntry); */
+       } else if (rc != -EACCES) {
+               cERROR(1, ("Unexpected lookup error %d", rc));
+               /* We special case check for Access Denied - since that
+               is a common return code */
        }
 
        kfree(full_path);
@@ -445,21 +733,19 @@ cifs_d_revalidate(struct dentry *direntry, struct nameidata *nd)
 {
        int isValid = 1;
 
-/*     lock_kernel(); *//* surely we do not want to lock the kernel for a whole network round trip which could take seconds */
-
        if (direntry->d_inode) {
-               if (cifs_revalidate(direntry)) {
-                       /* unlock_kernel(); */
+               if (cifs_revalidate(direntry))
                        return 0;
-               }
        } else {
-               cFYI(1,
-                    ("In cifs_d_revalidate with no inode but name = %s and dentry 0x%p",
-                     direntry->d_name.name, direntry));
+               cFYI(1, ("neg dentry 0x%p name = %s",
+                        direntry, direntry->d_name.name));
+               if (time_after(jiffies, direntry->d_time + HZ) ||
+                       !lookupCacheEnabled) {
+                       d_drop(direntry);
+                       isValid = 0;
+               }
        }
 
-/*    unlock_kernel(); */
-
        return isValid;
 }
 
@@ -472,8 +758,46 @@ cifs_d_revalidate(struct dentry *direntry, struct nameidata *nd)
        return rc;
 }     */
 
-struct dentry_operations cifs_dentry_ops = {
+const struct dentry_operations cifs_dentry_ops = {
+       .d_revalidate = cifs_d_revalidate,
+/* d_delete:       cifs_d_delete,      */ /* not needed except for debugging */
+};
+
+static int cifs_ci_hash(struct dentry *dentry, struct qstr *q)
+{
+       struct nls_table *codepage = CIFS_SB(dentry->d_inode->i_sb)->local_nls;
+       unsigned long hash;
+       int i;
+
+       hash = init_name_hash();
+       for (i = 0; i < q->len; i++)
+               hash = partial_name_hash(nls_tolower(codepage, q->name[i]),
+                                        hash);
+       q->hash = end_name_hash(hash);
+
+       return 0;
+}
+
+static int cifs_ci_compare(struct dentry *dentry, struct qstr *a,
+                          struct qstr *b)
+{
+       struct nls_table *codepage = CIFS_SB(dentry->d_inode->i_sb)->local_nls;
+
+       if ((a->len == b->len) &&
+           (nls_strnicmp(codepage, a->name, b->name, a->len) == 0)) {
+               /*
+                * To preserve case, don't let an existing negative dentry's
+                * case take precedence.  If a is not a negative dentry, this
+                * should have no side effects
+                */
+               memcpy((void *)a->name, b->name, a->len);
+               return 0;
+       }
+       return 1;
+}
+
+const struct dentry_operations cifs_ci_dentry_ops = {
        .d_revalidate = cifs_d_revalidate,
-/* d_delete:       cifs_d_delete,       *//* not needed except for debugging */
-       /* no need for d_hash, d_compare, d_release, d_iput ... yet. BB confirm this BB */
+       .d_hash = cifs_ci_hash,
+       .d_compare = cifs_ci_compare,
 };