bc4083aed2cf988922a259e9a391f553eb96fc00
[paraslash.git] / fsck.c
1 #include "para.h"
2 #include "error.h"
3 #include "osl_core.h"
4
5 #define OSL_DUMP_DIR "/tmp/osldump"
6
7 enum errors {
8         E_FSCK_SYNTAX = 501,
9         E_RANGE_VIOLATION,
10         E_NO_DS_FILE,
11         E_NOT_A_REGULAR_FILE,
12         E_BAD_HASH_PATH,
13 };
14
15 INIT_STDERR_LOGGING(1);
16
17 /* taken from git */
18 signed char hexval_table[256] = {
19          -1, -1, -1, -1, -1, -1, -1, -1,                /* 00-07 */
20          -1, -1, -1, -1, -1, -1, -1, -1,                /* 08-0f */
21          -1, -1, -1, -1, -1, -1, -1, -1,                /* 10-17 */
22          -1, -1, -1, -1, -1, -1, -1, -1,                /* 18-1f */
23          -1, -1, -1, -1, -1, -1, -1, -1,                /* 20-27 */
24          -1, -1, -1, -1, -1, -1, -1, -1,                /* 28-2f */
25           0,  1,  2,  3,  4,  5,  6,  7,                /* 30-37 */
26           8,  9, -1, -1, -1, -1, -1, -1,                /* 38-3f */
27          -1, 10, 11, 12, 13, 14, 15, -1,                /* 40-47 */
28          -1, -1, -1, -1, -1, -1, -1, -1,                /* 48-4f */
29          -1, -1, -1, -1, -1, -1, -1, -1,                /* 50-57 */
30          -1, -1, -1, -1, -1, -1, -1, -1,                /* 58-5f */
31          -1, 10, 11, 12, 13, 14, 15, -1,                /* 60-67 */
32          -1, -1, -1, -1, -1, -1, -1, -1,                /* 68-67 */
33          -1, -1, -1, -1, -1, -1, -1, -1,                /* 70-77 */
34          -1, -1, -1, -1, -1, -1, -1, -1,                /* 78-7f */
35          -1, -1, -1, -1, -1, -1, -1, -1,                /* 80-87 */
36          -1, -1, -1, -1, -1, -1, -1, -1,                /* 88-8f */
37          -1, -1, -1, -1, -1, -1, -1, -1,                /* 90-97 */
38          -1, -1, -1, -1, -1, -1, -1, -1,                /* 98-9f */
39          -1, -1, -1, -1, -1, -1, -1, -1,                /* a0-a7 */
40          -1, -1, -1, -1, -1, -1, -1, -1,                /* a8-af */
41          -1, -1, -1, -1, -1, -1, -1, -1,                /* b0-b7 */
42          -1, -1, -1, -1, -1, -1, -1, -1,                /* b8-bf */
43          -1, -1, -1, -1, -1, -1, -1, -1,                /* c0-c7 */
44          -1, -1, -1, -1, -1, -1, -1, -1,                /* c8-cf */
45          -1, -1, -1, -1, -1, -1, -1, -1,                /* d0-d7 */
46          -1, -1, -1, -1, -1, -1, -1, -1,                /* d8-df */
47          -1, -1, -1, -1, -1, -1, -1, -1,                /* e0-e7 */
48          -1, -1, -1, -1, -1, -1, -1, -1,                /* e8-ef */
49          -1, -1, -1, -1, -1, -1, -1, -1,                /* f0-f7 */
50          -1, -1, -1, -1, -1, -1, -1, -1,                /* f8-ff */
51 };
52
53 int asc_to_hash(const char *asc_hash, int len, HASH_TYPE *hash)
54 {
55         int i = 0;
56         const unsigned char *asc = (const unsigned char *) asc_hash;
57
58         while (*asc && i++ < len) {
59                 unsigned int val = (hexval_table[asc[0]] << 4) | hexval_table[asc[1]];
60                 if (val & ~0xff)
61                         return -1;
62                 *hash++ = val;
63                 asc += 2;
64
65         }
66         return 1;
67 }
68
69 /*
70  * check for object boundary violations
71  *
72  * test whether the range pointed to by the index entry for a given cell is
73  * contained in mapped data file. This should always be the case. Otherwise
74  * we are in real trouble.
75  */
76 static int check_range(struct osl_table *t, uint32_t row_num, uint32_t col_num)
77 {
78         char *index_entry;
79         struct osl_object obj;
80         struct osl_column *col;
81         int ret;
82         char *map_start, *obj_start;
83
84         ret = get_cell_index(t, row_num, col_num, &index_entry);
85         if (ret < 0)
86                 return ret;
87         ret = get_mapped_object(t, col_num, row_num, &obj);
88         if (ret < 0)
89                 return ret;
90         col = t->columns + col_num;
91         obj_start = obj.data;
92         map_start = col->data_map.data;
93 //      PARA_INFO_LOG("obj: %p..%p\n", obj_start, obj_start + obj.size);
94 //      PARA_INFO_LOG("map: %p..%p\n", map_start, map_start + col->data_map.size);
95         if (obj_start < map_start || obj_start + obj.size > map_start + col->data_map.size) {
96                 PARA_CRIT_LOG("row %u, col %u: range violation, very bad\n", row_num, col_num);
97                 return -E_RANGE_VIOLATION;
98         }
99         PARA_DEBUG_LOG("col %u: ok\n", col_num);
100         return 1;
101 }
102
103 /*
104  * check all cells of the given table for boundary violations
105  */
106 static int check_index_ranges(struct osl_table *t)
107 {
108         int i, j, ret;
109
110         PARA_NOTICE_LOG("checking for range violations in index\n");
111         //PARA_DEBUG_LOG("%d rows. %d columns\n", t->num_rows, t->desc->num_columns);
112         t->num_invalid_rows = 0;
113         for (i = 0; i < t->num_rows; i++) {
114                 if (row_is_invalid(t, i)) {
115                         t->num_invalid_rows++;
116                         continue;
117                 }
118                 for (j = 0; j < t->desc->num_columns; j++) { /* FXIME */
119                         const struct osl_column_description *cd =
120                                 get_column_description(t->desc, j);
121                         if (cd->storage_type != OSL_MAPPED_STORAGE)
122                                 continue;
123                         ret = check_range(t, i, j);
124                         if (ret < 0) {
125                                 if (ret != -E_INVALID_OBJECT &&
126                                                 ret != -E_RANGE_VIOLATION)
127                                         goto err;
128                                 if (ret == -E_INVALID_OBJECT) {
129                                         PARA_CRIT_LOG("row %d, col %d maps to an "
130                                                 "invalid object\n", i, j);
131                                 }
132                                 ret = mark_row_invalid(t, i);
133                                 if (ret < 0)
134                                         goto err;
135                                 t->num_invalid_rows++;
136                                 break;
137                         }
138                 }
139
140         }
141         if (t->num_invalid_rows)
142                 PARA_NOTICE_LOG("ranges OK. %d invalid row(s) detected\n",
143                         t->num_invalid_rows);
144         else
145                 PARA_INFO_LOG("no invalid rows, no range violations, good\n");
146         return 1;
147 err:
148         return ret;
149 }
150
151 static int move_index_entry(struct osl_table *t, uint32_t dest, uint32_t src)
152 {
153         char *dest_ie, *src_ie;
154         int ret = get_row_index(t, dest, &dest_ie);
155
156         if (ret < 0)
157                 return ret;
158         ret = get_row_index(t, src, &src_ie);
159         if (ret < 0)
160                 return ret;
161         PARA_INFO_LOG("moving entry #%u to position %u\n", src, dest);
162         memcpy(dest_ie, src_ie, t->row_index_size);
163         return 1;
164 }
165
166 static int map_index(const struct osl_table_description *desc, struct osl_object *map)
167 {
168         char *filename = index_filename(desc);
169         int ret;
170
171         ret = mmap_full_file(filename, O_RDWR, map);
172         PARA_INFO_LOG("mapping index %s: ret: %d, size: %zu\n", filename, ret, map->size);
173         free(filename);
174         return ret;
175 }
176
177 static int prune_invalid_rows_from_index(struct osl_table *t)
178 {
179         uint32_t top = 0, bottom;
180         char *filename;
181         int ret;
182
183         if (!t->num_invalid_rows) {
184                 PARA_INFO_LOG("all rows are valid, good\n");
185                 return 1;
186         }
187         PARA_NOTICE_LOG("deleting %u invalid row(s) (%d bytes) from index\n",
188                 t->num_invalid_rows, t->row_index_size * t->num_invalid_rows);
189         bottom = t->num_rows - 1;
190         while (top < bottom) {
191                 if (!row_is_invalid(t, top)) {
192                         top++;
193                         continue;
194                 }
195                 while (bottom > top) {
196                         if (row_is_invalid(t, bottom)) {
197                                 bottom--;
198                                 continue;
199                         }
200                         /* move bottom index entry to top */
201                         move_index_entry(t, top, bottom);
202                         bottom--;
203                         top++;
204                         break;
205                 }
206         }
207         PARA_INFO_LOG("unmapping index\n");
208         para_munmap(t->index_map.data, t->index_map.size);
209         filename = index_filename(t->desc);
210         ret = para_truncate(filename, t->row_index_size
211                 * t->num_invalid_rows);
212         free(filename);
213         if (ret < 0)
214                 return ret;
215         ret = map_index(t->desc, &t->index_map);
216         if (ret < 0)
217                 return ret;
218         t->num_rows = table_num_rows(t);
219         return 1;
220 }
221
222 static int check_for_invalid_objects(struct osl_table *t, uint32_t **lost_bytes)
223 {
224         int i, j, ret;
225         const struct osl_column_description *cd;
226         uint32_t *loss = para_malloc(sizeof(uint32_t) * t->desc->num_columns);
227
228         PARA_NOTICE_LOG("looking for mapped objects not contained in index\n");
229         /* first count used bytes */
230         FOR_EACH_MAPPED_COLUMN(i, t, cd) {
231                 loss[i] = t->columns[i].data_map.size;
232                 for (j = 0; j < t->num_rows; j++) {
233                         struct osl_object obj;
234                         ret = get_mapped_object(t, i, j, &obj);
235                         if (ret >= 0) {
236                                 loss[i] -= obj.size + 1; /* add one for header byte */
237                                 continue;
238                         }
239                         if (ret != -E_INVALID_OBJECT)
240                                 goto err;
241                         PARA_CRIT_LOG("row %d, col %d points to an invalid "
242                                 "mapped object, bad\n", j, i);
243                 }
244         }
245         ret = 0;
246         FOR_EACH_MAPPED_COLUMN(i, t, cd) {
247                 if (loss[i]) {
248                         PARA_NOTICE_LOG("column %u contains %u lost bytes\n",
249                                 i, loss[i]);
250                         ret = 1;
251                 }
252         }
253         if (!ret)
254                 PARA_INFO_LOG("all mapped objects are valid, good\n");
255         *lost_bytes = loss;
256         return ret;
257 err:
258         free(loss);
259         return ret;
260 }
261
262 /* prune_invalid_rows() must be run on the table before calling this */
263 static int prune_mapped_column(struct osl_table *t, uint32_t col_num, int fd)
264 {
265         int i, ret;
266         uint32_t written = 0;
267         struct osl_column *col = t->columns + col_num;
268
269         PARA_INFO_LOG("pruning col %u\n", col_num);
270         for (i = 0; i < t->num_rows; i++) {
271                 struct osl_object obj;
272                 char *index_entry;
273
274                 PARA_DEBUG_LOG("checking row %u/%u\n", i, t->num_rows);
275                 ret = get_mapped_object(t, col_num, i, &obj);
276                 if (ret < 0)
277                         return ret;
278                 ret = para_write_all(fd, (char *)(obj.data) - 1, obj.size + 1);
279                 if (ret < 0)
280                         return ret;
281                 written += obj.size + 1;
282                 ret = get_row_index(t, i, &index_entry);
283                 if (ret < 0)
284                         return ret;
285                 update_cell_index(index_entry, col, written, obj.size);
286         }
287         return 1;
288 }
289
290 static int prune_objects(struct osl_table *t, uint32_t *lost_bytes)
291 {
292         int i, ret;
293         const struct osl_column_description *cd;
294         char **col_filenames = para_calloc(t->desc->num_columns * sizeof(char *));
295         char **new_col_filenames = para_calloc(t->desc->num_columns * sizeof(char *));
296         char *idx_filename = index_filename(t->desc);
297         char *old_idx_filename = make_message("%s.bak", idx_filename);
298         int fd;
299
300         PARA_NOTICE_LOG("removing unreferenced objects from data files\n");
301         /* first make a copy of the index */
302         ret = para_open(old_idx_filename, O_WRONLY | O_CREAT | O_EXCL, 0644);
303         if (ret < 0)
304                 goto out_free;
305         fd = ret;
306         ret = para_write_all(fd, t->index_map.data, t->index_map.size);
307         close(fd);
308         if (ret < 0)
309                 goto out_free;
310         FOR_EACH_MAPPED_COLUMN(i, t, cd) {
311                 if (!lost_bytes[i])
312                         continue;
313                 col_filenames[i] = column_filename(t, i);
314                 new_col_filenames[i] = make_message("%s.fsck", col_filenames[i]);
315                 ret = para_open(new_col_filenames[i], O_WRONLY | O_CREAT | O_EXCL, 0644);
316                 if (ret < 0)
317                         goto out_unlink_data;
318                 fd = ret;
319                 ret = prune_mapped_column(t, i, fd);
320                 close(fd);
321                 if (ret < 0)
322                         goto out_unlink_data;
323         }
324         ret = unmap_table(t, OSL_MARK_CLEAN);
325         if (ret < 0)
326                 goto out_unlink_data;
327         FOR_EACH_MAPPED_COLUMN(i, t, cd) {
328                 if (!lost_bytes[i])
329                         continue;
330                 ret = para_rename(new_col_filenames[i], col_filenames[i]);
331                 if (ret < 0) { /* we're kinda screwed here */
332                         PARA_CRIT_LOG("rename of col %i failed: %s\n", i,
333                                 strerror(errno));
334                         goto out_free;
335                 }
336         }
337         unlink(old_idx_filename);
338         ret = map_table(t, 0);
339         goto out_free;
340 out_unlink_data:
341         FOR_EACH_MAPPED_COLUMN(i, t, cd)
342                 unlink(new_col_filenames[i]);
343 out_free:
344         free(old_idx_filename);
345         free(idx_filename);
346         FOR_EACH_MAPPED_COLUMN(i, t, cd) {
347                 free(col_filenames[i]);
348                 free(new_col_filenames[i]);
349         }
350         free(col_filenames);
351         free(new_col_filenames);
352         return ret;
353 }
354
355 static struct osl_column_description hash_tree_table_cols[] = {
356         {
357                 .storage_type = OSL_NO_STORAGE,
358                 .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
359                 .name = "hash",
360                 .compare_function = uint32_compare,
361                 .data_size = HASH_SIZE
362         },
363 };
364
365 static const struct osl_table_description hash_tree_table_desc = {
366         .dir = "/", /* irrelevant */
367         .name = "hash_tree",
368         .num_columns = 1,
369         .flags = 0,
370         .column_descriptions = hash_tree_table_cols
371 };
372
373 /**
374  * The hash_tree table contains all hashes of the disk storage name column.
375  * of each row. It is used for checking if a disk storage file has a reference
376  * in the table.
377  */
378 static struct osl_table *hash_tree_table;
379 static HASH_TYPE *hashes;
380
381 static int check_disk_storage_column(struct osl_table *t, int row_num,
382                 int col_num, char *ds_name, unsigned *num_missing_objects)
383 {
384         int ret;
385         struct stat statbuf;
386         char *path = disk_storage_path(t, col_num, ds_name);
387         unsigned dsnc = t->disk_storage_name_column;
388         struct osl_object obj;
389
390         PARA_DEBUG_LOG("checking if %s is a regular file\n", path);
391         ret = stat(path, &statbuf);
392         if (ret < 0 && errno == ENOENT) {
393                 struct osl_row *row;
394                 (*num_missing_objects)++;
395                 PARA_ERROR_LOG("row %d: object %s is missing\n", row_num, path);
396                 PARA_NOTICE_LOG("trying to delete row %d\n", row_num);
397                 ret = osl_get_row(t, dsnc, &obj, &row);
398                 if (ret < 0) {
399                         PARA_CRIT_LOG("unable to get row %d\n", row_num);
400                         mark_row_invalid(t, row_num);
401                         PARA_CRIT_LOG("Please re-run fsck\n");
402                         goto out;
403                 }
404                 ret = osl_del_row(t, row);
405                 if (ret < 0)
406                         goto out;
407         }
408 out:
409         free(path);
410         if (ret < 0)
411                 return ret;
412         ret = -E_NOT_A_REGULAR_FILE;
413         if (!(S_IFREG & statbuf.st_mode))
414                 return ret;
415         return 1;
416 }
417
418 static int check_disk_storage_presence(struct osl_table *t)
419 {
420         int ret, i, j;
421         struct osl_object obj, hash_obj = {.size = HASH_SIZE};
422         char *ds_name;
423         const struct osl_column_description *cd;
424         unsigned dsnc = t->disk_storage_name_column, missing_objects = 0;
425
426         if (!t->num_rows)
427                 return 1;
428         hashes = para_malloc(t->num_rows * HASH_SIZE);
429         PARA_NOTICE_LOG("looking for missing disk storage objects\n");
430         for (i = 0; i < t->num_rows; i++) {
431                 if (row_is_invalid(t, i))
432                         continue;
433                 ret = get_mapped_object(t, dsnc, i, &obj);
434                 if (ret < 0)
435                         return ret;
436                 hash_object(&obj, hashes + i * HASH_SIZE);
437                 hash_obj.data = hashes + i * HASH_SIZE;
438                 osl_add_row(hash_tree_table, &hash_obj);
439                 ds_name = disk_storage_name_of_hash(t, hashes + i * HASH_SIZE);
440                 FOR_EACH_DISK_STORAGE_COLUMN(j, t, cd) {
441                         ret = check_disk_storage_column(t, i, j, ds_name,
442                                 &missing_objects);
443                         if (ret < 0)
444                                 goto err;
445                 }
446                 free(ds_name);
447         }
448         if (!missing_objects)
449                 PARA_INFO_LOG("all referenced disk storage objects exist, good\n");
450         else
451                 PARA_NOTICE_LOG("%d missing object(s)\n", missing_objects);
452         return missing_objects;
453 err:
454         free(ds_name);
455         return ret;
456 }
457
458 static int dummy_compare(const struct osl_object *obj1, const struct osl_object *obj2)
459 {
460         if (obj1 < obj2)
461                 return -1;
462         if (obj1 > obj2)
463                 return 1;
464         return 0;
465 }
466
467 static unsigned files_pruned;
468
469 int prune_disk_storage_file(const char *path, const void *private_data)
470 {
471         HASH_TYPE hash[HASH_SIZE];
472         unsigned flags = *(unsigned *)private_data;
473         struct osl_object obj = {.data = hash, .size = HASH_SIZE};
474         struct osl_row *row;
475         int ret = -1;
476         size_t len = strlen(path);
477
478
479         PARA_DEBUG_LOG("path: %s\n", path);
480         if (flags & OSL_LARGE_TABLE) {
481                 if (len < HASH_SIZE * 2 + 2)
482                         goto invalid;
483 //              PARA_NOTICE_LOG("p: %s\n", path + len - 2 * HASH_SIZE - 1);
484                 ret = asc_to_hash(path + len - 2 * HASH_SIZE - 1, 1, hash);
485                 if (ret < 0)
486                         goto invalid;
487                 ret = asc_to_hash(path + len - 2 * HASH_SIZE + 2, HASH_SIZE - 1,
488                         hash + 1);
489                 if (ret < 0)
490                         goto invalid;
491 //              PARA_INFO_LOG("high: %x, low: %x, hash: %x\n", high, low, hash);
492         } else {
493                 if (len < 2 * HASH_SIZE + 1)
494                         goto invalid;
495                 ret = asc_to_hash(path + len - 2 * HASH_SIZE, 2 * HASH_SIZE, hash);
496                 if (ret < 0)
497                         goto invalid;
498 //              PARA_INFO_LOG("hash: %x\n", hash);
499         }
500 #if 0
501 {
502         char asc[2 * HASH_SIZE + 1];
503         hash_to_asc(hash, asc);
504         PARA_NOTICE_LOG("before: %s\nafter: %s\n", path, asc);
505 }
506 #endif
507         ret = osl_get_row(hash_tree_table, 0, &obj, &row);
508         if (ret >= 0)
509                 return 1;
510         PARA_NOTICE_LOG("unreferenced file in hash dir: %s\n", path);
511         goto remove;
512 invalid:
513         PARA_ERROR_LOG("could not read hash value of %s\n", path);
514 remove:
515         PARA_NOTICE_LOG("removing %s\n", path);
516         unlink(path);
517         files_pruned++;
518         return 1;
519 }
520
521 static int prune_disk_storage_files(struct osl_table *t)
522 {
523         int i, ret = 1;
524         const struct osl_column_description *cd;
525
526         PARA_NOTICE_LOG("looking for unreferenced disk storage files\n");
527         FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) {
528                 char *dirname = column_filename(t, i);
529                 ret = for_each_file_in_dir(dirname, prune_disk_storage_file, &t->desc->flags);
530                 free(dirname);
531         }
532         if (files_pruned)
533                 PARA_NOTICE_LOG("%u disk storage files deleted\n",
534                         files_pruned);
535         else
536                 PARA_INFO_LOG("all files are are referenced, good\n");
537         return ret;
538 }
539
540 static int check_disk_storage_columns(struct osl_table *t)
541 {
542         int ret, i;
543         const struct osl_column_description *cd;
544
545         if (!t->num_disk_storage_columns) {
546                 PARA_NOTICE_LOG("no disk storage columns in table '%s', "
547                         "skipping checks\n", t->desc->name);
548                 return 1;
549         }
550         FOR_EACH_COLUMN(i, t->desc, cd)
551                 t->desc->column_descriptions[i].compare_function = dummy_compare;
552         ret = init_rbtrees(t);
553         if (ret < 0)
554                 return ret;
555         PARA_NOTICE_LOG("creating rbtree for disk storage hash values\n");
556         ret = osl_open_table(&hash_tree_table_desc, &hash_tree_table);
557         if (ret < 0)
558                 goto out;
559         ret = check_disk_storage_presence(t);
560         if (ret < 0)
561                 goto out_close_hash_tree;
562         ret = prune_disk_storage_files(t);
563 out_close_hash_tree:
564         osl_close_table(hash_tree_table, 0);
565         free(hashes);
566 out:
567         clear_rbtrees(t); /* TODO why are we doing that here? Seems odd */
568         return ret;
569 }
570
571 #define FSCK 1
572 #define FORCE 1
573 #define DUMP 0
574
575 static void set_dummy_contents(struct osl_table_description *desc)
576 {
577         int i;
578         struct osl_column_description *cd;
579
580         for (i = 0; i < desc->num_columns; i++) {
581                 cd = get_column_description(desc, i);
582                 cd->compare_function = dummy_compare;
583         }
584 }
585
586 static int fsck_init(struct osl_table_description *desc, struct osl_table **t)
587 {
588         struct osl_object map;
589         int ret = map_index(desc, &map);
590
591         if (ret < 0)
592                 goto out;
593         ret = read_table_desc(&map, desc);
594         if (ret < 0) {
595                 para_munmap(map.data, map.size);
596                 goto out;
597         }
598         set_dummy_contents(desc);
599         ret = init_table_structure(desc, t);
600         if (ret < 0) {
601                 para_munmap(map.data, map.size);
602                 goto out;
603         }
604         PARA_INFO_LOG("unmapping index\n");
605         para_munmap(map.data, map.size);
606         if (FORCE)
607                 ret = map_table(*t, (MAP_TBL_FL_IGNORE_DIRTY));
608         else
609                 ret = map_table(*t, 0);
610         if (ret >= 0)
611                 (*t)->num_rows = table_num_rows(*t);
612 out:
613         return ret;
614 }
615
616 static void fsck_cleanup(struct osl_table *t)
617 {
618         int i;
619         if (t->desc->column_descriptions) {
620                 struct osl_column_description *cd;
621                 for (i = 0; i < t->desc->num_columns; i++) {
622                         cd = get_column_description(t->desc, i);
623                         free((char*)cd->name);
624                 }
625                 free(t->desc->column_descriptions);
626         }
627         if (t) {
628                 free(t->columns);
629                 free(t);
630         }
631
632 }
633
634 #define ST_CASE(st) case st: return #st
635
636 const char *get_asc_storage_type(enum osl_storage_type st)
637 {
638         switch (st) {
639                 ST_CASE(OSL_MAPPED_STORAGE);
640                 ST_CASE(OSL_DISK_STORAGE);
641                 ST_CASE(OSL_NO_STORAGE);
642         }
643         return NULL;
644 }
645
646 #define APPEND_ASC_SF(sf, flag, str) do { if (sf & flag) { \
647         if (str) str = para_strcat(str, " | " # flag); \
648         else str = para_strdup(#flag); }} while (0)
649
650
651 char *get_asc_storage_flags(enum osl_storage_type sf)
652 {
653         char *asc_sf = NULL;
654
655         APPEND_ASC_SF(sf, OSL_RBTREE, asc_sf);
656         APPEND_ASC_SF(sf, OSL_FIXED_SIZE, asc_sf);
657         APPEND_ASC_SF(sf, OSL_UNIQUE, asc_sf);
658         return asc_sf;
659 }
660
661 static int dump_table_desc(struct osl_table *t, int fd)
662 {
663         const struct osl_table_description *desc = t->desc;
664         int ret, i;
665         struct osl_column_description *cd;
666         char *msg = make_message("static struct osl_column_description cols[] = {\n");
667         ret = para_write_all(fd, msg, strlen(msg));
668         if (ret < 0)
669                 return ret;
670         free(msg);
671         FOR_EACH_COLUMN(i, desc, cd) {
672                 const char *asc_st;
673                 msg = make_message("\t[%d] = {\n", i);
674                 ret = para_write_all(fd, msg, strlen(msg));
675                 if (ret < 0)
676                         return ret;
677                 free(msg);
678                 asc_st = get_asc_storage_type(cd->storage_type);
679                 msg = make_message("\t\t.storage_type = %s,\n", asc_st);
680                 ret = para_write_all(fd, msg, strlen(msg));
681                 if (ret < 0)
682                         return ret;
683                 free(msg);
684                 if (cd->storage_flags) {
685                         char *asc_sf = get_asc_storage_flags(cd->storage_flags);
686                         msg = make_message("\t\t,storage_flags = %s,\n", asc_sf);
687                         free(asc_sf);
688                         ret = para_write_all(fd, msg, strlen(msg));
689                         if (ret < 0)
690                                 return ret;
691                         free(msg);
692                 }
693                 if (cd->storage_flags & OSL_FIXED_SIZE) {
694                         msg = make_message("\t\t.data_size = %u,\n", cd->data_size);
695                         ret = para_write_all(fd, msg, strlen(msg));
696                         if (ret < 0)
697                                 return ret;
698                         free(msg);
699                 }
700                 msg = make_message("\t\t.name = \"%s\",\n", cd->name);
701                 ret = para_write_all(fd, msg, strlen(msg));
702                 if (ret < 0)
703                         return ret;
704                 free(msg);
705                 if (cd->storage_flags & OSL_RBTREE) {
706                         msg = make_message("\t\t.compare_function = compare_func,\n");
707                         ret = para_write_all(fd, msg, strlen(msg));
708                         if (ret < 0)
709                                 return ret;
710                         free(msg);
711                 }
712                 msg = make_message("\t},\n");
713                 ret = para_write_all(fd, msg, strlen(msg));
714                 if (ret < 0)
715                         return ret;
716                 free(msg);
717         }
718         msg = make_message("};\n");
719         ret = para_write_all(fd, msg, strlen(msg));
720         if (ret < 0)
721                 return ret;
722         free(msg);
723         return 1;
724 }
725
726 static int dump_row(struct osl_table *t, unsigned row_num, const char *row_dir)
727 {
728         int ret, i;
729         const struct osl_column_description *cd;
730         unsigned dsnc;
731         struct osl_object obj;
732         char *ds_name;
733         HASH_TYPE hash[HASH_SIZE];
734         char *filename;
735
736         FOR_EACH_MAPPED_COLUMN(i, t, cd) {
737                 ret = get_mapped_object(t, i, row_num, &obj);
738                 if (ret < 0)
739                         return ret;
740                 filename = make_message("%s/col_%03u", row_dir, i);
741                 ret = para_write_file(filename, obj.data, obj.size);
742                 free(filename);
743                 if (ret < 0)
744                         return ret;
745         }
746         if (!t->num_disk_storage_columns)
747                 return 1;
748         dsnc = t->disk_storage_name_column;
749         ret = get_mapped_object(t, dsnc, i, &obj);
750         if (ret < 0)
751                 return ret;
752         hash_object(&obj, hash);
753         ds_name = disk_storage_name_of_hash(t, hash);
754         FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) {
755                 filename = disk_storage_path(t, i, ds_name);
756                 ret = mmap_full_file(filename, O_RDONLY, &obj);
757                 free(filename);
758                 if (ret < 0)
759                         goto out;
760                 filename = make_message("%s/col_%03u", row_dir, i);
761                 ret = para_write_file(filename, obj.data, obj.size);
762                 free(filename);
763                 if (ret < 0)
764                         goto out;
765         }
766         ret = 1;
767 out:
768         free(ds_name);
769         return ret;
770 }
771
772 static int dump_rows(char *dump_dir, struct osl_table *t)
773 {
774         unsigned i;
775         char *current_dir = NULL;
776         int ret = 0;
777
778         for (i = 0; i < t->num_rows; i++) {
779                 char *row_dir;
780                 if (row_is_invalid(t, i))
781                         continue;
782                 if (!(i % 1000)) {
783                         free(current_dir);
784                         current_dir = make_message("%s/rows_%u-%u", dump_dir, i, i + 999);
785                         PARA_NOTICE_LOG("dumping rows %u - %u\n", i, i + 999);
786                         ret = para_mkdir(current_dir, 0777);
787                         if (ret < 0)
788                                 goto out;
789                 }
790                 row_dir = make_message("%s/row_%03u", current_dir, i);
791                 ret = para_mkdir(row_dir, 0777);
792                 if (ret < 0) {
793                         free(row_dir);
794                         goto out;
795                 }
796                 ret = dump_row(t, i, row_dir);
797                 free(row_dir);
798                 if (ret < 0)
799                         goto out;
800         }
801 out:
802         free(current_dir);
803         return ret;
804 }
805
806 static int dump_table(char *dump_dir, struct osl_table_description *desc)
807 {
808         struct osl_table *t = NULL;
809         int fd, ret = fsck_init(desc, &t);
810         char *desc_file;
811
812         if (ret < 0)
813                 goto out;
814         ret = para_mkdir(dump_dir, 0777);
815         if (ret < 0)
816                 goto out;
817         desc_file = make_message("%s/table_description.c", dump_dir);
818         ret = para_open(desc_file, O_WRONLY | O_CREAT | O_EXCL, 0644);
819         free(desc_file);
820         if (ret < 0)
821                 goto out;
822         fd = ret;
823         ret = dump_table_desc(t, fd);
824         close(fd);
825         if (ret < 0)
826                 goto out;
827         ret = dump_rows(dump_dir, t);
828 out:
829         fsck_cleanup(t);
830         return ret;
831 }
832
833 static int fsck(struct osl_table_description *desc)
834 {
835         int ret;
836         struct osl_table *t = NULL;
837         uint32_t *lost_bytes = NULL;
838
839         ret = fsck_init(desc, &t);
840         if (ret < 0)
841                 goto out;
842         ret = check_index_ranges(t);
843         if (ret < 0)
844                 goto out_unmap;
845         ret = check_disk_storage_columns(t);
846         if (ret < 0)
847                 goto out_unmap;
848         ret = prune_invalid_rows_from_index(t);
849         if (ret < 0)
850                 goto out_unmap;
851         ret = check_for_invalid_objects(t, &lost_bytes);
852         if (ret < 0)
853                 goto out_unmap;
854         if (ret > 0) { /* at least one mapped data file needs pruning */
855                 ret = prune_objects(t, lost_bytes);
856                 if (ret < 0)
857                         goto out_unmap;
858         }
859         free(lost_bytes);
860         PARA_INFO_LOG("success\n");
861 out_unmap:
862         unmap_table(t, OSL_MARK_CLEAN);
863 out:
864         fsck_cleanup(t);
865         return ret;
866 }
867 int main(__a_unused int argc, __a_unused char **argv)
868 {
869         int ret;
870         struct osl_table_description desc = {.column_descriptions = NULL};
871
872         ret = -E_FSCK_SYNTAX;
873         if (argc < 3)
874                 goto out;
875         desc.dir = argv[1];
876         desc.name = argv[2];
877         if (FSCK) {
878                 ret = fsck(&desc);
879                 if (ret < 0)
880                         goto out;
881         }
882         if (DUMP)
883                 ret = dump_table(OSL_DUMP_DIR, &desc);
884 out:
885         if (ret < 0)
886                 PARA_ERROR_LOG("error %d\n", ret);
887         return ret < 0? EXIT_FAILURE: EXIT_SUCCESS;
888 }