Configuration file, HTTP Basic authentication
[novacoin.git] / util.cpp
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Distributed under the MIT/X11 software license, see the accompanying
3 // file license.txt or http://www.opensource.org/licenses/mit-license.php.
4
5 #include "headers.h"
6
7
8 map<string, string> mapArgs;
9 map<string, vector<string> > mapMultiArgs;
10 bool fDebug = false;
11 bool fPrintToConsole = false;
12 bool fPrintToDebugger = false;
13 char pszSetDataDir[MAX_PATH] = "";
14 bool fShutdown = false;
15 bool fDaemon = false;
16 bool fCommandLine = false;
17
18
19
20
21
22 // Init openssl library multithreading support
23 static boost::interprocess::interprocess_mutex** ppmutexOpenSSL;
24 void locking_callback(int mode, int i, const char* file, int line)
25 {
26     if (mode & CRYPTO_LOCK)
27         ppmutexOpenSSL[i]->lock();
28     else
29         ppmutexOpenSSL[i]->unlock();
30 }
31
32 // Init
33 class CInit
34 {
35 public:
36     CInit()
37     {
38         // Init openssl library multithreading support
39         ppmutexOpenSSL = (boost::interprocess::interprocess_mutex**)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(boost::interprocess::interprocess_mutex*));
40         for (int i = 0; i < CRYPTO_num_locks(); i++)
41             ppmutexOpenSSL[i] = new boost::interprocess::interprocess_mutex();
42         CRYPTO_set_locking_callback(locking_callback);
43
44 #ifdef __WXMSW__
45         // Seed random number generator with screen scrape and other hardware sources
46         RAND_screen();
47 #endif
48
49         // Seed random number generator with performance counter
50         RandAddSeed();
51     }
52     ~CInit()
53     {
54         // Shutdown openssl library multithreading support
55         CRYPTO_set_locking_callback(NULL);
56         for (int i = 0; i < CRYPTO_num_locks(); i++)
57             delete ppmutexOpenSSL[i];
58         OPENSSL_free(ppmutexOpenSSL);
59     }
60 }
61 instance_of_cinit;
62
63
64
65
66
67
68
69
70 void RandAddSeed()
71 {
72     // Seed with CPU performance counter
73     int64 nCounter = PerformanceCounter();
74     RAND_add(&nCounter, sizeof(nCounter), 1.5);
75     memset(&nCounter, 0, sizeof(nCounter));
76 }
77
78 void RandAddSeedPerfmon()
79 {
80     RandAddSeed();
81
82     // This can take up to 2 seconds, so only do it every 10 minutes
83     static int64 nLastPerfmon;
84     if (GetTime() < nLastPerfmon + 10 * 60)
85         return;
86     nLastPerfmon = GetTime();
87
88 #ifdef __WXMSW__
89     // Don't need this on Linux, OpenSSL automatically uses /dev/urandom
90     // Seed with the entire set of perfmon data
91     unsigned char pdata[250000];
92     memset(pdata, 0, sizeof(pdata));
93     unsigned long nSize = sizeof(pdata);
94     long ret = RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", NULL, NULL, pdata, &nSize);
95     RegCloseKey(HKEY_PERFORMANCE_DATA);
96     if (ret == ERROR_SUCCESS)
97     {
98         uint256 hash;
99         SHA256(pdata, nSize, (unsigned char*)&hash);
100         RAND_add(&hash, sizeof(hash), min(nSize/500.0, (double)sizeof(hash)));
101         hash = 0;
102         memset(pdata, 0, nSize);
103
104         printf("%s RandAddSeed() %d bytes\n", DateTimeStrFormat("%x %H:%M", GetTime()).c_str(), nSize);
105     }
106 #endif
107 }
108
109 uint64 GetRand(uint64 nMax)
110 {
111     if (nMax == 0)
112         return 0;
113
114     // The range of the random source must be a multiple of the modulus
115     // to give every possible output value an equal possibility
116     uint64 nRange = (UINT64_MAX / nMax) * nMax;
117     uint64 nRand = 0;
118     do
119         RAND_bytes((unsigned char*)&nRand, sizeof(nRand));
120     while (nRand >= nRange);
121     return (nRand % nMax);
122 }
123
124
125
126
127
128
129
130
131
132
133
134 inline int OutputDebugStringF(const char* pszFormat, ...)
135 {
136     int ret = 0;
137     if (fPrintToConsole || wxTheApp == NULL)
138     {
139         // print to console
140         va_list arg_ptr;
141         va_start(arg_ptr, pszFormat);
142         ret = vprintf(pszFormat, arg_ptr);
143         va_end(arg_ptr);
144     }
145     else
146     {
147         // print to debug.log
148         char pszFile[MAX_PATH+100];
149         GetDataDir(pszFile);
150         strlcat(pszFile, "/debug.log", sizeof(pszFile));
151         FILE* fileout = fopen(pszFile, "a");
152         if (fileout)
153         {
154             //// Debug print useful for profiling
155             //fprintf(fileout, " %"PRI64d" ", GetTimeMillis());
156             va_list arg_ptr;
157             va_start(arg_ptr, pszFormat);
158             ret = vfprintf(fileout, pszFormat, arg_ptr);
159             va_end(arg_ptr);
160             fclose(fileout);
161         }
162     }
163
164 #ifdef __WXMSW__
165     if (fPrintToDebugger)
166     {
167         // accumulate a line at a time
168         static CCriticalSection cs_OutputDebugStringF;
169         CRITICAL_BLOCK(cs_OutputDebugStringF)
170         {
171             static char pszBuffer[50000];
172             static char* pend;
173             if (pend == NULL)
174                 pend = pszBuffer;
175             va_list arg_ptr;
176             va_start(arg_ptr, pszFormat);
177             int limit = END(pszBuffer) - pend - 2;
178             int ret = _vsnprintf(pend, limit, pszFormat, arg_ptr);
179             va_end(arg_ptr);
180             if (ret < 0 || ret >= limit)
181             {
182                 pend = END(pszBuffer) - 2;
183                 *pend++ = '\n';
184             }
185             else
186                 pend += ret;
187             *pend = '\0';
188             char* p1 = pszBuffer;
189             char* p2;
190             while (p2 = strchr(p1, '\n'))
191             {
192                 p2++;
193                 char c = *p2;
194                 *p2 = '\0';
195                 OutputDebugStringA(p1);
196                 *p2 = c;
197                 p1 = p2;
198             }
199             if (p1 != pszBuffer)
200                 memmove(pszBuffer, p1, pend - p1 + 1);
201             pend -= (p1 - pszBuffer);
202         }
203     }
204 #endif
205     return ret;
206 }
207
208
209 // Safer snprintf
210 //  - prints up to limit-1 characters
211 //  - output string is always null terminated even if limit reached
212 //  - return value is the number of characters actually printed
213 int my_snprintf(char* buffer, size_t limit, const char* format, ...)
214 {
215     if (limit == 0)
216         return 0;
217     va_list arg_ptr;
218     va_start(arg_ptr, format);
219     int ret = _vsnprintf(buffer, limit, format, arg_ptr);
220     va_end(arg_ptr);
221     if (ret < 0 || ret >= limit)
222     {
223         ret = limit - 1;
224         buffer[limit-1] = 0;
225     }
226     return ret;
227 }
228
229
230 string strprintf(const char* format, ...)
231 {
232     char buffer[50000];
233     char* p = buffer;
234     int limit = sizeof(buffer);
235     int ret;
236     loop
237     {
238         va_list arg_ptr;
239         va_start(arg_ptr, format);
240         ret = _vsnprintf(p, limit, format, arg_ptr);
241         va_end(arg_ptr);
242         if (ret >= 0 && ret < limit)
243             break;
244         if (p != buffer)
245             delete p;
246         limit *= 2;
247         p = new char[limit];
248         if (p == NULL)
249             throw std::bad_alloc();
250     }
251     string str(p, p+ret);
252     if (p != buffer)
253         delete p;
254     return str;
255 }
256
257
258 bool error(const char* format, ...)
259 {
260     char buffer[50000];
261     int limit = sizeof(buffer);
262     va_list arg_ptr;
263     va_start(arg_ptr, format);
264     int ret = _vsnprintf(buffer, limit, format, arg_ptr);
265     va_end(arg_ptr);
266     if (ret < 0 || ret >= limit)
267     {
268         ret = limit - 1;
269         buffer[limit-1] = 0;
270     }
271     printf("ERROR: %s\n", buffer);
272     return false;
273 }
274
275
276 void ParseString(const string& str, char c, vector<string>& v)
277 {
278     if (str.empty())
279         return;
280     string::size_type i1 = 0;
281     string::size_type i2;
282     loop
283     {
284         i2 = str.find(c, i1);
285         if (i2 == str.npos)
286         {
287             v.push_back(str.substr(i1));
288             return;
289         }
290         v.push_back(str.substr(i1, i2-i1));
291         i1 = i2+1;
292     }
293 }
294
295
296 string FormatMoney(int64 n, bool fPlus)
297 {
298     n /= CENT;
299     string str = strprintf("%"PRI64d".%02"PRI64d, (n > 0 ? n : -n)/100, (n > 0 ? n : -n)%100);
300     for (int i = 6; i < str.size(); i += 4)
301         if (isdigit(str[str.size() - i - 1]))
302             str.insert(str.size() - i, 1, ',');
303     if (n < 0)
304         str.insert((unsigned int)0, 1, '-');
305     else if (fPlus && n > 0)
306         str.insert((unsigned int)0, 1, '+');
307     return str;
308 }
309
310
311 bool ParseMoney(const string& str, int64& nRet)
312 {
313     return ParseMoney(str.c_str(), nRet);
314 }
315
316 bool ParseMoney(const char* pszIn, int64& nRet)
317 {
318     string strWhole;
319     int64 nCents = 0;
320     const char* p = pszIn;
321     while (isspace(*p))
322         p++;
323     for (; *p; p++)
324     {
325         if (*p == ',' && p > pszIn && isdigit(p[-1]) && isdigit(p[1]) && isdigit(p[2]) && isdigit(p[3]) && !isdigit(p[4]))
326             continue;
327         if (*p == '.')
328         {
329             p++;
330             if (isdigit(*p))
331             {
332                 nCents = 10 * (*p++ - '0');
333                 if (isdigit(*p))
334                     nCents += (*p++ - '0');
335             }
336             break;
337         }
338         if (isspace(*p))
339             break;
340         if (!isdigit(*p))
341             return false;
342         strWhole.insert(strWhole.end(), *p);
343     }
344     for (; *p; p++)
345         if (!isspace(*p))
346             return false;
347     if (strWhole.size() > 14)
348         return false;
349     if (nCents < 0 || nCents > 99)
350         return false;
351     int64 nWhole = atoi64(strWhole);
352     int64 nPreValue = nWhole * 100 + nCents;
353     int64 nValue = nPreValue * CENT;
354     if (nValue / CENT != nPreValue)
355         return false;
356     if (nValue / COIN != nWhole)
357         return false;
358     nRet = nValue;
359     return true;
360 }
361
362
363 vector<unsigned char> ParseHex(const char* psz)
364 {
365     vector<unsigned char> vch;
366     while (isspace(*psz))
367         psz++;
368     vch.reserve((strlen(psz)+1)/3);
369
370     static char phexdigit[256] =
371     { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
372       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
373       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
374       0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1,
375       -1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1,
376       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
377       -1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1
378       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
379       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
380       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
381       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
382       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
383       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
384       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
385       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
386       -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, };
387
388     while (*psz)
389     {
390         char c = phexdigit[(unsigned char)*psz++];
391         if (c == -1)
392             break;
393         unsigned char n = (c << 4);
394         if (*psz)
395         {
396             char c = phexdigit[(unsigned char)*psz++];
397             if (c == -1)
398                 break;
399             n |= c;
400             vch.push_back(n);
401         }
402         while (isspace(*psz))
403             psz++;
404     }
405
406     return vch;
407 }
408
409 vector<unsigned char> ParseHex(const std::string& str)
410 {
411     return ParseHex(str.c_str());
412 }
413
414
415 void ParseParameters(int argc, char* argv[])
416 {
417     mapArgs.clear();
418     mapMultiArgs.clear();
419     for (int i = 1; i < argc; i++)
420     {
421         char psz[10000];
422         strlcpy(psz, argv[i], sizeof(psz));
423         char* pszValue = (char*)"";
424         if (strchr(psz, '='))
425         {
426             pszValue = strchr(psz, '=');
427             *pszValue++ = '\0';
428         }
429         #ifdef __WXMSW__
430         _strlwr(psz);
431         if (psz[0] == '/')
432             psz[0] = '-';
433         #endif
434         if (psz[0] != '-')
435             break;
436         mapArgs[psz] = pszValue;
437         mapMultiArgs[psz].push_back(pszValue);
438     }
439 }
440
441
442 const char* wxGetTranslation(const char* pszEnglish)
443 {
444     // Wrapper of wxGetTranslation returning the same const char* type as was passed in
445     static CCriticalSection cs;
446     CRITICAL_BLOCK(cs)
447     {
448         // Look in cache
449         static map<string, char*> mapCache;
450         map<string, char*>::iterator mi = mapCache.find(pszEnglish);
451         if (mi != mapCache.end())
452             return (*mi).second;
453
454         // wxWidgets translation
455         wxString strTranslated = wxGetTranslation(wxString(pszEnglish, wxConvUTF8));
456
457         // We don't cache unknown strings because caller might be passing in a
458         // dynamic string and we would keep allocating memory for each variation.
459         if (strcmp(pszEnglish, strTranslated.utf8_str()) == 0)
460             return pszEnglish;
461
462         // Add to cache, memory doesn't need to be freed.  We only cache because
463         // we must pass back a pointer to permanently allocated memory.
464         char* pszCached = new char[strlen(strTranslated.utf8_str())+1];
465         strcpy(pszCached, strTranslated.utf8_str());
466         mapCache[pszEnglish] = pszCached;
467         return pszCached;
468     }
469     return NULL;
470 }
471
472
473
474
475
476
477
478
479
480
481 void FormatException(char* pszMessage, std::exception* pex, const char* pszThread)
482 {
483 #ifdef __WXMSW__
484     char pszModule[MAX_PATH];
485     pszModule[0] = '\0';
486     GetModuleFileNameA(NULL, pszModule, sizeof(pszModule));
487 #else
488     // might not be thread safe, uses wxString
489     //const char* pszModule = wxStandardPaths::Get().GetExecutablePath().mb_str();
490     const char* pszModule = "bitcoin";
491 #endif
492     if (pex)
493         snprintf(pszMessage, 1000,
494             "EXCEPTION: %s       \n%s       \n%s in %s       \n", typeid(*pex).name(), pex->what(), pszModule, pszThread);
495     else
496         snprintf(pszMessage, 1000,
497             "UNKNOWN EXCEPTION       \n%s in %s       \n", pszModule, pszThread);
498 }
499
500 void LogException(std::exception* pex, const char* pszThread)
501 {
502     char pszMessage[1000];
503     FormatException(pszMessage, pex, pszThread);
504     printf("\n%s", pszMessage);
505 }
506
507 void PrintException(std::exception* pex, const char* pszThread)
508 {
509     char pszMessage[1000];
510     FormatException(pszMessage, pex, pszThread);
511     printf("\n\n************************\n%s\n", pszMessage);
512     fprintf(stderr, "\n\n************************\n%s\n", pszMessage);
513     if (wxTheApp && !fDaemon && fGUI)
514         MyMessageBox(pszMessage, "Error", wxOK | wxICON_ERROR);
515     throw;
516     //DebugBreak();
517 }
518
519
520
521
522
523
524
525
526 #ifdef __WXMSW__
527 typedef WINSHELLAPI BOOL (WINAPI *PSHGETSPECIALFOLDERPATHA)(HWND hwndOwner, LPSTR lpszPath, int nFolder, BOOL fCreate);
528
529 string MyGetSpecialFolderPath(int nFolder, bool fCreate)
530 {
531     char pszPath[MAX_PATH+100] = "";
532
533     // SHGetSpecialFolderPath isn't always available on old Windows versions
534     HMODULE hShell32 = LoadLibraryA("shell32.dll");
535     if (hShell32)
536     {
537         PSHGETSPECIALFOLDERPATHA pSHGetSpecialFolderPath =
538             (PSHGETSPECIALFOLDERPATHA)GetProcAddress(hShell32, "SHGetSpecialFolderPathA");
539         if (pSHGetSpecialFolderPath)
540             (*pSHGetSpecialFolderPath)(NULL, pszPath, nFolder, fCreate);
541         FreeModule(hShell32);
542     }
543
544     // Backup option
545     if (pszPath[0] == '\0')
546     {
547         if (nFolder == CSIDL_STARTUP)
548         {
549             strcpy(pszPath, getenv("USERPROFILE"));
550             strcat(pszPath, "\\Start Menu\\Programs\\Startup");
551         }
552         else if (nFolder == CSIDL_APPDATA)
553         {
554             strcpy(pszPath, getenv("APPDATA"));
555         }
556     }
557
558     return pszPath;
559 }
560 #endif
561
562 string GetDefaultDataDir()
563 {
564     // Windows: C:\Documents and Settings\username\Application Data\Bitcoin
565     // Mac: ~/Library/Application Support/Bitcoin
566     // Unix: ~/.bitcoin
567 #ifdef __WXMSW__
568     // Windows
569     return MyGetSpecialFolderPath(CSIDL_APPDATA, true) + "\\Bitcoin";
570 #else
571     char* pszHome = getenv("HOME");
572     if (pszHome == NULL || strlen(pszHome) == 0)
573         pszHome = (char*)"/";
574     string strHome = pszHome;
575     if (strHome[strHome.size()-1] != '/')
576         strHome += '/';
577 #ifdef __WXOSX__
578     // Mac
579     strHome += "Library/Application Support/";
580     _mkdir(strHome.c_str());
581     return strHome + "Bitcoin";
582 #else
583     // Unix
584     return strHome + ".bitcoin";
585 #endif
586 #endif
587 }
588
589 void GetDataDir(char* pszDir)
590 {
591     // pszDir must be at least MAX_PATH length.
592     if (pszSetDataDir[0] != 0)
593     {
594         strlcpy(pszDir, pszSetDataDir, MAX_PATH);
595         static bool fMkdirDone;
596         if (!fMkdirDone)
597         {
598             fMkdirDone = true;
599             _mkdir(pszDir);
600         }
601     }
602     else
603     {
604         // This can be called during exceptions by printf, so we cache the
605         // value so we don't have to do memory allocations after that.
606         static char pszCachedDir[MAX_PATH];
607         if (pszCachedDir[0] == 0)
608         {
609             //strlcpy(pszCachedDir, wxStandardPaths::Get().GetUserDataDir().c_str(), sizeof(pszCachedDir));
610             strlcpy(pszCachedDir, GetDefaultDataDir().c_str(), sizeof(pszCachedDir));
611             _mkdir(pszCachedDir);
612         }
613         strlcpy(pszDir, pszCachedDir, MAX_PATH);
614     }
615 }
616
617 string GetDataDir()
618 {
619     char pszDir[MAX_PATH];
620     GetDataDir(pszDir);
621     return pszDir;
622 }
623
624 string GetConfigFile()
625 {
626     namespace fs = boost::filesystem;
627     fs::path pathConfig(mapArgs.count("-conf") ? mapArgs["-conf"] : string("bitcoin.conf"));
628     if (!pathConfig.is_complete())
629         pathConfig = fs::path(GetDataDir()) / pathConfig;
630     return pathConfig.string();
631 }
632
633 void ReadConfigFile(map<string, string>& mapSettingsRet,
634                     map<string, vector<string> >& mapMultiSettingsRet)
635 {
636     namespace fs = boost::filesystem;
637     namespace pod = boost::program_options::detail;
638
639     fs::ifstream streamConfig(GetConfigFile());
640     if (!streamConfig.good())
641         return;
642
643     set<string> setOptions;
644     setOptions.insert("*");
645
646     for (pod::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it)
647     {
648         // Don't overwrite existing settings so command line settings override bitcoin.conf
649         string strKey = string("-") + it->string_key;
650         if (mapSettingsRet.count(strKey) == 0)
651             mapSettingsRet[strKey] = it->value[0];
652         mapMultiSettingsRet[strKey].push_back(it->value[0]);
653     }
654 }
655
656 int GetFilesize(FILE* file)
657 {
658     int nSavePos = ftell(file);
659     int nFilesize = -1;
660     if (fseek(file, 0, SEEK_END) == 0)
661         nFilesize = ftell(file);
662     fseek(file, nSavePos, SEEK_SET);
663     return nFilesize;
664 }
665
666 void ShrinkDebugFile()
667 {
668     // Scroll debug.log if it's getting too big
669     string strFile = GetDataDir() + "/debug.log";
670     FILE* file = fopen(strFile.c_str(), "r");
671     if (file && GetFilesize(file) > 10 * 1000000)
672     {
673         // Restart the file with some of the end
674         char pch[200000];
675         fseek(file, -sizeof(pch), SEEK_END);
676         int nBytes = fread(pch, 1, sizeof(pch), file);
677         fclose(file);
678         if (file = fopen(strFile.c_str(), "w"))
679         {
680             fwrite(pch, 1, nBytes, file);
681             fclose(file);
682         }
683     }
684 }
685
686
687
688
689
690
691
692
693 //
694 // "Never go to sea with two chronometers; take one or three."
695 // Our three chronometers are:
696 //  - System clock
697 //  - Median of other server's clocks
698 //  - NTP servers
699 //
700 // note: NTP isn't implemented yet, so until then we just use the median
701 //  of other nodes clocks to correct ours.
702 //
703 int64 GetTime()
704 {
705     return time(NULL);
706 }
707
708 static int64 nTimeOffset = 0;
709
710 int64 GetAdjustedTime()
711 {
712     return GetTime() + nTimeOffset;
713 }
714
715 void AddTimeData(unsigned int ip, int64 nTime)
716 {
717     int64 nOffsetSample = nTime - GetTime();
718
719     // Ignore duplicates
720     static set<unsigned int> setKnown;
721     if (!setKnown.insert(ip).second)
722         return;
723
724     // Add data
725     static vector<int64> vTimeOffsets;
726     if (vTimeOffsets.empty())
727         vTimeOffsets.push_back(0);
728     vTimeOffsets.push_back(nOffsetSample);
729     printf("Added time data, samples %d, offset %+"PRI64d" (%+"PRI64d" minutes)\n", vTimeOffsets.size(), vTimeOffsets.back(), vTimeOffsets.back()/60);
730     if (vTimeOffsets.size() >= 5 && vTimeOffsets.size() % 2 == 1)
731     {
732         sort(vTimeOffsets.begin(), vTimeOffsets.end());
733         int64 nMedian = vTimeOffsets[vTimeOffsets.size()/2];
734         nTimeOffset = nMedian;
735         if ((nMedian > 0 ? nMedian : -nMedian) > 36 * 60 * 60)
736         {
737             // Only let other nodes change our clock so far before we
738             // go to the NTP servers
739             /// todo: Get time from NTP servers, then set a flag
740             ///    to make sure it doesn't get changed again
741             nTimeOffset = 0;
742         }
743         foreach(int64 n, vTimeOffsets)
744             printf("%+"PRI64d"  ", n);
745         printf("|  nTimeOffset = %+"PRI64d"  (%+"PRI64d" minutes)\n", nTimeOffset, nTimeOffset/60);
746     }
747 }