1 /*-
2 * Copyright (c) 1997, 1998, 1999
3 * Nan Yang Computer Services Limited. All rights reserved.
4 *
5 * Parts copyright (c) 1997, 1998 Cybernet Corporation, NetMAX project.
6 *
7 * Written by Greg Lehey
8 *
9 * This software is distributed under the so-called ``Berkeley
10 * License'':
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by Nan Yang Computer
23 * Services Limited.
24 * 4. Neither the name of the Company nor the names of its contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
27 *
28 * This software is provided ``as is'', and any express or implied
29 * warranties, including, but not limited to, the implied warranties of
30 * merchantability and fitness for a particular purpose are disclaimed.
31 * In no event shall the company or contributors be liable for any
32 * direct, indirect, incidental, special, exemplary, or consequential
33 * damages (including, but not limited to, procurement of substitute
34 * goods or services; loss of use, data, or profits; or business
35 * interruption) however caused and on any theory of liability, whether
36 * in contract, strict liability, or tort (including negligence or
37 * otherwise) arising in any way out of the use of this software, even if
38 * advised of the possibility of such damage.
39 *
40 * $Id: vinumrevive.c,v 1.19 2003/05/08 04:34:47 grog Exp grog $
41 */
42
43 #include <sys/cdefs.h>
44
45 __FBSDID("$FreeBSD$");
46 #include <dev/vinum/vinumhdr.h>
47 #include <dev/vinum/request.h>
48
49 /*
50 * Revive a block of a subdisk. Return an error
51 * indication. EAGAIN means successful copy, but
52 * that more blocks remain to be copied. EINVAL
53 * means that the subdisk isn't associated with a
54 * plex (which means a programming error if we get
55 * here at all; FIXME).
56 */
57
58 int
59 revive_block(int sdno)
60 {
61 int s; /* priority level */
62 struct sd *sd;
63 struct plex *plex;
64 struct volume *vol;
65 struct buf *bp;
66 int error = EAGAIN;
67 int size; /* size of revive block, bytes */
68 daddr_t plexblkno; /* lblkno in plex */
69 int psd; /* parity subdisk number */
70 u_int64_t stripe; /* stripe number */
71 int paritysd = 0; /* set if this is the parity stripe */
72 struct rangelock *lock; /* for locking */
73 daddr_t stripeoffset; /* offset in stripe */
74
75 plexblkno = 0; /* to keep the compiler happy */
76 sd = &SD[sdno];
77 lock = NULL;
78 if (sd->plexno < 0) /* no plex? */
79 return EINVAL;
80 plex = &PLEX[sd->plexno]; /* point to plex */
81 if (plex->volno >= 0)
82 vol = &VOL[plex->volno];
83 else
84 vol = NULL;
85
86 if ((sd->revive_blocksize == 0) /* no block size */
87 ||(sd->revive_blocksize & ((1 << DEV_BSHIFT) - 1))) /* or invalid block size */
88 sd->revive_blocksize = DEFAULT_REVIVE_BLOCKSIZE;
89 else if (sd->revive_blocksize > MAX_REVIVE_BLOCKSIZE)
90 sd->revive_blocksize = MAX_REVIVE_BLOCKSIZE;
91 size = min(sd->revive_blocksize >> DEV_BSHIFT, sd->sectors - sd->revived) << DEV_BSHIFT;
92 sd->reviver = curproc->p_pid; /* note who last had a bash at it */
93
94 /* Now decide where to read from */
95 switch (plex->organization) {
96 case plex_concat:
97 plexblkno = sd->revived + sd->plexoffset; /* corresponding address in plex */
98 break;
99
100 case plex_striped:
101 stripeoffset = sd->revived % plex->stripesize; /* offset from beginning of stripe */
102 if (stripeoffset + (size >> DEV_BSHIFT) > plex->stripesize)
103 size = (plex->stripesize - stripeoffset) << DEV_BSHIFT;
104 plexblkno = sd->plexoffset /* base */
105 + (sd->revived - stripeoffset) * plex->subdisks /* offset to beginning of stripe */
106 + stripeoffset; /* offset from beginning of stripe */
107 break;
108
109 case plex_raid4:
110 case plex_raid5:
111 stripeoffset = sd->revived % plex->stripesize; /* offset from beginning of stripe */
112 plexblkno = sd->plexoffset /* base */
113 + (sd->revived - stripeoffset) * (plex->subdisks - 1) /* offset to beginning of stripe */
114 +stripeoffset; /* offset from beginning of stripe */
115 stripe = (sd->revived / plex->stripesize); /* stripe number */
116
117 /* Make sure we don't go beyond the end of the band. */
118 size = min(size, (plex->stripesize - stripeoffset) << DEV_BSHIFT);
119 if (plex->organization == plex_raid4)
120 psd = plex->subdisks - 1; /* parity subdisk for this stripe */
121 else
122 psd = plex->subdisks - 1 - stripe % plex->subdisks; /* parity subdisk for this stripe */
123 paritysd = plex->sdnos[psd] == sdno; /* note if it's the parity subdisk */
124
125 /*
126 * Now adjust for the strangenesses
127 * in RAID-4 and RAID-5 striping.
128 */
129 if (sd->plexsdno > psd) /* beyond the parity stripe, */
130 plexblkno -= plex->stripesize; /* one stripe less */
131 else if (paritysd)
132 plexblkno -= plex->stripesize * sd->plexsdno; /* go back to the beginning of the band */
133 break;
134
135 case plex_disorg: /* to keep the compiler happy */
136 break; /* to keep the pedants happy */
137 }
138
139 if (paritysd) { /* we're reviving a parity block, */
140 bp = parityrebuild(plex, sd->revived, size, rebuildparity, &lock, NULL); /* do the grunt work */
141 if (bp == NULL) /* no buffer space */
142 return ENOMEM; /* chicken out */
143 } else { /* data block */
144 s = splbio();
145 bp = geteblk(size); /* Get a buffer */
146 splx(s);
147 if (bp == NULL)
148 return ENOMEM;
149
150 /*
151 * Amount to transfer: block size, unless it
152 * would overlap the end.
153 */
154 bp->b_bcount = size;
155 bp->b_resid = bp->b_bcount;
156 bp->b_blkno = plexblkno; /* start here */
157 if (isstriped(plex)) /* we need to lock striped plexes */
158 lock = lockrange(plexblkno << DEV_BSHIFT, bp, plex); /* lock it */
159 if (vol != NULL) /* it's part of a volume, */
160 /*
161 * First, read the data from the volume. We
162 * don't care which plex, that's bre's job.
163 */
164 bp->b_dev = VOL[plex->volno].dev; /* create the device number */
165 else /* it's an unattached plex */
166 bp->b_dev = PLEX[sd->plexno].dev; /* create the device number */
167
168 bp->b_iocmd = BIO_READ; /* either way, read it */
169 bp->b_flags = 0;
170 vinumstart(bp, 1);
171 bufwait(bp);
172 }
173
174 if (bp->b_ioflags & BIO_ERROR) {
175 error = bp->b_error;
176 if (lock) /* we took a lock, */
177 unlockrange(sd->plexno, lock); /* give it back */
178 } else
179 /* Now write to the subdisk */
180 {
181 bp->b_dev = SD[sdno].dev; /* create the device number */
182 bp->b_flags &= ~B_DONE; /* no longer done */
183 bp->b_ioflags = 0;
184 bp->b_iocmd = BIO_WRITE;
185 bp->b_resid = bp->b_bcount;
186 bp->b_blkno = sd->revived; /* write it to here */
187 sdio(bp); /* perform the I/O */
188 bufwait(bp);
189 if (bp->b_ioflags & BIO_ERROR)
190 error = bp->b_error;
191 else {
192 sd->revived += bp->b_bcount >> DEV_BSHIFT; /* moved this much further down */
193 if (sd->revived >= sd->sectors) { /* finished */
194 sd->revived = 0;
195 set_sd_state(sdno, sd_up, setstate_force); /* bring the sd up */
196 log(LOG_INFO, "vinum: %s is %s\n", sd->name, sd_state(sd->state));
197 save_config(); /* and save the updated configuration */
198 error = 0; /* we're done */
199 }
200 }
201 if (lock) /* we took a lock, */
202 unlockrange(sd->plexno, lock); /* give it back */
203 while (sd->waitlist) { /* we have waiting requests */
204 #ifdef VINUMDEBUG
205 struct request *rq = sd->waitlist;
206
207 if (debug & DEBUG_REVIVECONFLICT)
208 log(LOG_DEBUG,
209 "Relaunch revive conflict sd %d: %p\n%s dev %d.%d, offset 0x%jx, length %ld\n",
210 rq->sdno,
211 rq,
212 rq->bp->b_iocmd == BIO_READ ? "Read" : "Write",
213 major(rq->bp->b_dev),
214 minor(rq->bp->b_dev),
215 (intmax_t) rq->bp->b_blkno,
216 rq->bp->b_bcount);
217 #endif
218 launch_requests(sd->waitlist, 1); /* do them now */
219 sd->waitlist = sd->waitlist->next; /* and move on to the next */
220 }
221 }
222 if (bp->b_qindex == 0) { /* not on a queue, */
223 bp->b_flags |= B_INVAL;
224 bp->b_ioflags &= ~BIO_ERROR;
225 brelse(bp); /* is this kosher? */
226 }
227 return error;
228 }
229
230 /*
231 * Check or rebuild the parity blocks of a RAID-4
232 * or RAID-5 plex.
233 *
234 * The variables plex->checkblock and
235 * plex->rebuildblock represent the
236 * subdisk-relative address of the stripe we're
237 * looking at, not the plex-relative address. We
238 * store it in the plex and not as a local
239 * variable because this function could be
240 * stopped, and we don't want to repeat the part
241 * we've already done. This is also the reason
242 * why we don't initialize it here except at the
243 * end. It gets initialized with the plex on
244 * creation.
245 *
246 * Each call to this function processes at most
247 * one stripe. We can't loop in this function,
248 * because we're unstoppable, so we have to be
249 * called repeatedly from userland.
250 */
251 void
252 parityops(struct vinum_ioctl_msg *data)
253 {
254 int plexno;
255 struct plex *plex;
256 int size; /* I/O transfer size, bytes */
257 int stripe; /* stripe number in plex */
258 int psd; /* parity subdisk number */
259 struct rangelock *lock; /* lock on stripe */
260 struct _ioctl_reply *reply;
261 off_t pstripe; /* pointer to our stripe counter */
262 struct buf *pbp;
263 off_t errorloc; /* offset of parity error */
264 enum parityop op; /* operation to perform */
265
266 plexno = data->index;
267 op = data->op;
268 pbp = NULL;
269 reply = (struct _ioctl_reply *) data;
270 reply->error = EAGAIN; /* expect to repeat this call */
271 plex = &PLEX[plexno];
272 if (!isparity(plex)) { /* not RAID-4 or RAID-5 */
273 reply->error = EINVAL;
274 return;
275 } else if (plex->state < plex_flaky) {
276 reply->error = EIO;
277 strcpy(reply->msg, "Plex is not completely accessible\n");
278 return;
279 }
280 pstripe = data->offset;
281 stripe = pstripe / plex->stripesize; /* stripe number */
282 psd = plex->subdisks - 1 - stripe % plex->subdisks; /* parity subdisk for this stripe */
283 size = min(DEFAULT_REVIVE_BLOCKSIZE, /* one block at a time */
284 plex->stripesize << DEV_BSHIFT);
285
286 pbp = parityrebuild(plex, pstripe, size, op, &lock, &errorloc); /* do the grunt work */
287 if (pbp == NULL) { /* no buffer space */
288 reply->error = ENOMEM;
289 return; /* chicken out */
290 }
291 /*
292 * Now we have a result in the data buffer of
293 * the parity buffer header, which we have kept.
294 * Decide what to do with it.
295 */
296 reply->msg[0] = '\0'; /* until shown otherwise */
297 if ((pbp->b_ioflags & BIO_ERROR) == 0) { /* no error */
298 if ((op == rebuildparity)
299 || (op == rebuildandcheckparity)) {
300 pbp->b_iocmd = BIO_WRITE;
301 pbp->b_resid = pbp->b_bcount;
302 sdio(pbp); /* write the parity block */
303 bufwait(pbp);
304 }
305 if (((op == checkparity)
306 || (op == rebuildandcheckparity))
307 && (errorloc != -1)) {
308 if (op == checkparity)
309 reply->error = EIO;
310 sprintf(reply->msg,
311 "Parity incorrect at offset 0x%jx\n",
312 (intmax_t) errorloc);
313 }
314 if (reply->error == EAGAIN) { /* still OK, */
315 plex->checkblock = pstripe + (pbp->b_bcount >> DEV_BSHIFT); /* moved this much further down */
316 if (plex->checkblock >= SD[plex->sdnos[0]].sectors) { /* finished */
317 plex->checkblock = 0;
318 reply->error = 0;
319 }
320 }
321 }
322 if (pbp->b_ioflags & BIO_ERROR)
323 reply->error = pbp->b_error;
324 pbp->b_flags |= B_INVAL;
325 pbp->b_ioflags &= ~BIO_ERROR;
326 brelse(pbp);
327 unlockrange(plexno, lock);
328 }
329
330 /*
331 * Rebuild a parity stripe. Return pointer to
332 * parity bp. On return,
333 *
334 * 1. The band is locked. The caller must unlock
335 * the band and release the buffer header.
336 *
337 * 2. All buffer headers except php have been
338 * released. The caller must release pbp.
339 *
340 * 3. For checkparity and rebuildandcheckparity,
341 * the parity is compared with the current
342 * parity block. If it's different, the
343 * offset of the error is returned to
344 * errorloc. The caller can set the value of
345 * the pointer to NULL if this is called for
346 * rebuilding parity.
347 *
348 * pstripe is the subdisk-relative base address of
349 * the data to be reconstructed, size is the size
350 * of the transfer in bytes.
351 */
352 struct buf *
353 parityrebuild(struct plex *plex,
354 u_int64_t pstripe,
355 int size,
356 enum parityop op,
357 struct rangelock **lockp,
358 off_t * errorloc)
359 {
360 int error;
361 int s;
362 int sdno;
363 u_int64_t stripe; /* stripe number */
364 int *parity_buf; /* buffer address for current parity block */
365 int *newparity_buf; /* and for new parity block */
366 int mysize; /* I/O transfer size for this transfer */
367 int isize; /* mysize in ints */
368 int i;
369 int psd; /* parity subdisk number */
370 int newpsd; /* and "subdisk number" of new parity */
371 struct buf **bpp; /* pointers to our bps */
372 struct buf *pbp; /* buffer header for parity stripe */
373 int *sbuf;
374 int bufcount; /* number of buffers we need */
375
376 stripe = pstripe / plex->stripesize; /* stripe number */
377 psd = plex->subdisks - 1 - stripe % plex->subdisks; /* parity subdisk for this stripe */
378 parity_buf = NULL; /* to keep the compiler happy */
379 error = 0;
380
381 /*
382 * It's possible that the default transfer size
383 * we chose is not a factor of the stripe size.
384 * We *must* limit this operation to a single
385 * stripe, at least for RAID-5 rebuild, since
386 * the parity subdisk changes between stripes,
387 * so in this case we need to perform a short
388 * transfer. Set variable mysize to reflect
389 * this.
390 */
391 mysize = min(size, (plex->stripesize * (stripe + 1) - pstripe) << DEV_BSHIFT);
392 isize = mysize / (sizeof(int)); /* number of ints in the buffer */
393 bufcount = plex->subdisks + 1; /* sd buffers plus result buffer */
394 newpsd = plex->subdisks;
395 bpp = (struct buf **) Malloc(bufcount * sizeof(struct buf *)); /* array of pointers to bps */
396
397 /* First, build requests for all subdisks */
398 for (sdno = 0; sdno < bufcount; sdno++) { /* for each subdisk */
399 if ((sdno != psd) || (op != rebuildparity)) {
400 /* Get a buffer header and initialize it. */
401 s = splbio();
402 bpp[sdno] = geteblk(mysize); /* Get a buffer */
403 if (bpp[sdno] == NULL) {
404 while (sdno-- > 0) { /* release the ones we got */
405 bpp[sdno]->b_flags |= B_INVAL;
406 brelse(bpp[sdno]); /* give back our resources */
407 }
408 splx(s);
409 printf("vinum: can't allocate buffer space for parity op.\n");
410 return NULL; /* no bpps */
411 }
412 splx(s);
413 if (sdno == psd)
414 parity_buf = (int *) bpp[sdno]->b_data;
415 if (sdno == newpsd) /* the new one? */
416 bpp[sdno]->b_dev = SD[plex->sdnos[psd]].dev; /* write back to the parity SD */
417 else
418 bpp[sdno]->b_dev = SD[plex->sdnos[sdno]].dev; /* device number */
419 bpp[sdno]->b_iocmd = BIO_READ; /* either way, read it */
420 bpp[sdno]->b_flags = 0;
421 bpp[sdno]->b_bcount = mysize;
422 bpp[sdno]->b_resid = bpp[sdno]->b_bcount;
423 bpp[sdno]->b_blkno = pstripe; /* transfer from here */
424 }
425 }
426
427 /* Initialize result buffer */
428 pbp = bpp[newpsd];
429 newparity_buf = (int *) bpp[newpsd]->b_data;
430 bzero(newparity_buf, mysize);
431
432 /*
433 * Now lock the stripe with the first non-parity
434 * bp as locking bp.
435 */
436 *lockp = lockrange(pstripe * plex->stripesize * (plex->subdisks - 1),
437 bpp[psd ? 0 : 1],
438 plex);
439
440 /*
441 * Then issue requests for all subdisks in
442 * parallel. Don't transfer the parity stripe
443 * if we're rebuilding parity, unless we also
444 * want to check it.
445 */
446 for (sdno = 0; sdno < plex->subdisks; sdno++) { /* for each real subdisk */
447 if ((sdno != psd) || (op != rebuildparity)) {
448 sdio(bpp[sdno]);
449 }
450 }
451
452 /*
453 * Next, wait for the requests to complete.
454 * We wait in the order in which they were
455 * issued, which isn't necessarily the order in
456 * which they complete, but we don't have a
457 * convenient way of doing the latter, and the
458 * delay is minimal.
459 */
460 for (sdno = 0; sdno < plex->subdisks; sdno++) { /* for each subdisk */
461 if ((sdno != psd) || (op != rebuildparity)) {
462 bufwait(bpp[sdno]);
463 if (bpp[sdno]->b_ioflags & BIO_ERROR) /* can't read, */
464 error = bpp[sdno]->b_error;
465 else if (sdno != psd) { /* update parity */
466 sbuf = (int *) bpp[sdno]->b_data;
467 for (i = 0; i < isize; i++)
468 ((int *) newparity_buf)[i] ^= sbuf[i]; /* xor in the buffer */
469 }
470 }
471 if (sdno != psd) { /* release all bps except parity */
472 bpp[sdno]->b_flags |= B_INVAL;
473 brelse(bpp[sdno]); /* give back our resources */
474 }
475 }
476
477 /*
478 * If we're checking, compare the calculated
479 * and the read parity block. If they're
480 * different, return the plex-relative offset;
481 * otherwise return -1.
482 */
483 if ((op == checkparity)
484 || (op == rebuildandcheckparity)) {
485 *errorloc = -1; /* no error yet */
486 for (i = 0; i < isize; i++) {
487 if (parity_buf[i] != newparity_buf[i]) {
488 *errorloc = (off_t) (pstripe << DEV_BSHIFT) * (plex->subdisks - 1)
489 + i * sizeof(int);
490 break;
491 }
492 }
493 bpp[psd]->b_flags |= B_INVAL;
494 brelse(bpp[psd]); /* give back our resources */
495 }
496 /* release our resources */
497 Free(bpp);
498 if (error) {
499 pbp->b_ioflags |= BIO_ERROR;
500 pbp->b_error = error;
501 }
502 return pbp;
503 }
504
505 /*
506 * Initialize a subdisk by writing zeroes to the
507 * complete address space. If verify is set,
508 * check each transfer for correctness.
509 *
510 * Each call to this function writes (and maybe
511 * checks) a single block.
512 */
513 int
514 initsd(int sdno, int verify)
515 {
516 int s; /* priority level */
517 struct sd *sd;
518 struct plex *plex;
519 struct volume *vol;
520 struct buf *bp;
521 int error;
522 int size; /* size of init block, bytes */
523 daddr_t plexblkno; /* lblkno in plex */
524 int verified; /* set when we're happy with what we wrote */
525
526 error = 0;
527 plexblkno = 0; /* to keep the compiler happy */
528 sd = &SD[sdno];
529 if (sd->plexno < 0) /* no plex? */
530 return EINVAL;
531 plex = &PLEX[sd->plexno]; /* point to plex */
532 if (plex->volno >= 0)
533 vol = &VOL[plex->volno];
534 else
535 vol = NULL;
536
537 if (sd->init_blocksize == 0) {
538 sd->init_blocksize = DEFAULT_REVIVE_BLOCKSIZE;
539 } else if (sd->init_blocksize > MAX_REVIVE_BLOCKSIZE)
540 sd->init_blocksize = MAX_REVIVE_BLOCKSIZE;
541
542 size = min(sd->init_blocksize >> DEV_BSHIFT, sd->sectors - sd->initialized) << DEV_BSHIFT;
543
544 verified = 0;
545 while (!verified) { /* until we're happy with it, */
546 s = splbio();
547 bp = geteblk(size); /* Get a buffer */
548 splx(s);
549 if (bp == NULL)
550 return ENOMEM;
551
552 bp->b_bcount = size;
553 bp->b_resid = bp->b_bcount;
554 bp->b_blkno = sd->initialized; /* write it to here */
555 bzero(bp->b_data, bp->b_bcount);
556 bp->b_dev = SD[sdno].dev; /* create the device number */
557 bp->b_iocmd = BIO_WRITE;
558 sdio(bp); /* perform the I/O */
559 bufwait(bp);
560 if (bp->b_ioflags & BIO_ERROR)
561 error = bp->b_error;
562 if (bp->b_qindex == 0) { /* not on a queue, */
563 bp->b_flags |= B_INVAL;
564 bp->b_ioflags &= ~BIO_ERROR;
565 brelse(bp); /* is this kosher? */
566 }
567 if ((error == 0) && verify) { /* check that it got there */
568 s = splbio();
569 bp = geteblk(size); /* get a buffer */
570 if (bp == NULL) {
571 splx(s);
572 error = ENOMEM;
573 } else {
574 bp->b_bcount = size;
575 bp->b_resid = bp->b_bcount;
576 bp->b_blkno = sd->initialized; /* read from here */
577 bp->b_dev = SD[sdno].dev; /* create the device number */
578 bp->b_iocmd = BIO_READ; /* read it back */
579 splx(s);
580 sdio(bp);
581 bufwait(bp);
582 /*
583 * XXX Bug fix code. This is hopefully no
584 * longer needed (21 February 2000).
585 */
586 if (bp->b_ioflags & BIO_ERROR)
587 error = bp->b_error;
588 else if ((*bp->b_data != 0) /* first word spammed */
589 ||(bcmp(bp->b_data, &bp->b_data[1], bp->b_bcount - 1))) { /* or one of the others */
590 printf("vinum: init error on %s, offset 0x%llx sectors\n",
591 sd->name,
592 (long long) sd->initialized);
593 verified = 0;
594 } else
595 verified = 1;
596 if (bp->b_qindex == 0) { /* not on a queue, */
597 bp->b_flags |= B_INVAL;
598 bp->b_ioflags &= ~BIO_ERROR;
599 brelse(bp); /* is this kosher? */
600 }
601 }
602 } else
603 verified = 1;
604 }
605 if (error == 0) { /* did it, */
606 sd->initialized += size >> DEV_BSHIFT; /* moved this much further down */
607 if (sd->initialized >= sd->sectors) { /* finished */
608 sd->initialized = 0;
609 set_sd_state(sdno, sd_initialized, setstate_force); /* bring the sd up */
610 log(LOG_INFO, "vinum: %s is %s\n", sd->name, sd_state(sd->state));
611 save_config(); /* and save the updated configuration */
612 } else /* more to go, */
613 error = EAGAIN; /* ya'll come back, see? */
614 }
615 return error;
616 }
617
618 /* Local Variables: */
619 /* fill-column: 50 */
620 /* End: */
Cache object: 19b4b8774a6c11a620ce9ecc05c583dc
|