3 * swift the multiparty transport protocol
5 * Created by Victor Grishchenko on 2/15/10.
6 * Copyright 2009-2012 TECHNISCHE UNIVERSITEIT DELFT. All rights reserved.
15 using namespace swift;
19 #define RESCAN_DIR_INTERVAL 30 // seconds
23 #define quit(...) {fprintf(stderr,__VA_ARGS__); exit(1); }
24 int OpenSwiftFile(const TCHAR* filename, const Sha1Hash& hash, Address tracker, bool force_check_diskvshash, uint32_t chunk_size);
25 int OpenSwiftDirectory(const TCHAR* dirname, Address tracker, bool force_check_diskvshash, uint32_t chunk_size);
27 void ReportCallback(int fd, short event, void *arg);
28 void EndCallback(int fd, short event, void *arg);
29 void RescanDirCallback(int fd, short event, void *arg);
30 void TimerCallback(int fd, short event, void *arg);
34 bool InstallHTTPGateway(struct event_base *evbase,Address addr,uint32_t chunk_size, double *maxspeed);
35 bool InstallStatsGateway(struct event_base *evbase,Address addr);
36 bool InstallCmdGateway (struct event_base *evbase,Address cmdaddr,Address httpaddr);
39 void CmdGwUpdateDLStatesCallback();
43 struct event evreport, evrescan, evend, evtimer;
45 bool file_enable_checkpoint = false;
46 bool file_checkpointed = false;
47 bool report_progress = false;
49 bool exitoncomplete=false;
50 bool httpgw_enabled=false,cmdgw_enabled=false;
52 bool do_nat_test = false;
54 char *scan_dirname = 0;
55 uint32_t chunk_size = SWIFT_DEFAULT_CHUNK_SIZE;
59 int main (int argc, char** argv)
61 static struct option long_options[] =
63 {"hash", required_argument, 0, 'h'},
64 {"file", required_argument, 0, 'f'},
65 {"dir", required_argument, 0, 'd'}, // SEEDDIR reuse
66 {"listen", required_argument, 0, 'l'},
67 {"tracker", required_argument, 0, 't'},
68 {"debug", no_argument, 0, 'D'},
69 {"progress",no_argument, 0, 'p'},
70 {"httpgw", required_argument, 0, 'g'},
71 {"wait", optional_argument, 0, 'w'},
72 {"nat-test",no_argument, 0, 'N'},
73 {"statsgw", required_argument, 0, 's'}, // SWIFTPROC
74 {"cmdgw", required_argument, 0, 'c'}, // SWIFTPROC
75 {"destdir", required_argument, 0, 'o'}, // SWIFTPROC
76 {"uprate", required_argument, 0, 'u'}, // RATELIMIT
77 {"downrate",required_argument, 0, 'y'}, // RATELIMIT
78 {"checkpoint",no_argument, 0, 'H'},
79 {"chunksize",required_argument, 0, 'z'}, // CHUNKSIZE
80 {"printurl",no_argument, 0, 'm'},
86 const char *destdir = 0, *trackerargstr = 0; // UNICODE?
93 double maxspeed[2] = {DBL_MAX,DBL_MAX};
96 Channel::evbase = event_base_new();
99 while ( -1 != (c = getopt_long (argc, argv, ":h:f:d:l:t:D:pg:s:c:o:u:y:z:wBNHm", long_options, 0)) ) {
102 if (strlen(optarg)!=40)
103 quit("SHA1 hash must be 40 hex symbols\n");
104 root_hash = Sha1Hash(true,optarg); // FIXME ambiguity
105 if (root_hash==Sha1Hash::ZERO)
106 quit("SHA1 hash must be 40 hex symbols\n");
109 filename = strdup(optarg);
112 scan_dirname = strdup(optarg);
115 bindaddr = Address(optarg);
116 if (bindaddr==Address())
117 quit("address must be hostname:port, ip:port or just port\n");
118 wait_time = TINT_NEVER;
121 tracker = Address(optarg);
122 trackerargstr = strdup(optarg); // UNICODE
123 if (tracker==Address())
124 quit("address must be hostname:port, ip:port or just port\n");
128 Channel::debug_file = optarg ? fopen(optarg,"a") : stderr;
130 // Arno hack: get opt diff Win32 doesn't allow -D without arg
132 fprintf(stderr,"SETTING DEBUG TO STDOUT\n");
133 Channel::debug_file = stderr;
136 report_progress = true;
139 httpgw_enabled = true;
140 httpaddr = Address(optarg);
141 wait_time = TINT_NEVER; // seed
146 if (sscanf(optarg,"%lli%c",&wait_time,&unit)!=2)
147 quit("time format: 1234[umsMHD], e.g. 1M = one minute\n");
150 case 'D': wait_time *= 24;
151 case 'H': wait_time *= 60;
152 case 'M': wait_time *= 60;
153 case 's': wait_time *= 1000;
154 case 'm': wait_time *= 1000;
156 default: quit("time format: 1234[umsMHD], e.g. 1D = one day\n");
159 wait_time = TINT_NEVER;
161 case 'N': // Gertjan fix
164 case 's': // SWIFTPROC
165 statsaddr = Address(optarg);
166 if (statsaddr==Address())
167 quit("address must be hostname:port, ip:port or just port\n");
169 case 'c': // SWIFTPROC
170 cmdgw_enabled = true;
171 cmdaddr = Address(optarg);
172 if (cmdaddr==Address())
173 quit("address must be hostname:port, ip:port or just port\n");
174 wait_time = TINT_NEVER; // seed
176 case 'o': // SWIFTPROC
177 destdir = strdup(optarg); // UNICODE
179 case 'u': // RATELIMIT
180 n = sscanf(optarg,"%lf",&maxspeed[DDIR_UPLOAD]);
182 quit("uprate must be KiB/s as float\n");
183 maxspeed[DDIR_UPLOAD] *= 1024.0;
185 case 'y': // RATELIMIT
186 n = sscanf(optarg,"%lf",&maxspeed[DDIR_DOWNLOAD]);
188 quit("downrate must be KiB/s as float\n");
189 maxspeed[DDIR_DOWNLOAD] *= 1024.0;
191 case 'H': //CHECKPOINT
192 file_enable_checkpoint = true;
194 case 'z': // CHUNKSIZE
195 n = sscanf(optarg,"%i",&chunk_size);
197 quit("chunk size must be bytes as int\n");
199 case 'm': // printurl
206 } // arguments parsed
211 // Change current directory to a temporary one
214 std::string destdirstr = gettmpdir();
215 !::SetCurrentDirectory(destdirstr.c_str());
218 !::SetCurrentDirectory(destdir);
219 TCHAR szDirectory[MAX_PATH] = "";
221 !::GetCurrentDirectory(sizeof(szDirectory) - 1, szDirectory);
222 fprintf(stderr,"CWD %s\n",szDirectory);
225 chdir(gettmpdir().c_str());
231 if (bindaddr!=Address()) { // seeding
232 if (Listen(bindaddr)<=0)
233 quit("cant listen to %s\n",bindaddr.str())
234 } else if (tracker!=Address() || httpgw_enabled || cmdgw_enabled) { // leeching
235 evutil_socket_t sock = INVALID_SOCKET;
236 for (int i=0; i<=10; i++) {
237 bindaddr = Address((uint32_t)INADDR_ANY,0);
238 sock = Listen(bindaddr);
242 quit("cant listen on %s\n",bindaddr.str());
245 fprintf(stderr,"swift: My listen port is %d\n", BoundAddress(sock).port() );
248 if (tracker!=Address())
252 InstallHTTPGateway(Channel::evbase,httpaddr,chunk_size,maxspeed);
254 InstallCmdGateway(Channel::evbase,cmdaddr,httpaddr);
256 // TRIALM36: Allow browser to retrieve stats via AJAX and as HTML page
257 if (statsaddr != Address())
258 InstallStatsGateway(Channel::evbase,statsaddr);
261 if (!cmdgw_enabled && !httpgw_enabled)
264 if (filename || root_hash != Sha1Hash::ZERO) {
266 if (root_hash!=Sha1Hash::ZERO && !filename)
267 filename = strdup(root_hash.hex().c_str());
269 single_fd = OpenSwiftFile(filename,root_hash,Address(),false,chunk_size);
271 quit("cannot open file %s",filename);
273 if (trackerargstr == NULL)
275 printf("tswift://%s/%s$%i\n", trackerargstr, RootMerkleHash(single_fd).hex().c_str(), chunk_size);
277 // Arno, 2012-01-04: LivingLab: Create checkpoint such that content
278 // can be copied to scanned dir and quickly loaded
279 swift::Checkpoint(single_fd);
282 printf("Root hash: %s\n", RootMerkleHash(single_fd).hex().c_str());
285 FileTransfer *ft = FileTransfer::file(single_fd);
286 ft->SetMaxSpeed(DDIR_DOWNLOAD,maxspeed[DDIR_DOWNLOAD]);
287 ft->SetMaxSpeed(DDIR_UPLOAD,maxspeed[DDIR_UPLOAD]);
291 else if (scan_dirname != NULL)
292 ret = OpenSwiftDirectory(scan_dirname,Address(),false,chunk_size);
296 // No file/dir nor HTTP gateway nor CMD gateway, will never know what to swarm
298 fprintf(stderr,"Usage:\n");
299 fprintf(stderr," -h, --hash\troot Merkle hash for the transmission\n");
300 fprintf(stderr," -f, --file\tname of file to use (root hash by default)\n");
301 fprintf(stderr," -l, --listen\t[ip:|host:]port to listen to (default: random)\n");
302 fprintf(stderr," -t, --tracker\t[ip:|host:]port of the tracker (default: none)\n");
303 fprintf(stderr," -D, --debug\tfile name for debugging logs (default: stdout)\n");
304 fprintf(stderr," -B\tdebugging logs to stdout (win32 hack)\n");
305 fprintf(stderr," -p, --progress\treport transfer progress\n");
306 fprintf(stderr," -g, --httpgw\t[ip:|host:]port to bind HTTP content gateway to (no default)\n");
307 fprintf(stderr," -s, --statsgw\t[ip:|host:]port to bind HTTP stats listen socket to (no default)\n");
308 fprintf(stderr," -c, --cmdgw\t[ip:|host:]port to bind CMD listen socket to (no default)\n");
309 fprintf(stderr," -o, --destdir\tdirectory for saving data (default: none)\n");
310 fprintf(stderr," -u, --uprate\tupload rate limit in KiB/s (default: unlimited)\n");
311 fprintf(stderr," -y, --downrate\tdownload rate limit in KiB/s (default: unlimited)\n");
312 fprintf(stderr," -w, --wait\tlimit running time, e.g. 1[DHMs] (default: infinite with -l, -g)\n");
313 fprintf(stderr," -H, --checkpoint\tcreate checkpoint of file when complete for fast restart\n");
314 fprintf(stderr," -z, --chunksize\tchunk size in bytes (default: %d)\n", SWIFT_DEFAULT_CHUNK_SIZE);
315 fprintf(stderr," -m, --printurl\tcompose URL from tracker, file and chunksize\n");
320 // Arno, 2012-01-04: Allow download and quit mode
321 if (single_fd != -1 && root_hash != Sha1Hash::ZERO && wait_time == 0) {
322 wait_time = TINT_NEVER;
323 exitoncomplete = true;
326 // End after wait_time
327 if ((long)wait_time > 0) {
328 evtimer_assign(&evend, Channel::evbase, EndCallback, NULL);
329 evtimer_add(&evend, tint2tv(wait_time));
332 evtimer_assign(&evtimer, Channel::evbase, TimerCallback, NULL);
333 evtimer_add(&evtimer, tint2tv(TIMER_USEC));
335 // Enter mainloop, if daemonizing
336 if (wait_time == TINT_NEVER || (long)wait_time > 0) {
337 // Arno: always, for statsgw, rate control, etc.
338 evtimer_assign(&evreport, Channel::evbase, ReportCallback, NULL);
339 evtimer_add(&evreport, tint2tv(TINT_SEC));
343 if (scan_dirname != NULL) {
344 evtimer_assign(&evrescan, Channel::evbase, RescanDirCallback, NULL);
345 evtimer_add(&evrescan, tint2tv(RESCAN_DIR_INTERVAL*TINT_SEC));
349 fprintf(stderr,"swift: Mainloop\n");
350 // Enter libevent mainloop
351 event_base_dispatch(Channel::evbase);
353 // event_base_loopexit() was called, shutting down
356 // Arno, 2012-01-03: Close all transfers
357 for (int i=0; i<FileTransfer::files.size(); i++) {
358 if (FileTransfer::files[i] != NULL)
359 Close(FileTransfer::files[i]->fd());
362 if (Channel::debug_file)
363 fclose(Channel::debug_file);
372 int OpenSwiftFile(const TCHAR* filename, const Sha1Hash& hash, Address tracker, bool force_check_diskvshash, uint32_t chunk_size)
375 bfn.assign(filename);
376 bfn.append(".mbinmap");
377 const char *binmap_filename = bfn.c_str();
379 // Arno, 2012-01-03: Hack to discover root hash of a file on disk, such that
380 // we don't load it twice while rescanning a dir of content.
381 HashTree *ht = new HashTree(true,binmap_filename);
383 // fprintf(stderr,"swift: parsedir: File %s may have hash %s\n", filename, ht->root_hash().hex().c_str() );
385 int fd = swift::Find(ht->root_hash());
388 fprintf(stderr,"swift: parsedir: Opening %s\n", filename);
390 fd = swift::Open(filename,hash,tracker,force_check_diskvshash,true,chunk_size);
393 fprintf(stderr,"swift: parsedir: Ignoring loaded %s\n", filename);
398 int OpenSwiftDirectory(const TCHAR* dirname, Address tracker, bool force_check_diskvshash, uint32_t chunk_size)
402 WIN32_FIND_DATA FindFileData;
404 TCHAR pathsearch[MAX_PATH];
405 strcpy(pathsearch,dirname);
406 strcat(pathsearch,"\\*.*");
407 hFind = FindFirstFile(pathsearch, &FindFileData);
408 if(hFind != INVALID_HANDLE_VALUE) {
410 //fprintf(stderr,"swift: parsedir: %s\n", FindFileData.cFileName);
411 if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 && !strstr(FindFileData.cFileName,".mhash") && !strstr(FindFileData.cFileName,".mbinmap") ) {
412 TCHAR path[MAX_PATH];
413 strcpy(path,dirname);
415 strcat(path,FindFileData.cFileName);
417 int fd = OpenSwiftFile(path,Sha1Hash::ZERO,tracker,force_check_diskvshash,chunk_size);
421 } while(FindNextFile(hFind, &FindFileData));
429 DIR *dirp = opendir(dirname);
435 struct dirent *de = readdir(dirp);
438 if ((de->d_type & DT_DIR) !=0 || strstr(de->d_name,".mhash") || strstr(de->d_name,".mbinmap"))
441 strcpy(path,dirname);
443 strcat(path,de->d_name);
445 int fd = OpenSwiftFile(path,Sha1Hash::ZERO,tracker,force_check_diskvshash,chunk_size);
456 int CleanSwiftDirectory(const TCHAR* dirname)
458 std::set<int> delset;
459 std::vector<FileTransfer*>::iterator iter;
460 for (iter=FileTransfer::files.begin(); iter!=FileTransfer::files.end(); iter++)
462 FileTransfer *ft = *iter;
464 std::string filename = ft->file().filename();
470 fprintf(stderr,"swift: clean: Checking %s\n", filename.c_str() );
471 int res = stat( filename.c_str(), &buf );
472 if( res < 0 && errno == ENOENT) {
473 fprintf(stderr,"swift: clean: Missing %s\n", filename.c_str() );
474 delset.insert(ft->fd());
479 std::set<int>::iterator iiter;
480 for (iiter=delset.begin(); iiter!=delset.end(); iiter++)
483 fprintf(stderr,"swift: clean: Deleting transfer %d\n", fd );
494 void ReportCallback(int fd, short event, void *arg) {
495 // Called every second to print/calc some stats
499 if (report_progress) {
501 "%s %lli of %lli (seq %lli) %lli dgram %lli bytes up, " \
502 "%lli dgram %lli bytes down mptp[send:%lli,%lli;recv:%lli,%lli]\n",
503 IsComplete(single_fd ) ? "DONE" : "done",
504 Complete(single_fd), Size(single_fd), SeqComplete(single_fd),
505 Channel::global_dgrams_up, Channel::global_raw_bytes_up,
506 Channel::global_dgrams_down, Channel::global_raw_bytes_down,
507 Channel::global_buffers_up, Channel::global_syscalls_up,
508 Channel::global_buffers_down, Channel::global_syscalls_down);
511 FileTransfer *ft = FileTransfer::file(single_fd);
512 if (report_progress) { // TODO: move up
513 fprintf(stderr,"upload %lf\n",ft->GetCurrentSpeed(DDIR_UPLOAD));
514 fprintf(stderr,"dwload %lf\n",ft->GetCurrentSpeed(DDIR_DOWNLOAD) );
515 //fprintf(stderr,"npeers %d\n",ft->GetNumLeechers()+ft->GetNumSeeders() );
517 // Update speed measurements such that they decrease when DL/UL stops
523 if (file_enable_checkpoint && !file_checkpointed && IsComplete(single_fd))
525 std::string binmap_filename = ft->file().filename();
526 binmap_filename.append(".mbinmap");
527 fprintf(stderr,"swift: Complete, checkpointing %s\n", binmap_filename.c_str() );
528 FILE *fp = fopen(binmap_filename.c_str(),"wb");
530 print_error("cannot open mbinmap for writing");
533 if (ft->file().serialize(fp) < 0)
534 print_error("writing to mbinmap");
536 file_checkpointed = true;
541 if (exitoncomplete && IsComplete(single_fd))
542 // Download and stop mode
543 event_base_loopexit(Channel::evbase, NULL);
550 // ARNOSMPTODO: Restore fail behaviour when used in SwarmPlayer 3000.
551 if (!HTTPIsSending()) {
553 //event_base_loopexit(Channel::evbase, NULL);
559 // SwarmPlayer 3000: User click "Quit" button in webUI.
562 int ret = event_base_loopexit(Channel::evbase,&tv);
565 // ARNOSMPTODO: SCALE: perhaps less than once a second if many swarms
566 CmdGwUpdateDLStatesCallback();
569 // Arno, 2011-10-04: Temp disable
571 // nat_test_update();
573 evtimer_add(&evreport, tint2tv(TINT_SEC));
576 void TimerCallback(int fd, short event, void *arg) {
577 Channel::messageQueue.Flush();
578 evtimer_add(&evtimer, tint2tv(TIMER_USEC));
581 void EndCallback(int fd, short event, void *arg) {
582 // Called when wait timer expires == fixed time daemon
583 event_base_loopexit(Channel::evbase, NULL);
587 void RescanDirCallback(int fd, short event, void *arg) {
590 // Rescan dir: CAREFUL: this is blocking, better prepare .m* files first
591 // by running swift separately and then copy content + *.m* to scanned dir,
592 // such that a fast restore from checkpoint is done.
594 OpenSwiftDirectory(scan_dirname,tracker,false,chunk_size);
596 CleanSwiftDirectory(scan_dirname);
598 evtimer_add(&evrescan, tint2tv(RESCAN_DIR_INTERVAL*TINT_SEC));
604 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
606 return main(__argc,__argv);