|
|
1.1 root 1: /*==================================================================*\
2: * Vmm_user.c - routines for handling messages not processed *
3: * by the standard message processing routine *
4: * Created 1990, Microsoft, IBM Corp. *
5: *------------------------------------------------------------------*
6: * *
7: * This module contains the code for processing messages sent *
8: * to the standard window that the standard window does not *
9: * process. The application developer need only modify this *
10: * file in order to implement new menu items or process *
11: * messages not handled by the standard message routine. *
12: * *
13: * This module also contains some routines that demonstate the *
14: * various dialog box controls and message box types that can *
15: * be used. The sample code should be deleted when this *
16: * module is modified for an application. The demonstration *
17: * code is identified by comments. *
18: * *
19: *------------------------------------------------------------------*
20: * *
21: * This source file contains the following functions: *
22: * *
23: * UserWndProc(hwnd, msg, mp1, mp2) - user window procedure *
24: * UserCommand(mp1, mp2) - user WM_COMMAND processor *
25: * VMM_Error(pszFunction, ulErrorCode) - Error processing *
26: * CleanUpArray(pvAddress) - Page table maintenance *
27: * StoreInArray(pvAddress, ulNumPages) - Page table maintenance*
28: * *
29: \*==================================================================*/
30:
31: /*------------------------------------------------------------------*\
32: * Include files, macros, defined constants, and externs *
33: \*------------------------------------------------------------------*/
34:
35: #define LINT_ARGS
36:
37: #define INCL_WIN
38: #define INCL_DOSPROCESS
39: #define INCL_DOSERRORS
40: #include <os2.h>
41: #include "stdio.h"
42: #include "vmm_main.h"
43: #include "vmm_dlg.h"
44: #include "vmm_xtrn.h"
45: #include "string.h"
46: extern ULONG clrBackground, clrForeground;
47:
48: /*--------------------------------------------------------------*\
49: * Global variables *
50: \*--------------------------------------------------------------*/
51:
52: ULONG ulFreePage = 0L; /* First free page entry in array */
53:
54: PAGEENTRY apgentry[MAXPAGES]; /* Application page table */
55:
56: OBJSTRUCT ObjAlloc;
57:
58: char szBuffer[BUFF_SIZE];
59:
60: MSGENTRY amsgentry[] =
61: {
62: { ERROR_INVALID_PARAMETER,
63: "%s\n\nInvalid Parameter",
64: MB_ICONASTERISK
65: },
66:
67: { ERROR_ACCESS_DENIED,
68: "%s\n\nAccess to Memory Denied",
69: MB_ICONASTERISK
70: },
71:
72: { ERROR_NOT_ENOUGH_MEMORY,
73: "%s\n\nInsufficient Memory",
74: MB_ICONASTERISK
75: },
76:
77: { ERROR_INVALID_ACCESS,
78: "%s\n\nAn Invalid access to memory was"
79: " attempted.",
80: MB_ICONASTERISK
81: },
82:
83: { ERROR_INVALID_ADDRESS,
84: "%s\n\nAddress Given is Invalid",
85: MB_ICONASTERISK
86: },
87:
88: { ERROR_INTERRUPT,
89: "%s\n\nThe call was interrupted by an external"
90: " event and was not completed. Please retry the"
91: " operation.",
92: MB_ICONASTERISK
93: },
94:
95: /* Errors following are application defined */
96:
97: { VMERR_MAX_ALLOCATED,
98: "%s\n\nThe allocation has surpassed"
99: " this application's maximum number of displayable"
100: " pages of %u. Each page contains 4096 bytes"
101: " of data.",
102: MB_ICONASTERISK
103: },
104:
105: { VMERR_ZERO_ALLOCATED,
106: "%s\n\nA minimum of 1 byte must be specified for"
107: " an allocation.",
108: MB_ICONASTERISK
109: },
110:
111: { VMERR_TILE_ONLY,
112: "%s\n\nAllocation attempted with"
113: " tiled attribute and no access"
114: " protection specifed. Access protection required to"
115: " properly allocate memory.",
116: MB_ICONASTERISK
117: },
118:
119: { VMERR_COMMIT_AND_TILE_ONLY,
120: "%s\n\nAllocation attempted with no access"
121: " protection specified. Access protection required to"
122: " properly allocate memory.",
123: MB_ICONASTERISK
124: },
125:
126: { VMERR_ALREADY_FREED,
127: "%s\n\nThe memory address specified to be"
128: " freed has not been allocated. Only previously"
129: " allocated memory may be freed.",
130: MB_ICONASTERISK
131: },
132:
133: { VMERR_NOT_BASEPAGE,
134: "%s\n\nThe memory address specified to be"
135: " freed is not a base-page address. Only"
136: " base pages"
137: " may be freed.",
138: MB_ICONASTERISK
139: },
140:
141: { VMERR_DECOMMIT_RESERVED,
142: "%s\n\nThe memory specified to be"
143: " decommitted is in a reserved"
144: " state. Memory must"
145: " be committed before it can be decommitted.",
146: MB_ICONASTERISK
147: },
148:
149: { VMERR_ACCESS_ON_RESERVED,
150: "%s\n\nAccess protection specified for"
151: " reserved memory. Access protection may "
152: " only be specified for committed memory.",
153: MB_ICONASTERISK
154: },
155:
156: { VMERR_ACCESS_AND_DECOMMIT,
157: "%s\n\nAccess protection specified when"
158: " attempting to decommit memory. No access protection"
159: " may be specified when decommitting memory.",
160: MB_ICONASTERISK
161: },
162:
163: { VMERR_COMMIT_ON_COMMITTED,
164: "%s\n\nCommit Memory specified for"
165: " previously"
166: " committed memory. Choose \"Default\" or \""
167: "Decommit\" for committed memory.",
168: MB_ICONASTERISK
169: },
170:
171: { VMERR_COMMIT_ONLY,
172: "%s\n\nCommit Memory specified with"
173: " no access"
174: " protection specifed. Access protection required when"
175: " committing memory.",
176: MB_ICONASTERISK
177: },
178:
179: { VMERR_SET_NO_PARMS,
180: "%s\n\nNo parameters specified. Parameters"
181: " must be specified when using %s.",
182: MB_ICONASTERISK
183: },
184:
185: { VMERR_SET_ZERO_SIZE,
186: "%s\n\nSetting allocation attributes and/or access protection"
187: " on zero bytes is not permitted."
188: " A minimum size of one byte must be specified to set"
189: " allocation attributes and/or access protection on memory.",
190: MB_ICONASTERISK
191: },
192:
193: { VMERR_SET_ON_FREE,
194: "%s\n\nSetting allocation attributes and/or access protection"
195: " on non-allocated memory is not"
196: " permitted. Memory must be allocated before allocation attributes"
197: " or access protection may be specified for it.",
198: MB_ICONASTERISK
199: },
200:
201: { VMERR_DEFAULT,
202: "%s\n\n Error #%u occured during the call.",
203: MB_ICONASTERISK
204: }
205: };
206:
207: /*--------------------------------------------------------------*\
208: * Entry point declarations *
209: \*--------------------------------------------------------------*/
210:
211: VOID VMM_Error(PSZ pszFunction, ULONG ulErrorCode);
212:
213: VOID CleanUpArray(PVOID pvAddress);
214:
215: VOID StoreInArray(PVOID pvAddress, ULONG ulNumPages);
216:
217: MRESULT EXPENTRY ReadMemDlgProc(HWND hwnd, USHORT msg,
218: MPARAM mp1, MPARAM mp2);
219:
220: MRESULT EXPENTRY WriteMemDlgProc(HWND hwnd, USHORT msg,
221: MPARAM mp1, MPARAM mp2);
222:
223: MRESULT EXPENTRY FreeMemDlgProc(HWND hwnd, USHORT msg,
224: MPARAM mp1, MPARAM mp2);
225:
226: MRESULT EXPENTRY SetMem1DlgProc(HWND hwnd, USHORT msg,
227: MPARAM mp1, MPARAM mp2);
228:
229: MRESULT EXPENTRY SetMem2DlgProc(HWND hwnd, USHORT msg,
230: MPARAM mp1, MPARAM mp2);
231:
232: MRESULT EXPENTRY AllocMemDlgProc(HWND hwnd, USHORT msg,
233: MPARAM mp1, MPARAM mp2);
234:
235: /****************************************************************\
236: * Non-standard window message processing routine *
237: *--------------------------------------------------------------*
238: * *
239: * Name: UserWndProc(hwnd, msg, mp1, mp2) *
240: * *
241: * Purpose: Process any messages sent to hwndMain that *
242: * are not processed by the standard window *
243: * procedure *
244: * *
245: * Usage: Routine is called for each message MainWndProc *
246: * does not process *
247: * *
248: * Method: A switch statement branches control based upon *
249: * the message passed. Any messages not processed *
250: * here must be passed onto WinDefWindowProc() *
251: * *
252: * Returns: Return value depended upon the message processed *
253: \****************************************************************/
254: MRESULT UserWndProc(hwnd, msg, mp1, mp2)
255: HWND hwnd; /* handle of window */
256: USHORT msg; /* id of message */
257: MPARAM mp1; /* first message parameter */
258: MPARAM mp2; /* second message parameter */
259: {
260:
261: switch(msg) {
262:
263: /*--------------------------------------------------------------*\
264: * Add case statements for message ids you wish to process *
265: \*--------------------------------------------------------------*/
266:
267: default: /* default must call WinDefWindowProc() */
268: return(WinDefWindowProc(hwnd, msg, mp1, mp2));
269: break;
270: }
271:
272: return 0L;
273:
274: } /* UserWndProc() */
275:
276: /****************************************************************\
277: * DOS API and Application Specific Error reporting routine *
278: *--------------------------------------------------------------*
279: * *
280: * Name: VMM_Error(pszFunction, ulErrorCode) *
281: * *
282: * Purpose: Display errors returned by system calls to *
283: * the user when they occur. All other error handling *
284: * is done inside the functions themselves *
285: * *
286: * Usage: Routine is called for each error that the dialog *
287: * functions do not process *
288: * *
289: * Method: A switch statement branches control based upon *
290: * the error number passed. Any message that comes *
291: * in that is not known is given a generic message. *
292: * The message is displayed with WinMessageBox *
293: * *
294: * Returns: *
295: \****************************************************************/
296: VOID VMM_Error(pszFunction, ulErrorCode)
297: PSZ pszFunction;
298: ULONG ulErrorCode;
299: {
300: char szTempBuf[BUFF_SIZE];
301: ULONG ulMsgIndex;
302:
303: ulMsgIndex = 0L;
304:
305: while((amsgentry[ulMsgIndex].ulMsgNum != ulErrorCode) &&
306: (amsgentry[ulMsgIndex].ulMsgNum != VMERR_DEFAULT))
307: ulMsgIndex++;
308:
309: strcpy(szTempBuf,amsgentry[ulMsgIndex].szMsgText);
310:
311: if (amsgentry[ulMsgIndex].ulMsgNum == VMERR_DEFAULT)
312: {
313: sprintf(szBuffer,szTempBuf,pszFunction,ulErrorCode);
314: }
315: else if (amsgentry[ulMsgIndex].ulMsgNum == VMERR_MAX_ALLOCATED)
316: {
317: sprintf(szBuffer,szTempBuf,pszFunction,MAXPAGES);
318: }
319: else if (amsgentry[ulMsgIndex].ulMsgNum == VMERR_SET_NO_PARMS)
320: {
321: sprintf(szBuffer,szTempBuf,pszFunction,pszFunction);
322: }
323: else
324: {
325: sprintf(szBuffer,szTempBuf,pszFunction);
326: }
327:
328: WinMessageBox(HWND_DESKTOP,
329: hwndMain,
330: szBuffer,
331: szAppName,
332: 0,
333: MB_OK | amsgentry[ulMsgIndex].usMsgIcon);
334:
335: }
336:
337: /****************************************************************\
338: * Procedure to remove freed pages from array *
339: *--------------------------------------------------------------*
340: * *
341: * Name: CleanUpArray(pvAddress) *
342: * *
343: * Purpose: Remove pages that the user indicated from the *
344: * array of pages stored *
345: * *
346: * Usage: Routine is called after each successful free *
347: * memory call issued by the user *
348: * *
349: * Method: Pages are removed by finding the address in the *
350: * array that matches what the user input, and then *
351: * removing all subsequent pages until another base *
352: * page is found or the last stored page is reached *
353: * *
354: * Returns: *
355: \****************************************************************/
356: VOID CleanUpArray(pvAddress)
357: PVOID pvAddress;
358: {
359: ULONG ulIndexLow;
360: ULONG ulIndexHigh;
361:
362: /* At entry, we know that the pvAddress page is a base page and we
363: need to free up the pages associated with the base page for the
364: object. To do this, we find the next base address. We then make
365: the copies. */
366:
367: ulIndexLow = 0;
368:
369: while((apgentry[ulIndexLow].pvAddress != pvAddress) &&
370: (ulIndexLow < ulFreePage))
371: ulIndexLow++;
372:
373: /* If DosFreeMem worked but the address is not in our page array,
374: then don't modify the array or the ulFreePage count. */
375:
376: if (ulIndexLow < ulFreePage)
377: {
378: apgentry[ulIndexLow].fBaseAddr = FALSE;
379:
380: ulIndexHigh = ulIndexLow + 1;
381:
382: while((apgentry[ulIndexHigh].fBaseAddr == FALSE) &&
383: (ulIndexHigh < ulFreePage))
384: ulIndexHigh++;
385:
386: while(ulIndexHigh < ulFreePage)
387: apgentry[ulIndexLow++] = apgentry[ulIndexHigh++];
388:
389: ulFreePage -= (ulIndexHigh-ulIndexLow);
390: }
391: }
392:
393: /****************************************************************\
394: * Procedure to store allocated pages in array *
395: *--------------------------------------------------------------*
396: * *
397: * Name: StoreInArray(pvAddress,ulNumPages) *
398: * *
399: * Purpose: Store pages that were just successfully allocated *
400: * in our array so we can track them *
401: * *
402: * Usage: Routine is called after each successful allocation *
403: * of memory by the user *
404: * *
405: * Method: The pages are added to the end of the array based *
406: * upon the current status of our global end of array *
407: * index variable (ulFreePage) *
408: * *
409: * Returns: *
410: \****************************************************************/
411: VOID StoreInArray(pvAddress,ulNumPages)
412: PVOID pvAddress;
413: ULONG ulNumPages;
414: {
415: ULONG temp;
416:
417: /* Set all base indicators for the new pages to FALSE, and set the
418: addresses for the pages. */
419:
420: for(temp = 0L; temp < ulNumPages; temp++)
421: {
422: apgentry[temp+ulFreePage].pvAddress = (PVOID)((ULONG)pvAddress +
423: (PAGESIZE * temp));
424: apgentry[temp+ulFreePage].fBaseAddr = FALSE;
425: }
426:
427: /* Set the base indicator of the first page in the object to be
428: TRUE */
429:
430: apgentry[ulFreePage].fBaseAddr = TRUE;
431:
432: ulFreePage += ulNumPages;
433: }
434:
435: /****************************************************************\
436: * Non-standard menu item command processing procedure *
437: *--------------------------------------------------------------*
438: * *
439: * Name: UserCommand(mp1, mp2) *
440: * *
441: * Purpose: Process any WM_COMMAND messages send to hwndMain *
442: * that are not processed by MainCommand *
443: * *
444: * Usage: Routine is called for each WM_COMMAND that is *
445: * not posted by a standard menu item *
446: * *
447: * Method: A switch statement branches control based upon *
448: * the id of the control which posted the message *
449: * *
450: * Returns: *
451: \****************************************************************/
452: VOID UserCommand(mp1, mp2)
453: MPARAM mp1; /* first message parameter */
454: MPARAM mp2; /* second message parameter */
455: {
456: PVOID pvMemAddress;
457: ULONG ulRegionSize;
458:
459: ULONG ulTempRegionSize;
460: ULONG flTempAttributes;
461:
462: ULONG ulPages; /* Number of pages requested by user */
463: ULONG rc; /* Return Code variable from calls */
464: ULONG rc2; /* Return Code variable for investigative calls */
465:
466: switch(SHORT1FROMMP(mp1))
467: {
468:
469: /*--------------------------------------------------------------*\
470: * Add case statements for menuitem ids you wish to process *
471: \*--------------------------------------------------------------*/
472:
473: case IDM_VMMALLOC:
474: ObjAlloc.ulSize = 5000; /* default size to allocate */
475: ObjAlloc.ulAttr = PAG_READ | PAG_WRITE | PAG_EXECUTE;
476:
477: if(WinDlgBox( HWND_DESKTOP,
478: hwndMain,
479: (PFNWP) AllocMemDlgProc,
480: NULL,
481: (ULONG) IDD_ALLOCMEM,
482: (PVOID) &ObjAlloc ))
483:
484: {
485: /* Figure out the number of pages requested. */
486:
487: ulPages = (ObjAlloc.ulSize / PAGESIZE);
488: if ((ObjAlloc.ulSize % PAGESIZE) != 0L)
489: {
490: ulPages++;
491: }
492:
493: /* If the number of pages requested is zero (zero
494: bytes specified), or the number of pages is greater
495: than the number left in the array, report the error
496: condition. Otherwise, make the DosAllocMem call and
497: set any error condition returned by the call. */
498:
499: if (ulPages == 0L)
500: {
501: rc = VMERR_ZERO_ALLOCATED;
502: }
503: else if (ulPages + ulFreePage > MAXPAGES + 1)
504: {
505: rc = VMERR_MAX_ALLOCATED;
506: }
507: else
508: {
509: rc = DosAllocMem(&pvMemAddress,
510: ObjAlloc.ulSize,
511: ObjAlloc.ulAttr);
512: }
513: /* If an error has occured, display it. Otherwise,
514: process the request. */
515:
516: if (rc != 0L)
517: {
518: if (ObjAlloc.ulAttr == 0L)
519: {
520: rc = VMERR_SET_NO_PARMS;
521: }
522: else if (ObjAlloc.ulAttr == PAG_COMMIT)
523: {
524: rc = VMERR_COMMIT_ONLY;
525: }
526: else if (ObjAlloc.ulAttr == (PAG_COMMIT | OBJ_TILE))
527: {
528: rc = VMERR_COMMIT_AND_TILE_ONLY;
529: }
530: else if (ObjAlloc.ulAttr == OBJ_TILE)
531: {
532: rc = VMERR_TILE_ONLY;
533: }
534: VMM_Error("DosAllocMem()",rc);
535: }
536: else
537: {
538: StoreInArray(pvMemAddress,ulPages);
539: WinInvalidateRect(hwndMain, NULL, TRUE);
540: }
541: }
542: break;
543:
544: case IDM_VMMFREE:
545:
546: if (WinDlgBox(HWND_DESKTOP,
547: hwndMain,
548: (PFNWP) FreeMemDlgProc,
549: NULL,
550: (ULONG) IDD_FREEMEM,
551: (PVOID) &ObjAlloc ) != 0)
552: {
553:
554: rc = DosFreeMem(ObjAlloc.pvAddress);
555:
556: if (rc != 0L)
557: {
558: ulRegionSize = 1L;
559:
560: /* Try to figure out why the error occured based
561: on actual page attributes of page requested to be
562: freed. */
563:
564: rc2 = DosQueryMem(ObjAlloc.pvAddress,
565: &ulTempRegionSize,
566: &flTempAttributes);
567:
568: if (rc2 == 0L)
569:
570: /* If the page requested to be freed was already
571: in the freed state, or the requested page in the
572: object to be freed is not the base page of the
573: object, set the appropriate error condition. */
574:
575: {
576: if ((flTempAttributes & PAG_FREE) != 0L)
577: {
578: rc = VMERR_ALREADY_FREED;
579: }
580: else if ((flTempAttributes & PAG_BASE) == 0L)
581: {
582: rc = VMERR_NOT_BASEPAGE;
583: }
584: }
585:
586: VMM_Error("DosFreeMem()",rc);
587: }
588: else
589: {
590: CleanUpArray(ObjAlloc.pvAddress);
591: WinInvalidateRect(hwndMain, NULL, TRUE);
592: }
593: }
594: break;
595:
596: case IDM_VMMSET:
597:
598: if (WinDlgBox(HWND_DESKTOP,
599: hwndMain,
600: (PFNWP) SetMem1DlgProc,
601: NULL,
602: (ULONG) IDD_SETMEM1,
603: (PVOID) &ObjAlloc ))
604: {
605: if (WinDlgBox(HWND_DESKTOP,
606: hwndMain,
607: (PFNWP) SetMem2DlgProc,
608: NULL,
609: (ULONG) IDD_SETMEM2,
610: (PVOID) &ObjAlloc ))
611: {
612: rc = DosSetMem(ObjAlloc.pvAddress,
613: ObjAlloc.ulSize,
614: ObjAlloc.ulAttr);
615:
616: /* If an error occured making the DosSetMem call,
617: try to figure out the cause of the error by
618: studying the requested access and the access on
619: the memory prior to the DosSetMem call. */
620:
621: if (rc != 0L)
622: {
623: ulTempRegionSize = 1L;
624:
625: rc2 = DosQueryMem(ObjAlloc.pvAddress,
626: &ulTempRegionSize,
627: &flTempAttributes);
628:
629: if (rc2 == 0L)
630: {
631:
632: /* If no attributes, commit with no other
633: attributes, or a size of zero was
634: specified, set the appropriate error
635: condition. */
636:
637: if (ObjAlloc.ulAttr == 0L)
638: {
639: rc = VMERR_SET_NO_PARMS;
640: }
641: else if (ObjAlloc.ulAttr == PAG_COMMIT)
642: {
643: rc = VMERR_COMMIT_ONLY;
644: }
645: else if (ObjAlloc.ulSize == 0L)
646: {
647: rc = VMERR_SET_ZERO_SIZE;
648: }
649:
650: /* If memory is not allocated then set
651: error condition. */
652:
653: else if ((ObjAlloc.ulAttr & PAG_FREE) != 0L)
654: {
655: rc = VMERR_SET_ON_FREE;
656: }
657:
658: /* If the memory was already in the
659: reserved state and the user attempted to
660: decommit it or set attributes for it, set
661: the error condition. */
662:
663: else if ((flTempAttributes & PAG_COMMIT) == 0L)
664: {
665: if ((ObjAlloc.ulAttr & PAG_DECOMMIT) != 0L)
666: {
667: rc = VMERR_DECOMMIT_RESERVED;
668: }
669: else if ((ObjAlloc.ulAttr & ~PAG_COMMIT) != 0L)
670: {
671: rc = VMERR_ACCESS_ON_RESERVED;
672: }
673: }
674:
675: /* If the memory was in a committed state
676: and the user attempted to decommit it and
677: set attributes, or the user attempts to
678: commit the memory, set the appropriate
679: error condition. */
680:
681: else
682: {
683: if (((ObjAlloc.ulAttr & PAG_DECOMMIT) != 0L)
684: && ((ObjAlloc.ulAttr & ~PAG_DECOMMIT) != 0L))
685: {
686: rc = VMERR_ACCESS_AND_DECOMMIT;
687: }
688:
689: else if ((ObjAlloc.ulAttr & PAG_COMMIT) != 0L)
690: {
691: rc = VMERR_COMMIT_ON_COMMITTED;
692: }
693: }
694:
695: }
696:
697: VMM_Error("DosSetMem()",rc);
698: }
699: else
700: {
701: WinInvalidateRect(hwndMain, NULL, TRUE);
702: }
703: }
704: }
705: break;
706:
707: case IDM_VMMWRITE:
708:
709: if (WinDlgBox(HWND_DESKTOP,
710: hwndMain,
711: (PFNWP) WriteMemDlgProc,
712: NULL,
713: (ULONG) IDD_WRITEMEM,
714: (PVOID) NULL))
715: {
716:
717: WinInvalidateRect(hwndMain, NULL, TRUE);
718: }
719:
720: break;
721:
722:
723: case IDM_VMMREAD:
724:
725: if (WinDlgBox(HWND_DESKTOP,
726: hwndMain,
727: (PFNWP) ReadMemDlgProc,
728: NULL,
729: (ULONG) IDD_READMEM,
730: (PVOID) &ObjAlloc ))
731: {
732: sprintf(szBuffer, "Address %p contains %s",
733: ObjAlloc.pvAddress, ObjAlloc.pvAddress);
734:
735: WinMessageBox(HWND_DESKTOP,
736: hwndMain,
737: szBuffer,
738: "Read Memory",
739: 0,
740: MB_OK);
741:
742: WinInvalidateRect(hwndMain, NULL, TRUE);
743: }
744:
745: break;
746:
747: default:
748: break;
749: }
750:
751: /* This routine currently doesn't use the mp2 parameter but *\
752: * it is referenced here to prevent an 'Unreferenced Parameter' *
753: \* warning at compile time. */
754: mp2;
755:
756: } /* UserCommand() */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.