Further rationalise our usage of assert()
[openafs.git] / src / vol / vg_scan.c
1 /*
2  * Copyright 2009-2010, Sine Nomine Associates and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * demand attach fs
12  * volume group membership cache
13  * asynchronous partition scanner
14  */
15
16 #include <afsconfig.h>
17 #include <afs/param.h>
18
19 #include <roken.h>
20
21 #ifdef HAVE_SYS_FILE_H
22 #include <sys/file.h>
23 #endif
24
25 #ifdef AFS_DEMAND_ATTACH_FS
26
27 #include <afs/opr.h>
28 #include <lock.h>
29 #include <afs/afsutil.h>
30 #include <lwp.h>
31 #include "nfs.h"
32 #include <afs/afsint.h>
33 #include "ihandle.h"
34 #include "vnode.h"
35 #include "volume.h"
36 #include "viceinode.h"
37 #include "voldefs.h"
38 #include "partition.h"
39 #include <afs/errors.h>
40
41 #define __VOL_VG_CACHE_IMPL 1
42
43 #include "vg_cache.h"
44 #include "vg_cache_impl.h"
45
46 static int _VVGC_scan_table_init(VVGCache_scan_table_t * tbl);
47 static int _VVGC_scan_table_add(VVGCache_scan_table_t * tbl,
48                                 struct DiskPartition64 * dp,
49                                 VolumeId volid,
50                                 VolumeId parent);
51 static int _VVGC_scan_table_flush(VVGCache_scan_table_t * tbl,
52                                   struct DiskPartition64 * dp);
53 static void * _VVGC_scanner_thread(void *);
54 static int _VVGC_scan_partition(struct DiskPartition64 * part);
55 static VVGCache_dlist_entry_t * _VVGC_dlist_lookup_r(struct DiskPartition64 *dp,
56                                                      VolumeId parent,
57                                                      VolumeId child);
58 static void _VVGC_flush_dlist(struct DiskPartition64 *dp);
59
60 /**
61  * init a thread-local scan table.
62  *
63  * @param[in] tbl  scan table
64  *
65  * @return operation status
66  *    @retval 0 success
67  *
68  * @internal
69  */
70 static int
71 _VVGC_scan_table_init(VVGCache_scan_table_t * tbl)
72 {
73     memset(tbl, 0, sizeof(*tbl));
74
75     return 0;
76 }
77
78 /**
79  * add an entry to the thread-local scan table.
80  *
81  * @param[in] tbl     scan table
82  * @param[in] dp      disk partition object
83  * @param[in] volid   volume id
84  * @param[in] parent  parent volume id
85  *
86  * @pre VOL_LOCK is NOT held
87  *
88  * @note if the table is full, this routine will acquire
89  *       VOL_LOCK and flush the table to the global one.
90  *
91  * @return operation status
92  *    @retval 0 success
93  *    @retval nonzero a VVGCache_entry_add_r operation failed during a
94  *                    flush of the thread-local table
95  *
96  * @internal
97  */
98 static int
99 _VVGC_scan_table_add(VVGCache_scan_table_t * tbl,
100                      struct DiskPartition64 * dp,
101                      VolumeId volid,
102                      VolumeId parent)
103 {
104     int code = 0;
105
106     if (tbl->idx == VVGC_SCAN_TBL_LEN) {
107         code = _VVGC_scan_table_flush(tbl, dp);
108     }
109
110     tbl->entries[tbl->idx].volid = volid;
111     tbl->entries[tbl->idx].parent = parent;
112     tbl->idx++;
113
114     return code;
115 }
116
117 /**
118  * flush thread-local scan table to the global VG cache.
119  *
120  * @param[in] tbl     scan table
121  * @param[in] dp      disk partition object
122  *
123  * @pre VOL_LOCK is NOT held
124  *
125  * @return operation status
126  *    @retval 0 success
127  *    @retval nonzero a VVGCache_entry_add_r operation failed during a
128  *                    flush of the thread-local table
129  *
130  * @internal
131  */
132 static int
133 _VVGC_scan_table_flush(VVGCache_scan_table_t * tbl,
134                        struct DiskPartition64 * dp)
135 {
136     int code = 0, res, i;
137     afs_int32 newvg = 0;
138     unsigned long newvols, newvgs;
139
140     newvols = tbl->newvols;
141     newvgs = tbl->newvgs;
142
143     VOL_LOCK;
144
145     for (i = 0; i < tbl->idx; i++) {
146         /*
147          * We need to check the 'to-delete' list and prevent adding any entries
148          * that are on it. The volser could potentially create a volume in one
149          * VG, then delete it and put it on another VG. If we are doing a scan
150          * when that happens, tbl->entries could have the entries for trying to
151          * put the vol on both VGs, though at least one of them will also be on
152          * the dlist.  If we put everything in tbl->entries on the VGC then try
153          * to delete afterwards, putting one entry on the VGC cause an error,
154          * and we'll fail to add it. So instead, avoid adding any new VGC
155          * entries if it is on the dlist.
156          */
157         if (_VVGC_dlist_lookup_r(dp, tbl->entries[i].parent,
158                                  tbl->entries[i].volid)) {
159             continue;
160         }
161         res = VVGCache_entry_add_r(dp,
162                                    tbl->entries[i].parent,
163                                    tbl->entries[i].volid,
164                                    &newvg);
165         if (res) {
166             code = res;
167         } else {
168             newvols++;
169             newvgs += newvg;
170         }
171     }
172
173     /* flush the to-delete list while we're here. We don't need to preserve
174      * the list across the entire scan, and flushing it each time we flush
175      * a scan table will keep the size of the dlist down */
176     _VVGC_flush_dlist(dp);
177
178     VOL_UNLOCK;
179
180     ViceLog(125, ("VVGC_scan_table_flush: flushed %d entries from "
181                   "scan table to global VG cache\n", tbl->idx));
182     ViceLog(125, ("VVGC_scan_table_flush: %s total: %lu vols, %lu groups\n",
183                   VPartitionPath(dp), newvols, newvgs));
184
185     res = _VVGC_scan_table_init(tbl);
186     if (res) {
187         code = res;
188     }
189
190     tbl->newvols = newvols;
191     tbl->newvgs = newvgs;
192
193     return code;
194 }
195
196 /**
197  * record a volume header found by VWalkVolumeHeaders in a VGC scan table.
198  *
199  * @param[in] dp   the disk partition
200  * @param[in] name full path to the .vol header (unused)
201  * @param[in] hdr  the header data
202  * @param[in] last whether this is the last try or not (unused)
203  * @param[in] rock actually a VVGCache_scan_table_t* to add the volume to
204  *
205  * @return operation status
206  *  @retval 0  success
207  *  @retval -1 fatal error adding vol to the scan table
208  */
209 static int
210 _VVGC_RecordHeader(struct DiskPartition64 *dp, const char *name,
211                    struct VolumeDiskHeader *hdr, int last, void *rock)
212 {
213     int code;
214     VVGCache_scan_table_t *tbl;
215     tbl = (VVGCache_scan_table_t *)rock;
216
217     code = _VVGC_scan_table_add(tbl, dp, hdr->id, hdr->parent);
218     if (code) {
219         ViceLog(0, ("VVGC_scan_partition: error %d adding volume %s to scan table\n",
220                      code, name));
221         return -1;
222     }
223     return 0;
224 }
225
226 /**
227  * unlink a faulty volume header found by VWalkVolumeHeaders.
228  *
229  * @param[in] dp   the disk partition (unused)
230  * @param[in] name the full path to the .vol header
231  * @param[in] hdr  the header data (unused)
232  * @param[in] rock unused
233  */
234 static void
235 _VVGC_UnlinkHeader(struct DiskPartition64 *dp, const char *name,
236                    struct VolumeDiskHeader *hdr, void *rock)
237 {
238     ViceLog(0, ("%s is not a legitimate volume header file; deleted\n", name));
239     if (unlink(name)) {
240         ViceLog(0, ("Unable to unlink %s (errno = %d)\n",
241                     name, errno));
242     }
243 }
244
245 /**
246  * scan a disk partition for .vol files
247  *
248  * @param[in] part   disk partition object
249  *
250  * @pre VOL_LOCK is NOT held
251  *
252  * @return operation status
253  *    @retval 0 success
254  *    @retval -1 invalid disk partition object
255  *    @retval -2 failed to flush stale entries for this partition
256  *
257  * @internal
258  */
259 static int
260 _VVGC_scan_partition(struct DiskPartition64 * part)
261 {
262     int code, res;
263     DIR *dirp = NULL;
264     VVGCache_scan_table_t tbl;
265     char *part_path = NULL;
266
267     code = _VVGC_scan_table_init(&tbl);
268     if (code) {
269         ViceLog(0, ("VVGC_scan_partition: could not init scan table; error = %d\n",
270             code));
271         goto done;
272     }
273     part_path = VPartitionPath(part);
274     if (part_path == NULL) {
275         ViceLog(0, ("VVGC_scan_partition: invalid partition object given; aborting scan\n"));
276         code = -1;
277         goto done;
278     }
279
280     VOL_LOCK;
281     res = _VVGC_flush_part_r(part);
282     if (res) {
283         ViceLog(0, ("VVGC_scan_partition: error flushing partition %s; error = %d\n",
284             VPartitionPath(part), res));
285         code = -2;
286     }
287     VOL_UNLOCK;
288     if (code) {
289         goto done;
290     }
291
292     dirp = opendir(part_path);
293     if (dirp == NULL) {
294         ViceLog(0, ("VVGC_scan_partition: could not open %s, aborting scan; error = %d\n",
295             part_path, errno));
296         code = -1;
297         goto done;
298     }
299
300     ViceLog(5, ("VVGC_scan_partition: scanning partition %s for VG cache\n",
301                  part_path));
302
303     code = VWalkVolumeHeaders(part, part_path, _VVGC_RecordHeader,
304                               _VVGC_UnlinkHeader, &tbl);
305     if (code < 0) {
306         goto done;
307     }
308
309     _VVGC_scan_table_flush(&tbl, part);
310
311  done:
312     if (dirp) {
313         closedir(dirp);
314         dirp = NULL;
315     }
316     if (code) {
317         ViceLog(0, ("VVGC_scan_partition: error %d while scanning %s\n",
318                     code, part_path));
319     } else {
320         ViceLog(0, ("VVGC_scan_partition: finished scanning %s: %lu volumes in %lu groups\n",
321                      part_path, tbl.newvols, tbl.newvgs));
322     }
323
324     VOL_LOCK;
325
326     _VVGC_flush_dlist(part);
327     free(VVGCache.part[part->index].dlist_hash_buckets);
328     VVGCache.part[part->index].dlist_hash_buckets = NULL;
329
330     if (code) {
331         _VVGC_state_change(part, VVGC_PART_STATE_INVALID);
332     } else {
333         _VVGC_state_change(part, VVGC_PART_STATE_VALID);
334     }
335
336     VOL_UNLOCK;
337
338     return code;
339 }
340
341 /**
342  * scanner thread.
343  */
344 static void *
345 _VVGC_scanner_thread(void * args)
346 {
347     struct DiskPartition64 *part = args;
348     int code;
349
350     code = _VVGC_scan_partition(part);
351     if (code) {
352         ViceLog(0, ("Error: _VVGC_scan_partition failed with code %d for partition %s\n",
353             code, VPartitionPath(part)));
354     }
355
356     return NULL;
357 }
358
359 /**
360  * start a background scan.
361  *
362  * @param[in] dp  disk partition object
363  *
364  * @return operation status
365  *    @retval 0 success
366  *    @retval -1 internal error
367  *    @retval -3 racing against another thread
368  *
369  * @internal
370  */
371 int
372 _VVGC_scan_start(struct DiskPartition64 * dp)
373 {
374     int code = 0;
375     pthread_t tid;
376     pthread_attr_t attrs;
377     int i;
378
379     if (_VVGC_state_change(dp,
380                            VVGC_PART_STATE_UPDATING)
381         == VVGC_PART_STATE_UPDATING) {
382         /* race */
383         ViceLog(0, ("VVGC_scan_partition: race detected; aborting scanning partition %s\n",
384                     VPartitionPath(dp)));
385         code = -3;
386         goto error;
387     }
388
389     /* initialize partition's to-delete list */
390     VVGCache.part[dp->index].dlist_hash_buckets =
391         malloc(VolumeHashTable.Size * sizeof(struct rx_queue));
392     if (!VVGCache.part[dp->index].dlist_hash_buckets) {
393         code = -1;
394         goto error;
395     }
396     for (i = 0; i < VolumeHashTable.Size; i++) {
397         queue_Init(&VVGCache.part[dp->index].dlist_hash_buckets[i]);
398     }
399
400     code = pthread_attr_init(&attrs);
401     if (code) {
402         goto error;
403     }
404
405     code = pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
406     if (code) {
407         goto error;
408     }
409
410     code = pthread_create(&tid, &attrs, &_VVGC_scanner_thread, dp);
411
412     if (code) {
413         VVGCache_part_state_t old_state;
414
415         ViceLog(0, ("_VVGC_scan_start: pthread_create failed with %d\n", code));
416
417         old_state = _VVGC_state_change(dp, VVGC_PART_STATE_INVALID);
418         opr_Assert(old_state == VVGC_PART_STATE_UPDATING);
419     }
420
421  error:
422     if (code) {
423         ViceLog(0, ("_VVGC_scan_start failed with code %d for partition %s\n",
424                 code, VPartitionPath(dp)));
425         if (VVGCache.part[dp->index].dlist_hash_buckets) {
426             free(VVGCache.part[dp->index].dlist_hash_buckets);
427             VVGCache.part[dp->index].dlist_hash_buckets = NULL;
428         }
429     }
430
431     return code;
432 }
433
434 /**
435  * looks up an entry on the to-delete list, if it exists.
436  *
437  * @param[in] dp     the partition whose dlist we are looking at
438  * @param[in] parent the parent volume ID we're looking for
439  * @param[in] child  the child volume ID we're looking for
440  *
441  * @return a pointer to the entry in the dlist for that entry
442  *  @retval NULL the requested entry does not exist in the dlist
443  */
444 static VVGCache_dlist_entry_t *
445 _VVGC_dlist_lookup_r(struct DiskPartition64 *dp, VolumeId parent,
446                      VolumeId child)
447 {
448     int bucket = VVGC_HASH(child);
449     VVGCache_dlist_entry_t *ent, *nent;
450
451     for (queue_Scan(&VVGCache.part[dp->index].dlist_hash_buckets[bucket],
452                     ent, nent,
453                     VVGCache_dlist_entry)) {
454
455         if (ent->child == child && ent->parent == parent) {
456             return ent;
457         }
458     }
459
460     return NULL;
461 }
462
463 /**
464  * delete all of the entries in the dlist from the VGC.
465  *
466  * Traverses the to-delete list for the specified partition, and deletes
467  * the specified entries from the global VGC. Also deletes the entries from
468  * the dlist itself as it goes along.
469  *
470  * @param[in] dp  the partition whose dlist we are flushing
471  */
472 static void
473 _VVGC_flush_dlist(struct DiskPartition64 *dp)
474 {
475     int i;
476     VVGCache_dlist_entry_t *ent, *nent;
477
478     for (i = 0; i < VolumeHashTable.Size; i++) {
479         for (queue_Scan(&VVGCache.part[dp->index].dlist_hash_buckets[i],
480                         ent, nent,
481                         VVGCache_dlist_entry)) {
482
483             _VVGC_entry_purge_r(dp, ent->parent, ent->child);
484             queue_Remove(ent);
485             free(ent);
486         }
487     }
488 }
489
490 /**
491  * add a VGC entry to the partition's to-delete list.
492  *
493  * This adds a VGC entry (a parent/child pair) to a list of VGC entries to
494  * be deleted from the VGC at the end of a VGC scan. This is necessary,
495  * while a VGC scan is ocurring, volumes may be deleted. Since a VGC scan
496  * scans a partition in VVGC_SCAN_TBL_LEN chunks, a VGC delete operation
497  * may delete a volume, only for it to be added again when the VGC scan's
498  * table adds it to the VGC. So when a VGC entry is deleted and a VGC scan
499  * is running, this function must be called to ensure it does not come
500  * back onto the VGC.
501  *
502  * @param[in] dp      the partition to whose dlist we are adding
503  * @param[in] parent  the parent volumeID of the VGC entry
504  * @param[in] child   the child volumeID of the VGC entry
505  *
506  * @return operation status
507  *  @retval 0 success
508  *  @retval ENOMEM memory allocation error
509  *
510  * @pre VVGCache.part[dp->index].state == VVGC_PART_STATE_UPDATING
511  *
512  * @internal VGC use only
513  */
514 int
515 _VVGC_dlist_add_r(struct DiskPartition64 *dp, VolumeId parent,
516                   VolumeId child)
517 {
518     int bucket = VVGC_HASH(child);
519     VVGCache_dlist_entry_t *entry;
520
521     entry = malloc(sizeof(*entry));
522     if (!entry) {
523         return ENOMEM;
524     }
525
526     entry->child = child;
527     entry->parent = parent;
528
529     queue_Append(&VVGCache.part[dp->index].dlist_hash_buckets[bucket],
530                  entry);
531     return 0;
532 }
533
534 /**
535  * delete a VGC entry from the partition's to-delete list.
536  *
537  * When a VGC scan is ocurring, and a volume is removed, but then created
538  * again, we need to ensure that it does not get deleted from being on the
539  * dlist. Call this function whenever adding a new entry to the VGC during
540  * a VGC scan to ensure it doesn't get deleted later.
541  *
542  * @param[in] dp      the partition from whose dlist we are deleting
543  * @param[in] parent  the parent volumeID of the VGC entry
544  * @param[in] child   the child volumeID of the VGC entry
545  *
546  * @return operation status
547  *  @retval 0 success
548  *  @retval ENOENT the specified VGC entry is not on the dlist
549  *
550  * @pre VVGCache.part[dp->index].state == VVGC_PART_STATE_UPDATING
551  *
552  * @internal VGC use only
553  *
554  * @see _VVGC_dlist_add_r
555  */
556 int
557 _VVGC_dlist_del_r(struct DiskPartition64 *dp, VolumeId parent,
558                   VolumeId child)
559 {
560     VVGCache_dlist_entry_t *ent;
561
562     ent = _VVGC_dlist_lookup_r(dp, parent, child);
563     if (!ent) {
564         return ENOENT;
565     }
566
567     queue_Remove(ent);
568     free(ent);
569
570     return 0;
571 }
572
573 #endif /* AFS_DEMAND_ATTACH_FS */