root/fs/namei.c~

/* [previous][next][first][last][top][bottom][index][help] */
/*
 *  linux/fs/namei.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>

#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <const.h>
#include <sys/stat.h>

#define ACC_MODE(x) ("\004\002\006\377"[(x)&O_ACCMODE])

/*
 * comment out this line if you want names > NAME_LEN chars to be
 * truncated. Else they will be disallowed.
 */
/* #define NO_TRUNCATE */

#define MAY_EXEC 1
#define MAY_WRITE 2
#define MAY_READ 4

/*
 *      permission()
 *
 * is used to check for read/write/execute permissions on a file.
 * I don't know if we should look at just the euid or both euid and
 * uid, but that should be easily changed.
 */
static int permission(struct m_inode * inode,int mask)
{
        int mode = inode->i_mode;

/* special case: not even root can read/write a deleted file */
        if (inode->i_dev && !inode->i_nlinks)
                return 0;
        else if (current->euid==inode->i_uid)
                mode >>= 6;
        else if (current->egid==inode->i_gid)
                mode >>= 3;
        return mode & mask & 0007;
}

/*
 * ok, we cannot use strncmp, as the name is not in our data space.
 * Thus we'll have to use match. No big problem. Match also makes
 * some sanity tests.
 *
 * NOTE! unlike strncmp, match returns 1 for success, 0 for failure.
 */
static int match(int len,const char * name,struct dir_entry * de)
{
        register int same __asm__("ax");

        if (!de || !de->inode || len > NAME_LEN)
                return 0;
        if (len < NAME_LEN && de->name[len])
                return 0;
        __asm__("cld\n\t"
                "fs ; repe ; cmpsb\n\t"
                "setz %%al"
                :"=a" (same)
                :"0" (0),"S" ((long) name),"D" ((long) de->name),"c" (len)
                :"cx","di","si");
        return same;
}

/*
 *      find_entry()
 *
 * finds and entry in the specified directory with the wanted name. It
 * returns the cache buffer in which the entry was found, and the entry
 * itself (as a parameter - res_dir). It does NOT read the inode of the
 * entry - you'll have to do that yourself if you want to.
 *
 * This also takes care of the few special cases due to '..'-traversal
 * over a pseudo-root and a mount point.
 */
static struct buffer_head * find_entry(struct m_inode ** dir,
        const char * name, int namelen, struct dir_entry ** res_dir)
{
        int entries;
        int block,i;
        struct buffer_head * bh;
        struct dir_entry * de;
        struct super_block * sb;

#ifdef NO_TRUNCATE
        if (namelen > NAME_LEN)
                return NULL;
#else
        if (namelen > NAME_LEN)
                namelen = NAME_LEN;
#endif
        entries = (*dir)->i_size / (sizeof (struct dir_entry));
        *res_dir = NULL;
        if (!namelen)
                return NULL;
/* check for '..', as we might have to do some "magic" for it */
        if (namelen==2 && get_fs_byte(name)=='.' && get_fs_byte(name+1)=='.') {
/* '..' in a pseudo-root results in a faked '.' (just change namelen) */
                if ((*dir) == current->root)
                        namelen=1;
                else if ((*dir)->i_num == ROOT_INO) {
/* '..' over a mount-point results in 'dir' being exchanged for the mounted
   directory-inode. NOTE! We set mounted, so that we can iput the new dir */
                        sb=get_super((*dir)->i_dev);
                        if (sb->s_imount) {
                                iput(*dir);
                                (*dir)=sb->s_imount;
                                (*dir)->i_count++;
                        }
                }
        }
        if (!(block = (*dir)->i_zone[0]))
                return NULL;
        if (!(bh = bread((*dir)->i_dev,block)))
                return NULL;
        i = 0;
        de = (struct dir_entry *) bh->b_data;
        while (i < entries) {
                if ((char *)de >= BLOCK_SIZE+bh->b_data) {
                        brelse(bh);
                        bh = NULL;
                        if (!(block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK)) ||
                            !(bh = bread((*dir)->i_dev,block))) {
                                i += DIR_ENTRIES_PER_BLOCK;
                                continue;
                        }
                        de = (struct dir_entry *) bh->b_data;
                }
                if (match(namelen,name,de)) {
                        *res_dir = de;
                        return bh;
                }
                de++;
                i++;
        }
        brelse(bh);
        return NULL;
}

