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