1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "nel/misc/types_nl.h"
23 #include "nel/misc/common.h"
26 # include <ShellAPI.h>
31 #define pclose _pclose
33 #elif defined NL_OS_MAC
34 # include <ApplicationServices/ApplicationServices.h>
35 #elif defined NL_OS_UNIX
42 #define MAX_LINE_WIDTH 256
44 #include "nel/misc/command.h"
45 #include "nel/misc/path.h"
46 #include "nel/misc/i18n.h"
52 # pragma message( " " )
55 # pragma message( "************************" )
56 # pragma message( "**** FINAL_VERSION *****" )
57 # pragma message( "************************" )
59 # pragma message( "Not using FINAL_VERSION")
60 # endif // FINAL_VERSION
62 # ifdef ASSERT_THROW_EXCEPTION
63 # pragma message( "nlassert throws exceptions" )
65 # pragma message( "nlassert does not throw exceptions" )
66 # endif // ASSERT_THROW_EXCEPTION
68 # ifdef _STLPORT_VERSION
69 # pragma message( "Using STLport" )
71 # pragma message( "Using standard STL" )
72 # endif // _STLPORT_VERSION
74 # pragma message( " " )
76 # if (_MSC_VER >= 1200) && (_MSC_VER < 1400) && (WINVER < 0x0500)
77 //Using VC7 and later lib, need this to compile on VC6
78 extern "C" long _ftol2( double dblSource
) { return _ftol( dblSource
); }
82 #endif // NL_OS_WINDOWS
83 #endif // !NL_COMP_MINGW
86 #ifdef NL_USE_ALIGNED_MEMORY_OPERATORS
88 #ifdef NL_NO_EXCEPTION_SPECS
89 void *operator new(size_t size
)
91 void *p
= aligned_malloc(size
, NL_DEFAULT_MEMORY_ALIGNMENT
);
92 if (p
== NULL
) throw std::bad_alloc();
96 void *operator new[](size_t size
)
98 void *p
= aligned_malloc(size
, NL_DEFAULT_MEMORY_ALIGNMENT
);
99 if (p
== NULL
) throw std::bad_alloc();
103 void operator delete(void *p
) noexcept
108 void operator delete[](void *p
) noexcept
113 void *operator new(size_t size
) throw(std::bad_alloc
)
115 void *p
= aligned_malloc(size
, NL_DEFAULT_MEMORY_ALIGNMENT
);
116 if (p
== NULL
) throw std::bad_alloc();
120 void *operator new[](size_t size
) throw(std::bad_alloc
)
122 void *p
= aligned_malloc(size
, NL_DEFAULT_MEMORY_ALIGNMENT
);
123 if (p
== NULL
) throw std::bad_alloc();
127 void operator delete(void *p
) throw()
132 void operator delete[](void *p
) throw()
138 #endif /* NL_HAS_SSE2 */
142 #define new DEBUG_NEW
149 * Portable Sleep() function that suspends the execution of the calling thread for a number of milliseconds.
150 * Note: the resolution of the timer is system-dependant and may be more than 1 millisecond.
152 void nlSleep( uint32 ms
)
157 // a Sleep(0) "block" the other thread in DEBUG/_CONSOLE, so we clamp
158 ms
= max(ms
, (uint32
)1);
163 #elif defined NL_OS_UNIX
164 //usleep( ms*1000 ); // resolution: 20 ms!
168 ts
.tv_nsec
= (ms
%1000)*1000000;
172 res
= nanosleep( &ts
, &ts
); // resolution: 10 ms (with common scheduling policy)
174 while ( (res
!= 0) && (errno
==EINTR
) );
180 * Returns Thread Id (note: on Linux, Process Id is the same as the Thread Id)
185 return GetCurrentThreadId();
186 #elif defined NL_OS_UNIX
187 return size_t(pthread_self());
188 // doesnt work on linux kernel 2.6 return getpid();
195 * Returns a readable string from a vector of bytes. '\0' are replaced by ' '
197 string
stringFromVector( const vector
<uint8
>& v
, bool limited
)
203 int size
= (int)v
.size ();
204 if (limited
&& size
> 1000)
206 string middle
= "...<buf too big,skip middle part>...";
207 s
.resize (1000 + middle
.size());
208 memcpy (&*s
.begin(), &*v
.begin(), 500);
209 memcpy (&*s
.begin()+500, &*middle
.begin(), middle
.size());
210 memcpy (&*s
.begin()+500+middle
.size(), &*v
.begin()+size
-500, 500);
215 memcpy( &*s
.begin(), &*v
.begin(), v
.size() );
218 // Replace '\0' characters
220 for ( is
=s
.begin(); is
!=s
.end(); ++is
)
222 // remplace non printable char and % with '?' chat
223 if ( ! isprint((uint8
)(*is
)) || (*is
) == '%')
233 s.resize( v.size() );
234 memcpy( &*s.begin(), &*v.begin(), v.size() );
236 // Replace '\0' characters
238 for ( is=s.begin(); is!=s.end(); ++is )
240 // remplace non printable char and % with '?' chat
241 if ( ! isprint((*is)) || (*is) == '%')
251 sint
smprintf( char *buffer
, size_t count
, const char *format
, ... )
256 va_start( args
, format
);
257 ret
= vsnprintf( buffer
, count
, format
, args
);
260 buffer
[count
-1] = '\0';
268 sint64
atoiInt64 (const char *ident
, sint64 base
)
274 nlassert (ident
!= NULL
);
277 if (*ident
== '\0') goto end
;
280 if (*ident
== '+') ident
++;
283 if (*ident
== '-') { neg
= true; ident
++; }
285 while (*ident
!= '\0')
287 if (isdigit((unsigned char)*ident
))
290 number
+= (*ident
)-'0';
292 else if (base
> 10 && islower((unsigned char)*ident
))
295 number
+= (*ident
)-'a'+10;
297 else if (base
> 10 && isupper((unsigned char)*ident
))
300 number
+= (*ident
)-'A'+10;
309 if (neg
) number
= -number
;
313 void itoaInt64 (sint64 number
, char *str
, sint64 base
)
328 char baseTable
[] = "0123456789abcdefghijklmnopqrstuvwyz";
329 for(n
= 0; n
< 64; n
++)
331 sint num
= (sint
)(x
% base
);
332 b
[64 - n
] = baseTable
[num
];
343 for(k
= 64 - n
+ 1; k
<= 64; k
++)
354 uint
raiseToNextPowerOf2(uint v
)
363 uint
getPowerOf2(uint v
)
376 bool isPowerOf2(sint32 v
)
393 string
bytesToHumanReadable (const std::string
&bytes
)
395 return bytesToHumanReadable (atoiInt64(bytes
.c_str()));
398 string
bytesToHumanReadable (uint64 bytes
)
400 static const char *divTable
[]= { "B", "KiB", "MiB", "GiB", "TiB" };
407 if(newres
< 8 || div
> 3)
412 return toString ("%" NL_I64
"u %s", res
, divTable
[div
]);
415 std::string
bytesToHumanReadableUnits (uint64 bytes
, const std::vector
<std::string
> &units
)
417 if (units
.empty()) return "";
420 uint last
= units
.size()-1;
426 if(newres
< 8 || div
> 3 || div
== last
)
431 return toString ("%" NL_I64
"u %s", res
, units
[div
].c_str());
434 uint32
humanReadableToBytes (const string
&str
)
442 if(str
[0]<'0' || str
[0]>'9')
445 if (!fromString(str
, res
))
448 if(str
[str
.size()-1] == 'B')
453 // there's no break and it's **normal**
454 switch (str
[str
.size()-2])
456 // kB/KB, MB, GB and TB are 1000 multiples
457 case 'T': res
*= 1000;
458 case 'G': res
*= 1000;
459 case 'M': res
*= 1000;
460 case 'k': res
*= 1000; break; // kilo symbol should be a lowercase K
461 case 'K': res
*= 1000; break;
464 // KiB, MiB, GiB and TiB are 1024 multiples
468 switch (str
[str
.size()-3])
470 case 'T': res
*= 1024;
471 case 'G': res
*= 1024;
472 case 'M': res
*= 1024;
473 case 'K': res
*= 1024;
485 NLMISC_CATEGORISED_COMMAND(nel
,btohr
, "Convert a bytes number into an human readable number", "<int>")
487 nlunreferenced(rawCommandString
);
488 nlunreferenced(quiet
);
489 nlunreferenced(human
);
491 if (args
.size() != 1)
494 log
.displayNL("%s -> %s", args
[0].c_str(), bytesToHumanReadable(args
[0]).c_str());
500 NLMISC_CATEGORISED_COMMAND(nel
,hrtob
, "Convert a human readable number into a bytes number", "<hr>")
502 nlunreferenced(rawCommandString
);
503 nlunreferenced(quiet
);
504 nlunreferenced(human
);
506 if (args
.size() != 1)
509 log
.displayNL("%s -> %u", args
[0].c_str(), humanReadableToBytes(args
[0]));
515 string
secondsToHumanReadable (uint32 time
)
517 static const char *divTable
[] = { "s", "mn", "h", "d" };
518 static uint divCoef
[] = { 60, 60, 24 };
527 newres
/= divCoef
[div
];
535 return toString ("%u%s", res
, divTable
[div
]);
538 std::string
timestampToHumanReadable(uint32 timestamp
)
541 time_t dtime
= timestamp
;
542 tm
*tms
= localtime(&dtime
);
546 strftime(buffer
, 30, "%Y-%m-%d %H:%M:%S", tms
);
547 return std::string(buffer
);
553 uint32
fromHumanReadable (const std::string
&str
)
559 fromString(str
, val
);
561 switch (str
[str
.size()-1])
563 case 's': return val
; // second
564 case 'n': return val
*60; // minutes (mn)
565 case 'h': return val
*60*60; // hour
566 case 'd': return val
*60*60*24; // day
568 switch (str
[str
.size()-2])
570 case 'k': return val
*1024;
571 case 'm': return val
*1024*1024;
572 case 'g': return val
*1024*1024*1024;
573 default : return val
;
580 NLMISC_CATEGORISED_COMMAND(nel
,stohr
, "Convert a second number into an human readable time", "<int>")
582 nlunreferenced(rawCommandString
);
583 nlunreferenced(quiet
);
584 nlunreferenced(human
);
586 if (args
.size() != 1)
590 fromString(args
[0], seconds
);
591 log
.displayNL("%s -> %s", args
[0].c_str(), secondsToHumanReadable(seconds
).c_str());
596 NLMISC_CATEGORISED_COMMAND(nel
, toLower
, "Convert a string to lowercase", "<string>")
598 nlunreferenced(args
);
599 nlunreferenced(quiet
);
600 nlunreferenced(human
);
602 log
.displayNL("%s", toLower(rawCommandString
).c_str());
607 NLMISC_CATEGORISED_COMMAND(nel
, toUpper
, "Convert a string to uppercase", "<string>")
609 nlunreferenced(args
);
610 nlunreferenced(quiet
);
611 nlunreferenced(human
);
613 log
.displayNL("%s", toUpper(rawCommandString
).c_str());
618 NLMISC_CATEGORISED_COMMAND(nel
, toLowerAscii
, "Convert a string's ascii-characters to lowercase", "<string>")
620 nlunreferenced(args
);
621 nlunreferenced(quiet
);
622 nlunreferenced(human
);
624 log
.displayNL("%s", toLowerAscii(rawCommandString
).c_str());
629 NLMISC_CATEGORISED_COMMAND(nel
, toUpperAscii
, "Convert a string's ascii-characters to uppercase", "<string>")
631 nlunreferenced(args
);
632 nlunreferenced(quiet
);
633 nlunreferenced(human
);
635 log
.displayNL("%s", toUpperAscii(rawCommandString
).c_str());
642 std::string
toLower(const char *str
)
646 uint len
= strlen(str
);
649 for(uint i
= 0; i
< len
; i
++)
651 if( (str
[i
] >= 'A') && (str
[i
] <= 'Z') )
652 res
+= str
[i
] - 'A' + 'a';
659 std::string
toLower(const std::string
&str
)
662 res
.reserve(str
.size());
663 for(uint i
= 0; i
< str
.size(); i
++)
665 if( (str
[i
] >= 'A') && (str
[i
] <= 'Z') )
666 res
+= str
[i
] - 'A' + 'a';
675 char toLower(const char ch
)
677 if( (ch
>= 'A') && (ch
<= 'Z') )
679 return ch
- 'A' + 'a';
687 void toLower(char *str
)
694 if( (*str
>= 'A') && (*str
<= 'Z') )
696 *str
= *str
- 'A' + 'a';
704 std::string
toUpper(const std::string
&str
)
707 res
.reserve(str
.size());
708 for(uint i
= 0; i
< str
.size(); i
++)
710 if( (str
[i
] >= 'a') && (str
[i
] <= 'z') )
711 res
+= str
[i
] - 'a' + 'A';
720 void toUpper(char *str
)
727 if( (*str
>= 'a') && (*str
<= 'z') )
729 *str
= *str
- 'a' + 'A';
735 std::string
toLowerAscii(const std::string
&str
, char replacement
)
738 res
.reserve(str
.size());
739 for (std::string::const_iterator
it(str
.begin()), end(str
.end()); it
!= end
; ++it
)
742 if ((sint8
)(c
+ '\x01') < (sint8
)(' ' + '\x01')) res
+= replacement
;
743 else if (c
>= 'A' && c
<= 'Z') res
+= c
+ ('a' - 'A');
749 void toLowerAscii(char *str
, char replacement
)
751 for (ptrdiff_t i
= 0; str
[i
]; ++i
)
754 if ((sint8
)(c
+ '\x01') < (sint8
)(' ' + '\x01')) str
[i
] = replacement
;
755 else if (c
>= 'A' && c
<= 'Z') str
[i
] = c
+ ('a' - 'A');
760 std::string
toUpperAscii(const std::string
&str
, char replacement
)
763 res
.reserve(str
.size());
764 for (std::string::const_iterator
it(str
.begin()), end(str
.end()); it
!= end
; ++it
)
767 if ((sint8
)(c
+ '\x01') < (sint8
)(' ' + '\x01')) res
+= replacement
;
768 else if (c
>= 'a' && c
<= 'z') res
+= c
- ('a' - 'A');
774 void toUpperAscii(char *str
, char replacement
)
776 for (ptrdiff_t i
= 0; str
[i
]; ++i
)
779 if ((sint8
)(c
+ '\x01') < (sint8
)(' ' + '\x01')) str
[i
] = replacement
;
780 else if (c
>= 'a' && c
<= 'z') str
[i
] = c
- ('a' - 'A');
785 std::string
toLowerAscii(const std::string
&str
)
788 res
.reserve(str
.size());
789 for (std::string::const_iterator
it(str
.begin()), end(str
.end()); it
!= end
; ++it
)
792 if (c
>= 'A' && c
<= 'Z') res
+= c
+ ('a' - 'A');
798 void toLowerAscii(char *str
)
800 for (ptrdiff_t i
= 0; str
[i
]; ++i
)
803 if (c
>= 'A' && c
<= 'Z') str
[i
] = c
+ ('a' - 'A');
808 std::string
toUpperAscii(const std::string
&str
)
811 res
.reserve(str
.size());
812 for (std::string::const_iterator
it(str
.begin()), end(str
.end()); it
!= end
; ++it
)
815 if (c
>= 'a' && c
<= 'z') res
+= c
- ('a' - 'A');
821 void toUpperAscii(char *str
)
823 for (ptrdiff_t i
= 0; str
[i
]; ++i
)
826 if (c
>= 'a' && c
<= 'z') str
[i
] = c
- ('a' - 'A');
831 std::string
toHexa(const uint8
&b
)
833 return toString("%02hhx", b
);
836 std::string
toHexa(const uint8
*data
, uint size
)
840 // hexadecimal string will be always twice the original size
841 res
.reserve(size
* 2);
844 for (uint i
= 0; i
< size
; ++i
)
846 res
+= toHexa(data
[i
]);
852 std::string
toHexa(const std::string
&str
)
854 return toHexa((uint8
*)str
.c_str(), (uint
)str
.length());
857 std::string
toHexa(const char *str
)
859 return toHexa((uint8
*)str
, (uint
)strlen(str
));
862 bool fromHexa(const std::string
&hexa
, uint8
&b
)
864 return fromHexa(hexa
.c_str(), b
);
867 bool fromHexa(const std::string
&hexa
, uint8
*data
)
869 return fromHexa(hexa
.c_str(), data
);
872 bool fromHexa(const std::string
&hexa
, std::string
&str
)
874 return fromHexa(hexa
.c_str(), str
);
877 bool fromHexa(const char *hexa
, uint8
&b
)
882 if (!fromHexa(c1
, x1
)) return false;
883 if (!fromHexa(c2
, x2
)) return false;
890 bool fromHexa(const char *hexa
, uint8
*data
)
892 // length of the string
893 uint len
= strlen(hexa
);
896 for (uint i
= 0; i
< len
; i
+= 2)
898 if (!fromHexa(hexa
+ i
, *(data
++))) return false;
904 bool fromHexa(const char *hexa
, std::string
&str
)
906 str
.resize(strlen(hexa
) * 2);
908 return fromHexa(hexa
, (uint8
*)str
.c_str());
911 bool fromHexa(const char hexa
, uint8
&b
)
913 if (hexa
>= '0' && hexa
<= '9')
919 if (hexa
>= 'A' && hexa
<= 'F')
925 if (hexa
>= 'a' && hexa
<= 'f')
934 static std::vector
<char> makeCharLookupTable(const std::string
&chars
)
936 std::vector
<char> out(256, -1);
937 for(uint i
= 0; i
< chars
.size(); i
++)
943 std::string
encodeURIComponent(const std::string
&in
)
945 static const char hexLookup
[] = "0123456789ABCDEF";
946 static const std::vector
<char> notEscaped(makeCharLookupTable(
947 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
948 "abcdefghijklmnopqrstuvwxyz"
954 return std::string();
958 size_t inSize
= in
.size();
959 size_t outSize
= in
.size();
961 // resize to worst case for smaller strings,
962 // give some replacements for free for larger strings
964 out
.reserve(in
.size() * 3);
966 out
.reserve(in
.size() + 200);
968 for(size_t i
= 0; i
< inSize
; i
++)
971 if (notEscaped
[(uint8
)ch
] == -1)
974 out
+= hexLookup
[(ch
>>4)& 0x0F];
975 out
+= hexLookup
[ch
& 0x0F];
983 // resize back to correct size
989 std::string
decodeURIComponent(const std::string
&in
)
991 if (in
.find("%") == std::string::npos
)
995 out
.resize(in
.size());
997 size_t outIndex
= 0, inSize
= in
.size();
998 for(size_t i
= 0; i
< inSize
; i
++, outIndex
++)
1000 if (in
[i
] == '%' && (i
+2 < inSize
))
1004 if (fromHexa(in
[i
+1], a
) && fromHexa(in
[i
+2], b
))
1006 out
[outIndex
] = (a
<< 4) | b
;
1010 out
[outIndex
] = in
[i
];
1015 out
[outIndex
] = in
[i
];
1018 out
.resize(outIndex
);
1022 std::string
formatThousands(const std::string
& s
)
1025 sint remaining
= (sint
)s
.length() - 1;
1026 static std::string separator
= NLMISC::CI18N::get("uiThousandsSeparator");
1028 // Don't add separator if the number is < 10k
1029 if (remaining
< 4) return s
;
1035 for (i
= remaining
, k
= 0; i
>= 0 && k
< 3; --i
, ++k
)
1037 ns
= s
[i
] + ns
; // New char is added to front of ns
1038 if ( i
> 0 && k
== 2) ns
= separator
+ ns
; // j > 0 means still more digits
1043 while (remaining
>= 0);
1052 Exception::Exception() : _Reason("Unknown Exception")
1054 // nlinfo("Exception will be launched: %s", _Reason.c_str());
1057 Exception::Exception(const std::string
&reason
) : _Reason(reason
)
1059 nlinfo("Exception will be launched: %s", _Reason
.c_str());
1062 Exception::Exception(const char *format
, ...)
1064 NLMISC_CONVERT_VARGS (_Reason
, format
, NLMISC::MaxCStringSize
);
1065 nlinfo("Exception will be launched: %s", _Reason
.c_str());
1068 const char *Exception::what() const throw()
1070 return _Reason
.c_str();
1073 bool killProgram(uint32 pid
)
1076 int res
= kill(pid
, SIGKILL
);
1079 char *err
= strerror (errno
);
1080 nlwarning("Failed to kill '%d' err %d: '%s'", pid
, errno
, err
);
1083 /*#elif defined(NL_OS_WINDOWS)
1084 // it doesn't work because pid != handle and i don't know how to kill a pid or know the real handle of another service (not -1)
1085 int res = TerminateProcess((HANDLE)pid, 888);
1086 nlwarning("Failed to kill '%d' err %d: '%s'", pid, GetLastError (), lpMsgBuf);
1090 nlwarning("kill not implemented on this OS");
1095 bool abortProgram(uint32 pid
)
1098 int res
= kill(pid
, SIGABRT
);
1101 char *err
= strerror (errno
);
1102 nlwarning("Failed to abort '%d' err %d: '%s'", pid
, errno
, err
);
1106 nlwarning("abort not implemented on this OS");
1111 #ifdef NL_OS_WINDOWS
1113 static bool createProcess(const std::string
&programName
, const std::string
&arguments
, bool log
, PROCESS_INFORMATION
&pi
)
1116 memset(&si
, 0, sizeof(si
));
1117 memset(&pi
, 0, sizeof(pi
));
1121 // Enable nlassert/nlstop to display the error reason & callstack
1122 const char *SE_TRANSLATOR_IN_MAIN_MODULE
= "NEL_SE_TRANS";
1125 if (GetEnvironmentVariableA(SE_TRANSLATOR_IN_MAIN_MODULE
, envBuf
, 2) != 0)
1127 SetEnvironmentVariableA(SE_TRANSLATOR_IN_MAIN_MODULE
, NULL
);
1130 wchar_t *sProgramName
= NULL
;
1134 // a .bat file must have first parameter to NULL and use 2nd parameter to pass filename
1135 if (CFile::getExtension(programName
) == "bat")
1137 args
= "\"" + programName
+ "\" " + arguments
;
1141 ucstring ucProgramName
;
1142 ucProgramName
.fromUtf8(programName
);
1144 sProgramName
= new wchar_t[MAX_PATH
];
1145 wcscpy(sProgramName
, (wchar_t*)ucProgramName
.c_str());
1147 // important! we need to specify the executable full path as first argument
1148 args
= toString("\"%s\" ", programName
.c_str()) + arguments
;
1151 // or 0 for a window
1152 BOOL res
= CreateProcessW(sProgramName
, (LPWSTR
)nlUtf8ToWide(args
), NULL
, NULL
, FALSE
, CREATE_DEFAULT_ERROR_MODE
| CREATE_NO_WINDOW
, NULL
, NULL
/* current dir */, &si
, &pi
);
1156 delete [] sProgramName
;
1157 sProgramName
= NULL
;
1164 sint lastError
= getLastError();
1165 nlwarning("LAUNCH: Failed launched '%s' with arg '%s' err %d: '%s'", programName
.c_str(), arguments
.c_str(), lastError
, formatErrorMessage(lastError
).c_str());
1168 CloseHandle( pi
.hProcess
);
1169 CloseHandle( pi
.hThread
);
1179 bool launchProgram(const std::string
&programName
, const std::string
&arguments
, bool log
)
1181 #ifdef NL_OS_WINDOWS
1182 PROCESS_INFORMATION pi
;
1184 if (!createProcess(programName
, arguments
, log
, pi
)) return false;
1186 //nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str());
1187 CloseHandle( pi
.hProcess
);
1188 CloseHandle( pi
.hThread
);
1193 // special OS X case with bundles
1194 if (toLowerAscii(CFile::getExtension(programName
)) == ".app")
1196 // we need to open bundles with "open" command
1197 std::string command
= NLMISC::toString("open \"%s\"", programName
.c_str());
1199 // append arguments if any
1200 if (!arguments
.empty())
1202 command
+= NLMISC::toString(" --args %s", arguments
.c_str());
1205 int res
= system(command
.c_str());
1207 if (!res
) return true;
1211 nlwarning ("LAUNCH: Failed launched '%s' with arg '%s' return code %d", programName
.c_str(), arguments
.c_str(), res
);
1218 static bool firstLaunchProgram
= true;
1220 if (firstLaunchProgram
)
1222 // The aim of this is to avoid defunct process.
1224 // From "man signal":
1226 // According to POSIX (3.3.1.3) it is unspecified what happens when SIGCHLD is set to SIG_IGN. Here
1227 // the BSD and SYSV behaviours differ, causing BSD software that sets the action for SIGCHLD to
1228 // SIG_IGN to fail on Linux.
1231 // But it works fine on my GNU/Linux so I do this because it's easier :) and I don't know exactly
1232 // what to do to be portable.
1233 signal(SIGCHLD
, SIG_IGN
);
1235 firstLaunchProgram
= false;
1238 // convert one arg into several args
1239 vector
<string
> args
;
1240 explodeArguments(arguments
, args
);
1242 // Store the size of each arg
1243 vector
<char *> argv(args
.size()+2);
1245 argv
[i
] = (char *)programName
.c_str();
1246 for (; i
< args
.size(); i
++)
1248 argv
[i
+1] = (char *) args
[i
].c_str();
1252 int status
= vfork ();
1253 /////////////////////////////////////////////////////////
1254 // WARNING : NO MORE INSTRUCTION AFTER VFORK !
1255 // READ VFORK manual
1256 /////////////////////////////////////////////////////////
1259 char *err
= strerror (errno
);
1261 nlwarning("LAUNCH: Failed launched '%s' with arg '%s' err %d: '%s'", programName
.c_str(), arguments
.c_str(), errno
, err
);
1263 else if (status
== 0)
1265 // Exec (the only allowed instruction after vfork)
1266 status
= execvp(programName
.c_str(), &argv
.front());
1270 perror("Failed launched");
1271 _exit(EXIT_FAILURE
);
1276 //nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str());
1285 bool launchProgramArray (const std::string
&programName
, const std::vector
<std::string
> &arguments
, bool log
)
1287 #ifdef NL_OS_WINDOWS
1288 PROCESS_INFORMATION pi
;
1290 std::string argumentsJoined
= joinArguments(arguments
);
1292 if (!createProcess(programName
, argumentsJoined
, log
, pi
)) return false;
1294 //nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str());
1295 CloseHandle( pi
.hProcess
);
1296 CloseHandle( pi
.hThread
);
1301 // special OS X case with bundles
1302 if (toLowerAscii(CFile::getExtension(programName
)) == "app")
1304 // we need to open bundles with "open" command
1305 std::string command
= NLMISC::toString("open \"%s\"", programName
.c_str());
1307 std::string argumentsJoined
= joinArguments(arguments
);
1309 // append arguments if any
1310 if (!argumentsJoined
.empty())
1312 command
+= NLMISC::toString(" --args %s", argumentsJoined
.c_str());
1315 int res
= system(command
.c_str());
1317 if (!res
) return true;
1321 nlwarning ("LAUNCH: Failed launched '%s' with arg '%s' return code %d", programName
.c_str(), argumentsJoined
.c_str(), res
);
1328 static bool firstLaunchProgram
= true;
1330 if (firstLaunchProgram
)
1332 // The aim of this is to avoid defunct process.
1334 // From "man signal":
1336 // According to POSIX (3.3.1.3) it is unspecified what happens when SIGCHLD is set to SIG_IGN. Here
1337 // the BSD and SYSV behaviours differ, causing BSD software that sets the action for SIGCHLD to
1338 // SIG_IGN to fail on Linux.
1341 // But it works fine on my GNU/Linux so I do this because it's easier :) and I don't know exactly
1342 // what to do to be portable.
1343 signal(SIGCHLD
, SIG_IGN
);
1345 firstLaunchProgram
= false;
1348 // Store the size of each arg
1349 vector
<char *> argv(arguments
.size()+2);
1351 argv
[i
] = (char *)programName
.c_str();
1352 for (; i
< arguments
.size(); i
++)
1354 argv
[i
+1] = (char *) arguments
[i
].c_str();
1358 int status
= vfork ();
1359 /////////////////////////////////////////////////////////
1360 // WARNING : NO MORE INSTRUCTION AFTER VFORK !
1361 // READ VFORK manual
1362 /////////////////////////////////////////////////////////
1365 char *err
= strerror (errno
);
1367 nlwarning("LAUNCH: Failed launched '%s' with arg '%s' err %d: '%s'", programName
.c_str(), joinArguments(arguments
).c_str(), errno
, err
);
1369 else if (status
== 0)
1371 // Exec (the only allowed instruction after vfork)
1372 status
= execvp(programName
.c_str(), &argv
.front());
1376 perror("Failed launched");
1377 _exit(EXIT_FAILURE
);
1382 //nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str());
1391 sint
launchProgramAndWaitForResult(const std::string
&programName
, const std::string
&arguments
, bool log
)
1393 #ifdef NL_OS_WINDOWS
1394 PROCESS_INFORMATION pi
;
1396 if (!createProcess(programName
, arguments
, log
, pi
)) return -1;
1398 // Successfully created the process. Wait for it to finish.
1399 DWORD ret
= WaitForSingleObject(pi
.hProcess
, INFINITE
);
1401 if (ret
== WAIT_OBJECT_0
)
1403 // Get the exit code.
1405 BOOL ok
= GetExitCodeProcess(pi
.hProcess
, &exitCode
);
1407 //nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str());
1408 CloseHandle(pi
.hProcess
);
1409 CloseHandle(pi
.hThread
);
1411 if (ok
) return (sint
)exitCode
;
1416 std::string error
= toString((uint
)ret
);
1418 if (ret
== WAIT_FAILED
)
1420 error
+= "(" + formatErrorMessage(getLastError()) +")";
1423 nlwarning("LAUNCH: Failed launched '%s' with arg '%s' and error: %s", programName
.c_str(), arguments
.c_str(), error
.c_str());
1428 // program name is the only required string
1429 std::string command
= programName
;
1431 // only appends arguments if any
1432 if (!arguments
.empty()) command
+= " " + arguments
;
1434 // execute the command
1435 sint res
= system(command
.c_str());
1438 nlwarning ("LAUNCH: Failed launched '%s' with arg '%s' return code %d", programName
.c_str(), arguments
.c_str(), res
);
1444 std::string
getCommandOutput(const std::string
&command
)
1446 FILE *pipe
= popen(command
.c_str(), "r");
1448 if (!pipe
) return "";
1450 char buffer
[MAX_LINE_WIDTH
];
1455 if (fgets(buffer
, MAX_LINE_WIDTH
, pipe
) != NULL
) result
+= buffer
;
1463 std::string
expandEnvironmentVariables(const std::string
&s
)
1465 size_t len
= s
.length();
1468 std::string::size_type pos1
= 0, pos2
= 0;
1470 // look for environement variables delimiters
1471 while(pos2
< len
&& (pos1
= s
.find_first_of("%$", pos2
)) != std::string::npos
)
1473 // copy string unprocessed part
1474 ret
+= s
.substr(pos2
, pos1
-pos2
);
1476 // extract a valid variable name (a-zA-Z0-9_)
1479 while(pos2
< len
&& (isalnum(s
[pos2
]) || s
[pos2
] == '_')) ++pos2
;
1481 // check if variable name is empty
1482 bool found
= pos2
> pos1
+1;
1488 // found at least 1 character
1489 name
= s
.substr(pos1
+1, pos2
-pos1
-1);
1492 // Windows format needs a trailing % delimiter
1493 if (found
&& s
[pos1
] == '%')
1495 if (pos2
>= len
|| s
[pos2
] != '%')
1497 // not a variable name, because no trailing %
1502 // found a trailing %, next character to check
1507 // get variable value if name found
1510 const char *value
= getenv(name
.c_str());
1515 ret
+= std::string(value
);
1521 nlwarning("Environment variable '%s' not found, won't be replaced", name
.c_str());
1527 // variable or value not found, don't evaluate variable
1528 ret
+= s
.substr(pos1
, pos2
-pos1
);
1532 // copy last unprocessed part
1533 ret
+= s
.substr(pos2
);
1538 bool explodeArguments(const std::string
&str
, std::vector
<std::string
> &args
)
1540 if (str
.empty()) return false;
1542 std::string::size_type pos1
= 0, pos2
= 0;
1546 // Look for the first non space character
1547 pos1
= str
.find_first_not_of (" ", pos2
);
1548 if (pos1
== std::string::npos
) break;
1550 // Look for the first space or "
1551 pos2
= str
.find_first_of (" \"", pos1
);
1552 if (pos2
!= std::string::npos
)
1555 if (str
[pos2
] == '"')
1557 // Look for the final \"
1558 pos2
= str
.find_first_of ("\"", pos2
+1);
1559 if (pos2
!= std::string::npos
)
1561 // Look for the first space
1562 pos2
= str
.find_first_of (" ", pos2
+1);
1567 // Compute the size of the string to extract
1568 std::string::difference_type length
= (pos2
!= std::string::npos
) ? pos2
-pos1
: std::string::npos
;
1570 std::string tmp
= str
.substr (pos1
, length
);
1572 // remove escape " from argument
1573 if (tmp
.length() > 1 && tmp
[0] == '"' && tmp
[tmp
.length()-1] == '"') tmp
= tmp
.substr(1, tmp
.length()-2);
1575 args
.push_back (tmp
);
1577 while(pos2
!= std::string::npos
);
1582 std::string
joinArguments(const std::vector
<std::string
> &args
)
1586 for(uint i
= 0, len
= (uint
)args
.size(); i
< len
; ++i
)
1588 const std::string
&arg
= args
[i
];
1591 if (!res
.empty()) res
+= " ";
1593 // escape only if spaces or empty argument
1594 if (arg
.empty() || arg
.find(' ') != std::string::npos
)
1596 res
+= "\"" + arg
+ "\"";
1607 std::string
escapeArgument(const std::string
&arg
)
1609 #ifdef NL_OS_WINDOWS
1610 // we can't escape %VARIABLE% on command-line under Windows
1613 // characters to escape, only " and $ (to prevent a $something replaced by an environment variable)
1614 static const char s_charsToEscape
[] = "\"$";
1617 std::string::size_type pos
= 0, lastPos
= 0;
1619 // to avoid reallocations
1620 res
.reserve(arg
.size() * 2);
1622 while ((pos
= arg
.find_first_of(s_charsToEscape
, lastPos
)) != std::string::npos
)
1624 // add previous part
1625 res
+= arg
.substr(lastPos
, pos
- lastPos
);
1627 // not already escaped
1628 if (!pos
|| arg
[pos
- 1] != '\\') res
+= '\\';
1630 // add escaped character
1636 res
+= arg
.substr(lastPos
);
1643 * Display the bits (with 0 and 1) composing a byte (from right to left)
1645 void displayByteBits( uint8 b
, uint nbits
, sint beginpos
, bool displayBegin
, NLMISC::CLog
*log
)
1649 for ( i
=nbits
-1; i
!=-1; --i
)
1651 s1
+= ( (b
>> i
) & 1 ) ? '1' : '0';
1653 log
->displayRawNL( "%s", s1
.c_str() );
1656 for ( i
=nbits
; i
>beginpos
+1; --i
)
1661 log
->displayRawNL( "%s beginpos=%u", s2
.c_str(), beginpos
);
1666 //#define displayDwordBits(a,b,c)
1669 * Display the bits (with 0 and 1) composing a number (uint32) (from right to left)
1671 void displayDwordBits( uint32 b
, uint nbits
, sint beginpos
, bool displayBegin
, NLMISC::CLog
*log
)
1675 for ( i
=nbits
-1; i
!=-1; --i
)
1677 s1
+= ( (b
>> i
) & 1 ) ? '1' : '0';
1679 log
->displayRawNL( "%s", s1
.c_str() );
1682 for ( i
=nbits
; i
>beginpos
+1; --i
)
1687 log
->displayRawNL( "%s beginpos=%u", s2
.c_str(), beginpos
);
1691 FILE* nlfopen(const std::string
&filename
, const std::string
&mode
)
1693 #ifdef NL_OS_WINDOWS
1694 return _wfopen(nlUtf8ToWide(filename
), nlUtf8ToWide(mode
));
1696 return fopen(filename
.c_str(), mode
.c_str());
1700 int nlfseek64( FILE *stream
, sint64 offset
, int origin
)
1702 #ifdef NL_OS_WINDOWS
1709 if (fgetpos(stream
, &pos64
) != 0)
1712 pos64
= _filelengthi64(_fileno(stream
));
1720 // Set the final position
1721 return fsetpos (stream
, &pos64
);
1723 #else // NL_OS_WINDOWS
1724 // TODO: to fix for Linux and Mac OS X
1726 // This code doesn't work under windows : fseek() implementation uses a signed 32 bits offset. What ever we do, it can't seek more than 2 Go.
1727 // For the moment, i don't know if it works under linux for seek of more than 2 Go.
1729 nlassert ((offset
< SINT64_CONSTANT(2147483647)) && (offset
> SINT64_CONSTANT(-2147483648)));
1734 // Get the size of the next fseek
1737 nextSeek
= (sint
)std::min ((sint64
)SINT64_CONSTANT(2147483647), offset
);
1739 nextSeek
= (sint
)std::max ((sint64
)-SINT64_CONSTANT(2147483648), offset
);
1742 int result
= fseek ( stream
, nextSeek
, first
?origin
:SEEK_CUR
);
1754 #endif // NL_OS_WINDOWS
1757 sint64
nlftell64(FILE *stream
)
1759 #ifdef NL_OS_WINDOWS
1761 if (fgetpos(stream
, &pos64
) == 0)
1763 return (sint64
) pos64
;
1767 nlunreferenced(stream
);
1769 // TODO: implement for Linux and Mac OS X
1770 nlerror("Not implemented");
1776 //////////////////////////////////////////////////////////////////////////
1777 //////////////////////////////////////////////////////////////////////////
1779 //////////////////////////////////////////////////////////////////////////
1780 //////////////////////////////////////////////////////////////////////////
1782 NLMISC_CATEGORISED_COMMAND(nel
, sleep
, "Freeze the service for N seconds (for debug purpose)", "<N>")
1784 nlunreferenced(rawCommandString
);
1785 nlunreferenced(quiet
);
1786 nlunreferenced(human
);
1788 if(args
.size() != 1) return false;
1791 fromString(args
[0], n
);
1793 log
.displayNL ("Sleeping during %d seconds", n
);
1799 NLMISC_CATEGORISED_COMMAND(nel
, system
, "Execute the command line using system() function call (wait until the end of the command)", "<commandline>")
1801 nlunreferenced(rawCommandString
);
1802 nlunreferenced(quiet
);
1803 nlunreferenced(human
);
1805 if(args
.size() != 1) return false;
1807 string cmd
= args
[0];
1808 log
.displayNL ("Executing '%s'", cmd
.c_str());
1809 sint error
= system(cmd
.c_str());
1812 log
.displayNL ("Execution of '%s' failed with error code %d", cmd
.c_str(), error
);
1816 log
.displayNL ("End of Execution of '%s'", cmd
.c_str());
1821 NLMISC_CATEGORISED_COMMAND(nel
, launchProgram
, "Execute the command line using launcProgram() function call (launch in background task without waiting the end of the execution)", "<programName> <arguments>")
1823 nlunreferenced(rawCommandString
);
1824 nlunreferenced(quiet
);
1825 nlunreferenced(human
);
1827 if(args
.size() != 2) return false;
1829 string cmd
= args
[0];
1830 string arg
= args
[1];
1831 log
.displayNL ("Executing '%s' with argument '%s'", cmd
.c_str(), arg
.c_str());
1832 launchProgram(cmd
, arg
);
1833 log
.displayNL ("End of Execution of '%s' with argument '%s'", cmd
.c_str(), arg
.c_str());
1837 NLMISC_CATEGORISED_COMMAND(nel
, killProgram
, "kill a program given the pid", "<pid>")
1839 nlunreferenced(rawCommandString
);
1840 nlunreferenced(quiet
);
1841 nlunreferenced(human
);
1843 if(args
.size() != 1) return false;
1845 fromString(args
[0], pid
);
1850 #ifdef NL_OS_WINDOWS
1851 LONG
GetRegKey(HKEY key
, LPCWSTR subkey
, LPWSTR retdata
)
1854 LONG retval
= RegOpenKeyExW(key
, subkey
, 0, KEY_QUERY_VALUE
, &hkey
);
1856 if (retval
== ERROR_SUCCESS
)
1858 long datasize
= MAX_PATH
;
1859 wchar_t data
[MAX_PATH
];
1860 RegQueryValueW(hkey
, NULL
, data
, &datasize
);
1861 lstrcpyW(retdata
, data
);
1867 #endif // NL_OS_WINDOWS
1869 static bool openDocWithExtension (const std::string
&document
, const std::string
&ext
)
1871 #ifdef NL_OS_WINDOWS
1872 // First try ShellExecute()
1873 HINSTANCE result
= ShellExecuteW(NULL
, L
"open", nlUtf8ToWide(document
), NULL
, NULL
, SW_SHOWDEFAULT
);
1875 // If it failed, get the .htm regkey and lookup the program
1876 if ((uintptr_t)result
<= HINSTANCE_ERROR
)
1878 wchar_t key
[MAX_PATH
+ MAX_PATH
];
1880 // get the type of the extension
1881 if (GetRegKey(HKEY_CLASSES_ROOT
, nlUtf8ToWide("." + ext
), key
) == ERROR_SUCCESS
)
1883 lstrcatW(key
, L
"\\shell\\open\\command");
1885 // get the command used to open a file with this extension
1886 if (GetRegKey(HKEY_CLASSES_ROOT
, key
, key
) == ERROR_SUCCESS
)
1888 std::string program
= wideToUtf8(key
);
1891 if (program
.empty()) return false;
1893 if (program
[0] == '"')
1895 // program is quoted
1896 std::string::size_type pos
= program
.find('"', 1);
1898 if (pos
!= std::string::npos
)
1900 // take part before next quote
1901 program
= program
.substr(1, pos
- 1);
1906 // program has a parameter
1907 std::string::size_type pos
= program
.find(' ', 1);
1909 if (pos
!= std::string::npos
)
1911 // take part before first space
1912 program
= program
.substr(0, pos
);
1917 PROCESS_INFORMATION pi
;
1918 return createProcess(program
, document
, false, pi
);
1926 #elif defined(NL_OS_MAC)
1927 CFURLRef url
= CFURLCreateWithBytes(NULL
, (const UInt8
*)document
.c_str(), document
.length(), kCFStringEncodingUTF8
, NULL
);
1931 OSStatus res
= LSOpenCFURLRef(url
, 0);
1936 nlwarning("LSOpenCFURLRef %s returned %d", document
.c_str(), (sint
)res
);
1944 nlwarning("Unable to create URL from %s", document
.c_str());
1948 std::string command
= "/usr/bin/xdg-open";
1950 if (!CFile::fileExists(command
))
1954 command
= "/etc/alternatives/x-www-browser";
1956 if (!CFile::fileExists(command
))
1967 if (command
.empty())
1969 nlwarning("Unable to open %s", document
.c_str());
1973 // save LD_LIBRARY_PATH
1974 const char *previousEnv
= getenv("LD_LIBRARY_PATH");
1976 // clear LD_LIBRARY_PATH to avoid problems with Steam Runtime
1977 if (previousEnv
) setenv("LD_LIBRARY_PATH", "", 1);
1979 bool res
= launchProgram(command
, document
);
1981 // restore previous LD_LIBRARY_PATH
1982 if (previousEnv
) setenv("LD_LIBRARY_PATH", previousEnv
, 1);
1985 #endif // NL_OS_WINDOWS
1990 bool openURL(const std::string
&url
)
1992 return openDocWithExtension(url
, "htm");
1995 bool openDoc(const std::string
&document
)
1997 // get extension from document fullpath
1998 string ext
= CFile::getExtension(document
);
2000 // try to open document
2001 return openDocWithExtension(document
, ext
);