]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/tree.c
Replace explicit checks for "/" with calls to IsPathSeparator, strchr with first_path...
[bacula/bacula] / bacula / src / lib / tree.c
1 /*
2  * Directory tree build/traverse routines
3  *
4  *    Kern Sibbald, June MMII
5  *
6 */
7 /*
8    Bacula® - The Network Backup Solution
9
10    Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
11
12    The main author of Bacula is Kern Sibbald, with contributions from
13    many others, a complete list can be found in the file AUTHORS.
14    This program is Free Software; you can redistribute it and/or
15    modify it under the terms of version two of the GNU General Public
16    License as published by the Free Software Foundation plus additions
17    that are listed in the file LICENSE.
18
19    This program is distributed in the hope that it will be useful, but
20    WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22    General Public License for more details.
23
24    You should have received a copy of the GNU General Public License
25    along with this program; if not, write to the Free Software
26    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27    02110-1301, USA.
28
29    Bacula® is a registered trademark of John Walker.
30    The licensor of Bacula is the Free Software Foundation Europe
31    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
32    Switzerland, email:ftf@fsfeurope.org.
33 */
34
35
36 #include "bacula.h"
37 #include "findlib/find.h"
38
39
40 /* Forward referenced subroutines */
41 static TREE_NODE *search_and_insert_tree_node(char *fname, int type,
42                TREE_ROOT *root, TREE_NODE *parent);
43 static char *tree_alloc(TREE_ROOT *root, int size);
44
45 /*
46  * NOTE !!!!! we turn off Debug messages for performance reasons.
47  */
48 #undef Dmsg0
49 #undef Dmsg1
50 #undef Dmsg2
51 #undef Dmsg3
52 #define Dmsg0(n,f)
53 #define Dmsg1(n,f,a1)
54 #define Dmsg2(n,f,a1,a2)
55 #define Dmsg3(n,f,a1,a2,a3)
56
57 /*
58  * This subroutine gets a big buffer.
59  */
60 static void malloc_buf(TREE_ROOT *root, int size)
61 {
62    struct s_mem *mem;
63
64    mem = (struct s_mem *)malloc(size);
65    root->total_size += size;
66    root->blocks++;
67    mem->next = root->mem;
68    root->mem = mem;
69    mem->mem = mem->first;
70    mem->rem = (char *)mem + size - mem->mem;
71    Dmsg2(200, "malloc buf size=%d rem=%d\n", size, mem->rem);
72 }
73
74
75 /*
76  * Note, we allocate a big buffer in the tree root
77  *  from which we allocate nodes. This runs more
78  *  than 100 times as fast as directly using malloc()
79  *  for each of the nodes.
80  */
81 TREE_ROOT *new_tree(int count)
82 {
83    TREE_ROOT *root;
84    uint32_t size;
85
86    if (count < 1000) {                /* minimum tree size */
87       count = 1000;
88    }
89    root = (TREE_ROOT *)malloc(sizeof(TREE_ROOT));
90    memset(root, 0, sizeof(TREE_ROOT));
91    /* Assume filename + node  = 40 characters average length */
92    size = count * (BALIGN(sizeof(TREE_NODE)) + 40);
93    if (count > 1000000 || size > 10000000) {
94       size = 10000000;
95    }
96    Dmsg2(400, "count=%d size=%d\n", count, size);
97    malloc_buf(root, size);
98    root->cached_path_len = -1;
99    root->cached_path = get_pool_memory(PM_FNAME);
100    root->type = TN_ROOT;
101    root->fname = "";
102    return root;
103 }
104
105 /*
106  * Create a new tree node.
107  */
108 static TREE_NODE *new_tree_node(TREE_ROOT *root)
109 {
110    TREE_NODE *node;
111    int size = sizeof(TREE_NODE);
112    node = (TREE_NODE *)tree_alloc(root, size);
113    memset(node, 0, size);
114    return node;
115 }
116
117 /*
118  * This routine can be called to release the
119  *  previously allocated tree node.
120  */
121 static void free_tree_node(TREE_ROOT *root)
122 {
123    int asize = BALIGN(sizeof(TREE_NODE));
124    root->mem->rem += asize;
125    root->mem->mem -= asize;
126 }
127
128
129
130 /*
131  * Allocate bytes for filename in tree structure.
132  *  Keep the pointers properly aligned by allocating
133  *  sizes that are aligned.
134  */
135 static char *tree_alloc(TREE_ROOT *root, int size)
136 {
137    char *buf;
138    int asize = BALIGN(size);
139
140    if (root->mem->rem < asize) {
141       uint32_t mb_size;
142       if (root->total_size >= 1000000) {
143          mb_size = 1000000;
144       } else {
145          mb_size = 100000;
146       }
147       malloc_buf(root, mb_size);
148    }
149    root->mem->rem -= asize;
150    buf = root->mem->mem;
151    root->mem->mem += asize;
152    return buf;
153 }
154
155
156 /* This routine frees the whole tree */
157 void free_tree(TREE_ROOT *root)
158 {
159    struct s_mem *mem, *rel;
160
161    for (mem=root->mem; mem; ) {
162       rel = mem;
163       mem = mem->next;
164       free(rel);
165    }
166    if (root->cached_path) {
167       free_pool_memory(root->cached_path);
168       root->cached_path = NULL;
169    }
170    Dmsg2(400, "Total size=%u blocks=%d\n", root->total_size, root->blocks);
171    free(root);
172    return;
173 }
174
175
176 /*
177  * Insert a node in the tree. This is the main subroutine
178  *   called when building a tree.
179  *
180  */
181 TREE_NODE *insert_tree_node(char *path, char *fname, int type,
182                             TREE_ROOT *root, TREE_NODE *parent)
183 {
184    char *p, *q;
185    int path_len = strlen(path);
186    TREE_NODE *node;
187
188    Dmsg1(100, "insert_tree_node: %s\n", path);
189    /*
190     * If trailing slash on path, strip it
191     */
192    if (path_len > 0) {
193       q = path + path_len - 1;
194       if (IsPathSeparator(*q)) {
195          *q = 0;                      /* strip trailing slash */
196       } else {
197          q = NULL;                    /* no trailing slash */
198       }
199    } else {
200       q = NULL;                       /* no trailing slash */
201    }
202    /* If no filename, strip last component of path as "filename" */
203    if (*fname == 0) {
204       p = (char *)last_path_separator(path);  /* separate path and filename */
205       if (p) {
206          fname = p + 1;               /* set new filename */
207          *p = '\0';                   /* terminate new path */
208       }
209    } else {
210       p = NULL;
211    }
212    if (*fname) {
213       if (!parent) {                  /* if no parent, we need to make one */
214          Dmsg1(100, "make_tree_path for %s\n", path);
215          path_len = strlen(path);     /* get new length */
216          if (path_len == root->cached_path_len &&
217              strcmp(path, root->cached_path) == 0) {
218             parent = root->cached_parent;
219          } else {
220             root->cached_path_len = path_len;
221             pm_strcpy(&root->cached_path, path);
222             parent = make_tree_path(path, root);
223             root->cached_parent = parent;
224          }
225          Dmsg1(100, "parent=%s\n", parent->fname);
226       }
227    } else {
228       fname = path;
229       if (!parent) {
230          parent = (TREE_NODE *)root;
231          type = TN_DIR_NLS;
232       }
233       Dmsg1(100, "No / found: %s\n", path);
234    }
235
236    node = search_and_insert_tree_node(fname, 0, root, parent);
237    if (q) {                           /* if trailing slash on entry */
238       *q = '/';                       /*  restore it */
239    }
240    if (p) {                           /* if slash in path trashed */
241       *p = '/';                       /* restore full path */
242    }
243    return node;
244 }
245
246 /*
247  * Ensure that all appropriate nodes for a full path exist in
248  *  the tree.
249  */
250 TREE_NODE *make_tree_path(char *path, TREE_ROOT *root)
251 {
252    TREE_NODE *parent, *node;
253    char *fname, *p;
254    int type = TN_NEWDIR;
255
256    Dmsg1(100, "make_tree_path: %s\n", path);
257    if (*path == 0) {
258       Dmsg0(100, "make_tree_path: parent=*root*\n");
259       return (TREE_NODE *)root;
260    }
261    p = (char *)last_path_separator(path);           /* get last dir component of path */
262    if (p) {
263       fname = p + 1;
264       *p = 0;                         /* terminate path */
265       parent = make_tree_path(path, root);
266       *p = '/';                       /* restore full name */
267    } else {
268       fname = path;
269       parent = (TREE_NODE *)root;
270       type = TN_DIR_NLS;
271    }
272    node = search_and_insert_tree_node(fname, type, root, parent);
273    return node;
274 }
275
276 static int node_compare(void *item1, void *item2)
277 {
278    TREE_NODE *tn1 = (TREE_NODE *)item1;
279    TREE_NODE *tn2 = (TREE_NODE *)item2;
280    if (tn1->fname[0] > tn2->fname[0]) {
281       return 1;
282    } else if (tn1->fname[0] < tn2->fname[0]) {
283       return -1;
284    }
285    return strcmp(tn1->fname, tn2->fname);
286 }
287
288 /*
289  *  See if the fname already exists. If not insert a new node for it.
290  */
291 static TREE_NODE *search_and_insert_tree_node(char *fname, int type,
292                TREE_ROOT *root, TREE_NODE *parent)
293 {
294    TREE_NODE *node, *found_node;
295    node = new_tree_node(root);
296    node->fname = fname;
297    found_node = (TREE_NODE *)parent->child.binary_insert(node, node_compare);
298    if (found_node != node) {          /* already in list */
299       free_tree_node(root);           /* free node allocated above */
300       found_node->inserted = false;
301       return found_node;
302    }
303    /* It was not found, but is now inserted */
304    node->fname_len = strlen(fname);
305    node->fname = tree_alloc(root, node->fname_len + 1);
306    strcpy(node->fname, fname);
307    node->parent = parent;
308    node->type = type;
309
310    /* Maintain a linear chain of nodes */
311    if (!root->first) {
312       root->first = node;
313       root->last = node;
314    } else {
315       root->last->next = node;
316       root->last = node;
317    }
318    node->inserted = true;             /* inserted into tree */
319    return node;
320
321 }
322
323 int tree_getpath(TREE_NODE *node, char *buf, int buf_size)
324 {
325    if (!node) {
326       buf[0] = 0;
327       return 1;
328    }
329    tree_getpath(node->parent, buf, buf_size);
330    /*
331     * Fixup for Win32. If we have a Win32 directory and
332     *    there is only a / in the buffer, remove it since
333     *    win32 names don't generally start with /
334     */
335    if (node->type == TN_DIR_NLS && IsPathSeparator(buf[0]) && buf[1] == '\0') {
336       buf[0] = '\0';
337    }
338    bstrncat(buf, node->fname, buf_size);
339    /* Add a slash for all directories unless we are at the root,
340     *  also add a slash to a soft linked file if it has children
341     *  i.e. it is linked to a directory.
342     */
343    if ((node->type != TN_FILE && !(IsPathSeparator(buf[0]) && buf[1] == '\0')) ||
344        (node->soft_link && tree_node_has_child(node))) {
345       bstrncat(buf, "/", buf_size);
346    }
347    return 1;
348 }
349
350 /*
351  * Change to specified directory
352  */
353 TREE_NODE *tree_cwd(char *path, TREE_ROOT *root, TREE_NODE *node)
354 {
355    if (strcmp(path, ".") == 0) {
356       return node;
357    }
358    /* Handle relative path */
359    if (strncmp(path, "..", 2) == 0 && (path[2] == '/' || path[2] == 0)) {
360       TREE_NODE *parent = node->parent ? node->parent : node;
361       if (path[2] == 0) { 
362          return parent;
363       } else {
364          return tree_cwd(path+3, root, parent);
365       }
366    }
367    if (IsPathSeparator(path[0])) {
368       Dmsg0(100, "Doing absolute lookup.\n");
369       return tree_relcwd(path+1, root, (TREE_NODE *)root);
370    }
371    Dmsg0(100, "Doing relative lookup.\n");
372    return tree_relcwd(path, root, node);
373 }
374
375
376 /*
377  * Do a relative cwd -- i.e. relative to current node rather than root node
378  */
379 TREE_NODE *tree_relcwd(char *path, TREE_ROOT *root, TREE_NODE *node)
380 {
381    char *p;
382    int len;
383    TREE_NODE *cd;
384
385    if (*path == 0) {
386       return node;
387    }
388    /* Check the current segment only */
389    if ((p = first_path_separator(path)) != NULL) {
390       len = p - path;
391    } else {
392       len = strlen(path);
393    }
394    Dmsg2(100, "tree_relcwd: len=%d path=%s\n", len, path);
395    foreach_child(cd, node) {
396       Dmsg1(100, "tree_relcwd: test cd=%s\n", cd->fname);
397       if (cd->fname[0] == path[0] && len == (int)strlen(cd->fname)
398           && strncmp(cd->fname, path, len) == 0) {
399          break;
400       }
401    }
402    if (!cd || (cd->type == TN_FILE && !tree_node_has_child(cd))) {
403       return NULL;
404    }
405    if (!p) {
406       Dmsg0(100, "tree_relcwd: no more to lookup. found.\n");
407       return cd;
408    }
409    Dmsg2(100, "recurse tree_relcwd with path=%s, cd=%s\n", p+1, cd->fname);
410    /* Check the next segment if any */
411    return tree_relcwd(p+1, root, cd);
412 }
413
414
415
416 #ifdef BUILD_TEST_PROGRAM
417
418 void FillDirectoryTree(char *path, TREE_ROOT *root, TREE_NODE *parent);
419
420 static uint32_t FileIndex = 0;
421 /*
422  * Simple test program for tree routines
423  */
424 int main(int argc, char *argv[])
425 {
426     TREE_ROOT *root;
427     TREE_NODE *node;
428     char buf[MAXPATHLEN];
429
430     root = new_tree();
431     root->fname = tree_alloc(root, 1);
432     *root->fname = 0;
433     root->fname_len = 0;
434
435     FillDirectoryTree("/home/kern/bacula/k", root, NULL);
436
437     for (node = first_tree_node(root); node; node=next_tree_node(node)) {
438        tree_getpath(node, buf, sizeof(buf));
439        Dmsg2(100, "%d: %s\n", node->FileIndex, buf);
440     }
441
442     node = (TREE_NODE *)root;
443     Pmsg0(000, "doing cd /home/kern/bacula/k/techlogs\n");
444     node = tree_cwd("/home/kern/bacula/k/techlogs", root, node);
445     if (node) {
446        tree_getpath(node, buf, sizeof(buf));
447        Dmsg2(100, "findex=%d: cwd=%s\n", node->FileIndex, buf);
448     }
449
450     Pmsg0(000, "doing cd /home/kern/bacula/k/src/testprogs\n");
451     node = tree_cwd("/home/kern/bacula/k/src/testprogs", root, node);
452     if (node) {
453        tree_getpath(node, buf, sizeof(buf));
454        Dmsg2(100, "findex=%d: cwd=%s\n", node->FileIndex, buf);
455     } else {
456        Dmsg0(100, "testprogs not found.\n");
457     }
458
459     free_tree((TREE_NODE *)root);
460
461     return 0;
462 }
463
464 void FillDirectoryTree(char *path, TREE_ROOT *root, TREE_NODE *parent)
465 {
466    TREE_NODE *newparent = NULL;
467    TREE_NODE *node;
468    struct stat statbuf;
469    DIR *dp;
470    struct dirent *dir;
471    char pathbuf[MAXPATHLEN];
472    char file[MAXPATHLEN];
473    int type;
474    int i;
475
476    Dmsg1(100, "FillDirectoryTree: %s\n", path);
477    dp = opendir(path);
478    if (!dp) {
479       return;
480    }
481    while ((dir = readdir(dp))) {
482       if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) {
483          continue;
484       }
485       bstrncpy(file, dir->d_name, sizeof(file));
486       snprintf(pathbuf, MAXPATHLEN-1, "%s/%s", path, file);
487       if (lstat(pathbuf, &statbuf) < 0) {
488          printf("lstat() failed. ERR=%s\n", strerror(errno));
489          continue;
490       }
491 //      printf("got file=%s, pathbuf=%s\n", file, pathbuf);
492       type = TN_FILE;
493       if (S_ISLNK(statbuf.st_mode))
494          type =  TN_FILE;  /* link */
495       else if (S_ISREG(statbuf.st_mode))
496          type = TN_FILE;
497       else if (S_ISDIR(statbuf.st_mode)) {
498          type = TN_DIR;
499       } else if (S_ISCHR(statbuf.st_mode))
500          type = TN_FILE; /* char dev */
501       else if (S_ISBLK(statbuf.st_mode))
502          type = TN_FILE; /* block dev */
503       else if (S_ISFIFO(statbuf.st_mode))
504          type = TN_FILE; /* fifo */
505       else if (S_ISSOCK(statbuf.st_mode))
506          type = TN_FILE; /* sock */
507       else {
508          type = TN_FILE;
509          printf("Unknown file type: 0x%x\n", statbuf.st_mode);
510       }
511
512       Dmsg2(100, "Doing: %d %s\n", type, pathbuf);
513       node = new_tree_node(root);
514       node->FileIndex = ++FileIndex;
515       parent = insert_tree_node(pathbuf, node, root, parent);
516       if (S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) {
517          Dmsg2(100, "calling fill. pathbuf=%s, file=%s\n", pathbuf, file);
518          FillDirectoryTree(pathbuf, root, node);
519       }
520    }
521    closedir(dp);
522 }
523
524 #ifndef MAXPATHLEN
525 #define MAXPATHLEN 2000
526 #endif
527
528 void print_tree(char *path, TREE_NODE *tree)
529 {
530    char buf[MAXPATHLEN];
531    char *termchr;
532
533    if (!tree) {
534       return;
535    }
536    switch (tree->type) {
537    case TN_DIR_NLS:
538    case TN_DIR:
539    case TN_NEWDIR:
540       termchr = "/";
541       break;
542    case TN_ROOT:
543    case TN_FILE:
544    default:
545       termchr = "";
546       break;
547    }
548    Pmsg3(-1, "%s/%s%s\n", path, tree->fname, termchr);
549    switch (tree->type) {
550    case TN_FILE:
551    case TN_NEWDIR:
552    case TN_DIR:
553    case TN_DIR_NLS:
554       bsnprintf(buf, sizeof(buf), "%s/%s", path, tree->fname);
555       print_tree(buf, first_child(tree));
556       break;
557    case TN_ROOT:
558       print_tree(path, first_child(tree));
559       break;
560    default:
561       Pmsg1(000, "Unknown node type %d\n", tree->type);
562    }
563    print_tree(path, tree->sibling_);
564    return;
565 }
566
567 #endif