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