|
|
1.1 root 1: /*
2: * Copyright (c) 1982, 1986, 1989 Regents of the University of California.
3: * All rights reserved.
4: *
5: * Redistribution is only permitted until one year after the first shipment
6: * of 4.4BSD by the Regents. Otherwise, redistribution and use in source and
7: * binary forms are permitted provided that: (1) source distributions retain
8: * this entire copyright notice and comment, and (2) distributions including
9: * binaries display the following acknowledgement: This product includes
10: * software developed by the University of California, Berkeley and its
11: * contributors'' in the documentation or other materials provided with the
12: * distribution and in all advertising materials mentioning features or use
13: * of this software. Neither the name of the University nor the names of
14: * its contributors may be used to endorse or promote products derived from
15: * this software without specific prior written permission.
16: * THIS SOFTWARE IS PROVIDED AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
17: * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
18: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19: *
20: * @(#)vfs_bio.c 7.30 (Berkeley) 6/28/90
21: */
22:
23: #include "param.h"
24: #include "user.h"
25: #include "buf.h"
26: #include "vnode.h"
27: #include "specdev.h"
28: #include "mount.h"
29: #include "trace.h"
30: #include "ucred.h"
31:
32: /*
33: * Read in (if necessary) the block and return a buffer pointer.
34: */
35: bread(vp, blkno, size, cred, bpp)
36: struct vnode *vp;
37: daddr_t blkno;
38: int size;
39: struct ucred *cred;
40: struct buf **bpp;
41: {
42: register struct buf *bp;
43:
44: if (size == 0)
45: panic("bread: size 0");
46: *bpp = bp = getblk(vp, blkno, size);
47: if (bp->b_flags&(B_DONE|B_DELWRI)) {
48: trace(TR_BREADHIT, pack(vp, size), blkno);
49: return (0);
50: }
51: bp->b_flags |= B_READ;
52: if (bp->b_bcount > bp->b_bufsize)
53: panic("bread");
54: if (bp->b_rcred == NOCRED && cred != NOCRED) {
55: crhold(cred);
56: bp->b_rcred = cred;
57: }
58: VOP_STRATEGY(bp);
59: trace(TR_BREADMISS, pack(vp, size), blkno);
60: u.u_ru.ru_inblock++; /* pay for read */
61: return (biowait(bp));
62: }
63:
64: /*
65: * Read in the block, like bread, but also start I/O on the
66: * read-ahead block (which is not allocated to the caller)
67: */
68: breada(vp, blkno, size, rablkno, rabsize, cred, bpp)
69: struct vnode *vp;
70: daddr_t blkno; int size;
71: daddr_t rablkno; int rabsize;
72: struct ucred *cred;
73: struct buf **bpp;
74: {
75: register struct buf *bp, *rabp;
76:
77: bp = NULL;
78: /*
79: * If the block isn't in core, then allocate
80: * a buffer and initiate i/o (getblk checks
81: * for a cache hit).
82: */
83: if (!incore(vp, blkno)) {
84: *bpp = bp = getblk(vp, blkno, size);
85: if ((bp->b_flags&(B_DONE|B_DELWRI)) == 0) {
86: bp->b_flags |= B_READ;
87: if (bp->b_bcount > bp->b_bufsize)
88: panic("breada");
89: if (bp->b_rcred == NOCRED && cred != NOCRED) {
90: crhold(cred);
91: bp->b_rcred = cred;
92: }
93: VOP_STRATEGY(bp);
94: trace(TR_BREADMISS, pack(vp, size), blkno);
95: u.u_ru.ru_inblock++; /* pay for read */
96: } else
97: trace(TR_BREADHIT, pack(vp, size), blkno);
98: }
99:
100: /*
101: * If there's a read-ahead block, start i/o
102: * on it also (as above).
103: */
104: if (!incore(vp, rablkno)) {
105: rabp = getblk(vp, rablkno, rabsize);
106: if (rabp->b_flags & (B_DONE|B_DELWRI)) {
107: brelse(rabp);
108: trace(TR_BREADHITRA, pack(vp, rabsize), rablkno);
109: } else {
110: rabp->b_flags |= B_READ|B_ASYNC;
111: if (rabp->b_bcount > rabp->b_bufsize)
112: panic("breadrabp");
113: if (rabp->b_rcred == NOCRED && cred != NOCRED) {
114: crhold(cred);
115: rabp->b_rcred = cred;
116: }
117: VOP_STRATEGY(rabp);
118: trace(TR_BREADMISSRA, pack(vp, rabsize), rablkno);
119: u.u_ru.ru_inblock++; /* pay in advance */
120: }
121: }
122:
123: /*
124: * If block was in core, let bread get it.
125: * If block wasn't in core, then the read was started
126: * above, and just wait for it.
127: */
128: if (bp == NULL)
129: return (bread(vp, blkno, size, cred, bpp));
130: return (biowait(bp));
131: }
132:
133: /*
134: * Write the buffer, waiting for completion.
135: * Then release the buffer.
136: */
137: bwrite(bp)
138: register struct buf *bp;
139: {
140: register int flag;
141: int s, error;
142:
143: flag = bp->b_flags;
144: bp->b_flags &= ~(B_READ | B_DONE | B_ERROR | B_DELWRI);
145: if ((flag&B_DELWRI) == 0)
146: u.u_ru.ru_oublock++; /* noone paid yet */
147: else
148: reassignbuf(bp, bp->b_vp);
149: trace(TR_BWRITE, pack(bp->b_vp, bp->b_bcount), bp->b_lblkno);
150: if (bp->b_bcount > bp->b_bufsize)
151: panic("bwrite");
152: s = splbio();
153: bp->b_vp->v_numoutput++;
154: splx(s);
155: VOP_STRATEGY(bp);
156:
157: /*
158: * If the write was synchronous, then await i/o completion.
159: * If the write was "delayed", then we put the buffer on
160: * the q of blocks awaiting i/o completion status.
161: */
162: if ((flag&B_ASYNC) == 0) {
163: error = biowait(bp);
164: brelse(bp);
165: } else if (flag & B_DELWRI) {
166: bp->b_flags |= B_AGE;
167: error = 0;
168: }
169: return (error);
170: }
171:
172: /*
173: * Release the buffer, marking it so that if it is grabbed
174: * for another purpose it will be written out before being
175: * given up (e.g. when writing a partial block where it is
176: * assumed that another write for the same block will soon follow).
177: * This can't be done for magtape, since writes must be done
178: * in the same order as requested.
179: */
180: bdwrite(bp)
181: register struct buf *bp;
182: {
183:
184: if ((bp->b_flags & B_DELWRI) == 0) {
185: bp->b_flags |= B_DELWRI;
186: reassignbuf(bp, bp->b_vp);
187: u.u_ru.ru_oublock++; /* noone paid yet */
188: }
189: /*
190: * If this is a tape drive, the write must be initiated.
191: */
192: if (VOP_IOCTL(bp->b_vp, 0, B_TAPE, 0, NOCRED) == 0) {
193: bawrite(bp);
194: } else {
195: bp->b_flags |= B_DELWRI | B_DONE;
196: brelse(bp);
197: }
198: }
199:
200: /*
201: * Release the buffer, start I/O on it, but don't wait for completion.
202: */
203: bawrite(bp)
204: register struct buf *bp;
205: {
206:
207: bp->b_flags |= B_ASYNC;
208: (void) bwrite(bp);
209: }
210:
211: /*
212: * Release the buffer, with no I/O implied.
213: */
214: brelse(bp)
215: register struct buf *bp;
216: {
217: register struct buf *flist;
218: register s;
219:
220: trace(TR_BRELSE, pack(bp->b_vp, bp->b_bufsize), bp->b_lblkno);
221: /*
222: * If a process is waiting for the buffer, or
223: * is waiting for a free buffer, awaken it.
224: */
225: if (bp->b_flags&B_WANTED)
226: wakeup((caddr_t)bp);
227: if (bfreelist[0].b_flags&B_WANTED) {
228: bfreelist[0].b_flags &= ~B_WANTED;
229: wakeup((caddr_t)bfreelist);
230: }
231: /*
232: * Retry I/O for locked buffers rather than invalidating them.
233: */
234: if ((bp->b_flags & B_ERROR) && (bp->b_flags & B_LOCKED))
235: bp->b_flags &= ~B_ERROR;
236:
237: /*
238: * Disassociate buffers that are no longer valid.
239: */
240: if (bp->b_flags & (B_NOCACHE|B_ERROR))
241: bp->b_flags |= B_INVAL;
242: if ((bp->b_bufsize <= 0) || (bp->b_flags & (B_ERROR|B_INVAL))) {
243: if (bp->b_vp)
244: brelvp(bp);
245: bp->b_flags &= ~B_DELWRI;
246: }
247: /*
248: * Stick the buffer back on a free list.
249: */
250: s = splbio();
251: if (bp->b_bufsize <= 0) {
252: /* block has no buffer ... put at front of unused buffer list */
253: flist = &bfreelist[BQ_EMPTY];
254: binsheadfree(bp, flist);
255: } else if (bp->b_flags & (B_ERROR|B_INVAL)) {
256: /* block has no info ... put at front of most free list */
257: flist = &bfreelist[BQ_AGE];
258: binsheadfree(bp, flist);
259: } else {
260: if (bp->b_flags & B_LOCKED)
261: flist = &bfreelist[BQ_LOCKED];
262: else if (bp->b_flags & B_AGE)
263: flist = &bfreelist[BQ_AGE];
264: else
265: flist = &bfreelist[BQ_LRU];
266: binstailfree(bp, flist);
267: }
268: bp->b_flags &= ~(B_WANTED|B_BUSY|B_ASYNC|B_AGE|B_NOCACHE);
269: splx(s);
270: }
271:
272: /*
273: * See if the block is associated with some buffer
274: * (mainly to avoid getting hung up on a wait in breada)
275: */
276: incore(vp, blkno)
277: struct vnode *vp;
278: daddr_t blkno;
279: {
280: register struct buf *bp;
281: register struct buf *dp;
282:
283: dp = BUFHASH(vp, blkno);
284: for (bp = dp->b_forw; bp != dp; bp = bp->b_forw)
285: if (bp->b_lblkno == blkno && bp->b_vp == vp &&
286: (bp->b_flags & B_INVAL) == 0)
287: return (1);
288: return (0);
289: }
290:
291: /*
292: * Return a block if it is in memory.
293: */
294: baddr(vp, blkno, size, cred, bpp)
295: struct vnode *vp;
296: daddr_t blkno;
297: int size;
298: struct ucred *cred;
299: struct buf **bpp;
300: {
301:
302: if (incore(vp, blkno))
303: return (bread(vp, blkno, size, cred, bpp));
304: *bpp = 0;
305: return (0);
306: }
307:
308: /*
309: * Assign a buffer for the given block. If the appropriate
310: * block is already associated, return it; otherwise search
311: * for the oldest non-busy buffer and reassign it.
312: *
313: * We use splx here because this routine may be called
314: * on the interrupt stack during a dump, and we don't
315: * want to lower the ipl back to 0.
316: */
317: struct buf *
318: getblk(vp, blkno, size)
319: register struct vnode *vp;
320: daddr_t blkno;
321: int size;
322: {
323: register struct buf *bp, *dp;
324: int s;
325:
326: if (size > MAXBSIZE)
327: panic("getblk: size too big");
328: /*
329: * Search the cache for the block. If we hit, but
330: * the buffer is in use for i/o, then we wait until
331: * the i/o has completed.
332: */
333: dp = BUFHASH(vp, blkno);
334: loop:
335: for (bp = dp->b_forw; bp != dp; bp = bp->b_forw) {
336: if (bp->b_lblkno != blkno || bp->b_vp != vp ||
337: bp->b_flags&B_INVAL)
338: continue;
339: s = splbio();
340: if (bp->b_flags&B_BUSY) {
341: bp->b_flags |= B_WANTED;
342: sleep((caddr_t)bp, PRIBIO+1);
343: splx(s);
344: goto loop;
345: }
346: bremfree(bp);
347: bp->b_flags |= B_BUSY;
348: splx(s);
349: if (bp->b_bcount != size) {
350: printf("getblk: stray size");
351: bp->b_flags |= B_INVAL;
352: bwrite(bp);
353: goto loop;
354: }
355: bp->b_flags |= B_CACHE;
356: return (bp);
357: }
358: bp = getnewbuf();
359: bfree(bp);
360: bremhash(bp);
361: bgetvp(vp, bp);
362: bp->b_lblkno = blkno;
363: bp->b_blkno = blkno;
364: bp->b_error = 0;
365: bp->b_resid = 0;
366: binshash(bp, dp);
367: brealloc(bp, size);
368: return (bp);
369: }
370:
371: /*
372: * get an empty block,
373: * not assigned to any particular device
374: */
375: struct buf *
376: geteblk(size)
377: int size;
378: {
379: register struct buf *bp, *flist;
380:
381: if (size > MAXBSIZE)
382: panic("geteblk: size too big");
383: bp = getnewbuf();
384: bp->b_flags |= B_INVAL;
385: bfree(bp);
386: bremhash(bp);
387: flist = &bfreelist[BQ_AGE];
388: bp->b_error = 0;
389: bp->b_resid = 0;
390: binshash(bp, flist);
391: brealloc(bp, size);
392: return (bp);
393: }
394:
395: /*
396: * Allocate space associated with a buffer.
397: */
398: brealloc(bp, size)
399: register struct buf *bp;
400: int size;
401: {
402: daddr_t start, last;
403: register struct buf *ep;
404: struct buf *dp;
405: int s;
406:
407: if (size == bp->b_bcount)
408: return;
409: allocbuf(bp, size);
410: }
411:
412: /*
413: * Find a buffer which is available for use.
414: * Select something from a free list.
415: * Preference is to AGE list, then LRU list.
416: */
417: struct buf *
418: getnewbuf()
419: {
420: register struct buf *bp, *dp;
421: register struct ucred *cred;
422: int s;
423:
424: loop:
425: s = splbio();
426: for (dp = &bfreelist[BQ_AGE]; dp > bfreelist; dp--)
427: if (dp->av_forw != dp)
428: break;
429: if (dp == bfreelist) { /* no free blocks */
430: dp->b_flags |= B_WANTED;
431: sleep((caddr_t)dp, PRIBIO+1);
432: splx(s);
433: goto loop;
434: }
435: bp = dp->av_forw;
436: bremfree(bp);
437: bp->b_flags |= B_BUSY;
438: splx(s);
439: if (bp->b_flags & B_DELWRI) {
440: (void) bawrite(bp);
441: goto loop;
442: }
443: trace(TR_BRELSE, pack(bp->b_vp, bp->b_bufsize), bp->b_lblkno);
444: if (bp->b_vp)
445: brelvp(bp);
446: if (bp->b_rcred != NOCRED) {
447: cred = bp->b_rcred;
448: bp->b_rcred = NOCRED;
449: crfree(cred);
450: }
451: if (bp->b_wcred != NOCRED) {
452: cred = bp->b_wcred;
453: bp->b_wcred = NOCRED;
454: crfree(cred);
455: }
456: bp->b_flags = B_BUSY;
457: return (bp);
458: }
459:
460: /*
461: * Wait for I/O completion on the buffer; return errors
462: * to the user.
463: */
464: biowait(bp)
465: register struct buf *bp;
466: {
467: int s;
468:
469: s = splbio();
470: while ((bp->b_flags & B_DONE) == 0)
471: sleep((caddr_t)bp, PRIBIO);
472: splx(s);
473: /*
474: * Pick up the device's error number and pass it to the user;
475: * if there is an error but the number is 0 set a generalized code.
476: */
477: if ((bp->b_flags & B_ERROR) == 0)
478: return (0);
479: if (bp->b_error)
480: return (bp->b_error);
481: return (EIO);
482: }
483:
484: /*
485: * Mark I/O complete on a buffer.
486: * If someone should be called, e.g. the pageout
487: * daemon, do so. Otherwise, wake up anyone
488: * waiting for it.
489: */
490: biodone(bp)
491: register struct buf *bp;
492: {
493: register struct vnode *vp;
494:
495: if (bp->b_flags & B_DONE)
496: panic("dup biodone");
497: bp->b_flags |= B_DONE;
498: if ((bp->b_flags & B_READ) == 0) {
499: bp->b_dirtyoff = bp->b_dirtyend = 0;
500: if (vp = bp->b_vp) {
501: vp->v_numoutput--;
502: if ((vp->v_flag & VBWAIT) && vp->v_numoutput <= 0) {
503: if (vp->v_numoutput < 0)
504: panic("biodone: neg numoutput");
505: vp->v_flag &= ~VBWAIT;
506: wakeup((caddr_t)&vp->v_numoutput);
507: }
508: }
509: }
510: if (bp->b_flags & B_CALL) {
511: bp->b_flags &= ~B_CALL;
512: (*bp->b_iodone)(bp);
513: return;
514: }
515: if (bp->b_flags&B_ASYNC)
516: brelse(bp);
517: else {
518: bp->b_flags &= ~B_WANTED;
519: wakeup((caddr_t)bp);
520: }
521: }
522:
523: /*
524: * Make sure all write-behind blocks associated
525: * with mount point are flushed out (from sync).
526: */
527: mntflushbuf(mountp, flags)
528: struct mount *mountp;
529: int flags;
530: {
531: register struct vnode *vp;
532:
533: if ((mountp->mnt_flag & MNT_MPBUSY) == 0)
534: panic("mntflushbuf: not busy");
535: loop:
536: for (vp = mountp->mnt_mounth; vp; vp = vp->v_mountf) {
537: if (vget(vp))
538: goto loop;
539: vflushbuf(vp, flags);
540: vput(vp);
541: if (vp->v_mount != mountp)
542: goto loop;
543: }
544: }
545:
546: /*
547: * Flush all dirty buffers associated with a vnode.
548: */
549: vflushbuf(vp, flags)
550: register struct vnode *vp;
551: int flags;
552: {
553: register struct buf *bp;
554: struct buf *nbp;
555: int s;
556:
557: loop:
558: s = splbio();
559: for (bp = vp->v_dirtyblkhd; bp; bp = nbp) {
560: nbp = bp->b_blockf;
561: if ((bp->b_flags & B_BUSY))
562: continue;
563: if ((bp->b_flags & B_DELWRI) == 0)
564: panic("vflushbuf: not dirty");
565: bremfree(bp);
566: bp->b_flags |= B_BUSY;
567: splx(s);
568: /*
569: * Wait for I/O associated with indirect blocks to complete,
570: * since there is no way to quickly wait for them below.
571: * NB - This is really specific to ufs, but is done here
572: * as it is easier and quicker.
573: */
574: if (bp->b_vp == vp || (flags & B_SYNC) == 0) {
575: (void) bawrite(bp);
576: s = splbio();
577: } else {
578: (void) bwrite(bp);
579: goto loop;
580: }
581: }
582: splx(s);
583: if ((flags & B_SYNC) == 0)
584: return;
585: s = splbio();
586: while (vp->v_numoutput) {
587: vp->v_flag |= VBWAIT;
588: sleep((caddr_t)&vp->v_numoutput, PRIBIO+1);
589: }
590: splx(s);
591: if (vp->v_dirtyblkhd) {
592: vprint("vflushbuf: dirty", vp);
593: goto loop;
594: }
595: }
596:
597: /*
598: * Invalidate in core blocks belonging to closed or umounted filesystem
599: *
600: * Go through the list of vnodes associated with the file system;
601: * for each vnode invalidate any buffers that it holds. Normally
602: * this routine is preceeded by a bflush call, so that on a quiescent
603: * filesystem there will be no dirty buffers when we are done. Binval
604: * returns the count of dirty buffers when it is finished.
605: */
606: mntinvalbuf(mountp)
607: struct mount *mountp;
608: {
609: register struct vnode *vp;
610: int dirty = 0;
611:
612: if ((mountp->mnt_flag & MNT_MPBUSY) == 0)
613: panic("mntinvalbuf: not busy");
614: loop:
615: for (vp = mountp->mnt_mounth; vp; vp = vp->v_mountf) {
616: if (vget(vp))
617: goto loop;
618: dirty += vinvalbuf(vp, 1);
619: vput(vp);
620: if (vp->v_mount != mountp)
621: goto loop;
622: }
623: return (dirty);
624: }
625:
626: /*
627: * Flush out and invalidate all buffers associated with a vnode.
628: * Called with the underlying object locked.
629: */
630: vinvalbuf(vp, save)
631: register struct vnode *vp;
632: int save;
633: {
634: register struct buf *bp;
635: struct buf *nbp, *blist;
636: int s, dirty = 0;
637:
638: for (;;) {
639: if (blist = vp->v_dirtyblkhd)
640: /* void */;
641: else if (blist = vp->v_cleanblkhd)
642: /* void */;
643: else
644: break;
645: for (bp = blist; bp; bp = nbp) {
646: nbp = bp->b_blockf;
647: s = splbio();
648: if (bp->b_flags & B_BUSY) {
649: bp->b_flags |= B_WANTED;
650: sleep((caddr_t)bp, PRIBIO+1);
651: splx(s);
652: break;
653: }
654: bremfree(bp);
655: bp->b_flags |= B_BUSY;
656: splx(s);
657: if (save && (bp->b_flags & B_DELWRI)) {
658: dirty++;
659: (void) bwrite(bp);
660: break;
661: }
662: if (bp->b_vp != vp)
663: reassignbuf(bp, bp->b_vp);
664: else
665: bp->b_flags |= B_INVAL;
666: brelse(bp);
667: }
668: }
669: if (vp->v_dirtyblkhd || vp->v_cleanblkhd)
670: panic("vinvalbuf: flush failed");
671: return (dirty);
672: }
673:
674: /*
675: * Associate a buffer with a vnode.
676: */
677: bgetvp(vp, bp)
678: register struct vnode *vp;
679: register struct buf *bp;
680: {
681:
682: if (bp->b_vp)
683: panic("bgetvp: not free");
684: VHOLD(vp);
685: bp->b_vp = vp;
686: if (vp->v_type == VBLK || vp->v_type == VCHR)
687: bp->b_dev = vp->v_rdev;
688: else
689: bp->b_dev = NODEV;
690: /*
691: * Insert onto list for new vnode.
692: */
693: if (vp->v_cleanblkhd) {
694: bp->b_blockf = vp->v_cleanblkhd;
695: bp->b_blockb = &vp->v_cleanblkhd;
696: vp->v_cleanblkhd->b_blockb = &bp->b_blockf;
697: vp->v_cleanblkhd = bp;
698: } else {
699: vp->v_cleanblkhd = bp;
700: bp->b_blockb = &vp->v_cleanblkhd;
701: bp->b_blockf = NULL;
702: }
703: }
704:
705: /*
706: * Disassociate a buffer from a vnode.
707: */
708: brelvp(bp)
709: register struct buf *bp;
710: {
711: struct buf *bq;
712: struct vnode *vp;
713:
714: if (bp->b_vp == (struct vnode *) 0)
715: panic("brelvp: NULL");
716: /*
717: * Delete from old vnode list, if on one.
718: */
719: if (bp->b_blockb) {
720: if (bq = bp->b_blockf)
721: bq->b_blockb = bp->b_blockb;
722: *bp->b_blockb = bq;
723: bp->b_blockf = NULL;
724: bp->b_blockb = NULL;
725: }
726: vp = bp->b_vp;
727: bp->b_vp = (struct vnode *) 0;
728: HOLDRELE(vp);
729: }
730:
731: /*
732: * Reassign a buffer from one vnode to another.
733: * Used to assign file specific control information
734: * (indirect blocks) to the vnode to which they belong.
735: */
736: reassignbuf(bp, newvp)
737: register struct buf *bp;
738: register struct vnode *newvp;
739: {
740: register struct buf *bq, **listheadp;
741:
742: if (newvp == NULL)
743: panic("reassignbuf: NULL");
744: /*
745: * Delete from old vnode list, if on one.
746: */
747: if (bp->b_blockb) {
748: if (bq = bp->b_blockf)
749: bq->b_blockb = bp->b_blockb;
750: *bp->b_blockb = bq;
751: }
752: /*
753: * If dirty, put on list of dirty buffers;
754: * otherwise insert onto list of clean buffers.
755: */
756: if (bp->b_flags & B_DELWRI)
757: listheadp = &newvp->v_dirtyblkhd;
758: else
759: listheadp = &newvp->v_cleanblkhd;
760: if (*listheadp) {
761: bp->b_blockf = *listheadp;
762: bp->b_blockb = listheadp;
763: bp->b_blockf->b_blockb = &bp->b_blockf;
764: *listheadp = bp;
765: } else {
766: *listheadp = bp;
767: bp->b_blockb = listheadp;
768: bp->b_blockf = NULL;
769: }
770: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.