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