Browse Source

Implemented a process API as a part of the file API, but with an independent prefix in case that it later needs its own header.

David Piuva 3 years ago
parent
commit
21d8e23713
2 changed files with 147 additions and 4 deletions
  1. 117 0
      Source/DFPSR/api/fileAPI.cpp
  2. 30 4
      Source/DFPSR/api/fileAPI.h

+ 117 - 0
Source/DFPSR/api/fileAPI.cpp

@@ -27,11 +27,18 @@
 #include "fileAPI.h"
 #include "fileAPI.h"
 
 
 #ifdef USE_MICROSOFT_WINDOWS
 #ifdef USE_MICROSOFT_WINDOWS
+	// Headers for MS-Windows
 	#include <windows.h>
 	#include <windows.h>
+	#include <synchapi.h>
 #else
 #else
+	// Headers for Posix compliant systems.
 	#include <unistd.h>
 	#include <unistd.h>
+	#include <spawn.h>
+	#include <sys/wait.h>
 	#include <sys/stat.h>
 	#include <sys/stat.h>
 	#include <dirent.h>
 	#include <dirent.h>
+	// The environment flags contain information such as username, language, color settings, which system shell and window manager is used...
+	extern char **environ;
 #endif
 #endif
 #include <fstream>
 #include <fstream>
 #include <cstdlib>
 #include <cstdlib>
@@ -672,4 +679,114 @@ bool file_removeFile(const ReadableString& filename) {
 	return result;
 	return result;
 }
 }
 
 
+// DsrProcess is a reference counted pointer to DsrProcessImpl where the last retrieved status still remains for all to read.
+//   Because aliasing with multiple users of the same pid would deplete the messages in advance.
+struct DsrProcessImpl {
+	// Once the process has already terminated, process_getStatus will only return lastStatus.
+	bool terminated = false;
+	// We can assume that a newly created process is running until we are told that it terminated or crashed,
+	//   because DsrProcessImpl would not be created unless launching the application was successful.
+	DsrProcessStatus lastStatus = DsrProcessStatus::Running;
+	#ifdef USE_MICROSOFT_WINDOWS
+		PROCESS_INFORMATION processInfo;
+		DsrProcessImpl(const PROCESS_INFORMATION &processInfo)
+		: processInfo(processInfo) {}
+		~DsrProcessImpl() {
+			CloseHandle(processInfo.hProcess);
+			CloseHandle(processInfo.hThread);
+		}
+	#else
+		pid_t pid;
+		DsrProcessImpl(pid_t pid) : pid(pid) {}
+	#endif
+};
+
+DsrProcessStatus process_getStatus(const DsrProcess &process) {
+	if (process.get() == nullptr) {
+		return DsrProcessStatus::NotStarted;
+	} else {
+		if (!process->terminated) {
+			#ifdef USE_MICROSOFT_WINDOWS
+				DWORD status = WaitForSingleObject(process->processInfo.hProcess, 0);
+				if (status == WAIT_OBJECT_0) {
+					DWORD processResult;
+					GetExitCodeProcess(process->processInfo.hProcess, &processResult);
+					process->lastStatus = (processResult == 0) ? DsrProcessStatus::Completed : DsrProcessStatus::Crashed;
+					process->terminated = true;
+				}
+			#else
+				// When using WNOHANG, waitpid returns zero when the program is still running, and the child pid if it terminated.
+				int status = 0;
+				if (waitpid(process->pid, &status, WNOHANG) != 0) {
+					if (WIFEXITED(status)) {
+						process->lastStatus = DsrProcessStatus::Completed;
+						process->terminated = true;
+					} else if (WIFSIGNALED(status)) {
+						process->lastStatus = DsrProcessStatus::Crashed;
+						process->terminated = true;
+					}
+				}
+			#endif
+		}
+		return process->lastStatus;
+	}
+}
+
+DsrProcess process_execute(const ReadableString& programPath, List<String> arguments) {
+	// Convert the program path into the native format.
+	String optimizedPath = file_optimizePath(programPath, LOCAL_PATH_SYNTAX);
+	Buffer pathBuffer;
+	const NativeChar *nativePath = toNativeString(optimizedPath, pathBuffer);
+	// Convert
+	#ifdef USE_MICROSOFT_WINDOWS
+		DsrChar separator = U' ';
+	#else
+		DsrChar separator = U'\0';
+	#endif
+	String flattenedArguments;
+	string_append(flattenedArguments, programPath);
+	string_appendChar(flattenedArguments, U'\0');
+	for (int64_t a = 0; a < arguments.length(); a++) {
+		string_append(flattenedArguments, arguments[a]);
+		string_appendChar(flattenedArguments, U'\0');
+	}
+	Buffer argBuffer;
+	const NativeChar *nativeArgs = toNativeString(flattenedArguments, argBuffer);
+	#ifdef USE_MICROSOFT_WINDOWS
+		STARTUPINFOW startInfo;
+		PROCESS_INFORMATION processInfo;
+		memset(&startInfo, 0, sizeof(STARTUPINFO));
+		memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
+		startInfo.cb = sizeof(STARTUPINFO);
+		if (CreateProcessW(nullptr, (LPWSTR)nativeArgs, nullptr, nullptr, true, 0, nullptr, nullptr, &startInfo, &processInfo)) {
+			return std::make_shared<DsrProcessImpl>(processInfo); // Success
+		} else {
+			return DsrProcess(); // Failure
+		}
+	#else
+		int64_t codePoints = buffer_getSize(argBuffer) / sizeof(NativeChar);
+		// Count arguments.
+		int argc = arguments.length() + 1;
+		// Allocate an array of pointers for each argument and a null terminator.
+		const NativeChar *argv[argc + 1]; // TODO: Implement without VLA.
+		// Fill the array with pointers to the native strings.
+		int64_t startOffset = 0;
+		int currentArg = 0;
+		for (int64_t c = 0; c < codePoints && currentArg < argc; c++) {
+			if (nativeArgs[c] == 0) {
+				argv[currentArg] = &(nativeArgs[startOffset]);
+				startOffset = c + 1;
+				currentArg++;
+			}
+		}
+		argv[currentArg] = nullptr;
+		pid_t pid = 0;
+		if (posix_spawn(&pid, nativePath, nullptr, nullptr, (char* const*)argv, environ) == 0) {
+			return std::make_shared<DsrProcessImpl>(pid); // Success
+		} else {
+			return DsrProcess(); // Failure
+		}
+	#endif
+}
+
 }
 }

