systemd-journal-files.c 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "systemd-internals.h"
  3. #define SYSTEMD_JOURNAL_MAX_SOURCE_LEN 64
  4. #define VAR_LOG_JOURNAL_MAX_DEPTH 10
  5. struct journal_directory journal_directories[MAX_JOURNAL_DIRECTORIES] = { 0 };
  6. DICTIONARY *journal_files_registry = NULL;
  7. DICTIONARY *used_hashes_registry = NULL;
  8. static usec_t systemd_journal_session = 0;
  9. void buffer_json_journal_versions(BUFFER *wb) {
  10. buffer_json_member_add_object(wb, "versions");
  11. {
  12. buffer_json_member_add_uint64(wb, "sources",
  13. systemd_journal_session + dictionary_version(journal_files_registry));
  14. }
  15. buffer_json_object_close(wb);
  16. }
  17. static bool journal_sd_id128_parse(const char *in, sd_id128_t *ret) {
  18. while(isspace(*in))
  19. in++;
  20. char uuid[33];
  21. strncpyz(uuid, in, 32);
  22. uuid[32] = '\0';
  23. if(strlen(uuid) == 32) {
  24. sd_id128_t read;
  25. if(sd_id128_from_string(uuid, &read) == 0) {
  26. *ret = read;
  27. return true;
  28. }
  29. }
  30. return false;
  31. }
  32. //static void journal_file_get_header_from_journalctl(const char *filename, struct journal_file *jf) {
  33. // // unfortunately, our capabilities are not inheritted by journalctl
  34. // // so, it fails to give us the information we need.
  35. //
  36. // bool read_writer = false, read_head = false, read_tail = false;
  37. //
  38. // char cmd[FILENAME_MAX * 2];
  39. // snprintfz(cmd, sizeof(cmd), "journalctl --header --file '%s'", filename);
  40. // CLEAN_BUFFER *wb = run_command_and_get_output_to_buffer(cmd, 1024);
  41. // if(wb) {
  42. // const char *s = buffer_tostring(wb);
  43. //
  44. // const char *sequential_id_header = "Sequential Number ID:";
  45. // const char *sequential_id_data = strcasestr(s, sequential_id_header);
  46. // if(sequential_id_data) {
  47. // sequential_id_data += strlen(sequential_id_header);
  48. // if(journal_sd_id128_parse(sequential_id_data, &jf->first_writer_id))
  49. // read_writer = true;
  50. // }
  51. //
  52. // const char *head_sequential_number_header = "Head sequential number:";
  53. // const char *head_sequential_number_data = strcasestr(s, head_sequential_number_header);
  54. // if(head_sequential_number_data) {
  55. // head_sequential_number_data += strlen(head_sequential_number_header);
  56. //
  57. // while(isspace(*head_sequential_number_data))
  58. // head_sequential_number_data++;
  59. //
  60. // if(isdigit(*head_sequential_number_data)) {
  61. // jf->first_seqnum = strtoul(head_sequential_number_data, NULL, 10);
  62. // if(jf->first_seqnum)
  63. // read_head = true;
  64. // }
  65. // }
  66. //
  67. // const char *tail_sequential_number_header = "Tail sequential number:";
  68. // const char *tail_sequential_number_data = strcasestr(s, tail_sequential_number_header);
  69. // if(tail_sequential_number_data) {
  70. // tail_sequential_number_data += strlen(tail_sequential_number_header);
  71. //
  72. // while(isspace(*tail_sequential_number_data))
  73. // tail_sequential_number_data++;
  74. //
  75. // if(isdigit(*tail_sequential_number_data)) {
  76. // jf->last_seqnum = strtoul(tail_sequential_number_data, NULL, 10);
  77. // if(jf->last_seqnum)
  78. // read_tail = true;
  79. // }
  80. // }
  81. //
  82. // if(read_head && read_tail && jf->last_seqnum > jf->first_seqnum)
  83. // jf->messages_in_file = jf->last_seqnum - jf->first_seqnum;
  84. // }
  85. //
  86. // if(!jf->logged_journalctl_failure && (!read_head || !read_tail)) {
  87. //
  88. // nd_log(NDLS_COLLECTORS, NDLP_NOTICE,
  89. // "Failed to read %s%s%s from journalctl's output on filename '%s', using the command: %s",
  90. // read_writer?"":"writer id,",
  91. // read_head?"":"head id,",
  92. // read_tail?"":"tail id,",
  93. // filename, cmd);
  94. //
  95. // jf->logged_journalctl_failure = true;
  96. // }
  97. //}
  98. usec_t journal_file_update_annotation_boot_id(sd_journal *j, struct journal_file *jf __maybe_unused, const char *boot_id) {
  99. usec_t ut = UINT64_MAX;
  100. int r;
  101. char m[100];
  102. size_t len = snprintfz(m, sizeof(m), "_BOOT_ID=%s", boot_id);
  103. sd_journal_flush_matches(j);
  104. r = sd_journal_add_match(j, m, len);
  105. if(r < 0) {
  106. errno = -r;
  107. internal_error(true,
  108. "JOURNAL: while looking for the first timestamp of boot_id '%s', "
  109. "sd_journal_add_match('%s') on file '%s' returned %d",
  110. boot_id, m, jf->filename, r);
  111. return UINT64_MAX;
  112. }
  113. r = sd_journal_seek_head(j);
  114. if(r < 0) {
  115. errno = -r;
  116. internal_error(true,
  117. "JOURNAL: while looking for the first timestamp of boot_id '%s', "
  118. "sd_journal_seek_head() on file '%s' returned %d",
  119. boot_id, jf->filename, r);
  120. return UINT64_MAX;
  121. }
  122. r = sd_journal_next(j);
  123. if(r < 0) {
  124. errno = -r;
  125. internal_error(true,
  126. "JOURNAL: while looking for the first timestamp of boot_id '%s', "
  127. "sd_journal_next() on file '%s' returned %d",
  128. boot_id, jf->filename, r);
  129. return UINT64_MAX;
  130. }
  131. r = sd_journal_get_realtime_usec(j, &ut);
  132. if(r < 0 || !ut || ut == UINT64_MAX) {
  133. errno = -r;
  134. internal_error(r != -EADDRNOTAVAIL,
  135. "JOURNAL: while looking for the first timestamp of boot_id '%s', "
  136. "sd_journal_get_realtime_usec() on file '%s' returned %d",
  137. boot_id, jf->filename, r);
  138. return UINT64_MAX;
  139. }
  140. if(ut && ut != UINT64_MAX) {
  141. dictionary_set(boot_ids_to_first_ut, boot_id, &ut, sizeof(ut));
  142. return ut;
  143. }
  144. return UINT64_MAX;
  145. }
  146. static void journal_file_get_boot_id_annotations(sd_journal *j __maybe_unused, struct journal_file *jf __maybe_unused) {
  147. #ifdef HAVE_SD_JOURNAL_RESTART_FIELDS
  148. sd_journal_flush_matches(j);
  149. int r = sd_journal_query_unique(j, "_BOOT_ID");
  150. if (r < 0) {
  151. errno = -r;
  152. internal_error(true,
  153. "JOURNAL: while querying for the unique _BOOT_ID values, "
  154. "sd_journal_query_unique() on file '%s' returned %d",
  155. jf->filename, r);
  156. errno = -r;
  157. return;
  158. }
  159. const void *data = NULL;
  160. size_t data_length;
  161. DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
  162. SD_JOURNAL_FOREACH_UNIQUE(j, data, data_length) {
  163. const char *key, *value;
  164. size_t key_length, value_length;
  165. if(!parse_journal_field(data, data_length, &key, &key_length, &value, &value_length))
  166. continue;
  167. if(value_length != 32)
  168. continue;
  169. char buf[33];
  170. memcpy(buf, value, 32);
  171. buf[32] = '\0';
  172. dictionary_set(dict, buf, NULL, 0);
  173. }
  174. void *nothing;
  175. dfe_start_read(dict, nothing){
  176. journal_file_update_annotation_boot_id(j, jf, nothing_dfe.name);
  177. }
  178. dfe_done(nothing);
  179. dictionary_destroy(dict);
  180. #endif
  181. }
  182. void journal_file_update_header(const char *filename, struct journal_file *jf) {
  183. if(jf->last_scan_header_vs_last_modified_ut == jf->file_last_modified_ut)
  184. return;
  185. fstat_cache_enable_on_thread();
  186. const char *files[2] = {
  187. [0] = filename,
  188. [1] = NULL,
  189. };
  190. sd_journal *j = NULL;
  191. if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
  192. netdata_log_error("JOURNAL: cannot open file '%s' to update msg_ut", filename);
  193. fstat_cache_disable_on_thread();
  194. if(!jf->logged_failure) {
  195. netdata_log_error("cannot open journal file '%s', using file timestamps to understand time-frame.", filename);
  196. jf->logged_failure = true;
  197. }
  198. jf->msg_first_ut = 0;
  199. jf->msg_last_ut = jf->file_last_modified_ut;
  200. jf->last_scan_header_vs_last_modified_ut = jf->file_last_modified_ut;
  201. return;
  202. }
  203. usec_t first_ut = 0, last_ut = 0;
  204. uint64_t first_seqnum = 0, last_seqnum = 0;
  205. sd_id128_t first_writer_id = SD_ID128_NULL, last_writer_id = SD_ID128_NULL;
  206. if(sd_journal_seek_head(j) < 0 || sd_journal_next(j) < 0 || sd_journal_get_realtime_usec(j, &first_ut) < 0 || !first_ut) {
  207. internal_error(true, "cannot find the timestamp of the first message in '%s'", filename);
  208. first_ut = 0;
  209. }
  210. #ifdef HAVE_SD_JOURNAL_GET_SEQNUM
  211. else {
  212. if(sd_journal_get_seqnum(j, &first_seqnum, &first_writer_id) < 0 || !first_seqnum) {
  213. internal_error(true, "cannot find the first seqnums of the first message in '%s'", filename);
  214. first_seqnum = 0;
  215. memset(&first_writer_id, 0, sizeof(first_writer_id));
  216. }
  217. }
  218. #endif
  219. if(sd_journal_seek_tail(j) < 0 || sd_journal_previous(j) < 0 || sd_journal_get_realtime_usec(j, &last_ut) < 0 || !last_ut) {
  220. internal_error(true, "cannot find the timestamp of the last message in '%s'", filename);
  221. last_ut = jf->file_last_modified_ut;
  222. }
  223. #ifdef HAVE_SD_JOURNAL_GET_SEQNUM
  224. else {
  225. if(sd_journal_get_seqnum(j, &last_seqnum, &last_writer_id) < 0 || !last_seqnum) {
  226. internal_error(true, "cannot find the last seqnums of the first message in '%s'", filename);
  227. last_seqnum = 0;
  228. memset(&last_writer_id, 0, sizeof(last_writer_id));
  229. }
  230. }
  231. #endif
  232. if(first_ut > last_ut) {
  233. internal_error(true, "timestamps are flipped in file '%s'", filename);
  234. usec_t t = first_ut;
  235. first_ut = last_ut;
  236. last_ut = t;
  237. }
  238. if(!first_seqnum || !first_ut) {
  239. // extract these from the filename - if possible
  240. const char *at = strchr(filename, '@');
  241. if(at) {
  242. const char *dash_seqnum = strchr(at + 1, '-');
  243. if(dash_seqnum) {
  244. const char *dash_first_msg_ut = strchr(dash_seqnum + 1, '-');
  245. if(dash_first_msg_ut) {
  246. const char *dot_journal = strstr(dash_first_msg_ut + 1, ".journal");
  247. if(dot_journal) {
  248. if(dash_seqnum - at - 1 == 32 &&
  249. dash_first_msg_ut - dash_seqnum - 1 == 16 &&
  250. dot_journal - dash_first_msg_ut - 1 == 16) {
  251. sd_id128_t writer;
  252. if(journal_sd_id128_parse(at + 1, &writer)) {
  253. char *endptr = NULL;
  254. uint64_t seqnum = strtoul(dash_seqnum + 1, &endptr, 16);
  255. if(endptr == dash_first_msg_ut) {
  256. uint64_t ts = strtoul(dash_first_msg_ut + 1, &endptr, 16);
  257. if(endptr == dot_journal) {
  258. first_seqnum = seqnum;
  259. first_writer_id = writer;
  260. first_ut = ts;
  261. }
  262. }
  263. }
  264. }
  265. }
  266. }
  267. }
  268. }
  269. }
  270. jf->first_seqnum = first_seqnum;
  271. jf->last_seqnum = last_seqnum;
  272. jf->first_writer_id = first_writer_id;
  273. jf->last_writer_id = last_writer_id;
  274. jf->msg_first_ut = first_ut;
  275. jf->msg_last_ut = last_ut;
  276. if(!jf->msg_last_ut)
  277. jf->msg_last_ut = jf->file_last_modified_ut;
  278. if(last_seqnum > first_seqnum) {
  279. if(!sd_id128_equal(first_writer_id, last_writer_id)) {
  280. jf->messages_in_file = 0;
  281. nd_log(NDLS_COLLECTORS, NDLP_NOTICE,
  282. "The writers of the first and the last message in file '%s' differ."
  283. , filename);
  284. }
  285. else
  286. jf->messages_in_file = last_seqnum - first_seqnum + 1;
  287. }
  288. else
  289. jf->messages_in_file = 0;
  290. // if(!jf->messages_in_file)
  291. // journal_file_get_header_from_journalctl(filename, jf);
  292. journal_file_get_boot_id_annotations(j, jf);
  293. sd_journal_close(j);
  294. fstat_cache_disable_on_thread();
  295. jf->last_scan_header_vs_last_modified_ut = jf->file_last_modified_ut;
  296. nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
  297. "Journal file header updated '%s'",
  298. jf->filename);
  299. }
  300. static STRING *string_strdupz_source(const char *s, const char *e, size_t max_len, const char *prefix) {
  301. char buf[max_len];
  302. size_t len;
  303. char *dst = buf;
  304. if(prefix) {
  305. len = strlen(prefix);
  306. memcpy(buf, prefix, len);
  307. dst = &buf[len];
  308. max_len -= len;
  309. }
  310. len = e - s;
  311. if(len >= max_len)
  312. len = max_len - 1;
  313. memcpy(dst, s, len);
  314. dst[len] = '\0';
  315. buf[max_len - 1] = '\0';
  316. for(size_t i = 0; buf[i] ;i++)
  317. if(!isalnum(buf[i]) && buf[i] != '-' && buf[i] != '.' && buf[i] != ':')
  318. buf[i] = '_';
  319. return string_strdupz(buf);
  320. }
  321. static void files_registry_insert_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
  322. struct journal_file *jf = value;
  323. jf->filename = dictionary_acquired_item_name(item);
  324. jf->filename_len = strlen(jf->filename);
  325. jf->source_type = SDJF_ALL;
  326. // based on the filename
  327. // decide the source to show to the user
  328. const char *s = strrchr(jf->filename, '/');
  329. if(s) {
  330. if(strstr(jf->filename, "/remote/")) {
  331. jf->source_type |= SDJF_REMOTE_ALL;
  332. if(strncmp(s, "/remote-", 8) == 0) {
  333. s = &s[8]; // skip "/remote-"
  334. char *e = strchr(s, '@');
  335. if(!e)
  336. e = strstr(s, ".journal");
  337. if(e) {
  338. const char *d = s;
  339. for(; d < e && (isdigit(*d) || *d == '.' || *d == ':') ; d++) ;
  340. if(d == e) {
  341. // a valid IP address
  342. char ip[e - s + 1];
  343. memcpy(ip, s, e - s);
  344. ip[e - s] = '\0';
  345. char buf[SYSTEMD_JOURNAL_MAX_SOURCE_LEN];
  346. if(ip_to_hostname(ip, buf, sizeof(buf)))
  347. jf->source = string_strdupz_source(buf, &buf[strlen(buf)], SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
  348. else {
  349. internal_error(true, "Cannot find the hostname for IP '%s'", ip);
  350. jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
  351. }
  352. }
  353. else
  354. jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
  355. }
  356. }
  357. }
  358. else {
  359. jf->source_type |= SDJF_LOCAL_ALL;
  360. const char *t = s - 1;
  361. while(t >= jf->filename && *t != '.' && *t != '/')
  362. t--;
  363. if(t >= jf->filename && *t == '.') {
  364. jf->source_type |= SDJF_LOCAL_NAMESPACE;
  365. jf->source = string_strdupz_source(t + 1, s, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "namespace-");
  366. }
  367. else if(strncmp(s, "/system", 7) == 0)
  368. jf->source_type |= SDJF_LOCAL_SYSTEM;
  369. else if(strncmp(s, "/user", 5) == 0)
  370. jf->source_type |= SDJF_LOCAL_USER;
  371. else
  372. jf->source_type |= SDJF_LOCAL_OTHER;
  373. }
  374. }
  375. else
  376. jf->source_type |= SDJF_LOCAL_ALL | SDJF_LOCAL_OTHER;
  377. jf->msg_last_ut = jf->file_last_modified_ut;
  378. nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
  379. "Journal file added to the journal files registry: '%s'",
  380. jf->filename);
  381. }
  382. static bool files_registry_conflict_cb(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
  383. struct journal_file *jf = old_value;
  384. struct journal_file *njf = new_value;
  385. if(njf->last_scan_monotonic_ut > jf->last_scan_monotonic_ut)
  386. jf->last_scan_monotonic_ut = njf->last_scan_monotonic_ut;
  387. if(njf->file_last_modified_ut > jf->file_last_modified_ut) {
  388. jf->file_last_modified_ut = njf->file_last_modified_ut;
  389. jf->size = njf->size;
  390. jf->msg_last_ut = jf->file_last_modified_ut;
  391. nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
  392. "Journal file updated to the journal files registry '%s'",
  393. jf->filename);
  394. }
  395. return false;
  396. }
  397. struct journal_file_source {
  398. usec_t first_ut;
  399. usec_t last_ut;
  400. size_t count;
  401. uint64_t size;
  402. };
  403. static void human_readable_size_ib(uint64_t size, char *dst, size_t dst_len) {
  404. if(size > 1024ULL * 1024 * 1024 * 1024)
  405. snprintfz(dst, dst_len, "%0.2f TiB", (double)size / 1024.0 / 1024.0 / 1024.0 / 1024.0);
  406. else if(size > 1024ULL * 1024 * 1024)
  407. snprintfz(dst, dst_len, "%0.2f GiB", (double)size / 1024.0 / 1024.0 / 1024.0);
  408. else if(size > 1024ULL * 1024)
  409. snprintfz(dst, dst_len, "%0.2f MiB", (double)size / 1024.0 / 1024.0);
  410. else if(size > 1024ULL)
  411. snprintfz(dst, dst_len, "%0.2f KiB", (double)size / 1024.0);
  412. else
  413. snprintfz(dst, dst_len, "%"PRIu64" B", size);
  414. }
  415. #define print_duration(dst, dst_len, pos, remaining, duration, one, many, printed) do { \
  416. if((remaining) > (duration)) { \
  417. uint64_t _count = (remaining) / (duration); \
  418. uint64_t _rem = (remaining) - (_count * (duration)); \
  419. (pos) += snprintfz(&(dst)[pos], (dst_len) - (pos), "%s%s%"PRIu64" %s", (printed) ? ", " : "", _rem ? "" : "and ", _count, _count > 1 ? (many) : (one)); \
  420. (remaining) = _rem; \
  421. (printed) = true; \
  422. } \
  423. } while(0)
  424. static void human_readable_duration_s(time_t duration_s, char *dst, size_t dst_len) {
  425. if(duration_s < 0)
  426. duration_s = -duration_s;
  427. size_t pos = 0;
  428. dst[0] = 0 ;
  429. bool printed = false;
  430. print_duration(dst, dst_len, pos, duration_s, 86400 * 365, "year", "years", printed);
  431. print_duration(dst, dst_len, pos, duration_s, 86400 * 30, "month", "months", printed);
  432. print_duration(dst, dst_len, pos, duration_s, 86400 * 1, "day", "days", printed);
  433. print_duration(dst, dst_len, pos, duration_s, 3600 * 1, "hour", "hours", printed);
  434. print_duration(dst, dst_len, pos, duration_s, 60 * 1, "min", "mins", printed);
  435. print_duration(dst, dst_len, pos, duration_s, 1, "sec", "secs", printed);
  436. }
  437. static int journal_file_to_json_array_cb(const DICTIONARY_ITEM *item, void *entry, void *data) {
  438. struct journal_file_source *jfs = entry;
  439. BUFFER *wb = data;
  440. const char *name = dictionary_acquired_item_name(item);
  441. buffer_json_add_array_item_object(wb);
  442. {
  443. char size_for_humans[100];
  444. human_readable_size_ib(jfs->size, size_for_humans, sizeof(size_for_humans));
  445. char duration_for_humans[1024];
  446. human_readable_duration_s((time_t)((jfs->last_ut - jfs->first_ut) / USEC_PER_SEC),
  447. duration_for_humans, sizeof(duration_for_humans));
  448. char info[1024];
  449. snprintfz(info, sizeof(info), "%zu files, with a total size of %s, covering %s",
  450. jfs->count, size_for_humans, duration_for_humans);
  451. buffer_json_member_add_string(wb, "id", name);
  452. buffer_json_member_add_string(wb, "name", name);
  453. buffer_json_member_add_string(wb, "pill", size_for_humans);
  454. buffer_json_member_add_string(wb, "info", info);
  455. }
  456. buffer_json_object_close(wb); // options object
  457. return 1;
  458. }
  459. static bool journal_file_merge_sizes(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value , void *data __maybe_unused) {
  460. struct journal_file_source *jfs = old_value, *njfs = new_value;
  461. jfs->count += njfs->count;
  462. jfs->size += njfs->size;
  463. if(njfs->first_ut && njfs->first_ut < jfs->first_ut)
  464. jfs->first_ut = njfs->first_ut;
  465. if(njfs->last_ut && njfs->last_ut > jfs->last_ut)
  466. jfs->last_ut = njfs->last_ut;
  467. return false;
  468. }
  469. void available_journal_file_sources_to_json_array(BUFFER *wb) {
  470. DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_NAME_LINK_DONT_CLONE|DICT_OPTION_DONT_OVERWRITE_VALUE);
  471. dictionary_register_conflict_callback(dict, journal_file_merge_sizes, NULL);
  472. struct journal_file_source t = { 0 };
  473. struct journal_file *jf;
  474. dfe_start_read(journal_files_registry, jf) {
  475. t.first_ut = jf->msg_first_ut;
  476. t.last_ut = jf->msg_last_ut;
  477. t.count = 1;
  478. t.size = jf->size;
  479. dictionary_set(dict, SDJF_SOURCE_ALL_NAME, &t, sizeof(t));
  480. if(jf->source_type & SDJF_LOCAL_ALL)
  481. dictionary_set(dict, SDJF_SOURCE_LOCAL_NAME, &t, sizeof(t));
  482. if(jf->source_type & SDJF_LOCAL_SYSTEM)
  483. dictionary_set(dict, SDJF_SOURCE_LOCAL_SYSTEM_NAME, &t, sizeof(t));
  484. if(jf->source_type & SDJF_LOCAL_USER)
  485. dictionary_set(dict, SDJF_SOURCE_LOCAL_USERS_NAME, &t, sizeof(t));
  486. if(jf->source_type & SDJF_LOCAL_OTHER)
  487. dictionary_set(dict, SDJF_SOURCE_LOCAL_OTHER_NAME, &t, sizeof(t));
  488. if(jf->source_type & SDJF_LOCAL_NAMESPACE)
  489. dictionary_set(dict, SDJF_SOURCE_NAMESPACES_NAME, &t, sizeof(t));
  490. if(jf->source_type & SDJF_REMOTE_ALL)
  491. dictionary_set(dict, SDJF_SOURCE_REMOTES_NAME, &t, sizeof(t));
  492. if(jf->source)
  493. dictionary_set(dict, string2str(jf->source), &t, sizeof(t));
  494. }
  495. dfe_done(jf);
  496. dictionary_sorted_walkthrough_read(dict, journal_file_to_json_array_cb, wb);
  497. dictionary_destroy(dict);
  498. }
  499. static void files_registry_delete_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
  500. struct journal_file *jf = value; (void)jf;
  501. const char *filename = dictionary_acquired_item_name(item); (void)filename;
  502. internal_error(true, "removed journal file '%s'", filename);
  503. string_freez(jf->source);
  504. }
  505. void journal_directory_scan_recursively(DICTIONARY *files, DICTIONARY *dirs, const char *dirname, int depth) {
  506. static const char *ext = ".journal";
  507. static const ssize_t ext_len = sizeof(".journal") - 1;
  508. if (depth > VAR_LOG_JOURNAL_MAX_DEPTH)
  509. return;
  510. DIR *dir;
  511. struct dirent *entry;
  512. char full_path[FILENAME_MAX];
  513. // Open the directory.
  514. if ((dir = opendir(dirname)) == NULL) {
  515. if(errno != ENOENT && errno != ENOTDIR)
  516. netdata_log_error("Cannot opendir() '%s'", dirname);
  517. return;
  518. }
  519. bool existing = false;
  520. bool *found = dictionary_set(dirs, dirname, &existing, sizeof(existing));
  521. if(*found) return;
  522. *found = true;
  523. // Read each entry in the directory.
  524. while ((entry = readdir(dir)) != NULL) {
  525. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
  526. continue;
  527. ssize_t len = snprintfz(full_path, sizeof(full_path), "%s/%s", dirname, entry->d_name);
  528. if (entry->d_type == DT_DIR) {
  529. journal_directory_scan_recursively(files, dirs, full_path, depth++);
  530. }
  531. else if (entry->d_type == DT_REG && len > ext_len && strcmp(full_path + len - ext_len, ext) == 0) {
  532. if(files)
  533. dictionary_set(files, full_path, NULL, 0);
  534. send_newline_and_flush();
  535. }
  536. else if (entry->d_type == DT_LNK) {
  537. struct stat info;
  538. if (stat(full_path, &info) == -1)
  539. continue;
  540. if (S_ISDIR(info.st_mode)) {
  541. // The symbolic link points to a directory
  542. char resolved_path[FILENAME_MAX + 1];
  543. if (realpath(full_path, resolved_path) != NULL) {
  544. journal_directory_scan_recursively(files, dirs, resolved_path, depth++);
  545. }
  546. }
  547. else if(S_ISREG(info.st_mode) && len > ext_len && strcmp(full_path + len - ext_len, ext) == 0) {
  548. if(files)
  549. dictionary_set(files, full_path, NULL, 0);
  550. send_newline_and_flush();
  551. }
  552. }
  553. }
  554. closedir(dir);
  555. }
  556. static size_t journal_files_scans = 0;
  557. bool journal_files_completed_once(void) {
  558. return journal_files_scans > 0;
  559. }
  560. int filenames_compar(const void *a, const void *b) {
  561. const char *p1 = *(const char **)a;
  562. const char *p2 = *(const char **)b;
  563. const char *at1 = strchr(p1, '@');
  564. const char *at2 = strchr(p2, '@');
  565. if(!at1 && at2)
  566. return -1;
  567. if(at1 && !at2)
  568. return 1;
  569. if(!at1 && !at2)
  570. return strcmp(p1, p2);
  571. const char *dash1 = strrchr(at1, '-');
  572. const char *dash2 = strrchr(at2, '-');
  573. if(!dash1 || !dash2)
  574. return strcmp(p1, p2);
  575. uint64_t ts1 = strtoul(dash1 + 1, NULL, 16);
  576. uint64_t ts2 = strtoul(dash2 + 1, NULL, 16);
  577. if(ts1 > ts2)
  578. return -1;
  579. if(ts1 < ts2)
  580. return 1;
  581. return -strcmp(p1, p2);
  582. }
  583. void journal_files_registry_update(void) {
  584. static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
  585. if(spinlock_trylock(&spinlock)) {
  586. usec_t scan_monotonic_ut = now_monotonic_usec();
  587. DICTIONARY *files = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
  588. DICTIONARY *dirs = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE);
  589. for(unsigned i = 0; i < MAX_JOURNAL_DIRECTORIES; i++) {
  590. if(!journal_directories[i].path) break;
  591. journal_directory_scan_recursively(files, dirs, string2str(journal_directories[i].path), 0);
  592. }
  593. const char **array = mallocz(sizeof(const char *) * dictionary_entries(files));
  594. size_t used = 0;
  595. void *x;
  596. dfe_start_read(files, x) {
  597. if(used >= dictionary_entries(files)) continue;
  598. array[used++] = x_dfe.name;
  599. }
  600. dfe_done(x);
  601. qsort(array, used, sizeof(const char *), filenames_compar);
  602. for(size_t i = 0; i < used ;i++) {
  603. const char *full_path = array[i];
  604. struct stat info;
  605. if (stat(full_path, &info) == -1)
  606. continue;
  607. struct journal_file t = {
  608. .file_last_modified_ut = info.st_mtim.tv_sec * USEC_PER_SEC + info.st_mtim.tv_nsec / NSEC_PER_USEC,
  609. .last_scan_monotonic_ut = scan_monotonic_ut,
  610. .size = info.st_size,
  611. .max_journal_vs_realtime_delta_ut = JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT,
  612. };
  613. struct journal_file *jf = dictionary_set(journal_files_registry, full_path, &t, sizeof(t));
  614. journal_file_update_header(jf->filename, jf);
  615. }
  616. freez(array);
  617. dictionary_destroy(files);
  618. dictionary_destroy(dirs);
  619. struct journal_file *jf;
  620. dfe_start_write(journal_files_registry, jf){
  621. if(jf->last_scan_monotonic_ut < scan_monotonic_ut)
  622. dictionary_del(journal_files_registry, jf_dfe.name);
  623. }
  624. dfe_done(jf);
  625. journal_files_scans++;
  626. spinlock_unlock(&spinlock);
  627. internal_error(true,
  628. "Journal library scan completed in %.3f ms",
  629. (double)(now_monotonic_usec() - scan_monotonic_ut) / (double)USEC_PER_MS);
  630. }
  631. }
  632. // ----------------------------------------------------------------------------
  633. int journal_file_dict_items_backward_compar(const void *a, const void *b) {
  634. const DICTIONARY_ITEM **ad = (const DICTIONARY_ITEM **)a, **bd = (const DICTIONARY_ITEM **)b;
  635. struct journal_file *jfa = dictionary_acquired_item_value(*ad);
  636. struct journal_file *jfb = dictionary_acquired_item_value(*bd);
  637. // compare the last message timestamps
  638. if(jfa->msg_last_ut < jfb->msg_last_ut)
  639. return 1;
  640. if(jfa->msg_last_ut > jfb->msg_last_ut)
  641. return -1;
  642. // compare the file last modification timestamps
  643. if(jfa->file_last_modified_ut < jfb->file_last_modified_ut)
  644. return 1;
  645. if(jfa->file_last_modified_ut > jfb->file_last_modified_ut)
  646. return -1;
  647. // compare the first message timestamps
  648. if(jfa->msg_first_ut < jfb->msg_first_ut)
  649. return 1;
  650. if(jfa->msg_first_ut > jfb->msg_first_ut)
  651. return -1;
  652. return 0;
  653. }
  654. int journal_file_dict_items_forward_compar(const void *a, const void *b) {
  655. return -journal_file_dict_items_backward_compar(a, b);
  656. }
  657. static bool boot_id_conflict_cb(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
  658. usec_t *old_usec = old_value;
  659. usec_t *new_usec = new_value;
  660. if(*new_usec < *old_usec) {
  661. *old_usec = *new_usec;
  662. return true;
  663. }
  664. return false;
  665. }
  666. void journal_init_files_and_directories(void) {
  667. unsigned d = 0;
  668. // ------------------------------------------------------------------------
  669. // setup the journal directories
  670. journal_directories[d++].path = string_strdupz("/run/log/journal");
  671. journal_directories[d++].path = string_strdupz("/var/log/journal");
  672. if(*netdata_configured_host_prefix) {
  673. char path[PATH_MAX];
  674. snprintfz(path, sizeof(path), "%s/var/log/journal", netdata_configured_host_prefix);
  675. journal_directories[d++].path = string_strdupz(path);
  676. snprintfz(path, sizeof(path), "%s/run/log/journal", netdata_configured_host_prefix);
  677. journal_directories[d++].path = string_strdupz(path);
  678. }
  679. // terminate the list
  680. journal_directories[d].path = NULL;
  681. // ------------------------------------------------------------------------
  682. // initialize the used hashes files registry
  683. used_hashes_registry = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
  684. systemd_journal_session = (now_realtime_usec() / USEC_PER_SEC) * USEC_PER_SEC;
  685. journal_files_registry = dictionary_create_advanced(
  686. DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
  687. NULL, sizeof(struct journal_file));
  688. dictionary_register_insert_callback(journal_files_registry, files_registry_insert_cb, NULL);
  689. dictionary_register_delete_callback(journal_files_registry, files_registry_delete_cb, NULL);
  690. dictionary_register_conflict_callback(journal_files_registry, files_registry_conflict_cb, NULL);
  691. boot_ids_to_first_ut = dictionary_create_advanced(
  692. DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
  693. NULL, sizeof(usec_t));
  694. dictionary_register_conflict_callback(boot_ids_to_first_ut, boot_id_conflict_cb, NULL);
  695. }