vm: fix a null dereference on out-of-memory
[minix.git] / lib / libbdev / ipc.c
blobce6403e37471cf08ab120c93701133550f1e2cd7
1 /* libbdev - IPC and recovery functions */
3 #include <minix/drivers.h>
4 #include <minix/bdev.h>
5 #include <assert.h>
7 #include "const.h"
8 #include "type.h"
9 #include "proto.h"
11 static void bdev_cancel(dev_t dev)
13 /* Recovering the driver for the given device has failed repeatedly. Mark it as
14 * permanently unusable, and clean up any associated calls and resources.
16 bdev_call_t *call, *next;
18 printf("bdev: giving up on major %d\n", major(dev));
20 /* Cancel all pending asynchronous requests. */
21 call = NULL;
23 while ((call = bdev_call_iter_maj(dev, call, &next)) != NULL)
24 bdev_callback_asyn(call, EDEADSRCDST);
26 /* Mark the driver as unusable. */
27 bdev_driver_clear(dev);
30 static int bdev_recover(dev_t dev, int update_endpt)
32 /* The IPC subsystem has signaled an error communicating to the driver
33 * associated with the given device. Try to recover. If 'update_endpt' is set,
34 * we need to find the new endpoint of the driver first. Return TRUE iff
35 * recovery has been successful.
37 bdev_call_t *call, *next;
38 endpoint_t endpt;
39 int r, nr_tries;
41 printf("bdev: recovering from a driver restart on major %d\n", major(dev));
43 for (nr_tries = 0; nr_tries < RECOVER_TRIES; nr_tries++) {
44 /* First update the endpoint, if necessary. */
45 if (update_endpt)
46 (void) bdev_driver_update(dev);
48 if ((endpt = bdev_driver_get(dev)) == NONE)
49 break;
51 /* If anything goes wrong, update the endpoint again next time. */
52 update_endpt = TRUE;
54 /* Reopen all minor devices on the new driver. */
55 if ((r = bdev_minor_reopen(dev)) != OK) {
56 /* If the driver died again, we may give it another try. */
57 if (r == EDEADSRCDST)
58 continue;
60 /* If another error occurred, we cannot continue using the
61 * driver as is, but we also cannot force it to restart.
63 break;
66 /* Resend all asynchronous requests. */
67 call = NULL;
69 while ((call = bdev_call_iter_maj(dev, call, &next)) != NULL) {
70 /* It is not strictly necessary that we manage to reissue all
71 * asynchronous requests successfully. We can fail them on an
72 * individual basis here, without affecting the overall
73 * recovery. Note that we will never get new IPC failures here.
75 if ((r = bdev_restart_asyn(call)) != OK)
76 bdev_callback_asyn(call, r);
79 /* Recovery seems successful. We can now reissue the current
80 * synchronous request (if any), and continue normal operation.
82 printf("bdev: recovery successful, new driver is at %d\n", endpt);
84 return TRUE;
87 /* Recovery failed repeatedly. Give up on this driver. */
88 bdev_cancel(dev);
90 return FALSE;
93 void bdev_update(dev_t dev, char *label)
95 /* Set the endpoint for a driver. Perform recovery if necessary.
97 endpoint_t endpt, old_endpt;
99 old_endpt = bdev_driver_get(dev);
101 endpt = bdev_driver_set(dev, label);
103 /* If updating the driver causes an endpoint change, we need to perform
104 * recovery, but not update the endpoint yet again.
106 if (old_endpt != NONE && old_endpt != endpt)
107 bdev_recover(dev, FALSE /*update_endpt*/);
110 int bdev_senda(dev_t dev, const message *m_orig, bdev_id_t id)
112 /* Send an asynchronous request for the given device. This function will never
113 * get any new IPC errors sending to the driver. If sending an asynchronous
114 * request fails, we will find out through other ways later.
116 endpoint_t endpt;
117 message m;
118 int r;
120 /* If we have no usable driver endpoint, fail instantly. */
121 if ((endpt = bdev_driver_get(dev)) == NONE)
122 return EDEADSRCDST;
124 m = *m_orig;
125 m.BDEV_ID = id;
127 r = asynsend(endpt, &m);
129 if (r != OK)
130 printf("bdev: asynsend to driver (%d) failed (%d)\n", endpt, r);
132 return r;
135 int bdev_sendrec(dev_t dev, const message *m_orig)
137 /* Send a synchronous request for the given device, and wait for the reply.
138 * Return ERESTART if the caller should try to reissue the request.
140 endpoint_t endpt;
141 message m;
142 int r;
144 /* If we have no usable driver endpoint, fail instantly. */
145 if ((endpt = bdev_driver_get(dev)) == NONE)
146 return EDEADSRCDST;
148 /* Send the request and block until we receive a reply. */
149 m = *m_orig;
150 m.BDEV_ID = NO_ID;
152 r = sendrec(endpt, &m);
154 /* If communication failed, the driver has died. We assume it will be
155 * restarted soon after, so we attempt recovery. Upon success, we let the
156 * caller reissue the synchronous request.
158 if (r == EDEADSRCDST) {
159 if (!bdev_recover(dev, TRUE /*update_endpt*/))
160 return EDEADSRCDST;
162 return ERESTART;
165 if (r != OK) {
166 printf("bdev: IPC to driver (%d) failed (%d)\n", endpt, r);
167 return r;
170 if (m.m_type != BDEV_REPLY) {
171 printf("bdev: driver (%d) sent weird response (%d)\n",
172 endpt, m.m_type);
173 return EINVAL;
176 /* The protocol contract states that no asynchronous reply can satisfy a
177 * synchronous SENDREC call, so we can never get an asynchronous reply here.
179 if (m.BDEV_ID != NO_ID) {
180 printf("bdev: driver (%d) sent invalid ID (%ld)\n", endpt, m.BDEV_ID);
181 return EINVAL;
184 /* Unless the caller is misusing libbdev, we will only get ERESTART if we
185 * have managed to resend a raw block I/O request to the driver after a
186 * restart, but before VFS has had a chance to reopen the associated device
187 * first. This is highly exceptional, and hard to deal with correctly. We
188 * take the easiest route: sleep for a while so that VFS can reopen the
189 * device, and then resend the request. If the call keeps failing, the caller
190 * will eventually give up.
192 if (m.BDEV_STATUS == ERESTART) {
193 printf("bdev: got ERESTART from driver (%d), sleeping for reopen\n",
194 endpt);
196 micro_delay(1000);
198 return ERESTART;
201 /* Return the result of our request. */
202 return m.BDEV_STATUS;
205 static int bdev_receive(dev_t dev, message *m)
207 /* Receive one valid message.
209 endpoint_t endpt;
210 int r, nr_tries = 0;
212 for (;;) {
213 /* Retrieve and check the driver endpoint on every try, as it will
214 * change with each driver restart.
216 if ((endpt = bdev_driver_get(dev)) == NONE)
217 return EDEADSRCDST;
219 r = sef_receive(endpt, m);
221 if (r == EDEADSRCDST) {
222 /* If we reached the maximum number of retries, give up. */
223 if (++nr_tries == DRIVER_TRIES)
224 break;
226 /* Attempt recovery. If successful, all asynchronous requests
227 * will have been resent, and we can retry receiving a reply.
229 if (!bdev_recover(dev, TRUE /*update_endpt*/))
230 return EDEADSRCDST;
232 continue;
235 if (r != OK) {
236 printf("bdev: IPC to driver (%d) failed (%d)\n", endpt, r);
238 return r;
241 if (m->m_type != BDEV_REPLY) {
242 printf("bdev: driver (%d) sent weird response (%d)\n",
243 endpt, m->m_type);
244 return EINVAL;
247 /* The caller is responsible for checking the ID and status. */
248 return OK;
251 /* All tries failed, even though all recovery attempts succeeded. In this
252 * case, we let the caller recheck whether it wants to keep calling us,
253 * returning ERESTART to indicate we can be called again but did not actually
254 * receive a message.
256 return ERESTART;
259 void bdev_reply_asyn(message *m)
261 /* A reply has come in from a disk driver.
263 bdev_call_t *call;
264 endpoint_t endpt;
265 bdev_id_t id;
266 int r;
268 /* This is a requirement for the caller. */
269 assert(m->m_type == BDEV_REPLY);
271 /* Get the corresponding asynchronous call structure. */
272 id = m->BDEV_ID;
274 if ((call = bdev_call_get(id)) == NULL) {
275 printf("bdev: driver (%d) replied to unknown request (%ld)\n",
276 m->m_source, m->BDEV_ID);
277 return;
280 /* Make sure the reply was sent from the right endpoint. */
281 endpt = bdev_driver_get(call->dev);
283 if (m->m_source != endpt) {
284 /* If the endpoint is NONE, this may be a stray reply. */
285 if (endpt != NONE)
286 printf("bdev: driver (%d) replied to request not sent to it\n",
287 m->m_source);
288 return;
291 /* See the ERESTART comment in bdev_sendrec(). */
292 if (m->BDEV_STATUS == ERESTART) {
293 printf("bdev: got ERESTART from driver (%d), sleeping for reopen\n",
294 endpt);
296 micro_delay(1000);
298 if ((r = bdev_restart_asyn(call)) != OK)
299 bdev_callback_asyn(call, r);
301 return;
304 bdev_callback_asyn(call, m->BDEV_STATUS);
307 int bdev_wait_asyn(bdev_id_t id)
309 /* Wait for an asynchronous request to complete.
311 bdev_call_t *call;
312 dev_t dev;
313 message m;
314 int r;
316 if ((call = bdev_call_get(id)) == NULL)
317 return ENOENT;
319 dev = call->dev;
321 do {
322 if ((r = bdev_receive(dev, &m)) != OK && r != ERESTART)
323 return r;
325 /* Processing the reply will free up the call structure as a side
326 * effect. If we repeatedly get ERESTART, we will repeatedly resend the
327 * asynchronous request, which will then eventually hit the retry limit
328 * and we will break out of the loop.
330 if (r == OK)
331 bdev_reply_asyn(&m);
333 } while (bdev_call_get(id) != NULL);
335 return OK;