1// This file is part of SmallBASIC
2//
3// lowlevel device (OS) I/O
4//
5// This program is distributed under the terms of the GPL v2.0 or later
6// Download the GNU Public License (GPL) from www.gnu.org
7//
8// Copyright(C) 2000 Nicholas Christopoulos
9
10#include "common/device.h"
11
12#include <stdio.h>
13#include <time.h>
14
15#if defined(_UnixOS)
16#include <sys/time.h>
17#include <sys/types.h>
18#include <sys/stat.h>
19#include <unistd.h>
20#elif defined(_Win32)
21#include <windows.h>
22#include <process.h>
23#include <dir.h>
24#endif
25
26extern char **environ;
27
28#define BUFSIZE 1024
29
30#if defined(_Win32)
31
32/**
33 * w32 run process and capture stdout/stderr using pipes
34 *
35 * returns a newly allocated string with the result or NULL
36 *
37 * warning: if the cmd is a GUI process, the shell will hang
38 */
39int shell(const char *cmd, var_t *r) {
40 HANDLE hPipeRead, hPipeWrite;
41
42 SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) };
43 // Pipe handles are inherited by child process.
44 saAttr.bInheritHandle = TRUE;
45 saAttr.lpSecurityDescriptor = NULL;
46
47 // Create a pipe to get results from child's stdout.
48 if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) {
49 return 0;
50 }
51
52 STARTUPINFO si = { sizeof(STARTUPINFO) };
53 si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
54 si.hStdOutput = hPipeWrite;
55 si.hStdError = hPipeWrite;
56 // Prevents cmd window from flashing. Requires STARTF_USESHOWWINDOW in dwFlags.
57 si.wShowWindow = SW_HIDE;
58
59 PROCESS_INFORMATION pi = { 0 };
60 if (!CreateProcess(NULL, (LPSTR)cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
61 CloseHandle(hPipeWrite);
62 CloseHandle(hPipeRead);
63 return 0;
64 }
65
66 int processEnded = 0;
67 while (!processEnded) {
68 // Give some timeslice (50ms), so we won't waste 100% cpu.
69 processEnded = WaitForSingleObject(pi.hProcess, 50) == WAIT_OBJECT_0;
70
71 // Even if process exited - we continue reading, if there is some data available over pipe.
72 while (1) {
73 char buf[BUFSIZE];
74 DWORD numRead = 0;
75 DWORD numAvail = 0;
76
77 if (!PeekNamedPipe(hPipeRead, NULL, 0, NULL, &numAvail, NULL)) {
78 break;
79 }
80
81 if (!numAvail) {
82 // no data available
83 break;
84 }
85
86 if (!ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, numAvail), &numRead, NULL) ||
87 !numRead) {
88 // child process may have ended
89 break;
90 }
91 buf[numRead] = 0;
92 v_strcat(r, buf);
93 }
94 }
95 CloseHandle(hPipeWrite);
96 CloseHandle(hPipeRead);
97 CloseHandle(pi.hProcess);
98 CloseHandle(pi.hThread);
99 return 1;
100}
101
102int dev_run(const char *cmd, var_t *r, int wait) {
103 int result = 1;
104 if (r != NULL) {
105 v_zerostr(r);
106 result = shell(cmd, r);
107 } else if (wait) {
108 STARTUPINFO si;
109 PROCESS_INFORMATION pi;
110 memset(&si, 0, sizeof(STARTUPINFO));
111 si.cb = sizeof(STARTUPINFO);
112 si.dwFlags = STARTF_USESHOWWINDOW;
113 si.wShowWindow = SW_SHOWNORMAL;
114 if (CreateProcess(NULL, (LPSTR)cmd, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {
115 WaitForSingleObject(pi.hProcess, INFINITE);
116 CloseHandle(pi.hProcess);
117 CloseHandle(pi.hThread);
118 } else {
119 result = 0;
120 }
121 } else {
122 HWND hwnd = GetActiveWindow();
123 ShellExecute(hwnd, "open", cmd, 0, 0, SW_SHOWNORMAL);
124 }
125 return result;
126}
127
128#else
129int dev_run(const char *cmd, var_t *r, int wait) {
130 int result = 1;
131 if (r != NULL) {
132 v_zerostr(r);
133 FILE *fin = popen(cmd, "r");
134 if (fin) {
135 while (!feof(fin)) {
136 char buf[BUFSIZE + 1];
137 int bytes = fread(buf, 1, BUFSIZE, fin);
138 buf[bytes] = '\0';
139 v_strcat(r, buf);
140 }
141 pclose(fin);
142 } else {
143 result = 0;
144 }
145 } else if (wait) {
146 result = (system(cmd) != -1);
147 }
148 else if (fork() == 0) {
149 // exec separate process
150 int size = strlen(cmd) + 3;
151 char *src1 = malloc(size);
152 if (src1 != NULL) {
153 memset(src1, '\0', size);
154 // double quote the command
155 *src1 = '"';
156 strcat(src1, cmd);
157 *(src1 + strlen(cmd) + 1) = '"';
158 // -c means the next argument is the command string to execute
159 // this allow us to execute shell script
160 execlp("sh", "sh", "-c", src1, NULL);
161 }
162 exit(-1);
163 }
164 return result;
165}
166#endif
167
168#if !defined(IMPL_DEV_ENV)
169
170int dev_setenv(const char *key, const char *value) {
171#ifdef __MINGW32__
172 // use leaky putenv
173 unsigned size = snprintf(NULL, 0, "%s=%s", key, value) + 1;
174 char *buf = malloc(size);
175 buf[0] = '\0';
176 snprintf(buf, size, "%s=%s", key, value);
177 return putenv(buf);
178#else
179 return setenv(key, value, 1);
180#endif
181}
182
183/**
184 * The getenv() function searches the environment list for a string that matches the string pointed
185 * to by name. The strings are of the form name = value.
186 *
187 * The getenv() function returns a pointer to the value in the environment, or NULL if there is no match.
188 */
189const char *dev_getenv(const char *str) {
190 return getenv(str);
191}
192
193/**
194 * returns the number of environment variables
195 */
196int dev_env_count() {
197 int count = 0;
198 while (environ[count]) {
199 count++;
200 }
201 return count;
202}
203
204/**
205 * returns the value of the n-th system's environment variable
206 */
207const char *dev_getenv_n(int n) {
208 int count = 0;
209 while (environ[count]) {
210 if (n == count) {
211 return environ[count];
212 }
213 count++;
214 }
215 return NULL;
216}
217#endif
218
219uint32_t dev_get_millisecond_count(void) {
220#if defined(__MACH__)
221 struct timeval t;
222 gettimeofday(&t, NULL);
223 return (uint32_t) (1000L * t.tv_sec + (t.tv_usec / 1000.0));
224#elif defined(_Win32)
225 return GetTickCount();
226#else
227 struct timespec t;
228 t.tv_sec = t.tv_nsec = 0;
229 if (0 == clock_gettime(CLOCK_MONOTONIC, &t)) {
230 return (uint32_t) (1000L * t.tv_sec + (t.tv_nsec / 1e6));
231 } else {
232 struct timeval now;
233 gettimeofday(&now, NULL);
234 return (uint32_t) (1000L * now.tv_sec + (now.tv_usec / 1000.0));
235 }
236#endif
237}
238
239
240