VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gprocess_unix.cpp
1 //
2 // Copyright (C) 2017 Graeme Walker
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // gprocess_unix.cpp
19 //
20 
21 #include "gdef.h"
22 #include "glimits.h"
23 #include "gprocess.h"
24 #include "gidentity.h"
25 #include "gstr.h"
26 #include "gassert.h"
27 #include "gpath.h"
28 #include "glog.h"
29 #include <iostream>
30 #include <cstring> // std::strerror()
31 #include <limits.h>
32 #include <fcntl.h>
33 #include <errno.h>
34 
35 namespace
36 {
37  void noCloseOnExec( int fd )
38  {
39  ::fcntl( fd , F_SETFD , 0 ) ;
40  }
41 }
42 
43 /// \class G::Process::IdImp
44 /// A private implementation class used by G::Process that wraps
45 /// a process id and hides the pid_t type.
46 ///
48 {
49 public:
50  pid_t m_pid ;
51 } ;
52 
53 // ===
54 
55 void G::Process::cd( const Path & dir )
56 {
57  if( ! cd(dir,NoThrow()) )
58  throw CannotChangeDirectory( dir.str() ) ;
59 }
60 
61 bool G::Process::cd( const Path & dir , NoThrow )
62 {
63  return 0 == ::chdir( dir.str().c_str() ) ;
64 }
65 
66 void G::Process::closeStderr()
67 {
68  ::close( STDERR_FILENO ) ;
69  ::open( G::Path::nullDevice().str().c_str() , O_WRONLY ) ;
70  noCloseOnExec( STDERR_FILENO ) ;
71 }
72 
73 void G::Process::closeFiles( bool keep_stderr )
74 {
75  closeFilesExcept( keep_stderr ? STDERR_FILENO : -1 ) ;
76 }
77 
78 void G::Process::closeFilesExcept( int keep_1 , int keep_2 )
79 {
80  // flush
81  std::cout << std::flush ;
82  std::cerr << std::flush ;
83 
84  // find how many to close
85  int n = 256U ;
86  long rc = ::sysconf( _SC_OPEN_MAX ) ;
87  if( rc > 0L )
88  n = static_cast<int>( rc ) ;
89 
90  // close everything except those given
91  for( int fd = 0 ; fd < n ; fd++ )
92  {
93  if( fd != keep_1 && fd != keep_2 )
94  ::close( fd ) ;
95  }
96 
97  // reopen standard fds to /dev/null
98  G::Path dev_null = G::Path::nullDevice() ;
99  if( keep_1 != STDIN_FILENO && keep_2 != STDIN_FILENO )
100  {
101  ::open( dev_null.str().c_str() , O_RDONLY ) ;
102  }
103  if( keep_1 != STDOUT_FILENO && keep_2 != STDOUT_FILENO )
104  {
105  ::open( dev_null.str().c_str() , O_WRONLY ) ;
106  }
107  if( keep_1 != STDERR_FILENO && keep_2 != STDERR_FILENO )
108  {
109  ::open( dev_null.str().c_str() , O_WRONLY ) ;
110  }
111 
112  // make sure standard fds stay open across exec()
113  noCloseOnExec( STDIN_FILENO ) ;
114  noCloseOnExec( STDOUT_FILENO ) ;
115  noCloseOnExec( STDERR_FILENO ) ;
116 }
117 
118 int G::Process::errno_( const G::SignalSafe & )
119 {
120  return errno ; // not ::errno or std::errno
121 }
122 
123 int G::Process::errno_( const G::SignalSafe & , int e_new )
124 {
125  int e_old = errno ;
126  errno = e_new ;
127  return e_old ;
128 }
129 
130 std::string G::Process::strerror( int errno_ )
131 {
132  char * p = std::strerror( errno_ ) ;
133  std::string s( p ? p : "" ) ;
134  if( s.empty() ) s = "unknown error" ;
135  return G::Str::isPrintableAscii(s) ? G::Str::lower(s) : s ;
136 }
137 
138 void G::Process::revokeExtraGroups()
139 {
140  if( Identity::real().isRoot() || Identity::effective() != Identity::real() )
141  {
142  gid_t dummy ;
143  int rc = ::setgroups( 0U , &dummy ) ; G_IGNORE_VARIABLE(rc) ; // (only works for root, so ignore the return code)
144  }
145 }
146 
147 G::Identity G::Process::beSpecial( Identity special_identity , bool change_group )
148 {
149  change_group = Identity::real().isRoot() ? change_group : true ;
150  Identity old_identity( Identity::effective() ) ;
151  setEffectiveUserTo( special_identity ) ;
152  if( change_group )
153  setEffectiveGroupTo( special_identity ) ;
154  return old_identity ;
155 }
156 
157 G::Identity G::Process::beSpecial( SignalSafe safe , Identity special_identity , bool change_group )
158 {
159  change_group = Identity::real().isRoot() ? change_group : true ;
160  Identity old_identity( Identity::effective() ) ;
161  setEffectiveUserTo( safe , special_identity ) ;
162  if( change_group )
163  setEffectiveGroupTo( safe , special_identity ) ;
164  return old_identity ;
165 }
166 
167 G::Identity G::Process::beOrdinary( Identity ordinary_id , bool change_group )
168 {
169  Identity special_identity( Identity::effective() ) ;
170  if( Identity::real().isRoot() )
171  {
172  setEffectiveUserTo( Identity::root() ) ;
173  if( change_group )
174  setEffectiveGroupTo( ordinary_id ) ;
175  setEffectiveUserTo( ordinary_id ) ;
176  }
177  else
178  {
179  setEffectiveUserTo( Identity::real() ) ;
180  if( change_group )
181  setEffectiveGroupTo( Identity::real() ) ;
182  }
183  return special_identity ;
184 }
185 
186 G::Identity G::Process::beOrdinary( SignalSafe safe , Identity ordinary_id , bool change_group )
187 {
188  Identity special_identity( Identity::effective() ) ;
189  if( Identity::real().isRoot() )
190  {
191  setEffectiveUserTo( safe , Identity::root() ) ;
192  if( change_group )
193  setEffectiveGroupTo( safe , ordinary_id ) ;
194  setEffectiveUserTo( safe , ordinary_id ) ;
195  }
196  else
197  {
198  setEffectiveUserTo( safe , Identity::real() ) ;
199  if( change_group )
200  setEffectiveGroupTo( safe , Identity::real() ) ;
201  }
202  return special_identity ;
203 }
204 
205 void G::Process::beOrdinaryForExec( Identity run_as_id )
206 {
207  if( run_as_id != Identity::invalid() )
208  {
209  setEffectiveUserTo( Identity::root() , false ) ; // for root-suid
210  setRealGroupTo( run_as_id , false ) ;
211  setEffectiveGroupTo( run_as_id , false ) ;
212  setRealUserTo( run_as_id , false ) ;
213  setEffectiveUserTo( run_as_id , false ) ;
214  }
215 }
216 
217 std::string G::Process::cwd( bool no_throw )
218 {
219  std::vector<char> buffer( PATH_MAX + 10 ) ;
220  for(;;)
221  {
222  char * p = getcwd( &buffer[0] , buffer.size() ) ;
223  int error = errno_() ;
224  if( p == nullptr && error == ERANGE )
225  buffer.resize( buffer.size() + PATH_MAX ) ;
226  else if( p == nullptr && !no_throw )
227  throw std::runtime_error( "getcwd() failed" ) ;
228  else
229  break ;
230  }
231  buffer.push_back( '\0' ) ;
232  return std::string( &buffer[0] ) ;
233 }
234 
235 namespace
236 {
237  bool readlink_( const char * path , std::string & value )
238  {
239  std::vector<char> buffer( PATH_MAX + 1 ) ; // best effort, not guaranteed
240  buffer[0] = '\0' ;
241  ssize_t rc = readlink( path , &buffer[0] , buffer.size() ) ;
242  if( rc > 0 && static_cast<size_t>(rc) <= buffer.size() )
243  value = std::string(&buffer[0],static_cast<size_t>(rc)) ;
244  return rc > 0 ;
245  }
246 }
247 
248 #ifdef G_UNIX_MAC
249 #include <libproc.h>
250 std::string G::Process::exe()
251 {
252  // (see also _NSGetExecutablePath())
253  std::vector<char> buffer( std::max(100,PROC_PIDPATHINFO_MAXSIZE) ) ;
254  buffer[0] = '\0' ;
255  int rc = proc_pidpath( getpid() , &buffer[0] , buffer.size() ) ;
256  if( rc > 0 )
257  {
258  size_t n = static_cast<size_t>(rc) ;
259  if( n > buffer.size() ) n = buffer.size() ;
260  return std::string( &buffer[0] , n ) ;
261  }
262  else
263  {
264  return std::string() ;
265  }
266 }
267 #else
268 std::string G::Process::exe()
269 {
270  // best effort, not guaranteed
271  std::string result ;
272  readlink_( "/proc/self/exe" , result ) ||
273  readlink_( "/proc/curproc/file" , result ) ||
274  readlink_( "/proc/curproc/exe" , result ) ;
275  return result ;
276 }
277 #endif
278 
279 // ===
280 
281 G::Process::Id::Id()
282 {
283  m_pid = ::getpid() ;
284 }
285 
286 G::Process::Id::Id( SignalSafe , const char * path ) :
287  m_pid(0)
288 {
289  // signal-safe, reentrant implementation suitable for a signal handler...
290  int fd = ::open( path ? path : "" , O_RDONLY ) ;
291  if( fd >= 0 )
292  {
293  const size_t buffer_size = 11U ;
294  char buffer[buffer_size] ;
295  buffer[0U] = '\0' ;
296  ssize_t rc = ::read( fd , buffer , buffer_size-1U ) ;
297  ::close( fd ) ;
298  for( const char * p = buffer ; rc > 0 && *p >= '0' && *p <= '9' ; p++ , rc-- )
299  {
300  m_pid *= 10 ;
301  m_pid += ( *p - '0' ) ;
302  }
303  }
304 }
305 
306 G::Process::Id::Id( std::istream & stream )
307 {
308  stream >> m_pid ;
309  if( !stream.good() )
310  throw Process::InvalidId() ;
311 }
312 
313 std::string G::Process::Id::str() const
314 {
315  std::ostringstream ss ;
316  ss << m_pid ;
317  return ss.str() ;
318 }
319 
320 bool G::Process::Id::operator==( const Id & other ) const
321 {
322  return m_pid == other.m_pid ;
323 }
324 
325 // ===
326 
327 namespace
328 {
329  mode_t umask_value( G::Process::Umask::Mode mode )
330  {
331  mode_t m = 0 ;
332  if( mode == G::Process::Umask::Tightest ) m = 0177 ; // -rw-------
333  if( mode == G::Process::Umask::Tighter ) m = 0117 ; // -rw-rw----
334  if( mode == G::Process::Umask::Readable ) m = 0133 ; // -rw-r--r--
335  if( mode == G::Process::Umask::GroupOpen ) m = 0113 ;// -rw-rw-r--
336  return m ;
337  }
338 }
339 
340 /// \class G::Process::Umask::UmaskImp
341 /// A pimple-pattern implementation class used by G::Process::Umask.
342 ///
344 {
345 public:
346  mode_t m_old_mode ;
347 } ;
348 
349 G::Process::Umask::Umask( Mode mode ) :
350  m_imp(new UmaskImp)
351 {
352  m_imp->m_old_mode = ::umask( umask_value(mode) ) ;
353 }
354 
355 G::Process::Umask::~Umask()
356 {
357  mode_t rc = ::umask( m_imp->m_old_mode ) ; G_IGNORE_VARIABLE(rc) ;
358  delete m_imp ;
359 }
360 
361 void G::Process::Umask::set( Mode mode )
362 {
363  mode_t rc = ::umask( umask_value(mode) ) ; G_IGNORE_VARIABLE(rc) ;
364 }
365 
366 void G::Process::Umask::tighten()
367 {
368  ::umask( ::umask(2) | mode_t(7) ) ; // -xxxxxx---
369 }
370 
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
bool isRoot() const
Returns true if the userid is zero.
A pimple-pattern implementation class used by G::Process::Umask.
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:36
static Identity root()
Returns the superuser identity.
static Identity real()
Returns the calling process's real identity.
static Identity invalid()
Returns an invalid identity.
A combination of user-id and group-id, with a very low-level interface to the get/set/e/uid/gid funct...
Definition: gidentity.h:42
static Identity effective()
Returns the current effective identity.
static std::string lower(const std::string &s)
Returns a copy of 's' in which all Latin-1 uppercase characters have been replaced by lowercase chara...
Definition: gstr.cpp:561
static bool isPrintableAscii(const std::string &s)
Returns true if every character is a 7-bit, non-control character (ie.
Definition: gstr.cpp:244
A private implementation class used by G::Process that wraps a process id and hides the pid_t type...
A Path object represents a file system path.
Definition: gpath.h:72
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
Definition: gpath.cpp:285