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
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/>.
27 bool os_read_file(const char *path
, char **file
, size_t *len
, ajla_error_t
*err
)
31 if (unlikely(!array_init_mayfail(char, file
, len
, err
)))
33 h
= os_open(dir_none
, path
, O_RDONLY
, 0, err
);
34 if (unlikely(!handle_is_valid(h
)))
37 ssize_t rd
= os_read(h
, buffer
, sizeof buffer
, err
);
38 if (unlikely(rd
== OS_RW_ERROR
))
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");
46 if (unlikely(!array_add_multiple_mayfail(char, file
, len
, buffer
, rd
, NULL
, err
)))
57 bool os_pread_all(handle_t h
, char *ptr
, size_t len
, os_off_t offset
, ajla_error_t
*err
)
60 size_t this_step
= len
;
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
))
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");
72 fatal_warning_mayfail(error_ajla(EC_SYNC
, AJLA_ERROR_SYSTEM_RETURNED_INVALID_DATA
), err
, "zero-sized read");
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
);
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
))
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");
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");
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] == '\\'))
118 if ((path
[0] & 0xDF) >= 'A' && (path
[0] & 0xDF) <= 'Z' && path
[1] == ':' && (path
[2] == '/' || path
[2] == '\\'))
123 char *os_join_paths(const char *dir
, const char *path
, bool trim_last_slash
, ajla_error_t
*err
)
127 bool is_abs
= os_path_is_absolute(path
);
129 if (unlikely(!os_test_absolute_path(cast_ptr(char *, dir
), is_abs
, err
)))
132 if (unlikely(!array_init_mayfail(char, &ptr
, &len
, err
)))
136 if (unlikely(!array_add_multiple_mayfail(char, &ptr
, &len
, path
, strlen(path
), NULL
, err
)))
141 if (path
[0] == '/' || path
[0] == '\\') {
142 if (unlikely(!array_add_multiple_mayfail(char, &ptr
, &len
, dir
, 2, NULL
, err
)))
145 if (unlikely(!array_add_multiple_mayfail(char, &ptr
, &len
, dir
, strlen(dir
), NULL
, err
)))
147 if (len
&& ptr
[len
- 1] != '/' && ptr
[len
- 1] != '\\')
148 if (unlikely(!array_add_mayfail(char, &ptr
, &len
, '/', NULL
, err
)))
151 if (unlikely(!array_add_multiple_mayfail(char, &ptr
, &len
, path
, strlen(path
), NULL
, err
)))
155 if (trim_last_slash
) {
156 if (len
> 3 && (ptr
[len
- 1] == '/' || ptr
[len
- 1] == '\\'))
160 if (unlikely(!array_add_mayfail(char, &ptr
, &len
, 0, NULL
, err
)))
163 array_finish(char, &ptr
, &len
);
170 bool os_path_is_absolute(const char *path
)
172 #if defined(OS_CYGWIN)
173 if ((path
[0] & 0xdf) >= 'A' && (path
[0] & 0xdf) <= 'Z' &&
175 (path
[2] == '/' || path
[2] == '\\'))
178 return path
[0] == '/';
181 char *os_join_paths(const char *base
, const char *path
, bool trim_last_slash
, ajla_error_t
*err
)
184 size_t res_l
, base_l
, path_l
;
185 if (unlikely(!array_init_mayfail(char, &res
, &res_l
, err
)))
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
)))
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
)))
196 path_l
= strlen(path
);
197 if (unlikely(!array_add_multiple_mayfail(char, &res
, &res_l
, path
, path_l
, NULL
, err
)))
199 if (trim_last_slash
) {
200 if (res_l
> 1 && os_is_path_separator(res
[res_l
- 1]))
203 if (unlikely(!array_add_mayfail(char, &res
, &res_l
, 0, NULL
, err
)))
205 array_finish(char, &res
, &res_l
);
211 static char *os_get_directory(const char *env
, const char *home
, ajla_error_t
*err
)
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
))
220 const char *h
= getenv("HOME");
222 fatal_mayfail(error_ajla(EC_SYNC
, AJLA_ERROR_NOT_SUPPORTED
), err
, "no home directory");
226 cache_dir
= os_join_paths(h
, home
, true, err
);
227 if (unlikely(!cache_dir
))
230 cache_dir_ajla
= os_join_paths(cache_dir
, "ajla", true, err
);
231 if (unlikely(!cache_dir_ajla
)) {
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
);
244 return os_get_directory("XDG_CACHE_HOME", ".cache", err
);
248 static bool os_test_make_dir(const char *path
, size_t path_len
, ajla_error_t
*err
)
250 ajla_error_t mkdir_err
;
252 path_cpy
= str_dup(path
, path_len
, err
);
253 if (unlikely(!path_cpy
))
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
)) {
260 fatal_mayfail(mkdir_err
, err
, "unable to make directory '%s'", path_cpy
);
268 bool os_create_directory_parents(const char *path
, ajla_error_t
*err
)
272 if (os_test_make_dir(path
, strlen(path
), &sink
))
275 while (os_is_path_separator(path
[i
]))
277 if (!i
&& os_path_is_absolute(path
)) {
278 while (path
[i
] && !os_is_path_separator(path
[i
]))
280 while (os_is_path_separator(path
[i
]))
284 while (path
[i
] && !os_is_path_separator(path
[i
]))
286 while (os_is_path_separator(path
[i
]))
288 if (unlikely(!os_test_make_dir(path
, i
, err
)))
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
;
298 char tmp_file
[10 + 4 + 1];
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
)))
308 dir
= os_dir_open(os_cwd
, path
, 0, err
);
309 if (unlikely(!dir_handle_is_valid(dir
)))
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
)) {
327 fatal_mayfail(open_err
, err
, "unable to create file '%s'", tmp_file
);
332 if (unlikely(!os_write_all(h
, data
, data_len
, err
))) {
334 os_dir_action(dir
, tmp_file
, IO_Action_Rm
, 0, 0, 0, NULL
, &open_err
);
335 os_unblock_signals(&set
);
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
);
349 os_unblock_signals(&set
);