fix missing entries in fixed_point.ajla, long.ajla, longreal.ajla
[ajla.git] / os_util.c
blobb672fee7551cbbaae81e7e371b8ed9fa5c0e8745
1 /*
2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
9 * version.
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
19 #include "ajla.h"
21 #include "str.h"
22 #include "os.h"
23 #include "obj_reg.h"
25 #include "os_util.h"
27 bool os_read_file(const char *path, char **file, size_t *len, ajla_error_t *err)
29 handle_t h;
30 char buffer[192];
31 if (unlikely(!array_init_mayfail(char, file, len, err)))
32 return false;
33 h = os_open(dir_none, path, O_RDONLY, 0, err);
34 if (unlikely(!handle_is_valid(h)))
35 goto ret_false;
36 while (1) {
37 ssize_t rd = os_read(h, buffer, sizeof buffer, err);
38 if (unlikely(rd == OS_RW_ERROR))
39 goto ret_false;
40 if (unlikely(rd == OS_RW_WOULDBLOCK)) {
41 fatal_warning_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_SYSTEM_RETURNED_INVALID_DATA), err, "the read syscall tries to block");
42 goto ret_false;
44 if (unlikely(!rd))
45 break;
46 if (unlikely(!array_add_multiple_mayfail(char, file, len, buffer, rd, NULL, err)))
47 return false;
49 os_close(h);
50 return true;
52 ret_false:
53 mem_free(*file);
54 return false;
57 bool os_pread_all(handle_t h, char *ptr, size_t len, os_off_t offset, ajla_error_t *err)
59 while (len) {
60 size_t this_step = len;
61 ssize_t rd;
62 if (unlikely(this_step > (size_t)signed_maximum(int) + zero))
63 this_step = signed_maximum(int);
64 rd = os_pread(h, ptr, this_step, offset, err);
65 if (unlikely(rd == OS_RW_ERROR))
66 return false;
67 if (unlikely(rd == OS_RW_WOULDBLOCK)) {
68 fatal_warning_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_SYSTEM_RETURNED_INVALID_DATA), err, "the pread syscall tries to block");
69 return false;
71 if (unlikely(!rd)) {
72 fatal_warning_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_SYSTEM_RETURNED_INVALID_DATA), err, "zero-sized read");
73 return false;
75 ptr += rd;
76 len -= rd;
77 offset += rd;
79 return true;
82 bool os_write_all(handle_t h, const char *data, size_t len, ajla_error_t *err)
84 obj_registry_verify(OBJ_TYPE_HANDLE, (uintptr_t)h, file_line);
85 while (len) {
86 ssize_t wr;
87 size_t this_step = len;
88 if (unlikely(this_step > signed_maximum(int)))
89 this_step = signed_maximum(int);
90 wr = os_write(h, data, this_step, err);
91 if (unlikely(wr == OS_RW_ERROR))
92 return false;
93 if (unlikely(wr == OS_RW_WOULDBLOCK)) {
94 fatal_warning_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_SYSTEM_RETURNED_INVALID_DATA), err, "the write syscall tries to block");
95 return false;
97 data += wr;
98 len -= wr;
100 return true;
103 bool os_test_absolute_path(dir_handle_t dir, bool abs_path, ajla_error_t *err)
105 if (!dir_handle_is_valid(dir) && !abs_path) {
106 fatal_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_NON_ABSOLUTE_PATH), err, "non-absolute path");
107 return false;
109 return true;
112 #if defined(OS_DOS) || defined(OS_OS2) || defined(OS_WIN32)
114 bool os_path_is_absolute(const char *path)
116 if ((path[0] == '/' || path[0] == '\\') && (path[1] == '/' || path[1] == '\\'))
117 return true;
118 if ((path[0] & 0xDF) >= 'A' && (path[0] & 0xDF) <= 'Z' && path[1] == ':' && (path[2] == '/' || path[2] == '\\'))
119 return true;
120 return false;
123 char *os_join_paths(const char *dir, const char *path, bool trim_last_slash, ajla_error_t *err)
125 char *ptr;
126 size_t len;
127 bool is_abs = os_path_is_absolute(path);
129 if (unlikely(!os_test_absolute_path(cast_ptr(char *, dir), is_abs, err)))
130 return NULL;
132 if (unlikely(!array_init_mayfail(char, &ptr, &len, err)))
133 return NULL;
135 if (is_abs) {
136 if (unlikely(!array_add_multiple_mayfail(char, &ptr, &len, path, strlen(path), NULL, err)))
137 return NULL;
138 goto ret;
141 if (path[0] == '/' || path[0] == '\\') {
142 if (unlikely(!array_add_multiple_mayfail(char, &ptr, &len, dir, 2, NULL, err)))
143 return NULL;
144 } else {
145 if (unlikely(!array_add_multiple_mayfail(char, &ptr, &len, dir, strlen(dir), NULL, err)))
146 return NULL;
147 if (len && ptr[len - 1] != '/' && ptr[len - 1] != '\\')
148 if (unlikely(!array_add_mayfail(char, &ptr, &len, '/', NULL, err)))
149 return NULL;
151 if (unlikely(!array_add_multiple_mayfail(char, &ptr, &len, path, strlen(path), NULL, err)))
152 return NULL;
154 ret:
155 if (trim_last_slash) {
156 if (len > 3 && (ptr[len - 1] == '/' || ptr[len - 1] == '\\'))
157 len--;
160 if (unlikely(!array_add_mayfail(char, &ptr, &len, 0, NULL, err)))
161 return NULL;
163 array_finish(char, &ptr, &len);
165 return ptr;
168 #else
170 bool os_path_is_absolute(const char *path)
172 #if defined(OS_CYGWIN)
173 if ((path[0] & 0xdf) >= 'A' && (path[0] & 0xdf) <= 'Z' &&
174 path[1] == ':' &&
175 (path[2] == '/' || path[2] == '\\'))
176 return true;
177 #endif
178 return path[0] == '/';
181 char *os_join_paths(const char *base, const char *path, bool trim_last_slash, ajla_error_t *err)
183 char *res;
184 size_t res_l, base_l, path_l;
185 if (unlikely(!array_init_mayfail(char, &res, &res_l, err)))
186 return NULL;
187 if (!os_path_is_absolute(path)) {
188 base_l = strlen(base);
189 if (unlikely(!array_add_multiple_mayfail(char, &res, &res_l, base, base_l, NULL, err)))
190 return NULL;
191 if (res_l && !os_is_path_separator(res[res_l - 1])) {
192 if (unlikely(!array_add_mayfail(char, &res, &res_l, os_path_separator(), NULL, err)))
193 return NULL;
196 path_l = strlen(path);
197 if (unlikely(!array_add_multiple_mayfail(char, &res, &res_l, path, path_l, NULL, err)))
198 return NULL;
199 if (trim_last_slash) {
200 if (res_l > 1 && os_is_path_separator(res[res_l - 1]))
201 res_l--;
203 if (unlikely(!array_add_mayfail(char, &res, &res_l, 0, NULL, err)))
204 return NULL;
205 array_finish(char, &res, &res_l);
206 return res;
209 #endif
211 static char *os_get_directory(const char *env, const char *home, ajla_error_t *err)
213 const char *e;
214 char *cache_dir, *cache_dir_ajla;
215 if ((e = getenv(env)) && *e) {
216 cache_dir = str_dup(e, -1, err);
217 if (unlikely(!cache_dir))
218 return NULL;
219 } else {
220 const char *h = getenv("HOME");
221 if (unlikely(!h)) {
222 fatal_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_NOT_SUPPORTED), err, "no home directory");
223 return NULL;
226 cache_dir = os_join_paths(h, home, true, err);
227 if (unlikely(!cache_dir))
228 return NULL;
230 cache_dir_ajla = os_join_paths(cache_dir, "ajla", true, err);
231 if (unlikely(!cache_dir_ajla)) {
232 mem_free(cache_dir);
233 return NULL;
235 mem_free(cache_dir);
236 return cache_dir_ajla;
239 char *os_get_directory_cache(ajla_error_t *err)
241 #if defined(OS_DOS) || defined(OS_OS2) || defined(OS_WIN32)
242 return os_get_directory("TEMP", "cache", err);
243 #else
244 return os_get_directory("XDG_CACHE_HOME", ".cache", err);
245 #endif
248 static bool os_test_make_dir(const char *path, size_t path_len, ajla_error_t *err)
250 ajla_error_t mkdir_err;
251 char *path_cpy;
252 path_cpy = str_dup(path, path_len, err);
253 if (unlikely(!path_cpy))
254 return false;
255 if (unlikely(!os_dir_action(os_cwd, path_cpy, IO_Action_Mk_Dir, 0700, 0, 0, NULL, &mkdir_err))) {
256 if (likely(mkdir_err.error_type == AJLA_ERROR_SYSTEM) && likely(mkdir_err.error_aux == SYSTEM_ERROR_EEXIST)) {
257 mem_free(path_cpy);
258 return true;
260 fatal_mayfail(mkdir_err, err, "unable to make directory '%s'", path_cpy);
261 mem_free(path_cpy);
262 return false;
264 mem_free(path_cpy);
265 return true;
268 bool os_create_directory_parents(const char *path, ajla_error_t *err)
270 ajla_error_t sink;
271 size_t i;
272 if (os_test_make_dir(path, strlen(path), &sink))
273 return true;
274 i = 0;
275 while (os_is_path_separator(path[i]))
276 i++;
277 if (!i && os_path_is_absolute(path)) {
278 while (path[i] && !os_is_path_separator(path[i]))
279 i++;
280 while (os_is_path_separator(path[i]))
281 i++;
283 while (path[i]) {
284 while (path[i] && !os_is_path_separator(path[i]))
285 i++;
286 while (os_is_path_separator(path[i]))
287 i++;
288 if (unlikely(!os_test_make_dir(path, i, err)))
289 return false;
291 return true;
294 bool os_write_atomic(const char *path, const char *filename, const char *data, size_t data_len, ajla_error_t *err)
296 ajla_error_t open_err;
297 uint32_t count = 0;
298 char tmp_file[10 + 4 + 1];
299 char *tmp_ptr;
300 dir_handle_t dir;
301 handle_t h;
302 sig_state_t set;
304 dir = os_dir_open(os_cwd, path, 0, &open_err);
305 if (unlikely(!dir_handle_is_valid(dir))) {
306 if (unlikely(!os_create_directory_parents(path, err)))
307 return false;
308 dir = os_dir_open(os_cwd, path, 0, err);
309 if (unlikely(!dir_handle_is_valid(dir)))
310 return false;
313 try_next_file:
314 tmp_ptr = tmp_file;
315 str_add_unsigned(&tmp_ptr, NULL, count, 10);
316 strcpy(tmp_ptr, ".tmp");
318 os_block_signals(&set);
320 h = os_open(dir, tmp_file, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600, &open_err);
321 if (unlikely(!handle_is_valid(h))) {
322 os_unblock_signals(&set);
323 if (likely(open_err.error_type == AJLA_ERROR_SYSTEM) && likely(open_err.error_aux == SYSTEM_ERROR_EEXIST)) {
324 if (++count)
325 goto try_next_file;
327 fatal_mayfail(open_err, err, "unable to create file '%s'", tmp_file);
328 os_dir_close(dir);
329 return false;
332 if (unlikely(!os_write_all(h, data, data_len, err))) {
333 os_close(h);
334 os_dir_action(dir, tmp_file, IO_Action_Rm, 0, 0, 0, NULL, &open_err);
335 os_unblock_signals(&set);
336 os_dir_close(dir);
337 return false;
340 os_close(h);
342 if (unlikely(!os_dir2_action(dir, filename, IO_Action_Rename, dir, tmp_file, &open_err))) {
343 os_dir_action(dir, tmp_file, IO_Action_Rm, 0, 0, 0, NULL, &open_err);
344 os_unblock_signals(&set);
345 os_dir_close(dir);
346 return false;
349 os_unblock_signals(&set);
350 os_dir_close(dir);
352 return true;