A byte-range lock can protect the same inode yet have radically different lifetime semantics depending on what owns the lock. Traditional fcntl() record locks are process-associated. Open file description locks instead attach to the kernel open file description referenced by a descriptor. That shift changes which close operation releases a lock, what survives fork(), and whether two threads in one process can contend on the same file region.

Linux exposes this model through F_OFD_SETLK, F_OFD_SETLKW, and F_OFD_GETLK. The range model remains familiar: struct flock specifies a read lock, write lock, or unlock together with an offset and length. The significant difference is ownership.

A descriptor is not the lock owner

A file descriptor is an entry in a process descriptor table. The entry refers to an open file description maintained by the kernel. Several descriptors can refer to one open file description after operations such as dup(), and a child created by fork() can inherit descriptors referring to the same description.

An OFD lock belongs to that shared open file description rather than to one descriptor number. Closing one duplicate therefore does not release the lock while another descriptor still references the same description. Automatic release occurs when the last reference to that open file description is closed.

This is materially different from traditional process-associated record locks. On Linux, closing any descriptor for a file can release all traditional record locks that the process holds on that file, even when the closed descriptor was not the descriptor used to acquire them. Code that treats a descriptor as the lifetime token for a traditional record lock can therefore lose protection through an unrelated close.

OFD locks align the lock lifetime more closely with the lifetime of the kernel file instance that carries it.

dup() preserves one ownership domain

Duplicating a descriptor does not create an independent OFD lock owner:

int a = open("state.bin", O_RDWR);
int b = dup(a);

Both a and b refer to the same open file description. An OFD write lock acquired through a can be modified or released through b. A conflicting OFD request made through either descriptor is treated as originating from the same ownership domain.

This property matters when descriptors pass through layers of a program. A wrapper can duplicate a descriptor without accidentally creating a competing lock owner. The duplicates remain aliases to the same kernel object.

A separate open() is different:

int a = open("state.bin", O_RDWR);
int b = open("state.bin", O_RDWR);

Even when both descriptors resolve to the same file, they normally refer to distinct open file descriptions. OFD locks acquired through those descriptions can conflict. The distinction is therefore not pathname identity, inode identity alone, or descriptor number. It is the open file description reached by the descriptor used for the locking operation.

fork() can extend the lock lifetime

A descriptor inherited across fork() still refers to the same open file description. The child consequently shares the OFD lock ownership associated with that description. If the parent closes its descriptor while the child retains an inherited reference, the lock can remain active.

That behavior can be useful when a process deliberately hands an already-open resource to a child. It can also produce a lock lifetime longer than the parent expected.

Consider this sequence:

parent opens file
parent acquires OFD lock
parent forks
child keeps inherited descriptor
parent closes its descriptor
lock remains
child closes final reference
lock can be released

The lifecycle contract must therefore account for descriptor inheritance. FD_CLOEXEC controls whether a particular descriptor remains open across execve(), but it does not retroactively change references already inherited by a child before execution of a new program.

The relevant invariant is simple: ownership follows the open file description, and the description remains alive while references to it remain alive.

Independent opens allow thread-level contention

Traditional process-associated record locks are a poor primitive for coordinating threads that need to contend with one another on file regions because the locks are owned at process scope. OFD locks can express a different arrangement.

Each thread can perform its own open() and obtain a distinct open file description. Locks acquired through those independent descriptions can then conflict even though the callers belong to the same process. This permits the kernel locking mechanism to represent contention between threads when the application intentionally gives each thread a separate open file description.

The inverse is equally important. Passing duplicated descriptors to two threads does not create independent lock owners. If both descriptors refer to the same open file description, the kernel treats their OFD locks as belonging to the same ownership domain. Correct synchronization therefore depends on descriptor provenance, not merely on having different integer descriptor values.

Range semantics still permit partial conversion

OFD locks retain byte-range semantics. A struct flock identifies the range using l_whence, l_start, and l_len. A zero l_len extends the range from its starting position through the end of the file, including future growth covered by that definition.

A new lock request through the same open file description can convert an already locked region. Depending on the overlap, the kernel may split, shrink, or coalesce lock regions. This means the application should model lock state as ranges, not as a single Boolean attached to a file.

For example, replacing a write lock on bytes 0 through 4095 with a read lock on bytes 1024 through 2047 changes only the requested subrange. The surrounding regions can remain write-locked under the same ownership domain.

Range conversion is convenient for storage engines that partition a file into independently coordinated regions, but it also makes cleanup logic sensitive to exact offsets. An unlock request affects the specified range rather than implicitly clearing every OFD lock associated with the description.

Advisory status remains unchanged

OFD ownership semantics do not turn advisory locking into mandatory access control. A cooperating process that uses compatible locking calls can coordinate around the protected ranges. A process that simply issues ordinary reads or writes is not automatically blocked solely because an OFD advisory lock exists.

The lock therefore represents a synchronization contract among participants that honor the protocol. It is not an authorization boundary against arbitrary code with access to the file.

This distinction also affects failure analysis. A data race caused by a component that ignores the locking protocol is not evidence that the kernel lost an OFD lock. The protocol itself was bypassed.

Filesystem and network-filesystem behavior also deserves explicit validation for a deployment target. Applications should not infer identical remote locking behavior from local filesystem behavior merely because the same fcntl() interface is present.

OFD and traditional record locks share a conflict space

OFD locks are not an isolated parallel universe beside traditional F_SETLK locks. On Linux, incompatible OFD and traditional record locks conflict with each other even when the same process attempts both on the same file.

That interaction matters during migrations. Replacing only part of a codebase with OFD operations can create contention between old and new paths. The ownership rules differ, but the byte ranges can still block one another.

F_OFD_GETLK can inspect a conflicting lock. When the conflict is an OFD lock, l_pid is reported as -1, because the owner is not represented by a process ID. For OFD operations supplied by the caller, l_pid must be set to zero.

The absence of a PID is not missing bookkeeping. It reflects the ownership model: an open file description can be referenced by multiple descriptors and can cross a process boundary through inheritance.

Blocking acquisition has a different deadlock boundary

F_OFD_SETLK performs a nonblocking acquisition and fails when an incompatible lock prevents placement. F_OFD_SETLKW waits for the conflicting lock to become available and can return with EINTR when interrupted by a caught signal.

A critical implementation distinction is deadlock detection. Linux performs deadlock detection for blocking traditional process-associated record locks, but its OFD locking implementation does not provide equivalent deadlock detection. Two execution paths that acquire independent OFD locks in opposite order can therefore wait indefinitely if the application supplies no timeout, cancellation path, or global ordering discipline.

The kernel primitive does not replace lock-order design. Systems using multiple ranges or multiple files still need a deterministic acquisition order or another mechanism that bounds circular waits.

Lifetime semantics are the primary design choice

OFD locks are most valuable when lock ownership should follow a shared open file description rather than a process. That choice makes duplicated descriptors cooperate, lets inherited references preserve ownership across fork(), and permits independent open() calls to establish distinct owners even inside one process.

Those properties also define the failure modes. An unexpected inherited descriptor can keep a lock alive. Two descriptors that look independent can actually share one owner after duplication. Two descriptors for the same inode can contend when they came from separate opens. Blocking acquisition can participate in a deadlock that the kernel does not diagnose.

The API is still advisory byte-range locking. Its distinctive contract lies in the object that owns the lock. Once ownership is attached to the open file description, descriptor topology becomes part of concurrency correctness.