1/* 2 3/usr/src/ext2ed/init.c 4 5A part of the extended file system 2 disk editor. 6 7-------------------------------- 8Various initialization routines. 9-------------------------------- 10 11First written on: April 9 1995 12 13Copyright (C) 1995 Gadi Oxman 14 15*/ 16 17#include <stdio.h> 18#include <stdlib.h> 19#include <string.h> 20#ifdef HAVE_READLINE 21#include <readline.h> 22#endif 23#include <signal.h> 24#include <unistd.h> 25 26#include <sys/types.h> 27#include <sys/stat.h> 28#include <fcntl.h> 29 30#include "ext2ed.h" 31 32char lines_s [80],cols_s [80]; 33 34void signal_handler (void); 35 36void prepare_to_close (void) 37 38{ 39 close_windows (); 40 if (device_handle!=NULL) 41 fclose (device_handle); 42 free_user_commands (&general_commands); 43 free_user_commands (&ext2_commands); 44 free_struct_descriptors (); 45} 46 47int init (void) 48 49{ 50 printf ("Initializing ...\n"); 51 52 if (!process_configuration_file ()) { 53 fprintf (stderr,"Error - Unable to complete configuration. Quitting.\n"); 54 return (0); 55 }; 56 57 general_commands.last_command=-1; /* No commands whatsoever meanwhile */ 58 ext2_commands.last_command=-1; 59 add_general_commands (); /* Add the general commands, aviable always */ 60 device_handle=NULL; /* Notice that our device is still not set up */ 61 device_offset=-1; 62 current_type=NULL; /* No filesystem specific types yet */ 63 64 remember_lifo.entries_count=0; /* Object memory is empty */ 65 66 init_windows (); /* Initialize the NCURSES interface */ 67 init_readline (); /* Initialize the READLINE interface */ 68 init_signals (); /* Initialize the signal handlers */ 69 write_access=0; /* Write access disabled */ 70 71 strcpy (last_command_line,"help"); /* Show the help screen to the user */ 72 dispatch ("help"); 73 return (1); /* Success */ 74} 75 76void add_general_commands (void) 77 78{ 79 add_user_command (&general_commands,"help","EXT2ED help system",help); 80 add_user_command (&general_commands,"set","Changes a variable in the current object",set); 81 add_user_command (&general_commands,"setdevice","Selects the filesystem block device (e.g. /dev/hda1)",set_device); 82 add_user_command (&general_commands,"setoffset","Moves asynchronicly in the filesystem",set_offset); 83 add_user_command (&general_commands,"settype","Tells EXT2ED how to interpert the current object",set_type); 84 add_user_command (&general_commands,"show","Displays the current object",show); 85 add_user_command (&general_commands,"pgup","Scrolls data one page up",pgup); 86 add_user_command (&general_commands,"pgdn","Scrolls data one page down",pgdn); 87 add_user_command (&general_commands,"redraw","Redisplay the screen",redraw); 88 add_user_command (&general_commands,"remember","Saves the current position and data information",remember); 89 add_user_command (&general_commands,"recall","Gets back to the saved object position",recall); 90 add_user_command (&general_commands,"enablewrite","Enters Read/Write mode - Allows changing the filesystem",enable_write); 91 add_user_command (&general_commands,"disablewrite","Enters read only mode",disable_write); 92 add_user_command (&general_commands,"writedata","Write data back to disk",write_data); 93 add_user_command (&general_commands,"next","Moves to the next byte in hex mode",next); 94 add_user_command (&general_commands,"prev","Moves to the previous byte in hex mode",prev); 95} 96 97void add_ext2_general_commands (void) 98 99{ 100 add_user_command (&ext2_commands,"super","Moves to the superblock of the filesystem",type_ext2___super); 101 add_user_command (&ext2_commands,"group","Moves to the first group descriptor",type_ext2___group); 102 add_user_command (&ext2_commands,"cd","Moves to the directory specified",type_ext2___cd); 103} 104 105int set_struct_descriptors (char *file_name) 106 107{ 108 FILE *fp; 109 char current_line [500],current_word [50],*ch; 110 char variable_name [50],variable_type [20]; 111 struct struct_descriptor *current_descriptor; 112 113 if ( (fp=fopen (file_name,"rt"))==NULL) { 114 wprintw (command_win,"Error - Failed to open descriptors file %s\n",file_name); 115 refresh_command_win (); return (0); 116 }; 117 118 while (!feof (fp)) { 119 fgets (current_line,500,fp); 120 if (feof (fp)) break; 121 ch=parse_word (current_line,current_word); 122 if (strcmp (current_word,"struct")==0) { 123 ch=parse_word (ch,current_word); 124 current_descriptor=add_new_descriptor (current_word); 125 126 while (strchr (current_line,'{')==NULL) { 127 fgets (current_line,500,fp); 128 if (feof (fp)) break; 129 }; 130 if (feof (fp)) break; 131 132 fgets (current_line,500,fp); 133 134 while (strchr (current_line,'}')==NULL) { 135 while (strchr (current_line,';')==NULL) { 136 fgets (current_line,500,fp); 137 if (strchr (current_line,'}')!=NULL) break; 138 }; 139 if (strchr (current_line,'}') !=NULL) break; 140 ch=parse_word (current_line,variable_type); 141 ch=parse_word (ch,variable_name); 142 while (variable_name [strlen (variable_name)-1]!=';') { 143 strcpy (variable_type,variable_name); 144 ch=parse_word (ch,variable_name); 145 }; 146 variable_name [strlen (variable_name)-1]=0; 147 add_new_variable (current_descriptor,variable_type,variable_name); 148 fgets (current_line,500,fp); 149 }; 150 }; 151 }; 152 153 fclose (fp); 154 return (1); 155} 156 157void free_struct_descriptors (void) 158 159{ 160 struct struct_descriptor *ptr,*next; 161 162 ptr=first_type; 163 while (ptr!=NULL) { 164 next=ptr->next; 165 free_user_commands (&ptr->type_commands); 166 free (ptr); 167 ptr=next; 168 } 169 first_type=last_type=current_type=NULL; 170} 171 172void free_user_commands (struct struct_commands *ptr) 173 174{ 175 int i; 176 177 for (i=0;i<=ptr->last_command;i++) { 178 free (ptr->names [i]); 179 free (ptr->descriptions [i]); 180 } 181 182 ptr->last_command=-1; 183} 184 185struct struct_descriptor *add_new_descriptor (char *name) 186 187{ 188 struct struct_descriptor *ptr; 189 190 ptr = malloc (sizeof (struct struct_descriptor)); 191 if (ptr == NULL) { 192 printf ("Error - Can not allocate memory - Quitting\n"); 193 exit (1); 194 } 195 memset(ptr, 0, sizeof(struct struct_descriptor)); 196 ptr->prev = ptr->next = NULL; 197 strcpy (ptr->name,name); 198 ptr->length=0; 199 ptr->fields_num=0; 200 if (first_type==NULL) { 201 first_type = last_type = ptr; 202 } else { 203 ptr->prev = last_type; last_type->next = ptr; last_type=ptr; 204 } 205 ptr->type_commands.last_command=-1; 206 fill_type_commands (ptr); 207 return (ptr); 208} 209 210struct type_table { 211 char *name; 212 int field_type; 213 int len; 214}; 215 216struct type_table type_table[] = { 217 { "long", FIELD_TYPE_INT, 4 }, 218 { "short", FIELD_TYPE_INT, 2 }, 219 { "char", FIELD_TYPE_CHAR, 1 }, 220 { "__u32", FIELD_TYPE_UINT, 4 }, 221 { "__s32", FIELD_TYPE_INT, 4 }, 222 { "__u16", FIELD_TYPE_UINT, 2 }, 223 { "__s16", FIELD_TYPE_INT, 2 }, 224 { "__u8", FIELD_TYPE_UINT, 1 }, 225 { "__s8", FIELD_TYPE_INT, 1 }, 226 { 0, 0, 0 } 227}; 228 229void add_new_variable (struct struct_descriptor *ptr,char *v_type,char *v_name) 230 231{ 232 short len=1; 233 char field_type=FIELD_TYPE_INT; 234 struct type_table *p; 235 236 strcpy (ptr->field_names [ptr->fields_num],v_name); 237 ptr->field_positions [ptr->fields_num]=ptr->length; 238 239 for (p = type_table; p->name; p++) { 240 if (strcmp(v_type, p->name) == 0) { 241 len = p->len; 242 field_type = p->field_type; 243 break; 244 } 245 } 246 if (p->name == 0) { 247 if (strncmp(v_type, "char[", 5) == 0) { 248 len = atoi(v_type+5); 249 field_type = FIELD_TYPE_CHAR; 250 } else { 251 printf("Unknown type %s for field %s\n", v_type, v_name); 252 exit(1); 253 } 254 } 255 256 ptr->field_lengths [ptr->fields_num] = len; 257 ptr->field_types [ptr->fields_num] = field_type; 258 259 ptr->length+=len; 260 ptr->fields_num++; 261} 262 263void fill_type_commands (struct struct_descriptor *ptr) 264 265/* 266 267Set specific type user commands. 268 269*/ 270 271{ 272 273 if (strcmp ((ptr->name),"file")==0) { 274 add_user_command (&ptr->type_commands,"show","Shows file data",type_file___show); 275 add_user_command (&ptr->type_commands,"inode","Returns to the inode of the current file",type_file___inode); 276 add_user_command (&ptr->type_commands,"display","Specifies data format - text or hex",type_file___display); 277 add_user_command (&ptr->type_commands,"next","Pass to next byte",type_file___next); 278 add_user_command (&ptr->type_commands,"prev","Pass to the previous byte",type_file___prev); 279 add_user_command (&ptr->type_commands,"offset","Pass to a specified byte in the current block",type_file___offset); 280 add_user_command (&ptr->type_commands,"nextblock","Pass to next file block",type_file___nextblock); 281 add_user_command (&ptr->type_commands,"prevblock","Pass to the previous file block",type_file___prevblock); 282 add_user_command (&ptr->type_commands,"block","Specify which file block to edit",type_file___block); 283 add_user_command (&ptr->type_commands,"remember","Saves the file\'s inode position for later reference",type_file___remember); 284 add_user_command (&ptr->type_commands,"set","Sets the current byte",type_file___set); 285 add_user_command (&ptr->type_commands,"writedata","Writes the current block to the disk",type_file___writedata); 286 } 287 288 if (strcmp ((ptr->name),"ext2_inode")==0) { 289 add_user_command (&ptr->type_commands,"show","Shows inode data",type_ext2_inode___show); 290 add_user_command (&ptr->type_commands,"next","Move to next inode in current block group",type_ext2_inode___next); 291 add_user_command (&ptr->type_commands,"prev","Move to next inode in current block group",type_ext2_inode___prev); 292 add_user_command (&ptr->type_commands,"group","Move to the group descriptors of the current inode table",type_ext2_inode___group); 293 add_user_command (&ptr->type_commands,"entry","Move to a specified entry in the current inode table",type_ext2_inode___entry); 294 add_user_command (&ptr->type_commands,"file","Display file data of the current inode",type_ext2_inode___file); 295 add_user_command (&ptr->type_commands,"dir","Display directory data of the current inode",type_ext2_inode___dir); 296 } 297 298 if (strcmp ((ptr->name),"dir")==0) { 299 add_user_command (&ptr->type_commands,"show","Shows current directory data",type_dir___show); 300 add_user_command (&ptr->type_commands,"inode","Returns to the inode of the current directory",type_dir___inode); 301 add_user_command (&ptr->type_commands,"next","Pass to the next directory entry",type_dir___next); 302 add_user_command (&ptr->type_commands,"prev","Pass to the previous directory entry",type_dir___prev); 303 add_user_command (&ptr->type_commands,"followinode","Follows the inode specified in this directory entry",type_dir___followinode); 304 add_user_command (&ptr->type_commands,"remember","Remember the inode of the current directory entry",type_dir___remember); 305 add_user_command (&ptr->type_commands,"cd","Changes directory relative to the current directory",type_dir___cd); 306 add_user_command (&ptr->type_commands,"entry","Moves to a specified entry in the current directory",type_dir___entry); 307 add_user_command (&ptr->type_commands,"writedata","Writes the current entry to the disk",type_dir___writedata); 308 add_user_command (&ptr->type_commands,"set","Changes a variable in the current directory entry",type_dir___set); 309 } 310 311 if (strcmp ((ptr->name),"ext2_super_block")==0) { 312 add_user_command (&ptr->type_commands,"show","Displays the super block data",type_ext2_super_block___show); 313 add_user_command (&ptr->type_commands,"gocopy","Move to another backup copy of the superblock",type_ext2_super_block___gocopy); 314 add_user_command (&ptr->type_commands,"setactivecopy","Copies the current superblock to the main superblock",type_ext2_super_block___setactivecopy); 315 } 316 317 if (strcmp ((ptr->name),"ext2_group_desc")==0) { 318 add_user_command (&ptr->type_commands,"next","Pass to the next block group decriptor",type_ext2_group_desc___next); 319 add_user_command (&ptr->type_commands,"prev","Pass to the previous group descriptor",type_ext2_group_desc___prev); 320 add_user_command (&ptr->type_commands,"entry","Pass to a specific group descriptor",type_ext2_group_desc___entry); 321 add_user_command (&ptr->type_commands,"show","Shows the current group descriptor",type_ext2_group_desc___show); 322 add_user_command (&ptr->type_commands,"inode","Pass to the inode table of the current group block",type_ext2_group_desc___inode); 323 add_user_command (&ptr->type_commands,"gocopy","Move to another backup copy of the group descriptor",type_ext2_group_desc___gocopy); 324 add_user_command (&ptr->type_commands,"blockbitmap","Show the block allocation bitmap of the current group block",type_ext2_group_desc___blockbitmap); 325 add_user_command (&ptr->type_commands,"inodebitmap","Show the inode allocation bitmap of the current group block",type_ext2_group_desc___inodebitmap); 326 add_user_command (&ptr->type_commands,"setactivecopy","Copies the current group descriptor to the main table",type_ext2_super_block___setactivecopy); 327 } 328 329 if (strcmp ((ptr->name),"block_bitmap")==0) { 330 add_user_command (&ptr->type_commands,"show","Displays the block allocation bitmap",type_ext2_block_bitmap___show); 331 add_user_command (&ptr->type_commands,"entry","Moves to a specific bit",type_ext2_block_bitmap___entry); 332 add_user_command (&ptr->type_commands,"next","Moves to the next bit",type_ext2_block_bitmap___next); 333 add_user_command (&ptr->type_commands,"prev","Moves to the previous bit",type_ext2_block_bitmap___prev); 334 add_user_command (&ptr->type_commands,"allocate","Allocates the current block",type_ext2_block_bitmap___allocate); 335 add_user_command (&ptr->type_commands,"deallocate","Deallocates the current block",type_ext2_block_bitmap___deallocate); 336 } 337 338 if (strcmp ((ptr->name),"inode_bitmap")==0) { 339 add_user_command (&ptr->type_commands,"show","Displays the inode allocation bitmap",type_ext2_inode_bitmap___show); 340 add_user_command (&ptr->type_commands,"entry","Moves to a specific bit",type_ext2_inode_bitmap___entry); 341 add_user_command (&ptr->type_commands,"next","Moves to the next bit",type_ext2_inode_bitmap___next); 342 add_user_command (&ptr->type_commands,"prev","Moves to the previous bit",type_ext2_inode_bitmap___prev); 343 add_user_command (&ptr->type_commands,"allocate","Allocates the current inode",type_ext2_inode_bitmap___allocate); 344 add_user_command (&ptr->type_commands,"deallocate","Deallocates the current inode",type_ext2_inode_bitmap___deallocate); 345 } 346 347} 348 349void add_user_command (struct struct_commands *ptr,char *name,char *description,PF callback) 350 351{ 352 int num; 353 354 num=ptr->last_command; 355 if (num+1==MAX_COMMANDS_NUM) { 356 printf ("Internal Error - Can't add command %s\n",name); 357 return; 358 } 359 360 ptr->last_command=++num; 361 362 ptr->names [num]=(char *) malloc (strlen (name)+1); 363 strcpy (ptr->names [num],name); 364 365 if (*description!=0) { 366 ptr->descriptions [num]=(char *) malloc (strlen (description)+1); 367 strcpy (ptr->descriptions [num],description); 368 } 369 370 ptr->callback [num]=callback; 371} 372 373static unsigned int div_ceil(unsigned int a, unsigned int b) 374{ 375 if (!a) 376 return 0; 377 return ((a - 1) / b) + 1; 378} 379 380int set_file_system_info (void) 381 382{ 383 int ext2_detected=0; 384 struct ext2_super_block *sb; 385 386 file_system_info.super_block_offset=1024; 387 file_system_info.file_system_size=DefaultTotalBlocks*DefaultBlockSize; 388 389 low_read ((char *) &file_system_info.super_block,sizeof (struct ext2_super_block),file_system_info.super_block_offset); 390 391 sb=&file_system_info.super_block; 392 393 if (sb->s_magic == EXT2_SUPER_MAGIC) 394 ext2_detected=1; 395 396 if (ext2_detected) 397 wprintw (command_win,"Detected extended 2 file system on device %s\n",device_name); 398 else 399 wprintw (command_win,"Warning - Extended 2 filesystem not detected on device %s\n",device_name); 400 401 if (!ext2_detected && !ForceExt2) 402 wprintw (command_win,"You may wish to use the configuration option ForceExt2 on\n"); 403 404 if (ForceExt2 && !ext2_detected) 405 wprintw (command_win,"Forcing extended 2 filesystem\n"); 406 407 if (ForceDefault || !ext2_detected) 408 wprintw (command_win,"Forcing default parameters\n"); 409 410 refresh_command_win (); 411 412 if (ext2_detected || ForceExt2) { 413 add_ext2_general_commands (); 414 if (!set_struct_descriptors (Ext2Descriptors)) 415 return (0); 416 } 417 418 if (!ForceDefault && ext2_detected) { 419 420 file_system_info.block_size=EXT2_MIN_BLOCK_SIZE << sb->s_log_block_size; 421 if (file_system_info.block_size == EXT2_MIN_BLOCK_SIZE) 422 file_system_info.first_group_desc_offset=2*EXT2_MIN_BLOCK_SIZE; 423 else 424 file_system_info.first_group_desc_offset=file_system_info.block_size; 425 file_system_info.groups_count = div_ceil(sb->s_blocks_count, 426 sb->s_blocks_per_group); 427 428 file_system_info.inodes_per_block=file_system_info.block_size/sizeof (struct ext2_inode); 429 file_system_info.blocks_per_group=sb->s_inodes_per_group/file_system_info.inodes_per_block; 430 file_system_info.no_blocks_in_group=sb->s_blocks_per_group; 431 file_system_info.file_system_size=(sb->s_blocks_count-1)*file_system_info.block_size; 432 } 433 434 else { 435 file_system_info.file_system_size=DefaultTotalBlocks*DefaultBlockSize; 436 file_system_info.block_size=DefaultBlockSize; 437 file_system_info.no_blocks_in_group=DefaultBlocksInGroup; 438 } 439 440 if (file_system_info.file_system_size > 2147483647) { 441 wprintw (command_win,"Sorry, filesystems bigger than 2 GB are currently not supported\n"); 442 return (0); 443 } 444 return (1); 445} 446 447void init_readline (void) 448 449{ 450#ifdef HAVE_READLINE 451 rl_completion_entry_function=(Function *) complete_command; 452#endif 453} 454 455void init_signals (void) 456 457{ 458 signal (SIGWINCH, signal_SIGWINCH_handler); /* Catch SIGWINCH */ 459 signal (SIGTERM, signal_SIGTERM_handler); 460 signal (SIGSEGV, signal_SIGSEGV_handler); 461 462} 463 464void signal_SIGWINCH_handler (int sig_num) 465 466{ 467 redraw_request=1; /* We will handle it in main.c */ 468 469 /* Reset signal handler */ 470 signal (SIGWINCH, signal_SIGWINCH_handler); 471 472} 473 474void signal_SIGTERM_handler (int sig_num) 475 476{ 477 prepare_to_close (); 478 printf ("Terminated due to signal %d\n",sig_num); 479 exit (1); 480} 481 482void signal_SIGSEGV_handler (int sig_num) 483 484{ 485 prepare_to_close (); 486 printf ("Killed by signal %d!\n",sig_num); 487 exit (1); 488} 489 490int process_configuration_file (void) 491 492{ 493 char buffer [300]; 494 char option [80],value [80]; 495 FILE *fp; 496 497 strcpy (buffer, ETC_DIR); 498 strcat (buffer,"/ext2ed.conf"); 499 500 if ((fp=fopen (buffer,"rt"))==NULL) { 501 fprintf (stderr,"Error - Unable to open configuration file %s\n",buffer); 502 return (0); 503 } 504 505 while (get_next_option (fp,option,value)) { 506 if (strcasecmp (option,"Ext2Descriptors")==0) { 507 strcpy (Ext2Descriptors,value); 508 } 509 510 else if (strcasecmp (option,"AlternateDescriptors")==0) { 511 strcpy (AlternateDescriptors,value); 512 } 513 514 else if (strcasecmp (option,"LogFile")==0) { 515 strcpy (LogFile,value); 516 } 517 518 else if (strcasecmp (option,"LogChanges")==0) { 519 if (strcasecmp (value,"on")==0) 520 LogChanges = 1; 521 else if (strcasecmp (value,"off")==0) 522 LogChanges = 0; 523 else { 524 fprintf (stderr,"Error - Illegal value: %s %s\n",option,value); 525 fclose (fp);return (0); 526 } 527 } 528 529 else if (strcasecmp (option,"AllowChanges")==0) { 530 if (strcasecmp (value,"on")==0) 531 AllowChanges = 1; 532 else if (strcasecmp (value,"off")==0) 533 AllowChanges = 0; 534 else { 535 fprintf (stderr,"Error - Illegal value: %s %s\n",option,value); 536 fclose (fp);return (0); 537 } 538 } 539 540 else if (strcasecmp (option,"AllowMountedRead")==0) { 541 if (strcasecmp (value,"on")==0) 542 AllowMountedRead = 1; 543 else if (strcasecmp (value,"off")==0) 544 AllowMountedRead = 0; 545 else { 546 fprintf (stderr,"Error - Illegal value: %s %s\n",option,value); 547 fclose (fp);return (0); 548 } 549 } 550 551 else if (strcasecmp (option,"ForceExt2")==0) { 552 if (strcasecmp (value,"on")==0) 553 ForceExt2 = 1; 554 else if (strcasecmp (value,"off")==0) 555 ForceExt2 = 0; 556 else { 557 fprintf (stderr,"Error - Illegal value: %s %s\n",option,value); 558 fclose (fp);return (0); 559 } 560 } 561 562 else if (strcasecmp (option,"DefaultBlockSize")==0) { 563 DefaultBlockSize = atoi (value); 564 } 565 566 else if (strcasecmp (option,"DefaultTotalBlocks")==0) { 567 DefaultTotalBlocks = strtoul (value,NULL,10); 568 } 569 570 else if (strcasecmp (option,"DefaultBlocksInGroup")==0) { 571 DefaultBlocksInGroup = strtoul (value,NULL,10); 572 } 573 574 else if (strcasecmp (option,"ForceDefault")==0) { 575 if (strcasecmp (value,"on")==0) 576 ForceDefault = 1; 577 else if (strcasecmp (value,"off")==0) 578 ForceDefault = 0; 579 else { 580 fprintf (stderr,"Error - Illegal value: %s %s\n",option,value); 581 fclose (fp);return (0); 582 } 583 } 584 585 else { 586 fprintf (stderr,"Error - Unknown option: %s\n",option); 587 fclose (fp);return (0); 588 } 589 } 590 591 printf ("Configuration completed\n"); 592 fclose (fp); 593 return (1); 594} 595 596int get_next_option (FILE *fp,char *option,char *value) 597 598{ 599 char *ptr; 600 char buffer [600]; 601 602 if (feof (fp)) return (0); 603 do{ 604 if (feof (fp)) return (0); 605 fgets (buffer,500,fp); 606 } while (buffer [0]=='#' || buffer [0]=='\n'); 607 608 ptr=parse_word (buffer,option); 609 ptr=parse_word (ptr,value); 610 return (1); 611} 612 613void check_mounted (char *name) 614 615{ 616 FILE *fp; 617 char *ptr; 618 char current_line [500],current_word [200]; 619 620 mounted=0; 621 622 if ( (fp=fopen ("/etc/mtab","rt"))==NULL) { 623 wprintw (command_win,"Error - Failed to open /etc/mtab. Assuming filesystem is mounted.\n"); 624 refresh_command_win ();mounted=1;return; 625 }; 626 627 while (!feof (fp)) { 628 fgets (current_line,500,fp); 629 if (feof (fp)) break; 630 ptr=parse_word (current_line,current_word); 631 if (strcasecmp (current_word,name)==0) { 632 mounted=1;fclose (fp);return; 633 } 634 }; 635 636 fclose (fp); 637 638 return; 639} 640