master
c 286 lines 9.94 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "dyncfg-internals.h"
4 #include "dyncfg.h"
5
6 void dyncfg_file_delete(const char *id) {
7 CLEAN_CHAR_P *escaped_id = dyncfg_escape_id_for_filename(id);
8 char filename[FILENAME_MAX];
9 snprintfz(filename, sizeof(filename), "%s/%s.dyncfg", dyncfg_globals.dir, escaped_id);
10 unlink(filename);
11 }
12
13 void dyncfg_file_save(const char *id, DYNCFG *df) {
14 CLEAN_CHAR_P *escaped_id = dyncfg_escape_id_for_filename(id);
15 char filename[FILENAME_MAX];
16 snprintfz(filename, sizeof(filename), "%s/%s.dyncfg", dyncfg_globals.dir, escaped_id);
17
18 FILE *fp = fopen(filename, "w");
19 if(!fp) {
20 nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: cannot create file '%s'", filename);
21 return;
22 }
23
24 df->dyncfg.modified_ut = now_realtime_usec();
25 if(!df->dyncfg.created_ut)
26 df->dyncfg.created_ut = df->dyncfg.modified_ut;
27
28 fprintf(fp, "version=%zu\n", DYNCFG_VERSION);
29 fprintf(fp, "id=%s\n", id);
30
31 if(df->template)
32 fprintf(fp, "template=%s\n", string2str(df->template));
33
34 char uuid_str[UUID_COMPACT_STR_LEN];
35 uuid_unparse_lower_compact(df->host_uuid.uuid, uuid_str);
36 fprintf(fp, "host=%s\n", uuid_str);
37
38 fprintf(fp, "path=%s\n", string2str(df->path));
39 fprintf(fp, "type=%s\n", dyncfg_id2type(df->type));
40
41 fprintf(fp, "source_type=%s\n", dyncfg_id2source_type(df->dyncfg.source_type));
42 fprintf(fp, "source=%s\n", string2str(df->dyncfg.source));
43
44 fprintf(fp, "created=%"PRIu64"\n", df->dyncfg.created_ut);
45 fprintf(fp, "modified=%"PRIu64"\n", df->dyncfg.modified_ut);
46 fprintf(fp, "sync=%s\n", df->sync ? "true" : "false");
47 fprintf(fp, "user_disabled=%s\n", df->dyncfg.user_disabled ? "true" : "false");
48 fprintf(fp, "saves=%"PRIu32"\n", ++df->dyncfg.saves);
49
50 fprintf(fp, "cmds=");
51 dyncfg_cmds2fp(df->cmds, fp);
52 fprintf(fp, "\n");
53
54 if(df->dyncfg.payload && buffer_strlen(df->dyncfg.payload) > 0) {
55 fprintf(fp, "content_type=%s\n", content_type_id2string(df->dyncfg.payload->content_type));
56 fprintf(fp, "content_length=%zu\n", buffer_strlen(df->dyncfg.payload));
57 fprintf(fp, "---\n");
58 fwrite(buffer_tostring(df->dyncfg.payload), 1, buffer_strlen(df->dyncfg.payload), fp);
59 }
60
61 fclose(fp);
62 }
63
64 void dyncfg_file_load(const char *d_name) {
65 char filename[PATH_MAX];
66 snprintf(filename, sizeof(filename), "%s/%s", dyncfg_globals.dir, d_name);
67
68 FILE *fp = fopen(filename, "r");
69 if (!fp) {
70 nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: cannot open file '%s'", filename);
71 return;
72 }
73
74 DYNCFG tmp = { 0 };
75
76 char line[PLUGINSD_LINE_MAX];
77 CLEAN_CHAR_P *id = NULL;
78
79 HTTP_CONTENT_TYPE content_type = CT_NONE;
80 size_t content_length = 0;
81 bool read_payload = false;
82
83 while (fgets(line, sizeof(line), fp)) {
84 if(strcmp(line, "---\n") == 0) {
85 read_payload = true;
86 break;
87 }
88
89 char *value = strchr(line, '=');
90 if(!value) continue;
91
92 *value++ = '\0';
93
94 value = trim(value);
95 if(!value) continue;
96
97 char *key = trim(line);
98 if(!key) continue;
99
100 // Parse key-value pairs
101 if (strcmp(key, "version") == 0) {
102 size_t version = strtoull(value, NULL, 10);
103
104 if(version > DYNCFG_VERSION)
105 nd_log(NDLS_DAEMON, NDLP_NOTICE,
106 "DYNCFG: configuration file '%s' has version %zu, which is newer than our version %zu",
107 filename, version, DYNCFG_VERSION);
108
109 } else if (strcmp(key, "id") == 0) {
110 freez(id);
111 id = strdupz(value);
112 } else if (strcmp(key, "template") == 0) {
113 tmp.template = string_strdupz(value);
114 } else if (strcmp(key, "host") == 0) {
115 uuid_parse_flexi(value, tmp.host_uuid.uuid);
116 } else if (strcmp(key, "path") == 0) {
117 tmp.path = string_strdupz(value);
118 } else if (strcmp(key, "type") == 0) {
119 tmp.type = dyncfg_type2id(value);
120 } else if (strcmp(key, "source_type") == 0) {
121 tmp.dyncfg.source_type = dyncfg_source_type2id(value);
122 } else if (strcmp(key, "source") == 0) {
123 tmp.dyncfg.source = string_strdupz(value);
124 } else if (strcmp(key, "created") == 0) {
125 tmp.dyncfg.created_ut = strtoull(value, NULL, 10);
126 } else if (strcmp(key, "modified") == 0) {
127 tmp.dyncfg.modified_ut = strtoull(value, NULL, 10);
128 } else if (strcmp(key, "sync") == 0) {
129 tmp.sync = (strcmp(value, "true") == 0);
130 } else if (strcmp(key, "user_disabled") == 0) {
131 tmp.dyncfg.user_disabled = (strcmp(value, "true") == 0);
132 } else if (strcmp(key, "saves") == 0) {
133 tmp.dyncfg.saves = strtoull(value, NULL, 10);
134 } else if (strcmp(key, "content_type") == 0) {
135 content_type = content_type_string2id(value);
136 } else if (strcmp(key, "content_length") == 0) {
137 content_length = strtoull(value, NULL, 10);
138 } else if (strcmp(key, "cmds") == 0) {
139 tmp.cmds = dyncfg_cmds2id(value);
140 }
141 }
142
143 if (read_payload) {
144 // Determine the actual size of the remaining file content
145 int rc = 0;
146 long saved_position = ftell(fp); // Save current position
147
148 long total_size = 0;
149 size_t actual_size = 0;
150 if (saved_position != -1) {
151 rc = fseek(fp, 0, SEEK_END);
152 if (!rc) {
153 total_size = ftell(fp); // Total size of the file
154 actual_size = total_size - saved_position; // Calculate remaining content size
155 rc = fseek(fp, saved_position, SEEK_SET); // Reset file pointer to the beginning of the payload
156 }
157 }
158
159 // Check if any of the system calls failed
160 if (rc || saved_position == -1 || total_size == -1) {
161 nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: error while accessing '%s' to calculate the %s.", filename,
162 saved_position == -1 ? "payload position" : "file size");
163 fclose(fp);
164 dyncfg_cleanup(&tmp);
165 return;
166 }
167 // Use actual_size instead of content_length to handle the whole remaining file
168 tmp.dyncfg.payload = buffer_create(actual_size, NULL);
169 tmp.dyncfg.payload->content_type = content_type;
170
171 buffer_need_bytes(tmp.dyncfg.payload, actual_size);
172 tmp.dyncfg.payload->len = fread(tmp.dyncfg.payload->buffer, 1, actual_size, fp);
173
174 if (content_length != tmp.dyncfg.payload->len) {
175 nd_log(NDLS_DAEMON, NDLP_WARNING,
176 "DYNCFG: content_length %zu does not match actual payload size %zu for file '%s'",
177 content_length, actual_size, filename);
178 }
179 }
180
181 fclose(fp);
182
183 if(!id) {
184 nd_log(NDLS_DAEMON, NDLP_ERR,
185 "DYNCFG: configuration file '%s' does not include a unique id. Ignoring it.",
186 filename);
187
188 dyncfg_cleanup(&tmp);
189 return;
190 }
191
192 tmp.dyncfg.status = DYNCFG_STATUS_ORPHAN;
193 tmp.dyncfg.restart_required = false;
194
195 dyncfg_set_current_from_dyncfg(&tmp);
196
197 // Heal cmds on load: files written by older binaries can carry a stale
198 // mask (e.g. an overridden stock job persisted without REMOVE pre-fix).
199 // The invariant elsewhere is cmds := f(type, source_type, cmds); enforce
200 // it here too so the conflict_cb SWAP cannot reintroduce a stale mask.
201 tmp.cmds = dyncfg_sanitize_cmds(tmp.type, tmp.current.source_type, tmp.cmds);
202
203 dictionary_set(dyncfg_globals.nodes, id, &tmp, sizeof(tmp));
204
205 // check if we need to rename the file
206 CLEAN_CHAR_P *fixed_id = dyncfg_escape_id_for_filename(id);
207 char fixed_filename[PATH_MAX];
208 snprintf(fixed_filename, sizeof(fixed_filename), "%s/%s.dyncfg", dyncfg_globals.dir, fixed_id);
209
210 if(strcmp(filename, fixed_filename) != 0) {
211 if(rename(filename, fixed_filename) != 0)
212 nd_log(NDLS_DAEMON, NDLP_ERR,
213 "DYNCFG: cannot rename file '%s' into '%s'. Saving a new configuraton may not overwrite the old one.",
214 filename, fixed_filename);
215 }
216 }
217
218 void dyncfg_load_all(void) {
219 DIR *dir = opendir(dyncfg_globals.dir);
220 if (!dir) {
221 nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: cannot open directory '%s'", dyncfg_globals.dir);
222 return;
223 }
224
225 struct dirent *entry;
226 while ((entry = readdir(dir)) != NULL) {
227 if ((entry->d_type == DT_REG || entry->d_type == DT_LNK) && strendswith(entry->d_name, ".dyncfg"))
228 dyncfg_file_load(entry->d_name);
229 }
230
231 closedir(dir);
232 }
233
234 // ----------------------------------------------------------------------------
235 // schemas loading
236
237 static bool dyncfg_read_file_to_buffer(const char *filename, BUFFER *dst) {
238 int fd = open(filename, O_RDONLY | O_CLOEXEC, 0666);
239 if(unlikely(fd == -1))
240 return false;
241
242 struct stat st = { 0 };
243 if(fstat(fd, &st) != 0) {
244 close(fd);
245 return false;
246 }
247
248 buffer_flush(dst);
249 buffer_need_bytes(dst, st.st_size + 1); // +1 for the terminating zero
250
251 ssize_t r = read(fd, (char*)dst->buffer, st.st_size);
252 if(unlikely(r == -1)) {
253 close(fd);
254 return false;
255 }
256 dst->len = r;
257 dst->buffer[dst->len] = '\0';
258
259 close(fd);
260 return true;
261 }
262
263 static bool dyncfg_get_schema_from(const char *dir, const char *id, BUFFER *dst) {
264 char filename[FILENAME_MAX + 1];
265
266 CLEAN_CHAR_P *escaped_id = dyncfg_escape_id_for_filename(id);
267 snprintfz(filename, sizeof(filename), "%s/schema.d/%s.json", dir, escaped_id);
268 if(dyncfg_read_file_to_buffer(filename, dst))
269 return true;
270
271 snprintfz(filename, sizeof(filename), "%s/schema.d/%s.json", dir, id);
272 if(dyncfg_read_file_to_buffer(filename, dst))
273 return true;
274
275 return false;
276 }
277
278 bool dyncfg_get_schema(const char *id, BUFFER *dst) {
279 if(dyncfg_get_schema_from(netdata_configured_user_config_dir, id, dst))
280 return true;
281
282 if(dyncfg_get_schema_from(netdata_configured_stock_config_dir, id, dst))
283 return true;
284
285 return false;
286 }