1 /*
2  * filedb3.c -- part of filesys.mod
3  *   low level functions for file db handling
4  *
5  * Rewritten by Fabian Knittel <fknittel@gmx.de>
6  */
7 /*
8  * Copyright (C) 1997 Robey Pointer
9  * Copyright (C) 1999 - 2021 Eggheads Development Team
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  */
25 
26 /*  filedb structure:
27  *
28  *  +---------------+                           _
29  *  | filedb_top    |                           _|  DB header
30  *  |---------------|
31  *  .               .
32  *  .               .
33  *  | ...           |
34  *  |---------------|     _                     _
35  *  | filedb_header |     _|  Static length      |
36  *  |- --- - --- - -|     _                      |
37  *  | filename      |      |                     |
38  *  |- - - - - - - -|      |                     |
39  *  | description   |      |                     |
40  *  |- - - - - - - -|      |  Dynamic length     |  Complete entry
41  *  | channel name  |      |                     |
42  *  |- - - - - - - -|      |                     |
43  *  | uploader      |      |                     |
44  *  |- - - - - - - -|      |                     |
45  *  | flags_req     |      |                     |
46  *  |- - - - - - - -|      |                     |
47  *  | share link    |      |                     |
48  *  |- - - - - - - -|      |                     |
49  *  | buffer        |     _|                    _|
50  *  |---------------|
51  *  | ...           |
52  *  .               .
53  *  .               .
54  *
55  *  To know how long the complete entry is, you need to read out the
56  *  header first. This concept allows us to have unlimited filename
57  *  lengths, unlimited description lengths, etc.
58  *
59  *  Buffer is an area which doesn't contain any information and which
60  *  is just skipped. It only serves as a placeholder to allow entries
61  *  which shrink in size to maintain their position without messing up
62  *  the position of all following entries.
63  */
64 
65 /* Number of databases opened simultaneously. More than 2 is
66  * be unnormal.
67  */
68 static int count = 0;
69 
70 
71 /*
72  *   Memory management helper functions
73  */
74 
75 /* Frees a filedb entry and all it's elements.
76  */
free_fdbe(filedb_entry ** fdbe)77 static void free_fdbe(filedb_entry ** fdbe)
78 {
79   if (!fdbe || !*fdbe)
80     return;
81   if ((*fdbe)->filename)
82     my_free((*fdbe)->filename);
83   if ((*fdbe)->desc)
84     my_free((*fdbe)->desc);
85   if ((*fdbe)->sharelink)
86     my_free((*fdbe)->sharelink);
87   if ((*fdbe)->chan)
88     my_free((*fdbe)->chan);
89   if ((*fdbe)->uploader)
90     my_free((*fdbe)->uploader);
91   if ((*fdbe)->flags_req)
92     my_free((*fdbe)->flags_req);
93   my_free(*fdbe);
94 }
95 
96 /* Allocates and initialises a filedb entry
97  */
_malloc_fdbe(char * file,int line)98 static filedb_entry *_malloc_fdbe(char *file, int line)
99 {
100   filedb_entry *fdbe = NULL;
101 
102 #ifdef DEBUG_MEM
103   /* This is a hack to access the nmalloc function with
104    * special file and line information
105    */
106   fdbe = (((void *(*)())global[0])(sizeof(filedb_entry),MODULE_NAME,file,line));
107 #else
108   fdbe = nmalloc(sizeof(filedb_entry));
109 #endif
110   egg_bzero(fdbe, sizeof(filedb_entry));
111 
112   /* Mark as new, will be overwritten if necessary. */
113   fdbe->_type = TYPE_NEW;
114   return fdbe;
115 }
116 
117 
118 /*
119  *  File locking
120  */
121 
122 /* Locks the file, using fcntl. Execution is locked until we
123  * have exclusive access to it.
124  */
lockfile(FILE * fdb)125 static void lockfile(FILE *fdb)
126 {
127   struct flock fl;
128 
129   fl.l_type = F_WRLCK;
130   fl.l_start = 0;
131   fl.l_whence = SEEK_SET;
132   fl.l_len = 0;
133   fcntl(fileno(fdb), F_SETLKW, &fl);
134 }
135 
136 /* Unlocks the file using fcntl.
137  */
unlockfile(FILE * f)138 static void unlockfile(FILE *f)
139 {
140   struct flock fl;
141 
142   fl.l_type = F_UNLCK;
143   fl.l_start = 0;
144   fl.l_whence = SEEK_SET;
145   fl.l_len = 0;
146   fcntl(fileno(f), F_SETLKW, &fl);
147 }
148 
149 
150 /*
151  *   filedb functions
152  */
153 
154 /* Copies the DB header to fdbt or just positions the file
155  * position pointer onto the first entry after the db header.
156  */
filedb_readtop(FILE * fdb,filedb_top * fdbt)157 static int filedb_readtop(FILE *fdb, filedb_top *fdbt)
158 {
159   if (fdbt) {
160     /* Read header */
161     fseek(fdb, 0L, SEEK_SET);
162     if (feof(fdb) || !fread(fdbt, sizeof *fdbt, 1, fdb) || ferror(fdb))
163       return 0;
164   } else
165     fseek(fdb, sizeof(filedb_top), SEEK_SET);
166   return 1;
167 }
168 
169 /* Writes the DB header to the top of the filedb.
170  */
filedb_writetop(FILE * fdb,filedb_top * fdbt)171 static int filedb_writetop(FILE *fdb, filedb_top *fdbt)
172 {
173   fseek(fdb, 0L, SEEK_SET);
174   fwrite(fdbt, 1, sizeof(filedb_top), fdb);
175   return 1;
176 }
177 
178 /* Deletes the entry at 'pos'. It just adjusts the header to
179  * mark it as 'unused' and to assign all dynamic space to
180  * the buffer.
181  */
filedb_delfile(FILE * fdb,long pos)182 static int filedb_delfile(FILE *fdb, long pos)
183 {
184   filedb_header fdh;
185 
186   fseek(fdb, pos, SEEK_SET);    /* Go to start of entry */
187   /* Read header */
188   if (feof(fdb) || !fread(&fdh, sizeof fdh, 1, fdb) || ferror(fdb))
189     return 0;
190   fdh.stat = FILE_UNUSED;
191 
192   /* Assign all available space to buffer. Simplifies
193    * space calculation later on.
194    */
195   fdh.buffer_len += filedb_tot_dynspace(fdh);
196   filedb_zero_dynspace(fdh);
197 
198   fseek(fdb, pos, SEEK_SET);    /* Go back to start     */
199   fwrite(&fdh, 1, sizeof(filedb_header), fdb);  /* Write new header     */
200   return 1;
201 }
202 
203 /* Searches for a suitable place to write an entry which uses tot
204  * bytes for dynamic data.
205  *
206  *  * If there is no such existing entry, it just points to the
207  *    end of the DB.
208  *  * If it finds an empty entry and it has enough space to fit
209  *    in another entry, we split it up and only use the space we
210  *    really need.
211  *
212  * Note: We can assume that empty entries' dyn_lengths are zero.
213  *       Therefore we only need to check buf_len.
214  */
filedb_findempty(FILE * fdb,int tot)215 static filedb_entry *filedb_findempty(FILE *fdb, int tot)
216 {
217   filedb_entry *fdbe;
218 
219   filedb_readtop(fdb, NULL);
220   fdbe = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
221   while (fdbe) {
222     /* Found an existing, empty entry? */
223     if ((fdbe->stat & FILE_UNUSED) && (fdbe->buf_len >= tot)) {
224       /* Do we have enough space to split up the entry to form
225        * another empty entry? That way we would use the space
226        * more efficiently.
227        */
228       if (fdbe->buf_len > (tot + sizeof(filedb_header) + FILEDB_ESTDYN)) {
229         filedb_entry *fdbe_oe;
230 
231         /* Create new entry containing the additional space */
232         fdbe_oe = malloc_fdbe();
233         fdbe_oe->stat = FILE_UNUSED;
234         fdbe_oe->pos = fdbe->pos + sizeof(filedb_header) + tot;
235         fdbe_oe->buf_len = fdbe->buf_len - tot - sizeof(filedb_header);
236         filedb_movefile(fdb, fdbe_oe->pos, fdbe_oe);
237         free_fdbe(&fdbe_oe);
238 
239         /* Cut down buf_len of entry as the rest is now used in the new
240          * entry.
241          */
242         fdbe->buf_len = tot;
243       }
244       return fdbe;
245     }
246     free_fdbe(&fdbe);
247     fdbe = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
248   }
249 
250   /* No existing entries, so create new entry at end of DB instead. */
251   fdbe = malloc_fdbe();
252   fseeko(fdb, 0, SEEK_END);
253   fdbe->pos = ftello(fdb);
254   return fdbe;
255 }
256 
257 /* Updates or creates entries and information in the filedb.
258  *
259  *   * If the new entry is the same size or smaller than the original
260  *     one, we use the old position and just adjust the buffer length
261  *     appropriately.
262  *   * If the entry is completely _new_ or if the entry's new size is
263  *     _bigger_ than the old one, we search for a new position which
264  *     suits our needs.
265  *
266  * Note that the available space also includes the buffer.
267  *
268  * The file pointer will _always_ position directly after the updated
269  * entry.
270  */
_filedb_updatefile(FILE * fdb,long pos,filedb_entry * fdbe,int update,char * file,int line)271 static int _filedb_updatefile(FILE *fdb, long pos, filedb_entry *fdbe,
272                               int update, char *file, int line)
273 {
274   filedb_header fdh;
275   int reposition = 0;
276   int ndyntot, odyntot, nbuftot, obuftot;
277 
278   egg_bzero(&fdh, sizeof(filedb_header));
279   fdh.uploaded = fdbe->uploaded;
280   fdh.size = fdbe->size;
281   fdh.stat = fdbe->stat;
282   fdh.gots = fdbe->gots;
283 
284   /* Only add the buffer length if the buffer is not empty. Otherwise it
285    * would result in lots of 1 byte entries which actually don't contain
286    * any data.
287    */
288   if (fdbe->filename)
289     fdh.filename_len = strlen(fdbe->filename) + 1;
290   if (fdbe->desc)
291     fdh.desc_len = strlen(fdbe->desc) + 1;
292   if (fdbe->chan)
293     fdh.chan_len = strlen(fdbe->chan) + 1;
294   if (fdbe->uploader)
295     fdh.uploader_len = strlen(fdbe->uploader) + 1;
296   if (fdbe->flags_req)
297     fdh.flags_req_len = strlen(fdbe->flags_req) + 1;
298   if (fdbe->sharelink)
299     fdh.sharelink_len = strlen(fdbe->sharelink) + 1;
300 
301   odyntot = fdbe->dyn_len;      /* Old length of dynamic data   */
302   obuftot = fdbe->buf_len;      /* Old length of spare space    */
303   ndyntot = filedb_tot_dynspace(fdh);   /* New length of dynamic data   */
304   nbuftot = obuftot;
305 
306   if (fdbe->_type == TYPE_EXIST) {
307     /* If we only update the header, we don't need to worry about
308      * sizes and just use the old place (i.e. the place pointed
309      * to by pos).
310      */
311     if (update < UPDATE_ALL) {
312       /* Unless forced to it, we ignore new buffer sizes if we do not
313        * run in UPDATE_ALL mode.
314        */
315       if (update != UPDATE_SIZE) {
316         ndyntot = odyntot;
317         nbuftot = obuftot;
318       }
319     } else {
320       /* If we have a given/preferred position */
321       if ((pos != POS_NEW) &&
322           /* and if our new size is smaller than the old size, we
323            * just adjust the buffer length and still use the same
324            * position
325            */
326           (ndyntot <= (odyntot + obuftot))) {
327         nbuftot = (odyntot + obuftot) - ndyntot;
328       } else {
329         /* If we have an existing position, but the new entry doesn't
330          * fit into it's old home, we need to delete it before
331          * repositioning.
332          */
333         if (pos != POS_NEW)
334           filedb_delfile(fdb, pos);
335         reposition = 1;
336       }
337     }
338   } else {
339     fdbe->_type = TYPE_EXIST;   /* Update type                  */
340     reposition = 1;
341   }
342 
343   /* Search for a new home */
344   if (reposition) {
345     filedb_entry *n_fdbe;
346 
347     n_fdbe = filedb_findempty(fdb, filedb_tot_dynspace(fdh));
348     fdbe->pos = pos = n_fdbe->pos;
349     /* If we create a new entry (instead of using an existing one),
350      * buf_len is zero
351      */
352     if (n_fdbe->buf_len > 0)
353       /* Note: empty entries have dyn_len set to zero, so we only
354        *       need to consider buf_len.
355        */
356       nbuftot = n_fdbe->buf_len - ndyntot;
357     else
358       nbuftot = 0;
359     free_fdbe(&n_fdbe);
360   }
361 
362   /* Set length of dynamic data and buffer */
363   fdbe->dyn_len = ndyntot;
364   fdbe->buf_len = fdh.buffer_len = nbuftot;
365 
366   /* Write header */
367   fseek(fdb, pos, SEEK_SET);
368   fwrite(&fdh, 1, sizeof(filedb_header), fdb);
369   /* Write dynamic data */
370   if (update == UPDATE_ALL) {
371     if (fdbe->filename)
372       fwrite(fdbe->filename, 1, fdh.filename_len, fdb);
373     if (fdbe->desc)
374       fwrite(fdbe->desc, 1, fdh.desc_len, fdb);
375     if (fdbe->chan)
376       fwrite(fdbe->chan, 1, fdh.chan_len, fdb);
377     if (fdbe->uploader)
378       fwrite(fdbe->uploader, 1, fdh.uploader_len, fdb);
379     if (fdbe->flags_req)
380       fwrite(fdbe->flags_req, 1, fdh.flags_req_len, fdb);
381     if (fdbe->sharelink)
382       fwrite(fdbe->sharelink, 1, fdh.sharelink_len, fdb);
383   } else
384     fseek(fdb, ndyntot, SEEK_CUR);      /* Skip over dynamic data */
385   fseek(fdb, nbuftot, SEEK_CUR);        /* Skip over buffer       */
386   return 0;
387 }
388 
389 /* Moves an existing file entry, saved in fdbe to a new position.
390  */
_filedb_movefile(FILE * fdb,long pos,filedb_entry * fdbe,char * file,int line)391 static int _filedb_movefile(FILE *fdb, long pos, filedb_entry *fdbe,
392                             char *file, int line)
393 {
394   fdbe->_type = TYPE_EXIST;
395   _filedb_updatefile(fdb, pos, fdbe, UPDATE_ALL, file, line);
396   return 0;
397 }
398 
399 /* Adds a completely new file.
400  */
_filedb_addfile(FILE * fdb,filedb_entry * fdbe,char * file,int line)401 static int _filedb_addfile(FILE *fdb, filedb_entry *fdbe, char *file, int line)
402 {
403   fdbe->_type = TYPE_NEW;
404   _filedb_updatefile(fdb, POS_NEW, fdbe, UPDATE_ALL, file, line);
405   return 0;
406 }
407 
408 /* Short-cut macro to read an entry from disc to memory. Only
409  * useful for filedb_getfile().
410  */
411 #define filedb_read(fdb, entry, len)            \
412 {                                               \
413   if ((len) > 0) {                              \
414     (entry) = nmalloc((len));                   \
415     if (feof((fdb)) ||                          \
416         !fread((entry), (len), 1, (fdb)) ||     \
417         ferror((fdb))) {                         \
418       nfree(entry);                             \
419       return NULL;                              \
420     }                                           \
421   }                                             \
422 }
423 
424 /* Reads an entry from the fildb at the specified position. The
425  * amount of information returned depends on the get flag.
426  * It always positions the file position pointer exactly behind
427  * the entry.
428  */
_filedb_getfile(FILE * fdb,long pos,int get,char * file,int line)429 static filedb_entry *_filedb_getfile(FILE *fdb, long pos, int get,
430                                      char *file, int line)
431 {
432   filedb_entry *fdbe;
433   filedb_header fdh;
434 
435   /* Read header */
436   fseek(fdb, pos, SEEK_SET);
437   if (feof(fdb) || !fread(&fdh, sizeof fdh, 1, fdb) || ferror(fdb))
438     return NULL;
439 
440   /* Allocate memory for file db entry */
441   fdbe = _malloc_fdbe(file, line);
442 
443   /* Optimisation: maybe use memcpy here? */
444   fdbe->uploaded = fdh.uploaded;
445   fdbe->size = fdh.size;
446   fdbe->stat = fdh.stat;
447   fdbe->gots = fdh.gots;
448 
449   fdbe->buf_len = fdh.buffer_len;
450   fdbe->dyn_len = filedb_tot_dynspace(fdh);
451   fdbe->pos = pos;              /* Save position                */
452   fdbe->_type = TYPE_EXIST;     /* Entry exists in DB           */
453 
454   /* This is useful for cases where we don't read the rest of the
455    * data, but need to know whether the file is a link.
456    */
457   if (fdh.sharelink_len > 0)
458     fdbe->stat |= FILE_ISLINK;
459   else
460     fdbe->stat &= ~FILE_ISLINK;
461 
462   /* Read additional data from db */
463   if (get >= GET_FILENAME) {
464     filedb_read(fdb, fdbe->filename, fdh.filename_len);
465   } else
466     fseek(fdb, fdh.filename_len, SEEK_CUR);
467   if (get < GET_FULL || (fdh.stat & FILE_UNUSED))
468     fseek(fdb, filedb_tot_dynspace(fdh) - fdh.filename_len, SEEK_CUR);
469   else if (get == GET_FULL) {
470     filedb_read(fdb, fdbe->desc, fdh.desc_len);
471     filedb_read(fdb, fdbe->chan, fdh.chan_len);
472     filedb_read(fdb, fdbe->uploader, fdh.uploader_len);
473     filedb_read(fdb, fdbe->flags_req, fdh.flags_req_len);
474     filedb_read(fdb, fdbe->sharelink, fdh.sharelink_len);
475   }
476   fseek(fdb, fdh.buffer_len, SEEK_CUR); /* Skip buffer                  */
477   return fdbe;                  /* Return the ready structure   */
478 }
479 
480 /* Searches the filedb for a file matching the specified mask, starting
481  * at position 'pos'. The first matching file is returned.
482  */
_filedb_matchfile(FILE * fdb,long pos,char * match,char * file,int line)483 static filedb_entry *_filedb_matchfile(FILE *fdb, long pos, char *match,
484                                        char *file, int line)
485 {
486   filedb_entry *fdbe = NULL;
487 
488   fseek(fdb, pos, SEEK_SET);
489   while (!feof(fdb)) {
490     pos = ftell(fdb);
491     fdbe = filedb_getfile(fdb, pos, GET_FILENAME);
492     if (fdbe) {
493       if (!(fdbe->stat & FILE_UNUSED) &&        /* Not unused?         */
494           wild_match_file(match, fdbe->filename)) {     /* Matches our mask?   */
495         free_fdbe(&fdbe);
496         fdbe = _filedb_getfile(fdb, pos, GET_FULL, file, line); /* Save all data now   */
497         return fdbe;
498       }
499       free_fdbe(&fdbe);
500     }
501   }
502   return NULL;
503 }
504 
505 /* Throws out all entries marked as unused, by walking through the
506  * filedb and moving all good ones towards the top.
507  */
filedb_cleanup(FILE * fdb)508 static void filedb_cleanup(FILE *fdb)
509 {
510   long oldpos, newpos, temppos;
511   filedb_entry *fdbe = NULL;
512 
513   filedb_readtop(fdb, NULL);    /* Skip DB header  */
514   oldpos = ftell(fdb);
515   fseek(fdb, oldpos, SEEK_SET); /* Go to beginning */
516   while (!feof(fdb)) {          /* Loop until EOF  */
517     fdbe = filedb_getfile(fdb, oldpos, GET_HEADER);     /* Read header     */
518     if (fdbe) {
519       if (fdbe->stat & FILE_UNUSED) {   /* Found dirt!     */
520         free_fdbe(&fdbe);
521         while (!feof(fdb)) {    /* Loop until EOF  */
522           newpos = ftell(fdb);
523           fdbe = filedb_getfile(fdb, newpos, GET_FULL); /* Read next entry */
524           if (!fdbe)
525             break;
526           if (!(fdbe->stat & FILE_UNUSED)) {    /* Not unused?     */
527             temppos = ftell(fdb);
528             filedb_movefile(fdb, oldpos, fdbe); /* Move to top     */
529             oldpos = ftell(fdb);
530             fseek(fdb, temppos, SEEK_SET);
531           }
532           free_fdbe(&fdbe);
533         }
534       } else {
535         free_fdbe(&fdbe);
536         oldpos = ftell(fdb);
537       }
538     }
539   }
540   if (ftruncate(fileno(fdb), oldpos) == -1) {       /* Shorten file    */
541     putlog(LOG_MISC, "*", "FILESYS: Error truncating file.");
542   }
543 }
544 
545 /* Merges empty entries to one big entry, if they directly
546  * follow each other. Does this for the complete DB.
547  * This considerably speeds up several actions performed on
548  * the db.
549  */
filedb_mergeempty(FILE * fdb)550 static void filedb_mergeempty(FILE *fdb)
551 {
552   filedb_entry *fdbe_t, *fdbe_i;
553   int modified;
554 
555   filedb_readtop(fdb, NULL);
556   while (!feof(fdb)) {
557     fdbe_t = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
558     if (fdbe_t) {
559       if (fdbe_t->stat & FILE_UNUSED) {
560         modified = 0;
561         fdbe_i = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
562         while (fdbe_i) {
563           /* Is this entry in use? */
564           if (!(fdbe_i->stat & FILE_UNUSED))
565             break;              /* It is, exit loop. */
566 
567           /* Woohoo, found an empty entry. Append it's space to
568            * our target entry's buffer space.
569            */
570           fdbe_t->buf_len += sizeof(filedb_header) + fdbe_i->buf_len;
571           modified++;
572           free_fdbe(&fdbe_i);
573           /* Get next file entry */
574           fdbe_i = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
575         }
576 
577         /* Did we exit the loop because of a used entry? */
578         if (fdbe_i) {
579           free_fdbe(&fdbe_i);
580           /* Did we find any empty entries before? */
581           if (modified)
582             filedb_updatefile(fdb, fdbe_t->pos, fdbe_t, UPDATE_SIZE);
583           /* ... or because we hit EOF? */
584         } else {
585           /* Truncate trailing empty entries and exit. */
586           if (ftruncate(fileno(fdb), fdbe_t->pos) == -1) {
587             putlog(LOG_MISC, "*", "FILESYS: Error truncating file");
588           }
589           free_fdbe(&fdbe_t);
590           return;
591         }
592       }
593       free_fdbe(&fdbe_t);
594     }
595   }
596 }
597 
598 /* Returns the filedb entry matching the filename 'fn' in
599  * directory 'dir'.
600  */
filedb_getentry(char * dir,char * fn)601 static filedb_entry *filedb_getentry(char *dir, char *fn)
602 {
603   FILE *fdb;
604   filedb_entry *fdbe = NULL;
605 
606   fdb = filedb_open(dir, 0);
607   if (fdb) {
608     filedb_readtop(fdb, NULL);
609     fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
610     filedb_close(fdb);
611   }
612   return fdbe;
613 }
614 
615 /* Initialises a new filedb by writing the db header, etc.
616  */
filedb_initdb(FILE * fdb)617 static void filedb_initdb(FILE *fdb)
618 {
619   filedb_top fdbt;
620 
621   fdbt.version = FILEDB_NEWEST_VER;
622   fdbt.timestamp = now;
623   filedb_writetop(fdb, &fdbt);
624 }
625 
filedb_timestamp(FILE * fdb)626 static void filedb_timestamp(FILE *fdb)
627 {
628   filedb_top fdbt;
629 
630   filedb_readtop(fdb, &fdbt);
631   fdbt.timestamp = time(NULL);
632   filedb_writetop(fdb, &fdbt);
633 }
634 
635 /* Updates the specified filedb in several ways:
636  *
637  * 1. Adds all new files from the directory to the db.
638  * 2. Removes all stale entries from the db.
639  * 3. Optimises the db.
640  */
filedb_update(char * path,FILE * fdb,int sort)641 static void filedb_update(char *path, FILE *fdb, int sort)
642 {
643   struct dirent *dd = NULL;
644   struct stat st;
645   filedb_entry *fdbe = NULL;
646   DIR *dir = NULL;
647   long where = 0;
648   char *name = NULL, *s = NULL;
649 
650   /*
651    * FIRST: make sure every real file is in the database
652    */
653   dir = opendir(path);
654   if (dir == NULL) {
655     putlog(LOG_MISC, "*", FILES_NOUPDATE);
656     return;
657   }
658   dd = readdir(dir);
659   while (dd != NULL) {
660     malloc_strcpy(name, dd->d_name);
661     if (name[0] != '.') {
662       s = nmalloc(strlen(path) + strlen(name) + 2);
663       sprintf(s, "%s/%s", path, name);
664       stat(s, &st);
665       my_free(s);
666       filedb_readtop(fdb, NULL);
667       fdbe = filedb_matchfile(fdb, ftell(fdb), name);
668       if (!fdbe) {
669         /* new file! */
670         fdbe = malloc_fdbe();
671         malloc_strcpy(fdbe->filename, name);
672         malloc_strcpy(fdbe->uploader, botnetnick);
673         fdbe->uploaded = now;
674         fdbe->size = st.st_size;
675         if (S_ISDIR(st.st_mode))
676           fdbe->stat |= FILE_DIR;
677         filedb_addfile(fdb, fdbe);
678       } else if (fdbe->size != st.st_size) {
679         /* update size if needed */
680         fdbe->size = st.st_size;
681         filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
682       }
683       free_fdbe(&fdbe);
684     }
685     dd = readdir(dir);
686   }
687   if (name)
688     my_free(name);
689   closedir(dir);
690 
691   /*
692    * SECOND: make sure every db file is real
693    */
694   filedb_readtop(fdb, NULL);
695   fdbe = filedb_getfile(fdb, ftell(fdb), GET_FILENAME);
696   while (fdbe) {
697     where = ftell(fdb);
698     if (!(fdbe->stat & FILE_UNUSED) && !(fdbe->stat & FILE_ISLINK) &&
699         fdbe->filename) {
700       s = nmalloc(strlen(path) + 1 + strlen(fdbe->filename) + 1);
701       sprintf(s, "%s/%s", path, fdbe->filename);
702       if (stat(s, &st) != 0)
703         /* gone file */
704         filedb_delfile(fdb, fdbe->pos);
705       my_free(s);
706     }
707     free_fdbe(&fdbe);
708     fdbe = filedb_getfile(fdb, where, GET_FILENAME);
709   }
710 
711   /*
712    * THIRD: optimise database
713    *
714    * Instead of sorting, we only clean up the db, because sorting is now
715    * done on-the-fly when we display the file list.
716    */
717   if (sort)
718     filedb_cleanup(fdb);        /* Cleanup DB           */
719   filedb_timestamp(fdb);        /* Write new timestamp  */
720 }
721 
722 /* Converts all slashes to dots. Returns an allocated buffer, so
723  * do not forget to FREE it after use.
724  */
make_point_path(char * path)725 static char *make_point_path(char *path)
726 {
727   char *s2 = NULL, *p = NULL;
728 
729   malloc_strcpy(s2, path);
730   if (s2[strlen(s2) - 1] == '/')
731     s2[strlen(s2) - 1] = 0;     /* remove trailing '/' */
732   p = s2;
733   while (*p++)
734     if (*p == '/')
735       *p = '.';
736   return s2;
737 }
738 
739 /* Opens the filedb responsible to the specified directory.
740  */
filedb_open(char * path,int sort)741 static FILE *filedb_open(char *path, int sort)
742 {
743   char *s, *npath;
744   FILE *fdb;
745   filedb_top fdbt;
746   struct stat st;
747 
748   if (count >= 2)
749     putlog(LOG_MISC, "*", "(@) warning: %d open filedb's", count);
750   npath = nmalloc(strlen(dccdir) + strlen(path) + 1);
751   simple_sprintf(npath, "%s%s", dccdir, path);
752   /* Use alternate filename if requested */
753   if (filedb_path[0]) {
754     char *s2;
755 
756     s2 = make_point_path(path);
757     s = nmalloc(strlen(filedb_path) + strlen(s2) + 8);
758     simple_sprintf(s, "%sfiledb.%s", filedb_path, s2);
759     my_free(s2);
760   } else {
761     s = nmalloc(strlen(npath) + 10);
762     simple_sprintf(s, "%s/.filedb", npath);
763   }
764   fdb = fopen(s, "r+b");
765   if (!fdb) {
766     if (convert_old_files(npath, s)) {
767       fdb = fopen(s, "r+b");
768       if (fdb == NULL) {
769         putlog(LOG_MISC, "*", FILES_NOCONVERT, npath);
770         my_free(s);
771         my_free(npath);
772         return NULL;
773       }
774       lockfile(fdb);
775       filedb_update(npath, fdb, sort);
776       count++;
777       my_free(s);
778       my_free(npath);
779       return fdb;
780     } else {
781       filedb_top fdbt;
782 
783       /* Create new database and fix it up */
784       fdb = fopen(s, "w+b");
785       if (!fdb) {
786         my_free(s);
787         my_free(npath);
788         return NULL;
789       }
790       lockfile(fdb);
791       fdbt.version = FILEDB_NEWEST_VER;
792       fdbt.timestamp = now;
793       filedb_writetop(fdb, &fdbt);
794       filedb_update(npath, fdb, sort);
795       count++;
796       my_free(s);
797       my_free(npath);
798       return fdb;
799     }
800   }
801 
802   lockfile(fdb);                /* Lock it from other bots */
803   filedb_readtop(fdb, &fdbt);
804   if (fdbt.version < FILEDB_NEWEST_VER) {
805     if (!convert_old_db(&fdb, s)) {
806       /* Conversion failed. Unlock file again and error out.
807        * (convert_old_db() could have modified fdb, so check
808        * for fdb != NULL.)
809        */
810       if (fdb)
811         unlockfile(fdb);
812       my_free(npath);
813       my_free(s);
814       return NULL;
815     }
816     filedb_update(npath, fdb, sort);
817   }
818   stat(npath, &st);
819   /* Update filedb if:
820    * + it's been 6 hours since it was last updated
821    * + the directory has been visibly modified since then
822    * (6 hours may be a bit often)
823    */
824   if (sort || ((now - fdbt.timestamp) > (6 * 3600)) ||
825       (fdbt.timestamp < st.st_mtime) || (fdbt.timestamp < st.st_ctime))
826     /* File database isn't up-to-date! */
827     filedb_update(npath, fdb, sort & 1);
828   else if ((now - fdbt.timestamp) > 300)
829     filedb_mergeempty(fdb);
830 
831   count++;
832   my_free(npath);
833   my_free(s);
834   return fdb;
835 }
836 
837 /* Closes the filedb. Also removes the lock and updates the
838  * timestamp.
839  */
filedb_close(FILE * fdb)840 static void filedb_close(FILE *fdb)
841 {
842   filedb_timestamp(fdb);
843   fseeko(fdb, 0, SEEK_END);
844   count--;
845   unlockfile(fdb);
846   fclose(fdb);
847 }
848 
849 /* Adds information for a newly added file. Actually the name
850  * is misleading, as the file is added in filedb_open() and we
851  * only add information in here.
852  */
filedb_add(FILE * fdb,char * filename,char * nick)853 static void filedb_add(FILE *fdb, char *filename, char *nick)
854 {
855   filedb_entry *fdbe = NULL;
856 
857   filedb_readtop(fdb, NULL);
858   /* When the filedb was opened, a record was already created. */
859   fdbe = filedb_matchfile(fdb, ftell(fdb), filename);
860   if (!fdbe)
861     return;
862   my_free(fdbe->uploader);
863   malloc_strcpy(fdbe->uploader, nick);
864   fdbe->uploaded = now;
865   filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
866   free_fdbe(&fdbe);
867 }
868 
869 /* Outputs a sorted list of files/directories matching the mask,
870  * to idx.
871  */
filedb_ls(FILE * fdb,int idx,char * mask,int showall)872 static void filedb_ls(FILE *fdb, int idx, char *mask, int showall)
873 {
874   int ok = 0, cnt = 0, is = 0;
875   char s1[81], *p = NULL;
876   struct flag_record user = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
877   filedb_entry *fdbe = NULL;
878   filelist_t *flist = NULL;
879 
880   flist = filelist_new();
881   filedb_readtop(fdb, NULL);
882   fdbe = filedb_getfile(fdb, ftell(fdb), GET_FULL);
883   while (fdbe) {
884     ok = 1;
885     if (fdbe->stat & FILE_UNUSED)
886       ok = 0;
887     if (ok && (fdbe->stat & FILE_DIR) && fdbe->flags_req) {
888       /* Check permissions */
889       struct flag_record req = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
890 
891       break_down_flags(fdbe->flags_req, &req, NULL);
892       get_user_flagrec(dcc[idx].user, &user, dcc[idx].u.file->chat->con_chan);
893       if (!flagrec_ok(&req, &user)) {
894         ok = 0;
895       }
896     }
897     if (ok)
898       is = 1;
899     if (ok && !wild_match_file(mask, fdbe->filename))
900       ok = 0;
901     if (ok && (fdbe->stat & FILE_HIDDEN) && !(showall))
902       ok = 0;
903     if (ok) {
904       /* Display it! */
905       if (cnt == 0) {
906         dprintf(idx, FILES_LSHEAD1);
907         dprintf(idx, FILES_LSHEAD2);
908       }
909       filelist_add(flist, fdbe->filename);
910       if (fdbe->stat & FILE_DIR) {
911         char *s2 = NULL, *s3 = NULL;
912 
913         /* Too long? */
914         if (strlen(fdbe->filename) > 45) {
915           /* Display the filename on its own line. */
916           s2 = nmalloc(strlen(fdbe->filename) + 3);
917           sprintf(s2, "%s/\n", fdbe->filename);
918           filelist_addout(flist, s2);
919           my_free(s2);
920         } else {
921           s2 = nmalloc(strlen(fdbe->filename) + 2);
922           sprintf(s2, "%s/", fdbe->filename);
923         }
924         /* Note: You have to keep the sprintf and the nmalloc statements
925          *       in sync, i.e. always check that you allocate enough
926          *       memory.
927          */
928         if ((fdbe->flags_req) && (user.global &(USER_MASTER | USER_JANITOR))) {
929           s3 = nmalloc(42 + strlen(s2 ? s2 : "") + 6 +
930                        strlen(FILES_REQUIRES) + strlen(fdbe->flags_req) + 1 +
931                        strlen(fdbe->chan ? fdbe->chan : "") + 1);
932           sprintf(s3, "%-30s <DIR%s>  (%s %s%s%s)\n", s2,
933                   fdbe->stat & FILE_SHARE ?
934                   " SHARE" : "", FILES_REQUIRES, fdbe->flags_req,
935                   fdbe->chan ? " " : "", fdbe->chan ? fdbe->chan : "");
936         } else {
937           s3 = nmalloc(38 + strlen(s2 ? s2 : ""));
938           sprintf(s3, "%-30s <DIR>\n", s2 ? s2 : "");
939         }
940         if (s2)
941           my_free(s2);
942         filelist_addout(flist, s3);
943         my_free(s3);
944       } else {
945         char s2[41], t[50], *s3 = NULL, *s4;
946 
947         s2[0] = 0;
948         if (showall) {
949           if (fdbe->stat & FILE_SHARE)
950             strcat(s2, " (shr)");
951           if (fdbe->stat & FILE_HIDDEN)
952             strcat(s2, " (hid)");
953         }
954         strftime(t, 10, "%d%b%Y", localtime(&fdbe->uploaded));
955         if (fdbe->size < 1024)
956           sprintf(s1, "%5d", fdbe->size);
957         else
958           sprintf(s1, "%4dk", (int) (fdbe->size / 1024));
959         if (fdbe->sharelink)
960           strcpy(s1, "     ");
961         /* Too long? */
962         if (strlen(fdbe->filename) > 30) {
963           s3 = nmalloc(strlen(fdbe->filename) + 2);
964           sprintf(s3, "%s\n", fdbe->filename);
965           filelist_addout(flist, s3);
966           my_free(s3);
967           /* Causes filename to be displayed on its own line */
968         } else
969           malloc_strcpy(s3, fdbe->filename);
970         s4 = nmalloc(69 + strlen(s3 ? s3 : "") + strlen(s1) +
971                      strlen(fdbe->uploader) + strlen(t) + strlen(s2));
972         sprintf(s4, "%-30s %s  %-9s (%s)  %6d%s\n", s3 ? s3 : "", s1,
973                 fdbe->uploader, t, fdbe->gots, s2);
974         if (s3)
975           my_free(s3);
976         filelist_addout(flist, s4);
977         my_free(s4);
978         if (fdbe->sharelink) {
979           s4 = nmalloc(9 + strlen(fdbe->sharelink));
980           sprintf(s4, "   --> %s\n", fdbe->sharelink);
981           filelist_addout(flist, s4);
982           my_free(s4);
983         }
984       }
985       if (fdbe->desc) {
986         p = strchr(fdbe->desc, '\n');
987         while (p != NULL) {
988           *p = 0;
989           if ((fdbe->desc)[0]) {
990             char *sd;
991 
992             sd = nmalloc(strlen(fdbe->desc) + 5);
993             sprintf(sd, "   %s\n", fdbe->desc);
994             filelist_addout(flist, sd);
995             my_free(sd);
996           }
997           strcpy(fdbe->desc, p + 1);
998           p = strchr(fdbe->desc, '\n');
999         }
1000         if ((fdbe->desc)[0]) {
1001           char *sd;
1002 
1003           sd = nmalloc(strlen(fdbe->desc) + 5);
1004           sprintf(sd, "   %s\n", fdbe->desc);
1005           filelist_addout(flist, sd);
1006           my_free(sd);
1007         }
1008       }
1009       cnt++;
1010     }
1011     free_fdbe(&fdbe);
1012     fdbe = filedb_getfile(fdb, ftell(fdb), GET_FULL);
1013   }
1014   if (is == 0)
1015     dprintf(idx, FILES_NOFILES);
1016   else if (cnt == 0)
1017     dprintf(idx, FILES_NOMATCH);
1018   else {
1019     filelist_sort(flist);
1020     filelist_idxshow(flist, idx);
1021     dprintf(idx, "--- %d file%s.\n", cnt, cnt != 1 ? "s" : "");
1022   }
1023   filelist_free(flist);
1024 }
1025 
remote_filereq(int idx,char * from,char * file)1026 static void remote_filereq(int idx, char *from, char *file)
1027 {
1028   char *p = NULL, *what = NULL, *dir = NULL,
1029     *s1 = NULL, *reject = NULL, *s = NULL;
1030   FILE *fdb = NULL;
1031   int i = 0;
1032   filedb_entry *fdbe = NULL;
1033 
1034   malloc_strcpy(what, file);
1035   p = strrchr(what, '/');
1036   if (p) {
1037     *p = 0;
1038     malloc_strcpy(dir, what);
1039     strcpy(what, p + 1);
1040   } else {
1041     malloc_strcpy(dir, "");
1042   }
1043   fdb = filedb_open(dir, 0);
1044   if (!fdb) {
1045     reject = FILES_DIRDNE;
1046   } else {
1047     filedb_readtop(fdb, NULL);
1048     fdbe = filedb_matchfile(fdb, ftell(fdb), what);
1049     filedb_close(fdb);
1050     if (!fdbe) {
1051       reject = FILES_FILEDNE;
1052     } else {
1053       if ((!(fdbe->stat & FILE_SHARE)) ||
1054           (fdbe->stat & (FILE_HIDDEN | FILE_DIR)))
1055         reject = FILES_NOSHARE;
1056       else {
1057         s1 = nmalloc(strlen(dccdir) + strlen(dir) + strlen(what) + 2);
1058         sprintf(s1, "%s%s%s%s", dccdir, dir, dir[0] ? "/" : "", what);
1059         i = raw_dcc_send(s1, "*remote", FILES_REMOTE);
1060         if (i > 0) {
1061           reject = FILES_SENDERR;
1062         }
1063         my_free(s1);
1064       }
1065       free_fdbe(&fdbe);
1066     }
1067   }
1068   s1 = nmalloc(strlen(botnetnick) + strlen(dir) + strlen(what) + 3);
1069   simple_sprintf(s1, "%s:%s/%s", botnetnick, dir, what);
1070   if (reject) {
1071     botnet_send_filereject(idx, s1, from, reject);
1072     my_free(s1);
1073     my_free(what);
1074     my_free(dir);
1075     return;
1076   }
1077   /* Grab info from dcc struct and bounce real request across net */
1078   i = dcc_total - 1;
1079 #ifdef IPV6
1080   s = nmalloc(INET6_ADDRSTRLEN);
1081   getdccaddr(&dcc[i].sockname, s, INET6_ADDRSTRLEN);
1082 #else
1083   s = nmalloc(INET_ADDRSTRLEN);
1084   getdccaddr(&dcc[i].sockname, s, INET_ADDRSTRLEN);
1085 #endif
1086   simple_sprintf(s, "%s %u %d", s, dcc[i].port, dcc[i].u.xfer->length);
1087   botnet_send_filesend(idx, s1, from, s);
1088   putlog(LOG_FILES, "*", FILES_REMOTEREQ, dir, dir[0] ? "/" : "", what);
1089   my_free(s1);
1090   my_free(s);
1091   my_free(what);
1092   my_free(dir);
1093 }
1094 
1095 
1096 /*
1097  *    Tcl functions
1098  */
1099 
filedb_getdesc(char * dir,char * fn,char ** desc)1100 static void filedb_getdesc(char *dir, char *fn, char **desc)
1101 {
1102   filedb_entry *fdbe = NULL;
1103 
1104   fdbe = filedb_getentry(dir, fn);
1105   if (fdbe) {
1106     if (fdbe->desc) {
1107       *desc = nmalloc(strlen(fdbe->desc) + 1);
1108       strcpy(*desc, fdbe->desc);
1109     }
1110     free_fdbe(&fdbe);
1111   } else
1112     *desc = NULL;
1113 }
1114 
filedb_getowner(char * dir,char * fn,char ** owner)1115 static void filedb_getowner(char *dir, char *fn, char **owner)
1116 {
1117   filedb_entry *fdbe = NULL;
1118 
1119   fdbe = filedb_getentry(dir, fn);
1120   if (fdbe) {
1121     *owner = nmalloc(strlen(fdbe->uploader) + 1);
1122     strcpy(*owner, fdbe->uploader);
1123     free_fdbe(&fdbe);
1124   } else
1125     *owner = NULL;
1126 }
1127 
filedb_getgots(char * dir,char * fn)1128 static int filedb_getgots(char *dir, char *fn)
1129 {
1130   filedb_entry *fdbe = NULL;
1131   int gots = 0;
1132 
1133   fdbe = filedb_getentry(dir, fn);
1134   if (fdbe) {
1135     gots = fdbe->gots;
1136     free_fdbe(&fdbe);
1137   }
1138   return gots;
1139 }
1140 
filedb_setdesc(char * dir,char * fn,char * desc)1141 static void filedb_setdesc(char *dir, char *fn, char *desc)
1142 {
1143   filedb_entry *fdbe = NULL;
1144   FILE *fdb = NULL;
1145 
1146   fdb = filedb_open(dir, 0);
1147   if (!fdb)
1148     return;
1149   filedb_readtop(fdb, NULL);
1150   fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
1151   if (fdbe) {
1152     my_free(fdbe->desc);
1153     malloc_strcpy(fdbe->desc, desc);
1154     filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
1155     free_fdbe(&fdbe);
1156   }
1157   filedb_close(fdb);
1158 }
1159 
filedb_setowner(char * dir,char * fn,char * owner)1160 static void filedb_setowner(char *dir, char *fn, char *owner)
1161 {
1162   filedb_entry *fdbe = NULL;
1163   FILE *fdb = NULL;
1164 
1165   fdb = filedb_open(dir, 0);
1166   if (!fdb)
1167     return;
1168   filedb_readtop(fdb, NULL);
1169   fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
1170   if (fdbe) {
1171     my_free(fdbe->uploader);
1172     malloc_strcpy(fdbe->uploader, owner);
1173     filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
1174     free_fdbe(&fdbe);
1175   }
1176   filedb_close(fdb);
1177 }
1178 
filedb_setlink(char * dir,char * fn,char * link)1179 static void filedb_setlink(char *dir, char *fn, char *link)
1180 {
1181   filedb_entry *fdbe = NULL;
1182   FILE *fdb = NULL;
1183 
1184   fdb = filedb_open(dir, 0);
1185   if (!fdb)
1186     return;
1187   filedb_readtop(fdb, NULL);
1188   fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
1189   if (fdbe) {
1190     /* Change existing one? */
1191     if ((fdbe->stat & FILE_DIR) || !fdbe->sharelink)
1192       return;
1193     if (!link || !link[0])
1194       filedb_delfile(fdb, fdbe->pos);
1195     else {
1196       my_free(fdbe->sharelink);
1197       malloc_strcpy(fdbe->sharelink, link);
1198       filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
1199     }
1200     free_fdbe(&fdbe);
1201     return;
1202   }
1203 
1204   fdbe = malloc_fdbe();
1205   malloc_strcpy(fdbe->uploader, botnetnick);
1206   malloc_strcpy(fdbe->filename, fn);
1207   malloc_strcpy(fdbe->sharelink, link);
1208   fdbe->uploaded = now;
1209   filedb_addfile(fdb, fdbe);
1210   free_fdbe(&fdbe);
1211   filedb_close(fdb);
1212 }
1213 
filedb_getlink(char * dir,char * fn,char ** link)1214 static void filedb_getlink(char *dir, char *fn, char **link)
1215 {
1216   filedb_entry *fdbe = NULL;
1217 
1218   fdbe = filedb_getentry(dir, fn);
1219   if (fdbe && (!(fdbe->stat & FILE_DIR))) {
1220     malloc_strcpy(*link, fdbe->sharelink);
1221   } else
1222     *link = NULL;
1223   if (fdbe)
1224     free_fdbe(&fdbe);
1225   return;
1226 }
1227 
filedb_getfiles(Tcl_Interp * irp,char * dir)1228 static void filedb_getfiles(Tcl_Interp *irp, char *dir)
1229 {
1230   FILE *fdb;
1231   filedb_entry *fdbe;
1232 
1233   fdb = filedb_open(dir, 0);
1234   if (!fdb)
1235     return;
1236   filedb_readtop(fdb, NULL);
1237   while (!feof(fdb)) {
1238     fdbe = filedb_getfile(fdb, ftell(fdb), GET_FILENAME);
1239     if (fdbe) {
1240       if (!(fdbe->stat & (FILE_DIR | FILE_UNUSED)))
1241         Tcl_AppendElement(irp, fdbe->filename);
1242       free_fdbe(&fdbe);
1243     }
1244   }
1245   filedb_close(fdb);
1246 }
1247 
filedb_getdirs(Tcl_Interp * irp,char * dir)1248 static void filedb_getdirs(Tcl_Interp *irp, char *dir)
1249 {
1250   FILE *fdb;
1251   filedb_entry *fdbe;
1252 
1253   fdb = filedb_open(dir, 0);
1254   if (!fdb)
1255     return;
1256   filedb_readtop(fdb, NULL);
1257   while (!feof(fdb)) {
1258     fdbe = filedb_getfile(fdb, ftell(fdb), GET_FILENAME);
1259     if (fdbe) {
1260       if ((!(fdbe->stat & FILE_UNUSED)) && (fdbe->stat & FILE_DIR))
1261         Tcl_AppendElement(irp, fdbe->filename);
1262       free_fdbe(&fdbe);
1263     }
1264   }
1265   filedb_close(fdb);
1266 }
1267 
filedb_change(char * dir,char * fn,int what)1268 static void filedb_change(char *dir, char *fn, int what)
1269 {
1270   FILE *fdb;
1271   filedb_entry *fdbe;
1272   int changed = 0;
1273 
1274   fdb = filedb_open(dir, 0);
1275   if (!fdb)
1276     return;
1277   filedb_readtop(fdb, NULL);
1278   fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
1279   if (fdbe) {
1280     if (!(fdbe->stat & FILE_DIR)) {
1281       switch (what) {
1282       case FILEDB_SHARE:
1283         fdbe->stat |= FILE_SHARE;
1284         break;
1285       case FILEDB_UNSHARE:
1286         fdbe->stat &= ~FILE_SHARE;
1287         break;
1288       }
1289       changed = 1;
1290     }
1291     switch (what) {
1292     case FILEDB_HIDE:
1293       fdbe->stat |= FILE_HIDDEN;
1294       changed = 1;
1295       break;
1296     case FILEDB_UNHIDE:
1297       fdbe->stat &= ~FILE_HIDDEN;
1298       changed = 1;
1299       break;
1300     }
1301     if (changed)
1302       filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
1303     free_fdbe(&fdbe);
1304   }
1305   filedb_close(fdb);
1306 }
1307