Landin library reference discuss source

platform/posix/files.ldn

1--  The rest of POSIX's file system in Landin types: reading and writing at
2--  an offset, sizes and synchronizing, renaming, hard and symbolic links,
3--  permission bits, owners and file times, advisory locks of both kinds
4--  the systems have, and the working directory's path.  Each path is a
5--  host string, terminated beneath the caller as `open_at`'s is, and each
6--  call relative to a directory takes the directory as `at`, or
7--  `working_directory`.  Written once here, over the `system_*` routines
8--  linux.ldn and libc.ldn give.
9
10import core/text
11
12--  The largest offset a call takes, C's `off_t` being signed.
13largest_offset: u64 = 0x7FFFFFFFFFFFFFFF
14
15--- Read at most `lenof into` bytes of `source`, starting `offset` bytes
16--- into the file, POSIX's `pread`: the count read, zero at or past the
17--- file's end, or the errno, `espipe` for a pipe or a socket and `einval`
18--- for an offset past what the system counts.  The file's position, which
19--- `read` and `write` use, does not move.
20public link(symbol: "_landin_posix_read_at") read_at:
21    (source: descriptor, offset: u64, into: []mut u8)
22    -> (count: usize) ! errno =
23    fail einval when offset > largest_offset
24    count = try system_read_at(source, offset, into)
25end read_at
26
27--- Write at most `lenof bytes` bytes to `to`, starting `offset` bytes
28--- into the file, POSIX's `pwrite`: the count accepted, which may be
29--- short, or the errno.  A write past the end extends the file, and the
30--- bytes between its old end and `offset` read as zeros, a hole that need
31--- take no room on the device.  The position does not move.  On a file
32--- opened `append`, Linux writes at the end whatever `offset` says, and
33--- Darwin and FreeBSD write at `offset`: do not write at an offset to a
34--- file opened to append.
35public link(symbol: "_landin_posix_write_at") write_at:
36    (to: descriptor, offset: u64, bytes: []u8) -> (count: usize) ! errno =
37    fail einval when offset > largest_offset
38    count = try system_write_at(to, offset, bytes)
39end write_at
40
41--- Give the file open as `opened`, which must be open for writing, the
42--- size `size`, POSIX's `ftruncate`: what lies beyond it is cut off, and a
43--- larger size extends the file with zeros.  `einval` for a size past what
44--- the system counts or a file not open for writing.
45public link(symbol: "_landin_posix_truncate") truncate:
46    (opened: descriptor, size: u64) -> none ! errno =
47    fail einval when size > largest_offset
48    try system_truncate(opened, size)
49end truncate
50
51--- Hand what the system holds of the file open as `opened` to its device
52--- before returning, POSIX's `fsync`: its data and its status, or with
53--- `data_only` its data and only so much of its status as reading the data
54--- back needs, its size among it, `fdatasync`.  On Darwin, whose `fsync`
55--- leaves what the device caches in the device, both ask the device to
56--- write that too, `F_FULLFSYNC`, and fall back to `fsync` on a file system
57--- that refuses it.
58public link(symbol: "_landin_posix_synchronize") synchronize:
59    (opened: descriptor, data_only: bool) -> none ! errno =
60    try system_synchronize(opened, data_only)
61end synchronize
62
63--- Give the file `old` names, relative to `old_at`, the name `new`,
64--- relative to `new_at`, POSIX's `renameat`, in one step: no process sees
65--- both names or neither.  A file already named `new` is replaced in the
66--- same step, a directory only by a directory and only when it is empty.
67--- The errno: `enoent` when `old` or `new`'s parent is missing, `exdev`
68--- when the two are on different file systems, which a rename never
69--- crosses, `enotempty` or `eexist` for a directory that is not empty,
70--- `eisdir` for a file onto a directory and `enotdir` the other way, and
71--- `einval` for a directory into itself.
72public link(symbol: "_landin_posix_rename_at") rename_at:
73    (old_at: descriptor, old: text.host_string, new_at: descriptor,
74     new: text.host_string) -> none ! errno =
75    mut old_room: path_room = zeroed
76    old_path: ptr u8 = try terminate(old, old_room[0..<lenof old_room])
77    mut new_room: path_room = zeroed
78    new_path: ptr u8 = try terminate(new, new_room[0..<lenof new_room])
79    try system_rename(old_at, old_path, new_at, new_path)
80end rename_at
81
82--- Give the file `old` names, relative to `old_at`, a second name `new`,
83--- relative to `new_at`, POSIX's `linkat`, a hard link: both names are one
84--- file.  A final symbolic link of `old` is linked itself, not followed.
85--- The errno: `eexist` when `new` is taken, `exdev` across file systems,
86--- `eperm` for a directory, which no system links, and `emlink` for a file
87--- with as many names as it may have.
88public link(symbol: "_landin_posix_link_at") link_at:
89    (old_at: descriptor, old: text.host_string, new_at: descriptor,
90     new: text.host_string) -> none ! errno =
91    mut old_room: path_room = zeroed
92    old_path: ptr u8 = try terminate(old, old_room[0..<lenof old_room])
93    mut new_room: path_room = zeroed
94    new_path: ptr u8 = try terminate(new, new_room[0..<lenof new_room])
95    try system_link(old_at, old_path, new_at, new_path)
96end link_at
97
98--- Make `path`, relative to `at`, a symbolic link whose target is
99--- `target`, POSIX's `symlinkat`: a path the system follows when it meets
100--- the link, relative to the link's own directory unless it begins with
101--- `/`, which need not name anything.  `eexist` when `path` is taken.
102public link(symbol: "_landin_posix_symbolic_link_at") symbolic_link_at:
103    (target: text.host_string, at: descriptor, path: text.host_string)
104    -> none ! errno =
105    mut target_room: path_room = zeroed
106    pointed: ptr u8 = try terminate(target, target_room[0..<lenof target_room])
107    mut room: path_room = zeroed
108    terminated: ptr u8 = try terminate(path, room[0..<lenof room])
109    try system_symbolic_link(pointed, at, terminated)
110end symbolic_link_at
111
112--- The target of the symbolic link `path` names, relative to `at`,
113--- POSIX's `readlinkat`, copied into `into` and lent from it: `einval` when
114--- `path` is no symbolic link, and `erange` when the target fills `into`,
115--- since a target as long as the buffer may have been cut.
116public link(symbol: "_landin_posix_read_link_at") read_link_at:
117    (at: descriptor, path: text.host_string, into: []mut u8)
118    -> (target: text.host_string from into) ! errno =
119    mut room: path_room = zeroed
120    terminated: ptr u8 = try terminate(path, room[0..<lenof room])
121    fail erange when lenof into == 0
122    count: usize = try system_read_link(at, terminated, into)
123    fail erange when count >= lenof into
124    target = text.host_over(into[0..<count])
125end read_link_at
126
127--- Set the permission bits of the file `path` names, relative to `at`, to
128--- `permissions`, POSIX's `fchmodat`, following a final symbolic link,
129--- whose own bits no system gives a meaning: `einval` for bits beyond
130--- `0o7777`, and `eperm` when the process neither owns the file nor may
131--- override that.
132public link(symbol: "_landin_posix_change_mode_at") change_mode_at:
133    (at: descriptor, path: text.host_string, permissions: u32)
134    -> none ! errno =
135    fail einval when permissions > 0o7777
136    mut room: path_room = zeroed
137    terminated: ptr u8 = try terminate(path, room[0..<lenof room])
138    try system_change_mode_at(at, terminated, permissions)
139end change_mode_at
140
141--- Set the permission bits of the file open as `opened`, POSIX's
142--- `fchmod`, as `change_mode_at` sets a path's.
143public link(symbol: "_landin_posix_change_mode") change_mode:
144    (opened: descriptor, permissions: u32) -> none ! errno =
145    fail einval when permissions > 0o7777
146    try system_change_mode(opened, permissions)
147end change_mode
148
149--- Set the owner and group of the file `path` names, relative to `at`,
150--- POSIX's `fchownat`, `unchanged_user` and `unchanged_group` keeping
151--- either; with `follow` false a final symbolic link's own.  `eperm`
152--- unless the process may: only a privileged one gives a file away, and
153--- an owner may give it only to a group the owner is in.  A system may
154--- clear the set-user-ID and set-group-ID bits as it changes the owner.
155public link(symbol: "_landin_posix_change_owner_at") change_owner_at:
156    (at: descriptor, path: text.host_string, user: user_id, group: group_id,
157     follow: bool) -> none ! errno =
158    mut room: path_room = zeroed
159    terminated: ptr u8 = try terminate(path, room[0..<lenof room])
160    try system_change_owner_at(at, terminated, user, group, follow)
161end change_owner_at
162
163--- Set the owner and group of the file open as `opened`, POSIX's
164--- `fchown`, as `change_owner_at` sets a path's.
165public link(symbol: "_landin_posix_change_owner") change_owner:
166    (opened: descriptor, user: user_id, group: group_id) -> none ! errno =
167    try system_change_owner(opened, user, group)
168end change_owner
169
170--- Set when the file `path` names, relative to `at`, was last read and
171--- last written, POSIX's `utimensat`, to `accessed` and `modified`; with
172--- `follow` false a final symbolic link's own.  The system sets the time
173--- it was last changed to now, which nothing else can.  `einval` for
174--- nanoseconds of a second or more; a file system keeping coarser times
175--- rounds them down.
176public link(symbol: "_landin_posix_set_times_at") set_times_at:
177    (at: descriptor, path: text.host_string, accessed: time_value,
178     modified: time_value, follow: bool) -> none ! errno =
179    fail einval when accessed.nanoseconds >= 1000000000
180      or modified.nanoseconds >= 1000000000
181    mut room: path_room = zeroed
182    terminated: ptr u8 = try terminate(path, room[0..<lenof room])
183    try system_set_times_at(at, terminated, accessed, modified, follow)
184end set_times_at
185
186--- Set the two times of the file open as `opened`, POSIX's `futimens`, as
187--- `set_times_at` sets a path's.
188public link(symbol: "_landin_posix_set_times") set_times:
189    (opened: descriptor, accessed: time_value, modified: time_value)
190    -> none ! errno =
191    fail einval when accessed.nanoseconds >= 1000000000
192      or modified.nanoseconds >= 1000000000
193    try system_set_times(opened, accessed, modified)
194end set_times
195
196--- The status of the file open as `opened`, POSIX's `fstat`, as
197--- `status_at` gives a path's.
198public link(symbol: "_landin_posix_status_of") status_of:
199    (opened: descriptor) -> (result: status) ! errno =
200    result = try system_status_of(opened)
201end status_of
202
203--- The working directory's absolute path, POSIX's `getcwd`, copied into
204--- `into` and lent from it: `erange` when it does not fit with the NUL
205--- the system adds, and `enoent` when the directory has been removed.
206--- The working directory is the process's, so this module never changes
207--- it: beside threads a change would move every relative path at once,
208--- and a path relative to a directory open as `at` serves instead.
209public link(symbol: "_landin_posix_current_directory") current_directory:
210    (into: []mut u8) -> (path: text.host_string from into) ! errno =
211    fail erange when lenof into == 0
212    count: usize = try system_current_directory(into)
213    fail erange when count > lenof into
214    path = text.host_over(into[0..<count])
215end current_directory
216
217--- A shared lock: any number of holders, none of them exclusive.
218public shared_lock: atom
219--- An exclusive lock: one holder, and no shared one beside it.
220public exclusive_lock: atom
221--- What a lock is: `shared_lock` or `exclusive_lock`.
222public lock_kind: type = shared_lock | exclusive_lock
223
224--- Take a lock of `kind` on the whole file open as `opened`, waiting for
225--- it when `wait` is true, BSD's `flock`, which Linux, Darwin and FreeBSD
226--- all give: or the errno, `eagain` when another holds a lock in the way
227--- and `wait` is false, `eintr` when a signal came while waiting.  The
228--- lock is advisory, binding only programs that ask for it, and belongs to
229--- the open file `opened` is a descriptor of: a second `open_at` of the
230--- same file in the same process is another holder, which it stands in
231--- the way of, while a duplicate of `opened`, and a child that inherited
232--- it, shares it.  It lasts until `unlock`, or until every descriptor of
233--- that open file is closed.  Taking it again changes its kind, and the
234--- change is not atomic: another holder may come between.  Use one kind of
235--- lock on a file, `lock` or `lock_range`, since how the two see each
236--- other differs between systems.
237public link(symbol: "_landin_posix_lock") lock:
238    (opened: descriptor, kind: lock_kind, wait: bool) -> none ! errno =
239    try system_lock(opened, kind, wait)
240end lock
241
242--- Give up the lock `lock` took on the file open as `opened`.
243public link(symbol: "_landin_posix_unlock") unlock:
244    (opened: descriptor) -> none ! errno =
245    try system_unlock(opened)
246end unlock
247
248--- Take a lock of `kind` on `length` bytes of the file open as `opened`
249--- from `start`, a `length` of zero reaching however far the file ever
250--- will, POSIX's record lock, `fcntl`'s `F_SETLK`, or `F_SETLKW` with
251--- `wait`: or the errno, `eagain` or `eacces`, as the system chooses, when
252--- another process holds one in the way and `wait` is false, `edeadlk`
253--- when waiting would deadlock, and `ebadf` for a shared lock on a file not
254--- open for reading or an exclusive one on a file not open for writing.
255---
256--- A record lock belongs to the process, not to a descriptor: the process
257--- never stands in its own way, through any descriptor, and a lock over
258--- one it already holds replaces that part of it.  Closing any descriptor
259--- of the file, even one opened elsewhere in the program for a moment,
260--- gives up every record lock the process holds on it.  A child does not
261--- inherit one.  `lock` has none of these hazards and no byte ranges.
262public link(symbol: "_landin_posix_lock_range") lock_range:
263    (opened: descriptor, kind: lock_kind, start: u64, length: u64,
264     wait: bool) -> none ! errno =
265    fail einval when start > largest_offset or length > largest_offset
266    try system_lock_range(opened, kind, start, length, wait, true)
267end lock_range
268
269--- Give up the process's record locks on `length` bytes of the file open
270--- as `opened` from `start`, as `lock_range` counts them.
271public link(symbol: "_landin_posix_unlock_range") unlock_range:
272    (opened: descriptor, start: u64, length: u64) -> none ! errno =
273    fail einval when start > largest_offset or length > largest_offset
274    try system_lock_range(opened, shared_lock, start, length, false, false)
275end unlock_range