timemory 3.3.0
Modular C++ Toolkit for Performance Analysis and Logging. Profiling API and Tools for C, C++, CUDA, Fortran, and Python. The C++ template API is essentially a framework to creating tools: it is designed to provide a unifying interface for recording various performance measurements alongside data logging and interfaces to other tools.
popen.cpp
Go to the documentation of this file.
1// MIT License
2//
3// Copyright (c) 2020, The Regents of the University of California,
4// through Lawrence Berkeley National Laboratory (subject to receipt of any
5// required approvals from the U.S. Dept. of Energy). All rights reserved.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to deal
9// in the Software without restriction, including without limitation the rights
10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in all
15// copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23// SOFTWARE.
24//
25
27
28#if !defined(TIMEMORY_WINDOWS)
29
32
33# include <limits>
34
35# if !defined(OPEN_MAX)
36# define OPEN_MAX 1024
37# endif
38
39# if !defined(NGROUPS_MAX)
40# define NGROUPS_MAX 16
41# endif
42
43extern "C"
44{
45 extern char** environ;
46}
47
48namespace tim
49{
50namespace popen
51{
52//
53//--------------------------------------------------------------------------------------//
54//
56{
57 int ngroups = -1;
58 gid_t group_id;
59 uid_t user_id;
61};
62//
63//--------------------------------------------------------------------------------------//
64//
65inline group_info&
67{
68 static group_info _instance;
69 return _instance;
70}
71//
72//--------------------------------------------------------------------------------------//
73//
74void
75drop_privileges(int permanent)
76{
77 gid_t newgid = getgid();
78 gid_t oldgid = getegid();
79 uid_t newuid = getuid();
80 uid_t olduid = geteuid();
81
82 if(permanent == 0)
83 {
84 // Save information about the privileges that are being dropped so that they
85 // can be restored later.
86 //
87 get_group_info().group_id = oldgid;
88 get_group_info().user_id = olduid;
89 get_group_info().ngroups = getgroups(NGROUPS_MAX, get_group_info().groups);
90 }
91
92 // If root privileges are to be dropped, be sure to pare down the ancillary
93 // groups for the process before doing anything else because the setgroups( )
94 // system call requires root privileges. Drop ancillary groups regardless of
95 // whether privileges are being dropped temporarily or permanently.
96 //
97 if(olduid == 0)
98 setgroups(1, &newgid);
99
100 if(newgid != oldgid)
101 {
102# if !defined(TIMEMORY_LINUX)
103 auto ret = setegid(newgid);
104 if(ret != 0)
105 abort();
106 if(permanent != 0 && setgid(newgid) == -1)
107 abort();
108# else
109 if(setregid(((permanent != 0) ? newgid : oldgid), newgid) == -1)
110 abort();
111# endif
112 }
113
114 if(newuid != olduid)
115 {
116# if !defined(TIMEMORY_LINUX)
117 auto ret = seteuid(newuid);
118 if(ret != 0)
119 abort();
120 if(permanent != 0 && setuid(newuid) == -1)
121 abort();
122# else
123 if(setreuid(((permanent != 0) ? newuid : olduid), newuid) == -1)
124 abort();
125# endif
126 }
127
128 // verify that the changes were successful
129 if(permanent != 0)
130 {
131 if(newgid != oldgid && (setegid(oldgid) != -1 || getegid() != newgid))
132 abort();
133 if(newuid != olduid && (seteuid(olduid) != -1 || geteuid() != newuid))
134 abort();
135 }
136 else
137 {
138 if(newgid != oldgid && getegid() != newgid)
139 abort();
140 if(newuid != olduid && geteuid() != newuid)
141 abort();
142 }
143}
144//
145//--------------------------------------------------------------------------------------//
146//
147void
149{
150 if(geteuid() != get_group_info().user_id)
151 if(seteuid(get_group_info().user_id) == -1 ||
152 geteuid() != get_group_info().user_id)
153 abort();
154 if(getegid() != get_group_info().group_id)
155 if(setegid(get_group_info().group_id) == -1 ||
156 getegid() != get_group_info().group_id)
157 abort();
158 if(get_group_info().user_id == 0U)
159 setgroups(get_group_info().ngroups, get_group_info().groups);
160}
161//
162//--------------------------------------------------------------------------------------//
163//
164int
166{
167 FILE* f = nullptr;
168 switch(fd)
169 {
170 case 0: f = freopen("/dev/null", "rb", stdin); break;
171 case 1: f = freopen("/dev/null", "wb", stdout); break;
172 case 2: f = freopen("/dev/null", "wb", stderr); break;
173 default: break;
174 }
175 return (f != nullptr && fileno(f) == fd) ? 1 : 0;
176}
177//
178//--------------------------------------------------------------------------------------//
179//
180void
182{
183 // int fds;
184 struct stat st;
185
186 // Make sure all open descriptors other than the standard ones are closed
187 // if((fds = getdtablesize()) == -1)
188 // fds = OPEN_MAX;
189
190 // closing these files results in the inability to read the pipe from the parent
191 // for(int fd = 3; fd < fds; ++fd)
192 // close(fd);
193
194 // Verify that the standard descriptors are open. If they're not, attempt to
195 // open them using /dev/null. If any are unsuccessful, abort.
196 for(int fd = 0; fd < 3; ++fd)
197 {
198 if(fstat(fd, &st) == -1 && (errno != EBADF || open_devnull(fd) == 0))
199 {
200 abort();
201 }
202 }
203}
204//
205//--------------------------------------------------------------------------------------//
206//
207pid_t
209{
210 pid_t childpid;
211
212 if((childpid = ::fork()) == -1)
213 return -1;
214
215 // If this is the parent process, there's nothing more to do
216 if(childpid != 0)
217 return childpid;
218
219 // This is the child process
220 popen::sanitize_files(); // Close all open files.
221 popen::drop_privileges(1); // Permanently drop privileges.
222
223 return 0;
224}
225//
226//--------------------------------------------------------------------------------------//
227//
228TIMEMORY_PIPE*
229popen(const char* path, char** argv, char** envp)
230{
231 int stdin_pipe[2] = { 0, 0 };
232 int stdout_pipe[2] = { 0, 0 };
233 TIMEMORY_PIPE* p = nullptr;
234
235 static char** _argv = []() {
236 static auto* _tmp = new char*[1];
237 _tmp[0] = nullptr;
238 return _tmp;
239 }();
240
241 if(envp == nullptr)
242 envp = environ;
243 if(argv == nullptr)
244 argv = _argv;
245
246 p = new TIMEMORY_PIPE;
247
248 if(!p)
249 return nullptr;
250
251 p->read_fd = nullptr;
252 p->write_fd = nullptr;
253 p->child_pid = -1;
254
255 if(pipe(stdin_pipe) == -1)
256 {
257 delete p;
258 return nullptr;
259 }
260
261 if(pipe(stdout_pipe) == -1)
262 {
263 close(stdin_pipe[1]);
264 close(stdin_pipe[0]);
265 delete p;
266 return nullptr;
267 }
268
269 if(!(p->read_fd = fdopen(stdout_pipe[0], "r")))
270 {
271 close(stdout_pipe[1]);
272 close(stdout_pipe[0]);
273 close(stdin_pipe[1]);
274 close(stdin_pipe[0]);
275 delete p;
276 return nullptr;
277 }
278
279 if(!(p->write_fd = fdopen(stdin_pipe[1], "w")))
280 {
281 fclose(p->read_fd);
282 close(stdout_pipe[1]);
283 close(stdin_pipe[1]);
284 close(stdin_pipe[0]);
285 delete p;
286 return nullptr;
287 }
288
289 if((p->child_pid = popen::fork()) == -1)
290 {
291 fclose(p->write_fd);
292 fclose(p->read_fd);
293 close(stdout_pipe[1]);
294 close(stdin_pipe[0]);
295 delete p;
296 return nullptr;
297 }
298
299 if(p->child_pid == 0)
300 {
301 // this is the child process
302 close(stdout_pipe[0]);
303 close(stdin_pipe[1]);
304 if(stdin_pipe[0] != 0)
305 {
306 dup2(stdin_pipe[0], 0);
307 close(stdin_pipe[0]);
308 }
309 if(stdout_pipe[1] != 1)
310 {
311 dup2(stdout_pipe[1], 1);
312 close(stdout_pipe[1]);
313 }
314 execve(path, argv, envp);
315 exit(127);
316 }
317
318 close(stdout_pipe[1]);
319 close(stdin_pipe[0]);
320
321 return p;
322}
323//
324//--------------------------------------------------------------------------------------//
325//
326int
328{
329 int status = p->child_status;
330 pid_t pid = -1;
331
332 // clean up memory
333 auto _clean = [&]() {
334 if(p->read_fd)
335 fclose(p->read_fd);
336 if(p->write_fd)
337 fclose(p->write_fd);
338 delete p;
339 };
340
341 if(status != std::numeric_limits<int>::max())
342 {
343 _clean();
344 if(WIFEXITED(status))
345 {
346 // printf("process %i exited, status=%d\n", p->child_pid,
347 // WEXITSTATUS(status));
348 return EXIT_SUCCESS;
349 }
350 else if(WIFSIGNALED(status))
351 {
352 printf("process %i killed by signal %d\n", p->child_pid, WTERMSIG(status));
353 return EXIT_FAILURE;
354 }
355 else if(WIFSTOPPED(status))
356 {
357 printf("process %i stopped by signal %d\n", p->child_pid, WSTOPSIG(status));
358 // return EXIT_FAILURE;
359 }
360 else if(WIFCONTINUED(status))
361 {
362 printf("process %i continued\n", p->child_pid);
363 // return EXIT_FAILURE;
364 }
365 }
366 else
367 {
368 if(p->child_pid != -1)
369 {
370 do
371 {
372 pid = waitpid(p->child_pid, &status, 0);
373 } while(pid == -1 && errno == EINTR);
374 }
375 }
376 _clean();
377 if(pid != -1 && WIFEXITED(status))
378 return WEXITSTATUS(status);
379 return (pid == -1 ? -1 : 0);
380}
381//
382//--------------------------------------------------------------------------------------//
383//
386{
387 int counter = 0;
388 strvec_t linked_libraries;
389
390 while(proc)
391 {
392 char buffer[4096];
393 auto* ret = fgets(buffer, 4096, proc->read_fd);
394 if(ret == nullptr || strlen(buffer) == 0)
395 {
396 if(max_counter == 0)
397 {
398 pid_t cpid = waitpid(proc->child_pid, &proc->child_status, WNOHANG);
399 if(cpid == 0)
400 continue;
401 else
402 break;
403 }
404 if(counter++ > max_counter)
405 break;
406 continue;
407 }
408 auto line = string_t(buffer);
409 auto loc = string_t::npos;
410 while((loc = line.find_first_of("\n\t")) != string_t::npos)
411 line.erase(loc, 1);
412 auto delim = delimit(line, " \n\t=>");
413 for(const auto& itr : delim)
414 {
415 if(itr.find('/') == 0)
416 linked_libraries.push_back(itr);
417 }
418 }
419
420 return linked_libraries;
421}
422//
423//--------------------------------------------------------------------------------------//
424//
425std::ostream&
426flush_output(std::ostream& os, TIMEMORY_PIPE* proc, int max_counter)
427{
428 int counter = 0;
429 while(proc)
430 {
431 char buffer[4096];
432 auto* ret = fgets(buffer, 4096, proc->read_fd);
433 if(ret == nullptr || strlen(buffer) == 0)
434 {
435 if(max_counter == 0)
436 {
437 pid_t cpid = waitpid(proc->child_pid, &proc->child_status, WNOHANG);
438 if(cpid == 0)
439 continue;
440 else
441 break;
442 }
443 if(counter++ > max_counter)
444 break;
445 continue;
446 }
447 os << string_t{ buffer } << std::flush;
448 }
449
450 return os;
451}
452//
453//--------------------------------------------------------------------------------------//
454//
455} // namespace popen
456} // namespace tim
457
458#else
459
460namespace
461{
462int windows_popen = 0;
463}
464#endif
::tim::statistics< Tp > max(::tim::statistics< Tp > lhs, const Tp &rhs)
Definition: statistics.hpp:320
char char ** envp
Definition: popen.hpp:69
void drop_privileges(int permanent)
Definition: popen.cpp:75
TIMEMORY_PIPE * popen(const char *path, char **argv, char **envp)
Definition: popen.cpp:229
int max_counter
Definition: popen.hpp:90
void restore_privileges()
Definition: popen.cpp:148
std::ostream & flush_output(std::ostream &os, TIMEMORY_PIPE *proc, int max_counter)
Definition: popen.cpp:426
std::vector< string_t > strvec_t
Definition: popen.hpp:58
void sanitize_files()
Definition: popen.cpp:181
int open_devnull(int fd)
Definition: popen.cpp:165
std::string string_t
Definition: popen.hpp:57
int pclose(TIMEMORY_PIPE *p)
Definition: popen.cpp:327
strvec_t read_fork(TIMEMORY_PIPE *proc, int max_counter)
Definition: popen.cpp:385
pid_t fork()
Definition: popen.cpp:208
char ** argv
Definition: popen.hpp:69
gid_t groups[16]
Definition: popen.cpp:60
group_info & get_group_info()
Definition: popen.cpp:66
Definition: kokkosp.cpp:39
const std::string std::ostream * os
ContainerT delimit(const std::string &line, const std::string &delimiters="\"',;: ", PredicateT &&predicate=[](const std::string &s) -> std::string { return s;})
Definition: delimit.hpp:68
char ** environ
#define NGROUPS_MAX
Definition: popen.cpp:40