美文网首页
[APUE习题]不使用fcntl实现dup2函数

[APUE习题]不使用fcntl实现dup2函数

作者: 哈莉_奎茵 | 来源:发表于2018-03-20 00:44 被阅读0次

    选自《Unix环境高级编程》习题3.2

    编写一个与3.12节中dup2功能相同的函数,要求不调用fcntl函数,并且要有正确的出错处理。

    int dup2(int oldfd, int newfd);
    

    dup2和让newfdoldfd指向同一张文件表(原来oldfd指向的),如果newfd已经打开,则需要关闭newfd。如果newfd等于oldfd,则不需要关闭。

    思路

    是在dup函数的基础上来实现,因为dup(oldfd)每次返回的都是最小的未打开的文件描述符,因此可以一个一个打开直到dup返回newfd,最后把之前打开的文件描述符全部关闭。在此之前需要检查参数的合法性。

    1. 判断oldfdnewfd是否合法,因为文件描述符是有范围限制;
    2. 判断oldfd是否打开,若未打开则代表oldfd未指向文件表;
    3. 判断oldfdnewfd是否相等,若相等则直接返回newfd;
    4. 判断newfd是否打开,若已打开则关闭newfd
    5. 反复调用dup,直到返回值等于newfd

    上述判断过程的实现细节有几点需要注意。

    1. 取得文件描述符的范围:使用库函数getdtablesize(3)取得文件描述符表的大小N,则文件描述符范围是[0, N)
    2. 如何判断文件描述符是否打开。参考how-to-check-if-a-given-file-descriptor-stored-in-a-variable-is-still-valid使用fcntl(fd, F_GETFD)是最权威的做法,因为它在内核中仅仅只是解引用而未做其他操作。由于可能会被信号中断(或其他情况)导致fcntl返回-1,此时需要检查errno是否为EBADF,即Bad file descriptor
    bool fd_is_valid(int fd) {
        errno = 0;
        return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
    }
    

    由于题目禁止用fcntl,这里可以用dup来代替。

    实现(兼容C99和C++11)

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <stdbool.h>
    
    // 检查文件描述符fd是否合法,若不合法则把errno和errnum置为EBADF
    static inline bool fd_is_valid(int fd, int* errnum) {
        int olderr = errno;  // 备份原来的errno防止errno之前已经是EBADF
        if (errno == EBADF)
            errno = 0;
        int newfd = dup(fd);
        if (newfd != -1 || errno != EBADF) {  // 合法,把errno恢复成原来的值
            close(newfd);
            errno = olderr;
            return true;
        } else {  // 不合法,errno和errnum均设置为EBADF
            errno = *errnum = EBADF;
            return false;
        }
    }
    
    // dup()的包裹函数,不改变errno,若dup调用失败则设置errnum为错误码
    static inline int dup_checked(int oldfd, int* errnum) {
        int olderr = errno;
        int newfd = dup(oldfd);
        if (newfd == -1)
            *errnum = errno;
        errno = olderr;
        return newfd;
    }
    
    static inline int mydup2_imp(int oldfd, int newfd, int* errnum) {
        close(newfd);
        int* fds = (int*)calloc(newfd + 1, sizeof(int));
        int index = 0;
        int res = -1;
        for (; index < (newfd + 1); ++index) {
            fds[index] = dup_checked(oldfd, errnum);
            if (fds[index] == -1 || fds[index] == newfd) {
                res = newfd;
                break;
            }
        }  // fds[index]为-1或者newfd
        printf("close fd: ");
        for (int i = 0; i < index; ++i) {
            printf("%d ", fds[i]);
            close(fds[i]);
        }
        printf("\n");
        free(fds);
        return res;
    }
    
    int mydup2(int oldfd, int newfd) {
        int errnum = errno;
        int res = -1;
        // 1.检查文件描述符范围是否合法
        int tbl_size = getdtablesize();
        if (oldfd < 0 || oldfd >= tbl_size ||
            newfd < 0 || newfd >= tbl_size) {
            errnum = EBADF;
            goto exit_mydup2;
        }
        // 2.检查oldfd是否打开,若未打开则errnum被置为EBADF
        if (!fd_is_valid(oldfd, &errnum))
            goto exit_mydup2;
        // 3.检查oldfd是否和newfd相同
        if (oldfd == newfd) {
            res = oldfd;
            goto exit_mydup2;
        }
        // 4.执行实际的dup2过程
        res = mydup2_imp(oldfd, newfd, &errnum);
    
    exit_mydup2:
        errno = errnum;
        return res;
    }
    
    int main() {
        int fd = 20;
        mydup2(STDOUT_FILENO, fd);
        write(fd, "hello\n", 6);
        return 0;
    }
    
    $ gcc mydup2.c -std=c99 -D_BSD_SOURCE
    $ ./a.out 
    close fd: 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
    hello
    

    相关文章

      网友评论

          本文标题:[APUE习题]不使用fcntl实现dup2函数

          本文链接:https://www.haomeiwen.com/subject/rmvmqftx.html