/*
 *      add_entry()
 *
 * adds a file entry to the specified directory, using the same
 * semantics as find_entry(). It returns NULL if it failed.
 *
 * NOTE!! The inode part of 'de' is left at 0 - which means you
 * may not sleep between calling this and putting something into
 * the entry, as someone else might have used it while you slept.
 */
static struct buffer_head * add_entry(struct m_inode * dir,
        const char * name, int namelen, struct dir_entry ** res_dir)
{
        int block,i;
        struct buffer_head * bh;
        struct dir_entry * de;

        *res_dir = NULL;
#ifdef NO_TRUNCATE
        if (namelen > NAME_LEN)
                return NULL;
#else
        if (namelen > NAME_LEN)
                namelen = NAME_LEN;
#endif
        if (!namelen)
                return NULL;
        if (!(block = dir->i_zone[0]))
                return NULL;
        if (!(bh = bread(dir->i_dev,block)))
                return NULL;
        i = 0;
        de = (struct dir_entry *) bh->b_data;
        while (1) {
                if ((char *)de >= BLOCK_SIZE+bh->b_data) {
                        brelse(bh);
                        bh = NULL;
                        block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);
                        if (!block)
                                return NULL;
                        if (!(bh = bread(dir->i_dev,block))) {
                                i += DIR_ENTRIES_PER_BLOCK;
                                continue;
                        }
                        de = (struct dir_entry *) bh->b_data;
                }
                if (i*sizeof(struct dir_entry) >= dir->i_size) {
                        de->inode=0;
                        dir->i_size = (i+1)*sizeof(struct dir_entry);
                        dir->i_dirt = 1;
                        dir->i_ctime = CURRENT_TIME;
                }
                if (!de->inode) {
                        dir->i_mtime = CURRENT_TIME;
                        for (i=0; i < NAME_LEN ; i++)
                                de->name[i]=(i<namelen)?get_fs_byte(name+i):0;
                        bh->b_dirt = 1;
                        *res_dir = de;
                        return bh;
                }
                de++;
                i++;
        }
        brelse(bh);
        return NULL;
}

/*
 *      get_dir()
 *
 * Getdir traverses the pathname until it hits the topmost directory.
 * It returns NULL on failure.
 */
static struct m_inode * get_dir(const char * pathname)
{
        char c;
        const char * thisname;
        struct m_inode * inode;
        struct buffer_head * bh;
        int namelen,inr,idev;
        struct dir_entry * de;

        if (!current->root || !current->root->i_count)
                panic("No root inode");
        if (!current->pwd || !current->pwd->i_count)
                panic("No cwd inode");
        if ((c=get_fs_byte(pathname))=='/') {
                inode = current->root;
                pathname++;
        } else if (c)
                inode = current->pwd;
        else
                return NULL;    /* empty name is bad */
        inode->i_count++;
        while (1) {
                thisname = pathname;
                if (!S_ISDIR(inode->i_mode) ||
                    !(permission(inode,MAY_EXEC) || suser())) {
                        iput(inode);
                        return NULL;
                }
                for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)
                        /* nothing */ ;
                if (!c)
                        return inode;
                if (!(bh = find_entry(&inode,thisname,namelen,&de))) {
                        iput(inode);
                        return NULL;
                }
                inr = de->inode;
                idev = inode->i_dev;
                brelse(bh);
                iput(inode);
                if (!(inode = iget(idev,inr)))
                        return NULL;
        }
}

/*
 *      dir_namei()
 *
 * dir_namei() returns the inode of the directory of the
 * specified name, and the name within that directory.
 */
static struct m_inode * dir_namei(const char * pathname,
        int * namelen, const char ** name)
{
        char c;
        const char * basename;
        struct m_inode * dir;

        if (!(dir = get_dir(pathname)))
                return NULL;
        basename = pathname;
        while (c=get_fs_byte(pathname++))
                if (c=='/')
                        basename=pathname;
        *namelen = pathname-basename-1;
        *name = basename;
        return dir;
}

/*
 *      namei()
 *
 * is used by most simple commands to get the inode of a specified name.
 * Open, link etc use their own routines, but this is enough for things
 * like 'chmod' etc.
 */
struct m_inode * namei(const char * pathname)
{
        const char * basename;
        int inr,dev,namelen;
        struct m_inode * dir;
        struct buffer_head * bh;
        struct dir_entry * de;

