Skip to content

Commit

Permalink
Feature/automatic search for a suitable interpreter on POSIX (#74)
Browse files Browse the repository at this point in the history
* added: automatic search for a suitable interpreter to run extensions on POSIX systems.
* refactored: renamed Script.cpp to a more appropriate ScriptController.cpp name
  • Loading branch information
dnzbk committed Dec 15, 2023
1 parent 3f8fd6d commit ec0756d
Show file tree
Hide file tree
Showing 16 changed files with 97 additions and 42 deletions.
4 changes: 2 additions & 2 deletions Makefile.am
Expand Up @@ -150,8 +150,8 @@ nzbget_SOURCES = \
daemon/util/Container.h \
daemon/util/Observer.cpp \
daemon/util/Observer.h \
daemon/util/Script.cpp \
daemon/util/Script.h \
daemon/util/ScriptController.cpp \
daemon/util/ScriptController.h \
daemon/util/Thread.cpp \
daemon/util/Thread.h \
daemon/util/Service.cpp \
Expand Down
2 changes: 1 addition & 1 deletion daemon/extension/NzbScript.h
Expand Up @@ -21,7 +21,7 @@
#ifndef NZBSCRIPT_H
#define NZBSCRIPT_H

#include "Script.h"
#include "ScriptController.h"
#include "DownloadInfo.h"
#include "ScriptConfig.h"

Expand Down
2 changes: 1 addition & 1 deletion daemon/main/Maintenance.h
Expand Up @@ -22,7 +22,7 @@

#include "NString.h"
#include "Thread.h"
#include "Script.h"
#include "ScriptController.h"
#include "Log.h"
#include "Util.h"

Expand Down
2 changes: 1 addition & 1 deletion daemon/postprocess/Cleanup.h
Expand Up @@ -25,7 +25,7 @@
#include "Log.h"
#include "Thread.h"
#include "DownloadInfo.h"
#include "Script.h"
#include "ScriptController.h"

class MoveController : public Thread, public ScriptController
{
Expand Down
2 changes: 1 addition & 1 deletion daemon/postprocess/DirectUnpack.h
Expand Up @@ -24,7 +24,7 @@
#include "Log.h"
#include "Thread.h"
#include "DownloadInfo.h"
#include "Script.h"
#include "ScriptController.h"

class DirectUnpack : public Thread, public ScriptController
{
Expand Down
2 changes: 1 addition & 1 deletion daemon/postprocess/DupeMatcher.cpp
Expand Up @@ -24,7 +24,7 @@
#include "Util.h"
#include "FileSystem.h"
#include "Options.h"
#include "Script.h"
#include "ScriptController.h"
#include "Thread.h"

class RarLister : public Thread, public ScriptController
Expand Down
2 changes: 1 addition & 1 deletion daemon/postprocess/Rename.h
Expand Up @@ -23,7 +23,7 @@

#include "Thread.h"
#include "DownloadInfo.h"
#include "Script.h"
#include "ScriptController.h"
#include "RarRenamer.h"

#ifndef DISABLE_PARCHECK
Expand Down
2 changes: 1 addition & 1 deletion daemon/postprocess/Repair.h
Expand Up @@ -23,7 +23,7 @@

#include "DownloadInfo.h"
#include "Thread.h"
#include "Script.h"
#include "ScriptController.h"

#ifndef DISABLE_PARCHECK
#include "ParChecker.h"
Expand Down
2 changes: 1 addition & 1 deletion daemon/postprocess/Unpack.h
Expand Up @@ -24,7 +24,7 @@
#include "Log.h"
#include "Thread.h"
#include "DownloadInfo.h"
#include "Script.h"
#include "ScriptController.h"

class UnpackController : public Thread, public ScriptController
{
Expand Down
90 changes: 76 additions & 14 deletions daemon/util/Script.cpp → daemon/util/ScriptController.cpp
Expand Up @@ -14,12 +14,12 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/


#include "nzbget.h"
#include "Script.h"
#include "ScriptController.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
Expand Down Expand Up @@ -236,10 +236,10 @@ void ScriptController::SetEnvVarSpecial(const char* prefix, const char* name, co

void ScriptController::PrepareArgs()
{
const char* extension = strrchr(m_args[0], '.');

if (m_args.size() == 1 && !Util::EmptyStr(g_Options->GetShellOverride()))
{
const char* extension = strrchr(m_args[0], '.');

Tokenizer tok(g_Options->GetShellOverride(), ",;");
while (CString shellover = tok.Next())
{
Expand All @@ -259,14 +259,17 @@ void ScriptController::PrepareArgs()
}
}

PrepareCmdLine(extension);
}

#ifdef WIN32
void ScriptController::PrepareCmdLine(const char* extension)
{
*m_cmdLine = '\0';

if (m_args.size() == 1)
{
// Special support for script languages:
// automatically find the app registered for this extension and run it
const char* extension = strrchr(m_args[0], '.');
if (extension && strcasecmp(extension, ".exe") && strcasecmp(extension, ".bat") && strcasecmp(extension, ".cmd"))
{
debug("Looking for associated program for %s", extension);
Expand All @@ -283,9 +286,9 @@ void ScriptController::PrepareArgs()
{
command[bufLen] = '\0';
CString scommand(command);
scommand.Replace("%L", "%1");// Python 3.x has %L in command instead of %1
scommand.Replace("%L", "%1"); // Python 3.x has %L in command instead of %1
debug("Command: %s", scommand.Str());
DWORD_PTR args[] = {(DWORD_PTR)*m_args[0], (DWORD_PTR)0};
DWORD_PTR args[] = { (DWORD_PTR)*m_args[0], (DWORD_PTR)0 };
if (FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, scommand, 0, 0,
m_cmdLine, sizeof(m_cmdLine), (va_list*)args))
{
Expand All @@ -299,8 +302,68 @@ void ScriptController::PrepareArgs()
extension, FileSystem::BaseFileName(m_args[0]));
}
}
}
#endif

#ifndef WIN32
void ScriptController::PrepareCmdLine(const char* extension)
{
*m_cmdLine = '\0';
m_cmdArgs.clear();
if (m_args.size() == 1)
{
if (strcmp(extension, ".py") == 0)
{
if (std::system("python3 --version > /dev/null 2>&1") == 0)
{
strncpy(m_cmdLine, "python3", sizeof(m_cmdLine));
m_cmdArgs.emplace_back("python3");
debug("CmdLine: %s", m_cmdLine);
return;
}
if (std::system("python --version > /dev/null 2>&1") == 0)
{
strncpy(m_cmdLine, "python", sizeof(m_cmdLine));
m_cmdArgs.emplace_back("python");
debug("CmdLine: %s", m_cmdLine);
return;
}
if (std::system("py --version > /dev/null 2>&1") == 0)
{
strncpy(m_cmdLine, "py", sizeof(m_cmdLine));
m_cmdArgs.emplace_back("py");
debug("CmdLine: %s", m_cmdLine);
return;
}
}
if (strcmp(extension, ".sh") == 0)
{
if (std::system("bash --version > /dev/null 2>&1") == 0)
{
strncpy(m_cmdLine, "bash", sizeof(m_cmdLine));
m_cmdArgs.emplace_back("bash");
debug("CmdLine: %s", m_cmdLine);
return;
}
if (std::system("sh --version > /dev/null 2>&1") == 0)
{
strncpy(m_cmdLine, "sh", sizeof(m_cmdLine));
m_cmdArgs.emplace_back("sh");
debug("CmdLine: %s", m_cmdLine);
return;
}
}
warn("Could not find associated program for %s. Trying to execute %s directly",
extension, FileSystem::BaseFileName(m_args[0]));
strncpy(m_cmdLine, m_args[0], sizeof(m_cmdLine));
return;
}
else
{
strncpy(m_cmdLine, m_args[0], sizeof(m_cmdLine));
}
}
#endif

int ScriptController::Execute()
{
Expand Down Expand Up @@ -580,10 +643,9 @@ void ScriptController::StartProcess(int* pipein, int* pipeout)
std::vector<char*> environmentStrings = m_environmentStrings.GetStrings();
char** envdata = environmentStrings.data();

ArgList args;
std::copy(m_args.begin(), m_args.end(), std::back_inserter(args));
args.emplace_back(nullptr);
char* const* argdata = (char* const*)args.data();
std::copy(std::begin(m_args), std::end(m_args), std::back_inserter(m_cmdArgs));
m_cmdArgs.emplace_back(nullptr);
char* const* argdata = (char* const*)m_cmdArgs.data();

#ifdef DEBUG
debug("Starting process: %s", script);
Expand Down Expand Up @@ -641,7 +703,7 @@ void ScriptController::StartProcess(int* pipein, int* pipeout)
chdir(workingDir);
environ = envdata;

execvp(script, argdata);
execvp(m_cmdLine, argdata);

if (errno == EACCES)
{
Expand All @@ -650,7 +712,7 @@ void ScriptController::StartProcess(int* pipein, int* pipeout)
write(1, "\n", 1);
fsync(1);
FileSystem::FixExecPermission(script);
execvp(script, argdata);
execvp(m_cmdLine, argdata);
}

// NOTE: the text "[ERROR] Could not start " is checked later,
Expand Down
10 changes: 6 additions & 4 deletions daemon/util/Script.h → daemon/util/ScriptController.h
Expand Up @@ -14,12 +14,12 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/


#ifndef SCRIPT_H
#define SCRIPT_H
#ifndef SCRIPTCONTROLLER_H
#define SCRIPTCONTROLLER_H

#include "NString.h"
#include "Container.h"
Expand Down Expand Up @@ -78,6 +78,7 @@ class ScriptController
void ResetEnv();
void PrepareEnvOptions(const char* stripPrefix);
void PrepareArgs();
void PrepareCmdLine(const char* extension);
virtual const char* GetOptValue(const char* name, const char* value) { return value; }
void StartProcess(int* pipein, int* pipeout);
int WaitProcess();
Expand All @@ -90,6 +91,7 @@ class ScriptController

private:
ArgList m_args;
ArgList m_cmdArgs;
const char* m_workingDir = nullptr;
CString m_infoName;
const char* m_logPrefix = nullptr;
Expand All @@ -100,10 +102,10 @@ class ScriptController
bool m_needWrite = false;
FILE* m_readpipe = 0;
FILE* m_writepipe = 0;
char m_cmdLine[2048];
#ifdef WIN32
HANDLE m_processId = 0;
DWORD m_dwProcessId = 0;
char m_cmdLine[2048];
#else
pid_t m_processId = 0;
#endif
Expand Down
4 changes: 2 additions & 2 deletions nzbget.vcxproj
Expand Up @@ -273,7 +273,7 @@
<ClCompile Include="daemon\remote\XmlRpc.cpp" />
<ClCompile Include="daemon\util\Log.cpp" />
<ClCompile Include="daemon\util\Observer.cpp" />
<ClCompile Include="daemon\util\Script.cpp" />
<ClCompile Include="daemon\util\ScriptController.cpp" />
<ClCompile Include="daemon\util\Service.cpp" />
<ClCompile Include="daemon\util\Thread.cpp" />
<ClCompile Include="daemon\util\NString.cpp" />
Expand Down Expand Up @@ -392,7 +392,7 @@
<ClInclude Include="daemon\remote\XmlRpc.h" />
<ClInclude Include="daemon\util\Log.h" />
<ClInclude Include="daemon\util\Observer.h" />
<ClInclude Include="daemon\util\Script.h" />
<ClInclude Include="daemon\util\ScriptController.h" />
<ClInclude Include="daemon\util\Service.h" />
<ClInclude Include="daemon\util\Thread.h" />
<ClInclude Include="daemon\util\NString.h" />
Expand Down
2 changes: 1 addition & 1 deletion scripts/EMail.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
#
# E-Mail post-processing script for NZBGet
#
Expand Down
2 changes: 1 addition & 1 deletion scripts/Logger.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
#
# Logger post-processing script for NZBGet
#
Expand Down
2 changes: 1 addition & 1 deletion tests/postprocess/CMakeLists.txt
Expand Up @@ -20,7 +20,7 @@ file(GLOB PostprocessTestsSrc
${CMAKE_SOURCE_DIR}/daemon/util/Util.cpp
${CMAKE_SOURCE_DIR}/daemon/util/Thread.cpp
${CMAKE_SOURCE_DIR}/daemon/util/Log.cpp
${CMAKE_SOURCE_DIR}/daemon/util/Script.cpp
${CMAKE_SOURCE_DIR}/daemon/util/ScriptController.cpp
${CMAKE_SOURCE_DIR}/daemon/util/FileSystem.cpp
${CMAKE_SOURCE_DIR}/daemon/queue/DownloadInfo.cpp
${CMAKE_SOURCE_DIR}/daemon/queue/DiskState.cpp
Expand Down
9 changes: 0 additions & 9 deletions windows/build-nzbget-vs22.bat
Expand Up @@ -225,15 +225,6 @@ mkdir ..\distrib\NZBGet\scripts
xcopy /E scripts ..\distrib\NZBGet\scripts
if errorlevel 1 goto BUILD_FAILED

rem The default PATH to python changed in most POSIX systems after python2 was deprecated.
rem On Windows, it remained the same.
rem Now we need to add Windows specific shebang (#!/usr/bin/env python) to the scripts.
set "SCRIPTS=..\distrib\NZBGet\scripts\EMail.py ..\distrib\NZBGet\scripts\Logger.py"
for %%F in (%SCRIPTS%) do (
%SED% -e "s|#!/usr/bin/env python3|#!/usr/bin/env python|" -i %%F
)
if errorlevel 1 goto BUILD_FAILED

copy ..\..\image\* ..\distrib\NZBGet
copy ..\..\image\32\* ..\distrib\NZBGet\32
copy ..\..\image\64\* ..\distrib\NZBGet\64
Expand Down

0 comments on commit ec0756d

Please sign in to comment.