]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/mntent_cache.c
Enhance mountcache with rescan option after interval.
[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 /**
108  * Add a new entry to the cache.
109  * This function should be called with a write lock on the mntent_cache.
110  */
111 static inline void add_mntent_mapping(uint32_t dev, const char *special, const char *mountpoint,
112                                       const char *fstype, const char *mntopts)
113 {
114    int len;
115    mntent_cache_entry_t *mce;
116
117    /*
118     * Calculate the length of all strings so we can allocate the buffer
119     * as one big chunk of memory using the hash_malloc method.
120     */
121    len = strlen(special) + 1;
122    len += strlen(mountpoint) + 1;
123    len += strlen(fstype) + 1;
124    if (mntopts) {
125       len += strlen(mntopts) + 1;
126    }
127
128    /*
129     * We allocate all members of the hash entry in the same memory chunk.
130     */
131    mce = (mntent_cache_entry_t *)mntent_cache_entry_hashtable->hash_malloc(sizeof(mntent_cache_entry_t) + len);
132    mce->dev = dev;
133
134    mce->special = (char *)mce + sizeof(mntent_cache_entry_t);
135    strcpy(mce->special, special);
136
137    mce->mountpoint = mce->special + strlen(mce->special) + 1;
138    strcpy(mce->mountpoint, mountpoint);
139
140    mce->fstype = mce->mountpoint + strlen(mce->mountpoint) + 1;
141    strcpy(mce->fstype, fstype);
142
143    if (mntopts) {
144       mce->mntopts = mce->fstype + strlen(mce->fstype) + 1;
145       strcpy(mce->mntopts, mntopts);
146    } else {
147       mce->mntopts = NULL;
148    }
149
150    mntent_cache_entry_hashtable->insert(mce->dev, mce);
151 }
152
153 /**
154  * OS specific function to load the different mntents into the cache.
155  * This function should be called with a write lock on the mntent_cache.
156  */
157 static void refresh_mount_cache(void)
158 {
159 #if defined(HAVE_GETMNTENT)
160    FILE *fp;
161    struct stat st;
162 #if defined(HAVE_LINUX_OS) || defined(HAVE_HPUX_OS) || defined(HAVE_IRIX_OS) || defined(HAVE_AIX_OS)
163    struct mntent *mnt;
164
165 #if defined(HAVE_LINUX_OS)
166    if ((fp = setmntent("/proc/mounts", "r")) == (FILE *)NULL) {
167       if ((fp = setmntent(_PATH_MOUNTED, "r")) == (FILE *)NULL) {
168          return;
169       }
170    }
171 #elif defined(HAVE_HPUX_OS)
172    if ((fp = fopen(MNT_MNTTAB, "r")) == (FILE *)NULL) {
173       return;
174    }
175 #elif defined(HAVE_IRIX_OS)
176    if ((fp = setmntent(MOUNTED, "r")) == (FILE *)NULL) {
177       return;
178    }
179 #elif defined(HAVE_AIX_OS)
180    if ((fp = setmntent(MNTTAB, "r")) == (FILE *)NULL) {
181       return;
182    }
183 #endif
184
185    while ((mnt = getmntent(fp)) != (struct mntent *)NULL) {
186       if (stat(mnt->mnt_dir, &st) < 0) {
187          continue;
188       }
189
190       add_mntent_mapping(st.st_dev, mnt->mnt_fsname, mnt->mnt_dir, mnt->mnt_type, mnt->mnt_opts);
191    }
192
193    endmntent(fp);
194 #elif defined(HAVE_SUN_OS)
195    struct mnttab mnt;
196
197    if ((fp = fopen(MNTTAB, "r")) == (FILE *)NULL)
198       return;
199
200    while (getmntent(fp, &mnt) == 0) {
201       if (stat(mnt.mnt_mountp, &st) < 0) {
202          continue;
203       }
204
205       add_mntent_mapping(st.st_dev, mnt.mnt_special, mnt.mnt_mountp, mnt.mnt_fstype, mnt.mnt_mntopts);
206    }
207
208    fclose(fp);
209 #endif /* HAVE_SUN_OS */
210 #elif defined(HAVE_GETMNTINFO)
211    int cnt;
212    struct stat st;
213 #if defined(HAVE_NETBSD_OS)
214    struct statvfs *mntinfo;
215 #else
216    struct statfs *mntinfo;
217 #endif
218 #if defined(ST_NOWAIT)
219    int flags = ST_NOWAIT;
220 #elif defined(MNT_NOWAIT)
221    int flags = MNT_NOWAIT;
222 #else
223    int flags = 0;
224 #endif
225
226    if ((cnt = getmntinfo(&mntinfo, flags)) > 0) {
227       while (cnt > 0) {
228          if (stat(mntinfo->f_mntonname, &st) == 0) {
229             add_mntent_mapping(st.st_dev,
230                                mntinfo->f_mntfromname,
231                                mntinfo->f_mntonname,
232                                mntinfo->f_fstypename,
233                                NULL);
234          }
235          mntinfo++;
236          cnt--;
237       }
238    }
239 #elif defined(HAVE_AIX_OS)
240    int bufsize;
241    char *entries, *current;
242    struct vmount *vmp;
243    struct stat st;
244    struct vfs_ent *ve;
245    int n_entries, cnt;
246
247    if (mntctl(MCTL_QUERY, sizeof(bufsize), (struct vmount *)&bufsize) != 0) {
248       return;
249    }
250
251    entries = malloc(bufsize);
252    if ((n_entries = mntctl(MCTL_QUERY, bufsize, (struct vmount *) entries)) < 0) {
253       free(entries);
254       return;
255    }
256
257    cnt = 0;
258    current = entries;
259    while (cnt < n_entries) {
260       vmp = (struct vmount *)current;
261
262       if (stat(current + vmp->vmt_data[VMT_STUB].vmt_off, &st) < 0) {
263          continue;
264       }
265
266       ve = getvfsbytype(vmp->vmt_gfstype);
267       if (ve && ve->vfsent_name) {
268          add_mntent_mapping(st.st_dev,
269                             current + vmp->vmt_data[VMT_OBJECT].vmt_off,
270                             current + vmp->vmt_data[VMT_STUB].vmt_off,
271                             ve->vfsent_name,
272                             current + vmp->vmt_data[VMT_ARGS].vmt_off);
273       }
274       current = current + vmp->vmt_length;
275       cnt++;
276    }
277    free(entries);
278 #elif defined(HAVE_OSF1_OS)
279    struct statfs *entries, *current;
280    struct stat st;
281    int n_entries, cnt;
282    int size;
283
284    if ((n_entries = getfsstat((struct statfs *)0, 0L, MNT_NOWAIT)) < 0) {
285       return;
286    }
287
288    size = (n_entries + 1) * sizeof(struct statfs);
289    entries = malloc(size);
290
291    if ((n_entries = getfsstat(entries, size, MNT_NOWAIT)) < 0) {
292       free(entries);
293       return;
294    }
295
296    cnt = 0;
297    current = entries;
298    while (cnt < n_entries) {
299       if (stat(current->f_mntonname, &st) < 0) {
300          continue;
301       }
302       add_mntent_mapping(st.st_dev,
303                          current->f_mntfromname,
304                          current->f_mntonname,
305                          current->f_fstypename,
306                          NULL);
307       current++;
308       cnt++;
309    }
310    free(stats);
311 #endif
312 }
313
314 /**
315  * Clear the cache (either by flushing it or by initializing it.)
316  * This function should be called with a write lock on the mntent_cache.
317  */
318 static void clear_mount_cache()
319 {
320    mntent_cache_entry_t *mce = NULL;
321
322    if (!mntent_cache_entry_hashtable) {
323       /**
324        * Initialize the hash table.
325        */
326       mntent_cache_entry_hashtable = (htable *)malloc(sizeof(htable));
327       mntent_cache_entry_hashtable->init(mce, &mce->link,
328                                          NR_MNTENT_CACHE_ENTRIES,
329                                          NR_MNTENT_HTABLE_PAGES);
330    } else {
331       /**
332        * Clear the previous_cache_hit.
333        */
334       previous_cache_hit = NULL;
335
336       /**
337        * Destroy the current content and (re)initialize the hashtable.
338        */
339       mntent_cache_entry_hashtable->destroy();
340       mntent_cache_entry_hashtable->init(mce, &mce->link,
341                                          NR_MNTENT_CACHE_ENTRIES,
342                                          NR_MNTENT_HTABLE_PAGES);
343    }
344 }
345
346 /**
347  * Initialize the cache for use.
348  */
349 static void initialize_mntent_cache(void)
350 {
351    /**
352     * Lock the cache while we update it.
353     */
354    P(mntent_cache_lock);
355
356    /**
357     * Make sure the cache is empty (either by flushing it or by initializing it.)
358     */
359    clear_mount_cache();
360
361    /**
362     * Refresh the cache.
363     */
364    refresh_mount_cache();
365
366    /**
367     * We are done updating the cache.
368     */
369    V(mntent_cache_lock);
370 }
371
372 void flush_mntent_cache(void)
373 {
374    /**
375     * Lock the cache while we update it.
376     */
377    P(mntent_cache_lock);
378
379    /**
380     * Make sure the cache is empty (either by flushing it or by initializing it.)
381     */
382    clear_mount_cache();
383
384    /**
385     * We are done updating the cache.
386     */
387    V(mntent_cache_lock);
388 }
389
390 /**
391  * Find a mapping in the cache.
392  */
393 mntent_cache_entry_t *find_mntent_mapping(uint32_t dev)
394 {
395    mntent_cache_entry_t *mce = NULL;
396    time_t now;
397
398    /**
399     * Initialize the cache if that was not done before.
400     */
401    if (!mntent_cache_entry_hashtable) {
402       initialize_mntent_cache();
403       last_rescan = time(NULL);
404    } else {
405       /**
406        * We rescan the mountlist when called when more then
407        * MNTENT_RESCAN_INTERVAL seconds have past since the
408        * last rescan. This way we never work with data older
409        * then MNTENT_RESCAN_INTERVAL seconds.
410        */
411       now = time(NULL);
412       if ((now - last_rescan) > MNTENT_RESCAN_INTERVAL) {
413          initialize_mntent_cache();
414       }
415    }
416
417    /**
418     * Shortcut when we get a request for the same device again.
419     */
420    if (previous_cache_hit && previous_cache_hit->dev == dev) {
421       return previous_cache_hit;
422    }
423
424    /**
425     * Lock the cache while we walk it.
426     */
427    P(mntent_cache_lock);
428
429    mce = (mntent_cache_entry_t *)mntent_cache_entry_hashtable->lookup(dev);
430
431    /**
432     * If we fail to lookup the mountpoint its probably a mountpoint added
433     * after we did our initial scan. Lets rescan the mountlist and try
434     * the lookup again.
435     */
436    if (!mce) {
437       /**
438        * Make sure the cache is empty (either by flushing it or by initializing it.)
439        */
440       clear_mount_cache();
441
442       /**
443        * Refresh the cache.
444        */
445       refresh_mount_cache();
446
447       mce = (mntent_cache_entry_t *)mntent_cache_entry_hashtable->lookup(dev);
448    }
449
450    /*
451     * Store the last successfull lookup as the previous_cache_hit.
452     */
453    if (mce) {
454       previous_cache_hit = mce;
455    }
456
457    /**
458     * We are done walking the cache.
459     */
460    V(mntent_cache_lock);
461    return mce;
462 }