        if (!(dir = dir_namei(pathname,&namelen,&basename)))
                return NULL;
        if (!namelen)                   /* special case: '/usr/' etc */
                return dir;
        bh = find_entry(&dir,basename,namelen,&de);
        if (!bh) {
                iput(dir);
                return NULL;
        }
        inr = de->inode;
        dev = dir->i_dev;
        brelse(bh);
        iput(dir);
        dir=iget(dev,inr);
        if (dir) {
                dir->i_atime=CURRENT_TIME;
                dir->i_dirt=1;
        }
        return dir;
}

/*
 *      open_namei()
 *
 * namei for open - this is in fact almost the whole open-routine.
 */
int open_namei(const char * pathname, int flag, int mode,
        struct m_inode ** res_inode)
{
        const char * basename;
        int inr,dev,namelen;
        struct m_inode * dir, *inode;
        struct buffer_head * bh;
        struct dir_entry * de;

        if ((flag & O_TRUNC) && !(flag & O_ACCMODE))
                flag |= O_WRONLY;
        mode &= 0777 & ~current->umask;
        mode |= I_REGULAR;
        if (!(dir = dir_namei(pathname,&namelen,&basename)))
                return -ENOENT;
        if (!namelen) {                 /* special case: '/usr/' etc */
                if (!(flag & (O_ACCMODE|O_CREAT|O_TRUNC))) {
                        *res_inode=dir;
                        return 0;
                }
                iput(dir);
                return -EISDIR;
        }
        bh = find_entry(&dir,basename,namelen,&de);
        if (!bh) {
                if (!(flag & O_CREAT)) {
                        iput(dir);
                        return -ENOENT;
                }
                if (!(permission(dir,MAY_WRITE) || suser())) {
                        iput(dir);
                        return -EACCES;
                }
                inode = new_inode(dir->i_dev);
                if (!inode) {
                        iput(dir);
                        return -ENOSPC;
                }
                inode->i_mode = mode;
                inode->i_dirt = 1;
                bh = add_entry(dir,basename,namelen,&de);
                if (!bh) {
                        inode->i_nlinks--;
                        iput(inode);
                        iput(dir);
                        return -ENOSPC;
                }
                de->inode = inode->i_num;
                bh->b_dirt = 1;
                brelse(bh);
                iput(dir);
                *res_inode = inode;
                return 0;
        }
        inr = de->inode;
        dev = dir->i_dev;
        brelse(bh);
        iput(dir);
        if (flag & O_EXCL)
                return -EEXIST;
        if (!(inode=iget(dev,inr)))
                return -EACCES;
        if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) ||
            (!(permission(inode,ACC_MODE(flag))==ACC_MODE(flag)) ||
             suser())) {
                iput(inode);
                return -EPERM;
        }
        inode->i_atime = CURRENT_TIME;
        if (flag & O_TRUNC)
                truncate(inode);
        *res_inode = inode;
        return 0;
}

int sys_mknod(const char * filename, int mode, int dev)
{
        const char * basename;
        int namelen;
        struct m_inode * dir, * inode;
        struct buffer_head * bh;
        struct dir_entry * de;
        
        if (!suser())
                return -EPERM;
        if (!(dir = dir_namei(filename,&namelen,&basename)))
                return -ENOENT;
        if (!namelen) {
                iput(dir);
                return -ENOENT;
        }
        if (!(permission(dir,MAY_WRITE) || suser())) {
                iput(dir);
                return -EPERM;
        }
        bh = find_entry(&dir,basename,namelen,&de);
        if (bh) {
                brelse(bh);
                iput(dir);
                return -EEXIST;
        }
        inode = new_inode(dir->i_dev);
        if (!inode) {
                iput(dir);
                return -ENOSPC;
        }
        inode->i_mode = mode;
        if (S_ISBLK(mode) || S_ISCHR(mode))
                inode->i_zone[0] = dev;
        inode->i_mtime = inode->i_atime = CURRENT_TIME;
        inode->i_dirt = 1;
        bh = add_entry(dir,basename,namelen,&de);
        if (!bh) {
                iput(dir);
                inode->i_nlinks=0;
                iput(inode);
                return -ENOSPC;
        }
        de->inode = inode->i_num;
        bh->b_dirt = 1;
        iput(dir);
        iput(inode);
        brelse(bh);
        return 0;
}

