[funini.com] -> [kei@sodan] -> Kernel Reading

root/fs/cifs/dir.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. renew_parental_timestamps
  2. build_path_from_dentry
  3. cifs_create
  4. cifs_mknod
  5. cifs_lookup
  6. cifs_d_revalidate
  7. cifs_ci_hash
  8. cifs_ci_compare

/*
 *   fs/cifs/dir.c
 *
 *   vfs operations that deal with dentries
 *
 *   Copyright (C) International Business Machines  Corp., 2002,2008
 *   Author(s): Steve French (sfrench@us.ibm.com)
 *
 *   This library is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published
 *   by the Free Software Foundation; either version 2.1 of the License, or
 *   (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 *   the GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/namei.h>
#include "cifsfs.h"
#include "cifspdu.h"
#include "cifsglob.h"
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifs_fs_sb.h"

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 */
        do {
                direntry->d_time = jiffies;
                direntry = direntry->d_parent;
        } while (!IS_ROOT(direntry));
}

/* Note: caller must free return buffer */
char *
build_path_from_dentry(struct dentry *direntry)
{
        struct dentry *temp;
        int namelen;
        int pplen;
        int dfsplen;
        char *full_path;
        char dirsep;
        struct cifs_sb_info *cifs_sb;

        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"));
                        return NULL;
                }
        }

        full_path = kmalloc(namelen+1, GFP_KERNEL);
        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] = dirsep;
                        strncpy(full_path + namelen + 1, temp->d_name.name,
                                temp->d_name.len);
                        cFYI(0, ("name: %s", full_path + namelen));
                }
                temp = temp->d_parent;
                if (temp == NULL) {
                        cERROR(1, ("corrupt dentry"));
                        kfree(full_path);
                        return NULL;
                }
        }
        if (namelen != pplen + dfsplen) {
                cERROR(1,
                       ("did not end path lookup where expected namelen is %d",
                        namelen));
                /* 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);
                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;
}

/* 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,
                struct nameidata *nd)
{
        int rc = -ENOENT;
        int xid;
        int create_options = CREATE_NOT_DIR;
        int oplock = 0;
        int desiredAccess = GENERIC_READ | GENERIC_WRITE;
        __u16 fileHandle;
        struct cifs_sb_info *cifs_sb;
        struct cifsTconInfo *pTcon;
        char *full_path = NULL;
        FILE_ALL_INFO *buf = NULL;
        struct inode *newinode = NULL;
        struct cifsFileInfo *pCifsFile = NULL;
        struct cifsInodeInfo *pCifsInode;
        int disposition = FILE_OVERWRITE_IF;
        bool write_only = false;

        xid = GetXid();

        cifs_sb = CIFS_SB(inode->i_sb);
        pTcon = cifs_sb->tcon;

        full_path = build_path_from_dentry(direntry);
        if (full_path == NULL) {
                FreeXid(xid);
                return -ENOMEM;
        }

        if (nd && (nd->flags & LOOKUP_OPEN)) {
                int oflags = nd->intent.open.flags;

                desiredAccess = 0;
                if (oflags & FMODE_READ)
                        desiredAccess |= GENERIC_READ;
                if (oflags & FMODE_WRITE) {
                        desiredAccess |= GENERIC_WRITE;
                        if (!(oflags & FMODE_READ))
                                write_only = true;
                }

                if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
                        disposition = FILE_CREATE;
                else if ((oflags & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC))
                        disposition = FILE_OVERWRITE_IF;
                else if ((oflags & O_CREAT) == O_CREAT)
                        disposition = FILE_OPEN_IF;
                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;

        buf = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
        if (buf == NULL) {
                kfree(full_path);
                FreeXid(xid);
                return -ENOMEM;
        }

        mode &= ~current->fs->umask;

        /*
         * if we're not using unix extensions, see if we need to set
         * ATTR_READONLY on the create call
         */
        if (!pTcon->unix_ext && (mode & S_IWUGO) == 0)
                create_options |= CREATE_OPTION_READONLY;

        if (cifs_sb->tcon->ses->capabilities & CAP_NT_SMBS)
                rc = CIFSSMBOpen(xid, pTcon, 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, pTcon, 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 ((pTcon->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 {
                                args.uid = NO_CHANGE_64;
                                args.gid = NO_CHANGE_64;
                        }
                        CIFSSMBUnixSetInfo(xid, pTcon, 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,pTcon,path,mode,-1,-1,nls);*/

                        /* Could set r/o dos attribute if mode & 0222 == 0 */
                }

                /* server might mask mode so we have to query for it */
                if (pTcon->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;
                                }
                        }
                }

                if (rc != 0) {
                        cFYI(1,
                             ("Create worked but get_inode_info failed rc = %d",
                              rc));
                } else {
                        if (pTcon->nocase)
                                direntry->d_op = &cifs_ci_dentry_ops;
                        else
                                direntry->d_op = &cifs_dentry_ops;
                        d_instantiate(direntry, newinode);
                }
                if ((nd == NULL /* nfsd case - nfs srv does not set nd */) ||
                        (!(nd->flags & LOOKUP_OPEN))) {
                        /* mknod case - do not leave file open */
                        CIFSSMBClose(xid, pTcon, fileHandle);
                } else if (newinode) {
                        pCifsFile =
                           kzalloc(sizeof(struct cifsFileInfo), GFP_KERNEL);

                        if (pCifsFile == NULL)
                                goto cifs_create_out;
                        pCifsFile->netfid = fileHandle;
                        pCifsFile->pid = current->tgid;
                        pCifsFile->pInode = newinode;
                        pCifsFile->invalidHandle = false;
                        pCifsFile->closePend     = false;
                        init_MUTEX(&pCifsFile->fh_sem);
                        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, &pTcon->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);
                }
        }
