summaryrefslogtreecommitdiff
path: root/lib/filters/filter-persistent.c
blob: 212a5c1836b6cc1cbd2b7f0e575261d84a8a9203 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/*
 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
 * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
 *
 * This file is part of LVM2.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License v.2.1.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "base/memory/zalloc.h"
#include "lib/misc/lib.h"
#include "lib/filters/filter.h"
#include "lib/config/config.h"

struct pfilter {
	struct dm_hash_table *devices;
	struct dev_filter *real;
	struct dev_types *dt;
};

/*
 * The persistent filter is filter layer that sits above the other filters and
 * caches the final result of those other filters.  When a device is first
 * checked against filters, it will not be in this cache, so this filter will
 * pass the device down to the other filters to check it.  The other filters
 * will run and either include the device (good/pass) or exclude the device
 * (bad/fail).  That good or bad result propagates up through this filter which
 * saves the result.  The next time some code checks the filters against the
 * device, this persistent/cache filter is checked first.  This filter finds
 * the previous result in its cache and returns it without reevaluating the
 * other real filters.
 *
 * FIXME: a cache like this should not be needed.  The fact it's needed is a
 * symptom of code that should be fixed to not reevaluate filters multiple
 * times.  A device should be checked against the filter once, and then not
 * need to be checked again.  With scanning now controlled, we could probably
 * do this.
 */

static int _good_device;
static int _bad_device;

/*
 * The hash table holds one of these two states
 * against each entry.
 */
#define PF_BAD_DEVICE ((void *) &_good_device)
#define PF_GOOD_DEVICE ((void *) &_bad_device)

static int _init_hash(struct pfilter *pf)
{
	if (pf->devices)
		dm_hash_destroy(pf->devices);

	if (!(pf->devices = dm_hash_create(511)))
		return_0;

	return 1;
}

static void _persistent_filter_wipe(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
{
	struct pfilter *pf = (struct pfilter *) f->private;
	struct dm_str_list *sl;

	if (!dev) {
		dm_hash_wipe(pf->devices);
	} else {
		dm_list_iterate_items(sl, &dev->aliases)
			dm_hash_remove(pf->devices, sl->str);
	}
}

static int _lookup_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
{
	struct pfilter *pf = (struct pfilter *) f->private;
	void *l;
	struct dm_str_list *sl;
	int pass = 1;

	if (use_filter_name && strcmp(f->name, use_filter_name))
		return pf->real->passes_filter(cmd, pf->real, dev, use_filter_name);

	if (dm_list_empty(&dev->aliases)) {
		log_debug_devs("%d:%d: filter cache skipping (no name)",
				(int)MAJOR(dev->dev), (int)MINOR(dev->dev));
		return 0;
	}

	l = dm_hash_lookup(pf->devices, dev_name(dev));

	/* Cached bad, skip dev */
	if (l == PF_BAD_DEVICE) {
		log_debug_devs("%s: filter cache skipping (cached bad)", dev_name(dev));
		return 0;
	}

	/* Cached good, use dev */
	if (l == PF_GOOD_DEVICE) {
		log_debug_devs("%s: filter cache using (cached good)", dev_name(dev));
		return 1;
	}

	/* Uncached, check filters and cache the result */
	if (!l) {
		pass = pf->real->passes_filter(cmd, pf->real, dev, use_filter_name);

		if (!pass) {
			/*
			 * A device that does not pass one filter is excluded
			 * even if the result of another filter is deferred,
			 * because the deferred result won't change the exclude.
			 */
			l = PF_BAD_DEVICE;
		} else if (pass == 1) {
			l = PF_GOOD_DEVICE;
		} else {
			log_error("Ignore invalid filter result %d %s", pass, dev_name(dev));
			pass = 1;
			/* don't cache invalid result */
			goto out;
		}

		if (!dev->filtered_flags) /* skipping reason already logged by filter */
			log_debug_devs("filter caching %s %s", pass ? "good" : "bad", dev_name(dev));

		dm_list_iterate_items(sl, &dev->aliases)
			if (!dm_hash_insert(pf->devices, sl->str, l)) {
				log_error("Failed to hash alias to filter.");
				return 0;
			}
	}
 out:
	return pass;
}

static void _persistent_destroy(struct dev_filter *f)
{
	struct pfilter *pf = (struct pfilter *) f->private;

	if (f->use_count)
		log_error(INTERNAL_ERROR "Destroying persistent filter while in use %u times.", f->use_count);

	dm_hash_destroy(pf->devices);
	pf->real->destroy(pf->real);
	free(pf);
	free(f);
}

struct dev_filter *persistent_filter_create(struct dev_types *dt, struct dev_filter *real)
{
	struct pfilter *pf;
	struct dev_filter *f = NULL;

	if (!(pf = zalloc(sizeof(*pf)))) {
		log_error("Allocation of persistent filter failed.");
		return NULL;
	}

	pf->dt = dt;

	pf->real = real;

	if (!(_init_hash(pf))) {
		log_error("Couldn't create hash table for persistent filter.");
		goto bad;
	}

	if (!(f = zalloc(sizeof(*f)))) {
		log_error("Allocation of device filter for persistent filter failed.");
		goto bad;
	}

	f->passes_filter = _lookup_p;
	f->destroy = _persistent_destroy;
	f->use_count = 0;
	f->private = pf;
	f->wipe = _persistent_filter_wipe;
	f->name = "persistent";

	log_debug_devs("Persistent filter initialised.");

	return f;

      bad:
	if (pf->devices)
		dm_hash_destroy(pf->devices);
	free(pf);
	free(f);
	return NULL;
}