Skip to content

Commit 761c6ad

Browse files
committed
Share file offset across processes
This change ensures that if a file descriptor for an open disk file gets shared by multiple processes within a process tree, then lseek() changes will be visible across processes, and read() / write() are synchronized. Note this only applies to Windows, because UNIX kernels already do this.
1 parent a80ab3f commit 761c6ad

File tree

15 files changed

+256
-63
lines changed

15 files changed

+256
-63
lines changed

libc/calls/close-nt.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
│ PERFORMANCE OF THIS SOFTWARE. │
1818
╚─────────────────────────────────────────────────────────────────────────────*/
1919
#include "libc/calls/internal.h"
20-
#include "libc/intrin/fds.h"
2120
#include "libc/calls/syscall-nt.internal.h"
2221
#include "libc/calls/syscall_support-nt.internal.h"
22+
#include "libc/intrin/fds.h"
2323
#include "libc/intrin/weaken.h"
2424
#include "libc/nt/enum/filetype.h"
2525
#include "libc/nt/files.h"
2626
#include "libc/nt/runtime.h"
27+
#include "libc/runtime/runtime.h"
2728
#include "libc/runtime/zipos.internal.h"
2829
#include "libc/sock/syscall_fd.internal.h"
2930
#include "libc/sysv/consts/o.h"
@@ -64,5 +65,7 @@ textwindows int sys_close_nt(int fd, int fildes) {
6465
default:
6566
break;
6667
}
68+
if (f->shared && !f->isdup)
69+
munmap(f->shared, sizeof(struct Cursor));
6770
return CloseHandle(f->handle) ? 0 : __winerr();
6871
}

