6.2.7 Opening a file

データへアクセスするときに最も重要な命令のひとつはopenシステムコールである.システムはモンヂアなくデータへのアクセスを確保するための適切な準備をするだけでなく,プロセスの認証もまた確認する必要がある.また,仮想ファイルシステムの実切り替えが実装され,特定のファイルシステムの実装と,様々なデバイス間でデータを渡せるようになった.

fs/open.c

asmlinkage int sys_open(const char * filename,int flags,int mode)
{
    char * tmp;
    int error;

    error = getname(filename, &tmp);
    if (error)
        return error;
    error = do_open(tmp,flags,mode);
    putname(tmp);
    return error;
}

呼び出し元のプロセスが最初にファイルを開く権限があると確認されると,get_empty_flip()関数を通じて新しいファイル構造体が要求され,プロセスのファイル記述子テーブルに入力される.この構造体では,f_flagsf_modeの各フィールドに適切な値が入力され,open_namei()関数が呼び出され開くファイルのinodeが取得される.

int do_open(const char * filename,int flags,int mode)
{
    struct inode * inode;
    struct file * f;
    int flag,error,fd;

    for(fd=0; fd<NR_OPEN && fd<current->rlim[RLIMIT_NOFILE].rlim_cur; fd++)
        if (!current->files->fd[fd])
            break;
    if (fd>=NR_OPEN || fd>=current->rlim[RLIMIT_NOFILE].rlim_cur)
        return -EMFILE;
    FD_CLR(fd,&current->files->close_on_exec);
    f = get_empty_filp();
    if (!f)
        return -ENFILE;
    current->files->fd[fd] = f;
    f->f_flags = flag = flags;
    f->f_mode = (flag+1) & O_ACCMODE;
    if (f->f_mode)
        flag++;
    if (flag & (O_TRUNC | O_CREAT))
        flag |= 2;
    error = open_namei(filename,flag,mode,&inode,NULL);

この関数が呼び出される前にopen()フラグが変更され,アクセス許可を保持している2つの最下位ビットが残される(0は読み込み,1は書き込みの操作).この方法によるファイルへのアクセスの利点は明らかである.単純なビットのテストでアクセス権限を確認できる.

open_namei()関数はdir_namei()関数を呼び出し,ファイルの基底名を除きファイル名の解決をおこない,ファイルが存在するディレクトリのinodeを取得する.open_namei()関数はいくつかのテストを実行する.

このファイルがテストに通ったなら,open_namei()res_inodeに新しく開いたファイルのinodeを入力し,do_open()fs/namei.c )に0を返す.

int open_namei(const char * pathname, int flag, int mode,
    struct inode ** res_inode, struct inode * base)
{
    const char * basename;
    int namelen,error;
    struct inode * dir, *inode;

    mode &= S_IALLUGO & ~current->fs->umask;
    mode |= S_IFREG;
    error = dir_namei(pathname,&namelen,&basename,base,&dir);
    if (error)
        return error;
    if (!namelen) {            /* special case: '/usr/' etc */
        if (flag & 2) {
            iput(dir);
            return -EISDIR;
        }
        /* thanks to Paul Pluzhnikov for noticing this was missing.. */
        if ((error = permission(dir,ACC_MODE(flag))) != 0) {
            iput(dir);
            return error;
        }
        *res_inode=dir;
        return 0;
    }
    dir->i_count++;        /* lookup eats the dir */
    if (flag & O_CREAT) {
        down(&dir->i_sem);
        error = lookup(dir,basename,namelen,&inode);
        if (!error) {
            if (flag & O_EXCL) {
                iput(inode);
                error = -EEXIST;
            }
        } else if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0)
            ;    /* error is already set! */
        else if (!dir->i_op || !dir->i_op->create)
            error = -EACCES;
        else if (IS_RDONLY(dir))
            error = -EROFS;
        else {
            dir->i_count++;        /* create eats the dir */
            error = dir->i_op->create(dir,basename,namelen,mode,res_inode);
            up(&dir->i_sem);
            iput(dir);
            return error;
        }
        up(&dir->i_sem);
    } else
        error = lookup(dir,basename,namelen,&inode);
    if (error) {
        iput(dir);
        return error;
    }
    error = follow_link(dir,inode,flag,mode,&inode);
    if (error)
        return error;
    if (S_ISDIR(inode->i_mode) && (flag & 2)) {
        iput(inode);
        return -EISDIR;
    }
    if ((error = permission(inode,ACC_MODE(flag))) != 0) {
        iput(inode);
        return error;
    }
    if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) {
        if (IS_NODEV(inode)) {
            iput(inode);
            return -EACCES;
        }
        flag &= ~O_TRUNC;
    } else {
        if (IS_RDONLY(inode) && (flag & 2)) {
            iput(inode);
            return -EROFS;
        }
    }
    /*
     * An append-only file must be opened in append mode for writing
     */
    if (IS_APPEND(inode) && ((flag & 2) && !(flag & O_APPEND))) {
        iput(inode);
        return -EPERM;
    }
    if (flag & O_TRUNC) {
        struct iattr newattrs;

        if ((error = get_write_access(inode))) {
            iput(inode);
            return error;
        }
        newattrs.ia_size = 0;
        newattrs.ia_valid = ATTR_SIZE;
        if ((error = notify_change(inode, &newattrs))) {
            put_write_access(inode);
            iput(inode);
            return error;
        }
        inode->i_size = 0;
        if (inode->i_op && inode->i_op->truncate)
            inode->i_op->truncate(inode);
        inode->i_dirt = 1;
        put_write_access(inode);
    }
    *res_inode = inode;
    return 0;
}

