Crazy Psychild
Crazy Psychild

Reputation: 592

Strange behavior of dup system call

So, I was reading about File I/O in Linux and thought of playing around with it. However, I encountered two strange behaviors in the code and I am struggling to find out a reason for them.

/*
 * This program shows the usage of dup and dup2 functions
 */

#include "creep.h"
#include <fcntl.h>

#define BUFSIZE 2048

int main(int argc, char *argv[]) {

    int fd, dup_fd, n;
    char buf[BUFSIZE], buff[BUFSIZE];
    if (argc != 2) 
        err_quit("Usage: dup <filename>\n");

    fd = open(argv[1], O_RDWR);

    while ((n = read(fd, buf, BUFSIZE)) > 0)
        if (write(STDOUT_FILENO, buf, n) != n)
            err_sys("write error");

    if (n < 0)
        err_sys("read error");

    dup_fd = dup(fd);

    while ((n = read(dup_fd, buff, BUFSIZE)) > 0)
        if (write(STDOUT_FILENO, buff, n) != n)
            err_sys("write error");

    if (n < 0)
        err_sys("read error");

    printf("\nValues are : %d and %d\n", fd, dup_fd);

    close(fd);
    exit(0);
}

Now, when I run this program :-

# ./dup dup.c

It is actually printing the file only once and not for the second duplicate descriptor.

I ran a strace for the above in hope of finding out what is happening but all I could see was this ->

open("dup.c", O_RDWR)                   = 3
read(3, "/*\n * This program shows the usa"..., 2048) = 708
write(1, "/*\n * This program shows the usa"..., 708/
------------omitting the file from trace-------------
read(3, "", 2048)                       = 0
dup(3)                                  = 4
read(4, "", 2048)                       = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f170112d000
write(1, "\n", 1
)                       = 1
write(1, "Values are : 3 and 4\n", 21Values are : 3 and 4
)  = 21
close(3)   

It's kind of weird that first time when the read/write calls were made on fd, they had file content in the buffer. However for dup_fd, the buffer is empty. I am clueless why is this happening. Previously, I used same buffer both times and I thought that I should use separate buffers but to no avail.

I read that the dup function gives the lowest numbered available file descriptor which is duplicate of original one. Duplicate in the sense that the file pointer in the Process Table Entry points to the same file table for the file descriptors.

Why am I not able to read the file with duplicated file descriptor. Is there something that I am doing wrong?

The creep.h header file :-

/*
 * My own header, to be included before all standard system headers written by me.
 */
#ifndef _CREEP_H
#define _CREEP_H

#define _POSIX_C_SOURCE 200809L

#if defined(SOLARIS)
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 700
#endif

#include <sys/types.h>   /* some systems will require this */
#include <sys/stat.h>
#include <sys/termios.h>   /* for winsize */

#if defined(MACOS) || !defined(TIOCGWINSZ)
#include <sys/ioctl.h>
#endif

#include <stdio.h>     /* for convenience */
#include <stdlib.h>    /* for convenience */
#include <stddef.h>    /* for offsetof */
#include <string.h>    /* for convenience */
#include <unistd.h>    /* for convenience */
#include <signal.h>    /* for SIG_ERR */
#include <errno.h>     /* for definition of errno */
#include <stdarg.h>    /* ISO C variable arguments */
#include <syslog.h>    /* for convenience */


#define MAXLINE 4096           /* max line length */

/*
 * Default file access permissions for new files.
 */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

/*
 * Default permissions for new directories.
 */
#define DIR_MODE (FILE_MODE | S_IXUSR | S_IXGRP | SIXOTH)

typedef void Sigfunc(int);   /* for signal handlers */

#define min(a,b)  ((a) < (b) ? (a) : (b))
#define max(a,b)  ((a) > (b) ? (a) : (b))

/*
 * Prototypes for my own functions.
 */
char *path_alloc(size_t *);  
long open_max(void);

int set_cloexec(int);
void clr_fl(int, int);
void set_fl(int, int);

void pr_exit(int);

void pr_mask(const char *);
Sigfunc *signal_intr(int, Sigfunc *);

void daemonize(const char *);

void sleep_us(unsigned int);
ssize_t readn(int, void *, size_t);
ssize_t writen(int, const void *, size_t);

int fd_pipe(int *);
int recv_fd(int, ssize_t (*func) (int, const void *, size_t));
int send_fd(int, int);
int send_err(int, int, const char *);
int serv_listen(const char *);
int serv_accept(int, uid_t *);
int cli_conn(const char *);
int buf_args(char *, int (*func)(int, char **));
int tty_cbreak(int);
int tty_raw(int);
int tty_reset(int);
void tty_atexit(void);
struct termios *tty_termios(void);

int ptym_open(char *, int);
int ptys_open(char *);
#ifdef TIOCGWINSZ
pid_t pty_fork(int *, char *, int, const struct termios *, const struct winsize *);
#endif

int lock_reg(int, int, int, off_t, int, off_t);

#define read_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lcok(fd, offset, whence, len) \
    lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))

