132 const std::vector<std::string>& args,
133 const std::string& subjectCode,
134 const std::string& language,
138 printf(
"Warning: Experiment already running\n");
142 std::string peblPath = GetPEBLExecutablePath();
145 std::string workingDir;
146 size_t lastSlash = scriptPath.find_last_of(
"/\\");
147 if (lastSlash != std::string::npos) {
148 workingDir = scriptPath.substr(0, lastSlash);
154 std::string scriptFilename;
155 if (lastSlash != std::string::npos) {
156 scriptFilename = scriptPath.substr(lastSlash + 1);
158 scriptFilename = scriptPath;
162 std::vector<std::string> fullArgs;
165 for (
const auto& arg : args) {
166 fullArgs.push_back(arg);
170 if (!subjectCode.empty()) {
171 fullArgs.push_back(
"-s");
172 fullArgs.push_back(subjectCode);
176 if (!language.empty()) {
177 fullArgs.push_back(
"--language");
178 fullArgs.push_back(language);
183 fullArgs.push_back(
"--fullscreen");
189 std::string winPeblPath = NormalizeWindowsPath(peblPath);
190 std::string winWorkingDir = NormalizeWindowsPath(workingDir);
192 std::string cmdLine =
"\"" + winPeblPath +
"\" \"" + scriptFilename +
"\"";
195 for (
const auto& arg : fullArgs) {
197 if (arg.find(
' ') != std::string::npos) {
198 cmdLine +=
"\"" + arg +
"\"";
204 printf(
"Windows CreateProcess: cmd=%s, workdir=%s\n", cmdLine.c_str(), winWorkingDir.c_str());
207 mStdoutBuffer.clear();
208 mStderrBuffer.clear();
211 SECURITY_ATTRIBUTES sa;
212 sa.nLength =
sizeof(SECURITY_ATTRIBUTES);
213 sa.bInheritHandle = TRUE;
214 sa.lpSecurityDescriptor =
NULL;
217 HANDLE hStdoutRead, hStdoutWrite;
218 if (!CreatePipe(&hStdoutRead, &hStdoutWrite, &sa, 0)) {
219 printf(
"Failed to create stdout pipe: %lu\n", GetLastError());
223 SetHandleInformation(hStdoutRead, HANDLE_FLAG_INHERIT, 0);
226 HANDLE hStderrRead, hStderrWrite;
227 if (!CreatePipe(&hStderrRead, &hStderrWrite, &sa, 0)) {
228 printf(
"Failed to create stderr pipe: %lu\n", GetLastError());
229 CloseHandle(hStdoutRead);
230 CloseHandle(hStdoutWrite);
234 SetHandleInformation(hStderrRead, HANDLE_FLAG_INHERIT, 0);
237 mStdoutReadPipe = hStdoutRead;
238 mStdoutWritePipe = hStdoutWrite;
239 mStderrReadPipe = hStderrRead;
240 mStderrWritePipe = hStderrWrite;
243 PROCESS_INFORMATION pi;
244 ZeroMemory(&si,
sizeof(si));
246 si.hStdOutput = hStdoutWrite;
247 si.hStdError = hStderrWrite;
248 si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
249 si.dwFlags |= STARTF_USESTDHANDLES;
250 ZeroMemory(&pi,
sizeof(pi));
255 const_cast<char*
>(cmdLine.c_str()),
261 winWorkingDir.c_str(),
265 printf(
"Failed to create process: %s\n", cmdLine.c_str());
266 printf(
"Working directory was: %s\n", winWorkingDir.c_str());
267 printf(
"GetLastError: %lu\n", GetLastError());
269 CloseHandle(hStdoutRead);
270 CloseHandle(hStdoutWrite);
271 CloseHandle(hStderrRead);
272 CloseHandle(hStderrWrite);
273 mStdoutReadPipe =
nullptr;
274 mStdoutWritePipe =
nullptr;
275 mStderrReadPipe =
nullptr;
276 mStderrWritePipe =
nullptr;
280 mProcessHandle = pi.hProcess;
281 mProcessId = pi.dwProcessId;
282 CloseHandle(pi.hThread);
285 CloseHandle(hStdoutWrite);
286 CloseHandle(hStderrWrite);
287 mStdoutWritePipe =
nullptr;
288 mStderrWritePipe =
nullptr;
294 if (pipe(mStdoutPipe) < 0 || pipe(mStderrPipe) < 0) {
295 printf(
"Failed to create pipes\n");
300 fcntl(mStdoutPipe[0], F_SETFL, O_NONBLOCK);
301 fcntl(mStderrPipe[0], F_SETFL, O_NONBLOCK);
304 mStdoutBuffer.clear();
305 mStderrBuffer.clear();
310 printf(
"Failed to fork process\n");
311 close(mStdoutPipe[0]);
312 close(mStdoutPipe[1]);
313 close(mStderrPipe[0]);
314 close(mStderrPipe[1]);
320 close(mStdoutPipe[0]);
321 close(mStderrPipe[0]);
323 dup2(mStdoutPipe[1], STDOUT_FILENO);
324 dup2(mStderrPipe[1], STDERR_FILENO);
326 close(mStdoutPipe[1]);
327 close(mStderrPipe[1]);
330 if (chdir(workingDir.c_str()) != 0) {
331 fprintf(stderr,
"Failed to change directory to: %s\n", workingDir.c_str());
335 std::vector<char*> argv;
336 argv.push_back(
const_cast<char*
>(peblPath.c_str()));
337 argv.push_back(
const_cast<char*
>(scriptFilename.c_str()));
339 for (
const auto& arg : fullArgs) {
340 argv.push_back(
const_cast<char*
>(arg.c_str()));
343 argv.push_back(
nullptr);
345 execvp(peblPath.c_str(), argv.data());
348 fprintf(stderr,
"Failed to execute: %s\n", peblPath.c_str());
353 close(mStdoutPipe[1]);
354 close(mStderrPipe[1]);
362 printf(
"Launched experiment: %s (PID: %lu) in directory: %s\n",
363 scriptFilename.c_str(), (
unsigned long)mProcessId, workingDir.c_str());
366 mCurrentScript = scriptPath;
367 mCurrentSubject = subjectCode;
368 mCurrentLanguage = language;
369 mCurrentFullscreen = fullscreen;
370 mLaunchTime = std::time(
nullptr);
371 LogLaunch(scriptPath, subjectCode, language, fullscreen);
481 char docPath[MAX_PATH];
482 if (SUCCEEDED(SHGetFolderPathA(
NULL, CSIDL_PERSONAL,
NULL, 0, docPath))) {
483 logPath = std::string(docPath);
487 const char* homeDir = getenv(
"HOME");
489 struct passwd* pw = getpwuid(getuid());
490 homeDir = pw->pw_dir;
493 std::string docDir = std::string(homeDir) +
"/Documents";
495 if (stat(docDir.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
503 const char* versions[] = {
514 const char* separator =
"\\";
516 const char* separator =
"/";
519 for (
const char* version : versions) {
520 std::string peblPath = logPath + separator + version;
522 if (stat(peblPath.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
524 std::string logsPath = peblPath + separator +
"logs";
526 if (stat(logsPath.c_str(), &logsSt) != 0 || !S_ISDIR(logsSt.st_mode)) {
529 CreateDirectoryA(logsPath.c_str(),
NULL);
531 mkdir(logsPath.c_str(), 0755);
534 return logsPath + separator +
"launcher-log.csv";
539 return logPath + separator +
"launcher-log.csv";
636 if (mStdoutReadPipe) {
637 HANDLE hPipe =
static_cast<HANDLE
>(mStdoutReadPipe);
638 DWORD bytesAvailable = 0;
639 while (PeekNamedPipe(hPipe,
NULL, 0,
NULL, &bytesAvailable,
NULL) && bytesAvailable > 0) {
641 DWORD bytesToRead = (bytesAvailable <
sizeof(buffer) - 1) ? bytesAvailable :
sizeof(buffer) - 1;
643 if (ReadFile(hPipe, buffer, bytesToRead, &bytesRead,
NULL) && bytesRead > 0) {
644 buffer[bytesRead] =
'\0';
645 mStdoutBuffer.append(buffer);
653 if (mStderrReadPipe) {
654 HANDLE hPipe =
static_cast<HANDLE
>(mStderrReadPipe);
655 DWORD bytesAvailable = 0;
656 while (PeekNamedPipe(hPipe,
NULL, 0,
NULL, &bytesAvailable,
NULL) && bytesAvailable > 0) {
658 DWORD bytesToRead = (bytesAvailable <
sizeof(buffer) - 1) ? bytesAvailable :
sizeof(buffer) - 1;
660 if (ReadFile(hPipe, buffer, bytesToRead, &bytesRead,
NULL) && bytesRead > 0) {
661 buffer[bytesRead] =
'\0';
662 mStderrBuffer.append(buffer);
670 if (mStdoutPipe[0] >= 0) {
673 while ((bytesRead = read(mStdoutPipe[0], buffer,
sizeof(buffer) - 1)) > 0) {
674 buffer[bytesRead] =
'\0';
675 mStdoutBuffer.append(buffer);
680 if (mStderrPipe[0] >= 0) {
683 while ((bytesRead = read(mStderrPipe[0], buffer,
sizeof(buffer) - 1)) > 0) {
684 buffer[bytesRead] =
'\0';
685 mStderrBuffer.append(buffer);