]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/mntent_cache.c
fix #1938 about PATH_MAX on hurd
[bacula/bacula] / bacula / src / lib / mntent_cache.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2009-2011 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28
29 /**
30  * This code implements a cache with the current mounted filesystems for which
31  * its uses the mostly in kernel mount information and export the different OS
32  * specific interfaces using a generic interface. We use a hashed cache which is
33  * accessed using a hash on the device id and we keep the previous cache hit as
34  * most of the time we get called quite a lot with most of the time the same
35  * device so keeping the previous cache hit we have a very optimized code path.
36  *
37  * This interface is implemented for the following OS-es:
38  *
39  * - Linux
40  * - HPUX
41  * - DARWIN (OSX)
42  * - IRIX
43  * - AIX
44  * - OSF1 (Tru64)
45  * - Solaris
46  *
47  * Currently we only use this code for Linux and OSF1 based fstype determination.
48  * For the other OS-es we can use the fstype present in stat structure on those OS-es.
49  *
50  * This code replaces the big switch we used before based on SUPER_MAGIC present in
51  * the statfs(2) structure but which need extra code for each new filesystem added to
52  * the OS and for Linux that tends to be often as it has quite some different filesystems.
53  * This new implementation should eliminate this as we use the Linux /proc/mounts in kernel
54  * data which automatically adds any new filesystem when added to the kernel.
55  */
56
57 /*
58  *  Marco van Wieringen, August 2009
59  */
60
61 #include "bacula.h"
62 #include "mntent_cache.h"
63
64 #include <errno.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <sys/types.h>
68 #include <sys/stat.h>
69
70 #if defined(HAVE_GETMNTENT)
71 #if defined(HAVE_LINUX_OS) || defined(HAVE_HPUX_OS) || defined(HAVE_AIX_OS)
72 #include <mntent.h>
73 #elif defined(HAVE_SUN_OS)
74 #include <sys/mnttab.h>
75 #endif /* HAVE_GETMNTENT */
76 #elif defined(HAVE_GETMNTINFO)
77 #if defined(HAVE_OPENBSD_OS)
78 #include <sys/param.h>
79 #include <sys/mount.h>
80 #elif defined(HAVE_NETBSD_OS)
81 #include <sys/types.h>
82 #include <sys/statvfs.h>
83 #else
84 #include <sys/param.h>
85 #include <sys/ucred.h>
86 #include <sys/mount.h>
87 #endif
88 #elif defined(HAVE_AIX_OS)
89 #include <fshelp.h>
90 #include <sys/vfs.h>
91 #elif defined(HAVE_OSF1_OS)
92 #include <sys/mount.h>
93 #endif
94
95 /*
96  * Protected data by mutex lock.
97  */
98 static pthread_mutex_t mntent_cache_lock = PTHREAD_MUTEX_INITIALIZER;
99 static mntent_cache_entry_t *previous_cache_hit = NULL;
100 static htable *mntent_cache_entry_hashtable = NULL;
101
102 /*
103  * Last time a rescan of the mountlist took place.
104  */
105 static time_t last_rescan = 0;
106
107 static const char *skipped_fs_types[] = {
108 #if defined(HAVE_LINUX_OS)
109    "rootfs",
110 #endif
111    NULL
112 };
113
114 /**
115  * Add a new entry to the cache.
116  * This function should be called with a write lock on the mntent_cache.
117  */
118 static inline void add_mntent_mapping(uint32_t dev,
119                                       const char *special,
120                                       const char *mountpoint,
121                                       const char *fstype,
122                                       const char *mntopts)
123 {
124    int len;
125    mntent_cache_entry_t *mce;
126
127    /*
128     * Calculate the length of all strings so we can allocate the buffer
129     * as one big chunk of memory using the hash_malloc method.
130     */
131    len = strlen(special) + 1;
132    len += strlen(mountpoint) + 1;
133    len += strlen(fstype) + 1;
134    if (mntopts) {
135       len += strlen(mntopts) + 1;
136    }
137
138    /*
139     * We allocate all members of the hash entry in the same memory chunk.
140     */
141    mce = (mntent_cache_entry_t *)mntent_cache_entry_hashtable->hash_malloc(sizeof(mntent_cache_entry_t) + len);
142    mce->dev = dev;
143
144    mce->special = (char *)mce + sizeof(mntent_cache_entry_t);
145    strcpy(mce->special, special);
146
147    mce->mountpoint = mce->special + strlen(mce->special) + 1;
148    strcpy(mce->mountpoint, mountpoint);
149
150    mce->fstype = mce->mountpoint + strlen(mce->mountpoint) + 1;
151    strcpy(mce->fstype, fstype);
152
153    if (mntopts) {
154       mce->mntopts = mce->fstype + strlen(mce->fstype) + 1;
155       strcpy(mce->mntopts, mntopts);
156    } else {
157       mce->mntopts = NULL;
158    }
159
160    mntent_cache_entry_hashtable->insert(mce->dev, mce);
161 }
162
163 static inline bool skip_fstype(const char *fstype)
164 {
165    int i;
166
167    for (i = 0; skipped_fs_types[i]; i++) {
168       if (bstrcmp(fstype, skipped_fs_types[i]))
169          return true;
170    }
171
172    return false;
173 }
174
175 /**
176  * OS specific function to load the different mntents into the cache.
177  * This function should be called with a write lock on the mntent_cache.
178  */
179 static void refresh_mount_cache(void)
180 {
181 #if defined(HAVE_GETMNTENT)
182    FILE *fp;
183    struct stat st;
184 #if defined(HAVE_LINUX_OS) || defined(HAVE_HPUX_OS) || defined(HAVE_IRIX_OS) || defined(HAVE_AIX_OS)
185    struct mntent *mnt;
186
187 #if defined(HAVE_LINUX_OS)
188    if ((fp = setmntent("/proc/mounts", "r")) == (FILE *)NULL) {
189       if ((fp = setmntent(_PATH_MOUNTED, "r")) == (FILE *)NULL) {
190          return;
191       }
192    }
193 #elif defined(HAVE_HPUX_OS)
194    if ((fp = fopen(MNT_MNTTAB, "r")) == (FILE *)NULL) {
195       return;
196    }
197 #elif defined(HAVE_IRIX_OS)
198    if ((fp = setmntent(MOUNTED, "r")) == (FILE *)NULL) {
199       return;
200    }
201 #elif defined(HAVE_AIX_OS)
202    if ((fp = setmntent(MNTTAB, "r")) == (FILE *)NULL) {
203       return;
204    }
205 #endif
206
207    while ((mnt = getmntent(fp)) != (struct mntent *)NULL) {
208       if (skip_fstype(mnt->mnt_type)) {
209          continue;
210       }
211
212       if (stat(mnt->mnt_dir, &st) < 0) {
213          continue;
214       }
215
216       add_mntent_mapping(st.st_dev, mnt->mnt_fsname, mnt->mnt_dir, mnt->mnt_type, mnt->mnt_opts);
217    }
218
219    endmntent(fp);
220 #elif defined(HAVE_SUN_OS)
221    struct mnttab mnt;
222
223    if ((fp = fopen(MNTTAB, "r")) == (FILE *)NULL)
224       return;
225
226    while (getmntent(fp, &mnt) == 0) {
227       if (skip_fstype(mnt.mnt_fstype)) {
228          continue;
229       }
230
231       if (stat(mnt.mnt_mountp, &st) < 0) {
232          continue;
233       }
234
235       add_mntent_mapping(st.st_dev, mnt.mnt_special, mnt.mnt_mountp, mnt.mnt_fstype, mnt.mnt_mntopts);
236    }
237
238    fclose(fp);
239 #endif /* HAVE_SUN_OS */
240 #elif defined(HAVE_GETMNTINFO)
241    int cnt;
242    struct stat st;
243 #if defined(HAVE_NETBSD_OS)
244    struct statvfs *mntinfo;
245 #else
246    struct statfs *mntinfo;
247 #endif
248 #if defined(ST_NOWAIT)
249    int flags = ST_NOWAIT;
250 #elif defined(MNT_NOWAIT)
251    int flags = MNT_NOWAIT;
252 #else
253    int flags = 0;
254 #endif
255
256    if ((cnt = getmntinfo(&mntinfo, flags)) > 0) {
257       while (cnt > 0) {
258          if (!skip_fstype(mntinfo->f_fstypename) &&
259              stat(mntinfo->f_mntonname, &st) == 0) {
260             add_mntent_mapping(st.st_dev,
261                                mntinfo->f_mntfromname,
262                                mntinfo->f_mntonname,
263                                mntinfo->f_fstypename,
264                                NULL);
265          }
266          mntinfo++;
267          cnt--;
268       }
269    }
270 #elif defined(HAVE_AIX_OS)
271    int bufsize;
272    char *entries, *current;
273    struct vmount *vmp;
274    struct stat st;
275    struct vfs_ent *ve;
276    int n_entries, cnt;
277
278    if (mntctl(MCTL_QUERY, sizeof(bufsize), (struct vmount *)&bufsize) != 0) {
279       return;
280    }
281
282    entries = malloc(bufsize);
283    if ((n_entries = mntctl(MCTL_QUERY, bufsize, (struct vmount *) entries)) < 0) {
284       free(entries);
285       return;
286    }
287
288    cnt = 0;
289    current = entries;
290    while (cnt < n_entries) {
291       vmp = (struct vmount *)current;
292
293       if (skip_fstype(ve->vfsent_name)) {
294          continue;
295       }
296
297       if (stat(current + vmp->vmt_data[VMT_STUB].vmt_off, &st) < 0) {
298          continue;
299       }
300
301       ve = getvfsbytype(vmp->vmt_gfstype);
302       if (ve && ve->vfsent_name) {
303          add_mntent_mapping(st.st_dev,
304                             current + vmp->vmt_data[VMT_OBJECT].vmt_off,
305                             current + vmp->vmt_data[VMT_STUB].vmt_off,
306                             ve->vfsent_name,
307                             current + vmp->vmt_data[VMT_ARGS].vmt_off);
308       }
309       current = current + vmp->vmt_length;
310       cnt++;
311    }
312    free(entries);
313 #elif defined(HAVE_OSF1_OS)
314    struct statfs *entries, *current;
315    struct stat st;
316    int n_entries, cnt;
317    int size;
318
319    if ((n_entries = getfsstat((struct statfs *)0, 0L, MNT_NOWAIT)) < 0) {
320       return;
321    }
322
323    size = (n_entries + 1) * sizeof(struct statfs);
324    entries = malloc(size);
325
326    if ((n_entries = getfsstat(entries, size, MNT_NOWAIT)) < 0) {
327       free(entries);
328       return;
329    }
330
331    cnt = 0;
332    current = entries;
333    while (cnt < n_entries) {
334       if (skip_fstype(current->f_fstypename)) {
335          continue;
336       }
337
338       if (stat(current->f_mntonname, &st) < 0) {
339          continue;
340       }
341       add_mntent_mapping(st.st_dev,
342                          current->f_mntfromname,
343                          current->f_mntonname,
344                          current->f_fstypename,
345                          NULL);
346       current++;
347       cnt++;
348    }
349    free(stats);
350 #endif
351 }
352
353 /**
354  * Clear the cache (either by flushing it or by initializing it.)
355  * This function should be called with a write lock on the mntent_cache.
356  */
357 static void clear_mount_cache()
358 {
359    mntent_cache_entry_t *mce = NULL;
360
361    if (!mntent_cache_entry_hashtable) {
362       /**
363        * Initialize the hash table.
364        */
365       mntent_cache_entry_hashtable = (htable *)malloc(sizeof(htable));
366       mntent_cache_entry_hashtable->init(mce, &mce->link,
367                                          NR_MNTENT_CACHE_ENTRIES,
368                                          NR_MNTENT_HTABLE_PAGES);
369    } else {
370       /**
371        * Clear the previous_cache_hit.
372        */
373       previous_cache_hit = NULL;
374
375       /**
376        * Destroy the current content and (re)initialize the hashtable.
377        */
378       mntent_cache_entry_hashtable->destroy();
379       mntent_cache_entry_hashtable->init(mce, &mce->link,
380                                          NR_MNTENT_CACHE_ENTRIES,
381                                          NR_MNTENT_HTABLE_PAGES);
382    }
383 }
384
385 /**
386  * Initialize the cache for use.
387  * This function should be called with a write lock on the mntent_cache.
388  */
389 static void initialize_mntent_cache(void)
390 {
391    /**
392     * Make sure the cache is empty (either by flushing it or by initializing it.)
393     */
394    clear_mount_cache();
395
396    /**
397     * Refresh the cache.
398     */
399    refresh_mount_cache();
400 }
401
402 /**
403  * Flush the current content from the cache.
404  */
405 void flush_mntent_cache(void)
406 {
407    /**
408     * Lock the cache.
409     */
410    P(mntent_cache_lock);
411
412    if (mntent_cache_entry_hashtable) {
413       previous_cache_hit = NULL;
414       mntent_cache_entry_hashtable->destroy();
415       mntent_cache_entry_hashtable = NULL;
416    }
417
418    V(mntent_cache_lock);
419 }
420
421 /**
422  * Find a mapping in the cache.
423  */
424 mntent_cache_entry_t *find_mntent_mapping(uint32_t dev)
425 {
426    mntent_cache_entry_t *mce = NULL;
427    time_t now;
428
429    /**
430     * Lock the cache.
431     */
432    P(mntent_cache_lock);
433
434    /**
435     * Shortcut when we get a request for the same device again.
436     */
437    if (previous_cache_hit && previous_cache_hit->dev == dev) {
438       mce = previous_cache_hit;
439       goto ok_out;
440    }
441
442    /**
443     * Initialize the cache if that was not done before.
444     */
445    if (!mntent_cache_entry_hashtable) {
446       initialize_mntent_cache();
447       last_rescan = time(NULL);
448    } else {
449       /**
450        * We rescan the mountlist when called when more then
451        * MNTENT_RESCAN_INTERVAL seconds have past since the
452        * last rescan. This way we never work with data older
453        * then MNTENT_RESCAN_INTERVAL seconds.
454        */
455       now = time(NULL);
456       if ((now - last_rescan) > MNTENT_RESCAN_INTERVAL) {
457          initialize_mntent_cache();
458       }
459    }
460
461    mce = (mntent_cache_entry_t *)mntent_cache_entry_hashtable->lookup(dev);
462
463    /**
464     * If we fail to lookup the mountpoint its probably a mountpoint added
465     * after we did our initial scan. Lets rescan the mountlist and try
466     * the lookup again.
467     */
468    if (!mce) {
469       initialize_mntent_cache();
470       mce = (mntent_cache_entry_t *)mntent_cache_entry_hashtable->lookup(dev);
471    }
472
473    /*
474     * Store the last successfull lookup as the previous_cache_hit.
475     */
476    if (mce) {
477       previous_cache_hit = mce;
478    }
479
480 ok_out:
481    V(mntent_cache_lock);
482    return mce;
483 }