libc/calls/dup-nt.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ static textwindows int sys_dup_nt_impl(int oldfd, int newfd, int flags,
8282

8383
g_fds.p[newfd] = g_fds.p[oldfd];
8484
g_fds.p[newfd].handle = handle;
85+
g_fds.p[newfd].isdup = true;
86+
g_fds.p[oldfd].isdup = true; // TODO(jart): is it possible to avoid leak?
8587
if (flags & _O_CLOEXEC) {
8688
g_fds.p[newfd].flags |= _O_CLOEXEC;
8789
} else {

libc/calls/fcntl-nt.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
#include "libc/calls/calls.h"
2121
#include "libc/calls/createfileflags.internal.h"
2222
#include "libc/calls/internal.h"
23-
#include "libc/intrin/fds.h"
2423
#include "libc/calls/struct/flock.h"
2524
#include "libc/calls/struct/sigset.internal.h"
2625
#include "libc/calls/syscall-nt.internal.h"
2726
#include "libc/calls/syscall_support-nt.internal.h"
2827
#include "libc/calls/wincrash.internal.h"
2928
#include "libc/errno.h"
29+
#include "libc/intrin/fds.h"
3030
#include "libc/intrin/kprintf.h"
3131
#include "libc/intrin/weaken.h"
3232
#include "libc/limits.h"
@@ -151,7 +151,7 @@ static textwindows int sys_fcntl_nt_lock(struct Fd *f, int fd, int cmd,
151151
case SEEK_SET:
152152
break;
153153
case SEEK_CUR:
154-
off = f->pointer + off;
154+
off = f->shared->pointer + off;
155155
break;
156156
case SEEK_END: {
157157
int64_t size;
@@ -351,9 +351,14 @@ textwindows int sys_fcntl_nt(int fd, int cmd, uintptr_t arg) {
351351
}
352352
rc = 0;
353353
} else if (cmd == F_SETLK || cmd == F_SETLKW || cmd == F_GETLK) {
354-
pthread_mutex_lock(&g_locks.mu);
355-
rc = sys_fcntl_nt_lock(g_fds.p + fd, fd, cmd, arg);
356-
pthread_mutex_unlock(&g_locks.mu);
354+
struct Fd *f = g_fds.p + fd;
355+
if (f->shared) {
356+
pthread_mutex_lock(&g_locks.mu);
357+
rc = sys_fcntl_nt_lock(f, fd, cmd, arg);
358+
pthread_mutex_unlock(&g_locks.mu);
359+
} else {
360+
rc = ebadf();
361+
}
357362
} else if (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC) {
358363
rc = sys_fcntl_nt_dupfd(fd, cmd, arg);
359364
} else {

libc/calls/lseek-nt.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ static textwindows int64_t GetPosition(struct Fd *f, int whence) {
3131
case SEEK_SET:
3232
return 0;
3333
case SEEK_CUR:
34-
return f->pointer;
34+
return f->shared->pointer;
3535
case SEEK_END: {
3636
struct NtByHandleFileInformation wst;
3737
if (!GetFileInformationByHandle(f->handle, &wst)) {
@@ -67,11 +67,14 @@ textwindows int64_t sys_lseek_nt(int fd, int64_t offset, int whence) {
6767
} else if (__isfdkind(fd, kFdFile)) {
6868
struct Fd *f = g_fds.p + fd;
6969
int filetype = GetFileType(f->handle);
70-
if (filetype != kNtFileTypePipe && filetype != kNtFileTypeChar) {
70+
if (filetype != kNtFileTypePipe && //
71+
filetype != kNtFileTypeChar && //
72+
f->shared) {
7173
int64_t res;
72-
if ((res = Seek(f, offset, whence)) != -1) {
73-
f->pointer = res;
74-
}
74+
__fd_lock(f);
75+
if ((res = Seek(f, offset, whence)) != -1)
76+
f->shared->pointer = res;
77+
__fd_unlock(f);
7578
return res;
7679
} else {
7780
return espipe();

libc/calls/open-nt.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ static textwindows int sys_open_nt_file(int dirfd, const char *file,
138138
int64_t handle;
139139
if ((handle = sys_open_nt_impl(dirfd, file, flags, mode,
140140
kNtFileFlagOverlapped)) != -1) {
141+
g_fds.p[fd].shared = __cursor_new();
141142
g_fds.p[fd].handle = handle;
142143
g_fds.p[fd].kind = kFdFile;
143144
g_fds.p[fd].flags = flags;
@@ -170,14 +171,14 @@ static textwindows int sys_open_nt_no_handle(int fd, int flags, int mode,
170171

171172
static textwindows int sys_open_nt_dup(int fd, int flags, int mode, int oldfd) {
172173
int64_t handle;
173-
if (!__isfdopen(oldfd)) {
174+
if (!__isfdopen(oldfd))
174175
return enoent();
175-
}
176176
if (DuplicateHandle(GetCurrentProcess(), g_fds.p[oldfd].handle,
177177
GetCurrentProcess(), &handle, 0, true,
178178
kNtDuplicateSameAccess)) {
179179
g_fds.p[fd] = g_fds.p[oldfd];
180180
g_fds.p[fd].handle = handle;
181+
g_fds.p[fd].isdup = true;
181182
g_fds.p[fd].mode = mode;
182183
if (!sys_fcntl_nt_setfl(fd, flags)) {
183184
return fd;

libc/calls/readwrite-nt.c

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
#include "libc/calls/createfileflags.internal.h"
2020
#include "libc/calls/internal.h"
2121
#include "libc/calls/sig.internal.h"
22-
#include "libc/intrin/fds.h"
2322
#include "libc/calls/struct/sigset.h"
2423
#include "libc/calls/syscall_support-nt.internal.h"
24+
#include "libc/intrin/fds.h"
2525
#include "libc/intrin/weaken.h"
2626
#include "libc/nt/enum/filetype.h"
2727
#include "libc/nt/errors.h"
@@ -51,29 +51,28 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset,
5151
uint32_t exchanged;
5252
struct Fd *f = g_fds.p + fd;
5353

54-
// win32 i/o apis generally take 32-bit values thus we implicitly
55-
// truncate outrageously large sizes. linux actually does it too!
56-
size = MIN(size, 0x7ffff000);
57-
5854
// pread() and pwrite() perform an implicit lseek() operation, so
5955
// similar to the lseek() system call, they too raise ESPIPE when
6056
// operating on a non-seekable file.
6157
bool pwriting = offset != -1;
62-
bool seekable =
63-
(f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk) ||
64-
f->kind == kFdDevNull || f->kind == kFdDevRandom;
65-
if (pwriting && !seekable) {
58+
bool isdisk = f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk;
59+
bool seekable = isdisk || f->kind == kFdDevNull || f->kind == kFdDevRandom;
60+
if (pwriting && !seekable)
6661
return espipe();
67-
}
62+
63+
// determine if we need to lock a file descriptor across processes
64+
bool locked = isdisk && !pwriting && f->shared;
65+
if (locked)
66+
__fd_lock(f);
6867

6968
// when a file is opened in overlapped mode win32 requires that we
7069
// take over full responsibility for managing our own file pointer
7170
// which is fine, because the one win32 has was never very good in
7271
// the sense that it behaves so differently from linux, that using
7372
// win32 i/o required more compatibilty toil than doing it by hand
7473
if (!pwriting) {
75-
if (seekable) {
76-
offset = f->pointer;
74+
if (seekable && f->shared) {
75+
offset = f->shared->pointer;
7776
} else {
7877
offset = 0;
7978
}
@@ -82,8 +81,11 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset,
8281
RestartOperation:
8382
bool eagained = false;
8483
// check for signals and cancelation
85-
if (_check_cancel() == -1)
84+
if (_check_cancel() == -1) {
85+
if (locked)
86+
__fd_unlock(f);
8687
return -1; // ECANCELED
88+
}
8789
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
8890
goto HandleInterrupt;
8991
}
@@ -114,40 +116,49 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset,
114116
}
115117
ok = true;
116118
}
117-
if (ok) {
119+
if (ok)
118120
ok = GetOverlappedResult(handle, &overlap, &exchanged, true);
119-
}
120121
CloseHandle(overlap.hEvent);
121122

122123
// if i/o succeeded then return its result
123124
if (ok) {
124-
if (!pwriting && seekable) {
125-
f->pointer = offset + exchanged;
126-
}
125+
if (!pwriting && seekable && f->shared)
126+
f->shared->pointer = offset + exchanged;
127+
if (locked)
128+
__fd_unlock(f);
127129
return exchanged;
128130
}
129131

130132
// only raise EINTR or EAGAIN if I/O got canceled
131133
if (GetLastError() == kNtErrorOperationAborted) {
132134
// raise EAGAIN if it's due to O_NONBLOCK mmode
133135
if (eagained) {
136+
if (locked)
137+
__fd_unlock(f);
134138
return eagain();
135139
}
136140
// otherwise it must be due to a kill() via __sig_cancel()
137141
if (_weaken(__sig_relay) && (sig = _weaken(__sig_get)(waitmask))) {
138142
HandleInterrupt:
143+
if (locked)
144+
__fd_unlock(f);
139145
int handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
140146
if (_check_cancel() == -1)
141147
return -1; // possible if we SIGTHR'd
148+
if (locked)
149+
__fd_lock(f);
142150
// read() is @restartable unless non-SA_RESTART hands were called
143-
if (!(handler_was_called & SIG_HANDLED_NO_RESTART)) {
151+
if (!(handler_was_called & SIG_HANDLED_NO_RESTART))
144152
goto RestartOperation;
145-
}
146153
}
154+
if (locked)
155+
__fd_unlock(f);
147156
return eintr();
148157
}
149158

150159
// read() and write() have generally different error-handling paths
160+
if (locked)
161+
__fd_unlock(f);
151162
return -2;
152163
}
153164

libc/intrin/fds.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
1717
│ PERFORMANCE OF THIS SOFTWARE. │
1818
╚─────────────────────────────────────────────────────────────────────────────*/
19+
#include "libc/intrin/fds.h"
1920
#include "libc/calls/internal.h"
2021
#include "libc/calls/state.internal.h"
2122
#include "libc/calls/ttydefaults.h"
2223
#include "libc/dce.h"
2324
#include "libc/intrin/atomic.h"
2425
#include "libc/intrin/extend.h"
25-
#include "libc/intrin/fds.h"
2626
#include "libc/intrin/kprintf.h"
2727
#include "libc/intrin/nomultics.h"
2828
#include "libc/intrin/pushpop.h"
@@ -40,6 +40,7 @@
4040
#include "libc/sock/sock.h"
4141
#include "libc/sysv/consts/map.h"
4242
#include "libc/sysv/consts/o.h"
43+
#include "libc/sysv/consts/prot.h"
4344
#include "libc/thread/thread.h"
4445

4546
#define OPEN_MAX 16
@@ -156,12 +157,29 @@ textstartup void __init_fds(int argc, char **argv, char **envp) {
156157
f->kind = kind;
157158
f->flags = flags;
158159
f->mode = mode;
159-
f->pointer = pointer;
160160
f->type = type;
161161
f->family = family;
162162
f->protocol = protocol;
163163
atomic_store_explicit(&fds->f, fd + 1, memory_order_relaxed);
164+
165+
//
166+
// - v1 abi: This field was originally the file pointer.
167+
//
168+
// - v2 abi: This field is the negated shared memory address.
169+
//
170+
if (f->kind == kFdFile) {
171+
if (pointer < 0) {
172+
f->shared = (struct Cursor *)(uintptr_t)-pointer;
173+
} else if ((f->shared = __cursor_new())) {
174+
f->shared->pointer = pointer;
175+
}
176+
}
164177
}
165178
}
179+
for (int i = 0; i < 3; ++i) {
180+
struct Fd *f = fds->p + i;
181+
if (f->kind == kFdFile && !f->shared)
182+
f->shared = __cursor_new();
183+
}
166184
}
167185
}

libc/intrin/fds.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_
22
#define COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_
3+
#include "libc/atomic.h"
4+
#include "libc/thread/thread.h"
35
COSMOPOLITAN_C_START_
46

57
#define kFdEmpty 0
@@ -13,19 +15,25 @@ COSMOPOLITAN_C_START_
1315
#define kFdDevNull 9
1416
#define kFdDevRandom 10
1517

18+
struct Cursor {
19+
pthread_mutex_t lock;
20+
long pointer;
21+
};
22+
1623
struct Fd {
1724
char kind;
25+
bool isdup;
1826
bool isbound;
1927
unsigned flags;
2028
unsigned mode;
2129
long handle;
22-
long pointer;
2330
int family;
2431
int type;
2532
int protocol;
2633
unsigned rcvtimeo; /* millis; 0 means wait forever */
2734
unsigned sndtimeo; /* millis; 0 means wait forever */
2835
void *connect_op;
36+
struct Cursor *shared;
2937
};
3038

3139
struct Fds {
@@ -34,5 +42,9 @@ struct Fds {
3442
struct Fd *p, *e;
3543
};
3644

45+
void __fd_lock(struct Fd *);
46+
void __fd_unlock(struct Fd *);
47+
struct Cursor *__cursor_new(void);
48+
3749
COSMOPOLITAN_C_END_
3850
#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_ */

libc/intrin/fds_lock.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
│ PERFORMANCE OF THIS SOFTWARE. │
1818
╚─────────────────────────────────────────────────────────────────────────────*/
1919
#include "libc/calls/state.internal.h"
20+
#include "libc/intrin/fds.h"
21+
#include "libc/runtime/runtime.h"
2022
#include "libc/thread/thread.h"
2123

2224
void __fds_lock(void) {
@@ -26,3 +28,23 @@ void __fds_lock(void) {
2628
void __fds_unlock(void) {
2729
pthread_mutex_unlock(&__fds_lock_obj);
2830
}
31+
32+
void __fd_lock(struct Fd *f) {
33+
pthread_mutex_lock(&f->shared->lock);
34+
}
35+
36+
void __fd_unlock(struct Fd *f) {
37+
pthread_mutex_unlock(&f->shared->lock);
38+
}
39+
40+
struct Cursor *__cursor_new(void) {
41+
struct Cursor *c;
42+
if ((c = _mapshared(sizeof(struct Cursor)))) {
43+
pthread_mutexattr_t attr;
44+
pthread_mutexattr_init(&attr);
45+
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
46+
pthread_mutex_init(&c->lock, &attr);
47+
pthread_mutexattr_destroy(&attr);
48+
}
49+
return c;
50+
}
File renamed without changes.

0 commit comments

Comments
 (0)