cifs_create_out:
        kfree(buf);
        kfree(full_path);
        FreeXid(xid);
        return rc;
}

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;

        if (!old_valid_dev(device_number))
                return -EINVAL;

        xid = GetXid();

        cifs_sb = CIFS_SB(inode->i_sb);
        pTcon = cifs_sb->tcon;

        full_path = build_path_from_dentry(direntry);
        if (full_path == NULL)
                rc = -ENOMEM;
        else if (pTcon->unix_ext) {
                struct cifs_unix_set_info_args args = {
                        .mode   = mode & ~current->fs->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 {
                        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) {
                        rc = cifs_get_inode_info_unix(&newinode, full_path,
                                                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) {
                        int oplock = 0;
                        u16 fileHandle;
                        FILE_ALL_INFO *buf;

                        cFYI(1, ("sfu compat create special file"));

                        buf = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
                        if (buf == NULL) {
                                kfree(full_path);
                                FreeXid(xid);
                                return -ENOMEM;
                        }

                        rc = CIFSSMBOpen(xid, pTcon, full_path,
                                         FILE_CREATE, /* fail if exists */
                                         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_MOUNT_MAP_SPECIAL_CHR);

                        /* 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,
                                   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);
                        }
                        kfree(buf);
                        /* add code here to set EAs */
                }
        }

        kfree(full_path);
        FreeXid(xid);
        return rc;
}


struct dentry *
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 */
        struct cifs_sb_info *cifs_sb;
        struct cifsTconInfo *pTcon;
        struct inode *newInode = NULL;
        char *full_path = NULL;

        xid = GetXid();

        cFYI(1, (" parent inode = 0x%p name is: %s and dentry = 0x%p",
              parent_dir_inode, direntry->d_name.name, direntry));

        /* 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);
        if (full_path == NULL) {
                FreeXid(xid);
                return ERR_PTR(-ENOMEM);
        }

        if (direntry->d_inode != NULL) {
                cFYI(1, (" non-NULL inode in lookup"));
        } else {
                cFYI(1, (" NULL inode in lookup"));
        }
        cFYI(1,
             (" Full path: %s inode = 0x%p", full_path, direntry->d_inode));

        if (pTcon->unix_ext)
                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, NULL);

        if ((rc == 0) && (newInode != NULL)) {
                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 */
                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);
        /*      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);
        FreeXid(xid);
        return ERR_PTR(rc);
}

static int
cifs_d_revalidate(struct dentry *direntry, struct nameidata *nd)
{
        int isValid = 1;

        if (direntry->d_inode) {
                if (cifs_revalidate(direntry))
                        return 0;
        } else {
                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;
                }
        }

        return isValid;
}

/* static int cifs_d_delete(struct dentry *direntry)
{
        int rc = 0;

        cFYI(1, ("In cifs d_delete, name = %s", direntry->d_name.name));

        return rc;
}     */

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;
}

struct dentry_operations cifs_ci_dentry_ops = {
        .d_revalidate = cifs_d_revalidate,
        .d_hash = cifs_ci_hash,
        .d_compare = cifs_ci_compare,
};

/* [<][>][^][v][top][bottom][index][help] */

[funini.com] -> [kei@sodan] -> Kernel Reading