1/* 2 * This program is free software; you can redistribute it and/or 3 * modify it under the terms of the GNU General Public License as 4 * published by the Free Software Foundation; either version 2 of 5 * the License, or (at your option) any later version. 6 * 7 * This program is distributed in the hope that it will be useful, 8 * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 * GNU General Public License for more details. 11 * 12 * You should have received a copy of the GNU General Public License 13 * along with this program; if not, write to the Free Software 14 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, 15 * MA 02111-1307 USA 16 */ 17 18#include <stdio.h> 19#include <string.h> 20#include <stdlib.h> 21#include <dirent.h> 22#include <unistd.h> 23#include <ctype.h> 24#include <bcmnvram.h> 25#include <shutils.h> 26#include <rtconfig.h> 27 28#include "usb_info.h" 29#include "disk_initial.h" 30#include "disk_share.h" 31 32#include <linux/version.h> 33 34#define SAMBA_CONF "/etc/smb.conf" 35 36/* @return: 37 * If mount_point is equal to one of partition of all disks case-insensitivity, return true. 38 */ 39static int check_mount_point_icase(const disk_info_t *d_info, const partition_info_t *p_info, const disk_info_t *disk, const u32 part_nr, const char *m_point) 40{ 41 int v = 0; 42 const disk_info_t *d; 43 const partition_info_t *p; 44 45 if (!d_info || !p_info || !disk || part_nr > 15 || !m_point || *m_point == '\0') 46 return 0; 47 48 for (d = d_info; !v && d != NULL; d = d->next) { 49 for (p = d->partitions; !v && p != NULL; p = p->next) { 50 if (!p->mount_point || (d == disk && p->partition_order == part_nr)) 51 continue; 52 53 if (strcasecmp(p->mount_point, m_point)) 54 continue; 55 56 v = 1; 57 } 58 } 59 60 return v; 61} 62 63int 64is_invalid_char_for_hostname(char c) 65{ 66 int ret = 0; 67 68 if (c < 0x20) 69 ret = 1; 70 else if (c >= 0x21 && c <= 0x2c) 71 ret = 1; 72 else if (c >= 0x2e && c <= 0x2f) 73 ret = 1; 74 else if (c >= 0x3a && c <= 0x40) 75 ret = 1; 76#if 0 77 else if (c >= 0x5b && c <= 0x60) 78 ret = 1; 79#else /* allow '_' */ 80 else if (c >= 0x5b && c <= 0x5e) 81 ret = 1; 82 else if (c == 0x60) 83 ret = 1; 84#endif 85 else if (c >= 0x7b) 86 ret = 1; 87#if 0 88 printf("%c (0x%02x) is %svalid for hostname\n", c, c, (ret == 0) ? " " : "in"); 89#endif 90 return ret; 91} 92 93int 94is_valid_hostname(const char *name) 95{ 96 int ret = 1, len, i; 97 98 if (!name) 99 return 0; 100 101 len = strlen(name); 102 if (len == 0) 103 { 104 ret = 0; 105 goto ENDERR; 106 } 107 108 for (i = 0; i < len ; i++) 109 if (is_invalid_char_for_hostname(name[i])) 110 { 111 ret = 0; 112 break; 113 } 114 115ENDERR: 116#if 0 117 printf("%s is %svalid for hostname\n", name, (ret == 1) ? " " : "in"); 118#endif 119 return ret; 120} 121 122/* For NETBIOS name, 123 * 1. NetBIOS names are a sequence of alphanumeric characters. 124 * 2. The hyphen ("-") and full-stop (".") characters may also be used 125 * in the NetBIOS name, but not as the first or last character. 126 * 3. The NetBIOS name is 16 ASCII characters, however Microsoft limits 127 * the host name to 15 characters and reserves the 16th character 128 * as a NetBIOS Suffix 129 */ 130int 131is_valid_netbios_name(const char *name) 132{ 133 int i, valid = 1; 134 size_t len; 135 136 if (!name) 137 return 0; 138 139 len = strlen(name); 140 if (!len || len > 15) 141 return 0; 142 143 for (i = 0; valid && i < len; ++i) { 144 if (isalnum(name[i])) 145 continue; 146 else if ((name[i] == '-' || name[i] == '.') && (i > 0 && i < (len - 1))) 147 continue; 148 149 valid = 0; 150 } 151 152 return valid; 153} 154 155int check_existed_share(const char *string) 156{ 157 FILE *tp; 158 char buf[PATH_MAX], target[256]; 159 160 if((tp = fopen(SAMBA_CONF, "r")) == NULL) 161 return 0; 162 163 if(string == NULL || strlen(string) <= 0) 164 return 0; 165 166 memset(target, 0, 256); 167 sprintf(target, "[%s]", string); 168 169 memset(buf, 0, PATH_MAX); 170 while(fgets(buf, sizeof(buf), tp) != NULL){ 171 if(strstr(buf, target)){ 172 fclose(tp); 173 return 1; 174 } 175 } 176 177 fclose(tp); 178 return 0; 179} 180 181int get_list_strings_count(char **list, int size, char *str) 182{ 183 int i, count = 0; 184 185 for (i = 0; i < size; i++) 186 if (strcmp(list[i], str) == 0) count++; 187 return count; 188} 189 190int main(int argc, char *argv[]) 191{ 192 FILE *fp; 193 int n=0; 194 char *p_computer_name = NULL; 195 disk_info_t *follow_disk, *disks_info = NULL; 196 partition_info_t *follow_partition; 197 char *mount_folder; 198 int result, node_layer, samba_right; 199 int sh_num; 200 char **folder_list = NULL; 201 int acc_num; 202 char **account_list; 203 int dup, same_m_pt = 0; 204 char unique_share_name[PATH_MAX]; 205 206 unlink("/var/log.samba"); 207 208 if ((fp=fopen(SAMBA_CONF, "r"))) { 209 fclose(fp); 210 unlink(SAMBA_CONF); 211 } 212 213 if((fp = fopen(SAMBA_CONF, "w")) == NULL) 214 goto confpage; 215 216 fprintf(fp, "[global]\n"); 217 if (nvram_safe_get("st_samba_workgroup")) 218 fprintf(fp, "workgroup = %s\n", nvram_safe_get("st_samba_workgroup")); 219#if 0 220 if (nvram_safe_get("computer_name")) { 221 fprintf(fp, "netbios name = %s\n", nvram_safe_get("computer_name")); 222 fprintf(fp, "server string = %s\n", nvram_safe_get("computer_name")); 223 } 224#else 225 p_computer_name = nvram_get("computer_name") && is_valid_netbios_name(nvram_get("computer_name")) ? nvram_get("computer_name") : get_productid(); 226 if (p_computer_name) { 227 fprintf(fp, "netbios name = %s\n", p_computer_name); 228 fprintf(fp, "server string = %s\n", p_computer_name); 229 } 230#endif 231 232 fprintf(fp, "unix charset = UTF8\n"); // ASUS add 233 fprintf(fp, "display charset = UTF8\n"); // ASUS add 234 fprintf(fp, "log file = /var/log.samba\n"); 235 fprintf(fp, "log level = 0\n"); 236 fprintf(fp, "max log size = 5\n"); 237 238 // account mode 239 if(nvram_match("st_samba_mode", "2") || nvram_match("st_samba_mode", "4") 240 || (nvram_match("st_samba_mode", "1") && nvram_get("st_samba_force_mode") == NULL) 241 ){ 242 fprintf(fp, "security = USER\n"); 243 fprintf(fp, "guest ok = no\n"); 244 fprintf(fp, "map to guest = Bad User\n"); 245 } 246 // share mode 247 else if (nvram_match("st_samba_mode", "1") || nvram_match("st_samba_mode", "3")) { 248#if 0 249//#if defined(RTCONFIG_TFAT) || defined(RTCONFIG_TUXERA_NTFS) || defined(RTCONFIG_TUXERA_HFS) 250 if(nvram_get_int("enable_samba_tuxera") == 1){ 251 fprintf(fp, "auth methods = guest\n"); 252 fprintf(fp, "guest account = admin\n"); 253 fprintf(fp, "map to guest = Bad Password\n"); 254 fprintf(fp, "guest ok = yes\n"); 255 } 256 else{ 257 fprintf(fp, "security = SHARE\n"); 258 fprintf(fp, "guest only = yes\n"); 259 } 260#else 261 fprintf(fp, "security = SHARE\n"); 262 fprintf(fp, "guest only = yes\n"); 263#endif 264 } 265 else{ 266 usb_dbg("samba mode: no\n"); 267 goto confpage; 268 } 269 270 fprintf(fp, "encrypt passwords = yes\n"); 271 fprintf(fp, "pam password change = no\n"); 272 fprintf(fp, "null passwords = yes\n"); // ASUS add 273 274 fprintf(fp, "force directory mode = 0777\n"); 275 fprintf(fp, "force create mode = 0777\n"); 276 277 /* max users */ 278 if (strcmp(nvram_safe_get("st_max_user"), "") != 0) 279 fprintf(fp, "max connections = %s\n", nvram_safe_get("st_max_user")); 280 281 /* remove socket options due to NIC compatible issue */ 282 if(!nvram_get_int("stop_samba_speedup")){ 283#ifdef RTCONFIG_BCMARM 284#ifdef RTCONFIG_BCM_7114 285 fprintf(fp, "socket options = IPTOS_LOWDELAY TCP_NODELAY SO_RCVBUF=131072 SO_SNDBUF=131072\n"); 286#endif 287#else 288 fprintf(fp, "socket options = TCP_NODELAY SO_KEEPALIVE SO_RCVBUF=65536 SO_SNDBUF=65536\n"); 289#endif 290 } 291 fprintf(fp, "obey pam restrictions = no\n"); 292 fprintf(fp, "use spnego = no\n"); // ASUS add 293 fprintf(fp, "client use spnego = no\n"); // ASUS add 294// fprintf(fp, "client use spnego = yes\n"); // ASUS add 295 fprintf(fp, "disable spoolss = yes\n"); // ASUS add 296 fprintf(fp, "host msdfs = no\n"); // ASUS add 297 fprintf(fp, "strict allocate = No\n"); // ASUS add 298// fprintf(fp, "mangling method = hash2\n"); // ASUS add 299 fprintf(fp, "wide links = no\n"); // ASUS add 300 fprintf(fp, "bind interfaces only = yes\n"); // ASUS add 301#ifndef RTCONFIG_BCMARM 302 fprintf(fp, "interfaces = lo br0 %s\n", (is_routing_enabled() && nvram_get_int("smbd_wanac")) ? nvram_safe_get("wan0_ifname") : ""); 303#else 304 fprintf(fp, "interfaces = br0 %s/%s %s\n", nvram_safe_get("lan_ipaddr"), nvram_safe_get("lan_netmask"), (is_routing_enabled() && nvram_get_int("smbd_wanac")) ? nvram_safe_get("wan0_ifname") : ""); 305#endif 306#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36) 307 fprintf(fp, "use sendfile = no\n"); 308#else 309 fprintf(fp, "use sendfile = yes\n"); 310#endif 311#ifdef RTCONFIG_RECVFILE 312 if(!nvram_get_int("stop_samba_recv")) 313 fprintf(fp, "use recvfile = yes\n"); 314#endif 315 316 fprintf(fp, "map archive = no\n"); 317 fprintf(fp, "map hidden = no\n"); 318 fprintf(fp, "map read only = no\n"); 319 fprintf(fp, "map system = no\n"); 320 fprintf(fp, "store dos attributes = yes\n"); 321 fprintf(fp, "dos filemode = yes\n"); 322 fprintf(fp, "oplocks = yes\n"); 323 fprintf(fp, "level2 oplocks = yes\n"); 324 fprintf(fp, "kernel oplocks = no\n"); 325 326 disks_info = read_disk_data(); 327 if (disks_info == NULL) { 328 usb_dbg("Couldn't get disk list when writing smb.conf!\n"); 329 goto confpage; 330 } 331 332 /* share */ 333 if (nvram_match("st_samba_mode", "0") || !strcmp(nvram_safe_get("st_samba_mode"), "")) { 334 ; 335 } 336 else if (nvram_match("st_samba_mode", "1") && nvram_match("st_samba_force_mode", "1")) { 337 usb_dbg("samba mode: share\n"); 338 339 for (follow_disk = disks_info; follow_disk != NULL; follow_disk = follow_disk->next) { 340 for (follow_partition = follow_disk->partitions; follow_partition != NULL; follow_partition = follow_partition->next) { 341 if (follow_partition->mount_point == NULL) 342 continue; 343 344 strcpy(unique_share_name, follow_partition->mount_point); 345 do { 346 dup = check_mount_point_icase(disks_info, follow_partition, follow_disk, follow_partition->partition_order, unique_share_name); 347 if (dup) 348 sprintf(unique_share_name, "%s(%d)", follow_partition->mount_point, ++same_m_pt); 349 } while (dup); 350 mount_folder = strrchr(unique_share_name, '/')+1; 351 352 fprintf(fp, "[%s]\n", mount_folder); 353 fprintf(fp, "comment = %s's %s\n", follow_disk->tag, mount_folder); 354 fprintf(fp, "veto files = /.__*.txt*/asusware*/asus_lighttpdpasswd/\n"); 355 fprintf(fp, "path = %s\n", follow_partition->mount_point); 356 fprintf(fp, "writeable = yes\n"); 357 358 fprintf(fp, "dos filetimes = yes\n"); 359 fprintf(fp, "fake directory create times = yes\n"); 360 } 361 } 362 } 363 else if (nvram_match("st_samba_mode", "2")) { 364 usb_dbg("samba mode: share\n"); 365 366 for (follow_disk = disks_info; follow_disk != NULL; follow_disk = follow_disk->next) { 367 for (follow_partition = follow_disk->partitions; follow_partition != NULL; follow_partition = follow_partition->next) { 368 if (follow_partition->mount_point == NULL) 369 continue; 370 371 strcpy(unique_share_name, follow_partition->mount_point); 372 do { 373 dup = check_mount_point_icase(disks_info, follow_partition, follow_disk, follow_partition->partition_order, unique_share_name); 374 if (dup) 375 sprintf(unique_share_name, "%s(%d)", follow_partition->mount_point, ++same_m_pt); 376 } while (dup); 377 mount_folder = strrchr(unique_share_name, '/')+1; 378 379 node_layer = get_permission(NULL, follow_partition->mount_point, NULL, "cifs"); 380 if(node_layer == 3){ 381 fprintf(fp, "[%s]\n", mount_folder); 382 fprintf(fp, "comment = %s's %s\n", follow_disk->tag, mount_folder); 383 fprintf(fp, "path = %s\n", follow_partition->mount_point); 384 fprintf(fp, "writeable = yes\n"); 385 386 fprintf(fp, "dos filetimes = yes\n"); 387 fprintf(fp, "fake directory create times = yes\n"); 388 } 389 else{ 390 //result = get_all_folder(follow_partition->mount_point, &sh_num, &folder_list); 391 result = get_folder_list(follow_partition->mount_point, &sh_num, &folder_list); 392 if (result < 0){ 393 free_2_dimension_list(&sh_num, &folder_list); 394 continue; 395 } 396 397 for (n = 0; n < sh_num; ++n){ 398 samba_right = get_permission(NULL, follow_partition->mount_point, folder_list[n], "cifs"); 399 if (samba_right < 0 || samba_right > 3) 400 samba_right = DEFAULT_SAMBA_RIGHT; 401 402 if(samba_right > 0){ 403 int count = get_list_strings_count(folder_list, sh_num, folder_list[n]); 404 if (count <= 1) 405 fprintf(fp, "[%s]\n", folder_list[n]); 406 else 407 fprintf(fp, "[%s (at %s)]\n", folder_list[n], mount_folder); 408 fprintf(fp, "comment = %s's %s in %s\n", mount_folder, folder_list[n], follow_disk->tag); 409 fprintf(fp, "path = %s/%s\n", follow_partition->mount_point, folder_list[n]); 410 if(samba_right == 3) 411 fprintf(fp, "writeable = yes\n"); 412 else 413 fprintf(fp, "writeable = no\n"); 414 415 fprintf(fp, "dos filetimes = yes\n"); 416 fprintf(fp, "fake directory create times = yes\n"); 417 } 418 } 419 420 free_2_dimension_list(&sh_num, &folder_list); 421 } 422 } 423 } 424 } 425 else if (nvram_match("st_samba_mode", "3")) { 426 usb_dbg("samba mode: user\n"); 427 428 // get the account list 429 if (get_account_list(&acc_num, &account_list) < 0) { 430 usb_dbg("Can't read the account list.\n"); 431 free_2_dimension_list(&acc_num, &account_list); 432 goto confpage; 433 } 434 435 for (follow_disk = disks_info; follow_disk != NULL; follow_disk = follow_disk->next) { 436 for (follow_partition = follow_disk->partitions; follow_partition != NULL; follow_partition = follow_partition->next) { 437 if (follow_partition->mount_point == NULL) 438 continue; 439 440 mount_folder = strrchr(follow_partition->mount_point, '/')+1; 441 442 // 1. get the folder list 443 if (get_folder_list(follow_partition->mount_point, &sh_num, &folder_list) < 0) { 444 free_2_dimension_list(&sh_num, &folder_list); 445 } 446 447 // 2. start to get every share 448 for (n = -1; n < sh_num; ++n) { 449 int i, first; 450 451 if(n == -1){ 452 fprintf(fp, "[%s]\n", mount_folder); 453 fprintf(fp, "comment = %s's %s\n", follow_disk->tag, mount_folder); 454 fprintf(fp, "path = %s\n", follow_partition->mount_point); 455 } 456 else{ 457 int count = get_list_strings_count(folder_list, sh_num, folder_list[n]); 458 if (count <= 1) 459 fprintf(fp, "[%s]\n", folder_list[n]); 460 else 461 fprintf(fp, "[%s (at %s)]\n", folder_list[n], mount_folder); 462 fprintf(fp, "comment = %s's %s in %s\n", mount_folder, folder_list[n], follow_disk->tag); 463 fprintf(fp, "path = %s/%s\n", follow_partition->mount_point, folder_list[n]); 464 } 465 466 fprintf(fp, "dos filetimes = yes\n"); 467 fprintf(fp, "fake directory create times = yes\n"); 468 469 fprintf(fp, "valid users = "); 470 first = 1; 471 for (i = 0; i < acc_num; ++i) { 472 if(n == -1) 473 samba_right = get_permission(account_list[i], follow_partition->mount_point, NULL, "cifs"); 474 else 475 samba_right = get_permission(account_list[i], follow_partition->mount_point, folder_list[n], "cifs"); 476 if (first == 1) 477 first = 0; 478 else 479 fprintf(fp, ", "); 480 481 fprintf(fp, "%s", account_list[i]); 482 } 483 fprintf(fp, "\n"); 484 485 fprintf(fp, "invalid users = "); 486 first = 1; 487 for (i = 0; i < acc_num; ++i) { 488 if(n == -1) 489 samba_right = get_permission(account_list[i], follow_partition->mount_point, NULL, "cifs"); 490 else 491 samba_right = get_permission(account_list[i], follow_partition->mount_point, folder_list[n], "cifs"); 492 if (samba_right >= 1) 493 continue; 494 495 if (first == 1) 496 first = 0; 497 else 498 fprintf(fp, ", "); 499 500 fprintf(fp, "%s", account_list[i]); 501 } 502 fprintf(fp, "\n"); 503 504 fprintf(fp, "read list = "); 505 first = 1; 506 for (i = 0; i < acc_num; ++i) { 507 if(n == -1) 508 samba_right = get_permission(account_list[i], follow_partition->mount_point, NULL, "cifs"); 509 else 510 samba_right = get_permission(account_list[i], follow_partition->mount_point, folder_list[n], "cifs"); 511 if (samba_right < 1) 512 continue; 513 514 if (first == 1) 515 first = 0; 516 else 517 fprintf(fp, ", "); 518 519 fprintf(fp, "%s", account_list[i]); 520 } 521 fprintf(fp, "\n"); 522 523 fprintf(fp, "write list = "); 524 first = 1; 525 for (i = 0; i < acc_num; ++i) { 526 if(n == -1) 527 samba_right = get_permission(account_list[i], follow_partition->mount_point, NULL, "cifs"); 528 else 529 samba_right = get_permission(account_list[i], follow_partition->mount_point, folder_list[n], "cifs"); 530 if (samba_right < 2) 531 continue; 532 533 if (first == 1) 534 first = 0; 535 else 536 fprintf(fp, ", "); 537 538 fprintf(fp, "%s", account_list[i]); 539 } 540 fprintf(fp, "\n"); 541 } 542 543 free_2_dimension_list(&sh_num, &folder_list); 544 } 545 } 546 547 free_2_dimension_list(&acc_num, &account_list); 548 } 549 else if (nvram_match("st_samba_mode", "4") 550 || (nvram_match("st_samba_mode", "1") && nvram_get("st_samba_force_mode") == NULL) 551 ) { 552 usb_dbg("samba mode: user\n"); 553 554 // get the account list 555 if (get_account_list(&acc_num, &account_list) < 0) { 556 usb_dbg("Can't read the account list.\n"); 557 free_2_dimension_list(&acc_num, &account_list); 558 goto confpage; 559 } 560 561 for (follow_disk = disks_info; follow_disk != NULL; follow_disk = follow_disk->next) { 562 for (follow_partition = follow_disk->partitions; follow_partition != NULL; follow_partition = follow_partition->next) { 563 if (follow_partition->mount_point == NULL) 564 continue; 565 566 mount_folder = strrchr(follow_partition->mount_point, '/')+1; 567 568 // 1. get the folder list 569 if (get_folder_list(follow_partition->mount_point, &sh_num, &folder_list) < 0) { 570 free_2_dimension_list(&sh_num, &folder_list); 571 } 572 573 // 2. start to get every share 574 for (n = 0; n < sh_num; ++n) { 575 int i, first; 576 577 int count = get_list_strings_count(folder_list, sh_num, folder_list[n]); 578 if (count <= 1) 579 fprintf(fp, "[%s]\n", folder_list[n]); 580 else 581 fprintf(fp, "[%s (at %s)]\n", folder_list[n], mount_folder); 582 fprintf(fp, "comment = %s's %s in %s\n", mount_folder, folder_list[n], follow_disk->tag); 583 fprintf(fp, "path = %s/%s\n", follow_partition->mount_point, folder_list[n]); 584 585 fprintf(fp, "dos filetimes = yes\n"); 586 fprintf(fp, "fake directory create times = yes\n"); 587 588 fprintf(fp, "valid users = "); 589 first = 1; 590 for (i = 0; i < acc_num; ++i) { 591 if(n == -1) 592 samba_right = get_permission(account_list[i], follow_partition->mount_point, NULL, "cifs"); 593 else 594 samba_right = get_permission(account_list[i], follow_partition->mount_point, folder_list[n], "cifs"); 595 if (first == 1) 596 first = 0; 597 else 598 fprintf(fp, ", "); 599 600 fprintf(fp, "%s", account_list[i]); 601 } 602 fprintf(fp, "\n"); 603 604 fprintf(fp, "invalid users = "); 605 first = 1; 606 for (i = 0; i < acc_num; ++i) { 607 samba_right = get_permission(account_list[i], follow_partition->mount_point, folder_list[n], "cifs"); 608 if (samba_right >= 1) 609 continue; 610 611 if (first == 1) 612 first = 0; 613 else 614 fprintf(fp, ", "); 615 616 fprintf(fp, "%s", account_list[i]); 617 } 618 fprintf(fp, "\n"); 619 620 fprintf(fp, "read list = "); 621 first = 1; 622 for (i = 0; i < acc_num; ++i) { 623 samba_right = get_permission(account_list[i], follow_partition->mount_point, folder_list[n], "cifs"); 624 if (samba_right < 1) 625 continue; 626 627 if (first == 1) 628 first = 0; 629 else 630 fprintf(fp, ", "); 631 632 fprintf(fp, "%s", account_list[i]); 633 } 634 fprintf(fp, "\n"); 635 636 fprintf(fp, "write list = "); 637 first = 1; 638 for (i = 0; i < acc_num; ++i) { 639 samba_right = get_permission(account_list[i], follow_partition->mount_point, folder_list[n], "cifs"); 640 if (samba_right < 2) 641 continue; 642 643 if (first == 1) 644 first = 0; 645 else 646 fprintf(fp, ", "); 647 648 fprintf(fp, "%s", account_list[i]); 649 } 650 fprintf(fp, "\n"); 651 } 652 653 free_2_dimension_list(&sh_num, &folder_list); 654 } 655 } 656 657 free_2_dimension_list(&acc_num, &account_list); 658 } 659 660confpage: 661 if(fp != NULL) 662 fclose(fp); 663 free_disk_data(&disks_info); 664 return 0; 665} 666