pid_t lock_test(int, int, off_t, int, off_t);

#define is_read_lockable(fd, offset, whence, len) \
    (lock_test((fd), F_RDLCK, (offset), (whence), (len)) == 0)
#define is_write_lockable(fd, offset, whence, len) \
    (lock_test((fd), F_WRLCK, (offset), (whence), (len)) == 0)

void err_msg(const char *, ...); 
void err_dump(const char *, ...) __attribute__((noreturn));
void err_quit(const char *, ...) __attribute__((noreturn));
void err_cont(int, const char *, ...);
void err_exit(int, const char *, ...) __attribute__((noreturn));
void err_ret(const char *, ...);
void err_sys(const char *, ...) __attribute__((noreturn));

void log_msg(const char *, ...);
void log_open(const char *, int, int);
void log_quit(const char *, ...) __attribute__((noreturn));
void log_ret(const char *, ...);
void log_sys(const char *, ...) __attribute__((noreturn));
void log_exit(int, const char *, ...) __attribute__((noreturn));

void TELL_WAIT(void);
void TELL_PARENT(pid_t);
void TELL_CHILD(pid_t);
void WAIT_PARENT(void);
void WAIT_CHILD(void);

/*
 * ERROR Function Definitions
 */

static void err_doit(int, int, const char *, va_list);

/*
 * Nonfatal error rleated to a system call.
 * Print a message and return.
 */
void err_ret(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
}

/*
 * Fata error related to a system call.
 * Print a message and terminate.
 */
void err_sys(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    exit(1);
}

/*
 * Nonfatal error unrealted to a system call.
 * Error code passed as explicit parameter.
 * Print a message and return.
 */
void err_cont(int error, const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    err_doit(1, error, fmt, ap);
    va_end(ap);
}

/*
 * Fatal error unrelated to a system call.
 * Error code passed as explicit parameter.
 * Print a message and return.
 */
void err_exit(int error, const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    err_doit(1, error, fmt, ap);
    va_end(ap);
    exit(1);
}   

/*
 * Fatal error related to a system call.
 * Print a message, dump core and terminate.
 */
void err_dump(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    abort();     /* dump core and terminate */
    exit(1);     /* shouldn't get here */
}

/*
 * Nonfatal error unrelated to a system call.
 * Print a message and return.
 */
void err_msg(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
}

/*
 * Fatale error unrelated to a system call.
 * Print a message and terminate.
 */
void err_quit(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
    exit(1);
}

/*
 * Print a message and return to caller.
 * Caller specifies "errnoflag".
 */