+ 30 - 4
Source/DFPSR/api/fileAPI.h

@@ -30,8 +30,10 @@
 	#define USE_MICROSOFT_WINDOWS
 	#define USE_MICROSOFT_WINDOWS
 #endif
 #endif
 
 
-// A module for file access that exists to prevent cyclic dependencies between strings and buffers.
-//   Buffers need a filename to be saved or loaded while strings use buffers to store their characters.
+// The file API exists to save and load buffers of data for any type of file.
+// Any file format that is implemented against the Buffer type instead of hardcoding against the file stream can easily be
+//   reused for additional layers of processing such as checksums, archiving, compression, encryption, sending over a network...
+// The file API also has functions for processing paths to the filesystem and calling other programs.
 namespace dsr {
 namespace dsr {
 	// The PathSyntax enum allow processing theoreical paths for other operating systems than the local.
 	// The PathSyntax enum allow processing theoreical paths for other operating systems than the local.
 	enum class PathSyntax { Windows, Posix };
 	enum class PathSyntax { Windows, Posix };
@@ -215,13 +217,15 @@ namespace dsr {
 	String file_getTheoreticalAbsoluteParentFolder(const ReadableString &path, const ReadableString &currentPath, PathSyntax pathSyntax);
 	String file_getTheoreticalAbsoluteParentFolder(const ReadableString &path, const ReadableString &currentPath, PathSyntax pathSyntax);
 
 
 	// Gets the canonical absolute version of the path.
 	// Gets the canonical absolute version of the path.
+	// Current directory is expanded, but not user accounts.
 	// Path-syntax: According to the local computer.
 	// Path-syntax: According to the local computer.
-	// Post-condition: Returns an absolute version of the path, quickly without removing redundancy.
+	// Post-condition: Returns an absolute version of the path.
 	String file_getAbsolutePath(const ReadableString &path);
 	String file_getAbsolutePath(const ReadableString &path);
 
 
 	// A theoretical version of file_getAbsolutePath for evaluation on a theoretical system without actually calling file_getCurrentPath or running on the given system.
 	// A theoretical version of file_getAbsolutePath for evaluation on a theoretical system without actually calling file_getCurrentPath or running on the given system.
+	// Current directory is expanded, but not user accounts.
 	// Path-syntax: Depends on pathSyntax argument.
 	// Path-syntax: Depends on pathSyntax argument.
-	// Post-condition: Returns an absolute version of the path, quickly without removing redundancy.
+	// Post-condition: Returns an absolute version of the path.
 	String file_getTheoreticalAbsolutePath(const ReadableString &path, const ReadableString &currentPath, PathSyntax pathSyntax IMPLICIT_PATH_SYNTAX);
 	String file_getTheoreticalAbsolutePath(const ReadableString &path, const ReadableString &currentPath, PathSyntax pathSyntax IMPLICIT_PATH_SYNTAX);
 
 
 	// Path-syntax: According to the local computer.
 	// Path-syntax: According to the local computer.
@@ -269,6 +273,28 @@ namespace dsr {
 	// Side-effects: Removes the file at path. If the same file exists at multiple paths in the system, only the link to the file will be removed.
 	// Side-effects: Removes the file at path. If the same file exists at multiple paths in the system, only the link to the file will be removed.
 	// Post-condition: Returns true iff the operation was successful.
 	// Post-condition: Returns true iff the operation was successful.
 	bool file_removeFile(const ReadableString& filename);
 	bool file_removeFile(const ReadableString& filename);
+
+	// The status of a program started using file_execute.
+	enum class DsrProcessStatus {
+		NotStarted, // The handle was default initialized or the result of a failed call to process_execute.
+		Running,    // The process is still running.
+		Crashed,    // The process has crashed and might not have done the task you asked for.
+		Completed   // The process has terminated successfully, so you may now check if it has written any files for you.
+	};
+
+	// A reference counted handle to a process, so that multiple callers can read the status at any time.
+	class DsrProcessImpl;
+	using DsrProcess = std::shared_ptr<DsrProcessImpl>;
+
+	// Post-condition: Returns the status of process.
+	DsrProcessStatus process_getStatus(const DsrProcess &process);
+
+	// Path-syntax: According to the local computer.
+	// Pre-condition: The executable at path must exist and have execution rights.
+	// Side-effects: Starts the program at programPath, with programPath given again as argv[0] and arguments to argv[1...] to the program's main function.
+	// Post-condition: Returns a DsrProcess handle to the started process.
+	//                 On failure to start, the handle is empty and process_getStatus will then return DsrProcessStatus::NotStarted from it.
+	DsrProcess process_execute(const ReadableString& programPath, List<String> arguments);
 }
 }
 
 
 #endif
 #endif