do_open()get_write_access()を呼び出して,必要に応じてファイルに対する書き込み許可を要求する.さらに,現在のファイル位置が0に設定され,標準のファイル操作がinodeのf_inode->i_op->default_file_opsになるようにファイル構造体を変更する.

fs/namei.c

int get_write_access(struct inode * inode)
{
    struct task_struct ** p;

    if ((inode->i_count > 1) && S_ISREG(inode->i_mode)) /* shortcut */
        for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
                struct vm_area_struct * mpnt;
            if (!*p)
                continue;
            for(mpnt = (*p)->mm->mmap; mpnt; mpnt = mpnt->vm_next) {
                if (inode != mpnt->vm_inode)
                    continue;
                if (mpnt->vm_flags & VM_DENYWRITE)
                    return -ETXTBSY;
            }
        }
    inode->i_wcount++;
    return 0;
}

    if (!error && (f->f_mode & 2)) {
        error = get_write_access(inode);
        if (error)
            iput(inode);
    }
    if (error) {
        current->files->fd[fd]=NULL;
        f->f_count--;
        return error;
    }

    ...do_open()の続き...
    f->f_inode = inode;
    f->f_pos = 0;
    f->f_reada = 0;
    f->f_op = NULL;
    if (inode->i_op)
        f->f_op = inode->i_op->default_file_ops;
    if (f->f_op && f->f_op->open) {
        error = f->f_op->open(inode,f);
        if (error) {
            /*
             * fs/namei.c
             * 
             * void put_write_access(struct inode * inode)
             * {
             *     inode->i_wcount--;
             * }
             */
            if (f->f_mode & 2) put_write_access(inode);
            iput(inode);
            f->f_count--;
            current->files->fd[fd]=NULL;
            return error;
        }
    }
    f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
    return (fd);
}

この操作は,ファイルタイプ固有の動作を処理する.開かれたファイルが文字指向のデバイスであったなら,この時点でchrdev_open()関数が呼び出され,デバイスのメジャー番号にしたがってファイル操作が変更される.

fs/devices.c

/*
 * ダミーの標準のファイル操作:これは,特殊ファイルに応じて
 * 適切な操作がおこなわれるchrdev_open()を含んでいる.
 */
struct file_operations def_chr_fops = {
    NULL,        /* lseek */
    NULL,        /* read */
    NULL,        /* write */
    NULL,        /* readdir */
    NULL,        /* select */
    NULL,        /* ioctl */
    NULL,        /* mmap */
    chrdev_open,    /* open */
    NULL,        /* release */
};

int chrdev_open(struct inode * inode, struct file * filp)
{
    int i;

    i = MAJOR(inode->i_rdev);
    if (i >= MAX_CHRDEV || !chrdevs[i].fops)
        return -ENODEV;
    filp->f_op = chrdevs[i].fops;
    if (filp->f_op->open)
        return filp->f_op->open(inode,filp);
    return 0;
}

デバイスドライバのファイル操作は,ドライバが初期化されたときにregister_chrdev()関数によって入力されたchrdevs[]テーブルに保持される(7章をみよ).デバイスドライバのopen()関数は,デバイスのマイナー番号にしたがってファイル操作を追加するのが確実である.

これらのopen関数によってエラーが返されなかった場合,ファイルは正常に開かれ,do_open()またはsys_open()関数によってファイル記述子がプロセスに返される.