Changeset 82727 in vbox
- Timestamp:
- Jan 13, 2020 2:55:50 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/include/iprt/ftp.h
r82723 r82727 103 103 /** Invalid reply type, do not use. */ 104 104 RTFTPSERVER_REPLY_INVALID = 0, 105 /** Command okay. */ 106 RTFTPSERVER_REPLY_OKAY = 200, 105 107 /** Command not implemented, superfluous at this site. */ 106 108 RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL_SUPERFLUOUS = 202, 107 /** Command okay. */108 RTFTPSERVER_REPLY_OKAY = 200,109 109 /** Service ready for new user. */ 110 110 RTFTPSERVER_REPLY_READY_FOR_NEW_USER = 220, … … 117 117 /** User name okay, need password. */ 118 118 RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD = 331, 119 /** Can't open data connection. */ 120 RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN = 425, 119 121 /** Connection closed; transfer aborted. */ 120 122 RTFTPSERVER_REPLY_CONN_CLOSED_TRANSFER_ABORTED = 426, … … 143 145 { 144 146 /** User name. */ 145 char *pszUser;147 char *pszUser; 146 148 /** Number of failed login attempts. */ 147 uint8_t cFailedLoginAttempts;149 uint8_t cFailedLoginAttempts; 148 150 /** Timestamp (in ms) of last command issued by the client. */ 149 uint64_t tsLastCmdMs; 151 uint64_t tsLastCmdMs; 152 /** Current set data type. */ 153 RTFTPSERVER_DATA_TYPE enmDataType; 150 154 } RTFTPSERVERCLIENTSTATE; 151 155 /** Pointer to a FTP server client state. */ … … 202 206 */ 203 207 DECLCALLBACKMEMBER(int, pfnOnUserDisconnect)(PRTFTPCALLBACKDATA pData); 208 DECLCALLBACKMEMBER(int, pfnOnFileOpen)(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint32_t fMode, void **ppvHandle); 209 DECLCALLBACKMEMBER(int, pfnOnFileRead)(PRTFTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbToRead, size_t *pcbRead); 210 DECLCALLBACKMEMBER(int, pfnOnFileClose)(PRTFTPCALLBACKDATA pData, void *pvHandle); 204 211 /** 205 212 * Callback which gets invoked when the client wants to retrieve the size of a specific file. … … 216 223 * @param pData Pointer to generic callback data. 217 224 * @param pcszPath Path of file / directory to "stat". Optional. If NULL, the current directory will be used. 218 * @param pFsObjInfo Where to return the RTFSOBJINFO data on success. 225 * @param pFsObjInfo Where to return the RTFSOBJINFO data on success. Optional. 219 226 * @returns VBox status code. 220 227 */ -
trunk/src/VBox/Runtime/generic/ftp-server.cpp
r82723 r82727 32 32 * - No FTPS / SFTP support. 33 33 * - No passive mode ("PASV") support. 34 * - No IPv6 support. 34 35 * - No proxy support. 35 36 * - No FXP support. … … 48 49 #include <iprt/assert.h> 49 50 #include <iprt/errcore.h> 51 #include <iprt/file.h> /* For file mode flags. */ 50 52 #include <iprt/getopt.h> 51 53 #include <iprt/mem.h> … … 128 130 /** Retrieves a specific file. */ 129 131 RTFTPSERVER_CMD_RETR, 130 /** Recursively gets a directory (and its contents). */131 RTFTPSERVER_CMD_RGET,132 132 /** Retrieves the size of a file. */ 133 133 RTFTPSERVER_CMD_SIZE, … … 147 147 148 148 /** 149 * Structure for maintaining a single data connection. 150 */ 151 typedef struct RTFTPSERVERDATACONN 152 { 153 /** Data connection IP. */ 154 RTNETADDRIPV4 Addr; 155 /** Data connection port number. */ 156 uint16_t uPort; 157 /** The current data socket to use. 158 * Can be NIL_RTSOCKET if no data port has been specified (yet) or has been closed. */ 159 RTSOCKET hSocket; 160 /** Thread serving the data connection. */ 161 RTTHREAD hThread; 162 /** Thread started indicator. */ 163 volatile bool fStarted; 164 /** Thread stop indicator. */ 165 volatile bool fStop; 166 char szFile[RTPATH_MAX]; 167 } RTFTPSERVERDATACONN; 168 /** Pointer to a data connection struct. */ 169 typedef RTFTPSERVERDATACONN *PRTFTPSERVERDATACONN; 170 171 /** 149 172 * Structure for maintaining an internal FTP server client. 150 173 */ … … 157 180 /** Actual client state. */ 158 181 RTFTPSERVERCLIENTSTATE State; 182 /** Data connection information. 183 * At the moment we only allow one data connection per client at a time. */ 184 RTFTPSERVERDATACONN DataConn; 159 185 } RTFTPSERVERCLIENT; 160 186 /** Pointer to an internal FTP server client state. */ … … 222 248 } while (0) 223 249 250 251 /********************************************************************************************************************************* 252 * Defined Constants And Macros * 253 *********************************************************************************************************************************/ 254 255 static int rtFtpServerDataPortOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort); 256 224 257 /** 225 258 * Function prototypes for command handlers. … … 236 269 static FNRTFTPSERVERCMD rtFtpServerHandleQUIT; 237 270 static FNRTFTPSERVERCMD rtFtpServerHandleRETR; 238 static FNRTFTPSERVERCMD rtFtpServerHandleRGET;239 271 static FNRTFTPSERVERCMD rtFtpServerHandleSIZE; 240 272 static FNRTFTPSERVERCMD rtFtpServerHandleSTAT; … … 242 274 static FNRTFTPSERVERCMD rtFtpServerHandleTYPE; 243 275 static FNRTFTPSERVERCMD rtFtpServerHandleUSER; 276 244 277 245 278 /** … … 272 305 { RTFTPSERVER_CMD_QUIT, "QUIT", rtFtpServerHandleQUIT }, 273 306 { RTFTPSERVER_CMD_RETR, "RETR", rtFtpServerHandleRETR }, 274 { RTFTPSERVER_CMD_RGET, "RGET", rtFtpServerHandleRGET },275 307 { RTFTPSERVER_CMD_SIZE, "SIZE", rtFtpServerHandleSIZE }, 276 308 { RTFTPSERVER_CMD_STAT, "STAT", rtFtpServerHandleSTAT }, … … 442 474 } 443 475 476 /** 477 * Parses a string which consists of an IPv4 (ww,xx,yy,zz) and a port number (hi,lo), all separated by comma delimiters. 478 * See RFC 959, 4.1.2. 479 * 480 * @returns VBox status code. 481 * @param pcszStr String to parse. 482 * @param pAddr Where to store the IPv4 address on success. 483 * @param puPort Where to store the port number on success. 484 */ 485 static int rtFtpParseHostAndPort(const char *pcszStr, PRTNETADDRIPV4 pAddr, uint16_t *puPort) 486 { 487 AssertPtrReturn(pcszStr, VERR_INVALID_POINTER); 488 AssertPtrReturn(pAddr, VERR_INVALID_POINTER); 489 AssertPtrReturn(puPort, VERR_INVALID_POINTER); 490 491 char *pszNext; 492 int rc; 493 494 /* Parse IP (v4). */ 495 /** @todo I don't think IPv6 ever will be a thing here, or will it? */ 496 rc = RTStrToUInt8Ex(pcszStr, &pszNext, 10, &pAddr->au8[0]); 497 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS) 498 return VERR_INVALID_PARAMETER; 499 if (*pszNext++ != ',') 500 return VERR_INVALID_PARAMETER; 501 502 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[1]); 503 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS) 504 return VERR_INVALID_PARAMETER; 505 if (*pszNext++ != ',') 506 return VERR_INVALID_PARAMETER; 507 508 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[2]); 509 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS) 510 return VERR_INVALID_PARAMETER; 511 if (*pszNext++ != ',') 512 return VERR_INVALID_PARAMETER; 513 514 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[3]); 515 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS) 516 return VERR_INVALID_PARAMETER; 517 if (*pszNext++ != ',') 518 return VERR_INVALID_PARAMETER; 519 520 /* Parse port. */ 521 uint8_t uPortHi; 522 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortHi); 523 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS) 524 return VERR_INVALID_PARAMETER; 525 if (*pszNext++ != ',') 526 return VERR_INVALID_PARAMETER; 527 uint8_t uPortLo; 528 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortLo); 529 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS) 530 return VERR_INVALID_PARAMETER; 531 532 *puPort = RT_MAKE_U16(uPortLo, uPortHi); 533 534 return rc; 535 } 536 537 /** 538 * Opens a data connection to the client. 539 * 540 * @returns VBox status code. 541 * @param pDataConn Data connection to open. 542 * @param pAddr Address for the data connection. 543 * @param uPort Port for the data connection. 544 */ 545 static int rtFtpServerDataPortOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort) 546 { 547 RT_NOREF(pAddr); 548 549 char szAddress[32]; 550 const ssize_t cchAdddress = RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", 551 pAddr->au8[0], pAddr->au8[1], pAddr->au8[2], pAddr->au8[3]); 552 AssertReturn(cchAdddress > 0, VERR_NO_MEMORY); 553 554 return RTTcpClientConnect(szAddress, uPort, &pDataConn->hSocket); 555 } 556 557 /** 558 * Closes a data connection to the client. 559 * 560 * @returns VBox status code. 561 * @param pDataConn Data connection to close. 562 */ 563 static int rtFtpServerDataPortClose(PRTFTPSERVERDATACONN pDataConn) 564 { 565 int rc = VINF_SUCCESS; 566 567 if (pDataConn->hSocket != NIL_RTSOCKET) 568 { 569 rc = RTTcpClientClose(pDataConn->hSocket); 570 pDataConn->hSocket = NIL_RTSOCKET; 571 } 572 573 return rc; 574 } 575 576 /** 577 * Thread serving a data connection. 578 * 579 * @returns VBox status code. 580 * @param ThreadSelf Thread handle. Unused at the moment. 581 * @param pvUser Pointer to user-provided data. Of type PRTFTPSERVERCLIENT. 582 */ 583 static DECLCALLBACK(int) rtFtpServerDataConnThread(RTTHREAD ThreadSelf, void *pvUser) 584 { 585 RT_NOREF(ThreadSelf); 586 587 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser; 588 AssertPtr(pClient); 589 590 PRTFTPSERVERDATACONN pDataConn = &pClient->DataConn; 591 592 int rc = rtFtpServerDataPortOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort); 593 if (RT_FAILURE(rc)) 594 return rc; 595 596 uint32_t cbBuf = _64K; /** @todo Improve this. */ 597 void *pvBuf = RTMemAlloc(cbBuf); 598 if (!pvBuf) 599 return VERR_NO_MEMORY; 600 601 pDataConn->fStop = false; 602 pDataConn->fStarted = true; 603 604 RTThreadUserSignal(RTThreadSelf()); 605 606 const char *pcszFile = pDataConn->szFile; 607 608 void *pvHandle = NULL; /* Opaque handle known to the actual implementation. */ 609 610 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileOpen, pcszFile, 611 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pvHandle); 612 if (RT_SUCCESS(rc)) 613 { 614 do 615 { 616 size_t cbRead; 617 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileRead, pvHandle, pvBuf, cbBuf, &cbRead); 618 if (RT_SUCCESS(rc)) 619 rc = RTTcpWrite(pClient->DataConn.hSocket, pvBuf, cbRead); 620 621 if ( !cbRead 622 || ASMAtomicReadBool(&pDataConn->fStop)) 623 break; 624 } 625 while (RT_SUCCESS(rc)); 626 627 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileClose, pvHandle); 628 } 629 630 rtFtpServerDataPortClose(&pClient->DataConn); 631 632 RTMemFree(pvBuf); 633 pvBuf = NULL; 634 635 return rc; 636 } 637 638 /** 639 * Opens a data connection to the client. 640 * 641 * @returns VBox status code. 642 * @param pClient Client to open data connection for. 643 * @param pDataConn Data connection to open. 644 */ 645 static int rtFtpServerDataConnCreate(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN pDataConn) 646 { 647 int rc = RTThreadCreate(&pDataConn->hThread, rtFtpServerDataConnThread, 648 pClient, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, 649 "ftpdata"); 650 if (RT_SUCCESS(rc)) 651 { 652 int rc2 = RTThreadUserWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */); 653 AssertRC(rc2); 654 655 if (!pDataConn->fStarted) /* Did the thread indicate that it started correctly? */ 656 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */ 657 } 658 659 return rc; 660 } 661 662 /** 663 * Closes a data connection to the client. 664 * 665 * @returns VBox status code. 666 * @param pClient Client to close data connection for. 667 * @param pDataConn Data connection to close. 668 */ 669 static int rtFtpServerDataConnDestroy(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN pDataConn) 670 { 671 RT_NOREF(pClient); 672 673 if (pDataConn->hThread == NIL_RTTHREAD) 674 return VINF_SUCCESS; 675 676 LogFlowFuncEnter(); 677 678 /* Set stop indicator. */ 679 pDataConn->fStop = true; 680 681 int rcThread = VERR_WRONG_ORDER; 682 int rc = RTThreadWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */, &rcThread); 683 if (RT_SUCCESS(rc)) 684 { 685 if (pDataConn->hSocket != NIL_RTSOCKET) 686 { 687 RTTcpClientClose(pDataConn->hSocket); 688 pDataConn->hSocket = NIL_RTSOCKET; 689 } 690 691 pDataConn->fStarted = false; 692 pDataConn->hThread = NIL_RTTHREAD; 693 694 rc = rcThread; 695 } 696 697 return rc; 698 } 699 444 700 445 701 /********************************************************************************************************************************* … … 449 705 static int rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs) 450 706 { 451 RT_NOREF(pClient, cArgs, apcszArgs); 452 453 /** @todo Anything to do here? */ 454 return VERR_NOT_IMPLEMENTED; 707 RT_NOREF(cArgs, apcszArgs); 708 709 int rc = rtFtpServerDataConnDestroy(pClient, &pClient->DataConn); 710 if (RT_SUCCESS(rc)) 711 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); 712 713 return rc; 455 714 } 456 715 … … 551 810 static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs) 552 811 { 553 RT_NOREF(pClient, cArgs, apcszArgs); 554 555 /** @todo Anything to do here? */ 556 return VERR_NOT_IMPLEMENTED; 812 if (cArgs != 1) 813 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS); 814 815 /* Only allow one data connection per client at a time. */ 816 rtFtpServerDataPortClose(&pClient->DataConn); 817 818 int rc = rtFtpParseHostAndPort(apcszArgs[0], &pClient->DataConn.Addr, &pClient->DataConn.uPort); 819 if (RT_SUCCESS(rc)) 820 { 821 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); 822 } 823 else 824 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN); 825 826 return rc; 557 827 } 558 828 … … 575 845 static int rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs) 576 846 { 577 RT_NOREF(pClient, cArgs, apcszArgs); 578 579 /** @todo Anything to do here? */ 580 return VERR_NOT_IMPLEMENTED; 847 RT_NOREF(cArgs, apcszArgs); 848 849 return rtFtpServerDataConnDestroy(pClient, &pClient->DataConn); 581 850 } 582 851 583 852 static int rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs) 584 853 { 585 RT_NOREF(pClient, cArgs, apcszArgs); 586 587 /** @todo Anything to do here? */ 588 return VERR_NOT_IMPLEMENTED; 589 } 590 591 static int rtFtpServerHandleRGET(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs) 592 { 593 RT_NOREF(pClient, cArgs, apcszArgs); 594 595 /** @todo Anything to do here? */ 596 return VERR_NOT_IMPLEMENTED; 854 if (cArgs != 1) 855 return VERR_INVALID_PARAMETER; 856 857 int rc; 858 859 const char *pcszPath = apcszArgs[0]; 860 861 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */); 862 863 if (RT_SUCCESS(rc)) 864 { 865 rc = RTStrCopy(pClient->DataConn.szFile, sizeof(pClient->DataConn.szFile), pcszPath); 866 if (RT_SUCCESS(rc)) 867 { 868 rc = rtFtpServerDataConnCreate(pClient, &pClient->DataConn); 869 if (RT_SUCCESS(rc)) 870 { 871 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); 872 } 873 } 874 } 875 876 if (RT_FAILURE(rc)) 877 { 878 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN); 879 AssertRC(rc2); 880 } 881 882 return rc; 597 883 } 598 884 … … 678 964 static int rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs) 679 965 { 680 RT_NOREF(pClient, cArgs, apcszArgs); 681 682 /** @todo Anything to do here? */ 683 return VERR_NOT_IMPLEMENTED; 966 if (cArgs != 1) 967 return VERR_INVALID_PARAMETER; 968 969 const char *pcszType = apcszArgs[0]; 970 971 int rc = VINF_SUCCESS; 972 973 if (!RTStrICmp(pcszType, "A")) /* ASCII (can be 7 or 8 bits). */ 974 { 975 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII; 976 } 977 else if (!RTStrICmp(pcszType, "I")) /* Image (binary). */ 978 { 979 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE; 980 } 981 else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */ 982 rc = VERR_INVALID_PARAMETER; 983 984 return rc; 684 985 } 685 986 … … 871 1172 } 872 1173 1174 /* Make sure to close any open data connections. */ 1175 int rc2 = rtFtpServerDataConnDestroy(pClient, &pClient->DataConn); 1176 if (RT_SUCCESS(rc)) 1177 rc = rc2; 1178 873 1179 return rc; 874 1180 } -
trunk/src/VBox/Runtime/tools/RTFTPServer.cpp
r82723 r82727 70 70 char szRootDir[RTPATH_MAX]; 71 71 char szCWD[RTPATH_MAX]; 72 RTFILE hFile; 72 73 } FTPSERVERDATA; 73 74 typedef FTPSERVERDATA *PFTPSERVERDATA; … … 194 195 } 195 196 197 static DECLCALLBACK(int) onFileOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint32_t fMode, void **ppvHandle) 198 { 199 RT_NOREF(ppvHandle); 200 201 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser; 202 Assert(pData->cbUser == sizeof(FTPSERVERDATA)); 203 204 return RTFileOpen(&pThis->hFile, pcszPath, fMode); 205 } 206 207 static DECLCALLBACK(int) onFileRead(PRTFTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbToRead, size_t *pcbRead) 208 { 209 RT_NOREF(pvHandle); 210 211 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser; 212 Assert(pData->cbUser == sizeof(FTPSERVERDATA)); 213 214 return RTFileRead(pThis->hFile, pvBuf, cbToRead, pcbRead); 215 } 216 217 static DECLCALLBACK(int) onFileClose(PRTFTPCALLBACKDATA pData, void *pvHandle) 218 { 219 RT_NOREF(pvHandle); 220 221 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser; 222 Assert(pData->cbUser == sizeof(FTPSERVERDATA)); 223 224 return RTFileClose(pThis->hFile); 225 } 226 196 227 static DECLCALLBACK(int) onFileGetSize(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint64_t *puSize) 197 228 { … … 223 254 if (RT_SUCCESS(rc)) 224 255 { 225 rc = RTFileQueryInfo(hFile, pFsObjInfo, RTFSOBJATTRADD_NOTHING); 256 RTFSOBJINFO fsObjInfo; 257 rc = RTFileQueryInfo(hFile, &fsObjInfo, RTFSOBJATTRADD_NOTHING); 258 if (RT_SUCCESS(rc)) 259 { 260 if (pFsObjInfo) 261 *pFsObjInfo = fsObjInfo; 262 } 263 226 264 RTFileClose(hFile); 227 265 } … … 374 412 Callbacks.pfnOnUserAuthenticate = onUserAuthenticate; 375 413 Callbacks.pfnOnUserDisconnect = onUserDisonnect; 414 Callbacks.pfnOnFileOpen = onFileOpen; 415 Callbacks.pfnOnFileRead = onFileRead; 416 Callbacks.pfnOnFileClose = onFileClose; 376 417 Callbacks.pfnOnFileGetSize = onFileGetSize; 377 418 Callbacks.pfnOnFileStat = onFileStat;
Note:
See TracChangeset
for help on using the changeset viewer.