|
|
1.1 root 1: /*
2: * sh/exec3.c
3: * Bourne shell.
4: * Builtin commands and shell functions.
5: */
6:
7: #include "sh.h"
8: #include <sys/times.h>
9: #include <sys/const.h> /* HZ defined here */
10:
11: #define HOUR (60L*60L*HZ)
12: #define MINUTE (60L*HZ)
13: #define SECOND HZ
14:
15: NODE *copy_node();
16: char *cd();
17:
18: extern s_colon();
19: extern s_dot();
20: extern s_break();
21: #define s_continue s_break
22: extern s_cd();
23: extern s_dirs();
24: extern s_eval();
25: extern s_exec();
26: extern s_exit();
27: extern s_export();
28: extern s_login();
29: #define s_newgrp s_login
30: extern s_popd();
31: extern s_pushd();
32: extern s_read();
33: #define s_readonly s_export
34: extern s_set();
35: extern s_shift();
36: extern s_times();
37: extern s_trap();
38: extern s_umask();
39: extern s_wait();
40: #define SNULL ((int (*)())0)
41:
42: /* Built-in functions. */
43: typedef struct {
44: int i_hash;
45: char *i_name;
46: int (*i_func)();
47: } INLINE;
48:
49: INLINE inls[] = {
50: 0, ":", s_colon,
51: 0, ".", s_dot,
52: 0, "break", s_break,
53: 0, "continue", s_continue,
54: 0, "cd", s_cd,
55: 0, "dirs", s_dirs,
56: 0, "eval", s_eval,
57: 0, "exec", s_exec,
58: 0, "exit", s_exit,
59: 0, "export", s_export,
60: 0, "login", s_login,
61: 0, "newgrp", s_newgrp,
62: 0, "popd", s_popd,
63: 0, "pushd", s_pushd,
64: 0, "read", s_read,
65: 0, "readonly", s_readonly,
66: 0, "set", s_set,
67: 0, "shift", s_shift,
68: 0, "times", s_times,
69: 0, "trap", s_trap,
70: 0, "umask", s_umask,
71: 0, "wait", s_wait,
72: 0, NULL, SNULL
73: };
74:
75: inline()
76: {
77: register int (*s_func)();
78: register INLINE *ip;
79: register int ahash;
80:
81: if (inls[0].i_hash==0)
82: for (ip=inls; ip->i_name!=NULL; ip++)
83: ip->i_hash = ihash(ip->i_name);
84: if (nargv[0] == NULL)
85: return 0;
86: ahash = ihash(nargv[0]);
87: for (ip=inls; ip->i_name!=NULL; ip++)
88: if (ip->i_hash==ahash && strcmp(nargv[0], ip->i_name)==0)
89: break;
90: if ((s_func=ip->i_func) == SNULL)
91: return 0;
92: /*
93: * Process exec specially, because it has unique semantics for
94: * redirection (among other things). Redirection of builtins is done
95: * using the "undo" facility because builtins must execute in the
96: * top-level environment. The original code here permitted a small
97: * subset of builtins to be redirected, but executed them in a
98: * subshell, giving bogus semantics (eg, it permitted "export" to be
99: * redirected, but in a subshell it had no effect).
100: */
101: if (s_func == s_exec)
102: slret = (* s_func) ();
103: else {
104: REDIR_UNDO * undo = NULL;
105:
106: if (redirect (niovp, & undo) < 0)
107: slret = 1;
108: else
109: slret = (* s_func) ();
110:
111: /*
112: * NB: The rationale for the following kludge is not clear,
113: * but with the ability to properly redirect builtins and
114: * execute the in the top-level context, it no longer works.
115: * It could be added back in, but since it conflicts with
116: * POSIX.2 anyway, why bother.
117: */
118: #if 0
119: if (s_func == s_eval)
120: slret = (*s_func)();
121: else {
122: /* Kludge stderr output to stdout. */
123: dup2(1, 2);
124: close(1);
125: slret = (*s_func)();
126: }
127: #endif
128:
129: redirundo (& undo);
130: }
131: return 1;
132: }
133:
134: ihash(cp)
135: register char *cp;
136: {
137: register int i;
138: for (i=0; *cp; i+=*cp++);
139: return i;
140: }
141:
142:
143: /*
144: * Actual builtin functions.
145: */
146: s_colon()
147: {
148: return 0;
149: }
150:
151: s_dot()
152: {
153: if (nargc==2) {
154: ffind(NULL);
155: if (ffind(vpath, nargv[1], 4))
156: return session(SFILE, duplstr(strt, 0));
157: else {
158: ecantfind(nargv[1]);
159: return 1;
160: }
161: } else if (nargc==1)
162: return 0;
163: syntax();
164: return 1;
165: }
166:
167: s_break()
168: {
169: register CON *cp;
170: register int t;
171: register int n;
172: int ret;
173:
174: ret = nargv[0][0]=='b' ? 2 : 1;
175: n = nargc>1 ? atoi(nargv[1]) : 1;
176: for (cp = sesp->s_con; cp; cp = cp->c_next) {
177: t = cp->c_node->n_type;
178: if ((t==NWHILE || t==NFOR || t==NUNTIL) && --n == 0) {
179: sesp->s_con = cp;
180: longjmp(cp->c_envl, ret);
181: break;
182: }
183: freebuf(cp->c_bpp);
184: }
185: printe("%s out of bounds", ret==1 ? "Continue" : "Break");
186: }
187:
188: /* s_continue is overlaid with s_break */
189:
190: s_cd()
191: {
192: register char *dir;
193:
194: if ((dir = cd((nargc<2) ? vhome : nargv[1])) == NULL)
195: return -1; /* cd failed */
196: if (dstack[dstkp] != NULL)
197: sfree(dstack[dstkp]);
198: dstack[dstkp] = duplstr(dir, 1); /* update dir stack */
199: return 0;
200: }
201:
202: s_dirs()
203: {
204: register int i;
205:
206: for (i = dstkp; i >= 0; i--)
207: printf (stdout, "%s ", dstack[i]);
208: fputc('\n', stderr);
209: }
210:
211: s_eval()
212: {
213: if (nargc>1)
214: return session(SARGV, ++nargv);
215: else
216: return 0;
217: }
218:
219: s_exec()
220: {
221: if (redirect (niovp, NULL) < 0) {
222: if (nargc>1) {
223: exit(1);
224: NOTREACHED;
225: }
226: return 1;
227: }
228: if (nargc==1)
229: return 0;
230: if (no1flag) {
231: cleanup_shell_fns ();
232: unlink_temp (capture_temp ());
233: }
234: dflttrp(ICMD);
235: ++nargv;
236: --nargc;
237: nenvp = envlvar(nenvp);
238: flexec();
239: exit(1);
240: NOTREACHED;
241: }
242:
243: s_exit()
244: {
245: if (nargc > 1)
246: slret = atoi(nargv[1]);
247: reset(RUEXITS);
248: NOTREACHED;
249: }
250:
251: s_export()
252: {
253: register int flag;
254: register char **varv;
255:
256: flag = nargv[0][0]=='e' ? VEXP : VRDO;
257: if (nargc < 2)
258: tellvar(flag);
259: else
260: for (varv=++nargv; *varv; )
261: if (namevar(*varv))
262: flagvar(*varv++, flag);
263: else
264: eillvar(*varv++);
265: return 0;
266: }
267:
268: s_login()
269: {
270: register char *cmd;
271:
272: cmd = nargv[0][0]=='l' ? "/bin/login" : "/bin/newgrp";
273: execve(cmd, nargv, envlvar(nenvp));
274: ecantfind(cmd);
275: return 1;
276: }
277:
278: /* s_newgrp is overlaid with s_login */
279:
280: s_popd()
281: {
282: register int i, j, n, ret;
283:
284: if (nargc == 1)
285: return popd();
286: /*
287: * Kludge to pop one or more specific dir stack elements.
288: * Do args backwards so e.g. "popd 2 3 4" works as expected.
289: * Internal indices [0, dstkp] are user indices [dstkp, 0].
290: */
291: for (ret = 0, i = nargc-1; i > 0; i--) {
292: if ((n = atoi(nargv[i])) == 0)
293: ret |= popd();
294: else if (n < 0 || n > dstkp) {
295: printe("Illegal arg: %d", n);
296: ret = -1;
297: continue;
298: } else {
299: j = dstkp - n;
300: if (dstack[j] != NULL)
301: sfree(dstack[j]);
302: for ( ; j < dstkp; j++)
303: dstack[j] = dstack[j+1];
304: --dstkp;
305: }
306: }
307: return ret;
308: }
309:
310: s_pushd()
311: {
312: register char *dir;
313: register int i, ret;
314:
315: if (nargc == 1) {
316: /* Exchange top two stack elements. */
317: if (dstkp == 0)
318: return 1; /* only one element on stack */
319: dir = dstack[dstkp-1];
320: dstack[dstkp-1] = dstack[dstkp];
321: dstack[dstkp] = dir; /* exchange top two */
322: return ((cd(dir) == NULL) ? -1 : 0); /* and cd accordingly */
323: }
324: /* Push one or more directories to stack. */
325: for (ret = 0, i = 1; i < nargc; i++)
326: ret |= pushd(nargv[i]);
327: return ret;
328: }
329:
330: /*
331: * NB: For s_read (), wrap up the temporary-space global bullshit. Later we
332: * can add parameters to this stuff to get the effect of string streams.
333: */
334:
335: #define temp_string_begin() ((void) (strp = strt))
336: #define temp_string_max() STRSIZE
337: #define temp_string_add(c) \
338: (strp < strt + temp_string_max () ? (void) (* strp ++ = c) : \
339: (void) etoolong ())
340: #define temp_string_end() (strt)
341: #define temp_string_end_copy() (duplstr (temp_string_end (), 1))
342: #define temp_string_temp_copy() (duplstr (temp_string_end (), 0))
343: #define temp_mark() (0)
344: #define temp_release(x) ((void) x)
345:
346: typedef int temp_mark_type;
347:
348: #define ARGS(x) x
349: void temp_string_begin ARGS (()) {
350: temp_string_begin ();
351: }
352: int temp_string_max ARGS (()) {
353: return temp_string_max ();
354: }
355: void temp_string_add ARGS ((c)) int c; {
356: temp_string_add (c);
357: }
358: char * temp_string_end ARGS (()) {
359: return temp_string_end ();
360: }
361: char * temp_string_end_copy ARGS (()) {
362: return temp_string_end_copy ();
363: }
364: char * temp_string_temp_copy ARGS (()) {
365: return temp_string_temp_copy ();
366: }
367: temp_mark_type temp_mark ARGS (()) {
368: return temp_mark ();
369: }
370: void temp_release ARGS ((m)) temp_mark_type m; {
371: temp_release (m);
372: }
373:
374: /*
375: * NB: Originally, this code used yylex () to break the input up into words.
376: * This was a bad idea, because this caused the IFS variable to have no
377: * useful effect. Now we just read using <stdio.h> functions, because the
378: * extra machinery for session-management doesn't seem to buy anything.
379: */
380:
381: s_read()
382: {
383: char ** argp = nargv + 1;
384: char ** arg_end = argp + (nargc - 1);
385: char * delimiters;
386: int ch;
387:
388: while (argp < arg_end)
389: if (! namevar (* argp ++))
390: eillvar (argp - 1);
391:
392: delimiters = vifs == NULL ? " \t" : vifs;
393:
394: argp = nargv + 1;
395: while (argp < arg_end) {
396: temp_mark_type mark;
397: enum {
398: EAT_WS,
399: WORD
400: } state = EAT_WS;
401:
402: mark = temp_mark ();
403: temp_string_begin ();
404:
405: if (argp + 1 == arg_end)
406: delimiters = "";
407:
408: while ((ch = getc (stdin)) != EOF && ch != '\n') {
409:
410: if (strchr (delimiters, ch) != NULL) {
411:
412: if (state == EAT_WS)
413: continue;
414: break;
415: }
416:
417: temp_string_add (ch);
418: state = WORD;
419: }
420: temp_string_add (0);
421:
422: assnvar (* argp ++, temp_string_temp_copy ());
423: temp_release (mark);
424:
425: if (ch == EOF || ch == '\n')
426: break;
427: }
428:
429: while (argp < arg_end)
430: assnvar (* argp ++, "");
431:
432: return ch == EOF;
433: }
434:
435:
436: /* s_readonly overlaid with s_export */
437:
438: s_set()
439: {
440: if (nargc < 2) {
441: tellvar(0);
442: return 0;
443: }
444: return set(nargc, nargv, 0);
445: }
446:
447: s_shift()
448: {
449: register int n;
450:
451: n = nargc > 1 ? atoi(nargv[1]) : 1;
452: while (n > 0 && sargc > 0) {
453: n -= 1;
454: sargc -= 1;
455: sargp += 1;
456: }
457: return n!=0;
458: }
459:
460: s_times()
461: {
462: #if _I386
463: #define tb_cutime tms_cutime
464: #define tb_cstime tms_cstime
465: struct tms tb;
466: #else
467: struct tbuffer tb;
468:
469: #endif
470:
471: times(&tb);
472: ptime(tb.tb_cutime);
473: ptime(tb.tb_cstime);
474: puts ("\n");
475: return 0;
476: }
477:
478: s_trap()
479: {
480: register char **vp;
481: register char *cp;
482: register int err;
483:
484: err = 0;
485: if (nargc==1)
486: return telltrp();
487: vp = ++nargv;
488: cp = *vp;
489: if (class(cp[0], MDIGI)
490: && (cp[1]=='\0' || (class(cp[1], MDIGI) && cp[2]=='\0')))
491: cp = NULL;
492: else
493: ++vp;
494: while (*vp) {
495: if (class(vp[0][0], MDIGI))
496: err |= setstrp(atoi(*vp++), cp);
497: else {
498: printe("Bad trap: %s", *vp++);
499: err |= 1;
500: }
501: }
502: return err;
503: }
504:
505: s_umask()
506: {
507: if (nargc < 2)
508: printf ("%03o\n", ufmask);
509: else
510: umask(ufmask = atoi(nargv[1]));
511: return 0;
512: }
513:
514: s_wait()
515: {
516: register int f;
517:
518: f = (nargc > 1) ? atoi(nargv[1]) : 0;
519: if (f > 0)
520: f = -f;
521: waitc(f);
522: return slret;
523: }
524:
525: /*
526: * Change to given directory.
527: * Update global variable CWD accordingly.
528: * Return NULL if bad, otherwise full pathname of the directory.
529: */
530: char *
531: cd(dir) register char *dir;
532: {
533: if (chdir(dir) < 0) {
534: printe("%s: bad directory", dir);
535: return NULL;
536: }
537: if (*dir != '/') {
538: /*
539: * Find an absolute pathname for the dstack and $CWD.
540: * The directory now in dstack[dstkp] is "." if _getwd() failed
541: * for any reason (e.g., the user lacks search permission
542: * down the path to "/", or "." was rm'ed by another process).
543: * Avoid _getwd() in this case, it can undo the chdir() above.
544: */
545: if ((strcmp(dstack[dstkp], ".") == 0)
546: || ((dir = _getwd()) == NULL))
547: return NULL;
548: }
549: assnvar("CWD", dir);
550: return dir;
551: }
552:
553: /*
554: * Pop the directory stack and change to the previous stacked directory.
555: */
556: popd()
557: {
558: if (dstkp == 0) {
559: printe("Directory stack underflow");
560: return -1;
561: }
562: if (dstack[dstkp] != NULL)
563: sfree(dstack[dstkp]);
564: return (cd(dstack[--dstkp]) == NULL ? -1 : 0);
565: }
566:
567: /*
568: * Change to given directory and add it to the directory stack.
569: */
570: pushd(dir) register char *dir;
571: {
572: if ((dir = cd(dir)) == NULL)
573: return -1; /* cd failed */
574: if (++dstkp >= DSTACKN) {
575: --dstkp;
576: printe("Directory stack overflow");
577: return -1;
578: }
579: dstack[dstkp] = duplstr(dir, 1);
580: return 0;
581: }
582:
583: /*
584: * The set command. This is also called from `main' to set
585: * options from the command line. In this case `flag' is
586: * set.
587: */
588: set(argc, argv, flag)
589: register char *argv[];
590: {
591: int n;
592: register char *cp;
593:
594: if (flag) {
595: cflag = 0;
596: iflag = 0;
597: sflag = 0;
598: }
599: n = 0;
600: if (argc > 0)
601: n = 1;
602: if (argc>1 && argv[1][0]=='-') {
603: register char *fp;
604:
605: n++;
606: for (cp = &argv[1][1]; *cp; cp++) {
607: if ((fp=index(shfnams, *cp)) == NULL
608: || (fp+=shflags-shfnams) == NULL
609: || (fp != &lgnflag && fp > &xflag && flag == 0))
610: printe("-%c: Bad option", *cp);
611: else if (fp != &lgnflag)
612: *fp = *cp;
613: }
614: if (cp == &argv[1][1]) {
615: vflag = 0;
616: xflag = 0;
617: }
618: if (flag == 0 && argc == 2)
619: return errflag;
620: }
621: if (errflag)
622: return 1;
623: if (sargv != NULL)
624: vfree(sargv);
625: sargv = vdupl(argv);
626: sargc = argc - n;
627: sargp = sargv + n;
628: return 0;
629: }
630:
631: /*
632: * print the time as XmX.Xs
633: * as fixed by Randall.
634: */
635: ptime(t)
636: long t;
637: {
638: register int ticks, tenths, seconds;
639:
640: printf ("%ldm", t/MINUTE);
641: ticks = t%MINUTE;
642: seconds = ticks/SECOND;
643: tenths = (ticks%SECOND + SECOND/20)/(SECOND/10);
644: if (tenths == 10) {
645: tenths = 0;
646: seconds++;
647: }
648: printf ("%d.%ds ", seconds, tenths);
649: }
650:
651: /* User-defined shell functions. */
652:
653: /*
654: * Lookup a shell function name.
655: * The hashing is probably irrelevant.
656: */
657: SHFUNC *
658: lookup_sh_fn(name) char *name;
659: {
660: register SHFUNC *fnp;
661: register int ahash;
662:
663: ahash = ihash(name);
664: for (fnp=sh_fnp; fnp != NULL; fnp = fnp->fn_link)
665: if (fnp->fn_hash==ahash && strcmp(name, fnp->fn_name)==0)
666: return fnp;
667: return NULL;
668: }
669:
670:
671: /*
672: * When a subshell is created, the functions are available in the subshell
673: * but we don't want to lose the here-documents.
674: */
675:
676: void subshell_shell_fns ()
677: {
678: SHFUNC * scan;
679:
680: scan = sh_fnp;
681:
682: while (scan != NULL) {
683: forget_temp (scan->fn_temp);
684: scan->fn_temp = NULL;
685: scan = scan->fn_link;
686: }
687: }
688:
689:
690: /*
691: * Unset a shell function, freeing the function body and detaching any
692: * temporary files.
693: */
694:
695: void cleanup_shell_fn (fnp)
696: SHFUNC * fnp;
697: {
698: free_node (fnp->fn_body);
699: unlink_temp (fnp->fn_temp);
700: }
701:
702:
703: /*
704: * Before final exit, clean up all shell functions. Freeing the memory of
705: * the function bodies is a waste of time here, so we leave that alone.
706: */
707:
708: void cleanup_shell_fns ()
709: {
710: SHFUNC * scan;
711: SHFUNC * next;
712:
713: for (scan = sh_fnp ; scan != NULL ; scan = next) {
714: next = scan->fn_link;
715: unlink_temp (scan->fn_temp);
716: #if 0
717: cleanup_shell_fn (scan);
718: sfree (scan->fn_name);
719: sfree (scan);
720: #endif
721: }
722:
723: sh_fnp = NULL;
724: }
725:
726:
727: /*
728: * Define a shell function.
729: */
730: def_shell_fn(np) register NODE *np;
731: {
732: register char *name;
733: register SHFUNC *fnp;
734:
735: name = np->n_strp;
736: if ((fnp = lookup_sh_fn(name)) != NULL)
737: cleanup_shell_fn (fnp); /* redeclared, free old body */
738: else {
739: fnp = salloc(sizeof *fnp); /* allocate new function */
740: fnp->fn_link = sh_fnp; /* add it to list */
741: sh_fnp = fnp;
742: fnp->fn_hash = ihash(name); /* and set member info */
743: fnp->fn_name = duplstr(name, 1);
744: }
745: fnp->fn_temp = capture_temp ();
746: fnp->fn_body = copy_node(np->n_next); /* and copy function body */
747: }
748:
749: /*
750: * Look for a shell function, execute it if found.
751: */
752: int
753: sh_fn()
754: {
755: register SHFUNC *fnp;
756: CON *ocon;
757: int oargc;
758: char *oarg0;
759: char **oargv, **oargp;
760:
761: if (nargv[0] == NULL)
762: return 0;
763: if ((fnp = lookup_sh_fn(nargv[0])) == NULL)
764: return 0;
765:
766: /* Set up sargc, sarg0, sargv, sargp here for $1 etc. to work. */
767: oarg0 = sarg0;
768: oargc = sargc;
769: oargv = sargv;
770: oargp = sargp;
771: sarg0 = nargv[0];
772: sargc = nargc - 1;
773: sargp = sargv = vdupl(nargv+1);
774: ocon = sesp->s_con;
775: sesp->s_con = NULL;
776:
777: ++in_sh_fn;
778: slret = command(fnp->fn_body); /* execute it */
779: --in_sh_fn;
780: ret_done = 0;
781: vfree(sargv);
782:
783: sesp->s_con = ocon;
784: sarg0 = oarg0;
785: sargc = oargc;
786: sargv = oargv;
787: sargp = oargp;
788: return 1;
789: }
790:
791: /*
792: * Recursively allocate a fresh copy of a NODE.
793: * Examines type to decide if node uses strp or auxp.
794: * Watch out for nodes which contain loops: NFOR2, NWHILE, NUNTIL.
795: */
796: NODE *
797: copy_node(np) NODE *np;
798: {
799: register NODE *newnp;
800: int flag;
801:
802: #if 0
803: printf("copy_node(%x)", np);
804: if (np != NULL)
805: printf(" type=%d", np->n_type);
806: printf("\n");
807: #endif
808: if (np == NULL)
809: return NULL;
810: flag = 0;
811: newnp = salloc(sizeof *np); /* allocate new NODE */
812: newnp->n_type = np->n_type;
813: switch(np->n_type) {
814:
815: /* The following cases use the strp member. */
816: case NRET:
817: case NFOR:
818: case NARGS:
819: case NASSG:
820: case NCASE:
821: case NCASE3:
822: case NIORS:
823: case NFUNC:
824: newnp->n_strp = duplstr(np->n_strp, 1);
825: break;
826:
827: /* The following cases use the auxp member. */
828: case NWHILE:
829: case NUNTIL:
830: case NFOR2:
831: flag++;
832: np->n_next->n_next = NULL; /* zap the loop for recursion */
833: /* fall through... */
834: case NNULL:
835: case NCOMS:
836: case NCTRL:
837: case NBRAC:
838: case NPARN:
839: case NIF:
840: case NELSE:
841: case NCASE2:
842: case NLIST:
843: case NANDF:
844: case NORF:
845: case NBACK:
846: case NPIPE:
847: newnp->n_auxp = copy_node(np->n_auxp);
848: break;
849:
850: case NRPIPE:
851: case NWPIPE:
852: default:
853: /* ??? */
854: printf("type=%d\n", np->n_type);
855: panic(9);
856: }
857: newnp->n_next = copy_node(np->n_next);
858: if (flag)
859: newnp->n_next->n_next = newnp; /* restore loop */
860: return newnp;
861: }
862:
863: /*
864: * Undo the above.
865: */
866: free_node(np) register NODE *np;
867: {
868: if (np == NULL)
869: return;
870: switch(np->n_type) {
871:
872: /* strp */
873: case NRET:
874: case NFOR:
875: case NARGS:
876: case NASSG:
877: case NCASE:
878: case NCASE3:
879: case NIORS:
880: case NFUNC:
881: sfree(np->n_strp);
882: break;
883:
884: /* auxp */
885: case NWHILE:
886: case NUNTIL:
887: case NFOR2:
888: np->n_next->n_next = NULL; /* zap the loop for recursion */
889: /* fall through... */
890: case NNULL:
891: case NCOMS:
892: case NCTRL:
893: case NBRAC:
894: case NPARN:
895: case NIF:
896: case NELSE:
897: case NCASE2:
898: case NLIST:
899: case NANDF:
900: case NORF:
901: case NBACK:
902: case NPIPE:
903: free_node(np->n_auxp);
904: break;
905:
906: case NRPIPE:
907: case NWPIPE:
908: default:
909: /* ??? */
910: printf("type=%d\n", np->n_type);
911: panic(10);
912: }
913: free_node(np->n_next);
914: sfree(np);
915: }
916:
917: /* end of sh/exec3.c */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.