1 /* inode-sig.c:
2 *
3 ****************************************************************
4 * Copyright (C) 2003 Tom Lord
5 *
6 * See the file "COPYING" for further information about
7 * the copyright and warranty status of this work.
8 */
9
10
11 #include "hackerlab/bugs/panic.h"
12 #include "hackerlab/mem/mem.h"
13 #include "hackerlab/char/str.h"
14 #include "hackerlab/fs/file-names.h"
15 #include "hackerlab/vu/safe.h"
16 #include "tla/libfsutils/ensure-dir.h"
17 #include "tla/libfsutils/dir-listing.h"
18 #include "tla/libfsutils/string-files.h"
19 #include "tla/libawk/relational.h"
20 #include "tla/libawk/relassoc.h"
21 #include "tla/libarch/namespace.h"
22 #include "tla/libarch/invent.h"
23 #include "tla/libarch/inode-sig.h"
24
25
26
27 #define MAX_INODE_SIG_FILES 5
28
29
30 /* __STDC__ prototypes for static functions */
31 static void arch_update_inode_sig (rel_table sig,
32 assoc_table newsig,
33 assoc_table id_list);
34 static void inode_sig_callback (const t_uchar * path,
35 struct stat * stat_buf,
36 enum arch_inventory_category category,
37 const t_uchar * id,
38 int has_source_name,
39 void * closure,
40 int escape_classes);
41 static t_uchar * arch_inode_sig_dir (const t_uchar * tree_root);
42 static t_uchar * arch_inode_sig_file (const t_uchar * tree_root,
43 const t_uchar * archive,
44 const t_uchar * revision);
45 static t_uchar * arch_inode_sig_tmp_file (const t_uchar * tree_root,
46 const t_uchar * archive,
47 const t_uchar * revision);
48 static int arch_creat_inode_sig_file (const t_uchar * tree_root,
49 const t_uchar * archive,
50 const t_uchar * revision);
51 static void arch_finish_inode_sig_file (const t_uchar * tree_root,
52 const t_uchar * archive,
53 const t_uchar * revision,
54 int fd);
55 static void arch_prune_inode_sig_dir (const t_uchar * tree_root,
56 const t_uchar * current_inode_sig_file);
57 static void prune_old_ctime_cruft (rel_table the_table);
58 static void prune_old_dev_cruft (rel_table the_table);
59
60
61
62 t_uchar *
arch_statb_inode_sig(struct stat * statb)63 arch_statb_inode_sig (struct stat * statb)
64 {
65 int fd = make_output_to_string_fd ();
66
67 safe_printfmt (fd, "ino=%lu:mtime=%lu:size=%lu",
68 (t_ulong)statb->st_ino,
69 (t_ulong)statb->st_mtime,
70 (t_ulong)statb->st_size);
71
72 return string_fd_close (fd);
73 }
74
75
76 rel_table
arch_tree_inode_sig(const t_uchar * tree_root)77 arch_tree_inode_sig (const t_uchar * tree_root)
78 {
79 struct arch_inventory_options options = {0, };
80 int here_fd;
81 rel_table answer = rel_table_nil;
82
83 here_fd = safe_open (".", O_RDONLY, 0);
84 safe_chdir (tree_root);
85
86 options.categories = arch_inventory_source;
87 options.want_ids = 1;
88 options.include_excluded = 1;
89
90 arch_get_inventory_naming_conventions (&options, ".");
91
92 arch_inventory_traversal (&options, ".", inode_sig_callback, (void *)&answer, 0);
93
94 arch_free_inventory_naming_conventions (&options);
95
96 rel_sort_table_by_field (0, answer, 0);
97
98 safe_fchdir (here_fd);
99 safe_close (here_fd);
100
101 return answer;
102 }
103
104 void
arch_snap_inode_sig(const t_uchar * tree_root,const t_uchar * archive,const t_uchar * revision)105 arch_snap_inode_sig (const t_uchar * tree_root,
106 const t_uchar * archive,
107 const t_uchar * revision)
108 {
109 rel_table sig = arch_tree_inode_sig (tree_root);
110 int fd = arch_creat_inode_sig_file (tree_root, archive, revision);
111
112 rel_print_table (fd, sig);
113 arch_finish_inode_sig_file (tree_root, archive, revision, fd);
114
115 rel_free_table (sig);
116 }
117
118
119 void
arch_snap_inode_sig_files(const t_uchar * tree_root,const t_uchar * archive,const t_uchar * prev_revision,const t_uchar * revision,rel_table file_list)120 arch_snap_inode_sig_files (const t_uchar * tree_root,
121 const t_uchar * archive,
122 const t_uchar * prev_revision,
123 const t_uchar * revision,
124 rel_table file_list)
125 {
126 rel_table oldsig;
127 assoc_table newsig_as;
128 rel_table newsig_rel;
129 assoc_table id_list;
130 int fd = -1;
131
132 arch_read_inode_sig (&oldsig, 0, tree_root, archive, prev_revision);
133 newsig_rel = arch_tree_inode_sig (tree_root);
134 newsig_as = rel_to_assoc (newsig_rel, 0, 1);
135
136 id_list = arch_filenames_ids ( &file_list, tree_root);
137
138 arch_update_inode_sig (oldsig, newsig_as, id_list);
139
140 fd = arch_creat_inode_sig_file (tree_root, archive, revision);
141 rel_print_table (fd, oldsig);
142 arch_finish_inode_sig_file (tree_root, archive, revision, fd);
143
144 rel_free_table (oldsig);
145 rel_free_table (newsig_rel);
146 free_assoc_table (newsig_as);
147 free_assoc_table (id_list);
148 }
149
150
151 static void
arch_update_inode_sig(rel_table sig,assoc_table newsig,assoc_table id_list)152 arch_update_inode_sig (rel_table sig,
153 assoc_table newsig,
154 assoc_table id_list)
155 {
156 int sig_size = rel_n_records (sig);
157 int i;
158
159 for (i = 0; i != sig_size; ++ i)
160 {
161 if (assoc_get_str_taking (id_list, rel_get_field (sig, i, 0)))
162 {
163 rel_record new_record = rel_record_nil;
164 rel_field vals_field = assoc_get_taking (newsig, rel_get_field (sig, i, 0));
165
166 invariant (rel_field_str (vals_field) != 0);
167
168 new_record = rel_make_record_2_taking (rel_get_field (sig, i, 0), rel_field_ref (vals_field));
169 rel_set_record (sig, i, new_record);
170 rel_field_unref (vals_field);
171 }
172 }
173 }
174
175
176 int
arch_valid_inode_sig(const t_uchar * tree_root,const t_uchar * archive,const t_uchar * revision)177 arch_valid_inode_sig (const t_uchar * tree_root,
178 const t_uchar * archive,
179 const t_uchar * revision)
180 {
181 int result = 1;
182 t_uchar * sig_file = arch_inode_sig_file(tree_root, archive, revision);
183
184 if (safe_access (sig_file, F_OK))
185 {
186 safe_printfmt (2, "Missing inode signature. Cannot verify reference tree integrity: you should remove and recreate this reference tree.\n tree: %s\n archive: %s\n revision: %s\n",
187 tree_root, archive, revision);
188 }
189 else
190 {
191 rel_table existing_sig = rel_table_nil;
192 rel_table current_sig = arch_tree_inode_sig (tree_root);
193
194 arch_read_inode_sig (&existing_sig, NULL, tree_root, archive, revision);
195 /* Is there a convenience function (a rel_join magic foo?) for this */
196 if (rel_n_records (existing_sig) == rel_n_records (current_sig))
197 {
198 int counter,inner;
199 for (counter = 0; counter < rel_n_records (existing_sig); ++counter)
200 for (inner = 0; inner < 2; ++inner)
201 if (str_cmp (rel_peek_str (existing_sig, counter, inner), rel_peek_str (current_sig, counter, inner)))
202 result = 0;
203 }
204 else result = 0;
205 rel_free_table (existing_sig);
206 rel_free_table (current_sig);
207 }
208
209 lim_free (0, sig_file);
210 return result;
211 }
212
213
214 void
arch_read_inode_sig(rel_table * as_table,assoc_table * as_assoc,const t_uchar * tree_root,const t_uchar * archive,const t_uchar * revision)215 arch_read_inode_sig (rel_table * as_table,
216 assoc_table * as_assoc,
217 const t_uchar * tree_root,
218 const t_uchar * archive,
219 const t_uchar * revision)
220 {
221 t_uchar * sig_file = arch_inode_sig_file (tree_root, archive, revision);
222
223 if (as_table)
224 *as_table = rel_table_nil;
225
226 if (as_assoc)
227 *as_assoc = 0;
228
229 if (!safe_access (sig_file, F_OK))
230 {
231 int fd = safe_open (sig_file, O_RDONLY, 0);
232 rel_table the_table;
233
234 the_table = rel_read_table (fd, 2, "arch", sig_file);
235
236 prune_old_ctime_cruft (the_table);
237 prune_old_dev_cruft (the_table);
238
239 if (as_assoc)
240 *as_assoc = rel_to_assoc (the_table, 0, 1);
241
242 if (as_table)
243 *as_table = the_table;
244 else
245 rel_free_table (the_table);
246
247 safe_close (fd);
248 }
249
250 lim_free (0, sig_file);
251 }
252
253
254 void
arch_read_id_shortcut(assoc_table * ids_shortcut,const t_uchar * tree_root)255 arch_read_id_shortcut (assoc_table * ids_shortcut,
256 const t_uchar * tree_root)
257 {
258 t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
259 rel_table dir_listing = maybe_directory_files (inode_sig_dir);
260 assoc_table answer = 0;
261 assoc_table rejected = 0;
262 int x;
263
264 for (x = 0; x < rel_n_records (dir_listing); ++x)
265 {
266 t_uchar * path = file_name_in_vicinity (0, inode_sig_dir, rel_peek_str (dir_listing, x, 0));
267 t_uchar * pct = str_chr_index (rel_peek_str (dir_listing, x, 0), '%');
268
269 if (pct && str_cmp (".", rel_peek_str (dir_listing, x, 0)) && str_cmp ("..", rel_peek_str (dir_listing, x, 0)))
270 {
271 t_uchar * maybe_archive = str_save_n (0, rel_peek_str (dir_listing, x, 0), pct - rel_peek_str (dir_listing, x, 0));
272 t_uchar * maybe_revision = str_save (0, pct + 1);
273
274 if (arch_valid_archive_name (maybe_archive) && arch_valid_package_name (maybe_revision, arch_no_archive, arch_req_patch_level, 0))
275 {
276 rel_table sig_table = rel_table_nil;
277 int y;
278
279 arch_read_inode_sig (&sig_table, 0, tree_root, maybe_archive, maybe_revision);
280
281 for (y = 0; y < rel_n_records (sig_table); ++y)
282 {
283 rel_field this_id_field;
284 rel_field this_sig_field;
285 const t_uchar * is_rejected;
286 const t_uchar * already_have;
287
288 this_id_field = rel_get_field (sig_table, y, 0);
289 this_sig_field = rel_get_field (sig_table, y, 1);
290
291 rel_field_ref (this_sig_field);
292 is_rejected = assoc_get_str_taking (rejected, this_sig_field);
293
294 rel_field_ref (this_sig_field);
295 already_have = assoc_get_str_taking (answer, this_sig_field);
296
297 if (!is_rejected)
298 {
299 if (already_have)
300 {
301 if (!str_cmp (rel_field_str (this_id_field), already_have))
302 {
303 assoc_del_taking (answer, rel_field_ref (this_sig_field));
304 assoc_set_taking (&rejected, rel_field_ref (this_sig_field), rel_make_field_str ("yes"));
305 }
306 }
307 else
308 {
309 assoc_set_taking (&answer, rel_field_ref (this_sig_field), rel_field_ref (this_id_field));
310 }
311 }
312
313 rel_field_unref (this_id_field);
314 rel_field_unref (this_sig_field);
315 }
316
317 rel_free_table (sig_table);
318 }
319
320 lim_free (0, maybe_archive);
321 lim_free (0, maybe_revision);
322 }
323
324 lim_free (0, path);
325 }
326
327 lim_free (0, inode_sig_dir);
328 rel_free_table (dir_listing);
329 free_assoc_table (rejected);
330
331 *ids_shortcut = answer;
332 }
333
334
335
336
337 static void
inode_sig_callback(const t_uchar * path,struct stat * stat_buf,enum arch_inventory_category category,const t_uchar * id,int has_source_name,void * closure,int escape_classes)338 inode_sig_callback (const t_uchar * path,
339 struct stat * stat_buf,
340 enum arch_inventory_category category,
341 const t_uchar * id,
342 int has_source_name,
343 void * closure,
344 int escape_classes)
345 {
346 rel_table * answer = (rel_table *)closure;
347 t_uchar * signature = arch_statb_inode_sig (stat_buf);
348
349 if (!S_ISDIR (stat_buf->st_mode) && !S_ISLNK (stat_buf->st_mode))
350 rel_add_records (answer, rel_make_record_2_taking (rel_make_field_str (id), rel_make_field_str (signature)), rel_record_null);
351 lim_free(0, signature);
352 }
353
354
355 static t_uchar *
arch_inode_sig_dir(const t_uchar * tree_root)356 arch_inode_sig_dir (const t_uchar * tree_root)
357 {
358 return file_name_in_vicinity (0, tree_root, "{arch}/,,inode-sigs");
359 }
360
361
362 static t_uchar *
arch_inode_sig_file(const t_uchar * tree_root,const t_uchar * archive,const t_uchar * revision)363 arch_inode_sig_file (const t_uchar * tree_root,
364 const t_uchar * archive,
365 const t_uchar * revision)
366 {
367 t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
368 t_uchar * basename = str_alloc_cat_many (0, archive, "%", revision, str_end);
369 t_uchar * answer = file_name_in_vicinity (0, inode_sig_dir, basename);
370
371 lim_free (0, inode_sig_dir);
372 lim_free (0, basename);
373 return answer;
374 }
375
376
377 static t_uchar *
arch_inode_sig_tmp_file(const t_uchar * tree_root,const t_uchar * archive,const t_uchar * revision)378 arch_inode_sig_tmp_file (const t_uchar * tree_root,
379 const t_uchar * archive,
380 const t_uchar * revision)
381 {
382 t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
383 t_uchar * basename = str_alloc_cat_many (0, ",,", archive, "%", revision, str_end);
384 t_uchar * answer = file_name_in_vicinity (0, inode_sig_dir, basename);
385
386 lim_free (0, inode_sig_dir);
387 lim_free (0, basename);
388 return answer;
389 }
390
391
392 static int
arch_creat_inode_sig_file(const t_uchar * tree_root,const t_uchar * archive,const t_uchar * revision)393 arch_creat_inode_sig_file (const t_uchar * tree_root,
394 const t_uchar * archive,
395 const t_uchar * revision)
396 {
397 t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
398 t_uchar * inode_sig_tmp_file = arch_inode_sig_tmp_file (tree_root, archive, revision);
399 int ign;
400 int answer;
401
402 ensure_directory_exists (inode_sig_dir);
403 vu_unlink (&ign, inode_sig_tmp_file);
404 answer = safe_open (inode_sig_tmp_file, (O_WRONLY | O_CREAT | O_EXCL), 0444);
405
406 lim_free (0, inode_sig_dir);
407 lim_free (0, inode_sig_tmp_file);
408
409 return answer;
410 }
411
412
413 static void
arch_finish_inode_sig_file(const t_uchar * tree_root,const t_uchar * archive,const t_uchar * revision,int fd)414 arch_finish_inode_sig_file (const t_uchar * tree_root,
415 const t_uchar * archive,
416 const t_uchar * revision,
417 int fd)
418 {
419 t_uchar * inode_sig_tmp_file = arch_inode_sig_tmp_file (tree_root, archive, revision);
420 t_uchar * inode_sig_file = arch_inode_sig_file (tree_root, archive, revision);
421
422 safe_close (fd);
423 safe_rename (inode_sig_tmp_file, inode_sig_file);
424 arch_prune_inode_sig_dir (tree_root, inode_sig_file);
425
426 lim_free (0, inode_sig_tmp_file);
427 lim_free (0, inode_sig_file);
428 }
429
430
431 static void
arch_prune_inode_sig_dir(const t_uchar * tree_root,const t_uchar * current_inode_sig_file)432 arch_prune_inode_sig_dir (const t_uchar * tree_root,
433 const t_uchar * current_inode_sig_file)
434 {
435 t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
436 rel_table dir_listing = maybe_directory_files (inode_sig_dir);
437 int dead_one = -1;
438 time_t oldest_time;
439 int n_inode_sig_files;
440 int x;
441
442 do
443 {
444 n_inode_sig_files = 0;
445
446 for (x = 0; x < rel_n_records (dir_listing); ++x)
447 {
448 t_uchar * path = file_name_in_vicinity (0, inode_sig_dir, rel_peek_str (dir_listing, x, 0));
449 t_uchar * pct = str_chr_index (rel_peek_str (dir_listing, x, 0), '%');
450
451 if (pct && str_cmp (".", rel_peek_str (dir_listing, x, 0)) && str_cmp ("..", rel_peek_str (dir_listing, x, 0)))
452 {
453 t_uchar * maybe_archive = str_save_n (0, rel_peek_str (dir_listing, x, 0), pct - rel_peek_str (dir_listing, x, 0));
454 t_uchar * maybe_revision = str_save (0, pct + 1);
455
456 if (arch_valid_archive_name (maybe_archive) && arch_valid_package_name (maybe_revision, arch_no_archive, arch_req_patch_level, 0))
457 {
458 struct stat statb;
459
460 ++n_inode_sig_files;
461
462 safe_lstat (path, &statb);
463
464 if (str_cmp (current_inode_sig_file, path) && ((dead_one < 0) || (statb.st_mtime < oldest_time)))
465 {
466 dead_one = x;
467 oldest_time = statb.st_mtime;
468 }
469 }
470
471 lim_free (0, maybe_archive);
472 lim_free (0, maybe_revision);
473 }
474
475 lim_free (0, path);
476 }
477
478 if (n_inode_sig_files > MAX_INODE_SIG_FILES)
479 {
480 t_uchar * dead_path = file_name_in_vicinity (0, inode_sig_dir, rel_peek_str (dir_listing, dead_one, 0));
481
482 safe_unlink (dead_path);
483 --n_inode_sig_files;
484
485 lim_free (0, dead_path);
486 }
487
488
489 } while (n_inode_sig_files > MAX_INODE_SIG_FILES); /* usually only one iteration expected */
490
491 lim_free (0, inode_sig_dir);
492 rel_free_table (dir_listing);
493 }
494
495
496 static void
prune_old_ctime_cruft(rel_table the_table)497 prune_old_ctime_cruft (rel_table the_table)
498 {
499 int x;
500
501 #undef MIN
502 #define MIN(A,B) (((A) < (B)) ? (A) : (B))
503
504 for (x = 0; x < rel_n_records (the_table); ++x)
505 {
506 const t_uchar * sig = rel_peek_str (the_table, x, 1);
507 const t_uchar * third_colon;
508 const t_uchar * fourth_colon;
509 t_uchar * fixed_sig = 0;
510
511 third_colon = str_chr_index (sig, ':');
512 if (!third_colon)
513 return;
514
515 third_colon = str_chr_index (third_colon + 1, ':');
516 if (!third_colon)
517 return;
518
519 third_colon = str_chr_index (third_colon + 1, ':');
520 if (!third_colon)
521 return;
522
523 if (str_cmp_n (third_colon + 1, MIN (5, str_length (third_colon + 1)), "ctime", (size_t)5))
524 return;
525
526 fourth_colon = str_chr_index (third_colon + 1, ':');
527 if (!fourth_colon)
528 return;
529
530 fixed_sig = str_save_n (0, sig, third_colon - sig);
531 fixed_sig = str_realloc_cat_n (0, fixed_sig, fourth_colon, 1 + str_length (fourth_colon));
532
533 rel_set_taking (the_table, x, 1, rel_make_field_str (fixed_sig));
534
535 lim_free (0, fixed_sig);
536 }
537 }
538
539
540 static void
prune_old_dev_cruft(rel_table the_table)541 prune_old_dev_cruft (rel_table the_table)
542 {
543 int x;
544
545 #undef MIN
546 #define MIN(A,B) (((A) < (B)) ? (A) : (B))
547
548 for (x = 0; x < rel_n_records (the_table); ++x)
549 {
550 const t_uchar * sig = rel_peek_str (the_table, x, 1);
551 const t_uchar * first_colon;
552
553 if (str_cmp_n (sig, MIN (3, str_length (sig)), "dev", (size_t)3))
554 return;
555
556 first_colon = str_chr_index (sig, ':');
557 if (!first_colon)
558 return;
559
560 rel_set_taking (the_table, x, 1, rel_make_field_str_n (first_colon + 1, str_length (first_colon)));
561 }
562 }
563
564
565
566
567
568 /* tag: Tom Lord Fri Sep 12 09:56:44 2003 (inode-sig.c)
569 */
570