VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTFtpServer.cpp@ 86297

Last change on this file since 86297 was 85673, checked in by vboxsync, 4 years ago

IPRT: RTFTPServer -> RTFtpServer. bugref:9437

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

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