/*
  *   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
  /* 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>
        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;
 
 /*
  *   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
        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;
                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);
                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,
                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,
                        }
                }
        }
+mkdir_out:     
        kfree(full_path);
        FreeXid(xid);
        return rc;