Comparison of file descriptors

Hi all,

I was wondering if there is a way to determine if two file descriptors point to the same file description. E.g. if you have:

1
2
3
4
5
6
7
8
9
10
11
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

...

int myFd1 = socket(AF_INET, SOCK_STREAM, 0);
int myFd2 = socket(AF_INET, SOCK_STREAM, 0);
int myFd1Copy = dup(myFd1);
int myFd2Copy = dup(myFd2);

how can I write a function bool compareFds(int fd1, int fd2) such that:

1
2
3
4
5
compareFds(myFd1, myFd1) == true
compareFds(myFd1, myFd1Copy) == true
compareFds(myFd1, myFd2) == false
compareFds(myFd2, myFd2Copy) == true
compareFds(myFd1Copy, myFd2Copy) == false

?

It would be quite useful to implement a FileDescriptor class with operator= and the copy constructor implemented with dup(), but I need this comparison to implement operator==.
What would be the point of such a class? What would the copy semantics be if the FileDescriptor were:

1) a UDP socket bound to port X?
2) a TCP socket bound to port X?
3) a UNIX socket bound to an abstract name?
4) a disk file opened for writing?
5) a disk file opened for reading and writing?


Err ... you realize that dup() is a standard POSIX function that makes a copy of a file descriptor ? So the semantics for a FileDescriptor class would be exactly the same for the dup() function.

Basically, with a simple FileDescriptor class like that:

1
2
3
4
5
6
7
8
9
10
11
class FileDescriptor
{
public:
    FileDescriptor() : m_fd(-1) {}
    FileDescriptor(int fd) : m_fd(fd) {}
    ~FileDescriptor() {close(m_fd);}
    
    operator int() {return m_fd; }
private:
    int m_fd;
};

you cannot have e.g. a std::vector of FileDescriptors because when you add an element to the vector, a copy is made and the original FileDescriptor is likely going to be destructed, and thus close() will be called on the fd.

If however the operator= and the copy constructor of FileDescriptor are overloaded and use dup(), the problem is solved, except that I don't know how to implement operator==, which makes the whole thing pretty much useless.
I do realize that. But understand that the most common uses of dup (and dup2) are
to programmatically redirect stderr, stdout, and/or stdin to a different location
(perhaps to a file, or perhaps piping the output from a fork/exec'ed program back
to the parent).

It is not, in general, used to create two different file descriptors to the same disk file
or the same UDP or TCP port, or the same UNIX socket.

I only ask the questions because I think there are far more ways to abuse/misuse
a general FileDescriptor class than there are uses of it.

Personally I'd think about writing some sort of "smart fd" class that mimics the
way boost::shared_ptr works.
I think something needs to be clarified: a file descriptor does not point to a disk file or to a TCP/UDP port. It points to an open file description, i.e an open disk file, or an open TCP/UDP port, or an open TCP connection, etc. From the dup() manpage:

After a successful return from one of these system calls, the old and new file descriptors may be used inter‐changeably. They refer to the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the descriptors, the offset is also changed for the other.


And from the close() manpage:

If fd is the last file descriptor referring to the underlying open file description (see open(2)), the resources associated with the open file description are freed; if the descriptor was the last reference to a file which has been removed using unlink(2) the file is deleted.


So it means that the "smart fd" you propose could be very easily implemented using dup(), whereas a shared_ptr-like implementation would be quite complex (because the implementation of shared_ptr itself is quite complex). Hence my original question...
Last edited on
So if I create 2 TCP sockets, but don't bind them to a port, and compare them, they are equal. But as
soon as I bind one to port X, they are no longer equal.

If I open() "foo.txt" read only twice, they are equal. As soon as I read one character using one of
the file descriptors (but not the other), they are not equal? (Since your quoted man page includes
the file offset as part of the file description).

If I open() "foo.txt" twice, once as read-only and once as, say, read-write, they are not equal (again,
see man page).

1
2
3
4
5
6
7
// Assuming some syntax here
FileDescriptor f1( "foo.txt" );
FileDescriptor f2( "foo.txt" );
FileDescriptor f3( f2 );

// I suppose you are saying now that:
//  f1 != f2, but f2 == f3, since f1 and f2 point to different file descriptions whereas f2 and f3 point to the same. 


If those are the semantics you want, I think the answer to your question is no, you can't know that, because
the file descriptor table is not exposed to you (it is held by the kernel).

But if this is the problem you are trying to solve:

you cannot have e.g. a std::vector of FileDescriptors because when you add an element to the vector, a copy is made and the original FileDescriptor is likely going to be destructed, and thus close() will be called on the fd.


That problem should be solved via a "smart fd" class. I suggested it not only because it is the right solution,
but also because I have an implementation of one (it is less than 100 lines of actual C++ code and mimics
the APIs provided by the boost smart_ptr library), but I cannot post it here.


Either I did not make my problem clear enough, or you don't understand the concepts of file descriptors vs file descriptions vs files.

When you have:

1
2
int fd1 = open("foo.txt", "r");
int fd2 = open("foo.txt", "r");


fd1 and fd2 are different file descriptors which refer to different open file descriptions, which can have different offsets, etc, although they refer to the same file.

The thing is, I don't want to know if two file descriptors refer to the same TCP port or the same file. I want to compare two file descriptors to know if they refer to the same socket or open file description. Again, I don't care if they refer to the same file/TCP port or not.

My FileDescriptor class is not meant to open a file or create a socket, it is meant to help dealing with a file descriptor in a "RAII way".

In other words, I don't want my FileDescriptor to do something like that (as you suggested):

1
2
FileDescriptor f1( "foo.txt" ); // open file foo.txt
FileDescriptor f2(1234); // Create TCP socket listening on port 1234 


but rather like that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FileDescriptor f1(open("foo.txt", "r")); // open file foo.txt
FileDescriptor f2(socket(AF_INET, SOCK_STREAM, 0)); // Create TCP socket

FileDescriptor f3 = f1;
FileDescriptor f4 = f2;

std::vector<FileDescriptor> fds;
fds.push_back(f3);
fds.push_back(f4);

...

for (std::vector<FileDescriptor>::iterator it = fds.begin(); it != fds.end(); ++it)
{
    if (*it == f1) ...
    else if (*it == f2) ...
    ...
}


I agree with you that a "smart fd" class which would include a reference counter would work. But I find the solution quite complicated, since the (UNIX) operating system already implements a reference counter for each open file description, which is incremented each time you call dup() on a file descriptor referencing this file description, and decremented each time you call close() on such a file descriptor.
Last edited on
Ok, the answer to your question is no, you can't do the comparison you want.
Topic archived. No new replies allowed.