[APUE习题]不使用fcntl实现dup2函数
2018-03-20 本文已影响0人
哈莉_奎茵
选自《Unix环境高级编程》习题3.2
编写一个与3.12节中dup2功能相同的函数,要求不调用fcntl函数,并且要有正确的出错处理。
int dup2(int oldfd, int newfd);
dup2
和让newfd
与oldfd
指向同一张文件表(原来oldfd
指向的),如果newfd
已经打开,则需要关闭newfd
。如果newfd
等于oldfd
,则不需要关闭。
思路
是在dup
函数的基础上来实现,因为dup(oldfd)
每次返回的都是最小的未打开的文件描述符,因此可以一个一个打开直到dup
返回newfd
,最后把之前打开的文件描述符全部关闭。在此之前需要检查参数的合法性。
- 判断
oldfd
和newfd
是否合法,因为文件描述符是有范围限制; - 判断
oldfd
是否打开,若未打开则代表oldfd
未指向文件表; - 判断
oldfd
和newfd
是否相等,若相等则直接返回newfd; - 判断
newfd
是否打开,若已打开则关闭newfd
; - 反复调用
dup
,直到返回值等于newfd
。
上述判断过程的实现细节有几点需要注意。
- 取得文件描述符的范围:使用库函数
getdtablesize(3)
取得文件描述符表的大小N,则文件描述符范围是[0, N) - 如何判断文件描述符是否打开。参考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