yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
d30ae275e
master
1// slang-unix-process.cpp 2#include "../slang-common.h" 3#include "../slang-memory-arena.h" 4#include "../slang-process.h" 5#include "../slang-string-escape-util.h" 6#include "../slang-string-util.h" 7 8#include <stdio.h> 9#include <stdlib.h> 10#include <string.h> 11 12// #include <dirent.h> 13#include <errno.h> 14#include <fcntl.h> 15#include <poll.h> 16#include <sys/stat.h> 17#include <sys/types.h> 18#include <sys/wait.h> 19#include <unistd.h> 20 21#if SLANG_OSX 22#include <signal.h> 23#endif 24 25#include <time.h> 26 27namespace Slang 28{ 29 30class UnixProcess :public Process 31{ 32public : 33// Process 34virtual bool isTerminated ()SLANG_OVERRIDE ; 35virtual bool waitForTermination (Int timeInMs )SLANG_OVERRIDE ; 36virtual void terminate (int32_t returnValue )SLANG_OVERRIDE ; 37virtual void kill (int32_t returnValue )SLANG_OVERRIDE ; 38 39UnixProcess (pid_t pid ,Stream * const * streams ); 40 41protected : 42/// Returns true if terminated 43bool _updateTerminationState (int options ); 44 45bool m_isTerminated = false;///< True if ths process is terminated 46pid_t m_pid ;///< The process id 47}; 48 49class UnixPipeStream :public Stream 50{ 51public : 52typedef UnixPipeStream ThisType ; 53 54// Stream 55virtual Int64 getPosition ()SLANG_OVERRIDE {return 0 ; } 56virtual SlangResult seek (SeekOrigin origin ,Int64 offset )SLANG_OVERRIDE 57 { 58SLANG_UNUSED (origin ); 59SLANG_UNUSED (offset ); 60return SLANG_E_NOT_AVAILABLE ; 61 } 62virtual SlangResult read (void * buffer ,size_t length ,size_t & outReadBytes )SLANG_OVERRIDE ; 63virtual SlangResult write (const void * buffer ,size_t length )SLANG_OVERRIDE ; 64virtual bool isEnd ()SLANG_OVERRIDE {return m_isClosed ; } 65virtual bool canRead ()SLANG_OVERRIDE {return _has (FileAccess ::Read )&& !m_isClosed ; } 66virtual bool canWrite ()SLANG_OVERRIDE {return _has (FileAccess ::Write )&& !m_isClosed ; } 67virtual void close ()SLANG_OVERRIDE ; 68virtual SlangResult flush ()SLANG_OVERRIDE ; 69 70UnixPipeStream (int fd ,FileAccess access ,bool isOwned ) 71 :m_fd (fd ),m_access (access ),m_isOwned (isOwned ),m_isClosed (false) 72 { 73 } 74 75protected : 76/// This read file descriptor non blocking. Doing so will change the behavior of 77/// read - it can fail and return an error indicating there is no data, instead of blocking. 78/// Currently this mechanism isn't used, as checking via poll seemed to work. 79void _setReadNonBlocking () 80 { 81// Makes non blocking 82if (_has (FileAccess ::Read )) 83 { 84// Make non blocking, for read 85fcntl (m_fd ,F_SETFL ,fcntl (m_fd ,F_GETFL ) |O_NONBLOCK ); 86 } 87 } 88bool _has (FileAccess access )const {return (Index (access )& Index (m_access ))!= 0 ; } 89 90bool m_isClosed ;///< If true this stream has been closed (ie cannot read/write to anymore) 91bool m_isOwned ;///< True if m_fd is owned by this object. 92FileAccess m_access ;///< Access allowed to this stream - either Read or Write 93int m_fd ;/// The 'file descriptor' for the pipe 94}; 95 96/* !!!!!!!!!!!!!!!!!!!!!! UnixProcess !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ 97 98UnixProcess ::UnixProcess (pid_t pid ,Stream * const * streams ) 99 :m_pid (pid ) 100{ 101// Set to an 'odd value' 102m_returnValue = -1 ; 103 104for (Index i = 0 ;i < SLANG_COUNT_OF (m_streams );++ i ) 105 { 106m_streams [i ]= streams [i ]; 107 } 108} 109 110bool UnixProcess ::_updateTerminationState (int options ) 111{ 112if (!m_isTerminated ) 113 { 114int childStatus ; 115const pid_t terminatedPid = waitpid (m_pid ,& childStatus ,options ); 116if (terminatedPid == -1 ) 117 { 118// Guess we should just mark as terminated 119m_isTerminated = true; 120 121fprintf (stderr ,"error: `waitpid` failed\n" ); 122 } 123else if (terminatedPid == m_pid ) 124 { 125if (WIFEXITED (childStatus )) 126 { 127m_returnValue = (int )(int8_t )WEXITSTATUS (childStatus ); 128 } 129m_isTerminated = true; 130 } 131 } 132return m_isTerminated ; 133} 134 135bool UnixProcess ::isTerminated () 136{ 137if (m_isTerminated ) 138 { 139return true; 140 } 141return _updateTerminationState (WNOHANG ); 142} 143 144bool UnixProcess ::waitForTermination (Int timeInMs ) 145{ 146// If < 0 we will wait blocking until terminated 147if (timeInMs < 0 ) 148 { 149while (!_updateTerminationState (0 )) 150 ; 151return true; 152 } 153 154// Note that the amount of time waiting is very approximate (we are relying on sleeps time and 155// don't take into account time outside of sleeping) 156 157// How often to test 158const Int checkRateMs = 100 ;/// Check every 0.1 seconds 159 160while (timeInMs > 0 ) 161 { 162if (_updateTerminationState (WNOHANG )) 163 { 164return true; 165 } 166 167// Work out how long to sleep for 168const Int sleepMs = (timeInMs >=checkRateMs ) ?checkRateMs :timeInMs ; 169 170// Sleep 171sleepCurrentThread (sleepMs ); 172 173timeInMs -= sleepMs ; 174 } 175 176return _updateTerminationState (WNOHANG ); 177} 178 179void UnixProcess ::terminate (int32_t returnValue ) 180{ 181// Using this mechanism, we can't set a returnValue so just ignore 182SLANG_UNUSED (returnValue ); 183 184if (!isTerminated ()) 185 { 186// Request the process terminates 187 ::kill (m_pid ,SIGTERM ); 188 } 189} 190 191void UnixProcess ::kill (int32_t returnValue ) 192{ 193if (!isTerminated ()) 194 { 195// We waited, lets just terminate with kill 196 ::kill (m_pid ,SIGKILL ); 197 198// Set the return value 199m_returnValue = returnValue ; 200// Mark as terminated 201m_isTerminated = true; 202 } 203} 204 205/* !!!!!!!!!!!!!!!!!!!!!! UnixPipeStream !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ 206 207void UnixPipeStream ::close () 208{ 209if (!m_isClosed ) 210 { 211if (m_isOwned ) 212 { 213 ::close (m_fd ); 214 } 215 216m_isClosed = true; 217// Make something hopefully invalid 218m_fd = -1 ; 219 } 220} 221 222SlangResult UnixPipeStream ::flush () 223{ 224#if 0 225// https://stackoverflow.com/questions/43184035/flushing-pipe-without-closing-in-c 226// Makes the case that flushing is not applicable with pipes. 227if (canWrite ()) 228 { 229// We might want to use 230 ::fsync (m_fd ); 231 } 232#endif 233return SLANG_OK ; 234} 235 236SlangResult UnixPipeStream ::read (void * buffer ,size_t length ,size_t & outReadBytes ) 237{ 238outReadBytes = 0 ; 239 240if (!_has (FileAccess ::Read )) 241 { 242return SLANG_E_NOT_AVAILABLE ; 243 } 244if (m_isClosed ) 245 { 246return SLANG_OK ; 247 } 248 249// Check if it's hung up. 250pollfd pollInfo ; 251 252pollInfo .fd = m_fd ; 253pollInfo .events = POLLIN |POLLHUP ; 254pollInfo .revents = 0 ; 255 256// https://linux.die.net/man/2/poll 257 258// Return immediately 259const int pollTimeout = 0 ; 260 261const int pollResult = ::poll (& pollInfo ,1 ,pollTimeout ); 262if (pollResult < 0 ) 263 { 264return SLANG_FAIL ; 265 } 266 267// If there are no poll events, we are done 268if (pollResult == 0 ) 269 { 270return SLANG_OK ; 271 } 272 273// If there is data read that first 274if (pollInfo .revents & POLLIN ) 275 { 276auto count = ::read (m_fd ,buffer ,length ); 277 278// If it's -1 it seems like an error 279if (count == -1 ) 280 { 281const int err = errno ; 282 283// On non blocking pipe these indicate there could be more to come 284if (err == EAGAIN || err == EWOULDBLOCK ) 285 { 286return SLANG_OK ; 287 } 288// Okay - guess we have an error then 289return SLANG_FAIL ; 290 } 291 292outReadBytes = size_t (count ); 293 294// If no bytes were wanted, then there could still be bytes in the pipe 295// before a HUP. So don't fall through to check for HUP. 296// 297// If some bytes *were* wanted and none were read, we can allow fall through to 298// handle HUP. 299if (length == 0 || count > 0 ) 300 { 301return SLANG_OK ; 302 } 303 304// End of file. 305if (count == 0 ) 306 { 307close (); 308 } 309 } 310 311if (pollInfo .revents & POLLHUP ) 312 { 313close (); 314 } 315 316if (pollInfo .revents & POLLERR || pollInfo .revents & POLLNVAL ) 317 { 318return SLANG_FAIL ; 319 } 320 321return SLANG_OK ; 322} 323 324SlangResult UnixPipeStream ::write (const void * buffer ,size_t length ) 325{ 326if (!_has (FileAccess ::Write )) 327 { 328return SLANG_E_NOT_AVAILABLE ; 329 } 330if (m_isClosed ) 331 { 332// The pipe is closed 333return SLANG_FAIL ; 334 } 335 336pollfd pollInfo ; 337 338pollInfo .fd = m_fd ; 339pollInfo .events = POLLHUP ; 340pollInfo .revents = 0 ; 341 342// https://linux.die.net/man/2/poll 343 344// Return immediately 345const int pollTimeout = 0 ; 346 347int pollResult = ::poll (& pollInfo ,1 ,pollTimeout ); 348if (pollResult < 0 ) 349 { 350return SLANG_FAIL ; 351 } 352 353if (pollInfo .revents & POLLHUP ) 354 { 355close (); 356return SLANG_FAIL ; 357 } 358 359const ssize_t writeResult = ::write (m_fd ,buffer ,length ); 360 361if (writeResult < 0 || size_t (writeResult )!= length ) 362 { 363return SLANG_FAIL ; 364 } 365 366return SLANG_OK ; 367} 368 369/* !!!!!!!!!!!!!!!!!!!!!! Process !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ 370 371/* static */ UnownedStringSlice Process ::getExecutableSuffix () 372{ 373#if __CYGWIN__ 374return UnownedStringSlice ::fromLiteral (".exe" ); 375#else 376return UnownedStringSlice ::fromLiteral ("" ); 377#endif 378} 379 380/* static */ StringEscapeHandler * Process ::getEscapeHandler () 381{ 382return StringEscapeUtil ::getHandler (StringEscapeUtil ::Style ::Space ); 383} 384 385static const int kCannotExecute = 126 ; 386 387static int pipeCLOEXEC (int pipefd [2 ]) 388{ 389#if SLANG_APPLE_FAMILY 390// without pipe2 on macOS, there's an unavoidable race here where 391// another process could fork and execv with execWatchPipe before we 392// can set CLOEXEC on it... 393if (pipe (pipefd )== -1 || fcntl (pipefd [1 ],F_SETFD ,FD_CLOEXEC )== -1 || 394fcntl (pipefd [0 ],F_SETFD ,FD_CLOEXEC )== -1 ) 395 { 396return -1 ; 397 } 398return 0 ; 399#else 400return pipe2 (pipefd ,O_CLOEXEC ); 401#endif 402} 403 404/* static */ SlangResult Process ::create ( 405const CommandLine & commandLine , 406Process ::Flags , 407RefPtr < Process >& outProcess ) 408{ 409const char * whatFailed = nullptr ; 410pid_t childPid ; 411 412// 413// Set up command line 414// 415List < char const *> argPtrs ; 416 417const auto & exe = commandLine .m_executableLocation ; 418 419// Add the command 420argPtrs .add (exe .m_pathOrName .getBuffer ()); 421 422// Add all the args - they don't need any explicit escaping 423for (auto arg :commandLine .m_args ) 424 { 425// All args for this target must be unescaped (as they are in CommandLine) 426argPtrs .add (arg .getBuffer ()); 427 } 428 429// Terminate with a null 430argPtrs .add (nullptr ); 431 432// 433// Set up pipes 434// 435int stdinPipe [2 ]= {-1 ,-1 }; 436int stdoutPipe [2 ]= {-1 ,-1 }; 437int stderrPipe [2 ]= {-1 ,-1 }; 438 439// We will create this pipe with O_CLOEXEC, so that it gets closed 440// automatically if the child's exec succeeds 441int execWatchPipe [2 ]= {-1 ,-1 }; 442 443if (pipe (stdinPipe )== -1 || pipe (stdoutPipe )== -1 || pipe (stderrPipe )== -1 || 444pipeCLOEXEC (execWatchPipe )== -1 ) 445 { 446whatFailed = "pipe" ; 447 gotoreportErr ; 448 } 449 450// Make sure that none of our pipes are going to be clobbered by dup2 to 451// 0,1,2 in the child. 452whatFailed = "fcntl" ; 453int next ; 454if (stdinPipe [0 ]< 3 ) 455 { 456if (-1 == (next = fcntl (stdinPipe [0 ],F_DUPFD ,3 ))) 457 { 458 gotoreportErr ; 459 } 460close (stdinPipe [0 ]); 461stdinPipe [0 ]= next ; 462 } 463if (stdoutPipe [1 ]< 3 ) 464 { 465if (-1 == (next = fcntl (stdoutPipe [1 ],F_DUPFD ,3 ))) 466 { 467 gotoreportErr ; 468 } 469close (stdoutPipe [1 ]); 470stdoutPipe [1 ]= next ; 471 } 472if (stderrPipe [1 ]< 3 ) 473 { 474if (-1 == (next = fcntl (stderrPipe [1 ],F_DUPFD ,3 ))) 475 { 476 gotoreportErr ; 477 } 478close (stderrPipe [1 ]); 479stderrPipe [1 ]= next ; 480 } 481if (execWatchPipe [1 ]< 3 ) 482 { 483if (-1 == (next = fcntl (execWatchPipe [1 ],F_DUPFD_CLOEXEC ,3 ))) 484 { 485 gotoreportErr ; 486 } 487close (execWatchPipe [1 ]); 488execWatchPipe [1 ]= next ; 489 } 490whatFailed = nullptr ; 491 492childPid = fork (); 493if (childPid == -1 ) 494 { 495whatFailed = "fork" ; 496 gotoreportErr ; 497 } 498 499if (childPid == 0 ) 500 { 501// We are the child process. 502 503// Close unused fds and duplicate into standard handles 504 505 ::close (execWatchPipe [0 ]); 506 ::close (stdinPipe [1 ]); 507 ::close (stdoutPipe [0 ]); 508 ::close (stderrPipe [0 ]); 509 510dup2 (stdinPipe [0 ],STDIN_FILENO ); 511 ::close (stdinPipe [0 ]); 512dup2 (stdoutPipe [1 ],STDOUT_FILENO ); 513 ::close (stdoutPipe [1 ]); 514dup2 (stderrPipe [1 ],STDERR_FILENO ); 515 ::close (stderrPipe [1 ]); 516 517// Reset locale to ensure the output can be parsed regardless of user's 518// locale. 519setenv ("LC_ALL" ,"C" ,1 ); 520 521if (exe .m_type == ExecutableLocation ::Type ::Path ) 522 { 523// Use the specified path (ie don't search) 524 ::execv (argPtrs [0 ], (char * const * )& argPtrs [0 ]); 525 } 526else 527 { 528// Search for the executable 529 ::execvp (argPtrs [0 ], (char * const * )& argPtrs [0 ]); 530 } 531 532// If we get here, then `exec` failed 533 534// Signal the failure to our parent 535int execErr = errno ; 536if (::write (execWatchPipe [1 ],& execErr ,sizeof (execErr ))) 537fprintf (stderr ,"error: `exec` watch pipe write failed\n" ); 538 539// NOTE! Because we have dup2 into STDERR_FILENO, this error will *not* generally appear on 540// the terminal but in the stderrPipe. 541fprintf (stderr ,"error: `exec` failed\n" ); 542 543// Terminate with failure. 544// Call _exit() rather than exit() so we don't run anything registered with atexit() 545 ::_exit (kCannotExecute ); 546 } 547else 548 { 549// We are the parent process 550 ::close (execWatchPipe [1 ]); 551 ::close (stdinPipe [0 ]); 552 ::close (stdoutPipe [1 ]); 553 ::close (stderrPipe [1 ]); 554 555RefPtr < Stream > streams [Index (StdStreamType ::CountOf )]; 556 557// Previously code didn't need to close, so we'll make stream now own the handles 558streams [Index (StdStreamType ::Out )]= 559new UnixPipeStream (stdoutPipe [0 ],FileAccess ::Read , true); 560stdoutPipe [0 ]= -1 ; 561streams [Index (StdStreamType ::ErrorOut )]= 562new UnixPipeStream (stderrPipe [0 ],FileAccess ::Read , true); 563stderrPipe [0 ]= -1 ; 564streams [Index (StdStreamType ::In )]= 565new UnixPipeStream (stdinPipe [1 ],FileAccess ::Write , true); 566stdinPipe [1 ]= -1 ; 567 568// Check that the exec actually succeeded 569int execErrCode ; 570// Our success is if we read zero bytes, indicating that the pipe was 571// closed by the child's exec and O_CLOEXEC. (and us just above) 572const int readRes = ::read (execWatchPipe [0 ],& execErrCode ,sizeof (execErrCode )); 573if (readRes < 0 ) 574 { 575whatFailed = "read from forked process" ; 576 gotoreportErr ; 577 } 578else if (readRes > 0 ) 579 { 580// exec failed, and the child reported back to us 581// don't print messages by default, as we do some speculative 582// execution of processes to see if they exist and it gets noisy 583const bool verbose = false; 584if (verbose ) 585 { 586fprintf ( 587stderr , 588"error: exec for \"%s\" failed: %s\n" , 589argPtrs [0 ], 590 ::strerror (execErrCode )); 591 } 592whatFailed = "exec" ; 593// Don't report the exec as we expect some of them to fail 594 gotoclosePipes ; 595 } 596 597outProcess = new UnixProcess (childPid ,streams [0 ].readRef ()); 598 } 599 600 gotoclosePipes ; 601 602// Report any error and then cleanup 603reportErr : 604fprintf (stderr ,"error: `%s` failed (%s)\n" ,whatFailed ,strerror (errno )); 605closePipes : 606 ::close (execWatchPipe [0 ]); 607 ::close (execWatchPipe [1 ]); 608 ::close (stdinPipe [0 ]); 609 ::close (stdinPipe [1 ]); 610 ::close (stderrPipe [0 ]); 611 ::close (stderrPipe [1 ]); 612 ::close (stdoutPipe [0 ]); 613 ::close (stdoutPipe [1 ]); 614 615return whatFailed ?SLANG_FAIL :SLANG_OK ; 616} 617 618/* static */ uint64_t Process ::getClockFrequency () 619{ 620return 1000000000 ; 621} 622 623/* static */ uint64_t Process ::getClockTick () 624{ 625struct timespec now ; 626clock_gettime (CLOCK_MONOTONIC ,& now ); 627return uint64_t (now .tv_sec )* 1000000000 + now .tv_nsec ; 628} 629 630/* static */ void Process ::sleepCurrentThread (Int timeInMs ) 631{ 632struct timespec timeSpec ; 633 634if (timeInMs >=1000 ) 635 { 636timeSpec .tv_sec = timeInMs /1000 ; 637timeSpec .tv_nsec = (timeInMs %1000 )* 1000 * 1000 ; 638 } 639else if (timeInMs > 0 ) 640 { 641timeSpec .tv_sec = 0 ; 642timeSpec .tv_nsec = timeInMs * 1000 * 1000 ; 643 } 644else 645 { 646timeSpec .tv_sec = 0 ; 647timeSpec .tv_nsec = 0 ; 648 } 649nanosleep (& timeSpec ,nullptr ); 650} 651 652/* static */ SlangResult Process ::getStdStream (StdStreamType type ,RefPtr < Stream >& out ) 653{ 654switch (type ) 655 { 656case StdStreamType ::In : 657 { 658out = new UnixPipeStream (STDIN_FILENO ,FileAccess ::Read , false); 659break ; 660 } 661case StdStreamType ::Out : 662 { 663out = new UnixPipeStream (STDOUT_FILENO ,FileAccess ::Write , false); 664break ; 665 } 666case StdStreamType ::ErrorOut : 667 { 668out = new UnixPipeStream (STDERR_FILENO ,FileAccess ::Write , false); 669break ; 670 } 671default : 672return SLANG_FAIL ; 673 } 674return SLANG_OK ; 675} 676 677uint32_t Process ::getId () 678{ 679return getpid (); 680} 681 682}// namespace Slang