1 /* cmds.c 2 * 3 * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft. 4 * All rights reserved. 5 * 6 */ 7 8 #include "syshdrs.h" 9 10 int 11 FTPChdir(const FTPCIPtr cip, const char *const cdCwd) 12 { 13 int result; 14 15 if (cip == NULL) 16 return (kErrBadParameter); 17 if (strcmp(cip->magic, kLibraryMagic)) 18 return (kErrBadMagic); 19 20 if (cdCwd == NULL) { 21 result = kErrInvalidDirParam; 22 cip->errNo = kErrInvalidDirParam; 23 } else { 24 if (cdCwd[0] == '\0') /* But allow FTPChdir(cip, ".") to go through. */ 25 result = 2; 26 else if (strcmp(cdCwd, "..") == 0) 27 result = FTPCmd(cip, "CDUP"); 28 else 29 result = FTPCmd(cip, "CWD %s", cdCwd); 30 if (result >= 0) { 31 if (result == 2) { 32 result = kNoErr; 33 } else { 34 result = kErrCWDFailed; 35 cip->errNo = kErrCWDFailed; 36 } 37 } 38 } 39 return (result); 40 } /* FTPChdir */ 41 42 43 44 45 int 46 FTPChmod(const FTPCIPtr cip, const char *const pattern, const char *const mode, const int doGlob) 47 { 48 LineList fileList; 49 LinePtr filePtr; 50 char *file; 51 int onceResult, batchResult; 52 53 if (cip == NULL) 54 return (kErrBadParameter); 55 if (strcmp(cip->magic, kLibraryMagic)) 56 return (kErrBadMagic); 57 58 batchResult = FTPRemoteGlob(cip, &fileList, pattern, doGlob); 59 if (batchResult != kNoErr) 60 return (batchResult); 61 62 for (batchResult = kNoErr, filePtr = fileList.first; 63 filePtr != NULL; 64 filePtr = filePtr->next) 65 { 66 file = filePtr->line; 67 if (file == NULL) { 68 batchResult = kErrBadLineList; 69 cip->errNo = kErrBadLineList; 70 break; 71 } 72 onceResult = FTPCmd(cip, "SITE CHMOD %s %s", mode, file); 73 if (onceResult < 0) { 74 batchResult = onceResult; 75 break; 76 } 77 if (onceResult != 2) { 78 batchResult = kErrChmodFailed; 79 cip->errNo = kErrChmodFailed; 80 } 81 } 82 DisposeLineListContents(&fileList); 83 return (batchResult); 84 } /* FTPChmod */ 85 86 87 88 89 static int 90 FTPRmdirRecursiveL2(const FTPCIPtr cip) 91 { 92 LineList fileList; 93 LinePtr filePtr; 94 char *file; 95 int result; 96 97 result = FTPRemoteGlob(cip, &fileList, "**", kGlobYes); 98 if (result != kNoErr) { 99 return (result); 100 } 101 102 for (filePtr = fileList.first; 103 filePtr != NULL; 104 filePtr = filePtr->next) 105 { 106 file = filePtr->line; 107 if (file == NULL) { 108 cip->errNo = kErrBadLineList; 109 break; 110 } 111 112 if ((file[0] == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0')))) 113 continue; /* Skip . and .. */ 114 115 if (FTPChdir(cip, file) == kNoErr) { 116 /* It was a directory. 117 * Go in and wax it. 118 */ 119 result = FTPRmdirRecursiveL2(cip); 120 121 if (FTPChdir(cip, "..") != kNoErr) { 122 /* Panic -- we can no longer 123 * cd back to the directory 124 * we were in before. 125 */ 126 result = kErrCannotGoToPrevDir; 127 cip->errNo = kErrCannotGoToPrevDir; 128 return (result); 129 } 130 131 if ((result < 0) && (result != kErrGlobNoMatch)) 132 return (result); 133 134 result = FTPRmdir(cip, file, kRecursiveNo, kGlobNo); 135 if (result != kNoErr) { 136 /* Well, we couldn't remove the empty 137 * directory. Perhaps we screwed up 138 * and the directory wasn't empty. 139 */ 140 return (result); 141 } 142 } else { 143 /* Assume it was a file -- remove it. */ 144 result = FTPDelete(cip, file, kRecursiveNo, kGlobNo); 145 /* Try continuing to remove the rest, 146 * even if this failed. 147 */ 148 } 149 } 150 DisposeLineListContents(&fileList); 151 152 return (result); 153 } /* FTPRmdirRecursiveL2 */ 154 155 156 157 static int 158 FTPRmdirRecursive(const FTPCIPtr cip, const char *const dir) 159 { 160 int result, result2; 161 162 /* Preserve old working directory. */ 163 (void) FTPGetCWD(cip, cip->buf, cip->bufSize); 164 165 result = FTPChdir(cip, dir); 166 if (result != kNoErr) { 167 return (result); 168 } 169 170 result = FTPRmdirRecursiveL2(cip); 171 172 if (FTPChdir(cip, cip->buf) != kNoErr) { 173 /* Could not cd back to the original user directory -- bad. */ 174 if (result != kNoErr) { 175 result = kErrCannotGoToPrevDir; 176 cip->errNo = kErrCannotGoToPrevDir; 177 } 178 return (result); 179 } 180 181 /* Now rmdir the last node, the root of the tree 182 * we just went through. 183 */ 184 result2 = FTPRmdir(cip, dir, kRecursiveNo, kGlobNo); 185 if ((result2 != kNoErr) && (result == kNoErr)) 186 result = result2; 187 188 return (result); 189 } /* FTPRmdirRecursive */ 190 191 192 193 194 int 195 FTPDelete(const FTPCIPtr cip, const char *const pattern, const int recurse, const int doGlob) 196 { 197 LineList fileList; 198 LinePtr filePtr; 199 char *file; 200 int onceResult, batchResult; 201 202 if (cip == NULL) 203 return (kErrBadParameter); 204 if (strcmp(cip->magic, kLibraryMagic)) 205 return (kErrBadMagic); 206 207 batchResult = FTPRemoteGlob(cip, &fileList, pattern, doGlob); 208 if (batchResult != kNoErr) 209 return (batchResult); 210 211 for (batchResult = kNoErr, filePtr = fileList.first; 212 filePtr != NULL; 213 filePtr = filePtr->next) 214 { 215 file = filePtr->line; 216 if (file == NULL) { 217 batchResult = kErrBadLineList; 218 cip->errNo = kErrBadLineList; 219 break; 220 } 221 onceResult = FTPCmd(cip, "DELE %s", file); 222 if (onceResult < 0) { 223 batchResult = onceResult; 224 break; 225 } 226 if (onceResult != 2) { 227 if (recurse != kRecursiveYes) { 228 batchResult = kErrDELEFailed; 229 cip->errNo = kErrDELEFailed; 230 } else { 231 onceResult = FTPCmd(cip, "RMD %s", file); 232 if (onceResult < 0) { 233 batchResult = onceResult; 234 break; 235 } 236 if (onceResult != 2) { 237 onceResult = FTPRmdirRecursive(cip, file); 238 if (onceResult < 0) { 239 batchResult = kErrRMDFailed; 240 cip->errNo = kErrRMDFailed; 241 } 242 } 243 } 244 } 245 } 246 DisposeLineListContents(&fileList); 247 return (batchResult); 248 } /* FTPDelete */ 249 250 251 252 253 int 254 FTPGetCWD(const FTPCIPtr cip, char *const newCwd, const size_t newCwdSize) 255 { 256 ResponsePtr rp; 257 char *l, *r; 258 int result; 259 260 if (cip == NULL) 261 return (kErrBadParameter); 262 if (strcmp(cip->magic, kLibraryMagic)) 263 return (kErrBadMagic); 264 265 if ((newCwd == NULL) || (newCwdSize == 0)) { 266 result = kErrInvalidDirParam; 267 cip->errNo = kErrInvalidDirParam; 268 } else { 269 rp = InitResponse(); 270 if (rp == NULL) { 271 result = kErrMallocFailed; 272 cip->errNo = kErrMallocFailed; 273 Error(cip, kDontPerror, "Malloc failed.\n"); 274 } else { 275 result = RCmd(cip, rp, "PWD"); 276 if (result == 2) { 277 if ((r = strrchr(rp->msg.first->line, '"')) != NULL) { 278 /* "xxxx" is current directory. 279 * Strip out just the xxxx to copy into the remote cwd. 280 */ 281 l = strchr(rp->msg.first->line, '"'); 282 if ((l != NULL) && (l != r)) { 283 *r = '\0'; 284 ++l; 285 (void) Strncpy(newCwd, l, newCwdSize); 286 *r = '"'; /* Restore, so response prints correctly. */ 287 } 288 } else { 289 /* xxxx is current directory. 290 * Mostly for VMS. 291 */ 292 if ((r = strchr(rp->msg.first->line, ' ')) != NULL) { 293 *r = '\0'; 294 (void) Strncpy(newCwd, (rp->msg.first->line), newCwdSize); 295 *r = ' '; /* Restore, so response prints correctly. */ 296 } 297 } 298 result = kNoErr; 299 } else if (result > 0) { 300 result = kErrPWDFailed; 301 cip->errNo = kErrPWDFailed; 302 } 303 DoneWithResponse(cip, rp); 304 } 305 } 306 return (result); 307 } /* FTPGetCWD */ 308 309 310 311 312 int 313 FTPChdirAndGetCWD(const FTPCIPtr cip, const char *const cdCwd, char *const newCwd, const size_t newCwdSize) 314 { 315 ResponsePtr rp; 316 char *l, *r; 317 int result; 318 319 if (cip == NULL) 320 return (kErrBadParameter); 321 if (strcmp(cip->magic, kLibraryMagic)) 322 return (kErrBadMagic); 323 324 if ((newCwd == NULL) || (cdCwd == NULL)) { 325 result = kErrInvalidDirParam; 326 cip->errNo = kErrInvalidDirParam; 327 } else { 328 if (cdCwd[0] == '\0') { /* But allow FTPChdir(cip, ".") to go through. */ 329 result = FTPGetCWD(cip, newCwd, newCwdSize); 330 return (result); 331 } 332 rp = InitResponse(); 333 if (rp == NULL) { 334 result = kErrMallocFailed; 335 cip->errNo = kErrMallocFailed; 336 Error(cip, kDontPerror, "Malloc failed.\n"); 337 } else { 338 if (strcmp(cdCwd, "..") == 0) 339 result = RCmd(cip, rp, "CDUP"); 340 else 341 result = RCmd(cip, rp, "CWD %s", cdCwd); 342 if (result == 2) { 343 l = strchr(rp->msg.first->line, '"'); 344 if ((l == rp->msg.first->line) && ((r = strrchr(rp->msg.first->line, '"')) != NULL) && (l != r)) { 345 /* "xxxx" is current directory. 346 * Strip out just the xxxx to copy into the remote cwd. 347 * 348 * This is nice because we didn't have to do a PWD. 349 */ 350 *r = '\0'; 351 ++l; 352 (void) Strncpy(newCwd, l, newCwdSize); 353 *r = '"'; /* Restore, so response prints correctly. */ 354 DoneWithResponse(cip, rp); 355 result = kNoErr; 356 } else { 357 DoneWithResponse(cip, rp); 358 result = FTPGetCWD(cip, newCwd, newCwdSize); 359 } 360 } else if (result > 0) { 361 result = kErrCWDFailed; 362 cip->errNo = kErrCWDFailed; 363 DoneWithResponse(cip, rp); 364 } else { 365 DoneWithResponse(cip, rp); 366 } 367 } 368 } 369 return (result); 370 } /* FTPChdirAndGetCWD */ 371 372 373 374 375 int 376 FTPChdir3(FTPCIPtr cip, const char *const cdCwd, char *const newCwd, const size_t newCwdSize, int flags) 377 { 378 char *cp, *startcp; 379 int result; 380 int lastSubDir; 381 int mkd, pwd; 382 383 if (cip == NULL) 384 return (kErrBadParameter); 385 if (strcmp(cip->magic, kLibraryMagic)) 386 return (kErrBadMagic); 387 388 if (cdCwd == NULL) { 389 result = kErrInvalidDirParam; 390 cip->errNo = kErrInvalidDirParam; 391 return result; 392 } 393 394 if (flags == kChdirOnly) 395 return (FTPChdir(cip, cdCwd)); 396 if (flags == kChdirAndGetCWD) { 397 return (FTPChdirAndGetCWD(cip, cdCwd, newCwd, newCwdSize)); 398 } else if (flags == kChdirAndMkdir) { 399 result = FTPMkdir(cip, cdCwd, kRecursiveYes); 400 if (result == kNoErr) 401 result = FTPChdir(cip, cdCwd); 402 return result; 403 } else if (flags == (kChdirAndMkdir|kChdirAndGetCWD)) { 404 result = FTPMkdir(cip, cdCwd, kRecursiveYes); 405 if (result == kNoErr) 406 result = FTPChdirAndGetCWD(cip, cdCwd, newCwd, newCwdSize); 407 return result; 408 } 409 410 /* else: (flags | kChdirOneSubdirAtATime) == true */ 411 412 cp = cip->buf; 413 cp[cip->bufSize - 1] = '\0'; 414 (void) Strncpy(cip->buf, cdCwd, cip->bufSize); 415 if (cp[cip->bufSize - 1] != '\0') 416 return (kErrBadParameter); 417 418 mkd = (flags & kChdirAndMkdir); 419 pwd = (flags & kChdirAndGetCWD); 420 421 if ((cdCwd[0] == '\0') || (strcmp(cdCwd, ".") == 0)) { 422 result = 0; 423 if (flags == kChdirAndGetCWD) 424 result = FTPGetCWD(cip, newCwd, newCwdSize); 425 return (result); 426 } 427 428 lastSubDir = 0; 429 do { 430 startcp = cp; 431 cp = StrFindLocalPathDelim(cp); 432 if (cp != NULL) { 433 /* If this is the first slash in an absolute 434 * path, then startcp will be empty. We will 435 * use this below to treat this as the root 436 * directory. 437 */ 438 *cp++ = '\0'; 439 } else { 440 lastSubDir = 1; 441 } 442 if (strcmp(startcp, ".") == 0) { 443 result = 0; 444 if ((lastSubDir != 0) && (pwd != 0)) 445 result = FTPGetCWD(cip, newCwd, newCwdSize); 446 } else if ((lastSubDir != 0) && (pwd != 0)) { 447 result = FTPChdirAndGetCWD(cip, (*startcp != '\0') ? startcp : "/", newCwd, newCwdSize); 448 } else { 449 result = FTPChdir(cip, (*startcp != '\0') ? startcp : "/"); 450 } 451 if (result < 0) { 452 if ((mkd != 0) && (*startcp != '\0')) { 453 if (FTPCmd(cip, "MKD %s", startcp) == 2) { 454 result = FTPChdir(cip, startcp); 455 } else { 456 /* couldn't change nor create */ 457 cip->errNo = result; 458 } 459 } else { 460 cip->errNo = result; 461 } 462 } 463 } while ((!lastSubDir) && (result == 0)); 464 465 return (result); 466 } /* FTPChdir3 */ 467 468 469 470 471 int 472 FTPMkdir2(const FTPCIPtr cip, const char *const newDir, const int recurse, const char *const curDir) 473 { 474 int result, result2; 475 char *cp, *newTreeStart, *cp2; 476 char dir[512]; 477 char dir2[512]; 478 char c; 479 480 if (cip == NULL) 481 return (kErrBadParameter); 482 if (strcmp(cip->magic, kLibraryMagic)) 483 return (kErrBadMagic); 484 485 if ((newDir == NULL) || (newDir[0] == '\0')) { 486 cip->errNo = kErrInvalidDirParam; 487 return (kErrInvalidDirParam); 488 } 489 490 /* Preserve old working directory. */ 491 if ((curDir == NULL) || (curDir[0] == '\0')) { 492 /* This hack is nice so you can eliminate an 493 * unnecessary "PWD" command on the server, 494 * since if you already knew what directory 495 * you're in. We want to minimize the number 496 * of client-server exchanges when feasible. 497 */ 498 (void) FTPGetCWD(cip, cip->buf, cip->bufSize); 499 } 500 501 result = FTPChdir(cip, newDir); 502 if (result == kNoErr) { 503 /* Directory already exists -- but we 504 * must now change back to where we were. 505 */ 506 result2 = FTPChdir(cip, ((curDir == NULL) || (curDir[0] == '\0')) ? cip->buf : curDir); 507 if (result2 < 0) { 508 result = kErrCannotGoToPrevDir; 509 cip->errNo = kErrCannotGoToPrevDir; 510 return (result); 511 } 512 513 /* Don't need to create it. */ 514 return (kNoErr); 515 } 516 517 if (recurse == kRecursiveNo) { 518 result = FTPCmd(cip, "MKD %s", newDir); 519 if (result > 0) { 520 if (result != 2) { 521 Error(cip, kDontPerror, "MKD %s failed; [%s]\n", newDir, cip->lastFTPCmdResultStr); 522 result = kErrMKDFailed; 523 cip->errNo = kErrMKDFailed; 524 return (result); 525 } else { 526 result = kNoErr; 527 } 528 } 529 } else { 530 (void) STRNCPY(dir, newDir); 531 532 /* Strip trailing slashes. */ 533 cp = dir + strlen(dir) - 1; 534 for (;;) { 535 if (cp <= dir) { 536 if ((newDir == NULL) || (newDir[0] == '\0')) { 537 cip->errNo = kErrInvalidDirParam; 538 result = kErrInvalidDirParam; 539 return (result); 540 } 541 } 542 if ((*cp != '/') && (*cp != '\\')) { 543 cp[1] = '\0'; 544 break; 545 } 546 --cp; 547 } 548 (void) STRNCPY(dir2, dir); 549 550 if ((strrchr(dir, '/') == dir) || (strrchr(dir, '\\') == dir)) { 551 /* Special case "mkdir /subdir" */ 552 result = FTPCmd(cip, "MKD %s", dir); 553 if (result < 0) { 554 return (result); 555 } 556 if (result != 2) { 557 Error(cip, kDontPerror, "MKD %s failed; [%s]\n", dir, cip->lastFTPCmdResultStr); 558 result = kErrMKDFailed; 559 cip->errNo = kErrMKDFailed; 560 return (result); 561 } 562 /* Haven't chdir'ed, don't need to goto goback. */ 563 return (kNoErr); 564 } 565 566 for (;;) { 567 cp = strrchr(dir, '/'); 568 if (cp == NULL) 569 cp = strrchr(dir, '\\'); 570 if (cp == NULL) { 571 cp = dir + strlen(dir) - 1; 572 if (dir[0] == '\0') { 573 result = kErrMKDFailed; 574 cip->errNo = kErrMKDFailed; 575 return (result); 576 } 577 /* Note: below we will refer to cp + 1 578 * which is why we set cp to point to 579 * the byte before the array begins! 580 */ 581 cp = dir - 1; 582 break; 583 } 584 if (cp == dir) { 585 result = kErrMKDFailed; 586 cip->errNo = kErrMKDFailed; 587 return (result); 588 } 589 *cp = '\0'; 590 result = FTPChdir(cip, dir); 591 if (result == 0) { 592 break; /* Found a valid parent dir. */ 593 /* from this point, we need to preserve old dir. */ 594 } 595 } 596 597 newTreeStart = dir2 + ((cp + 1) - dir); 598 for (cp = newTreeStart; ; ) { 599 cp2 = cp; 600 cp = strchr(cp2, '/'); 601 c = '/'; 602 if (cp == NULL) 603 cp = strchr(cp2, '\\'); 604 if (cp != NULL) { 605 c = *cp; 606 *cp = '\0'; 607 if (cp[1] == '\0') { 608 /* Done, if they did "mkdir /tmp/dir/" */ 609 break; 610 } 611 } 612 result = FTPCmd(cip, "MKD %s", newTreeStart); 613 if (result < 0) { 614 return (result); 615 } 616 if (result != 2) { 617 Error(cip, kDontPerror, "Cwd=%s; MKD %s failed; [%s]\n", cip->buf, newTreeStart, cip->lastFTPCmdResultStr); 618 result = kErrMKDFailed; 619 cip->errNo = kErrMKDFailed; 620 goto goback; 621 } 622 if (cp == NULL) 623 break; /* No more to make, done. */ 624 *cp++ = c; 625 } 626 result = kNoErr; 627 628 goback: 629 result2 = FTPChdir(cip, ((curDir == NULL) || (curDir[0] == '\0')) ? cip->buf : curDir); 630 if ((result == 0) && (result2 < 0)) { 631 result = kErrCannotGoToPrevDir; 632 cip->errNo = kErrCannotGoToPrevDir; 633 } 634 } 635 return (result); 636 } /* FTPMkdir2 */ 637 638 639 640 int 641 FTPMkdir(const FTPCIPtr cip, const char *const newDir, const int recurse) 642 { 643 return (FTPMkdir2(cip, newDir, recurse, NULL)); 644 } /* FTPMkdir */ 645 646 647 648 int 649 FTPFileModificationTime(const FTPCIPtr cip, const char *const file, time_t *const mdtm) 650 { 651 int result; 652 ResponsePtr rp; 653 654 if (cip == NULL) 655 return (kErrBadParameter); 656 if (strcmp(cip->magic, kLibraryMagic)) 657 return (kErrBadMagic); 658 659 if ((mdtm == NULL) || (file == NULL)) 660 return (kErrBadParameter); 661 *mdtm = kModTimeUnknown; 662 663 if (cip->hasMDTM == kCommandNotAvailable) { 664 cip->errNo = kErrMDTMNotAvailable; 665 result = kErrMDTMNotAvailable; 666 } else { 667 rp = InitResponse(); 668 if (rp == NULL) { 669 result = kErrMallocFailed; 670 cip->errNo = kErrMallocFailed; 671 Error(cip, kDontPerror, "Malloc failed.\n"); 672 } else { 673 result = RCmd(cip, rp, "MDTM %s", file); 674 if (result < 0) { 675 DoneWithResponse(cip, rp); 676 return (result); 677 } else if (strncmp(rp->msg.first->line, "19100", 5) == 0) { 678 Error(cip, kDontPerror, "Warning: Server has Y2K Bug in \"MDTM\" command.\n"); 679 cip->errNo = kErrMDTMFailed; 680 result = kErrMDTMFailed; 681 } else if (result == 2) { 682 *mdtm = UnMDTMDate(rp->msg.first->line); 683 cip->hasMDTM = kCommandAvailable; 684 result = kNoErr; 685 } else if (UNIMPLEMENTED_CMD(rp->code)) { 686 cip->hasMDTM = kCommandNotAvailable; 687 cip->errNo = kErrMDTMNotAvailable; 688 result = kErrMDTMNotAvailable; 689 } else { 690 cip->errNo = kErrMDTMFailed; 691 result = kErrMDTMFailed; 692 } 693 DoneWithResponse(cip, rp); 694 } 695 } 696 return (result); 697 } /* FTPFileModificationTime */ 698 699 700 701 702 int 703 FTPRename(const FTPCIPtr cip, const char *const oldname, const char *const newname) 704 { 705 int result; 706 707 if (cip == NULL) 708 return (kErrBadParameter); 709 if (strcmp(cip->magic, kLibraryMagic)) 710 return (kErrBadMagic); 711 if ((oldname == NULL) || (oldname[0] == '\0')) 712 return (kErrBadParameter); 713 if ((newname == NULL) || (oldname[0] == '\0')) 714 return (kErrBadParameter); 715 716 717 result = FTPCmd(cip, "RNFR %s", oldname); 718 if (result < 0) 719 return (result); 720 if (result != 3) { 721 cip->errNo = kErrRenameFailed; 722 return (cip->errNo); 723 } 724 725 result = FTPCmd(cip, "RNTO %s", newname); 726 if (result < 0) 727 return (result); 728 if (result != 2) { 729 cip->errNo = kErrRenameFailed; 730 return (cip->errNo); 731 } 732 return (kNoErr); 733 } /* FTPRename */ 734 735 736 737 738 int 739 FTPRemoteHelp(const FTPCIPtr cip, const char *const pattern, const LineListPtr llp) 740 { 741 int result; 742 ResponsePtr rp; 743 744 if ((cip == NULL) || (llp == NULL)) 745 return (kErrBadParameter); 746 if (strcmp(cip->magic, kLibraryMagic)) 747 return (kErrBadMagic); 748 749 InitLineList(llp); 750 rp = InitResponse(); 751 if (rp == NULL) { 752 result = kErrMallocFailed; 753 cip->errNo = kErrMallocFailed; 754 Error(cip, kDontPerror, "Malloc failed.\n"); 755 } else { 756 if ((pattern == NULL) || (*pattern == '\0')) 757 result = RCmd(cip, rp, "HELP"); 758 else 759 result = RCmd(cip, rp, "HELP %s", pattern); 760 if (result < 0) { 761 DoneWithResponse(cip, rp); 762 return (result); 763 } else if (result == 2) { 764 if (CopyLineList(llp, &rp->msg) < 0) { 765 result = kErrMallocFailed; 766 cip->errNo = kErrMallocFailed; 767 Error(cip, kDontPerror, "Malloc failed.\n"); 768 } else { 769 result = kNoErr; 770 } 771 } else { 772 cip->errNo = kErrHELPFailed; 773 result = kErrHELPFailed; 774 } 775 DoneWithResponse(cip, rp); 776 } 777 return (result); 778 } /* FTPRemoteHelp */ 779 780 781 782 783 int 784 FTPRmdir(const FTPCIPtr cip, const char *const pattern, const int recurse, const int doGlob) 785 { 786 LineList fileList; 787 LinePtr filePtr; 788 char *file; 789 int onceResult, batchResult; 790 791 if (cip == NULL) 792 return (kErrBadParameter); 793 if (strcmp(cip->magic, kLibraryMagic)) 794 return (kErrBadMagic); 795 796 batchResult = FTPRemoteGlob(cip, &fileList, pattern, doGlob); 797 if (batchResult != kNoErr) 798 return (batchResult); 799 800 for (batchResult = kNoErr, filePtr = fileList.first; 801 filePtr != NULL; 802 filePtr = filePtr->next) 803 { 804 file = filePtr->line; 805 if (file == NULL) { 806 batchResult = kErrBadLineList; 807 cip->errNo = kErrBadLineList; 808 break; 809 } 810 onceResult = FTPCmd(cip, "RMD %s", file); 811 if (onceResult < 0) { 812 batchResult = onceResult; 813 break; 814 } 815 if (onceResult != 2) { 816 if (recurse == kRecursiveYes) { 817 onceResult = FTPRmdirRecursive(cip, file); 818 if (onceResult < 0) { 819 batchResult = kErrRMDFailed; 820 cip->errNo = kErrRMDFailed; 821 } 822 } else { 823 batchResult = kErrRMDFailed; 824 cip->errNo = kErrRMDFailed; 825 } 826 } 827 } 828 DisposeLineListContents(&fileList); 829 return (batchResult); 830 } /* FTPRmdir */ 831 832 833 834 835 int 836 FTPSetTransferType(const FTPCIPtr cip, int type) 837 { 838 int result; 839 840 if (cip == NULL) 841 return (kErrBadParameter); 842 if (strcmp(cip->magic, kLibraryMagic)) 843 return (kErrBadMagic); 844 845 if (cip->curTransferType != type) { 846 switch (type) { 847 case kTypeAscii: 848 case kTypeBinary: 849 case kTypeEbcdic: 850 break; 851 case 'i': 852 case 'b': 853 case 'B': 854 type = kTypeBinary; 855 break; 856 case 'e': 857 type = kTypeEbcdic; 858 break; 859 case 'a': 860 type = kTypeAscii; 861 break; 862 default: 863 /* Yeah, we don't support Tenex. Who cares? */ 864 Error(cip, kDontPerror, "Bad transfer type [%c].\n", type); 865 cip->errNo = kErrBadTransferType; 866 return (kErrBadTransferType); 867 } 868 result = FTPCmd(cip, "TYPE %c", type); 869 if (result != 2) { 870 result = kErrTYPEFailed; 871 cip->errNo = kErrTYPEFailed; 872 return (result); 873 } 874 cip->curTransferType = type; 875 } 876 return (kNoErr); 877 } /* FTPSetTransferType */ 878 879 880 881 882 /* If the remote host supports the SIZE command, we can find out the exact 883 * size of a remote file, depending on the transfer type in use. SIZE 884 * returns different values for ascii and binary modes! 885 */ 886 int 887 FTPFileSize(const FTPCIPtr cip, const char *const file, longest_int *const size, const int type) 888 { 889 int result; 890 ResponsePtr rp; 891 892 if (cip == NULL) 893 return (kErrBadParameter); 894 if (strcmp(cip->magic, kLibraryMagic)) 895 return (kErrBadMagic); 896 897 if ((size == NULL) || (file == NULL)) 898 return (kErrBadParameter); 899 *size = kSizeUnknown; 900 901 result = FTPSetTransferType(cip, type); 902 if (result < 0) 903 return (result); 904 905 if (cip->hasSIZE == kCommandNotAvailable) { 906 cip->errNo = kErrSIZENotAvailable; 907 result = kErrSIZENotAvailable; 908 } else { 909 rp = InitResponse(); 910 if (rp == NULL) { 911 result = kErrMallocFailed; 912 cip->errNo = kErrMallocFailed; 913 Error(cip, kDontPerror, "Malloc failed.\n"); 914 } else { 915 result = RCmd(cip, rp, "SIZE %s", file); 916 if (result < 0) { 917 DoneWithResponse(cip, rp); 918 return (result); 919 } else if (result == 2) { 920 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG) 921 (void) sscanf(rp->msg.first->line, SCANF_LONG_LONG, size); 922 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ) 923 *size = (longest_int) strtoq(rp->msg.first->line, NULL, 0); 924 #else 925 (void) sscanf(rp->msg.first->line, "%ld", size); 926 #endif 927 cip->hasSIZE = kCommandAvailable; 928 result = kNoErr; 929 } else if (UNIMPLEMENTED_CMD(rp->code)) { 930 cip->hasSIZE = kCommandNotAvailable; 931 cip->errNo = kErrSIZENotAvailable; 932 result = kErrSIZENotAvailable; 933 } else { 934 cip->errNo = kErrSIZEFailed; 935 result = kErrSIZEFailed; 936 } 937 DoneWithResponse(cip, rp); 938 } 939 } 940 return (result); 941 } /* FTPFileSize */ 942 943 944 945 946 int 947 FTPMListOneFile(const FTPCIPtr cip, const char *const file, const MLstItemPtr mlip) 948 { 949 int result; 950 ResponsePtr rp; 951 952 /* We do a special check for older versions of NcFTPd which 953 * are based off of an incompatible previous version of IETF 954 * extensions. 955 * 956 * Roxen also seems to be way outdated, where MLST was on the 957 * data connection among other things. 958 * 959 */ 960 if ( 961 (cip->hasMLST == kCommandNotAvailable) || 962 ((cip->serverType == kServerTypeNcFTPd) && (cip->ietfCompatLevel < 19981201)) || 963 (cip->serverType == kServerTypeRoxen) 964 ) { 965 cip->errNo = kErrMLSTNotAvailable; 966 return (cip->errNo); 967 } 968 969 rp = InitResponse(); 970 if (rp == NULL) { 971 result = cip->errNo = kErrMallocFailed; 972 Error(cip, kDontPerror, "Malloc failed.\n"); 973 } else { 974 result = RCmd(cip, rp, "MLST %s", file); 975 if ( 976 (result == 2) && 977 (rp->msg.first->line != NULL) && 978 (rp->msg.first->next != NULL) && 979 (rp->msg.first->next->line != NULL) 980 ) { 981 result = UnMlsT(rp->msg.first->next->line, mlip); 982 if (result < 0) { 983 cip->errNo = result = kErrInvalidMLSTResponse; 984 } 985 } else if (UNIMPLEMENTED_CMD(rp->code)) { 986 cip->hasMLST = kCommandNotAvailable; 987 cip->errNo = kErrMLSTNotAvailable; 988 result = kErrMLSTNotAvailable; 989 } else { 990 cip->errNo = kErrMLSTFailed; 991 result = kErrMLSTFailed; 992 } 993 DoneWithResponse(cip, rp); 994 } 995 996 return (result); 997 } /* FTPMListOneFile */ 998 999 1000 1001 1002 /* We only use STAT to see if files or directories exist. 1003 * But since it is so rarely used in the wild, we need to 1004 * make sure the server supports the use where we pass 1005 * a pathname as a parameter. 1006 */ 1007 int 1008 FTPFileExistsStat(const FTPCIPtr cip, const char *const file) 1009 { 1010 int result; 1011 ResponsePtr rp; 1012 LineList fileList; 1013 char savedCwd[512]; 1014 1015 if (cip == NULL) 1016 return (kErrBadParameter); 1017 if (strcmp(cip->magic, kLibraryMagic)) 1018 return (kErrBadMagic); 1019 1020 if (file == NULL) 1021 return (kErrBadParameter); 1022 1023 if (cip->STATfileParamWorks == kCommandNotAvailable) { 1024 cip->errNo = result = kErrSTATwithFileNotAvailable; 1025 return (result); 1026 } 1027 1028 if (cip->STATfileParamWorks == kCommandAvailabilityUnknown) { 1029 rp = InitResponse(); 1030 if (rp == NULL) { 1031 result = kErrMallocFailed; 1032 cip->errNo = kErrMallocFailed; 1033 Error(cip, kDontPerror, "Malloc failed.\n"); 1034 return (result); 1035 1036 } 1037 1038 /* First, make sure that when we STAT a pathname 1039 * that does not exist, that we get an error back. 1040 * 1041 * We also assume that a valid STAT response has 1042 * at least 3 lines of response text, typically 1043 * a "start" line, intermediate data, and then 1044 * a trailing line. 1045 * 1046 * We also can see a one-line case. 1047 */ 1048 result = RCmd(cip, rp, "STAT %s", "NoSuchFile"); 1049 if ((result == 2) && ((rp->msg.nLines >= 3) || (rp->msg.nLines == 1))) { 1050 /* Hmmm.... it gave back a positive 1051 * response. So STAT <file> does not 1052 * work correctly. 1053 */ 1054 if ( 1055 (rp->msg.first->next != NULL) && 1056 (rp->msg.first->next->line != NULL) && 1057 ( 1058 (strstr(rp->msg.first->next->line, "o such file") != NULL) || 1059 (strstr(rp->msg.first->next->line, "ot found") != NULL) 1060 ) 1061 ) { 1062 /* OK, while we didn't want a 200 1063 * level response, some servers, 1064 * like wu-ftpd print an error 1065 * message "No such file or 1066 * directory" which we can special 1067 * case. 1068 */ 1069 result = kNoErr; 1070 } else { 1071 cip->STATfileParamWorks = kCommandNotAvailable; 1072 cip->errNo = result = kErrSTATwithFileNotAvailable; 1073 DoneWithResponse(cip, rp); 1074 return (result); 1075 } 1076 } 1077 DoneWithResponse(cip, rp); 1078 1079 /* We can't assume that we can simply say STAT rootdir/firstfile, 1080 * since the remote host may not be using / as a directory 1081 * delimiter. So we have to change to the root directory 1082 * and then do the STAT on that file. 1083 */ 1084 if ( 1085 (FTPGetCWD(cip, savedCwd, sizeof(savedCwd)) != kNoErr) || 1086 (FTPChdir(cip, cip->startingWorkingDirectory) != kNoErr) 1087 ) { 1088 return (cip->errNo); 1089 } 1090 1091 /* OK, we get an error when we stat 1092 * a non-existant file, but now we need to 1093 * see if we get a positive reply when 1094 * we stat a file that does exist. 1095 * 1096 * To do this, we list the root directory, 1097 * which we assume has one or more items. 1098 * If it doesn't, the user can't do anything 1099 * anyway. Then we stat the first item 1100 * we found to see if STAT says it exists. 1101 */ 1102 if ( 1103 ((result = FTPListToMemory2(cip, "", &fileList, "", 0, (int *) 0)) < 0) || 1104 (fileList.last == NULL) || 1105 (fileList.last->line == NULL) 1106 ) { 1107 /* Hmmm... well, in any case we can't use STAT. */ 1108 cip->STATfileParamWorks = kCommandNotAvailable; 1109 cip->errNo = result = kErrSTATwithFileNotAvailable; 1110 DisposeLineListContents(&fileList); 1111 (void) FTPChdir(cip, savedCwd); 1112 return (result); 1113 } 1114 1115 rp = InitResponse(); 1116 if (rp == NULL) { 1117 result = kErrMallocFailed; 1118 cip->errNo = kErrMallocFailed; 1119 Error(cip, kDontPerror, "Malloc failed.\n"); 1120 DisposeLineListContents(&fileList); 1121 (void) FTPChdir(cip, savedCwd); 1122 return (result); 1123 1124 } 1125 1126 result = RCmd(cip, rp, "STAT %s", fileList.last->line); 1127 DisposeLineListContents(&fileList); 1128 1129 if ((result != 2) || (rp->msg.nLines == 2)) { 1130 /* Hmmm.... it gave back a negative 1131 * response. So STAT <file> does not 1132 * work correctly. 1133 */ 1134 cip->STATfileParamWorks = kCommandNotAvailable; 1135 cip->errNo = result = kErrSTATwithFileNotAvailable; 1136 DoneWithResponse(cip, rp); 1137 (void) FTPChdir(cip, savedCwd); 1138 return (result); 1139 } else if ( 1140 (rp->msg.first->next != NULL) && 1141 (rp->msg.first->next->line != NULL) && 1142 ( 1143 (strstr(rp->msg.first->next->line, "o such file") != NULL) || 1144 (strstr(rp->msg.first->next->line, "ot found") != NULL) 1145 ) 1146 ) { 1147 /* Same special-case of the second line of STAT response. */ 1148 cip->STATfileParamWorks = kCommandNotAvailable; 1149 cip->errNo = result = kErrSTATwithFileNotAvailable; 1150 DoneWithResponse(cip, rp); 1151 (void) FTPChdir(cip, savedCwd); 1152 return (result); 1153 } 1154 DoneWithResponse(cip, rp); 1155 cip->STATfileParamWorks = kCommandAvailable; 1156 1157 /* Don't forget to change back to the original directory. */ 1158 (void) FTPChdir(cip, savedCwd); 1159 } 1160 1161 rp = InitResponse(); 1162 if (rp == NULL) { 1163 result = kErrMallocFailed; 1164 cip->errNo = kErrMallocFailed; 1165 Error(cip, kDontPerror, "Malloc failed.\n"); 1166 return (result); 1167 } 1168 1169 result = RCmd(cip, rp, "STAT %s", file); 1170 if (result == 2) { 1171 result = kNoErr; 1172 if (((rp->msg.nLines >= 3) || (rp->msg.nLines == 1))) { 1173 if ( 1174 (rp->msg.first->next != NULL) && 1175 (rp->msg.first->next->line != NULL) && 1176 ( 1177 (strstr(rp->msg.first->next->line, "o such file") != NULL) || 1178 (strstr(rp->msg.first->next->line, "ot found") != NULL) 1179 ) 1180 ) { 1181 cip->errNo = kErrSTATFailed; 1182 result = kErrSTATFailed; 1183 } else { 1184 result = kNoErr; 1185 } 1186 } else if (rp->msg.nLines == 2) { 1187 cip->errNo = kErrSTATFailed; 1188 result = kErrSTATFailed; 1189 } else { 1190 result = kNoErr; 1191 } 1192 } else { 1193 cip->errNo = kErrSTATFailed; 1194 result = kErrSTATFailed; 1195 } 1196 DoneWithResponse(cip, rp); 1197 return (result); 1198 } /* FTPFileExistsStat */ 1199 1200 1201 1202 1203 /* We only use STAT to see if files or directories exist. 1204 * But since it is so rarely used in the wild, we need to 1205 * make sure the server supports the use where we pass 1206 * a pathname as a parameter. 1207 */ 1208 int 1209 FTPFileExistsNlst(const FTPCIPtr cip, const char *const file) 1210 { 1211 int result; 1212 LineList fileList, rootFileList; 1213 char savedCwd[512]; 1214 1215 if (cip == NULL) 1216 return (kErrBadParameter); 1217 if (strcmp(cip->magic, kLibraryMagic)) 1218 return (kErrBadMagic); 1219 1220 if (file == NULL) 1221 return (kErrBadParameter); 1222 1223 if (cip->NLSTfileParamWorks == kCommandNotAvailable) { 1224 cip->errNo = result = kErrNLSTwithFileNotAvailable; 1225 return (result); 1226 } 1227 1228 if (cip->NLSTfileParamWorks == kCommandAvailabilityUnknown) { 1229 /* First, make sure that when we NLST a pathname 1230 * that does not exist, that we get an error back. 1231 * 1232 * We also assume that a valid NLST response has 1233 * at least 3 lines of response text, typically 1234 * a "start" line, intermediate data, and then 1235 * a trailing line. 1236 * 1237 * We also can see a one-line case. 1238 */ 1239 if ( 1240 ((FTPListToMemory2(cip, "NoSuchFile", &fileList, "", 0, (int *) 0)) == kNoErr) && 1241 (fileList.nLines >= 1) && 1242 (strstr(fileList.last->line, "o such file") == NULL) && 1243 (strstr(fileList.last->line, "ot found") == NULL) && 1244 (strstr(fileList.last->line, "o Such File") == NULL) && 1245 (strstr(fileList.last->line, "ot Found") == NULL) 1246 1247 ) { 1248 cip->NLSTfileParamWorks = kCommandNotAvailable; 1249 cip->errNo = result = kErrNLSTwithFileNotAvailable; 1250 DisposeLineListContents(&fileList); 1251 return (result); 1252 } 1253 DisposeLineListContents(&fileList); 1254 1255 /* We can't assume that we can simply say NLST rootdir/firstfile, 1256 * since the remote host may not be using / as a directory 1257 * delimiter. So we have to change to the root directory 1258 * and then do the NLST on that file. 1259 */ 1260 if ( 1261 (FTPGetCWD(cip, savedCwd, sizeof(savedCwd)) != kNoErr) || 1262 (FTPChdir(cip, cip->startingWorkingDirectory) != kNoErr) 1263 ) { 1264 return (cip->errNo); 1265 } 1266 1267 /* OK, we get an error when we list 1268 * a non-existant file, but now we need to 1269 * see if we get a positive reply when 1270 * we stat a file that does exist. 1271 * 1272 * To do this, we list the root directory, 1273 * which we assume has one or more items. 1274 * If it doesn't, the user can't do anything 1275 * anyway. Then we do the first item 1276 * we found to see if NLST says it exists. 1277 */ 1278 if ( 1279 ((result = FTPListToMemory2(cip, "", &rootFileList, "", 0, (int *) 0)) < 0) || 1280 (rootFileList.last == NULL) || 1281 (rootFileList.last->line == NULL) 1282 ) { 1283 /* Hmmm... well, in any case we can't use NLST. */ 1284 cip->NLSTfileParamWorks = kCommandNotAvailable; 1285 cip->errNo = result = kErrNLSTwithFileNotAvailable; 1286 DisposeLineListContents(&rootFileList); 1287 (void) FTPChdir(cip, savedCwd); 1288 return (result); 1289 } 1290 1291 if ( 1292 ((FTPListToMemory2(cip, rootFileList.last->line, &fileList, "", 0, (int *) 0)) == kNoErr) && 1293 (fileList.nLines >= 1) && 1294 (strstr(fileList.last->line, "o such file") == NULL) && 1295 (strstr(fileList.last->line, "ot found") == NULL) && 1296 (strstr(fileList.last->line, "o Such File") == NULL) && 1297 (strstr(fileList.last->line, "ot Found") == NULL) 1298 1299 ) { 1300 /* Good. We listed the item. */ 1301 DisposeLineListContents(&fileList); 1302 DisposeLineListContents(&rootFileList); 1303 cip->NLSTfileParamWorks = kCommandAvailable; 1304 1305 /* Don't forget to change back to the original directory. */ 1306 (void) FTPChdir(cip, savedCwd); 1307 } else { 1308 cip->NLSTfileParamWorks = kCommandNotAvailable; 1309 cip->errNo = result = kErrNLSTwithFileNotAvailable; 1310 DisposeLineListContents(&fileList); 1311 DisposeLineListContents(&rootFileList); 1312 (void) FTPChdir(cip, savedCwd); 1313 return (result); 1314 } 1315 } 1316 1317 /* Check the requested item. */ 1318 InitLineList(&fileList); 1319 if ( 1320 ((FTPListToMemory2(cip, file, &fileList, "", 0, (int *) 0)) == kNoErr) && 1321 (fileList.nLines >= 1) && 1322 (strstr(fileList.last->line, "o such file") == NULL) && 1323 (strstr(fileList.last->line, "ot found") == NULL) && 1324 (strstr(fileList.last->line, "o Such File") == NULL) && 1325 (strstr(fileList.last->line, "ot Found") == NULL) 1326 1327 ) { 1328 /* The item existed. */ 1329 result = kNoErr; 1330 } else { 1331 cip->errNo = kErrNLSTFailed; 1332 result = kErrNLSTFailed; 1333 } 1334 1335 DisposeLineListContents(&fileList); 1336 return (result); 1337 } /* FTPFileExistsNlst*/ 1338 1339 1340 1341 1342 /* This functions goes to a great deal of trouble to try and determine if the 1343 * remote file specified exists. Newer servers support commands that make 1344 * it relatively inexpensive to find the answer, but older servers do not 1345 * provide a standard way. This means we may try a whole bunch of things, 1346 * but the good news is that the library saves information about which things 1347 * worked so if you do this again it uses the methods that work. 1348 */ 1349 int 1350 FTPFileExists2(const FTPCIPtr cip, const char *const file, const int tryMDTM, const int trySIZE, const int tryMLST, const int trySTAT, const int tryNLST) 1351 { 1352 int result; 1353 time_t mdtm; 1354 longest_int size; 1355 MLstItem mlsInfo; 1356 1357 if (tryMDTM != 0) { 1358 result = FTPFileModificationTime(cip, file, &mdtm); 1359 if (result == kNoErr) 1360 return (kNoErr); 1361 if (result == kErrMDTMFailed) { 1362 cip->errNo = kErrNoSuchFileOrDirectory; 1363 return (kErrNoSuchFileOrDirectory); 1364 } 1365 /* else keep going */ 1366 } 1367 1368 if (trySIZE != 0) { 1369 result = FTPFileSize(cip, file, &size, kTypeBinary); 1370 if (result == kNoErr) 1371 return (kNoErr); 1372 /* SIZE could fail if the server does 1373 * not support it for directories. 1374 * 1375 * if (result == kErrSIZEFailed) 1376 * return (kErrNoSuchFileOrDirectory); 1377 */ 1378 /* else keep going */ 1379 } 1380 1381 1382 if (tryMLST != 0) { 1383 result = FTPMListOneFile(cip, file, &mlsInfo); 1384 if (result == kNoErr) 1385 return (kNoErr); 1386 if (result == kErrMLSTFailed) { 1387 cip->errNo = kErrNoSuchFileOrDirectory; 1388 return (kErrNoSuchFileOrDirectory); 1389 } 1390 /* else keep going */ 1391 } 1392 1393 if (trySTAT != 0) { 1394 result = FTPFileExistsStat(cip, file); 1395 if (result == kNoErr) 1396 return (kNoErr); 1397 if (result == kErrSTATFailed) { 1398 cip->errNo = kErrNoSuchFileOrDirectory; 1399 return (kErrNoSuchFileOrDirectory); 1400 } 1401 /* else keep going */ 1402 } 1403 1404 if (tryNLST != 0) { 1405 result = FTPFileExistsNlst(cip, file); 1406 if (result == kNoErr) 1407 return (kNoErr); 1408 if (result == kErrNLSTFailed) { 1409 cip->errNo = kErrNoSuchFileOrDirectory; 1410 return (kErrNoSuchFileOrDirectory); 1411 } 1412 /* else keep going */ 1413 } 1414 1415 cip->errNo = kErrCantTellIfFileExists; 1416 return (kErrCantTellIfFileExists); 1417 } /* FTPFileExists2 */ 1418 1419 1420 1421 1422 int 1423 FTPFileExists(const FTPCIPtr cip, const char *const file) 1424 { 1425 return (FTPFileExists2(cip, file, 1, 1, 1, 1, 1)); 1426 } /* FTPFileExists */ 1427 1428 1429 1430 1431 1432 int 1433 FTPFileSizeAndModificationTime(const FTPCIPtr cip, const char *const file, longest_int *const size, const int type, time_t *const mdtm) 1434 { 1435 MLstItem mlsInfo; 1436 int result; 1437 1438 if (cip == NULL) 1439 return (kErrBadParameter); 1440 if (strcmp(cip->magic, kLibraryMagic)) 1441 return (kErrBadMagic); 1442 1443 if ((mdtm == NULL) || (size == NULL) || (file == NULL)) 1444 return (kErrBadParameter); 1445 1446 *mdtm = kModTimeUnknown; 1447 *size = kSizeUnknown; 1448 1449 result = FTPSetTransferType(cip, type); 1450 if (result < 0) 1451 return (result); 1452 1453 result = FTPMListOneFile(cip, file, &mlsInfo); 1454 if (result < 0) { 1455 /* Do it the regular way, where 1456 * we do a SIZE and then a MDTM. 1457 */ 1458 result = FTPFileSize(cip, file, size, type); 1459 if (result < 0) 1460 return (result); 1461 result = FTPFileModificationTime(cip, file, mdtm); 1462 return (result); 1463 } else { 1464 *mdtm = mlsInfo.ftime; 1465 *size = mlsInfo.fsize; 1466 } 1467 1468 return (result); 1469 } /* FTPFileSizeAndModificationTime */ 1470 1471 1472 1473 1474 int 1475 FTPFileType(const FTPCIPtr cip, const char *const file, int *const ftype) 1476 { 1477 int result; 1478 MLstItem mlsInfo; 1479 1480 if (cip == NULL) 1481 return (kErrBadParameter); 1482 if (strcmp(cip->magic, kLibraryMagic)) 1483 return (kErrBadMagic); 1484 1485 if ((file == NULL) || (file[0] == '\0')) { 1486 cip->errNo = kErrBadParameter; 1487 return (kErrBadParameter); 1488 } 1489 1490 if (ftype == NULL) { 1491 cip->errNo = kErrBadParameter; 1492 return (kErrBadParameter); 1493 } 1494 1495 *ftype = 0; 1496 result = FTPMListOneFile(cip, file, &mlsInfo); 1497 if (result == kNoErr) { 1498 *ftype = mlsInfo.ftype; 1499 return (kNoErr); 1500 } 1501 1502 /* Preserve old working directory. */ 1503 (void) FTPGetCWD(cip, cip->buf, cip->bufSize); 1504 1505 result = FTPChdir(cip, file); 1506 if (result == kNoErr) { 1507 *ftype = 'd'; 1508 /* Yes it was a directory, now go back to 1509 * where we were. 1510 */ 1511 (void) FTPChdir(cip, cip->buf); 1512 1513 /* Note: This improperly assumes that we 1514 * will be able to chdir back, which is 1515 * not guaranteed. 1516 */ 1517 return (kNoErr); 1518 } 1519 1520 result = FTPFileExists2(cip, file, 1, 1, 0, 1, 1); 1521 if (result != kErrNoSuchFileOrDirectory) 1522 result = kErrFileExistsButCannotDetermineType; 1523 1524 return (result); 1525 } /* FTPFileType */ 1526 1527 1528 1529 1530 int 1531 FTPIsDir(const FTPCIPtr cip, const char *const dir) 1532 { 1533 int result, ftype; 1534 1535 if (cip == NULL) 1536 return (kErrBadParameter); 1537 if (strcmp(cip->magic, kLibraryMagic)) 1538 return (kErrBadMagic); 1539 1540 if ((dir == NULL) || (dir[0] == '\0')) { 1541 cip->errNo = kErrInvalidDirParam; 1542 return (kErrInvalidDirParam); 1543 } 1544 1545 result = FTPFileType(cip, dir, &ftype); 1546 if ((result == kNoErr) || (result == kErrFileExistsButCannotDetermineType)) { 1547 result = 0; 1548 if (ftype == 'd') 1549 result = 1; 1550 } 1551 return (result); 1552 } /* FTPIsDir */ 1553 1554 1555 1556 1557 int 1558 FTPIsRegularFile(const FTPCIPtr cip, const char *const file) 1559 { 1560 int result, ftype; 1561 1562 if (cip == NULL) 1563 return (kErrBadParameter); 1564 if (strcmp(cip->magic, kLibraryMagic)) 1565 return (kErrBadMagic); 1566 1567 if ((file == NULL) || (file[0] == '\0')) { 1568 cip->errNo = kErrBadParameter; 1569 return (kErrBadParameter); 1570 } 1571 1572 result = FTPFileType(cip, file, &ftype); 1573 if ((result == kNoErr) || (result == kErrFileExistsButCannotDetermineType)) { 1574 result = 1; 1575 if (ftype == 'd') 1576 result = 0; 1577 } 1578 return (result); 1579 } /* FTPIsRegularFile */ 1580 1581 1582 1583 1584 int 1585 FTPSymlink(const FTPCIPtr cip, const char *const lfrom, const char *const lto) 1586 { 1587 if (strcmp(cip->magic, kLibraryMagic)) 1588 return (kErrBadMagic); 1589 if ((cip == NULL) || (lfrom == NULL) || (lto == NULL)) 1590 return (kErrBadParameter); 1591 if ((lfrom[0] == '\0') || (lto[0] == '\0')) 1592 return (kErrBadParameter); 1593 if (FTPCmd(cip, "SITE SYMLINK %s %s", lfrom, lto) == 2) 1594 return (kNoErr); 1595 return (kErrSYMLINKFailed); 1596 } /* FTPSymlink */ 1597 1598 1599 1600 1601 int 1602 FTPUmask(const FTPCIPtr cip, const char *const umsk) 1603 { 1604 if (cip == NULL) 1605 return (kErrBadParameter); 1606 if (strcmp(cip->magic, kLibraryMagic)) 1607 return (kErrBadMagic); 1608 if ((umsk == NULL) || (umsk[0] == '\0')) 1609 return (kErrBadParameter); 1610 if (FTPCmd(cip, "SITE UMASK %s", umsk) == 2) 1611 return (kNoErr); 1612 return (kErrUmaskFailed); 1613 } /* FTPUmask */ 1614 1615 1616 1617 1618 static void 1619 GmTimeStr(char *const dst, const size_t dstsize, time_t t) 1620 { 1621 char buf[64]; 1622 struct tm *gtp; 1623 1624 gtp = gmtime(&t); 1625 if (gtp == NULL) { 1626 dst[0] = '\0'; 1627 } else { 1628 #ifdef HAVE_SNPRINTF 1629 buf[sizeof(buf) - 1] = '\0'; 1630 (void) snprintf(buf, sizeof(buf) - 1, "%04d%02d%02d%02d%02d%02d", 1631 #else 1632 (void) sprintf(buf, "%04d%02d%02d%02d%02d%02d", 1633 #endif 1634 gtp->tm_year + 1900, 1635 gtp->tm_mon + 1, 1636 gtp->tm_mday, 1637 gtp->tm_hour, 1638 gtp->tm_min, 1639 gtp->tm_sec 1640 ); 1641 (void) Strncpy(dst, buf, dstsize); 1642 } 1643 } /* GmTimeStr */ 1644 1645 1646 1647 1648 int 1649 FTPUtime(const FTPCIPtr cip, const char *const file, time_t actime, time_t modtime, time_t crtime) 1650 { 1651 char mstr[64], astr[64], cstr[64]; 1652 int result; 1653 ResponsePtr rp; 1654 1655 if (cip == NULL) 1656 return (kErrBadParameter); 1657 if (strcmp(cip->magic, kLibraryMagic)) 1658 return (kErrBadMagic); 1659 1660 if (cip->hasUTIME == kCommandNotAvailable) { 1661 cip->errNo = kErrUTIMENotAvailable; 1662 result = kErrUTIMENotAvailable; 1663 } else { 1664 if ((actime == (time_t) 0) || (actime == (time_t) -1)) 1665 (void) time(&actime); 1666 if ((modtime == (time_t) 0) || (modtime == (time_t) -1)) 1667 (void) time(&modtime); 1668 if ((crtime == (time_t) 0) || (crtime == (time_t) -1)) 1669 crtime = modtime; 1670 1671 (void) GmTimeStr(astr, sizeof(astr), actime); 1672 (void) GmTimeStr(mstr, sizeof(mstr), modtime); 1673 (void) GmTimeStr(cstr, sizeof(cstr), crtime); 1674 1675 rp = InitResponse(); 1676 if (rp == NULL) { 1677 result = kErrMallocFailed; 1678 cip->errNo = kErrMallocFailed; 1679 Error(cip, kDontPerror, "Malloc failed.\n"); 1680 } else { 1681 result = RCmd(cip, rp, "SITE UTIME %s %s %s %s UTC", file, astr, mstr, cstr); 1682 if (result < 0) { 1683 DoneWithResponse(cip, rp); 1684 return (result); 1685 } else if (result == 2) { 1686 cip->hasUTIME = kCommandAvailable; 1687 result = kNoErr; 1688 } else if (UNIMPLEMENTED_CMD(rp->code)) { 1689 cip->hasUTIME = kCommandNotAvailable; 1690 cip->errNo = kErrUTIMENotAvailable; 1691 result = kErrUTIMENotAvailable; 1692 } else { 1693 cip->errNo = kErrUTIMEFailed; 1694 result = kErrUTIMEFailed; 1695 } 1696 DoneWithResponse(cip, rp); 1697 } 1698 } 1699 return (result); 1700 } /* FTPUtime */ 1701