What is a File Descriptor Owner?
When signal-driven I/O is enabled on a file descriptor, the kernel needs to know which process (or process group) should receive the SIGIO signal when I/O becomes possible. This target is called the owner of the file descriptor.
You set the owner with fcntl(fd, F_SETOWN, pid) and you can read it back with fcntl(fd, F_GETOWN). Understanding the rules for positive and negative values — and a historic glibc bug — is important for robust programs.
The general form is:
fcntl(fd, F_SETOWN, pid);
The meaning of pid depends on whether it is positive or negative:
| Value of pid | Interpretation | Who Gets SIGIO |
|---|---|---|
positive (e.g., 1234) |
Treated as a process ID | That specific process |
negative (e.g., -56) |
Absolute value treated as process group ID | All processes in that group |
getpid() |
The most common usage — send to self | The calling process itself |
-getpgrp() |
Sends to the caller’s process group | All processes in caller’s group |
Common usage examples:
/* Send SIGIO to this process only */
if (fcntl(fd, F_SETOWN, getpid()) == -1)
perror("fcntl F_SETOWN"), exit(1);
/* Send SIGIO to the entire process group of the caller */
if (fcntl(fd, F_SETOWN, -getpgrp()) == -1)
perror("fcntl F_SETOWN"), exit(1);
Permission rules: The kernel checks if the sending process (the one that did F_SETOWN) has permission to send signals to the target process or group, following the same rules as kill().
You can query the current owner of a file descriptor at any time:
pid_t id;
id = fcntl(fd, F_GETOWN);
if (id == -1)
perror("fcntl F_GETOWN");
/* Positive value = process ID */
/* Negative value = process group ID (absolute value is the group ID) */
| Return Value | Meaning |
|---|---|
| Positive number | Process ID of the owner |
| Negative number | Process group ID (negate it to get the actual PGID) |
| -1 with errno set | Error occurred |
Older UNIX implementations used ioctl() instead of fcntl() for this purpose. For compatibility, Linux also provides these ioctl operations:
| fcntl() operation | Equivalent older ioctl() |
|---|---|
F_SETOWN |
FIOSETOWN or SIOCSPGRP |
F_GETOWN |
FIOGETOWN or SIOCGPGRP |
There is a well-known limitation in how glibc handles the return value of fcntl(fd, F_GETOWN) on some Linux architectures (notably x86).
| Situation | Kernel Returns | glibc Interprets | App Sees |
|---|---|---|---|
| Owner is process ID 500 (positive) | +500 |
Success | 500 — correct ✓ |
| Owner is PGID 500 (group) | -500 |
Success (negative = group) | -500 — correct ✓ |
| Owner is PGID 3000 (group, < 4096) | -3000 |
Looks like errno = 3000, returns -1 | -1 with errno=3000 — WRONG ✗ |
Why does this happen?
The Linux kernel signals errors by returning a negative value in the range -1 to -4095 from a system call. glibc treats any value in that range as an error code: it negates the value into errno and returns -1 to the caller. The problem is that a valid process group ID of, say, 3000 is returned as -3000 by the kernel (because F_GETOWN uses negative for groups). This -3000 falls right inside the kernel’s error range, so glibc misreads it as an error.
fcntl(fd, F_GETOWN) will incorrectly return -1 and set errno to the group ID value. This is almost impossible to detect by checking return values alone.The Fix — F_GETOWN_EX (Linux 2.6.32+, glibc 2.11+):
Since glibc 2.11, the F_GETOWN wrapper internally uses F_GETOWN_EX to avoid this issue. F_GETOWN_EX fills a struct instead of returning a possibly-ambiguous integer:
#include <fcntl.h>
#include <stdio.h>
/* F_GETOWN_EX avoids the glibc limitation */
struct f_owner_ex owner;
if (fcntl(fd, F_GETOWN_EX, &owner) == -1) {
perror("fcntl F_GETOWN_EX");
exit(1);
}
/* owner.type is one of:
F_OWNER_PID — owner.pid is a process ID
F_OWNER_PGRP — owner.pid is a process group ID
F_OWNER_TID — owner.pid is a thread ID */
switch (owner.type) {
case F_OWNER_PID:
printf("Owner: process %d\n", owner.pid);
break;
case F_OWNER_PGRP:
printf("Owner: process group %d\n", owner.pid);
break;
case F_OWNER_TID:
printf("Owner: thread %d\n", owner.pid);
break;
}
Similarly, F_SETOWN_EX allows you to set ownership to a specific thread (not just a process or group), which is useful in multithreaded programs:
struct f_owner_ex owner;
/* Set fd owner to current thread */
owner.type = F_OWNER_TID;
owner.pid = gettid(); /* Linux-specific: get thread ID */
if (fcntl(fd, F_SETOWN_EX, &owner) == -1) {
perror("fcntl F_SETOWN_EX");
}
| Owner Type | How to Specify | Typical Use Case |
|---|---|---|
| Current process | fcntl(fd, F_SETOWN, getpid()) |
Most common — single-process programs |
| Another process | fcntl(fd, F_SETOWN, otherpid) |
Parent/child coordination |
| Process group | fcntl(fd, F_SETOWN, -pgid) |
Notify entire job/session |
| Specific thread | fcntl(fd, F_SETOWN_EX, &owner) |
Multithreaded servers (Linux 2.6.32+) |
struct f_owner_ex, avoiding the ambiguity of returning an integer that might clash with error codes. You should use it (or F_SETOWN_EX) when you need to set/get ownership reliably, especially with process groups, or when working with multithreaded programs where you want to direct SIGIO to a specific thread.fcntl(fd, F_SETOWN, -getpgrp()). All processes in that group will receive SIGIO when I/O is possible on the fd. However, using F_GETOWN to read back the owner may fail with the glibc bug if the group ID is less than 4096.ioctl(fd, FIOSETOWN, &pid) or ioctl(fd, SIOCSPGRP, &pid) instead of F_SETOWN, and ioctl(fd, FIOGETOWN, &id) or ioctl(fd, SIOCGPGRP, &id) instead of F_GETOWN. Linux still supports these for backward compatibility, but modern code should use the fcntl() approach.Terminals, pipes, FIFOs, sockets, inotify — learn the exact conditions for each file type.
