/* * Copyright (c) 2005-2019 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (c) 2010 Michael J. Wood * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Cross-platform file execution interface. */ #include #include #include #ifdef AG_ENABLE_EXEC #if defined(_XBOX) # include #elif defined(_WIN32) # undef SLIST_ENTRY # include #endif #include #ifdef _MK_HAVE_SIGNAL #include #endif AG_ProcessID AG_Execute(const char *file, char **argv) { #ifdef _XBOX AG_LAUNCH_DATA launchData = { LDT_TITLE }; char xbePath[AG_PATHNAME_MAX]; char xbeName[AG_FILENAME_MAX]; char argstr[AG_ARG_MAX]; char mntDev[AG_PATHNAME_MAX]; char *p; DWORD xbeID; int i = 0; if(!file) { AG_SetErrorS(_("No file provided for execution")); return (-1); } /* Get the destination xbe path */ if(!argv || !argv[0] || (file && strcmp(file, argv[0]))) { p = (char *)file; } else { p = argv[0]; i++; } /* Handle the command-line parameters */ argstr[0] = '\0'; if(argv) { while(argv[i] != NULL) { if( (AG_ARG_MAX - strlen(argstr) < strlen(argv[i]) + 1) ) { AG_SetError(_("%s: Arguments exceed AG_ARG_MAX (%d)"), p, AG_ARG_MAX); return (-1); } Strlcat(argstr, argv[i], sizeof(argstr)); Strlcat(argstr, " ", sizeof(argstr)); i++; } if (Strlcpy(launchData.szCmdLine, argstr, sizeof(argstr)) >= sizeof(argstr)) { AG_SetErrorS(_("Command argument is too long")); return (-1); } } /* Resolve the full xbe path */ if((strlen(p) >= 7) && (!strncmp(p, "\\Device", 7))) { /* The xbe path was passed with the partition mapping */ Strlcpy(xbePath, p, AG_PATHNAME_MAX); } else { char drive[3]; char *dev; if(strlen(p) > 3 && isalpha(p[0]) && p[1] == ':' && p[2] == AG_PATHSEPCHAR) { /* The xbe path was passed with a drive letter */ Strlcpy(drive, p, sizeof(drive)); p = &p[3]; } else { /* Path is relative */ Strlcpy(drive, "D:", sizeof(drive)); } if((dev = AG_XBOX_GetDeviceFromLogicalDrive(drive)) == NULL) { AG_SetErrorS(_("Invalid or unsupported drive letter. " "Please provide a valid drive letter or the full device path")); return (-1); } Strlcpy(xbePath, dev, sizeof(xbePath)); if(xbePath[strlen(xbePath) - 1] != AG_PATHSEPCHAR && p[0] != AG_PATHSEPCHAR) Strlcat(xbePath, AG_PATHSEP, sizeof(xbePath)); Strlcat(xbePath, p, sizeof(xbePath)); Free(dev); } /* Isolate the xbe name */ p = strrchr(xbePath, '\\') + 1; if(!p) { AG_SetErrorS(_("No XBE Name included with path")); return (-1); } Strlcpy(xbeName, p, AG_FILENAME_MAX); /* mntDev will be the D: path for the new xbe */ Strlcpy(mntDev, xbePath, p - xbePath); mntDev[p - xbePath] = '\0'; /* Get the xbe ID */ if((xbeID = AG_XBOX_GetXbeTitleId(xbePath)) == -1) { AG_SetErrorS(_("XBE is invalid or corrupted")); return (-1); } /* Complete the launch data */ Strlcpy(launchData.szLauncherXBE, XeImageFileName->Buffer, XeImageFileName->Length + 1); Strlcpy(launchData.szLaunchedXBE, xbePath, sizeof(xbePath)); /* Get the launcher ID */ launchData.dwID = AG_XBOX_GetXbeTitleId(launchData.szLauncherXBE); launchData.magic = AG_LAUNCH_MAGIC; /* If this call succeeds the Agar application will be terminated so any configs need to be saved prior to this call. */ XWriteTitleInfoAndRebootA(xbeName, mntDev, LDT_TITLE, xbeID, &launchData); /* If we are here an error occurred */ AG_SetError("XWriteTitleInfoAndRebootA failed"); return (-1); #elif defined(_WIN32) STARTUPINFOA si; PROCESS_INFORMATION pi; char argstr[AG_ARG_MAX]; int i = 0; if(!file) { AG_SetErrorS(_("No file provided for execution")); return (-1); } ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); if(file && strncmp(file, argv[0], strlen(file))) { Strlcpy(argstr, file, sizeof(argstr)); Strlcat(argstr, " ", sizeof(argstr)); } else { Strlcpy(argstr, argv[0], sizeof(argstr)); Strlcat(argstr, " ", sizeof(argstr)); i++; } // Add the command-line parameters while(argv[i] != NULL) { if( (AG_ARG_MAX - strlen(argstr) < strlen(argv[i]) + 1) ) { AG_SetError(_("%s: Arguments exceed AG_ARG_MAX (%d)"), file, AG_ARG_MAX); return (-1); } Strlcat(argstr, argv[i], sizeof(argstr)); Strlcat(argstr, " ", sizeof(argstr)); i++; } if(CreateProcessA(NULL, argstr, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == 0) { AG_SetError(_("Failed to execute (%s)"), AG_Strerror(GetLastError())); return (-1); } CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return (pi.dwProcessId); #elif defined(HAVE_EXECVP) && !defined(_WIN32) AG_ProcessID pid; if(!file) { AG_SetErrorS(_("No file provided for execution")); return (-1); } if((pid = fork()) == -1) { AG_SetError(_("Fork failed (%s)"), AG_Strerror(errno)); return (-1); } else if(pid == 0) { execvp(file, argv); // If we get here an error occurred _exit(EXIT_FAILURE); } else { return (pid); } #endif AG_SetErrorS(_("AG_Execute() is not supported on this platform")); return (-1); } /* * Obtain status information about an executing process. * If wait_t is AG_EXEC_WAIT_IMMEDIATE, peek at its status and return immediately. * If wait_t is AG_EXEC_WAIT_INFINITE, block until the process completes its * execution or is killed. */ AG_ProcessID AG_WaitOnProcess(AG_ProcessID pid, enum ag_exec_wait_type wait_t) { #if defined(_WIN32) && !defined(_XBOX) int time = 0, res; DWORD status; HANDLE psHandle; if (wait_t == AG_EXEC_WAIT_INFINITE) time = INFINITE; if ((psHandle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, pid)) == NULL) { AG_SetError(_("Unable to obtain process handle (%s)"), AG_Strerror(GetLastError())); return (-1); } res = WaitForSingleObject(psHandle, time); if (res) { if(res == WAIT_TIMEOUT) { return (0); } else if (res == WAIT_FAILED) { AG_SetError(_("Wait on process failed (%s)"), AG_Strerror(GetLastError())); return (-1); } } if (GetExitCodeProcess(psHandle, &status) == 0) { AG_SetError(_("Failed to obtain process exit code (%s)"), AG_Strerror(GetLastError())); return (-1); } else if (status) { AG_SetError(_("Process exited with status (%d)"), (int)status); return (-1); } CloseHandle(psHandle); return (int)(pid); #elif defined(HAVE_EXECVP) && !defined(_WIN32) int res, status, options = 0; if (wait_t == AG_EXEC_WAIT_IMMEDIATE) options = WNOHANG; res = waitpid(pid, &status, options); if (res == -1) { AG_SetError(_("waitpid() failed with error (%s)"), AG_Strerror(errno)); return (-1); } else if (res > 0 && status) { if(WIFEXITED(status)) { AG_SetError(_("Process exited with status (%d)"), WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) { AG_SetError(_("Process terminated by signal (%d)"), WTERMSIG(status)); } else { AG_SetErrorS(_("Process exited for unknown reason")); } return (-1); } return (res); #endif AG_SetErrorS(_("AG_WaitOnProcess() is not supported on this platform")); return (-1); } /* Send an immediate termination signal to the given process. */ int AG_Kill(AG_ProcessID pid) { #if defined(_WIN32) && !defined(_XBOX) HANDLE psHandle; #endif if (pid <= 0) { AG_SetErrorS(_("Invalid process id")); return (-1); } #if defined(_WIN32) && !defined(_XBOX) if ((psHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION, FALSE, pid)) == NULL) { AG_SetError(_("Unable to obtain process handle (%s)"), AG_Strerror(GetLastError())); return (-1); } if (TerminateProcess(psHandle, -1) == 0) { AG_SetError(_("Unable to kill process (%s)"), AG_Strerror(GetLastError())); return (-1); } CloseHandle(psHandle); return (0); #elif defined(_MK_HAVE_SIGNAL) && !defined(__CC65__) if (kill(pid, SIGKILL) == -1) { AG_SetError(_("Failed to kill process (%s)"), AG_Strerror(errno)); return (-1); } return (0); #endif AG_SetErrorS(_("AG_Kill() is not supported on this platform")); return (-1); } #endif /* AG_ENABLE_EXEC */