1 /***************************************************************************
2 * *
3 * This program is free software; you can redistribute it and/or modify *
4 * it under the terms of the GNU General Public License as published by *
5 * the Free Software Foundation; either version 2 of the License, or *
6 * (at your option) any later version. *
7 * *
8 ***************************************************************************
9 * fs/directory.cc -- part of ezbounce
10 * (c) 2006-2008 Murat Deligonul
11 */
12
13 #include "autoconf.h"
14
15 #include <vector>
16 #include <cstring>
17 #include <cstdlib>
18 #include <cstdio>
19 #include <ctime>
20 #include <cerrno>
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include "util/strings.h"
26 #include "util/generic.h"
27 #include "fs/directory.h"
28 #include "fs/error.h"
29 #include "io/utility.h"
30 #include "debug.h"
31
32 using namespace util::strings;
33
34 namespace fs {
35
36 /**
37 * Database file version. History:
38 *
39 * 201 - (current) added permissions and file group data.
40 * changed to three digit version numbers to handle minor changes.
41 * 2 - old format, used until 1.99.11
42 */
43 const unsigned short directory::DB_VERSION = 201;
44
45 const char directory::DB_FILE_NAME[] = "ezbfiles.db";
46
47 /**
48 * Construct directory object from a parent directory
49 * and entry structure.
50 * Directory names and paths are stored without a trailing '/'
51 * unless they refer to a root directory.
52 */
directory(directory * p,file_entry * f)53 directory::directory(directory * p, file_entry * f)
54 : _name(f->name()),
55 _entry(f),
56 fd(-1),
57 batch_mode(false),
58 last_mod(0), last_size(0),
59 file_cache(CACHE_BUCKETS)
60 {
61 assert(f->dir() == p);
62 int plen = strlen(p->abs_path());
63
64 a_path = my_strdup3(p->abs_path(), "/", _name);
65
66 r_path = a_path + (plen - strlen(p->rel_path()));
67 if (p->parent() == p) {
68 ++r_path;
69 }
70
71 dbfile = my_strdup3(a_path, "/", DB_FILE_NAME);
72
73 parent()->inc_count();
74 parent()->add_child(this);
75
76 _entry->inc_count();
77
78 assert(r_path[0] == '/');
79 assert(r_path[strlen(r_path)-1] != '/');
80 assert(_entry->dir() == parent());
81 }
82
83
84 /**
85 * Construct directory object for virtual root directory.
86 * Takes in parameter of absolute path this VFS resides in.
87 */
directory(const char * p,const char * owner,const char * group)88 directory::directory(const char * p, const char * owner, const char * group)
89 : fd(-1),
90 batch_mode(false),
91 last_mod(0), last_size(0),
92 file_cache(CACHE_BUCKETS)
93 {
94 int plen = strlen(p);
95 a_path = my_strdup(p);
96
97 /* strip any trailing '/' characters, unless we are actually
98 setting up in '/' -- ( why the hell would someone do that ???? )*/
99 while (a_path[plen-1] == '/' && plen > 1) {
100 a_path[--plen] = 0;
101 }
102
103 dbfile = my_strdup3(a_path, "/", DB_FILE_NAME);
104 _entry = new file_entry(this, "", owner, group);
105 _name = _entry->name();
106 r_path = "/";
107 _entry->set_mode(0755);
108 _entry->set_flags(FE_DIRECTORY);
109 _entry->inc_count();
110 cache_insert(_entry);
111 }
112
~directory()113 directory::~directory()
114 {
115 DEBUG("%s: called for '%s'\n", __PRETTY_FUNCTION__, rel_path());
116
117 #ifdef __DEBUG__
118 // NOTE: intense debug output here
119 std::string obj;
120 util::print_container(objects(), obj);
121 DEBUG("%s: remaining dirs: %s\n", __PRETTY_FUNCTION__, obj.c_str());
122 util::print_container(file_entry::objects(), obj);
123 DEBUG("%s: remaining file entries: %s\n", __PRETTY_FUNCTION__, obj.c_str());
124 #endif
125
126 cache_prune();
127 _entry->dec_count();
128 assert(_entry->usage_count() == 0);
129 if (parent() != this) {
130 parent()->dec_count();
131 parent()->remove_child(this);
132 }
133 else {
134 cache_remove(_entry->name());
135 delete _entry;
136 }
137
138 assert(cache_num_open() == 0);
139
140 delete[] a_path;
141 delete[] dbfile;
142 assert(fd == -1);
143 assert(!batch_mode);
144 }
145
add_child(directory * d)146 void directory::add_child(directory * d)
147 {
148 using util::contains;
149 assert(!contains(children.begin(), children.end(), d));
150 children.push_back(d);
151 }
152
remove_child(directory * d)153 void directory::remove_child(directory * d)
154 {
155 using util::contains;
156 assert(contains(children.begin(), children.end(), d));
157 children.erase(std::remove(children.begin(), children.end(),
158 d),
159 children.end());
160 }
161
162 /**
163 * Begin a series of operations while database file is locked
164 */
batch_start()165 int directory::batch_start()
166 {
167 DEBUG("directory::batch_start(): [%p]\n", this);
168 if (batch_mode || fd > -1) {
169 DEBUG("%s: oops, you screwed up\n", __PRETTY_FUNCTION__);
170 abort();
171 }
172
173 int r = opendb();
174 if (r < 0) {
175 DEBUG("%s: opendb() failed: %d\n", __PRETTY_FUNCTION__, r);
176 return r;
177 }
178
179 batch_mode = true;
180 return 0;
181 }
182
183 /**
184 * End the series of operations.
185 */
batch_end()186 int directory::batch_end()
187 {
188 DEBUG("directory::batch_end(): [%p]\n", this);
189 if (!batch_mode || fd < 0) {
190 DEBUG("%s: oops, you screwed up\n", __PRETTY_FUNCTION__);
191 abort();
192 }
193
194 batch_mode = false;
195 return closedb();
196 }
197
198 /**
199 * Get the database entry for a file.
200 * Ensures that the file exists both in the database and on disk. All other
201 * checks and operations (e.g. checking permissions, opening) are
202 * done at a higher level.
203 *
204 * Returns a new file_entry object upon success. On error, returns null
205 * and err is set to the appropriate error code.
206 */
disk_lookup(const char * file,int * err)207 file_entry * directory::disk_lookup(const char * file, int * err)
208 {
209 int r = opendb();
210 if (r < 0) {
211 *err = r;
212 return 0;
213 }
214
215 /** ensure sanity of filename **/
216 if (!is_legal_name(file)) {
217 *err = ERR_ILLEGAL_NAME;
218 closedb();
219 return 0;
220 }
221
222 raw_entry e;
223 memset(&e, 0, sizeof(e));
224 r = locate_entry(file, &e, MATCH_LITERAL);
225 if (r < 0) {
226 *err = r;
227 delete[] e.string_data;
228 closedb();
229 return 0;
230 }
231
232 file_entry * fe = new file_entry(this);
233 fill_file_entry(&e, fe);
234 delete[] e.string_data;
235
236 // TODO: check that we can read on disk entry
237 r = closedb();
238 if (r < 0) {
239 *err = r;
240 delete fe;
241 return NULL;
242 }
243 return fe;
244 }
245
246 /**
247 * Write entry to database file.
248 * If 'create' is true, the entry is written even if it can't be found.
249 */
write(const file_entry * fe,bool create)250 int directory::write(const file_entry * fe, bool create)
251 {
252 int r = opendb();
253 if (r < 0) {
254 return r;
255 }
256
257 /* ensure legality of name */
258 if (!is_legal_name(fe->name())) {
259 closedb();
260 return ERR_ILLEGAL_NAME;
261 }
262
263 raw_entry e;
264 memset(&e, 0, sizeof(e));
265
266 r = locate_entry(fe->name(), &e, MATCH_LITERAL);
267 delete[] e.string_data;
268 e.string_data = NULL;
269
270 if ((r == ERR_DOESNT_EXIST && !create)
271 || (r < 0 && r != ERR_DOESNT_EXIST)) {
272 closedb();
273 return r;
274 }
275
276 // save these
277 off_t old_offset = e.offset;
278 unsigned old_padding = e.header.padding_length;
279 unsigned old_dyn = calc_dynlen(&e.header);
280 unsigned old_total = calc_entry_len(&e.header);
281
282 // assemble new structure
283 size_t dyn_len = fill_header(fe, &e.header);
284 e.string_data = new char[dyn_len];
285 fill_dynamic(fe, e.string_data);
286
287 // special case where entry is last one in the file.
288 if (old_offset > 0 &&
289 (old_offset + old_total) == (unsigned) last_size) {
290 DEBUG("directory::write() [%p]: '%s': detected write to last entry in database\n",
291 this, fe->name());
292 off_t new_size = old_offset + dyn_len + sizeof(e.header);
293 if (new_size != last_size) {
294 ftruncate(fd, new_size);
295 last_size = new_size;
296 }
297 e.header.padding_length = 0;
298 }
299 else if (dyn_len > (old_dyn + old_padding)) {
300 // writing a brand new entry or we need more space
301 lseek(fd, sizeof(dbfile_header), SEEK_SET);
302 r = find_free_entry(dyn_len, &e);
303 if (r < 0) {
304 // failed: shouldn't happen unless there is
305 // corruption or other serious problem.
306 delete[] e.string_data;
307 closedb();
308 return r;
309 }
310 if (old_offset != 0) {
311 free_entry(old_offset);
312 }
313 e.header.padding_length -= dyn_len;
314 }
315 else {
316 // normal case: writing to sufficiently-spaced entry in the middle of the database
317 e.header.padding_length = (old_padding + old_dyn) - dyn_len;
318 }
319
320 r = write_entry(&e);
321 delete[] e.string_data;
322 closedb();
323 return r;
324 }
325
326 /**
327 * Removes a file entry from the database, optionally removing it
328 * from disk as well.
329 */
unlink(const char * name,bool remove)330 int directory::unlink(const char * name, bool remove)
331 {
332 int r = opendb();
333 if (r < 0) {
334 DEBUG("directory::unlink() [%p]: unlink of '%s' failed!\n", this, name);
335 return r;
336 }
337
338 /** ensure legality of name **/
339 if (!is_legal_name(name)) {
340 closedb();
341 return ERR_ILLEGAL_NAME;
342 }
343
344 raw_entry e;
345 memset(&e, 0, sizeof(e));
346
347 r = locate_entry(name, &e, MATCH_LITERAL);
348 delete[] e.string_data;
349
350 if (r < 0) {
351 DEBUG("directory::unlink() [%p]: unable to find '%s'\n", this, name);
352 closedb();
353 return r;
354 }
355 free_entry(e.offset);
356 closedb();
357
358 DEBUG("directory::unlink() [%p]: erased entry '%s' at offset %ld\n", this, name, e.offset);
359
360 if (remove) {
361 char * file = NULL;
362 my_asprintf(&file, "%s/%s", abs_path(), name);
363 ::unlink(file);
364 delete[] file;
365 }
366 return 0;
367 }
368
369 /**
370 * List files in the database that match pattern. Return results in
371 * an STL vector.
372 */
ls(const char * pattern,std::vector<entry_data> & results)373 int directory::ls(const char * pattern, std::vector<entry_data>& results)
374 {
375 int r = opendb();
376 if (r < 0) {
377 DEBUG("directory::unlink() [%p]: ls of '%s' failed!\n", this, pattern);
378 return r;
379 }
380
381 /**
382 * Ensure legality of pattern (really needed?)
383 */
384 if (!is_legal_name(pattern)) {
385 closedb();
386 return ERR_ILLEGAL_NAME;
387 }
388
389 results.reserve(25);
390
391 while (true) {
392 raw_entry e;
393 memset(&e, 0, sizeof(e));
394
395 r = locate_entry(pattern, &e, MATCH_WILDCARD);
396 if (r < 0) {
397 closedb();
398 delete[] e.string_data;
399 /** check if error was from corruption, otherwise return 0 **/
400 return (r == ERR_DOESNT_EXIST) ? 0 : r;
401 }
402 file_entry fe(this);
403 fill_file_entry(&e, &fe);
404 delete[] e.string_data;
405
406 fe.update_size();
407
408 // XXX: kind of wasteful; another copy of the data will be made
409 // for storage in the vector (and potentially more for vector
410 // resizes), but this is the simplest approach for now
411 const entry_data * data = fe.raw_data();
412 results.push_back(*data);
413 }
414 /* not reached! */
415 return 0;
416 }
417
418 /**
419 * Delete database file from disk.
420 */
destroy()421 int directory::destroy()
422 {
423 if (usage_count() > 1 || fd > -1) {
424 return ERR_BUSY;
425 }
426
427 if (::unlink(dbfile) < 0) {
428 DEBUG("directory::destroy() [%p]: Unable to delete '%s': %s\n", this, DB_FILE_NAME, strerror(errno));
429 return ERR_DB_DESTROY;
430 }
431 DEBUG("directory::destroy() [%p]: database file '%s' deleted\n", this, dbfile);
432 // FIXME: what else to do here?
433 return 0;
434 }
435
436 /**
437 * Checks if database file exists
438 */
exists() const439 bool directory::exists() const
440 {
441 int j = access(dbfile, R_OK | W_OK);
442 return (j == 0);
443 }
444
445 /**
446 * Confirm legality of database file.
447 */
validate()448 int directory::validate()
449 {
450 int r = opendb();
451 if (r < 0) {
452 return r;
453 }
454 closedb();
455 return 0;
456 }
457
458 /**
459 * Tests whether any files are listed in the database.
460 * Return:
461 * < 0 : access error
462 * 0 : false
463 * 1 : true
464 */
is_empty()465 int directory::is_empty()
466 {
467 int r = opendb();
468 if (r < 0) {
469 return r;
470 }
471
472 struct raw_entry e;
473 memset(&e, 0, sizeof(e));
474 r = locate_entry("*", &e, MATCH_WILDCARD);
475 delete[] e.string_data;
476 closedb();
477
478 if (r == ERR_DOESNT_EXIST) {
479 return 1;
480 }
481 else if (r < 0) {
482 return r;
483 }
484 /* files were found .. */
485 return 0;
486 }
487
488 /**
489 * Translate directory error codes.
490 */
strerror(int err)491 /* static */ const char * directory::strerror(int err)
492 {
493 switch (err) {
494 case ERR_ILLEGAL_NAME:
495 return "Illegal filename";
496 case ERR_DB_CORRUPTION:
497 return "Corrupted database file detected";
498 case ERR_BUSY:
499 return "File or directory is in use";
500 case ERR_FAILURE:
501 return "Operation failed";
502 case ERR_DB_VERSION:
503 return "Incompatible database version";
504 case ERR_DB_OPEN:
505 return "Unable to open database";
506 case ERR_DB_EXISTS:
507 return "A database file already exists";
508 case ERR_DB_DESTROY:
509 return "Unable to delete database file";
510 case ERR_BAD_WRITE:
511 return "Tried to write invalid entry!";
512 }
513 return "Unknown error";
514 }
515
516 /**
517 * Creates a new database file.
518 */
create()519 int directory::create()
520 {
521 if (exists()) {
522 DEBUG("directory::create(): database file '%s' already exists!\n", dbfile);
523 return ERR_DB_EXISTS;
524 }
525
526 fd = open(dbfile, O_CREAT | O_EXCL | O_WRONLY, 0600);
527 if (fd < 0) {
528 DEBUG("directory::create(): '%s': open() syscall failed!\n", dbfile);
529 return ERR_IO_WRITE;
530 }
531
532 /** Write header and exit **/
533 write_header(time(NULL));
534 close(fd);
535 fd = -1;
536 DEBUG("directory::create(): '%s': new database file created\n", dbfile);
537 return 0;
538 }
539 // implementation follows.
540
541 /**
542 * Open database file in the directory & lock it.
543 * Assumes database file has been created and updated to the current version!
544 */
opendb()545 int directory::opendb()
546 {
547 if (batch_mode) {
548 lseek(fd, sizeof(dbfile_header), SEEK_SET);
549 return fd;
550 }
551
552 fd = open(dbfile, O_RDWR);
553 if (fd < 0) {
554 DEBUG("directory::opendb(): unable to open database\n");
555 fd = -1;
556 return ERR_DB_OPEN;
557 }
558
559 /** lock it. **/
560 lockf(fd, F_LOCK, 0);
561
562 dbfile_header h;
563 memset(&h, 0, sizeof(h));
564 if (read_header(&h) < 0) {
565 DEBUG("directory::opendb(): database is corrupted.\n");
566 close(fd);
567 fd = -1;
568 return ERR_DB_CORRUPTION;
569 }
570
571 if (h.version > DB_VERSION) {
572 DEBUG("directory::opendb(); incorrect version\n");
573 close(fd);
574 fd = -1;
575 return ERR_DB_VERSION;
576 }
577
578 struct stat st;
579 fstat(fd, &st);
580
581 last_mod = st.st_mtime;
582 last_size = st.st_size;
583
584 /** success **/
585 return fd;
586 }
587
588 /**
589 * Closes and unlocks database file. Writes the top header, but nothing else.
590 */
closedb()591 int directory::closedb()
592 {
593 if (batch_mode) {
594 return 0;
595 }
596
597 write_header(time(NULL));
598 fsync(fd);
599 lockf(fd, F_ULOCK, 0);
600 close(fd);
601 fd = -1;
602 return 0;
603 }
604
605 /**
606 * Writes the header at the top of the database file. Repositions file pointer
607 * to the beginning of the file first.
608 */
write_header(time_t timestamp)609 int directory::write_header(time_t timestamp)
610 {
611 dbfile_header h;
612 memset(&h, 0, sizeof(h));
613 h.version = DB_VERSION;
614 h.timestamp = timestamp;
615 strcpy(h.hstring, "ezb fdbh");
616 lseek(fd, SEEK_SET, 0);
617 if (::write(fd, &h, sizeof(h)) == sizeof(h)) {
618 return 0;
619 }
620 return -1;
621 }
622
623
624 /**
625 * Return the number of files in the cache that are currently in use
626 */
cache_num_open() const627 int directory::cache_num_open() const
628 {
629 int count = 0;
630 hash_table_t::const_iterator i = file_cache.begin(),
631 e = file_cache.end();
632 for (; i != e ; ++i) {
633 file_entry * e = (*i).second;
634 if (e->usage_count() > 0) {
635 ++count;
636 }
637 }
638 return count;
639 }
640
641 /**
642 * Remove unused entries from the open file cache.
643 */
cache_prune()644 int directory::cache_prune()
645 {
646 DEBUG("directory::cache_prune(): [%p] entering for %s\n", this, rel_path());
647 hash_table_t::iterator i = file_cache.begin(),
648 e = file_cache.end();
649 while (i != e) {
650 file_entry * e = (*i).second;
651 if (e->usage_count() == 0) {
652 i = file_cache.erase(i);
653 delete e;
654 }
655 else {
656 ++i;
657 }
658 }
659 return file_cache.size();
660 }
661
662 /**
663 * Read header from top of the database file. Assumes file pointer
664 * is at the beginning of the file.
665 */
read_header(dbfile_header * h)666 int directory::read_header(dbfile_header * h)
667 {
668 assert(h != NULL);
669 assert(fd != -1);
670
671 int r = read(fd, h, sizeof(dbfile_header));
672
673 if (r < (signed) sizeof(dbfile_header)) {
674 return -1;
675 }
676
677 if (strncmp(h->hstring, "ezb fdbh", 8)) {
678 return -1;
679 }
680
681 return 0;
682 }
683
684 /**
685 * Look for a entry on disk matching "pattern".
686 * Return ERR_DOESNT_EXIST if not found.
687 */
locate_entry(const char * pattern,struct raw_entry * r,int match_options)688 int directory::locate_entry(const char * pattern, struct raw_entry * r, int match_options)
689 {
690 assert(fd > -1);
691 int j = 0;
692 int (*matcher)(const char *, const char *);
693
694 matcher = (match_options & MATCH_LITERAL) ? &strcmp : &wild_match;
695 off_t offset = lseek(fd, 0, SEEK_CUR);
696
697 while (true) {
698 j = read(fd, &r->header, sizeof(r->header));
699 if (j == 0) {
700 // EOF
701 r->offset = 0;
702 memset(&r->header, 0, sizeof(r->header));
703 return ERR_DOESNT_EXIST;
704 }
705 else if (j < 0) {
706 return ERR_IO_READ;
707 }
708 else if (j < (signed) sizeof(r->header)) {
709 // premature eof
710 return ERR_DB_CORRUPTION;
711 }
712 else if (validate_header(&r->header) != 0) {
713 // bad header -- corrupted file?
714 return ERR_DB_CORRUPTION;
715 }
716
717 // empty block?
718 if (r->header.flags & FE_EMPTY_ENTRY) {
719 offset = lseek(fd, r->header.padding_length, SEEK_CUR);
720 continue;
721 }
722
723 // header ok, now read dynamic data
724 unsigned short dyn_length = calc_dynlen(&r->header);
725 r->offset = offset;
726 r->string_data = new char[dyn_length];
727 j = read(fd, r->string_data, dyn_length);
728 if (j < (signed) dyn_length) {
729 // premature EOF or read error.
730 delete[] r->string_data;
731 r->string_data = NULL;
732 if (j < 0) {
733 return ERR_IO_READ;
734 }
735 return ERR_DB_CORRUPTION;
736 }
737 if (validate_dynamic(&r->header, r->string_data) != 0) {
738 delete[] r->string_data;
739 r->string_data = NULL;
740 return ERR_DB_CORRUPTION;
741 }
742
743 // goto end of record
744 offset = lseek(fd, r->header.padding_length, SEEK_CUR);
745
746 // make sure this is the file we're looking for
747 const char * filename = &r->string_data[dyn_length - r->header.name_length];
748 if (matcher(pattern, filename) != 0) {
749 delete[] r->string_data;
750 r->string_data = NULL;
751 continue;
752 }
753
754 // found a match.
755 break;
756 }
757 return 0;
758 }
759
760
761 /**
762 * Find a free block in the database file of at least size 'size'.
763 * Offset of found block is stored in r->offset.
764 * Size of found block is stored in r->header.padding_length.
765 * No other fields of 'r' are modified.
766 * File pointer is positioned at the beginning of the free entry.
767 * Consolidates sequences of empty blocks if possible.
768 * If nothing is found, result offset is set to the end of the file.
769 * Return 0 on success.
770 *
771 * FIXME: currently does not utilize padding as free space.
772 * (i.e., only seeks entries marked as FE_EMPTY_ENTRY)
773 */
find_free_entry(unsigned int size,raw_entry * r)774 int directory::find_free_entry(unsigned int size, raw_entry * r)
775 {
776 struct raw_entry free_block, current;
777 bool free_found = false;
778 int j = 0;
779 off_t offset = lseek(fd, 0, SEEK_CUR);
780
781 memset(&free_block, 0, sizeof (raw_entry));
782 memset(¤t, 0, sizeof (raw_entry));
783
784 while (true) {
785 current.offset = offset;
786 j = read(fd, ¤t.header, sizeof (current.header));
787 if (j == 0) {
788 /** EOF **/
789 break;
790 }
791 else if (j < 0) {
792 return ERR_IO_READ;
793 }
794 else if (j < (signed) sizeof(current.header)) {
795 return ERR_DB_CORRUPTION;
796 }
797 else if (validate_header(¤t.header) != 0) {
798 return ERR_DB_CORRUPTION;
799 }
800 offset += j;
801
802 if (free_found) {
803 if (current.header.flags & FE_EMPTY_ENTRY) {
804 /**
805 * Found two empty blocks next to each other.
806 * Consolidate them -- but make sure the resultant free entry
807 * does not exceed any size limits.
808 */
809 DEBUG("find_free_entry(): consolidating empty blocks:\n");
810 DEBUG(" block 1: %d bytes @ offset %ld | block 2: %d bytes @ offset %ld\n",
811 free_block.header.padding_length, free_block.offset,
812 current.header.padding_length, current.offset);
813
814 unsigned block1_size = calc_entry_len(&free_block.header);
815 unsigned block2_size = calc_entry_len(¤t.header);
816 if (block1_size + block2_size <= MAX_ENTRY_SIZE) {
817 free_block.header.padding_length += block2_size;
818 lseek(fd, free_block.offset, SEEK_SET);
819 // XXX: unchecked write
820 ::write(fd, &free_block.header, sizeof (free_block.header));
821 offset = lseek(fd, free_block.header.padding_length, SEEK_CUR);
822 continue;
823 }
824 // the resulting combined entry would have been too large ...
825 DEBUG("find_free_entry(): skipped consolidation; total was %u (max %zd)\n",
826 block1_size + block2_size, MAX_ENTRY_SIZE);
827 }
828
829 /**
830 * End of free space: check if it will fit here.
831 */
832 if (free_block.header.padding_length >= size) {
833 DEBUG("find_free_entry(): Using free block of %d bytes @ offset %ld\n",
834 free_block.header.padding_length, free_block.offset);
835 r->offset = free_block.offset;
836 r->header.padding_length = free_block.header.padding_length;
837 lseek(fd, free_block.offset, SEEK_SET);
838 return 0;
839 }
840 // not big enough
841 offset = lseek(fd, current.header.padding_length + calc_dynlen(¤t.header), SEEK_CUR);
842 free_found = false;
843 continue;
844 }
845
846 // Still looking for a free block
847 if (current.header.flags & FE_EMPTY_ENTRY) {
848 free_block.offset = current.offset;
849 free_block.header.padding_length = current.header.padding_length;
850 free_block.header.flags = FE_EMPTY_ENTRY;
851 free_found = true;
852 }
853
854 // Nothing
855 offset = lseek(fd, current.header.padding_length + calc_dynlen(¤t.header), SEEK_CUR);
856 }
857
858 /**
859 * EOF reached.
860 */
861 if (free_found) {
862 /**
863 * Last block was free one:
864 * just truncate to the start of the free block.
865 */
866 DEBUG("find_free_entry(): found free space at end of file with offset=%ld, truncating file\n", free_block.offset);
867 j = ftruncate(fd, free_block.offset);
868 if (j < 0) {
869 // This might happen if filesystem becomes full ?
870 DEBUG("find_free_entry(): ftruncate() failed!\n");
871 return ERR_IO_WRITE;
872 }
873 }
874 r->offset = lseek(fd, 0, SEEK_END);
875 r->header.padding_length = size;
876 return 0;
877 }
878
879 /**
880 * Zap the entry at 'offset', marking it as a free block.
881 */
free_entry(off_t offset)882 int directory::free_entry(off_t offset)
883 {
884 assert(offset > 0);
885 struct raw_entry e;
886 memset(&e, 0, sizeof(e));
887 lseek(fd, offset, SEEK_SET);
888 int j = read(fd, &e.header, sizeof (e.header));
889 if (j < 0) {
890 return ERR_IO_READ;
891 }
892 else if (j < (signed) sizeof(e.header)) {
893 return ERR_DB_CORRUPTION;
894 }
895 else if (validate_header(&e.header) != 0) {
896 return ERR_DB_CORRUPTION;
897 }
898
899 unsigned int new_padding = e.header.padding_length + calc_dynlen(&e.header);
900 memset(&e, 0, sizeof(e));
901
902 e.header.flags = FE_EMPTY_ENTRY;
903 e.header.padding_length = new_padding;
904 char * zeroes = new char[new_padding];
905 memset(zeroes, 0, new_padding);
906
907 lseek(fd, -(sizeof (e.header)), SEEK_CUR);
908 // XXX: unchecked writes
909 ::write(fd, &e.header, sizeof(e.header));
910 ::write(fd, zeroes, new_padding);
911 delete[] zeroes;
912 return 0;
913 }
914
915 /**
916 * Write this entry to database file.
917 * raw_entry r determines what gets written where (at r->offset).
918 * Overwrites any data at the spot and assumes there is sufficient space for
919 * the entry.
920 * NOTE: validates header and dynamic data block before writing.
921 *
922 * @param r pointer to raw_entry, which will be validated
923 * @return 0 on success
924 */
write_entry(const struct raw_entry * r)925 int directory::write_entry(const struct raw_entry * r)
926 {
927 assert(r->offset > 0);
928 int j = lseek(fd, r->offset, SEEK_SET);
929 if (j < 0) {
930 return ERR_DB_CORRUPTION;
931 }
932 if (validate_header(&r->header) != 0) {
933 return ERR_BAD_WRITE;
934 }
935 if (validate_dynamic(&r->header, r->string_data) != 0) {
936 return ERR_BAD_WRITE;
937 }
938
939 unsigned int dyn = calc_dynlen(&r->header);
940 unsigned int padding = r->header.padding_length;
941 unsigned int total = sizeof(r->header) + dyn + padding;
942
943 // TODO: use writev() here?
944 char * buffer = new char[total];
945 memset(buffer, 0, total);
946 memcpy(buffer, &r->header, sizeof(r->header));
947 memcpy(buffer + sizeof(r->header), r->string_data, dyn);
948
949 j = ::write(fd, buffer, total);
950 delete[] buffer;
951
952 if (j < (signed) total) {
953 return ERR_IO_WRITE;
954 }
955
956 /** update known filesize **/
957 if (r->offset + (signed) total > last_size) {
958 last_size = r->offset + total;
959 }
960 DEBUG("write_entry(): wrote entry @ offset %ld size: %u bytes [%zd header, %d dyn, %d padding]\n",
961 r->offset, total, sizeof(r->header), dyn, padding);
962 return 0;
963 }
964
is_legal_name(const char * name)965 /* static */ bool directory::is_legal_name(const char * name)
966 {
967 return (is_non_empty(name) && strcmp(name, DB_FILE_NAME) != 0
968 && !strchr(name, '/'));
969 }
970
971 /**
972 * Fill a entry header with information from a file_entry -- creation time, flags,
973 * downloads, and the string data.
974 *
975 * @param e file_entry to read
976 * @param h entry_header to populate
977 * @return length of the string data.
978 */
fill_header(const file_entry * e,entry_header * h)979 /* static */ size_t directory::fill_header(const file_entry * e, entry_header * h)
980 {
981 assert(h);
982 memset(h, 0, sizeof(entry_header));
983
984 size_t dyn = 0;
985 h->creation = e->creation();
986 h->flags = e->flags();
987 h->mode = e->mode();
988
989 // The owner and group are stored in the same field,
990 // separated by a colon.
991 if (is_non_empty(e->owner())) {
992 h->owner_length = strlen(e->owner()) + 1;
993 if (is_non_empty(e->group())) {
994 // space set aside for owner's null byte will
995 // be used for the colon
996 h->owner_length += strlen(e->group()) + 1;
997 }
998 dyn += h->owner_length;
999 }
1000 if (is_non_empty(e->misc())) {
1001 dyn += (h->misc_length = strlen(e->misc()) + 1);
1002 }
1003 if (is_non_empty(e->desc())) {
1004 dyn += (h->desc_length = strlen(e->desc()) + 1);
1005 }
1006 if (is_non_empty(e->name())) {
1007 dyn += (h->name_length = strlen(e->name()) + 1);
1008 }
1009 if (is_non_empty(e->from())) {
1010 dyn += (h->from_length = strlen(e->from()) + 1);
1011 }
1012 return dyn;
1013 }
1014
1015 /**
1016 * Create raw data buffer to be written to the database file.
1017 * The buffer must be as large as all of the length fields
1018 * combined. (which fill_header() will return)
1019 * @param e file_entry to read
1020 * @param b buffer to fill
1021 * @return the number of bytes written
1022 */
fill_dynamic(const file_entry * e,char * b)1023 /* static */ size_t directory::fill_dynamic(const file_entry * e, char * b)
1024 {
1025 unsigned offset = 0;
1026 unsigned len = 0;
1027 char * user = NULL;
1028
1029 my_asprintf(&user, "%s:%s", e->owner(), e->group());
1030 const char * table[] = {
1031 user, e->from(), e->misc(), e->desc(), e->name()
1032 };
1033
1034 for (unsigned j = 0; j < 5; ++j) {
1035 len = (table[j] ? strlen(table[j]) : 0);
1036 if (len > 0) {
1037 memcpy(b + offset, table[j], len);
1038 *(b+offset + len) = 0;
1039 offset += len+1;
1040 }
1041 }
1042
1043 delete[] user;
1044 return offset;
1045 }
1046
1047 /**
1048 * Fill a file_entry with information from an entry in the database.
1049 * Assumes that the header and dynamic data portions of the raw_entry
1050 * have already been validated. In particular, the string-terminating
1051 * null characters *must* be at the correct locations in the dynamic data block.
1052 *
1053 * @param r raw_entry containg header and dynamic string data
1054 * @param e file_entry to assign values to
1055 */
fill_file_entry(const raw_entry * r,file_entry * e)1056 /* static */ void directory::fill_file_entry(const raw_entry * r, file_entry * e)
1057 {
1058 assert(r != NULL);
1059 assert(e != NULL);
1060 DEBUG("%s [%p]\n", __PRETTY_FUNCTION__, e);
1061
1062 const char * raw = r->string_data;
1063 const struct entry_header * h = &r->header;
1064 unsigned int offset = 0;
1065 bool old_version = false;
1066
1067 /**
1068 * First set user/group information.
1069 * In older versions, group information did not exist. Now, the group is appended
1070 * to username, with a colon separating them. If the older format is encountered,
1071 * assign a default value to the group field.
1072 */
1073 const char * colon = strchr(raw, ':');
1074 if (colon != NULL) {
1075 // new format
1076 int offset = colon - raw;
1077 char * user = my_strdup(raw);
1078 user[offset] = 0;
1079
1080 const char * group = user+offset+1;
1081
1082 assert(strlen(user) > 0);
1083 assert(strlen(group) > 0);
1084 e->set_owner(user);
1085 e->set_group(group);
1086
1087 delete[] user;
1088 }
1089 else {
1090 // old format: set a default group
1091 old_version = true;
1092 e->set_owner(raw);
1093 e->set_group(raw);
1094 }
1095 offset += h->owner_length;
1096
1097 if (h->from_length > 0) {
1098 e->set_from(&raw[offset]);
1099 }
1100 offset += h->from_length;
1101
1102 if (h->misc_length > 0) {
1103 e->set_misc(&raw[offset]);
1104 }
1105 offset += h->misc_length;
1106
1107 if (h->desc_length > 0) {
1108 e->set_desc(&raw[offset]);
1109 }
1110 offset += h->desc_length;
1111
1112 if (raw[offset]) {
1113 e->set_name(&raw[offset]);
1114 }
1115
1116 e->set_creation(h->creation);
1117 e->set_flags(h->flags);
1118 e->set_mode(h->mode);
1119
1120 // backward compatiblity hacks
1121 // assign sane permissions to file if it didn't have any
1122 if (old_version) {
1123 DEBUG("directory::fill_file_entry(): detected old format for entry '%s' (owner: '%s' mode: %d')\n",
1124 e->name(), e->owner(), e->mode());
1125 if (strcasecmp(e->owner(), "(system)") == 0) {
1126 e->set_owner("root");
1127 e->set_group("root");
1128 }
1129 if (!e->mode()) {
1130 if (e->flags() & FE_DIRECTORY) {
1131 e->set_mode(0755);
1132 }
1133 else {
1134 e->set_mode(0600);
1135 }
1136 }
1137 }
1138 //--------------------------------------------------
1139 // DEBUG("\t---> new entry: name: '%s' owner: %s\n", name(), owner());
1140 // DEBUG("\t \\--> pass: %s desc: %s from: %s\n", misc(), desc(), from());
1141 // DEBUG("\t \\--> creation: %ld flags: %d\n", _creation, _flags);
1142 //--------------------------------------------------
1143 }
1144
1145 /**
1146 * Check legality of a entry header.
1147 * Header legality rules:
1148 * - Unfortunately there is no magic number for entry headers.
1149 * - The total advertised size of the entry must be <= MAX_ENTRY_SIZE.
1150 * - Each field's length must not exceed its limit.
1151 * - The name length must be >= 2
1152 * - The owner length cannot be >= 2
1153 * - Special exceptions for "Empty" entries
1154 *
1155 * Empty Entry header rules:
1156 * - Padding must be > 0 <= MAX_PADDING_LEN
1157 * - Creation field may be non-zero, all other fields must be zero.
1158 *
1159 * @param h pointer to entry_header struct to validate
1160 * @return 0 if the header appears legal
1161 */
validate_header(const entry_header * h)1162 /* static */ int directory::validate_header(const entry_header * h)
1163 {
1164 using util::in_range;
1165
1166 if (h->flags & FE_EMPTY_ENTRY) {
1167 // empty entry
1168 const bool padding_ok = h->padding_length > 0
1169 && h->padding_length <= MAX_PADDING_LEN;
1170 const bool no_dyn = (calc_dynlen(h) == 0);
1171 const bool fields_ok = !h->mode && !h->reserved1;
1172
1173 if (padding_ok && no_dyn && fields_ok) {
1174 assert(calc_entry_len(h) <= MAX_ENTRY_SIZE);
1175 return 0;
1176 }
1177 return -1;
1178 }
1179 // other entries
1180 if (calc_entry_len(h) > MAX_ENTRY_SIZE) {
1181 return -1;
1182 }
1183
1184 const bool name_ok = in_range(h->name_length, MIN_NAME_LEN, MAX_NAME_LEN);
1185 const bool owner_ok = in_range(h->owner_length, MIN_OWNER_LEN, MAX_OWNER_LEN);
1186 const bool misc_ok = in_range(h->misc_length, MIN_MISC_LEN, MAX_MISC_LEN);
1187 const bool desc_ok = in_range(h->desc_length, MIN_DESC_LEN, MAX_DESC_LEN);
1188 const bool from_ok = in_range(h->from_length, MIN_FROM_LEN, MAX_FROM_LEN);
1189 const bool padding_ok = in_range(h->padding_length, MIN_PADDING_LEN, MAX_PADDING_LEN);
1190
1191 // TODO: check value of reserved1 field?
1192 if (name_ok && owner_ok && misc_ok &&
1193 desc_ok && from_ok && padding_ok) {
1194 return 0;
1195 }
1196 return -1;
1197 }
1198
1199 /**
1200 * Given a legal entry_header, determine if a dynamic data block
1201 * is legal. Currently this only involves checking if the string
1202 * terminating null characters are at the right locations.
1203 */
validate_dynamic(const entry_header * h,const char * data)1204 /* static */ int directory::validate_dynamic(const entry_header * h, const char * data)
1205 {
1206 const unsigned offsets[] = {
1207 h->owner_length,
1208 h->from_length,
1209 h->misc_length,
1210 h->desc_length,
1211 h->name_length
1212 };
1213
1214 /**
1215 * This is a bit tricky, since the length of a dynamic field may be zero,
1216 * but if it's not zero, it always includes 1 byte for the terminating '\0'
1217 */
1218 for (unsigned i = 0, j = 0; i < (sizeof offsets / sizeof(offsets[0])); ++i) {
1219 j += offsets[i];
1220 if (offsets[i] != 0) {
1221 if (data == NULL) {
1222 // XXX: is this necessary?
1223 return -1;
1224 }
1225 if (data[j-1] != 0) {
1226 return -1;
1227 }
1228 }
1229 }
1230 return 0;
1231 }
1232
1233 #ifdef __DEBUG__
1234 /**
1235 * Print listing of raw entries to a file descriptor
1236 */
dump(int out)1237 int directory::dump(int out)
1238 {
1239 using io::fdprintf;
1240 int j = opendb();
1241 if (j < 0) {
1242 fdprintf(out, "Unable to open database: %s\n", this->strerror(j));
1243 return j;
1244 }
1245 std::string ts;
1246 timestamp_full(ts, last_mod);
1247
1248 fdprintf(out, "***************************************************************************\n");
1249 fdprintf(out, "Analyzing database file: %s\n", dbfile);
1250 fdprintf(out, "size: %u timestamp: %s\n", last_size, ts.c_str());
1251 fdprintf(out, "abs. path: %s\n", abs_path());
1252 fdprintf(out, "rel. path: %s\n", rel_path());
1253 fdprintf(out, "\nDumping entries...\n");
1254
1255 off_t offset = lseek(fd, 0, SEEK_CUR);
1256 struct raw_entry r;
1257
1258 while (true) {
1259 fdprintf(out, "\n---------------------------------------------------\n"
1260 "offset: %d [0x%x]\n", offset, offset);
1261 memset(&r, 0, sizeof(r));
1262 j = read(fd, &r.header, sizeof(r.header));
1263 if (j == 0) {
1264 // EOF
1265 break;
1266 }
1267 else if (j < (signed) sizeof(r.header)) {
1268 // premature eof or read error
1269 if (j < 0) {
1270 fdprintf(out, "* ERROR: read() failed: %s\n", ::strerror(errno));
1271 }
1272 else {
1273 fdprintf(out, "* ERROR: premature EOF: corruption detected!\n");
1274 }
1275 closedb();
1276 return ERR_DB_CORRUPTION;
1277 }
1278 else if (validate_header(&r.header) != 0) {
1279 return ERR_DB_CORRUPTION;
1280 }
1281 offset += j;
1282
1283 unsigned short dyn_length = calc_dynlen(&r.header);
1284 if (r.header.flags & FE_EMPTY_ENTRY) {
1285 offset = lseek(fd, dyn_length + r.header.padding_length, SEEK_CUR);
1286 fdprintf(out, "free block: %u bytes\n", r.header.padding_length);
1287 continue;
1288 }
1289
1290 fdprintf(out, "file block : %u bytes dynamic, %u bytes padding\n", dyn_length, r.header.padding_length);
1291 fdprintf(out, "flags : 0x%x\n", r.header.flags);
1292 r.string_data = new char[dyn_length];
1293 j = read(fd, r.string_data, dyn_length);
1294 if (j < (signed) dyn_length) {
1295 // premature EOF or read error.
1296 delete[] r.string_data;
1297 if (j < 0) {
1298 fdprintf(out, "* ERROR: read error while reading dynamic data: %s\n", ::strerror(errno));
1299 }
1300 else {
1301 fdprintf(out, "* ERROR: premature EOF while reading dynamic data: corruption detected!\n");
1302 }
1303 closedb();
1304 return ERR_DB_CORRUPTION;
1305 }
1306 offset += j;
1307 file_entry * e = new file_entry(this);
1308 fill_file_entry(&r, e);
1309 timestamp_full(ts, e->creation());
1310
1311 fdprintf(out, "file name : %s\n", e->name());
1312 fdprintf(out, "owner : %s\n", e->owner());
1313 fdprintf(out, "from : %s\n", e->from());
1314 fdprintf(out, "misc : %s\n", e->misc());
1315 fdprintf(out, "description : %s\n", e->desc());
1316 fdprintf(out, "creation time : %d (%s)\n", e->creation(), ts.c_str());
1317 fdprintf(out, "padding : %d\n", r.header.padding_length);
1318
1319 delete e;
1320 delete[] r.string_data;
1321 offset = lseek(fd, r.header.padding_length, SEEK_CUR);
1322 }
1323
1324 fdprintf(out, "\n\n *** END OF FILE ***\n");
1325 closedb();
1326 return 0;
1327 }
1328
1329 #endif /* __DEBUG__ */
1330
1331 } // namespace fs
1332