]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2002, 2007 Red Hat, Inc. All rights reserved. | |
3 | * | |
4 | * This software may be freely redistributed under the terms of the | |
5 | * GNU General Public License. | |
6 | * | |
7 | * You should have received a copy of the GNU General Public License | |
8 | * along with this program; if not, write to the Free Software | |
9 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
10 | * | |
11 | * Authors: David Woodhouse <dwmw2@infradead.org> | |
12 | * David Howells <dhowells@redhat.com> | |
13 | * | |
14 | */ | |
15 | ||
16 | #include <linux/kernel.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/init.h> | |
19 | #include <linux/circ_buf.h> | |
20 | #include <linux/sched.h> | |
21 | #include "internal.h" | |
22 | ||
23 | /* | |
24 | * Set up an interest-in-callbacks record for a volume on a server and | |
25 | * register it with the server. | |
26 | * - Called with vnode->io_lock held. | |
27 | */ | |
28 | int afs_register_server_cb_interest(struct afs_vnode *vnode, | |
29 | struct afs_server_list *slist, | |
30 | unsigned int index) | |
31 | { | |
32 | struct afs_server_entry *entry = &slist->servers[index]; | |
33 | struct afs_cb_interest *cbi, *vcbi, *new, *old; | |
34 | struct afs_server *server = entry->server; | |
35 | ||
36 | again: | |
37 | if (vnode->cb_interest && | |
38 | likely(vnode->cb_interest == entry->cb_interest)) | |
39 | return 0; | |
40 | ||
41 | read_lock(&slist->lock); | |
42 | cbi = afs_get_cb_interest(entry->cb_interest); | |
43 | read_unlock(&slist->lock); | |
44 | ||
45 | vcbi = vnode->cb_interest; | |
46 | if (vcbi) { | |
47 | if (vcbi == cbi) { | |
48 | afs_put_cb_interest(afs_v2net(vnode), cbi); | |
49 | return 0; | |
50 | } | |
51 | ||
52 | /* Use a new interest in the server list for the same server | |
53 | * rather than an old one that's still attached to a vnode. | |
54 | */ | |
55 | if (cbi && vcbi->server == cbi->server) { | |
56 | write_seqlock(&vnode->cb_lock); | |
57 | old = vnode->cb_interest; | |
58 | vnode->cb_interest = cbi; | |
59 | write_sequnlock(&vnode->cb_lock); | |
60 | afs_put_cb_interest(afs_v2net(vnode), old); | |
61 | return 0; | |
62 | } | |
63 | ||
64 | /* Re-use the one attached to the vnode. */ | |
65 | if (!cbi && vcbi->server == server) { | |
66 | write_lock(&slist->lock); | |
67 | if (entry->cb_interest) { | |
68 | write_unlock(&slist->lock); | |
69 | afs_put_cb_interest(afs_v2net(vnode), cbi); | |
70 | goto again; | |
71 | } | |
72 | ||
73 | entry->cb_interest = cbi; | |
74 | write_unlock(&slist->lock); | |
75 | return 0; | |
76 | } | |
77 | } | |
78 | ||
79 | if (!cbi) { | |
80 | new = kzalloc(sizeof(struct afs_cb_interest), GFP_KERNEL); | |
81 | if (!new) | |
82 | return -ENOMEM; | |
83 | ||
84 | refcount_set(&new->usage, 1); | |
85 | new->sb = vnode->vfs_inode.i_sb; | |
86 | new->vid = vnode->volume->vid; | |
87 | new->server = afs_get_server(server); | |
88 | INIT_LIST_HEAD(&new->cb_link); | |
89 | ||
90 | write_lock(&server->cb_break_lock); | |
91 | list_add_tail(&new->cb_link, &server->cb_interests); | |
92 | write_unlock(&server->cb_break_lock); | |
93 | ||
94 | write_lock(&slist->lock); | |
95 | if (!entry->cb_interest) { | |
96 | entry->cb_interest = afs_get_cb_interest(new); | |
97 | cbi = new; | |
98 | new = NULL; | |
99 | } else { | |
100 | cbi = afs_get_cb_interest(entry->cb_interest); | |
101 | } | |
102 | write_unlock(&slist->lock); | |
103 | afs_put_cb_interest(afs_v2net(vnode), new); | |
104 | } | |
105 | ||
106 | ASSERT(cbi); | |
107 | ||
108 | /* Change the server the vnode is using. This entails scrubbing any | |
109 | * interest the vnode had in the previous server it was using. | |
110 | */ | |
111 | write_seqlock(&vnode->cb_lock); | |
112 | ||
113 | old = vnode->cb_interest; | |
114 | vnode->cb_interest = cbi; | |
115 | vnode->cb_s_break = cbi->server->cb_s_break; | |
116 | clear_bit(AFS_VNODE_CB_PROMISED, &vnode->flags); | |
117 | ||
118 | write_sequnlock(&vnode->cb_lock); | |
119 | afs_put_cb_interest(afs_v2net(vnode), old); | |
120 | return 0; | |
121 | } | |
122 | ||
123 | /* | |
124 | * Set a vnode's interest on a server. | |
125 | */ | |
126 | void afs_set_cb_interest(struct afs_vnode *vnode, struct afs_cb_interest *cbi) | |
127 | { | |
128 | struct afs_cb_interest *old_cbi = NULL; | |
129 | ||
130 | if (vnode->cb_interest == cbi) | |
131 | return; | |
132 | ||
133 | write_seqlock(&vnode->cb_lock); | |
134 | if (vnode->cb_interest != cbi) { | |
135 | afs_get_cb_interest(cbi); | |
136 | old_cbi = vnode->cb_interest; | |
137 | vnode->cb_interest = cbi; | |
138 | } | |
139 | write_sequnlock(&vnode->cb_lock); | |
140 | afs_put_cb_interest(afs_v2net(vnode), cbi); | |
141 | } | |
142 | ||
143 | /* | |
144 | * Remove an interest on a server. | |
145 | */ | |
146 | void afs_put_cb_interest(struct afs_net *net, struct afs_cb_interest *cbi) | |
147 | { | |
148 | if (cbi && refcount_dec_and_test(&cbi->usage)) { | |
149 | if (!list_empty(&cbi->cb_link)) { | |
150 | write_lock(&cbi->server->cb_break_lock); | |
151 | list_del_init(&cbi->cb_link); | |
152 | write_unlock(&cbi->server->cb_break_lock); | |
153 | afs_put_server(net, cbi->server); | |
154 | } | |
155 | kfree(cbi); | |
156 | } | |
157 | } | |
158 | ||
159 | /* | |
160 | * allow the fileserver to request callback state (re-)initialisation | |
161 | */ | |
162 | void afs_init_callback_state(struct afs_server *server) | |
163 | { | |
164 | if (!test_and_clear_bit(AFS_SERVER_FL_NEW, &server->flags)) | |
165 | server->cb_s_break++; | |
166 | } | |
167 | ||
168 | /* | |
169 | * actually break a callback | |
170 | */ | |
171 | void afs_break_callback(struct afs_vnode *vnode) | |
172 | { | |
173 | _enter(""); | |
174 | ||
175 | write_seqlock(&vnode->cb_lock); | |
176 | ||
177 | if (test_and_clear_bit(AFS_VNODE_CB_PROMISED, &vnode->flags)) { | |
178 | vnode->cb_break++; | |
179 | afs_clear_permits(vnode); | |
180 | ||
181 | if (vnode->lock_state == AFS_VNODE_LOCK_WAITING_FOR_CB) | |
182 | afs_lock_may_be_available(vnode); | |
183 | } | |
184 | ||
185 | write_sequnlock(&vnode->cb_lock); | |
186 | } | |
187 | ||
188 | /* | |
189 | * allow the fileserver to explicitly break one callback | |
190 | * - happens when | |
191 | * - the backing file is changed | |
192 | * - a lock is released | |
193 | */ | |
194 | static void afs_break_one_callback(struct afs_server *server, | |
195 | struct afs_fid *fid) | |
196 | { | |
197 | struct afs_cb_interest *cbi; | |
198 | struct afs_iget_data data; | |
199 | struct afs_vnode *vnode; | |
200 | struct inode *inode; | |
201 | ||
202 | read_lock(&server->cb_break_lock); | |
203 | ||
204 | /* Step through all interested superblocks. There may be more than one | |
205 | * because of cell aliasing. | |
206 | */ | |
207 | list_for_each_entry(cbi, &server->cb_interests, cb_link) { | |
208 | if (cbi->vid != fid->vid) | |
209 | continue; | |
210 | ||
211 | data.volume = NULL; | |
212 | data.fid = *fid; | |
213 | inode = ilookup5_nowait(cbi->sb, fid->vnode, afs_iget5_test, &data); | |
214 | if (inode) { | |
215 | vnode = AFS_FS_I(inode); | |
216 | afs_break_callback(vnode); | |
217 | iput(inode); | |
218 | } | |
219 | } | |
220 | ||
221 | read_unlock(&server->cb_break_lock); | |
222 | } | |
223 | ||
224 | /* | |
225 | * allow the fileserver to break callback promises | |
226 | */ | |
227 | void afs_break_callbacks(struct afs_server *server, size_t count, | |
228 | struct afs_callback callbacks[]) | |
229 | { | |
230 | _enter("%p,%zu,", server, count); | |
231 | ||
232 | ASSERT(server != NULL); | |
233 | ASSERTCMP(count, <=, AFSCBMAX); | |
234 | ||
235 | for (; count > 0; callbacks++, count--) { | |
236 | _debug("- Fid { vl=%08x n=%u u=%u } CB { v=%u x=%u t=%u }", | |
237 | callbacks->fid.vid, | |
238 | callbacks->fid.vnode, | |
239 | callbacks->fid.unique, | |
240 | callbacks->version, | |
241 | callbacks->expiry, | |
242 | callbacks->type | |
243 | ); | |
244 | afs_break_one_callback(server, &callbacks->fid); | |
245 | } | |
246 | ||
247 | _leave(""); | |
248 | return; | |
249 | } | |
250 | ||
251 | /* | |
252 | * Clear the callback interests in a server list. | |
253 | */ | |
254 | void afs_clear_callback_interests(struct afs_net *net, struct afs_server_list *slist) | |
255 | { | |
256 | int i; | |
257 | ||
258 | for (i = 0; i < slist->nr_servers; i++) { | |
259 | afs_put_cb_interest(net, slist->servers[i].cb_interest); | |
260 | slist->servers[i].cb_interest = NULL; | |
261 | } | |
262 | } |