The open source OpenXR runtime
1// Copyright 2019-2025, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Very simple file opening functions.
6 * @author Jakob Bornecrantz <jakob@collabora.com>
7 * @author Pete Black <pblack@collabora.com>
8 * @ingroup aux_util
9 */
10
11#include "xrt/xrt_config_os.h"
12#include "xrt/xrt_windows.h"
13#include "util/u_file.h"
14
15#include <errno.h>
16#include <stdbool.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20
21#if defined(XRT_OS_WINDOWS) && !defined(XRT_ENV_MINGW)
22#define PATH_MAX 4096
23#endif
24
25#ifdef XRT_OS_LINUX
26#include <sys/stat.h>
27#include <linux/limits.h>
28
29static int
30mkpath(const char *path)
31{
32 char tmp[PATH_MAX];
33 char *p = NULL;
34 size_t len;
35
36 snprintf(tmp, sizeof(tmp), "%s", path);
37 len = strlen(tmp) - 1;
38 if (tmp[len] == '/') {
39 tmp[len] = 0;
40 }
41
42 for (p = tmp + 1; *p; p++) {
43 if (*p == '/') {
44 *p = 0;
45 if (mkdir(tmp, S_IRWXU) < 0 && errno != EEXIST) {
46 return -1;
47 }
48 *p = '/';
49 }
50 }
51
52 if (mkdir(tmp, S_IRWXU) < 0 && errno != EEXIST) {
53 return -1;
54 }
55
56 return 0;
57}
58
59static bool
60is_dir(const char *path)
61{
62 struct stat st = {0};
63 if (!stat(path, &st)) {
64 return S_ISDIR(st.st_mode);
65 } else {
66 return false;
67 }
68}
69
70int
71u_file_get_config_dir(char *out_path, size_t out_path_size)
72{
73 const char *xdg_home = getenv("XDG_CONFIG_HOME");
74 const char *home = getenv("HOME");
75 if (xdg_home != NULL) {
76 return snprintf(out_path, out_path_size, "%s/monado", xdg_home);
77 }
78 if (home != NULL) {
79 return snprintf(out_path, out_path_size, "%s/.config/monado", home);
80 }
81 return -1;
82}
83
84int
85u_file_get_path_in_config_dir(const char *suffix, char *out_path, size_t out_path_size)
86{
87 char tmp[PATH_MAX];
88 int i = u_file_get_config_dir(tmp, sizeof(tmp));
89 if (i <= 0) {
90 return i;
91 }
92
93 return snprintf(out_path, out_path_size, "%s/%s", tmp, suffix);
94}
95
96FILE *
97u_file_open_file_in_config_dir(const char *filename, const char *mode)
98{
99 char tmp[PATH_MAX];
100 int i = u_file_get_config_dir(tmp, sizeof(tmp));
101 if (i <= 0) {
102 return NULL;
103 }
104
105 char file_str[PATH_MAX + 15];
106 i = snprintf(file_str, sizeof(file_str), "%s/%s", tmp, filename);
107 if (i <= 0) {
108 return NULL;
109 }
110
111 FILE *file = fopen(file_str, mode);
112 if (file != NULL) {
113 return file;
114 }
115
116 // Try creating the path.
117 mkpath(tmp);
118
119 // Do not report error.
120 return fopen(file_str, mode);
121}
122
123FILE *
124u_file_open_file_in_config_dir_subpath(const char *subpath, const char *filename, const char *mode)
125{
126 char tmp[PATH_MAX];
127 int i = u_file_get_config_dir(tmp, sizeof(tmp));
128 if (i < 0 || i >= (int)sizeof(tmp)) {
129 return NULL;
130 }
131
132 char fullpath[PATH_MAX];
133 i = snprintf(fullpath, sizeof(fullpath), "%s/%s", tmp, subpath);
134 if (i < 0 || i >= (int)sizeof(fullpath)) {
135 return NULL;
136 }
137
138 char file_str[PATH_MAX + 15];
139 i = snprintf(file_str, sizeof(file_str), "%s/%s", fullpath, filename);
140 if (i < 0 || i >= (int)sizeof(file_str)) {
141 return NULL;
142 }
143
144 FILE *file = fopen(file_str, mode);
145 if (file != NULL) {
146 return file;
147 }
148
149 // Try creating the path.
150 mkpath(fullpath);
151
152 // Do not report error.
153 return fopen(file_str, mode);
154}
155
156int
157u_file_get_hand_tracking_models_dir(char *out_path, size_t out_path_size)
158{
159 const char *suffix = "/monado/hand-tracking-models";
160 const char *xdg_data_home = getenv("XDG_DATA_HOME");
161 const char *home = getenv("HOME");
162 int ret = 0;
163
164 if (xdg_data_home != NULL) {
165 ret = snprintf(out_path, out_path_size, "%s%s", xdg_data_home, suffix);
166 if (ret > 0 && is_dir(out_path)) {
167 return ret;
168 }
169 }
170
171 if (home != NULL) {
172 ret = snprintf(out_path, out_path_size, "%s/.local/share%s", home, suffix);
173 if (ret > 0 && is_dir(out_path)) {
174 return ret;
175 }
176 }
177
178 ret = snprintf(out_path, out_path_size, "/usr/local/share%s", suffix);
179 if (ret > 0 && is_dir(out_path)) {
180 return ret;
181 }
182
183 ret = snprintf(out_path, out_path_size, "/usr/share%s", suffix);
184 if (ret > 0 && is_dir(out_path)) {
185 return ret;
186 }
187
188 if (out_path_size > 0) {
189 out_path[0] = '\0';
190 }
191
192 return ret;
193}
194
195#endif /* XRT_OS_LINUX */
196
197int
198u_file_get_runtime_dir(char *out_path, size_t out_path_size)
199{
200 const char *xdg_rt = getenv("XDG_RUNTIME_DIR");
201 if (xdg_rt != NULL) {
202 return snprintf(out_path, out_path_size, "%s", xdg_rt);
203 }
204
205 const char *xdg_cache = getenv("XDG_CACHE_HOME");
206 if (xdg_cache != NULL) {
207 return snprintf(out_path, out_path_size, "%s", xdg_cache);
208 }
209
210#ifdef XRT_OS_WINDOWS
211#ifndef UNICODE // If Unicode support is disabled, use ANSI functions directly into out_path
212#ifdef GetTempPath2 // GetTempPath2 is only available on Windows 11 >= 22000, fallback to GetTempPath for older versions
213 return (int)GetTempPath2A(out_path_size, out_path);
214#else
215 return (int)GetTempPathA(out_path_size, out_path);
216#endif
217#else
218 WCHAR temp[MAX_PATH] = {0};
219#ifdef GetTempPath2 // GetTempPath2 is only available on Windows 11 >= 22000, fallback to GetTempPath for older versions
220 GetTempPath2W(sizeof(temp), temp);
221#else // GetTempPath2
222 GetTempPathW(sizeof(temp), temp);
223#endif
224 return wcstombs(out_path, temp, out_path_size);
225#endif // UNICODE
226#else
227 const char *cache = "~/.cache";
228 return snprintf(out_path, out_path_size, "%s", cache);
229#endif
230}
231
232int
233u_file_get_path_in_runtime_dir(const char *suffix, char *out_path, size_t out_path_size)
234{
235 char tmp[PATH_MAX];
236 int i = u_file_get_runtime_dir(tmp, sizeof(tmp));
237 if (i <= 0) {
238 return i;
239 }
240
241 return snprintf(out_path, out_path_size, "%s/%s", tmp, suffix);
242}
243
244char *
245u_file_read_content(FILE *file, size_t *out_file_size)
246{
247 // Go to the end of the file.
248 fseek(file, 0L, SEEK_END);
249 size_t file_size = ftell(file);
250
251 // Return back to the start of the file.
252 fseek(file, 0L, SEEK_SET);
253
254 char *buffer = (char *)calloc(file_size + 1, sizeof(char));
255 if (buffer == NULL) {
256 return NULL;
257 }
258
259 // Do the actual reading.
260 size_t ret = fread(buffer, sizeof(char), file_size, file);
261 if (ret != file_size) {
262 free(buffer);
263 return NULL;
264 }
265
266 if (out_file_size)
267 *out_file_size = file_size;
268
269 return buffer;
270}
271
272char *
273u_file_read_content_from_path(const char *path, size_t *out_file_size)
274{
275 FILE *file = fopen(path, "rb");
276 if (file == NULL) {
277 return NULL;
278 }
279 char *file_content = u_file_read_content(file, out_file_size);
280 int ret = fclose(file);
281 // We don't care about the return value since we're just reading
282 (void)ret;
283
284 // Either valid non-null or null
285 return file_content;
286}