int sys_mkdir(const char * pathname, int mode)
{
        const char * basename;
        int namelen;
        struct m_inode * dir, * inode;
        struct buffer_head * bh, *dir_block;
        struct dir_entry * de;

        if (!suser())
                return -EPERM;
        if (!(dir = dir_namei(pathname,&namelen,&basename)))
                return -ENOENT;
        if (!namelen) {
                iput(dir);
                return -ENOENT;
        }
        if (!(permission(dir,MAY_WRITE) || suser())) {
                iput(dir);
                return -EPERM;
        }
        bh = find_entry(&dir,basename,namelen,&de);
        if (bh) {
                brelse(bh);
                iput(dir);
                return -EEXIST;
        }
        inode = new_inode(dir->i_dev);
        if (!inode) {
                iput(dir);
                return -ENOSPC;
        }
        inode->i_size = 32;
        inode->i_dirt = 1;
        inode->i_mtime = inode->i_atime = CURRENT_TIME;
        if (!(inode->i_zone[0]=new_block(inode->i_dev))) {
                iput(dir);
                inode->i_nlinks--;
                iput(inode);
                return -ENOSPC;
        }
        inode->i_dirt = 1;
        if (!(dir_block=bread(inode->i_dev,inode->i_zone[0]))) {
                iput(dir);
                free_block(inode->i_dev,inode->i_zone[0]);
                inode->i_nlinks--;
                iput(inode);
                return -ERROR;
        }
        de = (struct dir_entry *) dir_block->b_data;
        de->inode=inode->i_num;
        strcpy(de->name,".");
        de++;
        de->inode = dir->i_num;
        strcpy(de->name,"..");
        inode->i_nlinks = 2;
        dir_block->b_dirt = 1;
        brelse(dir_block);
        inode->i_mode = I_DIRECTORY | (mode & 0777 & ~current->umask);
        inode->i_dirt = 1;
        bh = add_entry(dir,basename,namelen,&de);
        if (!bh) {
                iput(dir);
                free_block(inode->i_dev,inode->i_zone[0]);
                inode->i_nlinks=0;
                iput(inode);
                return -ENOSPC;
        }
        de->inode = inode->i_num;
        bh->b_dirt = 1;
        dir->i_nlinks++;
        dir->i_dirt = 1;
        iput(dir);
        iput(inode);
        brelse(bh);
        return 0;
}

/*
 * routine to check that the specified directory is empty (for rmdir)
 */
static int empty_dir(struct m_inode * inode)
{
        int nr,block;
        int len;
        struct buffer_head * bh;
        struct dir_entry * de;

        len = inode->i_size / sizeof (struct dir_entry);
        if (len<2 || !inode->i_zone[0] ||
            !(bh=bread(inode->i_dev,inode->i_zone[0]))) {
                printk("warning - bad directory on dev %04x\n",inode->i_dev);
                return 0;
        }
        de = (struct dir_entry *) bh->b_data;
        if (de[0].inode != inode->i_num || !de[1].inode || 
            strcmp(".",de[0].name) || strcmp("..",de[1].name)) {
                printk("warning - bad directory on dev %04x\n",inode->i_dev);
                return 0;
        }
        nr = 2;
        de += 2;
        while (nr<len) {
                if ((void *) de >= (void *) (bh->b_data+BLOCK_SIZE)) {
                        brelse(bh);
                        block=bmap(inode,nr/DIR_ENTRIES_PER_BLOCK);
                        if (!block) {
                                nr += DIR_ENTRIES_PER_BLOCK;
                                continue;
                        }
                        if (!(bh=bread(inode->i_dev,block)))
                                return 0;
                        de = (struct dir_entry *) bh->b_data;
                }
                if (de->inode) {
                        brelse(bh);
                        return 0;
                }
                de++;
                nr++;
        }
        brelse(bh);
        return 1;
}

