|
|
1.1 root 1: /*
2:
3: Copyright 1990,1991,1992 Eric R. Smith. All rights reserved.
4:
5: */
6:
7:
8:
9: #include "mint.h"
10:
11: #include "version.h"
12:
13: #include "cookie.h"
14:
15: #include "xbra.h"
16:
17:
18:
19: /* the kernel's stack size */
20:
21: #define STACK 8*1024L
22:
23:
24:
25: /* if the user is holding down the magic shift key, we ask before booting */
26:
27: #define MAGIC_SHIFT 0x1 /* right shift */
28:
29:
30:
31: static void xbra_install P_((xbra_vec *, long, void (*)()));
32:
33: static void init_intr P_((void));
34:
35: static void getmch P_((void));
36:
37: static void do_line P_((char *));
38:
39: static void shutmedown P_((PROC *));
40:
41: void shutdown P_((void));
42:
43: static void doset P_((char *,char *));
44:
45: static long mint_criticerr P_((long));
46:
47:
48:
49: static int gem_active; /* 0 if AES has started, 1 otherwise */
50:
51:
52:
53: static int check_for_gem P_((void));
54:
55: static void run_auto_prgs P_((void));
56:
57:
58:
59: #ifdef LATTICE
60:
61: /*
62:
63: * AGK: this is witchcraft to completely replace the startup code for
64:
65: * Lattice; doing so saves around 10K on the final binary and pulls only
66:
67: * long division & multitplication from the library (and not even those
68:
69: * if you compile for native '030). The drawback of this code is it
70:
71: * passes no environment or command line whatsoever. Since I always
72:
73: * set MiNT options & environment in 'mint.cnf' this is not a personal
74:
75: * downer, however at some point in the future we ought to have a kernel
76:
77: * parseargs() like call which sets these things up.
78:
79: */
80:
81: BASEPAGE *_base;
82:
83:
84:
85: static void
86:
87: start(BASEPAGE *bp)
88:
89: {
90:
91: long shrinklen;
92:
93:
94:
95: _base = bp;
96:
97: shrinklen = bp->p_tlen + bp->p_dlen + bp->p_blen + STACK + 0x100;
98:
99: if (bp->p_lowtpa + shrinklen <= bp->p_hitpa) {
100:
101: static char null[1] = {""};
102:
103: static char *argv[2] = {null, NULL};
104:
105: extern __builtin_putreg P_((int, long)); /* totally bogus */
106:
107:
108:
109: __builtin_putreg(15, bp->p_lowtpa + shrinklen);
110:
111: Mshrink((void *)bp->p_lowtpa, shrinklen);
112:
113: main(1, argv, argv);
114:
115: }
116:
117: Pterm(ENSMEM);
118:
119: }
120:
121: #endif
122:
123:
124:
125: #ifdef __GNUC__
126:
127: long _stksize = STACK;
128:
129: #include <minimal.h>
130:
131: #endif
132:
133:
134:
135: int curs_off = 0; /* set if we should turn the cursor off when exiting */
136:
137: int mint_errno = 0; /* error return from open and creat filesystem calls */
138:
139:
140:
141: /*
142:
143: * AGK: for proper co-processors we must consider saving their context.
144:
145: * This variable when non-zero indicates that the BIOS considers a true
146:
147: * coprocessor to be present. We use this variable in the context switch
148:
149: * code to decide whether to attempt an FPU context save.
150:
151: */
152:
153: short fpu = 0;
154:
155:
156:
157: /*
158:
159: * "mch" holds what kind of machine we are running on
160:
161: */
162:
163: long mch = 0;
164:
165:
166:
167: /*
168:
169: * variable holds processor type
170:
171: */
172:
173: long mcpu = 0;
174:
175:
176:
177: /*
178:
179: * variable set to 1 iff on a 68010 (used by the context switch code)
180:
181: * this is redundant with mcpu, but is kept for reasons of speed
182:
183: */
184:
185: short m68010 = 0;
186:
187:
188:
189: /*
190:
191: * variable set if someone has already installed an flk cookie
192:
193: */
194:
195: int flk = 0;
196:
197:
198:
199: /* program to run at startup */
200:
201: char orig_init[] = "init.prg";
202:
203: char *init_prg = orig_init;
204:
205: char init_tail[128];
206:
207:
208:
209: /* initial environment for that program */
210:
211: char *init_env = 0;
212:
213: /* temporary pointer into that environment for setenv */
214:
215: char *env_ptr;
216:
217: /* length of the environment */
218:
219: long env_len;
220:
221:
222:
223: /* flag that's set if we want pseudo-drives to have BIOS entries:
224:
225: * 0 means no BIOS interaction with pseudo-drives at all
226:
227: * 1 means the drives show up in _drvbits and Drvmap()
228:
229: * 2 means the above + fake handlers for Rwabs, Mediach, etc.
230:
231: */
232:
233:
234:
235: int pseudodrives = 2;
236:
237:
238:
239: /* GEMDOS pointer to current basepage */
240:
241: BASEPAGE **tosbp;
242:
243:
244:
245: /* pointer to the BIOS keyboard shift variable */
246:
247: extern char *kbshft; /* see bios.c */
248:
249:
250:
251: /* version of TOS we're running over */
252:
253: int tosvers;
254:
255:
256:
257: /* structures for keyboard/MIDI interrupt vectors */
258:
259: KBDVEC *syskey, oldkey;
260:
261: xbra_vec old_ikbd; /* old ikbd vector */
262:
263:
264:
265: /* values the user sees for the DOS, BIOS, and XBIOS vectors */
266:
267: long save_dos, save_bios, save_xbios;
268:
269:
270:
271: /* values for original system vectors */
272:
273: xbra_vec old_dos, old_bios, old_xbios, old_timer, old_vbl, old_5ms;
274:
275: xbra_vec old_criticerr;
276:
277:
278:
279: long old_term;
280:
281:
282:
283: xbra_vec old_resvec; /* old reset vector */
284:
285: long old_resval; /* old reset validation */
286:
287:
288:
289: #ifdef EXCEPTION_SIGS
290:
291: /* bus error, address error, illegal instruction, etc. vectors */
292:
293: xbra_vec old_bus, old_addr, old_ill, old_divzero, old_trace, old_priv;
294:
295: #endif
296:
297:
298:
299: /* BIOS disk vectors */
300:
301: xbra_vec old_mediach, old_getbpb, old_rwabs;
302:
303:
304:
305: /* BIOS drive map */
306:
307: long olddrvs;
308:
309:
310:
311: extern Func bios_tab[], dos_tab[];
312:
313:
314:
315: /* kernel info that is passed to loaded file systems and device drivers */
316:
317:
318:
319: struct kerinfo kernelinfo = {
320:
321: MAJ_VERSION, MIN_VERSION,
322:
323: DEFAULT_MODE, 0,
324:
325: bios_tab, dos_tab,
326:
327: changedrv,
328:
329: TRACE, DEBUG, ALERT, FATAL,
330:
331: kmalloc, kfree, umalloc, ufree,
332:
333: strnicmp, stricmp, strlwr, strupr, ksprintf,
334:
335: ms_time, unixtim, dostim,
336:
337: nap, sleep, wake, wakeselect,
338:
339: denyshare, denylock
340:
341: };
342:
343:
344:
345: /* temporary stack for resets -- see intr.s */
346:
347: char tmpstack[256];
348:
349:
350:
351: /* TOS and MiNT cookie jars, respectively. See the comments and code
352:
353: * after main() for further details
354:
355: */
356:
357:
358:
359: COOKIE *oldcookie, *newcookie;
360:
361:
362:
363: /*
364:
365: * install a new vector for address "addr", using the XBRA protocol.
366:
367: * must run in supervisor mode!
368:
369: */
370:
371:
372:
373: static void
374:
375: xbra_install(xv, addr, func)
376:
377: xbra_vec *xv;
378:
379: long addr;
380:
381: void (*func)();
382:
383: {
384:
385: xv->xbra_magic = XBRA_MAGIC;
386:
387: xv->xbra_id = MINT_MAGIC;
388:
389: xv->jump = JMP_OPCODE;
390:
391: xv->this = func;
392:
393: xv->next = *((struct xbra **)addr);
394:
395: *((short **)addr) = &xv->jump;
396:
397: }
398:
399:
400:
401: /*
402:
403: * MiNT critical error handler; all it does is to jump through
404:
405: * the vector for the current process
406:
407: */
408:
409:
410:
411: static long
412:
413: mint_criticerr(error)
414:
415: long error; /* high word is error, low is drive */
416:
417: {
418:
419: return (*curproc->criticerr)(error);
420:
421: }
422:
423:
424:
425: /* initialize all interrupt vectors and new trap routines
426:
427: * we also get here any TOS variables that we're going to change
428:
429: * (e.g. the pointer to the cookie jar) so that rest_intr can
430:
431: * restore them.
432:
433: */
434:
435:
436:
437: static void
438:
439: init_intr()
440:
441: {
442:
443: extern void mint_bios(), mint_dos(), mint_timer(), mint_vbl();
444:
445: extern void mint_5ms(); /* AKP */
446:
447: extern void mint_xbios(), reset();
448:
449: extern void new_ikbd();
450:
451: extern void new_bus(), new_addr(), new_ill(), new_divzero(),
452:
453: new_trace(), new_priv();
454:
455: short savesr;
456:
457:
458:
459: syskey = (KBDVEC *)Kbdvbase();
460:
461: oldkey = *syskey;
462:
463:
464:
465: xbra_install(&old_ikbd, (long)(&syskey->ikbdsys), new_ikbd);
466:
467:
468:
469: /* gratuitous (void *) for Lattice */
470:
471: old_term = (long)Setexc(0x102, (void *)-1L);
472:
473:
474:
475: savesr = spl7();
476:
477:
478:
479: xbra_install(&old_dos, 0x84L, mint_dos);
480:
481: save_dos = (long)old_dos.next;
482:
483:
484:
485: xbra_install(&old_bios, 0xb4L, mint_bios);
486:
487: save_bios = (long)old_bios.next;
488:
489:
490:
491: xbra_install(&old_xbios, 0xb8L, mint_xbios);
492:
493: save_xbios = (long)old_xbios.next;
494:
495:
496:
497: xbra_install(&old_timer, 0x400L, mint_timer);
498:
499: xbra_install(&old_criticerr, 0x404L, (void (*)())mint_criticerr);
500:
501: xbra_install(&old_5ms, 0x114L, mint_5ms);
502:
503: xbra_install(&old_vbl, 4*0x1cL, mint_vbl);
504:
505: xbra_install(&old_resvec, 0x42aL, reset);
506:
507: old_resval = *((long *)0x426L);
508:
509:
510:
511: spl(savesr);
512:
513:
514:
515: #ifdef EXCEPTION_SIGS
516:
517: /* set up signal handlers */
518:
519: xbra_install(&old_bus, 8L, new_bus);
520:
521: xbra_install(&old_addr, 12L, new_addr);
522:
523: xbra_install(&old_ill, 16L, new_ill);
524:
525: xbra_install(&old_divzero, 20L, new_divzero);
526:
527: xbra_install(&old_trace, 36L, new_trace);
528:
529: # ifdef STRICT_PRIV
530:
531: /* don't do this */
532:
533: xbra_install(&old_priv, 32L, new_priv);
534:
535: # endif
536:
537: #endif
538:
539:
540:
541: /* set up disk vectors */
542:
543: xbra_install(&old_mediach, 0x47eL, new_mediach);
544:
545: xbra_install(&old_rwabs, 0x476L, new_rwabs);
546:
547: xbra_install(&old_getbpb, 0x472L, new_getbpb);
548:
549: olddrvs = *((long *)0x4c2L);
550:
551:
552:
553: /* set up cookie jar */
554:
555: oldcookie = *CJAR; /* CJAR defined in cookie.h */
556:
557: install_cookies();
558:
559: }
560:
561:
562:
563: /* restore all interrupt vectors and trap routines */
564:
565: /*
566:
567: * NOTE: This is *not* the approved way of unlinking XBRA trap handlers.
568:
569: * Normally, one should trace through the XBRA chain. However, this is
570:
571: * a very unusual situation: when MiNT exits, any TSRs or programs running
572:
573: * under MiNT will no longer exist, and so any vectors that they have
574:
575: * caught will be pointing to never-never land! So we do something that
576:
577: * would normally be considered rude, and restore the vectors to
578:
579: * what they were before we ran.
580:
581: * BUG: we should restore *all* vectors, not just the ones that MiNT caught.
582:
583: */
584:
585:
586:
587: void
588:
589: restr_intr()
590:
591: {
592:
593: short savesr;
594:
595:
596:
597: savesr = spl7();
598:
599: *syskey = oldkey; /* restore keyboard vectors */
600:
601: *tosbp = _base; /* restore GEMDOS basepage pointer */
602:
603: *CJAR = oldcookie; /* restore old cookie jar */
604:
605:
606:
607: #ifdef EXCEPTION_SIGS
608:
609: *((long *)0x08L) = (long) old_bus.next;
610:
611: *((long *)0x0cL) = (long) old_addr.next;
612:
613: *((long *)0x10L) = (long) old_ill.next;
614:
615: *((long *)0x14L) = (long) old_divzero.next;
616:
617: # ifdef STRICT_PRIV
618:
619: *((long *)0x20L) = (long) old_priv.next;
620:
621: # endif
622:
623: *((long *)0x24L) = (long) old_trace.next;
624:
625: #endif
626:
627: *((long *)0x84L) = (long) old_dos.next;
628:
629: *((long *)0xb4L) = (long) old_bios.next;
630:
631: *((long *)0xb8L) = (long) old_xbios.next;
632:
633: *((long *)0x408L) = old_term;
634:
635: *((long *)0x404L) = (long) old_criticerr.next;
636:
637: *((long *)0x114L) = (long) old_5ms.next;
638:
639: *((long *)0x400L) = (long) old_timer.next;
640:
641: *((long *)0x70L) = (long) old_vbl.next;
642:
643: *((long *)0x426L) = old_resval;
644:
645: *((long *)0x42aL) = (long) old_resvec.next;
646:
647: *((long *)0x476L) = (long) old_rwabs.next;
648:
649: *((long *)0x47eL) = (long) old_mediach.next;
650:
651: *((long *)0x472L) = (long) old_getbpb.next;
652:
653: *((long *)0x4c2L) = olddrvs;
654:
655:
656:
657: spl(savesr);
658:
659: }
660:
661:
662:
663:
664:
665: /* we save the TOS supervisor stack pointer so that we can reset it when
666:
667: calling Pterm() (not that anyone will ever want to leave MiNT :-)).
668:
669: */
670:
671:
672:
673: long tosssp; /* TOS supervisor stack pointer */
674:
675:
676:
677:
678:
679: /*
680:
681: * enter_kernel: called every time we enter the MiNT kernel via a trap
682:
683: * call. Sets up the GEMDOS and BIOS vectors to point to TOS, and
684:
685: * sets up other vectors and system variables appropriately. Note that
686:
687: * calling enter_kernel multiple times should not be harmful!
688:
689: */
690:
691:
692:
693: short in_kernel = 0;
694:
695:
696:
697: void
698:
699: enter_kernel()
700:
701: {
702:
703: short save_sr;
704:
705:
706:
707: if (in_kernel) return;
708:
709:
710:
711: save_sr = spl7();
712:
713: save_dos = *((long *) 0x84L);
714:
715: save_bios = *((long *) 0xb4L);
716:
717: save_xbios = *((long *) 0xb8L);
718:
719: *((long *) 0x84L) = (long)old_dos.next;
720:
721: *((long *) 0xb4L) = (long)old_bios.next;
722:
723: *((long *) 0xb8L) = (long)old_xbios.next;
724:
725: *tosbp = rootproc->base;
726:
727:
728:
729: in_kernel = 1;
730:
731: spl(save_sr);
732:
733: }
734:
735:
736:
737: /*
738:
739: * leave_kernel: called before leaving the kernel, either back to
740:
741: * user mode or when calling a signal handler or the GEMDOS
742:
743: * terminate vector. Note that interrupts should be disabled before
744:
745: * this routine is called.
746:
747: */
748:
749:
750:
751: void
752:
753: leave_kernel()
754:
755: {
756:
757: if (!in_kernel) {
758:
759: ALERT("leave_kernel: not in kernel??");
760:
761: }
762:
763: *((long *) 0x84L) = save_dos;
764:
765: *((long *) 0xb4L) = save_bios;
766:
767: *((long *) 0xb8L) = save_xbios;
768:
769: *tosbp = curproc->base;
770:
771: in_kernel = 0;
772:
773: }
774:
775:
776:
777: /*
778:
779: * shut down processes; this involves waking them all up, and sending
780:
781: * them SIGTERM to give them a chance to clean up after themselves
782:
783: */
784:
785:
786:
787: static void
788:
789: shutmedown(p)
790:
791: PROC *p;
792:
793: {
794:
795: curproc->wait_cond = 0;
796:
797: }
798:
799:
800:
801: void
802:
803: shutdown()
804:
805: {
806:
807: PROC *p;
808:
809: int proc_left = 0;
810:
811:
812:
813: curproc->sighandle[SIGCHLD] = SIG_IGN;
814:
815:
816:
817: for (p = proclist; p; p = p->gl_next) {
818:
819: if (p->pid == 0) continue;
820:
821: if (p->wait_q != ZOMBIE_Q && p->wait_q != TSR_Q) {
822:
823: if (p->wait_q != READY_Q) {
824:
825: rm_q(p->wait_q, p);
826:
827: add_q(READY_Q, p);
828:
829: }
830:
831: post_sig(p, SIGTERM);
832:
833: proc_left++;
834:
835: }
836:
837: }
838:
839:
840:
841: if (proc_left) {
842:
843: /* sleep a little while, to give the other processes a chance to
844:
845: shut down
846:
847: */
848:
849:
850:
851: addtimeout(1000, shutmedown);
852:
853: do {
854:
855: sleep(WAIT_Q, (long)&shutdown);
856:
857: } while (curproc->wait_cond == (long)&shutdown);
858:
859: }
860:
861: }
862:
863:
864:
865: #ifdef ZEROEXIT
866:
867: /*
868:
869: * a horrible kludge: TOS 1.4 has a bug that causes GEM to get upset
870:
871: * if a large AUTO folder program runs and leaves non-zero memory
872:
873: * behind. So, before we leave, we zero as much of ourselves as
874:
875: * possible. To do this, we copy a short, position independent
876:
877: * assembly language function into the basepage, and then execute
878:
879: * it; the function will take care of exiting.
880:
881: * Note: the GEM bug doesn't usually bite because most AUTO folder
882:
883: * programs are TSRs and hence GEM doesn't reuse their memory.
884:
885: */
886:
887:
888:
889: static short exitfunc[] = {
890:
891: 0x41fa, 0x0012, /* lea end(pc), a0 */
892:
893: 0x226f, 0x0004, /* move.l 4(sp), a1 */
894:
895: 0x4240, /* clr.w d0 */
896:
897: 0x30c0, /* L1: move.w d0, a0@+ */
898:
899: 0xb3c8, /* cmp.l a0, a1 */
900:
901: 0x66fa, /* bne L1 */
902:
903: 0x3f00, /* move.w d0, sp@- */
904:
905: 0x4e41 /* trap #1 */
906:
907: }; /* end: */
908:
909:
910:
911: void zeroexit()
912:
913: {
914:
915: int i;
916:
917: short *to, *from;
918:
919: void (*func)();
920:
921:
922:
923: if (mcpu > 10) { /* on 68020+, we could run into cache problems */
924:
925: Pterm0(); /* let's hope we're running a fixed version of TOS */
926:
927: }
928:
929:
930:
931: to = (short *)_base->p_cmdlin;
932:
933: func = (void (*)()) to;
934:
935: from = exitfunc;
936:
937: for (i = 0; i < sizeof(exitfunc); i++) {
938:
939: *to++ = *from++;
940:
941: }
942:
943: (*P_((void (*)(long)))func)(_base->p_hitpa);
944:
945: }
946:
947: #else /* zerofunc */
948:
949: void zeroexit() { Pterm0(); }
950:
951: #endif
952:
953:
954:
955: int
956:
957: main(argc, argv, envp)
958:
959: int argc;
960:
961: char **argv;
962:
963: char **envp;
964:
965: {
966:
967: long *sysbase;
968:
969: long r;
970:
971: extern int debug_level; /* in debug.c */
972:
973: static char buf[SPRINTF_MAX];
974:
975: static char curpath[128];
976:
977: int yn;
978:
979: FILEPTR *f;
980:
981:
982:
983: /* Allow the user to abort the boot if the magic combination of shift keys
984:
985: * is held down (see MAGIC_SHIFT above)
986:
987: */
988:
989: if ((Kbshift(-1) & MAGIC_SHIFT) == MAGIC_SHIFT) {
990:
991: Cconws("Boot MiNT? (y/n) ");
992:
993: yn = Cconin() & 0x7f;
994:
995: if (yn != 'y' && yn != 'Y') {
996:
997: Cconws("\r\n\r\nMiNT not booted, at user's request.\r\n");
998:
999: Pterm0();
1000:
1001: }
1002:
1003: }
1004:
1005:
1006:
1007: if (argv[0][0] == 0) { /* maybe started from the desktop */
1008:
1009: curs_off = 1;
1010:
1011: }
1012:
1013:
1014:
1015: if (argc > 1) {
1016:
1017: debug_level++;
1018:
1019: }
1020:
1021: if (argc > 2) {
1022:
1023: debug_level++;
1024:
1025: }
1026:
1027:
1028:
1029: /* greetings */
1030:
1031: Cconws("\r\n\033eMiNT is Not TOS: MiNT version ");
1032:
1033: #ifdef PATCHLEVEL
1034:
1035: ksprintf(buf, VERS_STRING, MAJ_VERSION, MIN_VERSION, PATCHLEVEL);
1036:
1037: #else
1038:
1039: ksprintf(buf, VERS_STRING, MAJ_VERSION, MIN_VERSION);
1040:
1041: #endif
1042:
1043: Cconws(buf);
1044:
1045: Cconws("\r\nCopyright 1990,1991,1992 Eric R. Smith\r\n");
1046:
1047: Cconws("Use this program at your own risk!\r\n");
1048:
1049: Cconws("See the file \"copying\" for distribution conditions\r\n");
1050:
1051:
1052:
1053: gem_active = check_for_gem(); /* this must be done from user mode */
1054:
1055:
1056:
1057: /*
1058:
1059: * get the current directory, so that we can switch back to it after
1060:
1061: * the file systems are properly initialized
1062:
1063: */
1064:
1065: /* set the current directory for the current process */
1066:
1067: (void)Dgetpath(curpath, 0);
1068:
1069:
1070:
1071: tosssp = (long)Super(0L); /* enter supervisor mode */
1072:
1073:
1074:
1075: /* figure out what kind of machine we're running on */
1076:
1077: /* biosfs wants to know this, so we have to do it very
1078:
1079: * early in our initialization
1080:
1081: */
1082:
1083: getmch();
1084:
1085:
1086:
1087: /* get GEMDOS pointer to current basepage */
1088:
1089: /* 0x4f2 points to the base of the OS; here we can find the OS compilation
1090:
1091: date, and (in newer versions of TOS) where the current basepage pointer
1092:
1093: is kept; in older versions of TOS, it's at 0x602c
1094:
1095: */
1096:
1097: sysbase = *((long **)(0x4f2L)); /* gets the RAM OS header */
1098:
1099: sysbase = (long *)sysbase[2]; /* gets the ROM one */
1100:
1101:
1102:
1103: tosvers = (sysbase[0] & 0x0000ffff);
1104:
1105: if (tosvers == 0x100) {
1106:
1107: if ((sysbase[7] & 0xfffe0000) == 0x00080000)
1108:
1109: tosbp = (BASEPAGE **)0x873cL; /* SPANISH ROM */
1110:
1111: else
1112:
1113: tosbp = (BASEPAGE **) 0x602cL;
1114:
1115: kbshft = (char *) 0x0e1bL;
1116:
1117: } else {
1118:
1119: tosbp = (BASEPAGE **) sysbase[10];
1120:
1121: kbshft = (char *) sysbase[9];
1122:
1123: }
1124:
1125:
1126:
1127: /* The TT TOS release notes are wrong... this is the real way to test
1128:
1129: * for Bconmap ability
1130:
1131: */
1132:
1133: has_bconmap = (Bconmap(0) == 0);
1134:
1135:
1136:
1137: /* initialize memory */
1138:
1139: init_mem();
1140:
1141: TRACE("back from init_mem");
1142:
1143:
1144:
1145: /* initialize the basic file systems */
1146:
1147: init_filesys();
1148:
1149: TRACE("back from init_filesys");
1150:
1151:
1152:
1153: /* initialize processes */
1154:
1155: init_proc();
1156:
1157: TRACE("back from init_proc");
1158:
1159:
1160:
1161: /* initialize system calls */
1162:
1163: init_dos();
1164:
1165: init_bios();
1166:
1167: init_xbios();
1168:
1169:
1170:
1171: /* NOTE: there's a call to kmalloc embedded in install_cookies, which
1172:
1173: * is called by init_intr; so make sure this is the last of the
1174:
1175: * init_* things called!
1176:
1177: */
1178:
1179: init_intr();
1180:
1181: enter_kernel();
1182:
1183:
1184:
1185: /* set up standard file handles for the current process
1186:
1187: * do this here, *after* init_intr has set the Rwabs vector,
1188:
1189: * so that AHDI doesn't get upset by references to drive U:
1190:
1191: */
1192:
1193: f = do_open("U:\\DEV\\CONSOLE", O_RDWR, 0, (XATTR *)0);
1194:
1195: if (!f) {
1196:
1197: FATAL("unable to open CONSOLE device");
1198:
1199: }
1200:
1201: curproc->control = f;
1202:
1203: curproc->handle[0] = f;
1204:
1205: curproc->handle[1] = f;
1206:
1207: f->links = 3;
1208:
1209:
1210:
1211: f = do_open("U:\\DEV\\MODEM1", O_RDWR, 0, (XATTR *)0);
1212:
1213: curproc->aux = f;
1214:
1215: if (has_bconmap) {
1216:
1217: /* If someone has already done a Bconmap call, then
1218:
1219: * MODEM1 may no longer be the default
1220:
1221: */
1222:
1223: bconmap(curbconmap);
1224:
1225: f = curproc->aux; /* bconmap can change curproc->aux */
1226:
1227: }
1228:
1229: if (f) {
1230:
1231: curproc->handle[2] = f;
1232:
1233: f->links++;
1234:
1235: }
1236:
1237: f = do_open("U:\\DEV\\CENTR", O_RDWR, 0, (XATTR *)0);
1238:
1239: if (f) {
1240:
1241: curproc->handle[3] = curproc->prn = f;
1242:
1243: f->links = 2;
1244:
1245: }
1246:
1247: if (f) {
1248:
1249: f = do_open("U:\\DEV\\MIDI", O_RDWR, 0, (XATTR *)0);
1250:
1251: curproc->midiin = curproc->midiout = f;
1252:
1253: f->links = 2;
1254:
1255: }
1256:
1257:
1258:
1259: /* load external file systems */
1260:
1261: if (*curpath) {
1262:
1263: (void)d_setpath(curpath);
1264:
1265: }
1266:
1267:
1268:
1269: load_filesys();
1270:
1271:
1272:
1273: /* note that load_filesys changed the
1274:
1275: * directory on us!!
1276:
1277: */
1278:
1279: if (*curpath) {
1280:
1281: (void)d_setpath(curpath);
1282:
1283: }
1284:
1285:
1286:
1287: /* load the configuration file */
1288:
1289: load_config();
1290:
1291:
1292:
1293: /* if we want pseudo-drives, set the drive map accordingly */
1294:
1295: if (pseudodrives > 0) {
1296:
1297: *((long *)0x4c2L) |= PSEUDODRVS;
1298:
1299: };
1300:
1301: if (pseudodrives < 2) { /* restore old Rwabs, etc. */
1302:
1303: *((long *)0x476L) = (long) old_rwabs.next;
1304:
1305: *((long *)0x47eL) = (long) old_mediach.next;
1306:
1307: *((long *)0x472L) = (long) old_getbpb.next;
1308:
1309: }
1310:
1311:
1312:
1313: if (init_env == 0)
1314:
1315: init_env = (char *)_base->p_env;
1316:
1317:
1318:
1319: /* empty environment? Set the PATH variable to the root of the current drive */
1320:
1321: if (init_env[0] == 0) {
1322:
1323: static char path_env[] = "PATH=\0C:\0";
1324:
1325: path_env[6] = curproc->curdrv + 'A';
1326:
1327: init_env = path_env;
1328:
1329: }
1330:
1331:
1332:
1333: /* run any programs appearing after us in the AUTO folder */
1334:
1335: run_auto_prgs();
1336:
1337:
1338:
1339: /* run the initial program */
1340:
1341:
1342:
1343: r = p_exec(0, init_prg, init_tail, init_env);
1344:
1345:
1346:
1347: /* if it isn't found, and the user didn't say otherwise, try GEM */
1348:
1349: if (r == EFILNF && init_prg == orig_init) {
1350:
1351: if (!gem_active) {
1352:
1353: BASEPAGE *bp; int pid;
1354:
1355: bp = (BASEPAGE *)p_exec(7, (char *)7L, (char *)"\0", init_env);
1356:
1357: bp->p_tbase = *((long *) 0x4feL );
1358:
1359: pid = p_exec(106, (char *)"GEM", bp, 0L);
1360:
1361: if (pid > 0) {
1362:
1363: do {
1364:
1365: r = p_wait3(0, (long *)0);
1366:
1367: } while(pid != ((r & 0xffff0000) >> 16));
1368:
1369: r &= 0x0000ffff;
1370:
1371: }
1372:
1373: } else {
1374:
1375: Cconws("If MiNT is run after GEM starts, you must specify a program\r\n");
1376:
1377: Cconws("to run initially in MINT.CNF, with an INIT= line\r\n");
1378:
1379: r = 0;
1380:
1381: }
1382:
1383: }
1384:
1385:
1386:
1387: if (r < 0) {
1388:
1389: ksprintf(buf, "FATAL: couldn't run %s\r\n", init_prg);
1390:
1391: Cconws(buf);
1392:
1393: }
1394:
1395:
1396:
1397: if (r) {
1398:
1399: ksprintf(buf, "exit code: %ld\r\n", r);
1400:
1401: Cconws(buf);
1402:
1403: }
1404:
1405:
1406:
1407: /* shut down all processes gracefully */
1408:
1409: shutdown();
1410:
1411:
1412:
1413: /* put everything back and exit */
1414:
1415: restr_intr();
1416:
1417: close_filesys();
1418:
1419:
1420:
1421: (void)Super((void *)tosssp); /* gratuitous (void *) for Lattice */
1422:
1423: Cconws("leaving MiNT\r\n");
1424:
1425:
1426:
1427: if (curs_off)
1428:
1429: Cconws("\033f"); /* disable cursor */
1430:
1431:
1432:
1433: zeroexit();
1434:
1435: return 0; /* dummy -- this should never be reached */
1436:
1437: }
1438:
1439:
1440:
1441:
1442:
1443: /*
1444:
1445: * cookie jar handling routines. The "cookie jar" is an area of memory
1446:
1447: * reserved by TOS for TSR's and utility programs; the idea is that
1448:
1449: * you put a cookie in the jar to notify people of available services.
1450:
1451: * The BIOS uses the cookie jar in TOS 1.6 and higher; for earlier versions
1452:
1453: * of TOS, the jar is always empty (unless someone added a cookie before
1454:
1455: * us; POOLFIX does, for example).
1456:
1457: * MiNT establishes an entirely new cookie jar (with the old cookies copied
1458:
1459: * over) and frees it on exit. That's because TSR's run under MiNT
1460:
1461: * will no longer be accessible after MiNT exits.
1462:
1463: * MiNT also puts a cookie in the jar, with tag field 'MiNT' (of course)
1464:
1465: * and with the major version of MiNT in the high byte of the low word,
1466:
1467: * and the minor version in the low byte.
1468:
1469: */
1470:
1471:
1472:
1473: void
1474:
1475: install_cookies()
1476:
1477: {
1478:
1479: COOKIE *cookie;
1480:
1481: int i, ncookies;
1482:
1483:
1484:
1485: /* note that init_intr sets oldcookie to the old cookie jar */
1486:
1487:
1488:
1489: ncookies = 0;
1490:
1491: cookie = oldcookie;
1492:
1493: if (cookie) {
1494:
1495: while (cookie->tag.aslong != 0) {
1496:
1497: /* check for true FPU co-processor */
1498:
1499: if (!strncmp(cookie->tag.aschar, "_FPU",4) &&
1500:
1501: (cookie->value >> 16) >= 2)
1502:
1503: fpu = 1;
1504:
1505: /* check for _FLK cookie */
1506:
1507: else if (!strncmp(cookie->tag.aschar, "_FLK",4))
1508:
1509: flk = 1;
1510:
1511: cookie++; ncookies++;
1512:
1513: }
1514:
1515: }
1516:
1517:
1518:
1519: /* NOTE: obviously, we can do this only if init_intr is called
1520:
1521: * _after_ memory, processes, etc. have been initialized
1522:
1523: */
1524:
1525: newcookie = (COOKIE *)kmalloc((ncookies + 16)*sizeof(COOKIE));
1526:
1527: assert(newcookie);
1528:
1529:
1530:
1531: /* copy the old cookies to the new jar */
1532:
1533:
1534:
1535: for (i = 0; i < ncookies; i++) {
1536:
1537: newcookie[i] = oldcookie[i];
1538:
1539: }
1540:
1541:
1542:
1543: /* install MiNT cookie */
1544:
1545: strncpy(newcookie[i].tag.aschar, "MiNT", 4);
1546:
1547: newcookie[i].value = (MAJ_VERSION << 8) | MIN_VERSION;
1548:
1549: i++;
1550:
1551:
1552:
1553: /* install _FLK cookie to indicate that file locking works */
1554:
1555: if (!flk) {
1556:
1557: strncpy(newcookie[i].tag.aschar, "_FLK", 4);
1558:
1559: newcookie[i].value = 0;
1560:
1561: i++;
1562:
1563: }
1564:
1565:
1566:
1567: /* the last cookie should have a 0 tag, and a value indicating the number
1568:
1569: * of slots, total
1570:
1571: */
1572:
1573:
1574:
1575: newcookie[i].tag.aslong = 0;
1576:
1577: newcookie[i].value = ncookies+16;
1578:
1579:
1580:
1581: *CJAR = newcookie;
1582:
1583:
1584:
1585: }
1586:
1587:
1588:
1589: /*
1590:
1591: * get the value of the _MCH cookie, if one exists
1592:
1593: * this must be done in a separate routine because the machine type
1594:
1595: * is needed when initializing the bios file system, whereas
1596:
1597: * install_cookies is not called until everything is installed
1598:
1599: * In fact, getmch() should be called before *anything* else is
1600:
1601: * initialized, so that if we find a MiNT cookie already in the
1602:
1603: * jar we can bail out early.
1604:
1605: */
1606:
1607:
1608:
1609: static void
1610:
1611: getmch()
1612:
1613: {
1614:
1615: COOKIE *jar;
1616:
1617:
1618:
1619: jar = *CJAR; /* CJAR defined in cookie.h */
1620:
1621: if (jar) {
1622:
1623: while (jar->tag.aslong != 0) {
1624:
1625: /* check for machine type */
1626:
1627: if (!strncmp(jar->tag.aschar, "_MCH",4)) {
1628:
1629: mch = jar->value;
1630:
1631: } else if (!strncmp(jar->tag.aschar, "_CPU", 4)) {
1632:
1633: mcpu = jar->value;
1634:
1635: if (mcpu == 10)
1636:
1637: m68010 = 1;
1638:
1639: } else if (!strncmp(jar->tag.aschar, "MiNT",4)) {
1640:
1641: Cconws("MiNT is already installed!!\r\n");
1642:
1643: (void)Super((void *)tosssp);
1644:
1645: Pterm(2);
1646:
1647: }
1648:
1649: jar++;
1650:
1651: }
1652:
1653: }
1654:
1655: }
1656:
1657:
1658:
1659: /*
1660:
1661: * routines for reading the configuration file
1662:
1663: * we allow the following commands in the file:
1664:
1665: * # anything -- comment
1666:
1667: * INIT=file -- specify boot program
1668:
1669: * cd dir -- change directory/drive
1670:
1671: * ren file1 file2 -- rename a file
1672:
1673: * sln file1 file2 -- create a symbolic link
1674:
1675: * echo message -- print a message on the screen
1676:
1677: * setenv var value -- set an environment variable
1678:
1679: *
1680:
1681: * BUG: if you use setenv in mint.cnf, *none* of the original environment
1682:
1683: * gets passed to children. This is rarely a problem if mint.prg is
1684:
1685: * in the auto folder.
1686:
1687: */
1688:
1689:
1690:
1691: extern short bconbdev, bconbsiz; /* from bios.c */
1692:
1693:
1694:
1695: static void
1696:
1697: doset(name, val)
1698:
1699: char *name, *val;
1700:
1701: {
1702:
1703: char *t;
1704:
1705:
1706:
1707: if (!strcmp(name, "INIT")) {
1708:
1709: t = kmalloc(strlen(val)+1);
1710:
1711: if (!t) return;
1712:
1713: strcpy(t, val);
1714:
1715: init_prg = t;
1716:
1717: while (*t && !isspace(*t)) t++;
1718:
1719: /* get the command tail, too */
1720:
1721: if (*t) {
1722:
1723: *t++ = 0;
1724:
1725: strncpy(init_tail+1, t, 125);
1726:
1727: init_tail[0] = strlen(init_tail+1);
1728:
1729: }
1730:
1731: return;
1732:
1733: }
1734:
1735: if (!strcmp(name, "CON")) {
1736:
1737: FILEPTR *f;
1738:
1739: int i;
1740:
1741:
1742:
1743: f = do_open(val, O_RDWR, 0, (XATTR *)0);
1744:
1745: if (f) {
1746:
1747: for (i = -1; i < 2; i++) {
1748:
1749: do_close(curproc->handle[i]);
1750:
1751: curproc->handle[i] = f;
1752:
1753: f->links++;
1754:
1755: }
1756:
1757: f->links--; /* correct for overdoing it */
1758:
1759: }
1760:
1761: return;
1762:
1763: }
1764:
1765: if (!strcmp(name, "PRN")) {
1766:
1767: FILEPTR *f;
1768:
1769:
1770:
1771: f = do_open(val, O_RDWR|O_CREAT|O_TRUNC, 0, (XATTR *)0);
1772:
1773: if (f) {
1774:
1775: do_close(curproc->handle[2]);
1776:
1777: do_close(curproc->prn);
1778:
1779: curproc->prn = curproc->handle[2] = f;
1780:
1781: f->links = 2;
1782:
1783: }
1784:
1785: return;
1786:
1787: }
1788:
1789: if (!strcmp(name, "BIOSBUF")) {
1790:
1791: if (*val == 'n' || *val == 'N') {
1792:
1793: if (bconbsiz) bflush();
1794:
1795: bconbdev = -1;
1796:
1797: }
1798:
1799: return;
1800:
1801: }
1802:
1803: if (!strcmp(name, "PSEUDODRIVES")) {
1804:
1805: if (*val == 'n' || *val == 'N') {
1806:
1807: pseudodrives = 0;
1808:
1809: } else if (*val == 'm' || *val == 'M') {
1810:
1811: pseudodrives = 1;
1812:
1813: }
1814:
1815: return;
1816:
1817: }
1818:
1819: if (!strcmp(name, "DEBUG_LEVEL")) {
1820:
1821: extern int debug_level;
1822:
1823: if (*val >= '0' && *val <= '9') debug_level = atoi(val);
1824:
1825: else ALERT("Bad arg to \"DEBUG_LEVEL\" in cnf file");
1826:
1827: return;
1828:
1829: }
1830:
1831: if (!strcmp(name, "DEBUG_DEVNO")) {
1832:
1833: extern int out_device;
1834:
1835: if (*val >= '0' && *val <= '9') out_device= atoi(val);
1836:
1837: else ALERT("Bad arg to \"DEBUG_DEVNO\" in cnf file");
1838:
1839: return;
1840:
1841: }
1842:
1843:
1844:
1845: #ifdef FASTTEXT
1846:
1847: if (!strcmp(name, "HARDSCROLL")) {
1848:
1849: int i = 0;
1850:
1851: extern int hardscroll;
1852:
1853:
1854:
1855: i = *val++;
1856:
1857: if (i < '0' || i > '9') return;
1858:
1859: hardscroll = i-'0';
1860:
1861: i = *val;
1862:
1863: if (i < '0' || i > '9') return;
1864:
1865: hardscroll = 10*hardscroll + i - '0';
1866:
1867: return;
1868:
1869: }
1870:
1871: #endif
1872:
1873: ALERT("Unknown variable `%s'", name);
1874:
1875: }
1876:
1877:
1878:
1879: /* Execute a line from the config file */
1880:
1881: static void
1882:
1883: do_line(line)
1884:
1885: char *line;
1886:
1887: {
1888:
1889: char *cmd, *arg1, *arg2;
1890:
1891: char *newenv;
1892:
1893: char *t;
1894:
1895: int i;
1896:
1897:
1898:
1899: while (*line == ' ') line++;
1900:
1901: if (*line == '#') return; /* ignore comments */
1902:
1903: if (!*line) return; /* and also blank lines */
1904:
1905:
1906:
1907: cmd = line;
1908:
1909: /* check for variable assignments (e.g. INIT=, etc.) */
1910:
1911: /*
1912:
1913: * AGK: note we check for spaces whilst scanning so that an environment
1914:
1915: * variable may include an =, this has the unfortunate side effect that
1916:
1917: * the '=' _has_ to be concatenated to the variable name (INIT etc.)
1918:
1919: */
1920:
1921: for (t = cmd; *t && *t != ' '; t++) {
1922:
1923: if (*t == '=') {
1924:
1925: *t++ = 0;
1926:
1927: doset(cmd, t);
1928:
1929: return;
1930:
1931: }
1932:
1933: }
1934:
1935:
1936:
1937: /* OK, assume a regular command; break it up into 'cmd', 'arg1', arg2' */
1938:
1939:
1940:
1941: while (*line && *line != ' ') line++;
1942:
1943: if (*line == ' ') {
1944:
1945: *line++ = 0;
1946:
1947: while (*line == ' ') line++;
1948:
1949: }
1950:
1951:
1952:
1953: if (!strcmp(cmd, "echo")) {
1954:
1955: c_conws(line); c_conws("\r\n");
1956:
1957: return;
1958:
1959: }
1960:
1961: arg1 = line;
1962:
1963: while (*line && *line != ' ') line++;
1964:
1965: if (*line) {
1966:
1967: *line++ = 0;
1968:
1969: while (*line == ' ') line++;
1970:
1971: }
1972:
1973: if (!strcmp(cmd, "cd")) {
1974:
1975: int drv;
1976:
1977: (void)d_setpath(arg1);
1978:
1979: drv = toupper(*arg1) - 'A';
1980:
1981: if (arg1[1] == ':') (void)d_setdrv(drv);
1982:
1983: return;
1984:
1985: }
1986:
1987: if (!strcmp(cmd, "exec")) {
1988:
1989: static char cmdline[128];
1990:
1991: int i;
1992:
1993:
1994:
1995: i = strlen(line);
1996:
1997: if (i > 126) i = 126;
1998:
1999: cmdline[0] = i;
2000:
2001: strncpy(cmdline+1, line, i);
2002:
2003: cmdline[i+1] = 0;
2004:
2005: i = p_exec(0, arg1, cmdline, init_env);
2006:
2007: if (i == -33) {
2008:
2009: ALERT("%s: file not found", arg1);
2010:
2011: } else if (i < 0) {
2012:
2013: ALERT("%s: error while attempting to execute", arg1);
2014:
2015: }
2016:
2017: return;
2018:
2019: }
2020:
2021: if (!strcmp(cmd, "setenv")) {
2022:
2023: if (strlen(arg1) + strlen(line) + 4 + (env_ptr - init_env) >
2024:
2025: env_len) {
2026:
2027: newenv = umalloc(env_len + 1024L);
2028:
2029: t = init_env;
2030:
2031: env_ptr = newenv;
2032:
2033: for (i = 0; i < env_len; i++)
2034:
2035: *env_ptr++ = *t++;
2036:
2037: env_len += 1024;
2038:
2039: if (init_env)
2040:
2041: ufree(init_env);
2042:
2043: init_env = newenv;
2044:
2045: }
2046:
2047: while (*arg1) {
2048:
2049: *env_ptr++ = *arg1++; env_len++;
2050:
2051: }
2052:
2053: *env_ptr++ = '=';
2054:
2055: while (*line) {
2056:
2057: *env_ptr++ = *line++; env_len++;
2058:
2059: }
2060:
2061: *env_ptr++ = 0;
2062:
2063: *env_ptr = 0;
2064:
2065: return;
2066:
2067: }
2068:
2069:
2070:
2071: arg2 = line;
2072:
2073: while (*line && *line != ' ') line++;
2074:
2075: if (*line) {
2076:
2077: *line++ = 0;
2078:
2079: }
2080:
2081: if (!strcmp(cmd, "sln")) {
2082:
2083: (void)f_symlink(arg1, arg2);
2084:
2085: return;
2086:
2087: }
2088:
2089: if (!strcmp(cmd, "ren")) {
2090:
2091: (void)f_rename(0, arg1, arg2);
2092:
2093: return;
2094:
2095: }
2096:
2097: ALERT("syntax error in mint.cnf");
2098:
2099: }
2100:
2101:
2102:
2103: #define BUF 512
2104:
2105: #define LINE 256
2106:
2107:
2108:
2109: void
2110:
2111: load_config()
2112:
2113: {
2114:
2115: int fd;
2116:
2117: long r;
2118:
2119: char buf[BUF+1], c;
2120:
2121: char line[LINE+1];
2122:
2123: char *from;
2124:
2125: int count = 0;
2126:
2127:
2128:
2129: TRACE("reading configuration file");
2130:
2131: fd = f_open("mint.cnf", 0);
2132:
2133: if (fd < 0)
2134:
2135: fd = f_open("\\mint\\mint.cnf", 0);
2136:
2137: if (fd < 0) return;
2138:
2139: buf[BUF] = 0;
2140:
2141: from = &buf[BUF];
2142:
2143: line[LINE] = 0;
2144:
2145:
2146:
2147: for(;;) {
2148:
2149: c = *from++;
2150:
2151: if (!c) {
2152:
2153: r = f_read(fd, (long)BUF, buf);
2154:
2155: if (r <= 0) break;
2156:
2157: buf[r] = 0;
2158:
2159: from = buf;
2160:
2161: } else if (c == '\r') {
2162:
2163: continue;
2164:
2165: } else if (c == '\n') {
2166:
2167: line[count] = 0;
2168:
2169: do_line(line);
2170:
2171: count = 0;
2172:
2173: } else {
2174:
2175: if (count < LINE) {
2176:
2177: line[count++] = c;
2178:
2179: }
2180:
2181: }
2182:
2183: }
2184:
2185: f_close(fd);
2186:
2187: }
2188:
2189:
2190:
2191: /*
2192:
2193: * run programs in the AUTO folder that appear after MINT.PRG
2194:
2195: * some things to watch out for:
2196:
2197: * (1) make sure GEM isn't active
2198:
2199: * (2) make sure there really is a MINT.PRG in the auto folder
2200:
2201: */
2202:
2203:
2204:
2205: /*
2206:
2207: * some global variables used to see if GEM is active
2208:
2209: */
2210:
2211: static short aes_intout[64];
2212:
2213: static short aes_dummy[64];
2214:
2215: static short aes_globl[15];
2216:
2217: static short aes_cntrl[6] = { 10, 0, 1, 0, 0 };
2218:
2219:
2220:
2221: short *aes_pb[6] = { aes_cntrl, aes_globl, aes_dummy, aes_intout,
2222:
2223: aes_dummy, aes_dummy };
2224:
2225:
2226:
2227: /* check for whether GEM is active; remember, this *must* be done in
2228:
2229: * user mode
2230:
2231: */
2232:
2233:
2234:
2235: static int
2236:
2237: check_for_gem()
2238:
2239: {
2240:
2241: call_aes(aes_pb); /* does an appl_init */
2242:
2243: return aes_globl[0];
2244:
2245: }
2246:
2247:
2248:
2249: static void
2250:
2251: run_auto_prgs()
2252:
2253: {
2254:
2255: DTABUF *dta;
2256:
2257: long r;
2258:
2259: static char pathspec[32] = "\\AUTO\\";
2260:
2261: short runthem = 0; /* set to 1 after we find MINT.PRG */
2262:
2263:
2264:
2265: /* if the AES is running, don't check AUTO */
2266:
2267:
2268:
2269: if (gem_active) {
2270:
2271: return;
2272:
2273: }
2274:
2275:
2276:
2277: /* OK, now let's run through \\AUTO looking for
2278:
2279: * programs...
2280:
2281: */
2282:
2283: dta = (DTABUF *)f_getdta();
2284:
2285: r = f_sfirst("\\AUTO\\*.PRG", 0);
2286:
2287: while (r >= 0) {
2288:
2289: if (!strcmp(dta->dta_name, "MINT.PRG"))
2290:
2291: runthem = 1;
2292:
2293: else if (runthem) {
2294:
2295: strcpy(pathspec+6, dta->dta_name);
2296:
2297: (void)p_exec(0, pathspec, (char *)"", init_env);
2298:
2299: }
2300:
2301: r = f_snext();
2302:
2303: }
2304:
2305: }
2306:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.