master
c 227 lines 7.39 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "common.h"
4 #include "status-file-io.h"
5
6 // List of fallback directories to try
7 static const char *status_file_io_fallback_dirs[] = {
8 CACHE_DIR,
9 "/tmp",
10 "/run",
11 "/var/run",
12 ".",
13 };
14
15 static void status_file_io_fallback_dirs_update(void) {
16 status_file_io_fallback_dirs[0] = netdata_configured_cache_dir;
17 }
18
19 static bool status_file_io_check(const char *directory, const char *filename, char *dst, size_t dst_size, time_t *mtime) {
20 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
21 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
22
23 if(!directory || !*directory || !filename || !*filename || !dst || !dst_size || !mtime)
24 return false;
25
26 size_t len = 0;
27 len = strcatz(dst, len, directory, dst_size);
28 if(!len || dst[len - 1] != '/')
29 len = strcatz(dst, len, "/", dst_size);
30 len = strcatz(dst, len, filename, dst_size);
31
32 // Get file metadata
33 OS_FILE_METADATA metadata = os_get_file_metadata(dst);
34 if (!OS_FILE_METADATA_OK(metadata)) {
35 *mtime = 0;
36 return false;
37 }
38
39 *mtime = metadata.modified_time;
40 return true;
41 }
42
43 static void status_file_io_remove_obsolete(const char *protected_dir, const char *filename) {
44 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
45 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
46
47 FUNCTION_RUN_ONCE();
48
49 char dst[FILENAME_MAX];
50
51 status_file_io_fallback_dirs_update();
52 for(size_t i = 0; i < _countof(status_file_io_fallback_dirs); i++) {
53 if(strcmp(status_file_io_fallback_dirs[i], protected_dir) == 0)
54 continue;
55
56 size_t len = 0;
57 len = strcatz(dst, len, status_file_io_fallback_dirs[i], sizeof(dst));
58 if(!len || dst[len - 1] != '/')
59 len = strcatz(dst, len, "/", sizeof(dst));
60 len = strcatz(dst, len, filename, sizeof(dst));
61
62 unlink(dst);
63 }
64
65 errno_clear();
66 }
67
68 bool status_file_io_load(const char *filename, bool (*cb)(const char *, void *), void *data) {
69 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
70 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
71
72 char newest[FILENAME_MAX] = "";
73 char current[FILENAME_MAX];
74 time_t newest_mtime = 0, current_mtime;
75
76 // Check the primary directory first
77 if(status_file_io_check(netdata_configured_varlib_dir, filename, current, sizeof(current), &current_mtime)) {
78 strncpyz(newest, current, sizeof(newest) - 1);
79 newest_mtime = current_mtime;
80 }
81
82 // Check each fallback location
83 status_file_io_fallback_dirs_update();
84 for(size_t i = 0; i < _countof(status_file_io_fallback_dirs); i++) {
85 if(status_file_io_check(status_file_io_fallback_dirs[i], filename, current, sizeof(current), &current_mtime) &&
86 (!*newest || current_mtime > newest_mtime)) {
87 strncpyz(newest, current, sizeof(newest) - 1);
88 newest_mtime = current_mtime;
89 }
90 }
91
92 // Load the newest file found
93 if(*newest && cb(newest, data))
94 return true;
95
96 nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot find a status file in any location");
97 return false;
98 }
99
100 static bool status_file_io_save_this(const char *directory, const char *filename, const uint8_t *data, size_t size) {
101 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
102 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
103
104 // Linux: https://man7.org/linux/man-pages/man7/signal-safety.7.html
105 // memcpy(), strlen(), open(), write(), fsync(), close(), fchmod(), rename(), unlink()
106
107 // MacOS: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sigaction.2.html#//apple_ref/doc/man/2/sigaction
108 // open(), write(), fsync(), close(), rename(), unlink()
109 // does not explicitly mention fchmod, memcpy(), and strlen(), but they are safe
110
111 if(!directory || !*directory)
112 return false;
113
114 static uint64_t tmp_attempt_counter = 0;
115
116 char final[FILENAME_MAX];
117 char temp[FILENAME_MAX];
118 char tid_str[UINT64_MAX_LENGTH];
119
120 print_uint64(tid_str, __atomic_add_fetch(&tmp_attempt_counter, 1, __ATOMIC_RELAXED));
121 size_t dir_len = strlen(directory);
122 size_t fil_len = strlen(filename);
123 size_t tid_len = strlen(tid_str);
124
125 if (dir_len + 1 + fil_len + 1 + tid_len + 1 >= sizeof(final))
126 return false; // cannot fit the filename
127
128 // create the filename
129 size_t pos = 0;
130 memcpy(&final[pos], directory, dir_len); pos += dir_len;
131 final[pos] = '/'; pos++;
132 memcpy(&final[pos], filename, fil_len); pos += fil_len;
133 final[pos] = '\0';
134
135 // create the temp filename
136 memcpy(temp, final, pos);
137 temp[pos] = '-'; pos++;
138 memcpy(&temp[pos], tid_str, tid_len); pos += tid_len;
139 temp[pos] = '\0';
140
141 // Open file with O_WRONLY, O_CREAT, and O_TRUNC flags
142 int fd = open(temp, O_WRONLY | O_CREAT | O_TRUNC, 0664);
143 if (fd == -1)
144 return false;
145
146 /* Write content to file using write() */
147 size_t total_written = 0;
148
149 while (total_written < size) {
150 ssize_t bytes_written = write(fd, data + total_written, size - total_written);
151
152 if (bytes_written <= 0) {
153 if (errno == EINTR)
154 continue; /* Retry if interrupted by signal */
155
156 close(fd);
157 unlink(temp); /* Remove the temp file */
158 return false;
159 }
160
161 total_written += bytes_written;
162 }
163
164 /* Fsync to ensure data is written to disk */
165 if (fsync(fd) == -1) {
166 close(fd);
167 unlink(temp);
168 return false;
169 }
170
171 /* Set permissions using chmod() */
172 if (fchmod(fd, 0664) != 0) {
173 close(fd);
174 unlink(temp);
175 return false;
176 }
177
178 /* Close file */
179 if (close(fd) == -1) {
180 unlink(temp);
181 return false;
182 }
183
184 /* Rename temp file to target file */
185 if (rename(temp, final) != 0) {
186 unlink(temp);
187 return false;
188 }
189
190 return true;
191 }
192
193 bool status_file_io_save(const char *filename, const void *data, size_t size, bool log) {
194 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
195 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
196
197 // wb should have enough space to hold the JSON content, to avoid any allocations
198
199 // Try primary directory first
200 bool saved = false;
201 if (status_file_io_save_this(netdata_configured_varlib_dir, filename, data, size)) {
202 status_file_io_remove_obsolete(netdata_configured_varlib_dir, filename);
203 saved = true;
204 }
205 else {
206 if(log)
207 nd_log(NDLS_DAEMON, NDLP_DEBUG, "Failed to save status file in primary directory %s",
208 netdata_configured_varlib_dir);
209
210 // Try each fallback directory until successful
211 status_file_io_fallback_dirs_update();
212 for(size_t i = 0; i < _countof(status_file_io_fallback_dirs); i++) {
213 if (status_file_io_save_this(status_file_io_fallback_dirs[i], filename, data, size)) {
214 if(log)
215 nd_log(NDLS_DAEMON, NDLP_DEBUG, "Saved status file in fallback %s", status_file_io_fallback_dirs[i]);
216
217 saved = true;
218 break;
219 }
220 }
221 }
222
223 if (!saved && log)
224 nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to save status file in any location");
225
226 return saved;
227 }