SFI: compile sfifilecrawler as C++ source
[stwbeast.git] / sfi / sfifilecrawler.cc
1 /* SFI - Synthesis Fusion Kit Interface
2  * Copyright (C) 2003 Tim Janik
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * A copy of the GNU Lesser General Public License should ship along
15  * with this library; if not, see http://www.gnu.org/copyleft/.
16  */
17 #include "sfifilecrawler.h"
18 #include "sfiprimitives.h"
19 #include "sfiwrapper.h"
20 #include "topconfig.h"
21 #include <string.h>
22 #include <sys/types.h>
23 #include <dirent.h>
24
25 #define INCREMENTAL_RESULTS 1
26
27
28 /* --- prototypes --- */
29 static gchar*   get_user_home (const gchar *user,
30                                gboolean     use_fallbacks);
31
32
33 /* --- variables --- */
34 static gchar *init_cwd = NULL;
35
36
37 /* --- functions --- */
38 void
39 _sfi_init_file_crawler (void)
40 {
41   init_cwd = g_get_current_dir ();
42   if (!init_cwd || !g_path_is_absolute (init_cwd))
43     {
44       g_free (init_cwd);
45       init_cwd = g_get_tmp_dir ();
46     }
47   if (!init_cwd || !g_path_is_absolute (init_cwd))
48     {
49       g_free (init_cwd);
50       init_cwd = g_strdup (G_DIR_SEPARATOR_S);
51     }
52 }
53
54 /**
55  * Create a new file crawler. A file crawler collects all files matching
56  * a given search path and file test.
57  * sfi_file_crawler_crawl() needs to be called as long as
58  * sfi_file_crawler_needs_crawl() returns TRUE to collect all
59  * matching files.
60  */
61 SfiFileCrawler*
62 sfi_file_crawler_new (void)
63 {
64   SfiFileCrawler *self = g_new0 (SfiFileCrawler, 1);
65   self->cwd = g_strdup (init_cwd);
66   self->ptest = G_FILE_TEST_EXISTS;
67   return self;
68 }
69
70 /**
71  * @param self  valid SfiFileCrawler
72  * RETURNS: newly allocated string containig resulting filename
73  *
74  * Fetch next result if any or NULL.
75  */
76 char*
77 sfi_file_crawler_pop (SfiFileCrawler *self)
78 {
79   g_return_val_if_fail (self != NULL, NULL);
80   return (char*) sfi_ring_pop_head (&self->results);
81 }
82
83 /**
84  * @param self  valid SfiFileCrawler
85  * @param cwd   absolute path
86  *
87  * Set the path to be assumed the current working directory.
88  */
89 void
90 sfi_file_crawler_set_cwd (SfiFileCrawler *self,
91                           const gchar    *cwd)
92 {
93   g_return_if_fail (self != NULL);
94   g_return_if_fail (cwd != NULL && g_path_is_absolute (cwd));
95
96   g_free (self->cwd);
97   self->cwd = g_strdup (cwd);
98 }
99
100 /**
101  * @param self  valid SfiFileCrawler
102  * @param tests GFileTest test flags
103  *
104  * By default, results returned by @a self are only tested
105  * for existence. If additional file tests have to be met
106  * by the results, they can be set by this function.
107  */
108 void
109 sfi_file_crawler_add_tests (SfiFileCrawler *self,
110                             GFileTest       tests)
111 {
112   g_return_if_fail (self != NULL);
113
114   self->ptest = GFileTest (self->ptest | tests);
115 }
116
117 /**
118  * @param self  valid SfiFileCrawler
119  * @param pattern_paths colon (semicolon under win32) seperated search path
120  * @param file_pattern  wildcard pattern for file names
121  * @return              a singly linked list with newly allocated strings
122  *
123  * This function takes a search path (possibly containing wildcards)
124  * and adds them to the file crawlers search list.
125  * If @a file_pattern is non NULL, it is appended to each directory
126  * element extracted from @a pattern_paths, before attempting file
127  * system searches.
128  * sfi_file_crawler_needs_crawl() may return TRUE after calling
129  * this function.
130  */
131 void
132 sfi_file_crawler_add_search_path (SfiFileCrawler *self,
133                                   const gchar    *pattern_paths,
134                                   const gchar    *file_pattern)
135 {
136   g_return_if_fail (self != NULL);
137   if (pattern_paths)
138     {
139       const gchar *sep, *p = pattern_paths;
140       sep = strchr (p, G_SEARCHPATH_SEPARATOR);
141       while (sep)
142         {
143           if (sep > p)
144             {
145               gchar *path = g_strndup (p, sep - p);
146               if (file_pattern)
147                 {
148                   gchar *tmp = g_strconcat (path, G_DIR_SEPARATOR_S, file_pattern, NULL);
149                   g_free (path);
150                   path = tmp;
151                 }
152               self->dpatterns = sfi_ring_append (self->dpatterns, path);
153             }
154           p = sep + 1;
155           sep = strchr (p, G_SEARCHPATH_SEPARATOR);
156         }
157       if (*p)
158         {
159           gchar *path = g_strconcat (p, file_pattern ? G_DIR_SEPARATOR_S : NULL, file_pattern, NULL);
160           self->dpatterns = sfi_ring_append (self->dpatterns, path);
161         }
162     }
163 }
164
165 static void
166 file_crawler_queue_readdir (SfiFileCrawler *self,
167                             const gchar    *base_dir,
168                             const gchar    *file_pattern,
169                             GFileTest       file_test)
170 {
171   g_assert (self->dhandle == NULL);
172   
173   if (strchr (file_pattern, '?') || strchr (file_pattern, '*'))
174     {
175       gchar *s = g_strconcat (base_dir, G_DIR_SEPARATOR_S, NULL);
176       self->dhandle = opendir (s);
177       g_free (s);
178       if (self->dhandle)
179         {
180           self->pspec = g_pattern_spec_new (file_pattern);
181           self->base_dir = g_strdup (base_dir);
182           self->ftest = file_test;
183         }
184     }
185   else
186     {
187       gchar *s;
188       if (strcmp (file_pattern, ".") == 0)
189         s = g_strdup (base_dir);
190       else
191         s = g_strconcat (base_dir, G_DIR_SEPARATOR_S, file_pattern, NULL);
192       if (!g_file_test_all (s, file_test))
193         g_free (s);
194       else
195         self->accu = sfi_ring_prepend (self->accu, s);
196     }
197 }
198
199 static void     /* self->accu is implicit in/out arg */
200 file_crawler_crawl_readdir (SfiFileCrawler *self)
201 {
202   DIR *dd = (DIR*) self->dhandle;
203   struct dirent *d_entry = readdir (dd);
204
205   if (d_entry)
206     {
207       if (!(d_entry->d_name[0] == '.' && d_entry->d_name[1] == 0) &&
208           !(d_entry->d_name[0] == '.' && d_entry->d_name[1] == '.' && d_entry->d_name[2] == 0) &&
209           g_pattern_match_string (self->pspec, d_entry->d_name))
210         {
211           gchar *str = g_strconcat (self->base_dir, G_DIR_SEPARATOR_S, d_entry->d_name, NULL);
212           if (self->ftest && !g_file_test_all (str, self->ftest))
213             g_free (str);
214           else
215             self->accu = sfi_ring_prepend (self->accu, str);
216         }
217     }
218   else
219     {
220       g_pattern_spec_free (self->pspec);
221       self->pspec = NULL;
222       g_free (self->base_dir);
223       self->base_dir = NULL;
224       closedir (dd);
225       self->dhandle = NULL;
226       self->ftest = GFileTest (0);
227     }
228 }
229
230 static void
231 file_crawler_queue_abs_file_path (SfiFileCrawler *self,
232                                   const gchar    *path_pattern,
233                                   GFileTest       file_test)
234 {
235   gchar *sep, *p, *freeme, *tmp;
236
237   g_assert (self->pdqueue == NULL && self->dlist == NULL && self->accu == NULL);
238
239   freeme = p = g_strdup (path_pattern);
240   
241   /* seperate root */
242   sep = strchr (p, G_DIR_SEPARATOR);
243   g_return_if_fail (sep != NULL);       /* absolute paths must have a seperator */
244   *sep++ = 0;
245   
246   /* check root existance */
247   tmp = g_strconcat (p, G_DIR_SEPARATOR_S, NULL);
248   if (!g_file_test_all (tmp, G_FILE_TEST_IS_DIR))
249     {
250       g_free (tmp);
251       g_free (freeme);
252       return;
253     }
254   g_free (tmp);
255   
256   /* add root to dir list ("" on unix) */
257   self->dlist = sfi_ring_prepend (self->dlist, g_strdup (p));
258   
259   /* compress multiple dir seperators */
260   while (*sep == G_DIR_SEPARATOR)
261     sep++;
262   
263   /* add remaining segments to queue */
264   p = sep;
265   sep = strchr (p, G_DIR_SEPARATOR);
266   while (sep)
267     {
268       *sep++ = 0;
269       self->pdqueue = sfi_ring_append (self->pdqueue, g_strdup (p));
270       /* compress multiple dir seperators */
271       while (*sep == G_DIR_SEPARATOR)
272         sep++;
273       p = sep;
274       sep = strchr (p, G_DIR_SEPARATOR);
275     }
276   
277   /* final segment */
278   if (p[0])
279     self->pdqueue = sfi_ring_append (self->pdqueue, g_strdup (p));
280   
281   /* final segment test */
282   self->stest = file_test;
283   
284   /* cleanup */
285   g_free (freeme);
286 }
287
288 static void
289 file_crawler_crawl_abs_path (SfiFileCrawler *self)
290 {
291   g_assert (self->pdqueue || self->dlist);
292   if (self->dhandle)
293     {
294       /* finish reading directory contents */
295       file_crawler_crawl_readdir (self);
296 #if INCREMENTAL_RESULTS
297       /* on final segment, directly return results */
298       if (sfi_ring_cmp_length (self->pdqueue, 1) == 0)
299         {
300           self->results = sfi_ring_concat (self->results, self->accu);
301           self->accu = NULL;
302         }
303 #endif
304       return;
305     }
306   if (!self->dlist)
307     {
308       /* collect crawled directories */
309       self->dlist = self->accu;
310       self->accu = NULL;
311       /* free last processed segment */
312       g_free (sfi_ring_pop_head (&self->pdqueue));
313     }
314   if (self->dlist && !self->pdqueue)
315     {
316       /* we're done, return result */
317       self->results = sfi_ring_concat (self->results, self->dlist);
318       self->dlist = NULL;
319     }
320   else if (self->dlist) /* && self->pdqueue */
321     {
322       char *dir = (char*) sfi_ring_pop_head (&self->dlist);
323       char *pattern = (char*) self->pdqueue->data;
324       GFileTest ftest = self->pdqueue->next != self->pdqueue ? G_FILE_TEST_IS_DIR : self->stest;
325       /* continue reading {dir-list}/pattern files */
326       file_crawler_queue_readdir (self, dir, pattern, ftest);
327       g_free (dir);
328     }
329   else /* !self->dlist */
330     while (self->pdqueue)
331       {
332         char *seg = (char*) sfi_ring_pop_head (&self->pdqueue);
333         g_free (seg);
334         /* directory path was a dead end, we're done, no result */
335       }
336 }
337
338 static gchar*
339 path_make_absolute (const gchar *rpath,
340                     const gchar *cwd,
341                     gboolean     use_fallbacks)
342 {
343   const gchar *dir;
344   gchar *home, *user = NULL;
345   if (rpath[0] != '~')
346     return cwd ? g_strconcat (cwd, G_DIR_SEPARATOR_S, rpath, NULL) : NULL;
347   dir = strchr (rpath + 1, G_DIR_SEPARATOR);
348   if (dir && dir > rpath + 1)
349     user = g_strndup (rpath + 1, dir - rpath - 1);
350   else if (!dir && rpath[1])
351     user = g_strdup (rpath + 1);
352   home = get_user_home (user, use_fallbacks);
353   g_free (user);
354   if (!home || !g_path_is_absolute (home))
355     user = cwd ? g_strconcat (cwd, dir, NULL) : NULL;
356   else
357     user = home ? g_strconcat (home, dir, NULL) : NULL;
358   g_free (home);
359   return user;
360 }
361
362 static void
363 file_crawler_crawl_dpatterns (SfiFileCrawler *self)
364 {
365   char *dpattern = (char*) sfi_ring_pop_head (&self->dpatterns);
366   if (dpattern)
367     {
368       /* make absolute */
369       if (!g_path_is_absolute (dpattern))
370         {
371           gchar *path = path_make_absolute (dpattern, self->cwd, TRUE);
372           file_crawler_queue_abs_file_path (self, path, self->ptest);
373           g_free (path);
374         }
375       else
376         file_crawler_queue_abs_file_path (self, dpattern, self->ptest);
377       g_free (dpattern);
378     }
379 }
380
381 /**
382  * @param self  valid SfiFileCrawler
383  * RETURNS: TRUE if sfi_file_crawler_crawl() should be called
384  *
385  * Figure whether collecting all matching files has finished
386  * now. If not, sfi_file_crawler_crawl() needs to be called
387  * until this function returns FALSE.
388  */
389 gboolean
390 sfi_file_crawler_needs_crawl (SfiFileCrawler *self)
391 {
392   g_return_val_if_fail (self != NULL, FALSE);
393   
394   return (self->dpatterns ||
395           self->pdqueue || self->dlist ||
396           self->dhandle);
397 }
398
399 /**
400  * @param self  valid SfiFileCrawler
401  *
402  * Collect the next file or directory if possible,
403  * new results need not arrive after calling this
404  * function, and more than one may. This function
405  * does nothing if sfi_file_crawler_needs_crawl()
406  * returns FALSE.
407  */
408 void
409 sfi_file_crawler_crawl (SfiFileCrawler *self)
410 {
411   g_return_if_fail (self != NULL);
412   if (self->dhandle)
413     {
414 #if INCREMENTAL_RESULTS
415       if (self->pdqueue || self->dlist)
416         file_crawler_crawl_abs_path (self);
417       else
418 #endif
419         file_crawler_crawl_readdir (self);
420     }
421   else if (self->pdqueue || self->dlist)
422     file_crawler_crawl_abs_path (self);
423   else if (self->dpatterns)
424     file_crawler_crawl_dpatterns (self);
425 }
426
427 /**
428  * @param self  valid SfiFileCrawler
429  *
430  * Destroy an existing file crawler and free any resources
431  * allocated by it.
432  */
433 void
434 sfi_file_crawler_destroy (SfiFileCrawler *self)
435 {
436   g_return_if_fail (self != NULL);
437
438   g_free (self->cwd);
439   sfi_ring_free_deep (self->results, g_free);
440   sfi_ring_free_deep (self->dpatterns, g_free);
441   sfi_ring_free_deep (self->pdqueue, g_free);
442   sfi_ring_free_deep (self->dlist, g_free);
443   if (self->pspec)
444     g_pattern_spec_free (self->pspec);
445   g_free (self->base_dir);
446   sfi_ring_free_deep (self->accu, g_free);
447   g_free (self);
448 }
449
450 /**
451  * @param search_path   colon (semicolon under win32) seperated search path with '?' and '*' wildcards
452  * @param file_pattern  wildcard pattern for file names
453  * @param cwd   assumed current working directoy (to interpret './' in search_path)
454  * @param file_test     GFileTest file test condition (e.g. G_FILE_TEST_IS_REGULAR) or 0
455  * @return              an SfiRing with newly allocated strings
456  *
457  * Given a search path with wildcards, list all files matching @a file_pattern,
458  * contained in the directories which the search path matches. Files that do
459  * not pass @a file_test are not listed.
460  */
461 SfiRing*
462 sfi_file_crawler_list_files (const gchar *search_path,
463                              const gchar *file_pattern,
464                              GFileTest    file_test)
465 {
466   SfiFileCrawler *self;
467   SfiRing *results;
468   if (!search_path)
469     return NULL;
470
471   self = sfi_file_crawler_new ();
472   sfi_file_crawler_add_tests (self, file_test);
473   sfi_file_crawler_add_search_path (self, search_path, file_pattern);
474   while (sfi_file_crawler_needs_crawl (self))
475     sfi_file_crawler_crawl (self);
476   results = self->results;
477   self->results = NULL;
478   sfi_file_crawler_destroy (self);
479   return results;
480 }
481
482 #include <sys/types.h>
483 #include <sys/stat.h>
484 #include <unistd.h>
485
486 void
487 sfi_make_dirpath (const gchar *dir)
488 {
489   gchar *str, *dirpath = NULL;
490   guint i;
491
492   g_return_if_fail (dir != NULL);
493
494   if (!g_path_is_absolute (dir))
495     {
496       dirpath = path_make_absolute (dir, NULL, FALSE);
497       if (!dirpath)
498         return;
499       dir = dirpath;
500     }
501
502   i = strlen (dir);
503   str = g_new0 (gchar, i + 1);
504   for (i = 0; dir[i]; i++)
505     {
506       str[i] = dir[i];
507       if (str[i] == G_DIR_SEPARATOR || dir[i + 1] == 0)
508         {
509           struct stat st;
510           if (stat (str, &st) < 0)      /* guard against existance */
511             {
512               if (mkdir (str, 0755) < 0)
513                 break;
514             }
515         }
516     }
517   g_free (str);
518   g_free (dirpath);
519 }
520
521 void
522 sfi_make_dirname_path (const gchar  *file_name)
523 {
524   if (file_name)
525     {
526       gchar *dirname = g_path_get_dirname (file_name);
527       if (dirname)
528         sfi_make_dirpath (dirname);
529       g_free (dirname);
530     }
531 }
532
533 /**
534  * @param filename      possibly relative filename
535  * @param parentdir     possibly relative parent directory path
536  * @return              a newly allocated absolute pathname
537  *
538  * Construct an absolute filename from @a filename, using @a parentdir as
539  * parent directory if @a filename is not absolute. If @a parentdir is
540  * not absolute, it is assumed to be current directory relative.
541  * An exception are filenames starting out with '~' and '~USER', these
542  * are interpreted to refer to '/home' or '/home/USER' respectively.
543  */
544 gchar*
545 sfi_path_get_filename (const gchar  *filename,
546                        const gchar  *parentdir)
547 {
548   gchar *fname;
549   if (!filename)
550     return NULL;
551   if (!g_path_is_absolute (filename))
552     {
553       gchar *free1 = NULL;
554       if (!parentdir)
555         parentdir = init_cwd;
556       if (!g_path_is_absolute (parentdir))
557         parentdir = free1 = path_make_absolute (parentdir, init_cwd, FALSE);
558       fname = path_make_absolute (filename, parentdir, FALSE);
559       g_free (free1);
560     }
561   else
562     fname = g_strdup (filename);
563   return fname;
564 }
565
566 /**
567  * @param file  a file to test
568  * @param test  bitfield of GFileTest flags
569  *
570  * This is the AND version of g_file_test(). That is, all file tests
571  * specified in the @a test bits have to succed for this function to
572  * return TRUE. This function is implemented via birnet_file_check(),
573  * which allowes for more detailed mode tests and is recommended
574  * over use of this function.
575  * Here is the list of possible GFileTest flags:
576  * @li @c G_FILE_TEST_IS_REGULAR    - test for a recular file
577  * @li @c G_FILE_TEST_IS_SYMLINK    - test for a symlink
578  * @li @c G_FILE_TEST_IS_DIR        - test for a directory
579  * @li @c G_FILE_TEST_IS_EXECUTABLE - test for an executable
580  * @li @c G_FILE_TEST_EXISTS        - test whether the file exists
581  */
582 gboolean
583 g_file_test_all (const gchar  *file,
584                  GFileTest     test)
585 {
586   gchar buffer[65] = "";
587   if (test & G_FILE_TEST_EXISTS)
588     strcat (buffer, "e");
589   if (test & G_FILE_TEST_IS_EXECUTABLE)
590     strcat (buffer, "x");
591   if (test & G_FILE_TEST_IS_SYMLINK)
592     strcat (buffer, "l");
593   if (test & G_FILE_TEST_IS_REGULAR)
594     strcat (buffer, "f");
595   if (test & G_FILE_TEST_IS_DIR)
596     strcat (buffer, "d");
597   if (test & G_FILE_TEST_IS_EXECUTABLE)
598     strcat (buffer, "x");
599   return birnet_file_check (file, buffer);
600 }
601
602 #include <pwd.h>
603
604 static gchar*
605 get_user_home (const gchar *user,
606                gboolean     use_fallbacks)
607 {
608   struct passwd *p = NULL;
609 #if HAVE_GETPWNAM_R
610   if (user)
611     {
612       char buffer[8192];
613       struct passwd spwd;
614       if (getpwnam_r (user, &spwd, buffer, 8192, &p) == 0 && p)
615         return g_strdup (p->pw_dir);
616     }
617 #endif
618 #if HAVE_GETPWNAM
619   if (user)
620     {
621       p = getpwnam (user);
622       if (p)
623         return g_strdup (p->pw_dir);
624     }
625 #endif
626   if (!user)
627     return g_strdup (g_get_home_dir ());
628   return use_fallbacks ? g_strdup (g_get_home_dir ()) : NULL;
629 }