VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTFTPServer.cpp@ 82968

最後變更 在這個檔案從82968是 82843,由 vboxsync 提交於 5 年 前

IPRT/tools/FTPServer: Implemented VFS support for files also. bugref:9646

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 20.5 KB
 
1/* $Id: RTFTPServer.cpp 82843 2020-01-23 10:40:42Z vboxsync $ */
2/** @file
3 * IPRT - Utility for running a (simple) FTP server.
4 */
5
6/*
7 * Copyright (C) 2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.alldomusa.eu.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27/*
28 * Use this setup to best see what's going on:
29 *
30 * VBOX_LOG=rt_ftp=~0
31 * VBOX_LOG_DEST="nofile stderr"
32 * VBOX_LOG_FLAGS="unbuffered enabled thread msprog"
33 *
34 */
35
36
37/*********************************************************************************************************************************
38* Header Files *
39*********************************************************************************************************************************/
40#include <signal.h>
41
42#include <iprt/ftp.h>
43
44#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
45
46#include <iprt/asm.h>
47#include <iprt/assert.h>
48#include <iprt/ctype.h>
49#include <iprt/err.h>
50#include <iprt/file.h>
51#include <iprt/getopt.h>
52#include <iprt/initterm.h>
53#include <iprt/mem.h>
54#include <iprt/message.h>
55#include <iprt/path.h>
56#include <iprt/stream.h>
57#include <iprt/string.h>
58#include <iprt/thread.h>
59#include <iprt/vfs.h>
60
61#ifdef RT_OS_WINDOWS
62# include <iprt/win/windows.h>
63#endif
64
65
66/*********************************************************************************************************************************
67* Definitations *
68*********************************************************************************************************************************/
69typedef struct FTPSERVERDATA
70{
71 /** The absolute path of the FTP server's root directory. */
72 char szPathRootAbs[RTPATH_MAX];
73 /** The relative current working directory (CWD) to szRootDir. */
74 char szCWD[RTPATH_MAX];
75} FTPSERVERDATA;
76typedef FTPSERVERDATA *PFTPSERVERDATA;
77
78/**
79 * Enumeration specifying the VFS handle type of the FTP server.
80 */
81typedef enum FTPSERVERVFSHANDLETYPE
82{
83 FTPSERVERVFSHANDLETYPE_INVALID = 0,
84 FTPSERVERVFSHANDLETYPE_FILE,
85 FTPSERVERVFSHANDLETYPE_DIR,
86 /** The usual 32-bit hack. */
87 FTPSERVERVFSHANDLETYPE_32BIT_HACK = 0x7fffffff
88} FTPSERVERVFSHANDLETYPE;
89
90/**
91 * Structure for keeping a VFS handle of the FTP server.
92 */
93typedef struct FTPSERVERVFSHANDLE
94{
95 /** The type of the handle, stored in the union below. */
96 FTPSERVERVFSHANDLETYPE enmType;
97 union
98 {
99 /** The VFS (chain) handle to use for this file. */
100 RTVFSFILE hVfsFile;
101 /** The VFS (chain) handle to use for this directory. */
102 RTVFSDIR hVfsDir;
103 } u;
104} FTPSERVERVFSHANDLE;
105typedef FTPSERVERVFSHANDLE *PFTPSERVERVFSHANDLE;
106
107
108/*********************************************************************************************************************************
109* Global Variables *
110*********************************************************************************************************************************/
111/** Set by the signal handler when the FTP server shall be terminated. */
112static volatile bool g_fCanceled = false;
113static FTPSERVERDATA g_FTPServerData;
114
115
116#ifdef RT_OS_WINDOWS
117static BOOL WINAPI signalHandler(DWORD dwCtrlType)
118{
119 bool fEventHandled = FALSE;
120 switch (dwCtrlType)
121 {
122 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
123 * via GenerateConsoleCtrlEvent(). */
124 case CTRL_BREAK_EVENT:
125 case CTRL_CLOSE_EVENT:
126 case CTRL_C_EVENT:
127 ASMAtomicWriteBool(&g_fCanceled, true);
128 fEventHandled = TRUE;
129 break;
130 default:
131 break;
132 /** @todo Add other events here. */
133 }
134
135 return fEventHandled;
136}
137#else /* !RT_OS_WINDOWS */
138/**
139 * Signal handler that sets g_fCanceled.
140 *
141 * This can be executed on any thread in the process, on Windows it may even be
142 * a thread dedicated to delivering this signal. Don't do anything
143 * unnecessary here.
144 */
145static void signalHandler(int iSignal)
146{
147 NOREF(iSignal);
148 ASMAtomicWriteBool(&g_fCanceled, true);
149}
150#endif
151
152/**
153 * Installs a custom signal handler to get notified
154 * whenever the user wants to intercept the program.
155 *
156 * @todo Make this handler available for all VBoxManage modules?
157 */
158static int signalHandlerInstall(void)
159{
160 g_fCanceled = false;
161
162 int rc = VINF_SUCCESS;
163#ifdef RT_OS_WINDOWS
164 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
165 {
166 rc = RTErrConvertFromWin32(GetLastError());
167 RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
168 }
169#else
170 signal(SIGINT, signalHandler);
171 signal(SIGTERM, signalHandler);
172# ifdef SIGBREAK
173 signal(SIGBREAK, signalHandler);
174# endif
175#endif
176 return rc;
177}
178
179/**
180 * Uninstalls a previously installed signal handler.
181 */
182static int signalHandlerUninstall(void)
183{
184 int rc = VINF_SUCCESS;
185#ifdef RT_OS_WINDOWS
186 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
187 {
188 rc = RTErrConvertFromWin32(GetLastError());
189 RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
190 }
191#else
192 signal(SIGINT, SIG_DFL);
193 signal(SIGTERM, SIG_DFL);
194# ifdef SIGBREAK
195 signal(SIGBREAK, SIG_DFL);
196# endif
197#endif
198 return rc;
199}
200
201static DECLCALLBACK(int) onUserConnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
202{
203 RT_NOREF(pData, pcszUser);
204
205 RTPrintf("User '%s' connected\n", pcszUser);
206
207 return VINF_SUCCESS;
208}
209
210static DECLCALLBACK(int) onUserAuthenticate(PRTFTPCALLBACKDATA pData, const char *pcszUser, const char *pcszPassword)
211{
212 RT_NOREF(pData, pcszUser, pcszPassword);
213
214 RTPrintf("Authenticating user '%s' ...\n", pcszUser);
215
216 return VINF_SUCCESS;
217}
218
219static DECLCALLBACK(int) onUserDisonnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
220{
221 RT_NOREF(pData);
222
223 RTPrintf("User '%s' disconnected\n", pcszUser);
224
225 return VINF_SUCCESS;
226}
227
228static DECLCALLBACK(int) onFileOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint32_t fMode, void **ppvHandle)
229{
230 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
231 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
232
233 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)RTMemAllocZ(sizeof(FTPSERVERVFSHANDLE));
234 if (!pHandle)
235 return VERR_NO_MEMORY;
236
237 char *pszPathAbs = NULL;
238 if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
239 return VERR_NO_MEMORY;
240
241 int rc = RTVfsChainOpenFile(pszPathAbs, fMode, &pHandle->u.hVfsFile, NULL /*poffError */, NULL /* pErrInfo */);
242 if (RT_SUCCESS(rc))
243 {
244 pHandle->enmType = FTPSERVERVFSHANDLETYPE_FILE;
245
246 *ppvHandle = pHandle;
247 }
248 else
249 RTMemFree(pHandle);
250
251 RTStrFree(pszPathAbs);
252
253 return rc;
254}
255
256static DECLCALLBACK(int) onFileRead(PRTFTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbToRead, size_t *pcbRead)
257{
258 RT_NOREF(pData);
259
260 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
261 AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
262 AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_FILE, VERR_INVALID_PARAMETER);
263
264 return RTVfsFileRead(pHandle->u.hVfsFile, pvBuf, cbToRead, pcbRead);
265}
266
267static DECLCALLBACK(int) onFileClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
268{
269 RT_NOREF(pData);
270
271 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
272 AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
273 AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_FILE, VERR_INVALID_PARAMETER);
274
275 int rc = RTVfsFileRelease(pHandle->u.hVfsFile);
276 if (RT_SUCCESS(rc))
277 {
278 RTMemFree(pvHandle);
279 pvHandle = NULL;
280 }
281
282 return rc;
283}
284
285static DECLCALLBACK(int) onFileGetSize(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint64_t *puSize)
286{
287 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
288 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
289
290 char *pszStat = NULL;
291 if (RTStrAPrintf(&pszStat, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
292 return VERR_NO_MEMORY;
293
294 RTPrintf("Retrieving file size for '%s' ...\n", pcszPath);
295
296 RTFILE hFile;
297 int rc = RTFileOpen(&hFile, pcszPath,
298 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
299 if (RT_SUCCESS(rc))
300 {
301 rc = RTFileQuerySize(hFile, puSize);
302 if (RT_SUCCESS(rc))
303 RTPrintf("File size is: %RU64\n", *puSize);
304 RTFileClose(hFile);
305 }
306
307 RTStrFree(pszStat);
308
309 return rc;
310}
311
312static DECLCALLBACK(int) onFileStat(PRTFTPCALLBACKDATA pData, const char *pcszPath, PRTFSOBJINFO pFsObjInfo)
313{
314 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
315 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
316
317 char *pszStat = NULL;
318 if (RTStrAPrintf(&pszStat, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
319 return VERR_NO_MEMORY;
320
321 RTPrintf("Stat for '%s'\n", pszStat);
322
323 RTFILE hFile;
324 int rc = RTFileOpen(&hFile, pszStat,
325 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
326 if (RT_SUCCESS(rc))
327 {
328 RTFSOBJINFO fsObjInfo;
329 rc = RTFileQueryInfo(hFile, &fsObjInfo, RTFSOBJATTRADD_NOTHING);
330 if (RT_SUCCESS(rc))
331 {
332 if (pFsObjInfo)
333 *pFsObjInfo = fsObjInfo;
334 }
335
336 RTFileClose(hFile);
337 }
338
339 RTStrFree(pszStat);
340
341 return rc;
342}
343
344static DECLCALLBACK(int) onPathSetCurrent(PRTFTPCALLBACKDATA pData, const char *pcszCWD)
345{
346 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
347 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
348
349 RTPrintf("Setting current directory to '%s'\n", pcszCWD);
350
351 /** @todo BUGBUG Santiy checks! */
352
353 return RTStrCopy(pThis->szCWD, sizeof(pThis->szCWD), pcszCWD);
354}
355
356static DECLCALLBACK(int) onPathGetCurrent(PRTFTPCALLBACKDATA pData, char *pszPWD, size_t cbPWD)
357{
358 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
359 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
360
361 RTPrintf("Current directory is: '%s'\n", pThis->szCWD);
362
363 return RTStrCopy(pszPWD, cbPWD, pThis->szCWD);
364}
365
366static DECLCALLBACK(int) onPathUp(PRTFTPCALLBACKDATA pData)
367{
368 RT_NOREF(pData);
369
370 return VINF_SUCCESS;
371}
372
373static DECLCALLBACK(int) onDirOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, void **ppvHandle)
374{
375 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
376 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
377
378 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)RTMemAllocZ(sizeof(FTPSERVERVFSHANDLE));
379 if (!pHandle)
380 return VERR_NO_MEMORY;
381
382 /* Construct absolute path. */
383 char *pszPathAbs = NULL;
384 if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
385 return VERR_NO_MEMORY;
386
387 RTPrintf("Opening directory '%s'\n", pszPathAbs);
388
389 int rc = RTVfsChainOpenDir(pszPathAbs, 0 /*fFlags*/, &pHandle->u.hVfsDir, NULL /* poffError */, NULL /* pErrInfo */);
390 if (RT_SUCCESS(rc))
391 {
392 pHandle->enmType = FTPSERVERVFSHANDLETYPE_DIR;
393
394 *ppvHandle = pHandle;
395 }
396 else
397 {
398 RTMemFree(pHandle);
399 }
400
401 RTStrFree(pszPathAbs);
402
403 return rc;
404}
405
406static DECLCALLBACK(int) onDirClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
407{
408 RT_NOREF(pData);
409
410 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
411 AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
412 AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_DIR, VERR_INVALID_PARAMETER);
413
414 RTVfsDirRelease(pHandle->u.hVfsDir);
415
416 RTMemFree(pHandle);
417 pHandle = NULL;
418
419 return VINF_SUCCESS;
420}
421
422static DECLCALLBACK(int) onDirRead(PRTFTPCALLBACKDATA pData, void *pvHandle, char **ppszEntry,
423 PRTFSOBJINFO pInfo, char **ppszOwner, char **ppszGroup, char **ppszTarget)
424{
425 RT_NOREF(pData);
426 RT_NOREF(ppszTarget); /* No symlinks yet */
427
428 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
429 AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
430 AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_DIR, VERR_INVALID_PARAMETER);
431
432 size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
433 PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
434 if (!pDirEntry)
435 return VERR_NO_MEMORY;
436
437 int rc;
438
439 for (;;)
440 {
441 size_t cbDirEntry = cbDirEntryAlloced;
442 rc = RTVfsDirReadEx(pHandle->u.hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
443 if (RT_FAILURE(rc))
444 {
445 if (rc == VERR_BUFFER_OVERFLOW)
446 {
447 RTMemTmpFree(pDirEntry);
448 cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
449 pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
450 if (pDirEntry)
451 continue;
452 }
453 else if (rc != VERR_NO_MORE_FILES)
454 break;
455 }
456
457 if (RT_SUCCESS(rc))
458 {
459 if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID)
460 {
461 RTFSOBJINFO OwnerInfo;
462 rc = RTVfsDirQueryPathInfo(pHandle->u.hVfsDir,
463 pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
464 if ( RT_SUCCESS(rc)
465 && OwnerInfo.Attr.u.UnixOwner.szName[0])
466 {
467 *ppszOwner = RTStrDup(&OwnerInfo.Attr.u.UnixOwner.szName[0]);
468 if (!*ppszOwner)
469 rc = VERR_NO_MEMORY;
470 }
471 }
472
473 if ( RT_SUCCESS(rc)
474 && pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID)
475 {
476 RTFSOBJINFO GroupInfo;
477 rc = RTVfsDirQueryPathInfo(pHandle->u.hVfsDir,
478 pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
479 if ( RT_SUCCESS(rc)
480 && GroupInfo.Attr.u.UnixGroup.szName[0])
481 {
482 *ppszGroup = RTStrDup(&GroupInfo.Attr.u.UnixGroup.szName[0]);
483 if (!*ppszGroup)
484 rc = VERR_NO_MEMORY;
485 }
486 }
487 }
488
489 *ppszEntry = RTStrDup(pDirEntry->szName);
490 AssertPtrReturn(*ppszEntry, VERR_NO_MEMORY);
491
492 *pInfo = pDirEntry->Info;
493
494 break;
495
496 } /* for */
497
498 RTMemTmpFree(pDirEntry);
499 pDirEntry = NULL;
500
501 return rc;
502}
503
504int main(int argc, char **argv)
505{
506 int rc = RTR3InitExe(argc, &argv, 0);
507 if (RT_FAILURE(rc))
508 return RTMsgInitFailure(rc);
509
510 /* Use some sane defaults. */
511 char szAddress[64] = "localhost";
512 uint16_t uPort = 2121;
513
514 RT_ZERO(g_FTPServerData);
515
516 /*
517 * Parse arguments.
518 */
519 static const RTGETOPTDEF s_aOptions[] =
520 {
521 { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
522 /** @todo Implement IPv6 support? */
523 { "--port", 'p', RTGETOPT_REQ_UINT16 },
524 { "--root-dir", 'r', RTGETOPT_REQ_STRING },
525 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
526 };
527
528 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
529 unsigned uVerbosityLevel = 1;
530
531 RTGETOPTUNION ValueUnion;
532 RTGETOPTSTATE GetState;
533 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
534 while ((rc = RTGetOpt(&GetState, &ValueUnion)))
535 {
536 switch (rc)
537 {
538 case 'a':
539 RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
540 ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
541 break;
542
543 case 'p':
544 uPort = ValueUnion.u16;
545 break;
546
547 case 'r':
548 RTStrCopy(g_FTPServerData.szPathRootAbs, sizeof(g_FTPServerData.szPathRootAbs), ValueUnion.psz);
549 break;
550
551 case 'v':
552 uVerbosityLevel++;
553 break;
554
555 case 'h':
556 RTPrintf("Usage: %s [options]\n"
557 "\n"
558 "Options:\n"
559 " -a, --address (default: localhost)\n"
560 " Specifies the address to use for listening.\n"
561 " -p, --port (default: 2121)\n"
562 " Specifies the port to use for listening.\n"
563 " -r, --root-dir (default: current dir)\n"
564 " Specifies the root directory being served.\n"
565 " -v, --verbose\n"
566 " Controls the verbosity level.\n"
567 " -h, -?, --help\n"
568 " Display this help text and exit successfully.\n"
569 " -V, --version\n"
570 " Display the revision and exit successfully.\n"
571 , RTPathFilename(argv[0]));
572 return RTEXITCODE_SUCCESS;
573
574 case 'V':
575 RTPrintf("$Revision: 82843 $\n");
576 return RTEXITCODE_SUCCESS;
577
578 default:
579 return RTGetOptPrintError(rc, &ValueUnion);
580 }
581 }
582
583 if (!strlen(g_FTPServerData.szPathRootAbs))
584 {
585 /* By default use the current directory as serving root directory. */
586 rc = RTPathGetCurrent(g_FTPServerData.szPathRootAbs, sizeof(g_FTPServerData.szPathRootAbs));
587 if (RT_FAILURE(rc))
588 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
589 }
590
591 /* Initialize CWD. */
592 RTStrPrintf2(g_FTPServerData.szCWD, sizeof(g_FTPServerData.szCWD), "/");
593
594 /* Install signal handler. */
595 rc = signalHandlerInstall();
596 if (RT_SUCCESS(rc))
597 {
598 /*
599 * Create the FTP server instance.
600 */
601 RTFTPSERVERCALLBACKS Callbacks;
602 RT_ZERO(Callbacks);
603
604 Callbacks.pfnOnUserConnect = onUserConnect;
605 Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
606 Callbacks.pfnOnUserDisconnect = onUserDisonnect;
607 Callbacks.pfnOnFileOpen = onFileOpen;
608 Callbacks.pfnOnFileRead = onFileRead;
609 Callbacks.pfnOnFileClose = onFileClose;
610 Callbacks.pfnOnFileGetSize = onFileGetSize;
611 Callbacks.pfnOnFileStat = onFileStat;
612 Callbacks.pfnOnPathSetCurrent = onPathSetCurrent;
613 Callbacks.pfnOnPathGetCurrent = onPathGetCurrent;
614 Callbacks.pfnOnPathUp = onPathUp;
615 Callbacks.pfnOnDirOpen = onDirOpen;
616 Callbacks.pfnOnDirClose = onDirClose;
617 Callbacks.pfnOnDirRead = onDirRead;
618
619 RTFTPSERVER hFTPServer;
620 rc = RTFtpServerCreate(&hFTPServer, szAddress, uPort, &Callbacks,
621 &g_FTPServerData, sizeof(g_FTPServerData));
622 if (RT_SUCCESS(rc))
623 {
624 RTPrintf("Starting FTP server at %s:%RU16 ...\n", szAddress, uPort);
625 RTPrintf("Root directory is '%s'\n", g_FTPServerData.szPathRootAbs);
626
627 RTPrintf("Running FTP server ...\n");
628
629 for (;;)
630 {
631 RTThreadSleep(200);
632
633 if (g_fCanceled)
634 break;
635 }
636
637 RTPrintf("Stopping FTP server ...\n");
638
639 int rc2 = RTFtpServerDestroy(hFTPServer);
640 if (RT_SUCCESS(rc))
641 rc = rc2;
642
643 RTPrintf("Stopped FTP server\n");
644 }
645 else
646 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFTPServerCreate failed: %Rrc", rc);
647
648 int rc2 = signalHandlerUninstall();
649 if (RT_SUCCESS(rc))
650 rc = rc2;
651 }
652
653 /* Set rcExit on failure in case we forgot to do so before. */
654 if (RT_FAILURE(rc))
655 rcExit = RTEXITCODE_FAILURE;
656
657 return rcExit;
658}
659
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette