diff --git a/code/nel/include/nel/misc/common.h b/code/nel/include/nel/misc/common.h index dd1c80fb3..4ddc88076 100644 --- a/code/nel/include/nel/misc/common.h +++ b/code/nel/include/nel/misc/common.h @@ -366,6 +366,9 @@ std::string formatThousands(const std::string& s); /// The program will be launched in the current directory bool launchProgram (const std::string &programName, const std::string &arguments, bool log = true); +/// Same but with an array of strings for arguments +bool launchProgramArray (const std::string &programName, const std::vector &arguments, bool log = true); + /// This function executes a program and wait for result (used for example for crash report). /// The program will be launched in the current directory sint launchProgramAndWaitForResult (const std::string &programName, const std::string &arguments, bool log = true); @@ -378,6 +381,10 @@ std::string getCommandOutput(const std::string &command); /// Authorized characters in names are A-Z, a-z, 0-9 and _ std::string expandEnvironmentVariables(const std::string &s); +/// Functions to convert a string with arguments to array or array to string (will espace strings with spaces) +bool explodeArguments(const std::string &str, std::vector &args); +std::string joinArguments(const std::vector &args); + /// This function kills a program using his pid (on unix, it uses the kill() POSIX function) bool killProgram(uint32 pid); diff --git a/code/nel/src/misc/cmd_args.cpp b/code/nel/src/misc/cmd_args.cpp index 647bac008..65aae5769 100644 --- a/code/nel/src/misc/cmd_args.cpp +++ b/code/nel/src/misc/cmd_args.cpp @@ -202,42 +202,13 @@ bool CCmdArgs::parse(const std::string &args) wchar_t str[4096]; uint len = GetModuleFileNameW(NULL, str, 4096); + // first argument should be full path to executable if (len && len < 4096) argv.push_back(wideToUtf8(str)); #endif - std::string::size_type pos1 = 0, pos2 = 0; - - do - { - // Look for the first non space character - pos1 = args.find_first_not_of (" ", pos2); - if(pos1 == std::string::npos) break; - - // Look for the first space or " - pos2 = args.find_first_of (" \"", pos1); - if(pos2 != std::string::npos) - { - // " ? - if(args[pos2] == '"') - { - // Look for the final \" - pos2 = args.find_first_of ("\"", pos2+1); - if(pos2 != std::string::npos) - { - // Look for the first space - pos2 = args.find_first_of (" ", pos2+1); - } - } - } - - // Compute the size of the string to extract - std::string::difference_type length = (pos2 != std::string::npos) ? pos2-pos1 : std::string::npos; - - std::string tmp = args.substr (pos1, length); - argv.push_back (tmp); - } - while(pos2 != std::string::npos); + // convert string with arguments to array + explodeArguments(args, argv); return parse(argv); } diff --git a/code/nel/src/misc/common.cpp b/code/nel/src/misc/common.cpp index c71688174..424a1de20 100644 --- a/code/nel/src/misc/common.cpp +++ b/code/nel/src/misc/common.cpp @@ -831,6 +831,7 @@ bool launchProgram(const std::string &programName, const std::string &arguments, #endif static bool firstLaunchProgram = true; + if (firstLaunchProgram) { // The aim of this is to avoid defunct process. @@ -851,15 +852,7 @@ bool launchProgram(const std::string &programName, const std::string &arguments, // convert one arg into several args vector args; - string::size_type pos1 = 0, pos2 = 0; - do - { - pos1 = arguments.find_first_not_of (" ", pos2); - if (pos1 == string::npos) break; - pos2 = arguments.find_first_of (" ", pos1); - args.push_back (arguments.substr (pos1, pos2-pos1)); - } - while (pos2 != string::npos); + explodeArguments(arguments, args); // Store the size of each arg vector argv(args.size()+2); @@ -904,6 +897,110 @@ bool launchProgram(const std::string &programName, const std::string &arguments, return false; } +bool launchProgramArray (const std::string &programName, const std::vector &arguments, bool log) +{ +#ifdef NL_OS_WINDOWS + PROCESS_INFORMATION pi; + + std::string argumentsJoined = joinArguments(arguments); + + if (!createProcess(programName, argumentsJoined, log, pi)) return false; + + //nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str()); + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + return true; +#else + +#ifdef NL_OS_MAC + // special OS X case with bundles + if (toLower(programName).find(".app") != std::string::npos) + { + // we need to open bundles with "open" command + std::string command = NLMISC::toString("open \"%s\"", programName.c_str()); + + // append arguments if any + if (!arguments.empty()) + { + command += NLMISC::toString(" --args %s", joinArguments(arguments).c_str()); + } + + int res = system(command.c_str()); + + if (!res) return true; + + if (log) + { + nlwarning ("LAUNCH: Failed launched '%s' with arg '%s' return code %d", programName.c_str(), arguments.c_str(), res); + } + + return false; + } +#endif + + static bool firstLaunchProgram = true; + + if (firstLaunchProgram) + { + // The aim of this is to avoid defunct process. + // + // From "man signal": + //------ + // According to POSIX (3.3.1.3) it is unspecified what happens when SIGCHLD is set to SIG_IGN. Here + // the BSD and SYSV behaviours differ, causing BSD software that sets the action for SIGCHLD to + // SIG_IGN to fail on Linux. + //------ + // + // But it works fine on my GNU/Linux so I do this because it's easier :) and I don't know exactly + // what to do to be portable. + signal(SIGCHLD, SIG_IGN); + + firstLaunchProgram = false; + } + + // Store the size of each arg + vector argv(arguments.size()+2); + uint i = 0; + argv[i] = (char *)programName.c_str(); + for (; i < arguments.size(); i++) + { + argv[i+1] = (char *) arguments[i].c_str(); + } + argv[i+1] = NULL; + + int status = vfork (); + ///////////////////////////////////////////////////////// + /// WARNING : NO MORE INSTRUCTION AFTER VFORK ! + /// READ VFORK manual + ///////////////////////////////////////////////////////// + if (status == -1) + { + char *err = strerror (errno); + if (log) + nlwarning("LAUNCH: Failed launched '%s' with arg '%s' err %d: '%s'", programName.c_str(), arguments.c_str(), errno, err); + } + else if (status == 0) + { + // Exec (the only allowed instruction after vfork) + status = execvp(programName.c_str(), &argv.front()); + + if (status == -1) + { + perror("Failed launched"); + _exit(EXIT_FAILURE); + } + } + else + { + //nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str()); + + return true; + } +#endif + + return false; +} + sint launchProgramAndWaitForResult(const std::string &programName, const std::string &arguments, bool log) { #ifdef NL_OS_WINDOWS @@ -1039,6 +1136,75 @@ std::string expandEnvironmentVariables(const std::string &s) return ret; } +bool explodeArguments(const std::string &str, std::vector &args) +{ + if (str.empty()) return false; + + std::string::size_type pos1 = 0, pos2 = 0; + + do + { + // Look for the first non space character + pos1 = str.find_first_not_of (" ", pos2); + if (pos1 == std::string::npos) break; + + // Look for the first space or " + pos2 = str.find_first_of (" \"", pos1); + if (pos2 != std::string::npos) + { + // " ? + if (str[pos2] == '"') + { + // Look for the final \" + pos2 = str.find_first_of ("\"", pos2+1); + if (pos2 != std::string::npos) + { + // Look for the first space + pos2 = str.find_first_of (" ", pos2+1); + } + } + } + + // Compute the size of the string to extract + std::string::difference_type length = (pos2 != std::string::npos) ? pos2-pos1 : std::string::npos; + + std::string tmp = str.substr (pos1, length); + + // remove escape " from argument + if (tmp.length() > 1 && tmp[0] == '"' && tmp[tmp.length()-1] == '"') tmp = tmp.substr(1, tmp.length()-2); + + args.push_back (tmp); + } + while(pos2 != std::string::npos); + + return true; +} + +std::string joinArguments(const std::vector &args) +{ + std::string res; + + for(uint i = 0, len = (uint)args.size(); i < len; ++i) + { + const std::string &arg = args[i]; + + // prepend space + if (!res.empty()) res += " "; + + // escape only if spaces or empty argument + if (arg.empty() || arg.find(' ') != std::string::npos) + { + res += "\"" + arg + "\""; + } + else + { + res += arg; + } + } + + return res; +} + /* * Display the bits (with 0 and 1) composing a byte (from right to left) */ diff --git a/code/ryzom/client/src/login_patch.cpp b/code/ryzom/client/src/login_patch.cpp index 0d0209427..8208d6e28 100644 --- a/code/ryzom/client/src/login_patch.cpp +++ b/code/ryzom/client/src/login_patch.cpp @@ -167,18 +167,18 @@ CPatchManager::CPatchManager() : State("t_state"), DataScanState("t_data_scan_st ForceRemovePatchCategories.push_back("main_exedll_linux64"); ForceRemovePatchCategories.push_back("main_exedll_osx"); #elif defined(NL_OS_WIN32) - ForceRemovePatchCategories.push_back("main_exedll_win34"); + ForceRemovePatchCategories.push_back("main_exedll_win64"); ForceRemovePatchCategories.push_back("main_exedll_linux32"); ForceRemovePatchCategories.push_back("main_exedll_linux64"); ForceRemovePatchCategories.push_back("main_exedll_osx"); #elif defined(NL_OS_APPLE) ForceRemovePatchCategories.push_back("main_exedll_win32"); - ForceRemovePatchCategories.push_back("main_exedll_win34"); + ForceRemovePatchCategories.push_back("main_exedll_win64"); ForceRemovePatchCategories.push_back("main_exedll_linux32"); ForceRemovePatchCategories.push_back("main_exedll_linux64"); #elif defined(NL_OS_UNIX) && defined(_LP64) ForceRemovePatchCategories.push_back("main_exedll_win32"); - ForceRemovePatchCategories.push_back("main_exedll_win34"); + ForceRemovePatchCategories.push_back("main_exedll_win64"); ForceRemovePatchCategories.push_back("main_exedll_linux32"); ForceRemovePatchCategories.push_back("main_exedll_osx"); #else @@ -826,8 +826,8 @@ void CPatchManager::createBatchFile(CProductDescriptionForClient &descFile, bool content += toString("move %s %s\n", realSrcPath.c_str(), realDstPath.c_str()); #else // use DSTPATH and SRCPATH variables and append filenames - string realDstPath = toString("\"$ROOTPATH\\%s\"", batchRelativeDstPath.c_str()); - string realSrcPath = toString("\"$UNPACKPATH\\%s\"", FileName.c_str()); + string realDstPath = toString("\"$ROOTPATH/%s\"", batchRelativeDstPath.c_str()); + string realSrcPath = toString("\"$UNPACKPATH/%s\"", FileName.c_str()); content += toString("rm -rf %s\n", realDstPath.c_str()); content += toString("mv %s %s\n", realSrcPath.c_str(), realDstPath.c_str()); @@ -916,19 +916,21 @@ void CPatchManager::createBatchFile(CProductDescriptionForClient &descFile, bool contentPrefix += "set RYZOM_CLIENT=\"%1\"\n"; contentPrefix += "set UNPACKPATH=\"%2\"\n"; contentPrefix += "set ROOTPATH=\"%3\"\n"; + contentPrefix += toString("set UPGRADE_FILE=\"%%ROOTPATH%%\\%s\"\n", UpgradeBatchFilename.c_str()); + contentPrefix += "\n"; contentPrefix += "set LOGIN=%4\n"; contentPrefix += "set PASSWORD=%5\n"; contentPrefix += "set SHARDID=%6\n"; - contentPrefix += toString("set UPGRADE_FILE=\"%%ROOTPATH%%\\%s\"\n", UpgradeBatchFilename.c_str()); #else contentPrefix += "#!/bin/sh\n"; - contentPrefix += "RYZOM_CLIENT=\"$1\"\n"; - contentPrefix += "UNPACKPATH=\"$2\"\n"; - contentPrefix += "ROOTPATH=\"$3\"\n"; + contentPrefix += "export RYZOM_CLIENT=$1\n"; + contentPrefix += "export UNPACKPATH=$2\n"; + contentPrefix += "export ROOTPATH=$3\n"; + contentPrefix += toString("export UPGRADE_FILE=$ROOTPATH/%s\n", UpgradeBatchFilename.c_str()); + contentPrefix += "\n"; contentPrefix += "LOGIN=$4\n"; contentPrefix += "PASSWORD=$5\n"; contentPrefix += "SHARDID=$6\n"; - contentPrefix += toString("UPGRADE_FILE=\"$ROOTPATH\\%s\"\n", UpgradeBatchFilename.c_str()); #endif contentPrefix += "\n"; @@ -960,15 +962,15 @@ void CPatchManager::createBatchFile(CProductDescriptionForClient &descFile, bool } // launch upgrade script if present (it'll execute additional steps like moving or deleting files) - contentSuffix += "if [ -e \"$UPGRADE_FILE\" ]; then chmod +x \"$UPGRADE_FILE\" && \"$UPGRADE_FILE\"; fi\n"; + contentSuffix += "if [ -e \"$UPGRADE_FILE\" ]; then chmod +x \"$UPGRADE_FILE\" && \"$UPGRADE_FILE\"; fi\n\n"; // be sure file is executable - contentSuffix += "chmod +x \"$RYZOM_CLIENT\"\n"; + contentSuffix += "chmod +x \"$RYZOM_CLIENT\"\n\n"; if (wantRyzomRestart) { // change to previous client directory - contentSuffix += "cd \"$ROOTPATH\"\n"; + contentSuffix += "cd \"$ROOTPATH\"\n\n"; // launch new client contentSuffix += toString("\"$RYZOM_CLIENT\" %s $LOGIN $PASSWORD $SHARDID\n", additionalParams.c_str()); @@ -1020,32 +1022,37 @@ void CPatchManager::executeBatchFile() // make script executable CFile::setRWAccess(batchFilename); - std::string arguments; + std::vector arguments; // 3 first parameters are Ryzom client full path, patch directory full path and client root directory full path #ifdef NL_OS_WINDOWS - arguments += "\"" + CPath::standardizeDosPath(RyzomFilename) + "\" \"" + CPath::standardizeDosPath(ClientPatchPath) + "\" \"" + CPath::standardizeDosPath(ClientRootPath) + "\""; + arguments.push_back(CPath::standardizeDosPath(RyzomFilename)); + arguments.push_back(CPath::standardizeDosPath(ClientPatchPath)); + arguments.push_back(CPath::standardizeDosPath(ClientRootPath)); #else - arguments += "\"" + RyzomFilename + "\" \"" + ClientPatchPath + "\" " + ClientRootPath + "\""; + arguments.push_back(RyzomFilename); + arguments.push_back(ClientPatchPath); + arguments.push_back(ClientRootPath); #endif - // append login, password and shard + // append login, password and shard if (!LoginLogin.empty()) { - arguments += " " + LoginLogin; + arguments.push_back(LoginLogin); if (!LoginPassword.empty()) { - arguments += " " + LoginPassword; + arguments.push_back(LoginPassword); if (!r2Mode) { - arguments += " " + toString(LoginShardId); + arguments.push_back(toString(LoginShardId)); } } } - if (!launchProgram(batchFilename, arguments, false)) + // launchProgram with array of strings as argument will escape arguments with spaces + if (!launchProgramArray(batchFilename, arguments, false)) { // error occurs during the launch string str = toString("Can't execute '%s': code=%d %s (error code 30)", batchFilename.c_str(), errno, strerror(errno));