int sys_rmdir(const char * name)
{
        const char * basename;
        int namelen;
        struct m_inode * dir, * inode;
        struct buffer_head * bh;
        struct dir_entry * de;

        if (!suser())
                return -EPERM;
        if (!(dir = dir_namei(name,&namelen,&basename)))
                return -ENOENT;
        if (!namelen) {
                iput(dir);
                return -ENOENT;
        }
        bh = find_entry(&dir,basename,namelen,&de);
        if (!bh) {
                iput(dir);
                return -ENOENT;
        }
        if (!(permission(dir,MAY_WRITE) || suser())) {
                iput(dir);
                brelse(bh);
                return -EPERM;
        }
        if (!(inode = iget(dir->i_dev, de->inode))) {
                iput(dir);
                brelse(bh);
                return -EPERM;
        }
        if (inode == dir) {     /* we may not delete ".", but "../dir" is ok */
                iput(inode);
                iput(dir);
                brelse(bh);
                return -EPERM;
        }
        if (!S_ISDIR(inode->i_mode)) {
                iput(inode);
                iput(dir);
                brelse(bh);
                return -ENOTDIR;
        }
        if (!empty_dir(inode)) {
                iput(inode);
                iput(dir);
                brelse(bh);
                return -ENOTEMPTY;
        }
        if (inode->i_nlinks != 2)
                printk("empty directory has nlink!=2 (%d)",inode->i_nlinks);
        de->inode = 0;
        bh->b_dirt = 1;
        brelse(bh);
        inode->i_nlinks=0;
        inode->i_dirt=1;
        dir->i_nlinks--;
        dir->i_ctime = dir->i_mtime = CURRENT_TIME;
        dir->i_dirt=1;
        iput(dir);
        iput(inode);
        return 0;
}

int sys_unlink(const char * name)
{
        const char * basename;
        int namelen;
        struct m_inode * dir, * inode;
        struct buffer_head * bh;
        struct dir_entry * de;

        if (!(dir = dir_namei(name,&namelen,&basename)))
                return -ENOENT;
        if (!namelen) {
                iput(dir);
                return -ENOENT;
        }
        if (!(permission(dir,MAY_WRITE) || suser())) {
                iput(dir);
                return -EPERM;
        }
        bh = find_entry(&dir,basename,namelen,&de);
        if (!bh) {
                iput(dir);
                return -ENOENT;
        }
        inode = iget(dir->i_dev, de->inode);
        if (!inode) {
                printk("iget failed in delete (%04x:%d)",dir->i_dev,de->inode);
                iput(dir);
                brelse(bh);
                return -ENOENT;
        }
        if (S_ISDIR(inode->i_mode)) {
                iput(inode);
                iput(dir);
                brelse(bh);
                return -EPERM;
        }
        if (!inode->i_nlinks) {
                printk("Deleting nonexistent file (%04x:%d), %d\n",
                        inode->i_dev,inode->i_num,inode->i_nlinks);
                inode->i_nlinks=1;
        }
        de->inode = 0;
        bh->b_dirt = 1;
        brelse(bh);
        inode->i_nlinks--;
        inode->i_dirt = 1;
        inode->i_ctime = CURRENT_TIME;
        iput(inode);
        iput(dir);
        return 0;
}

int sys_link(const char * oldname, const char * newname)
{
        struct dir_entry * de;
        struct m_inode * oldinode, * dir;
        struct buffer_head * bh;
        const char * basename;
        int namelen;

        oldinode=namei(oldname);
        if (!oldinode)
                return -ENOENT;
        if (S_ISDIR(oldinode->i_mode)) {
                iput(oldinode);
                return -EPERM;
        }
        dir = dir_namei(newname,&namelen,&basename);
        if (!dir) {
                iput(oldinode);
                return -EACCES;
        }
        if (!namelen) {
                iput(oldinode);
                iput(dir);
                return -EPERM;
        }
        if (dir->i_dev != oldinode->i_dev) {
                iput(dir);
                iput(oldinode);
                return -EXDEV;
        }
        if (!(permission(dir,MAY_WRITE) || suser())) {
                iput(dir);
                iput(oldinode);
                return -EACCES;
        }
        bh = find_entry(&dir,basename,namelen,&de);
        if (bh) {
                brelse(bh);
                iput(dir);
                iput(oldinode);
                return -EEXIST;
        }
        bh = add_entry(dir,basename,namelen,&de);
        if (!bh) {
                iput(dir);
                iput(oldinode);
                return -ENOSPC;
        }
        de->inode = oldinode->i_num;
        bh->b_dirt = 1;
        brelse(bh);
        iput(dir);
        oldinode->i_nlinks++;
        oldinode->i_ctime = CURRENT_TIME;
        oldinode->i_dirt = 1;
        iput(oldinode);
        return 0;
}

/* [previous][next][first][last][top][bottom][index][help] */