Annotation of OpenXM_contrib2/windows/engine2000/io.c, Revision 1.1
1.1 ! noro 1: /*
! 2: main.c
! 3:
! 4: Windows toplevel and functions for communication, error-handling.
! 5: WinMain() is the common toplevel function for several asir variants.
! 6: 'Main()' (the toplevel of the usual asir session) is called from WinMain().
! 7: Furthermore, a special GUI for debugging is created by create_debug_gui().
! 8:
! 9: There are two sets of events:
! 10: {Notify0, Notify0_Ack, Intr0, Intr0_Ack, Kill}
! 11: and
! 12: {DebugNotify, DebugNotify_Ack, DebugIntr, DebugIntr_Ack}.
! 13: These are used for the communication between Asir GUI and engine,
! 14: or debug GUI and engine respectively.
! 15:
! 16: Asir GUI <-- Notify0, Notify0_Ack, Kill --> engine
! 17: debug GUI <-- DebugNotify, DebugNotify_Ack --> engine
! 18:
! 19: Revision History:
! 20:
! 21: 99/04/27 noro the first official version
! 22: 99/07/27 noro
! 23: */
! 24:
! 25: #include <stdio.h>
! 26: #include <stdlib.h>
! 27: #include <windows.h>
! 28: #include <signal.h>
! 29: #include <io.h>
! 30: #include <fcntl.h>
! 31: #include <process.h>
! 32: #include "../asir2000/include/ca.h"
! 33:
! 34: /* error message structure */
! 35:
! 36: typedef struct ErrMsg {
! 37: int code;
! 38: char reason[BUFSIZ*5]; /* XXX : the size should coincide with that in GUI */
! 39: char action[BUFSIZ*5];
! 40: } ErrMsg;
! 41:
! 42: /* main thread id */
! 43: DWORD MainThread;
! 44:
! 45: /* pipe handles */
! 46: static HANDLE hRead,hWrite;
! 47: static HANDLE hRead0,hWrite0;
! 48: static HANDLE hDebugRead,hDebugWrite;
! 49:
! 50: /* event handles */
! 51: static HANDLE hOxIntr,hOxReset,hOxKill;
! 52: static HANDLE hNotify,hNotify_Ack,hIntr,hIntr_Ack,hKill;
! 53: static HANDLE hNotify0,hNotify_Ack0,hIntr0;
! 54: static HANDLE hDebugNotify,hDebugNotify_Ack,hDebugIntr,hDebugIntr_Ack,hDebugKill;
! 55:
! 56: HANDLE hResizeNotify,hResizeNotify_Ack; /* should be visible from another file */
! 57:
! 58: /* XXX */
! 59: extern HANDLE hStreamNotify,hStreamNotify_Ack; /* declared in io/ox.c */
! 60:
! 61: /* process handle */
! 62: static HANDLE hDebugProc,hMessageProc;
! 63: static HANDLE hThread,hWatchIntrThread,hWatchStreamThread,hComputingThread;
! 64:
! 65: static struct ErrMsg Errmsg;
! 66:
! 67: /*
! 68: * recv_intr : flag for Asir
! 69: * interrupt_state : flag to distinguish an Asir error and a cancellation
! 70: */
! 71:
! 72: static int emergency;
! 73: static int interrupt_state;
! 74: int debuggui_is_present;
! 75: int messagegui_is_present;
! 76:
! 77: extern int recv_intr,doing_batch;
! 78: extern int ox_sock_id, do_message;
! 79:
! 80: void get_rootdir(char *,int);
! 81: void ox_watch_stream();
! 82: void ox_plot_main();
! 83:
! 84: void send_intr();
! 85: void SendCmdSize(unsigned int,unsigned int);
! 86: void SendBody(void *buf,unsigned int);
! 87: void SendHeapSize();
! 88: void Main(int,char **);
! 89: void AsirMain(int,char **);
! 90: void OxAsirMain(int,char **);
! 91: void OxPlotMain(int,char **);
! 92:
! 93: void Init_Asir(int,char **);
! 94: int Call_Asir(char *,void *);
! 95: void int_handler(int);
! 96: void set_debug_handles(int);
! 97: int create_debug_gui();
! 98: void terminate_debug_gui();
! 99:
! 100: void _setargv();
! 101:
! 102: #define FROMASIR_EXIT 0
! 103: #define FROMASIR_TEXT 1
! 104: #define FROMASIR_HEAPSIZE 2
! 105: #define FROMASIR_SHOW 3
! 106: #define FROMASIR_HIDE 4
! 107:
! 108: static char Asir_Cmd[BUFSIZ*8];
! 109:
! 110: extern char LastError[];
! 111:
! 112: /* the watch threads */
! 113:
! 114: void watch_intr() {
! 115: HANDLE handle[2];
! 116: DWORD ret;
! 117:
! 118: handle[0] = hIntr0; handle[1] = hKill;
! 119: while ( 1 ) {
! 120: ret = WaitForMultipleObjects(2,(CONST HANDLE *)handle,FALSE,(DWORD)-1);
! 121: switch ( ret ) {
! 122: case WAIT_OBJECT_0: /* hIntr0 */
! 123: if ( doing_batch )
! 124: send_intr();
! 125: /* for Asir; recv_intr is reset to 0 in Asir */
! 126: recv_intr = 1;
! 127: break;
! 128: case WAIT_OBJECT_0+1: /* hKill */
! 129: default:
! 130: terminate_debug_gui();
! 131: emergency = 1; /* XXX */
! 132: asir_terminate(2);
! 133: exit(0);
! 134: /* NOTREACHED */
! 135: break;
! 136: }
! 137: }
! 138: }
! 139:
! 140: void ox_watch_intr() {
! 141: HANDLE handle[3];
! 142: DWORD ret;
! 143:
! 144: handle[0] = hOxIntr; handle[1] = hOxReset; handle[2] = hOxKill;
! 145: while ( 1 ) {
! 146: ret = WaitForMultipleObjects(3,(CONST HANDLE *)handle,FALSE,(DWORD)-1);
! 147: switch ( ret ) {
! 148: case WAIT_OBJECT_0: /* hOxIntr */
! 149: ResetEvent(hOxIntr);
! 150: if ( doing_batch )
! 151: send_intr();
! 152: recv_intr = 1;
! 153: break;
! 154: case WAIT_OBJECT_0+1: /* hOxReset */
! 155: ResetEvent(hOxReset);
! 156: if ( doing_batch )
! 157: send_intr();
! 158: recv_intr = 2;
! 159: break;
! 160: case WAIT_OBJECT_0+2: /* hOxKill */
! 161: ResetEvent(hOxKill);
! 162: terminate_debug_gui();
! 163: emergency = 1; /* XXX */
! 164: asir_terminate(2);
! 165: /* NOTREACHED */
! 166: break;
! 167: }
! 168: }
! 169: }
! 170:
! 171: /*
! 172: setup the necessary events for communication between debug GUI and engine.
! 173: Then the debug GUI is invoked.
! 174: */
! 175:
! 176: int create_message_gui()
! 177: {
! 178: DWORD mypid,len;
! 179: HANDLE hR0,hW0,hR1,hW1;
! 180: SECURITY_ATTRIBUTES SecurityAttributes;
! 181: char remread[10],remwrite[10];
! 182: char notify[100],notify_ack[100];
! 183: char name[100];
! 184: char DebugGUI[100];
! 185: char *av[100];
! 186:
! 187: mypid = GetCurrentProcessId();
! 188: sprintf(notify,"message_notify_%d",mypid);
! 189: sprintf(notify_ack,"message_notify_ack_%d",mypid);
! 190: hNotify = hNotify0 = CreateEvent(NULL,TRUE,FALSE,notify);
! 191: hNotify_Ack = hNotify_Ack0 = CreateEvent(NULL,TRUE,FALSE,notify_ack);
! 192:
! 193: SecurityAttributes.nLength = sizeof(SecurityAttributes);
! 194: SecurityAttributes.lpSecurityDescriptor = NULL;
! 195: SecurityAttributes.bInheritHandle = TRUE;
! 196: CreatePipe(&hR0, &hW0, &SecurityAttributes, 65536);
! 197: CreatePipe(&hR1, &hW1, &SecurityAttributes, 65536);
! 198:
! 199: hRead = hRead0 = hR0;
! 200: hWrite = hWrite0 = hW1;
! 201: sprintf(remread,"%d",(DWORD)hR1);
! 202: sprintf(remwrite,"%d",(DWORD)hW0);
! 203: len = sizeof(name);
! 204: get_rootdir(name,len);
! 205: sprintf(DebugGUI,"%s\\bin\\asirgui.exe",name);
! 206: av[0] = "messagegui";
! 207: av[1] = remread;
! 208: av[2] = remwrite;
! 209: av[3] = notify;
! 210: av[4] = notify_ack;
! 211: av[5] = NULL;
! 212: hMessageProc = (HANDLE)_spawnv(_P_NOWAIT,DebugGUI,av);
! 213: if ( hMessageProc == (HANDLE)-1 ) {
! 214: fprintf(stderr,"%s not found",DebugGUI);
! 215: messagegui_is_present = 0;
! 216: return -1;
! 217: } else
! 218: messagegui_is_present = 1;
! 219: return 0;
! 220: }
! 221:
! 222: /*
! 223: setup the necessary events for communication between debug GUI and engine.
! 224: Then the debug GUI is invoked.
! 225: */
! 226:
! 227: int create_debug_gui()
! 228: {
! 229: DWORD mypid,len;
! 230: HANDLE hR0,hW0,hR1,hW1;
! 231: SECURITY_ATTRIBUTES SecurityAttributes;
! 232: char remread[10],remwrite[10];
! 233: char notify[100],notify_ack[100],intr[100],intr_ack[100],kill[100];
! 234: char name[100];
! 235: char DebugGUI[100];
! 236: char *av[100];
! 237:
! 238: mypid = GetCurrentProcessId();
! 239: sprintf(notify,"debug_notify_%d",mypid);
! 240: sprintf(notify_ack,"debug_notify_ack_%d",mypid);
! 241: sprintf(intr,"debug_intr_%d",mypid);
! 242: sprintf(intr_ack,"debug_intr_ack_%d",mypid);
! 243: sprintf(kill,"debug_kill_%d",mypid);
! 244: hDebugNotify = CreateEvent(NULL,TRUE,FALSE,notify);
! 245: hDebugNotify_Ack = CreateEvent(NULL,TRUE,FALSE,notify_ack);
! 246: hDebugIntr = CreateEvent(NULL,TRUE,FALSE,intr);
! 247: hDebugIntr_Ack = CreateEvent(NULL,TRUE,FALSE,intr_ack);
! 248: hDebugKill = CreateEvent(NULL,TRUE,FALSE,kill);
! 249:
! 250: SecurityAttributes.nLength = sizeof(SecurityAttributes);
! 251: SecurityAttributes.lpSecurityDescriptor = NULL;
! 252: SecurityAttributes.bInheritHandle = TRUE;
! 253: CreatePipe(&hR0, &hW0, &SecurityAttributes, 65536);
! 254: CreatePipe(&hR1, &hW1, &SecurityAttributes, 65536);
! 255:
! 256: hDebugRead = hR0;
! 257: hDebugWrite = hW1;
! 258: sprintf(remread,"%d",(DWORD)hR1);
! 259: sprintf(remwrite,"%d",(DWORD)hW0);
! 260: len = sizeof(name);
! 261: get_rootdir(name,len);
! 262: sprintf(DebugGUI,"%s\\bin\\asirgui.exe",name);
! 263: av[0] = "debuggui";
! 264: av[1] = remread;
! 265: av[2] = remwrite;
! 266: av[3] = notify;
! 267: av[4] = notify_ack;
! 268: av[5] = intr;
! 269: av[6] = intr_ack;
! 270: av[7] = kill;
! 271: av[8] = NULL;
! 272: hDebugProc = (HANDLE)_spawnv(_P_NOWAIT,DebugGUI,av);
! 273: if ( hDebugProc == (HANDLE)-1 ) {
! 274: fprintf(stderr,"%s not found",DebugGUI);
! 275: return -1;
! 276: }
! 277: return 0;
! 278: }
! 279:
! 280: /* if debug GUI is present, we have to terminate the process */
! 281:
! 282: void terminate_debug_gui()
! 283: {
! 284: if ( hDebugProc ) {
! 285: TerminateProcess(hDebugProc,0);
! 286: // hRead = hDebugRead; hWrite = hDebugWrite;
! 287: // hNotify = hDebugNotify; hNotify_Ack = hDebugNotify_Ack;
! 288: // hIntr = hDebugIntr;
! 289: // SendCmdSize(FROMASIR_EXIT,0);
! 290: hDebugProc = 0;
! 291: }
! 292: if ( hMessageProc ) {
! 293: TerminateProcess(hMessageProc,0);
! 294: // hRead = hRead0; hWrite = hWrite0;
! 295: // hNotify = hNotify0; hNotify_Ack = hNotify_Ack0;
! 296: // hIntr = hIntr0;
! 297: // SendCmdSize(FROMASIR_EXIT,0);
! 298: hMessageProc = 0;
! 299: }
! 300: }
! 301:
! 302: /* set the current active set of events */
! 303:
! 304: void set_debug_handles(int on)
! 305: {
! 306: if ( on ) {
! 307: if ( !debuggui_is_present ) {
! 308: hRead = hDebugRead; hWrite = hDebugWrite;
! 309: hNotify = hDebugNotify; hNotify_Ack = hDebugNotify_Ack;
! 310: hIntr = hDebugIntr;
! 311: SendCmdSize(FROMASIR_SHOW,0);
! 312: debuggui_is_present = 1;
! 313: }
! 314: } else {
! 315: if ( debuggui_is_present ) {
! 316: SendCmdSize(FROMASIR_HIDE,0);
! 317: hRead = hRead0; hWrite = hWrite0;
! 318: hNotify = hNotify0; hNotify_Ack = hNotify_Ack0;
! 319: hIntr = hIntr0;
! 320: debuggui_is_present = 0;
! 321: }
! 322: }
! 323: }
! 324:
! 325: void Init_IO()
! 326: {
! 327: _setargv();
! 328: if ( !strcmp(__argv[0],"ox_asir") ) {
! 329: OxAsirMain(__argc,__argv);
! 330: exit(0);
! 331: } else if ( !strcmp(__argv[0],"ox_plot") )
! 332: OxPlotMain(__argc,__argv);
! 333: else if ( !strcmp(__argv[0],"ox_launch") ) {
! 334: launch_main(__argc,__argv);
! 335: exit(0);
! 336: } else {
! 337: AsirMain(__argc,__argv);
! 338: exit(0);
! 339: }
! 340: }
! 341:
! 342: void AsirMain(int argc, char **argv)
! 343: {
! 344: DWORD tid;
! 345: int ret;
! 346:
! 347: hRead = (void *)atoi(__argv[1]);
! 348: hWrite = (void *)atoi(__argv[2]);
! 349: hNotify = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,__argv[3]);
! 350: hNotify_Ack = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,__argv[4]);
! 351: hIntr = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,__argv[5]);
! 352: hIntr_Ack = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,__argv[6]);
! 353: hKill = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,__argv[7]);
! 354: if ( !hRead || !hWrite || !hNotify || !hNotify_Ack || !hIntr || !hIntr_Ack || !hKill )
! 355: exit(0);
! 356: /* save the above handles */
! 357: hRead0 = hRead; hWrite0 = hWrite;
! 358: hNotify0 = hNotify; hNotify_Ack0 = hNotify_Ack;
! 359: hIntr0 = hIntr;
! 360:
! 361: hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)watch_intr,NULL,0,&tid);
! 362: if ( !hThread )
! 363: exit(0);
! 364: // ret = SetThreadPriority(hThread,THREAD_PRIORITY_BELOW_NORMAL);
! 365: // if ( !ret )
! 366: // exit(0);
! 367: /* messagegui is the asirgui main window. */
! 368: messagegui_is_present = 1;
! 369: /* The rest of the args are passed to Main(). */
! 370: /* XXX : process_args() increments argv. */
! 371: // ret = SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_ABOVE_NORMAL);
! 372: // if ( !ret )
! 373: // exit(0);
! 374: Main(__argc-7,__argv+7);
! 375: }
! 376:
! 377: void OxAsirMain(int argc, char **argv)
! 378: {
! 379: int create_message;
! 380: int tid;
! 381: int ret;
! 382:
! 383: ox_sock_id = atoi(__argv[1]);
! 384: create_message = atoi(__argv[2]);
! 385: hOxIntr = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,__argv[3]);
! 386: hOxReset = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,__argv[4]);
! 387: hOxKill = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,__argv[5]);
! 388: if ( create_message )
! 389: create_message_gui();
! 390: else
! 391: messagegui_is_present = 0;
! 392: if ( messagegui_is_present )
! 393: SendCmdSize(FROMASIR_SHOW,0);
! 394: hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ox_watch_intr,NULL,0,&tid);
! 395: // ret = SetThreadPriority(hThread,THREAD_PRIORITY_BELOW_NORMAL);
! 396: // if ( !ret )
! 397: // exit(0);
! 398: // ret = SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_ABOVE_NORMAL);
! 399: // if ( !ret )
! 400: // exit(0);
! 401: /* XXX : process_args() increments argv. */
! 402: ox_main(__argc-5,__argv+5);
! 403: }
! 404:
! 405: int plot_argc;
! 406: char **plot_argv;
! 407:
! 408: void OxPlotMain(int argc, char **argv)
! 409: {
! 410: DWORD tid;
! 411: DWORD mypid;
! 412: char eventname[BUFSIZ];
! 413: int ret;
! 414:
! 415: ox_sock_id = atoi(argv[1]);
! 416: do_message = atoi(argv[2]);
! 417: hOxIntr = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,argv[3]);
! 418: hOxReset = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,argv[4]);
! 419: hOxKill = OpenEvent(EVENT_ALL_ACCESS|EVENT_MODIFY_STATE,TRUE,argv[5]);
! 420:
! 421: if ( do_message )
! 422: create_message_gui();
! 423: else
! 424: messagegui_is_present = 0;
! 425: if ( messagegui_is_present )
! 426: SendCmdSize(FROMASIR_SHOW,0);
! 427:
! 428: mypid = GetCurrentProcessId();
! 429: sprintf(eventname,"stream_notify_%d",mypid);
! 430: hStreamNotify = CreateEvent(NULL,TRUE,FALSE,eventname);
! 431: sprintf(eventname,"stream_notify_ack_%d",mypid);
! 432: hStreamNotify_Ack = CreateEvent(NULL,TRUE,FALSE,eventname);
! 433: sprintf(eventname,"resize_notify_%d",mypid);
! 434: hResizeNotify = CreateEvent(NULL,TRUE,FALSE,eventname);
! 435: sprintf(eventname,"resize_notify_ack_%d",mypid);
! 436: hResizeNotify_Ack = CreateEvent(NULL,TRUE,FALSE,eventname);
! 437:
! 438: hWatchStreamThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ox_watch_stream,NULL,0,&tid);
! 439: // ret = SetThreadPriority(hWatchStreamThread,THREAD_PRIORITY_BELOW_NORMAL);
! 440: // if ( !ret )
! 441: // exit(0);
! 442: hWatchIntrThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ox_watch_intr,NULL,0,&tid);
! 443: // ret = SetThreadPriority(hWatchIntrThread,THREAD_PRIORITY_BELOW_NORMAL);
! 444: // if ( !ret )
! 445: // exit(0);
! 446:
! 447: /* process_args() increments argv */
! 448: plot_argc = argc-5;
! 449: plot_argv = argc+5;
! 450: hComputingThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ox_plot_main,NULL,0,&tid);
! 451: // ret = SetThreadPriority(hComputingThread,THREAD_PRIORITY_ABOVE_NORMAL);
! 452: // if ( !ret )
! 453: // exit(0);
! 454: }
! 455:
! 456: void ox_watch_stream() {
! 457: fd_set r,w,e;
! 458:
! 459: /* wait for the completion of initalization in the computing thread */
! 460: WaitForSingleObject(hStreamNotify_Ack,(DWORD)-1);
! 461: ResetEvent(hStreamNotify_Ack);
! 462: while ( 1 ) {
! 463: FD_ZERO(&r);
! 464: FD_ZERO(&w);
! 465: FD_ZERO(&e);
! 466: FD_SET(ox_sock_id,&r);
! 467: select(FD_SETSIZE,&r,&w,&e,NULL);
! 468: SetEvent(hStreamNotify);
! 469: WaitForSingleObject(hStreamNotify_Ack,(DWORD)-1);
! 470: ResetEvent(hStreamNotify_Ack);
! 471: }
! 472: }
! 473:
! 474: /* get_line : used in Asir mode */
! 475:
! 476: int get_line(char *buf) {
! 477: DWORD len;
! 478: int size;
! 479:
! 480: ReadFile(hRead,&size,sizeof(int),&len,NULL);
! 481: ReadFile(hRead,buf,size,&len,NULL);
! 482: buf[size] = 0;
! 483: return 0;
! 484: }
! 485:
! 486: /* put_line : used in Asir mode */
! 487:
! 488: void put_line(char *buf) {
! 489: if ( debuggui_is_present || messagegui_is_present ) {
! 490: int size = strlen(buf);
! 491:
! 492: if ( size ) {
! 493: SendCmdSize(FROMASIR_TEXT,size);
! 494: SendBody(buf,size);
! 495: }
! 496: }
! 497: }
! 498:
! 499: /* common function for error exit */
! 500:
! 501: void ExitAsir() {
! 502: terminate_debug_gui();
! 503: /* if emergency != 0, asirgui may not exist */
! 504: if ( !emergency )
! 505: SendCmdSize(FROMASIR_EXIT,0);
! 506: ExitProcess(0);
! 507: }
! 508:
! 509: /* SendHeapSize : used in Asir mode */
! 510:
! 511: int get_heapsize();
! 512:
! 513: void SendHeapSize()
! 514: {
! 515: if ( messagegui_is_present || debuggui_is_present ) {
! 516: int h = get_heapsize();
! 517: SendCmdSize(FROMASIR_HEAPSIZE,sizeof(int));
! 518: SendBody(&h,sizeof(int));
! 519: }
! 520: }
! 521:
! 522: /* SendCmdSize : used in Asir mode */
! 523:
! 524: void SendCmdSize(unsigned int cmd,unsigned int size)
! 525: {
! 526: DWORD len;
! 527: unsigned int cmdsize;
! 528:
! 529: if ( hNotify ) {
! 530: SetEvent(hNotify);
! 531: WaitForSingleObject(hNotify_Ack,(DWORD)-1);
! 532: ResetEvent(hNotify_Ack);
! 533: cmdsize = (cmd<<16)|size;
! 534: WriteFile(hWrite,&cmdsize,sizeof(unsigned int),&len,NULL);
! 535: }
! 536: }
! 537:
! 538: /* SendBody : used in Asir mode */
! 539:
! 540: void SendBody(void *buf,unsigned int size)
! 541: {
! 542:
! 543: DWORD len;
! 544:
! 545: WriteFile(hWrite,buf,size,&len,NULL);
! 546: }
! 547:
! 548: /* send header + send progress data */
! 549:
! 550: void send_progress(short percentage,char *message)
! 551: {
! 552: }
! 553:
! 554: /* set error code, message, action in Errmsg. */
! 555:
! 556: void set_error(int id,char *reason,char *action)
! 557: {
! 558: }
! 559:
! 560: /* dummy functions */
! 561: reset_current_computation(){}
! 562: set_selection(){}
! 563: reset_selection(){}
! 564: set_busy(){}
! 565: reset_busy(){}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>