Fix some printf format warnings when -Wall is enabled.
[febootstrap.git] / helper / utils.c
1 /* febootstrap-supermin-helper reimplementation in C.
2  * Copyright (C) 2009-2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program 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
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdint.h>
24 #include <string.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <fnmatch.h>
28 #include <inttypes.h>
29 #include <sys/time.h>
30 #include <sys/stat.h>
31 #include <assert.h>
32
33 #include "error.h"
34 #include "filevercmp.h"
35 #include "hash.h"
36 #include "hash-pjw.h"
37 #include "xalloc.h"
38
39 #include "helper.h"
40
41 /* Compute Y - X and return the result in milliseconds.
42  * Approximately the same as this code:
43  * http://www.mpp.mpg.de/~huber/util/timevaldiff.c
44  */
45 int64_t
46 timeval_diff (const struct timeval *x, const struct timeval *y)
47 {
48   int64_t msec;
49
50   msec = (y->tv_sec - x->tv_sec) * 1000;
51   msec += (y->tv_usec - x->tv_usec) / 1000;
52   return msec;
53 }
54
55 void
56 print_timestamped_message (const char *fs, ...)
57 {
58   struct timeval tv;
59   gettimeofday (&tv, NULL);
60
61   va_list args;
62   char *msg;
63   int err;
64
65   va_start (args, fs);
66   err = vasprintf (&msg, fs, args);
67   va_end (args);
68
69   if (err < 0) return;
70
71   fprintf (stderr, "supermin helper [%05" PRIi64 "ms] %s\n",
72            timeval_diff (&start_t, &tv), msg);
73
74   free (msg);
75 }
76
77 int
78 reverse_filevercmp (const void *p1, const void *p2)
79 {
80   const char *s1 = * (char * const *) p1;
81   const char *s2 = * (char * const *) p2;
82
83   /* Note, arguments are reversed to achieve a reverse sort. */
84   return filevercmp (s2, s1);
85 }
86
87 void
88 add_string (char ***argv, size_t *n_used, size_t *n_alloc, const char *str)
89 {
90   char *new_str;
91
92   if (*n_used >= *n_alloc)
93     *argv = x2nrealloc (*argv, n_alloc, sizeof (char *));
94
95   if (str)
96     new_str = xstrdup (str);
97   else
98     new_str = NULL;
99
100   (*argv)[*n_used] = new_str;
101
102   (*n_used)++;
103 }
104
105 size_t
106 count_strings (char *const *argv)
107 {
108   size_t argc;
109
110   for (argc = 0; argv[argc] != NULL; ++argc)
111     ;
112   return argc;
113 }
114
115 struct dir_cache {
116   char *path;
117   char **files;
118 };
119
120 static size_t
121 dir_cache_hash (void const *x, size_t table_size)
122 {
123   struct dir_cache const *p = x;
124   return hash_pjw (p->path, table_size);
125 }
126
127 static bool
128 dir_cache_compare (void const *x, void const *y)
129 {
130   struct dir_cache const *p = x;
131   struct dir_cache const *q = y;
132   return strcmp (p->path, q->path) == 0;
133 }
134
135 /* Read a directory into a list of strings.
136  *
137  * Previously looked up directories are cached and returned quickly,
138  * saving some considerable amount of time compared to reading the
139  * directory over again.  However this means you really must not
140  * alter the array of strings that are returned.
141  *
142  * Returns an empty list if the directory cannot be opened.
143  */
144 char **
145 read_dir (const char *name)
146 {
147   static Hash_table *ht = NULL;
148
149   if (!ht)
150     ht = hash_initialize (1024, NULL, dir_cache_hash, dir_cache_compare, NULL);
151
152   struct dir_cache key = { .path = (char *) name };
153   struct dir_cache *p = hash_lookup (ht, &key);
154   if (p)
155     return p->files;
156
157   char **files = NULL;
158   size_t n_used = 0, n_alloc = 0;
159
160   DIR *dir = opendir (name);
161   if (!dir) {
162     /* If it fails to open, that's OK, skip to the end. */
163     /*perror (name);*/
164     goto done;
165   }
166
167   for (;;) {
168     errno = 0;
169     struct dirent *d = readdir (dir);
170     if (d == NULL) {
171       if (errno != 0)
172         /* But if it fails here, after opening and potentially reading
173          * part of the directory, that's a proper failure - inform the
174          * user and exit.
175          */
176         error (EXIT_FAILURE, errno, "%s", name);
177       break;
178     }
179
180     add_string (&files, &n_used, &n_alloc, d->d_name);
181   }
182
183   if (closedir (dir) == -1)
184     error (EXIT_FAILURE, errno, "closedir: %s", name);
185
186  done:
187   /* NULL-terminate the array. */
188   add_string (&files, &n_used, &n_alloc, NULL);
189
190   /* Add it to the hash for next time. */
191   p = xmalloc (sizeof *p);
192   p->path = (char *) name;
193   p->files = files;
194   p = hash_insert (ht, p);
195   assert (p != NULL);
196
197   return files;
198 }
199
200 /* Filter a list of strings, returning only those where f(s) != 0. */
201 char **
202 filter (char **strings, int (*f) (const char *))
203 {
204   char **out = NULL;
205   size_t n_used = 0, n_alloc = 0;
206
207   int i;
208   for (i = 0; strings[i] != NULL; ++i) {
209     if (f (strings[i]) != 0)
210       add_string (&out, &n_used, &n_alloc, strings[i]);
211   }
212
213   add_string (&out, &n_used, &n_alloc, NULL);
214   return out;
215 }
216
217 /* Filter a list of strings and return only those matching the wildcard. */
218 char **
219 filter_fnmatch (char **strings, const char *patt, int flags)
220 {
221   char **out = NULL;
222   size_t n_used = 0, n_alloc = 0;
223
224   int i, r;
225   for (i = 0; strings[i] != NULL; ++i) {
226     r = fnmatch (patt, strings[i], flags);
227     if (r == 0)
228       add_string (&out, &n_used, &n_alloc, strings[i]);
229     else if (r != FNM_NOMATCH)
230       error (EXIT_FAILURE, 0, "internal error: fnmatch ('%s', '%s', %d) returned unexpected non-zero value %d\n",
231              patt, strings[i], flags, r);
232   }
233
234   add_string (&out, &n_used, &n_alloc, NULL);
235   return out;
236 }
237
238 /* Filter a list of strings and return only those which DON'T contain sub. */
239 char **
240 filter_notmatching_substring (char **strings, const char *sub)
241 {
242   char **out = NULL;
243   size_t n_used = 0, n_alloc = 0;
244
245   int i;
246   for (i = 0; strings[i] != NULL; ++i) {
247     if (strstr (strings[i], sub) == NULL)
248       add_string (&out, &n_used, &n_alloc, strings[i]);
249   }
250
251   add_string (&out, &n_used, &n_alloc, NULL);
252   return out;
253 }
254
255 /* Sort a list of strings, in place, with the comparison function supplied. */
256 void
257 sort (char **strings, int (*compare) (const void *, const void *))
258 {
259   qsort (strings, count_strings (strings), sizeof (char *), compare);
260 }
261
262 /* Return true iff path exists and is a directory.  This version
263  * follows symlinks.
264  */
265 int
266 isdir (const char *path)
267 {
268   struct stat statbuf;
269
270   if (stat (path, &statbuf) == -1)
271     return 0;
272
273   return S_ISDIR (statbuf.st_mode);
274 }
275
276 /* Load in a file, returning a list of lines. */
277 char **
278 load_file (const char *filename)
279 {
280   char **lines = 0;
281   size_t n_used = 0, n_alloc = 0;
282
283   FILE *fp;
284   fp = fopen (filename, "r");
285   if (fp == NULL)
286     error (EXIT_FAILURE, errno, "fopen: %s", filename);
287
288   char line[4096];
289   while (fgets (line, sizeof line, fp)) {
290     size_t len = strlen (line);
291     if (len > 0 && line[len-1] == '\n')
292       line[len-1] = '\0';
293     add_string (&lines, &n_used, &n_alloc, line);
294   }
295
296   add_string (&lines, &n_used, &n_alloc, NULL);
297   return lines;
298 }