1 /* 2 * Support for executing code in new tasks and threads. 3 * 4 * Copyright (C) 2022 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <l4/re/env.h> 23 #include <l4/sys/err.h> 24 #include <l4/sys/factory.h> 25 #include <l4/sys/task.h> 26 #include <l4/sys/thread.h> 27 #include <l4/util/elf.h> 28 #include <l4/util/util.h> 29 30 #include <l4/libloader/adjust_stack> 31 32 #include <fsclient/client.h> 33 #include <ipc/cap_alloc.h> 34 #include <ipc/mem_ipc.h> 35 #include <ipc/server.h> 36 #include <mem/memory_utils.h> 37 #include <systypes/fcntl.h> 38 39 #include <map> 40 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 45 #include <pthread-l4.h> 46 #include <pthread.h> 47 48 #include "pager_object_interface.h" 49 #include "pager_object_server.h" 50 51 52 53 /* Region data structures. */ 54 55 class Region 56 { 57 public: 58 l4_addr_t start; 59 unsigned int log2size; 60 l4_umword_t flags; 61 l4_addr_t map_start; 62 63 explicit Region() 64 : start(0), log2size(0), flags(0), map_start(0) 65 { 66 } 67 68 explicit Region(l4_addr_t start, unsigned int log2size, l4_umword_t flags, 69 l4_addr_t map_start) 70 : start(start), log2size(log2size), flags(flags), map_start(map_start) 71 { 72 } 73 }; 74 75 typedef std::map<l4_addr_t, Region> Regions; 76 77 78 79 /* A simple system pager also acting as a region mapper. */ 80 81 class ExecPager : public PagerObject 82 { 83 protected: 84 Regions _regions; 85 86 public: 87 virtual void add(Region region) 88 { 89 _regions[region.map_start] = region; 90 } 91 92 /* Notification methods. */ 93 94 virtual long exception(l4_exc_regs_t regs, 95 l4_snd_fpage_t *region); 96 97 virtual long page_fault(l4_umword_t pfa, l4_umword_t pc, 98 l4_snd_fpage_t *region); 99 100 /* Region manager/mapper methods. */ 101 102 virtual long attach(address_t *start, offset_t size, map_flags_t flags, 103 l4_cap_idx_t ds, address_t offset, unsigned char align); 104 105 }; 106 107 /* Handle a general exception. */ 108 109 long ExecPager::exception(l4_exc_regs_t regs, l4_snd_fpage_t *region) 110 { 111 (void) region; 112 113 printf("exception(...) -> pfa = %lx, pc = %lx\n", l4_utcb_exc_pfa(®s), l4_utcb_exc_pc(®s)); 114 115 printf("r15 = %lx\n", regs.r15); 116 printf("r14 = %lx\n", regs.r14); 117 printf("r13 = %lx\n", regs.r13); 118 printf("r12 = %lx\n", regs.r12); 119 printf("r11 = %lx\n", regs.r11); 120 printf("r10 = %lx\n", regs.r10); 121 printf("r9 = %lx\n", regs.r9); 122 printf("r8 = %lx\n", regs.r8); 123 printf("rdi = %lx\n", regs.rdi); 124 printf("rsi = %lx\n", regs.rsi); 125 printf("rbp = %lx\n", regs.rbp); 126 printf("pfa = %lx\n", regs.pfa); 127 printf("rbx = %lx\n", regs.rbx); 128 printf("rdx = %lx\n", regs.rdx); 129 printf("rcx = %lx\n", regs.rcx); 130 printf("rax = %lx\n", regs.rax); 131 printf("trapno = %lx\n", regs.trapno); 132 printf("err = %lx\n", regs.err); 133 printf("ip = %lx\n", regs.ip); 134 printf("flags = %lx\n", regs.flags); 135 printf("sp = %lx\n", regs.sp); 136 printf("ss = %lx\n", regs.ss); 137 printf("fs_base = %lx\n", regs.fs_base); 138 printf("gs_base = %lx\n", regs.gs_base); 139 140 return L4_EOK; 141 } 142 143 #define DEBUG 0 144 145 /* Handle a page fault using any configured regions. */ 146 147 long ExecPager::page_fault(l4_umword_t pfa, l4_umword_t pc, l4_snd_fpage_t *region) 148 { 149 l4_umword_t addr = pfa & ~7UL, flags = pfa & 7; 150 151 #if DEBUG 152 printf("page_fault(%lx, %lx) -> %lx (%lx) -> ", pfa, pc, addr, flags); 153 #endif 154 155 Regions::iterator it = _regions.upper_bound(addr); 156 157 if (it != _regions.begin()) 158 it--; 159 else 160 { 161 printf("not mapped!\n"); 162 return -L4_ENOMEM; 163 } 164 165 Region &r = it->second; 166 167 if ((addr >= r.map_start) && (addr < r.map_start + (1UL << r.log2size))) 168 { 169 l4_addr_t page_addr = trunc(addr, L4_PAGESIZE); 170 171 region->fpage = l4_fpage(r.start + (page_addr - r.map_start), L4_PAGESHIFT, r.flags); 172 region->snd_base = page_addr; 173 174 #if DEBUG 175 printf("%lx...%lx from %lx...%lx size %d rights %x\n", 176 r.map_start, region->snd_base, 177 r.start, l4_fpage_memaddr(region->fpage), 178 l4_fpage_size(region->fpage), 179 l4_fpage_rights(region->fpage)); 180 printf("%lx -> ", addr); 181 182 for (unsigned int i = 0; i < sizeof(l4_umword_t); i++) 183 printf("%02x", *((unsigned char *)(r.start + (addr - r.map_start) + i))); 184 185 printf("\n"); 186 #endif 187 188 return L4_EOK; 189 } 190 191 #if DEBUG 192 printf("not mapped!\n"); 193 #endif 194 195 return -L4_ENOMEM; 196 } 197 198 /* Attach a region for provision when page faults occur. This is required in 199 the initialisation of a program by the C library which requires a region 200 mapper. */ 201 202 long ExecPager::attach(address_t *start, offset_t size, map_flags_t flags, 203 l4_cap_idx_t ds, address_t offset, unsigned char align) 204 { 205 #if DEBUG 206 printf("attach(%lx, %ld, %lx, ..., %lx, %d)\n", *start, size, flags, offset, align); 207 #endif 208 209 if (align < L4_PAGESHIFT) 210 align = L4_PAGESHIFT; 211 212 offset_t increment = 1UL << align; 213 offset_t region_size = round(size, increment); 214 215 /* Either attempt to find an address for the specified region, starting from 216 any indicated address. */ 217 218 if (flags & L4RE_RM_F_SEARCH_ADDR) 219 { 220 address_t region_start = trunc(*start, increment); 221 Regions::iterator it = _regions.upper_bound(*start); 222 223 if (!region_start) 224 region_start += increment; 225 226 #if DEBUG 227 printf("-> search from %lx -> %lx...\n", *start, region_start); 228 #endif 229 230 /* Before last known region. */ 231 232 while (it != _regions.end()) 233 { 234 Regions::iterator next = it; 235 Region &r = it->second; 236 address_t start_limit; 237 address_t end_limit = r.map_start; 238 239 /* Consider any preceding region. If no such region exists, choose an 240 address at the start of memory. */ 241 242 if (it == _regions.begin()) 243 start_limit = L4_PAGESIZE; 244 else 245 { 246 it--; 247 Region &pr = it->second; 248 start_limit = pr.map_start + (1UL << pr.log2size); 249 it = next; 250 } 251 252 /* Test against the limits. */ 253 254 if (region_start < start_limit) 255 region_start = round(start_limit, increment); 256 257 /* Investigate subsequent regions if not enough space exists between the 258 preceding region (or start of memory) and the current region. */ 259 260 if ((region_start + region_size) > end_limit) 261 { 262 it++; 263 if (it == _regions.end()) 264 return -L4_ENOMEM; 265 } 266 else 267 break; 268 } 269 270 /* Attach the provided dataspace. 271 NOTE: This is only done in this implementation to support the paging 272 mechanism. In a region mapper residing within the actual task, the 273 dataspace's map operation would be invoked to obtain mappings. */ 274 275 l4_addr_t ds_start; 276 277 long err = ipc_attach_dataspace(ds, size, (void **) &ds_start); 278 279 if (err) 280 return err; 281 282 l4_touch_rw((const void *) ds_start, size); 283 284 #if DEBUG 285 printf("-> added region for %lx size %ld (%d)\n", region_start, region_size, page_order(region_size)); 286 #endif 287 288 add(Region(ds_start, page_order(region_size), flags & L4RE_DS_F_RIGHTS_MASK, region_start)); 289 290 *start = region_start; 291 return L4_EOK; 292 } 293 294 /* Or attempt to add the specified region at a specific address. */ 295 296 else 297 { 298 // NOTE: To be implemented. 299 300 return -L4_ENOMEM; 301 } 302 } 303 304 305 306 static ExecPager exec_pager; 307 308 static void init_pager(ipc_server_config_type *config) 309 { 310 ipc_server_init_config(config); 311 312 config->expected_items = PagerObject_expected_items; 313 config->handler = (ipc_server_handler_type) handle_PagerObject; 314 config->handler_obj = static_cast<PagerObject *>(&exec_pager); 315 } 316 317 static long start_pager(ipc_server_config_type *config, pthread_t thread) 318 { 319 config->config_thread = 1; 320 config->thread = pthread_l4_cap(thread); 321 322 printf("Starting pager thread...\n"); 323 return ipc_server_start_config(config); 324 } 325 326 327 328 /* UTCB properties. 329 See: moe/server/src/loader.cc */ 330 331 enum ipc_exec_utcb 332 { 333 Default_max_threads = 16, 334 #ifdef ARCH_mips 335 Utcb_area_start = 0x73000000, 336 #else 337 Utcb_area_start = 0xb3000000, 338 #endif 339 }; 340 341 342 343 /* Capability mapping definitions for the new task. */ 344 345 struct mapped_cap 346 { 347 l4_cap_idx_t cap; 348 unsigned char rights; 349 l4_umword_t spot; 350 }; 351 352 static long map_capabilities(l4_cap_idx_t task, struct mapped_cap mapped_caps[]) 353 { 354 long err = L4_EOK; 355 int i = 0; 356 357 while (l4_is_valid_cap(mapped_caps[i].cap) && !err) 358 { 359 err = l4_error(l4_task_map(task, L4RE_THIS_TASK_CAP, 360 l4_obj_fpage(mapped_caps[i].cap, 0, mapped_caps[i].rights), 361 l4_map_obj_control(mapped_caps[i].spot, L4_MAP_ITEM_MAP))); 362 i++; 363 } 364 365 return err; 366 } 367 368 369 370 int main(int argc, char *argv[]) 371 { 372 long err; 373 374 if (argc < 2) 375 { 376 printf("Need a program to run.\n"); 377 return 1; 378 } 379 380 /* Allocate capabilities for the task and thread. */ 381 382 l4_cap_idx_t caps[2]; 383 384 err = ipc_cap_alloc_many(caps, 2); 385 386 if (err) 387 { 388 printf("Could not allocate capabilities.\n"); 389 return 1; 390 } 391 392 l4_cap_idx_t &task = caps[0]; 393 l4_cap_idx_t &thread = caps[1]; 394 395 /* Obtain the payload as a dataspace. */ 396 397 file_t *file = client_open(argv[1], O_RDONLY); 398 399 if (file == NULL) 400 { 401 printf("Could not read file: %s\n", argv[1]); 402 return 1; 403 } 404 405 /* Copy the payload regions to new dataspaces. 406 NOTE: This should be directed by the ELF metadata. */ 407 408 address_t program_region_base = 0x1000000; 409 address_t program_start = 0x1000af3; 410 411 char *program_buf; 412 offset_t nread; 413 offset_t program_region_contents = 0x282ae; 414 offset_t program_region_size = round(program_region_contents, L4_PAGESIZE); 415 l4re_ds_t program_region_ds; 416 417 err = ipc_allocate_align(program_region_size, L4RE_RM_F_SEARCH_ADDR | L4RE_RM_F_RWX, 418 L4_PAGESHIFT, (void **) &program_buf, &program_region_ds); 419 420 if (err) 421 { 422 printf("Could not reserve program memory.\n"); 423 return 1; 424 } 425 426 nread = client_read(file, program_buf, program_region_contents); 427 428 printf("Read %ld from file into %p.\n", nread, program_buf); 429 430 if (memcmp(program_buf + program_start - program_region_base, "\x31\xed", 2)) 431 { 432 printf("Did not find expected instructions at start.\n"); 433 return 1; 434 } 435 436 offset_t data_region_start = 0x1029360; 437 offset_t data_region_size = round(0x8068, L4_PAGESIZE); 438 offset_t data_region_base = trunc(data_region_start, L4_PAGESIZE); 439 offset_t data_region_offset = data_region_start - data_region_base; 440 441 char *data_buf; 442 offset_t data_file_offset = 0x28360; 443 offset_t data_region_contents = 0x2058; 444 l4re_ds_t data_region_ds; 445 446 err = ipc_allocate_align(data_region_size, L4RE_RM_F_SEARCH_ADDR | L4RE_RM_F_RW, 447 L4_PAGESHIFT, (void **) &data_buf, &data_region_ds); 448 449 if (err) 450 { 451 printf("Could not reserve data memory.\n"); 452 return 1; 453 } 454 455 memset(data_buf, 0, data_region_size); 456 457 client_seek(file, data_file_offset, SEEK_SET); 458 nread = client_read(file, data_buf + data_region_offset, data_region_contents); 459 460 printf("Read %ld from file into %p in region %p with size %ld for %lx.\n", 461 nread, data_buf + data_region_offset, data_buf, data_region_size, data_region_base); 462 463 /* UTCB location and size. */ 464 465 l4_addr_t utcb_start = Utcb_area_start; 466 int utcb_log2size = page_order(Default_max_threads * L4_UTCB_OFFSET); 467 468 /* Round up to at least one page. */ 469 470 if (utcb_log2size < L4_PAGESHIFT) 471 utcb_log2size = L4_PAGESHIFT; 472 473 l4_fpage_t utcb_fpage = l4_fpage(utcb_start, utcb_log2size, 0); 474 475 /* KIP allocation. */ 476 477 l4_addr_t kip_start = (l4_addr_t) l4re_kip(); 478 479 printf("KIP at %lx.\n", kip_start); 480 481 /* Stack allocation. */ 482 483 l4_addr_t stack_buf; 484 offset_t stack_size = 16 * L4_PAGESIZE; 485 l4_addr_t stack_region_base = 0x8000000 - stack_size; 486 l4re_ds_t stack_ds; 487 488 err = ipc_allocate_align(stack_size, L4RE_RM_F_SEARCH_ADDR | L4RE_RM_F_RW, 489 L4_PAGESHIFT, (void **) &stack_buf, &stack_ds); 490 491 if (err) 492 { 493 printf("Could not reserve stack.\n"); 494 return 1; 495 } 496 497 /* Populate stack with additional capabilities. */ 498 499 l4re_env_cap_entry_t *stack_env_cap = (l4re_env_cap_entry_t *) (stack_buf + stack_size); 500 501 /* Special invalid/terminating environment capability entry. */ 502 503 *(--stack_env_cap) = l4re_env_cap_entry_t(); 504 505 printf("Stack region end: %p\n", stack_env_cap); 506 507 l4_addr_t caps_start = (l4_addr_t) stack_env_cap; 508 l4_umword_t *stack_element = (l4_umword_t *) stack_env_cap; 509 510 /* Populate stack with argument values. */ 511 512 char *stack_arg = (char *) stack_element; 513 514 stack_arg = (char *) trunc((offset_t) stack_arg - strlen(argv[1]) - 1, sizeof(l4_umword_t)); 515 516 memset(stack_arg, 0, (char *) stack_element - stack_arg); 517 memcpy(stack_arg, argv[1], strlen(argv[1])); 518 519 printf("Stack L4 program argument: %p / %lx\n", stack_arg, ((l4_addr_t) stack_arg - stack_buf) + stack_region_base); 520 521 stack_element = (l4_umword_t *) stack_arg; 522 523 /* Loader flags, debugging flags, and the KIP capability index. 524 See: generate_l4aux in Remote_app_model */ 525 526 *(--stack_element) = 0; 527 *(--stack_element) = 0; 528 *(--stack_element) = 0x14 << L4_CAP_SHIFT; 529 530 printf("Stack L4 aux elements: %p / %lx\n", stack_element, ((l4_addr_t) stack_element - stack_buf) + stack_region_base); 531 532 /* Populate stack with standard capabilities. */ 533 534 l4re_env_t *env = (l4re_env_t *) stack_element; 535 536 env--; 537 env->factory = L4_BASE_FACTORY_CAP; 538 env->main_thread = L4_BASE_THREAD_CAP; 539 env->log = L4_BASE_LOG_CAP; 540 env->scheduler = L4_BASE_SCHEDULER_CAP; 541 env->rm = 0x11 << L4_CAP_SHIFT; 542 env->mem_alloc = 0x12 << L4_CAP_SHIFT; 543 env->first_free_cap = 0x15; 544 env->caps = (l4re_env_cap_entry_t *) (caps_start - stack_buf) + stack_region_base; 545 env->utcb_area = utcb_fpage; 546 env->first_free_utcb = utcb_start + L4_UTCB_OFFSET; 547 548 stack_element = (l4_umword_t *) env; 549 550 /* Populate stack with AUXV. */ 551 552 /* AUXV NULL. */ 553 554 *(--stack_element) = 0; 555 *(--stack_element) = 0; 556 557 /* L4Re global environment pointer. */ 558 559 *(--stack_element) = ((l4_addr_t) env - stack_buf) + stack_region_base; 560 *(--stack_element) = 0xf1; 561 562 /* Apparently required entries. */ 563 564 *(--stack_element) = L4_PAGESIZE; 565 *(--stack_element) = AT_PAGESZ; 566 567 *(--stack_element) = 0; 568 *(--stack_element) = AT_UID; 569 570 *(--stack_element) = 0; 571 *(--stack_element) = AT_EUID; 572 573 *(--stack_element) = 0; 574 *(--stack_element) = AT_GID; 575 576 *(--stack_element) = 0; 577 *(--stack_element) = AT_EGID; 578 579 l4_addr_t stack_data_end = (l4_addr_t) stack_element; 580 581 printf("Stack L4 data: %lx / %lx\n", stack_data_end, (stack_data_end - stack_buf) + stack_region_base); 582 583 /* No environment pointers. */ 584 585 *(--stack_element) = 0; 586 587 /* Populate stack with argument pointers and count. */ 588 /* NOTE: Just one argument currently. */ 589 590 *(--stack_element) = 0; 591 *(--stack_element) = (l4_umword_t) ((l4_addr_t) stack_arg - stack_buf) + stack_region_base; 592 *(--stack_element) = 1; 593 594 char *stack_adjusted = Ldr::adjust_sp((char *) stack_element, NULL); 595 596 /* Adjust the stack alignment. */ 597 598 if (stack_adjusted != (char *) stack_element) 599 memmove(stack_adjusted, (const void *) stack_element, stack_data_end - (l4_addr_t) stack_element); 600 601 l4_umword_t *stack_adjusted_element = (l4_umword_t *) stack_adjusted; 602 603 printf("%ld %lx %lx\n", stack_adjusted_element[0], stack_adjusted_element[1], stack_adjusted_element[2]); 604 605 l4_addr_t stack_start = ((l4_addr_t) stack_adjusted - stack_buf) + stack_region_base; 606 607 printf("Stack L4 start: %p / %lx\n", stack_adjusted, ((l4_addr_t) stack_adjusted - stack_buf) + stack_region_base); 608 609 /* Create a new task and thread. */ 610 611 err = l4_error(l4_factory_create_task(l4re_env()->factory, task, utcb_fpage)); 612 613 if (err) 614 { 615 printf("Could not create task.\n"); 616 return 1; 617 } 618 619 err = l4_error(l4_factory_create_thread(l4re_env()->factory, thread)); 620 621 if (err) 622 { 623 printf("Could not create thread.\n"); 624 return 1; 625 } 626 627 /* Start the pager. */ 628 629 ipc_server_config_type config; 630 pthread_t pager_thread; 631 pthread_attr_t attr; 632 633 pthread_attr_init(&attr); 634 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 635 636 init_pager(&config); 637 638 exec_pager.add(Region((l4_addr_t) program_buf, page_order(program_region_size), L4_FPAGE_RX, program_region_base)); 639 exec_pager.add(Region((l4_addr_t) data_buf, page_order(data_region_size), L4_FPAGE_RW, data_region_base)); 640 exec_pager.add(Region((l4_addr_t) stack_buf, page_order(stack_size), L4_FPAGE_RW, stack_region_base)); 641 642 err = pthread_create(&pager_thread, &attr, ipc_server_start_mainloop, &config); 643 644 if (err) 645 { 646 printf("Could not start pager thread.\n"); 647 return 1; 648 } 649 650 err = start_pager(&config, pager_thread); 651 652 if (err) 653 { 654 printf("Could not start pager.\n"); 655 return 1; 656 } 657 658 /* Define capability mappings for the new task. */ 659 660 struct mapped_cap mapped_caps[] = { 661 {config.server, L4_CAP_FPAGE_RWS, 0x10 << L4_CAP_SHIFT}, 662 {config.server, L4_CAP_FPAGE_RWS, 0x11 << L4_CAP_SHIFT}, 663 {task, L4_CAP_FPAGE_RWS, L4_BASE_TASK_CAP}, 664 {thread, L4_CAP_FPAGE_RWS, L4_BASE_THREAD_CAP}, 665 {l4re_env()->factory, L4_CAP_FPAGE_RWS, L4_BASE_FACTORY_CAP}, 666 {l4re_env()->log, L4_CAP_FPAGE_RWS, L4_BASE_LOG_CAP}, 667 {l4re_env()->scheduler, L4_CAP_FPAGE_RWS, L4_BASE_SCHEDULER_CAP}, 668 {l4re_env()->mem_alloc, L4_CAP_FPAGE_RWS, 0x12 << L4_CAP_SHIFT}, 669 {L4_INVALID_CAP, 0, 0}, 670 }; 671 672 err = map_capabilities(task, mapped_caps); 673 674 if (err) 675 { 676 printf("Could not capabilities into task.\n"); 677 return 1; 678 } 679 680 /* Map the KIP into the task. */ 681 682 err = l4_error(l4_task_map(task, L4RE_THIS_TASK_CAP, 683 l4_fpage(kip_start, L4_PAGESHIFT, L4_FPAGE_RX), 684 kip_start)); 685 686 if (err) 687 { 688 printf("Could not map KIP into task.\n"); 689 return 1; 690 } 691 692 /* Configure the thread with the region manager acting as pager and exception 693 handler. The UTCB will be situated at an address supported by a dataspace 694 attached to the new task. */ 695 696 printf("Configure thread...\n"); 697 698 l4_thread_control_start(); 699 l4_thread_control_pager(0x10 << L4_CAP_SHIFT); 700 l4_thread_control_exc_handler(0x10 << L4_CAP_SHIFT); 701 l4_thread_control_bind((l4_utcb_t *) utcb_start, task); 702 err = l4_error(l4_thread_control_commit(thread)); 703 704 if (err) 705 { 706 printf("Could not configure thread.\n"); 707 return 1; 708 } 709 710 /* Start the new thread. */ 711 712 printf("Schedule thread...\n"); 713 714 printf("Stack at 0x%lx mapped to region at 0x%lx.\n", stack_start, stack_region_base); 715 716 err = l4_error(l4_thread_ex_regs(thread, program_start, stack_start, 0)); 717 718 if (err) 719 { 720 printf("Could not set thread registers.\n"); 721 return 1; 722 } 723 724 printf("Run thread...\n"); 725 726 l4_sched_param_t sp = l4_sched_param(L4RE_MAIN_THREAD_PRIO); 727 728 err = l4_error(l4_scheduler_run_thread(l4re_env()->scheduler, thread, &sp)); 729 730 if (err) 731 { 732 printf("Could not run thread.\n"); 733 return 1; 734 } 735 736 printf("Finished.\n"); 737 while (1); 738 739 return 0; 740 } 741 742 /* vim: tabstop=2 expandtab shiftwidth=2 743 */