static void err_doit(int errnoflag, int error, const char *fmt, va_list ap) {
    char buf[MAXLINE];
    vsnprintf(buf, MAXLINE-1, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s", strerror(error));
    strcat(buf, "\n");
    fflush(stdout);
    fputs(buf, stderr);
    fflush(NULL);
}

#if 0
/*
 * ERROR Routines for programs that can run as a Daemon.
 * Commented out because of undefined reference of log_to_stderr.
 */

static void log_doit(int, int, int, const char *, va_list ap);

/*
 * Caller must define and set this: nonzero if 
 * interactive, zero if daemon
 */
extern int log_to_stderr;

/*
 * Initialize syslog(), if running as daemon.
 */
void log_open(const char *ident, int option, int facility) {
    if (log_to_stderr == 0)
        openlog(ident, option, facility);
}

/*
 * Nonfatal error rleated to a system call.
 * Print a message with the systems' errno value and return.
 */
void log_ret(const char *fmt, ...) {
        va_list ap;
        va_start(ap, fmt);
        log_doit(1, errno, LOG_ERR, fmt, ap);
        va_end(ap);
}

/*
 * Fata error related to a system call.
 * Print a message and terminate.
 */
void log_sys(const char *fmt, ...) {
        va_list ap;
        va_start(ap, fmt);
        log_doit(1, errno, LOG_ERR, fmt, ap);
        va_end(ap);
        exit(2);
}

/*
 * Fatal error unrelated to a system call.
 * Error code passed as explicit parameter.
 * Print a message and return.
 */
void log_exit(int error, const char *fmt, ...) {
        va_list ap;
        va_start(ap, fmt);
        log_doit(1, error, LOG_ERR, fmt, ap);
        va_end(ap);
        exit(2);
}

/*
 * Nonfatal error unrelated to a system call.
 * Print a message and return.
 */
void log_msg(const char *fmt, ...) {
        va_list ap;
        va_start(ap, fmt);
        log_doit(0, 0, LOG_ERR, fmt, ap);
        va_end(ap);
}

/*
 * Fatale error unrelated to a system call.
 * Print a message and terminate.
 */
void log_quit(const char *fmt, ...) {
        va_list ap;
        va_start(ap, fmt);
        log_doit(0, 0, LOG_ERR, fmt, ap);
        va_end(ap);
    exit(2);
}

/*
 * Print a message and return to caller.
 * Caller specifies "errnoflag".
 */
static void log_doit(int errnoflag, int error, int priority, const char *fmt, va_list ap) {
        char buf[MAXLINE];
        vsnprintf(buf, MAXLINE-1, fmt, ap);
        if (errnoflag)
                snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s", strerror(error));
        strcat(buf, "\n");
    if (log_to_stderr) {
            fflush(stdout);
            fputs(buf, stderr);
        fflush(stderr);
    } else {
        syslog(priority, "%s", buf);
    }
}

#endif


#endif /* _CREEP_H */

Upvotes: 1

Views: 301

Answers (2)

Andrew Henle
Andrew Henle

Reputation: 1

This is expected behavior. The POSIX documentation for dup() states:

The dup() function provides an alternative interface to the service provided by fcntl() using the F_DUPFD command. The call dup(fildes) shall be equivalent to:

fcntl(fildes, F_DUPFD, 0);

The fcntl() documentation states:

F_DUPFD

Return a new file descriptor which shall be allocated as described in File Descriptor Allocation, except that it shall be the lowest numbered available file descriptor greater than or equal to the third argument, arg, taken as an integer of type int. The new file descriptor shall refer to the same open file description as the original file descriptor, and shall share any locks. The FD_CLOEXEC flag associated with the new file descriptor shall be cleared to keep the file open across calls to one of the exec functions.

Note the bolded part: "same open file description".

That means that a dup()'d file descriptor shares the same current file offset as the one it's dup()'d from.

And you've already set that offset to the end of the file by reading all the data in it.

Upvotes: 2

user2404501
user2404501

Reputation:

By the time you get to the dup, your first loop has ended.

Why did it end? Because you read everything. The fd is at the end of the file.

Then you duplicate it. Now you have 2 fd's referring to the same file object ("open file description" in POSIX terminology). They're both at the end of the file.

You need to rewind (lseek(dup_fd, 0, SEEK_SET)) to read the file again. And be aware that seeking is an operation on the file object, so it affects both of the fds. You could seek fd to position 0 and then read from dup_fd and it read the contents again.

You could even alternate between calls to read(fd,...) and read(dup_fd,...) and you'd still get the file contents in order if you just print each chunk as you read it.

Upvotes: 2

Related Questions