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

2018-03-20  本文已影响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
上一篇下一篇

猜你喜欢

热点阅读