E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gprocess_unix.cpp
1 //
2 // Copyright (C) 2001-2018 Graeme Walker <graeme_walker@users.sourceforge.net>
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 ///
47 class G::Process::IdImp
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 size_t n = PATH_MAX ; n += 10U ;
220 std::vector<char> buffer( n ) ;
221 for( int i = 0 ; i < 1000 ; i++ )
222 {
223 char * p = getcwd( &buffer[0] , buffer.size() ) ;
224 int error = errno_() ;
225 if( p == nullptr && error == ERANGE )
226 buffer.resize( buffer.size() + n ) ;
227 else if( p == nullptr && !no_throw )
228 throw std::runtime_error( "getcwd() failed" ) ;
229 else
230 break ;
231 }
232 buffer.push_back( '\0' ) ;
233 return std::string( &buffer[0] ) ;
234 }
235
236 namespace
237 {
238 bool readlink_( const char * path , std::string & value )
239 {
240 std::vector<char> buffer( PATH_MAX + 1 ) ; // best effort, not guaranteed
241 buffer[0] = '\0' ;
242 ssize_t rc = readlink( path , &buffer[0] , buffer.size() ) ;
243 if( rc > 0 && static_cast<size_t>(rc) <= buffer.size() )
244 value = std::string(&buffer[0],static_cast<size_t>(rc)) ;
245 return rc > 0 ;
246 }
247 }
248
249 #ifdef G_UNIX_MAC
250 #include <libproc.h>
251 std::string G::Process::exe()
252 {
253 // (see also _NSGetExecutablePath())
254 std::vector<char> buffer( std::max(100,PROC_PIDPATHINFO_MAXSIZE) ) ;
255 buffer[0] = '\0' ;
256 int rc = proc_pidpath( getpid() , &buffer[0] , buffer.size() ) ;
257 if( rc > 0 )
258 {
259 size_t n = static_cast<size_t>(rc) ;
260 if( n > buffer.size() ) n = buffer.size() ;
261 return std::string( &buffer[0] , n ) ;
262 }
263 else
264 {
265 return std::string() ;
266 }
267 }
268 #else
269 std::string G::Process::exe()
270 {
271 // best effort, not guaranteed
272 std::string result ;
273 readlink_( "/proc/self/exe" , result ) ||
274 readlink_( "/proc/curproc/file" , result ) ||
275 readlink_( "/proc/curproc/exe" , result ) ;
276 return result ;
277 }
278 #endif
279
280 // ===
281
282 G::Process::Id::Id()
283 {
284 m_pid = ::getpid() ;
285 }
286
287 G::Process::Id::Id( SignalSafe , const char * path ) :
288 m_pid(0)
289 {
290 // signal-safe, reentrant implementation suitable for a signal handler...
291 int fd = ::open( path ? path : "" , O_RDONLY ) ;
292 if( fd >= 0 )
293 {
294 const size_t buffer_size = 11U ;
295 char buffer[buffer_size] ;
296 buffer[0U] = '\0' ;
297 ssize_t rc = ::read( fd , buffer , buffer_size-1U ) ;
298 ::close( fd ) ;
299 for( const char * p = buffer ; rc > 0 && *p >= '0' && *p <= '9' ; p++ , rc-- )
300 {
301 m_pid *= 10 ;
302 m_pid += ( *p - '0' ) ;
303 }
304 }
305 }
306
307 G::Process::Id::Id( std::istream & stream )
308 {
309 stream >> m_pid ;
310 if( !stream.good() )
311 throw Process::InvalidId() ;
312 }
313
314 std::string G::Process::Id::str() const
315 {
316 std::ostringstream ss ;
317 ss << m_pid ;
318 return ss.str() ;
319 }
320
321 bool G::Process::Id::operator==( const Id & other ) const
322 {
323 return m_pid == other.m_pid ;
324 }
325
326 // ===
327
328 namespace
329 {
330 mode_t umask_value( G::Process::Umask::Mode mode )
331 {
332 mode_t m = 0 ;
333 if( mode == G::Process::Umask::Tightest ) m = 0177 ; // -rw-------
334 if( mode == G::Process::Umask::Tighter ) m = 0117 ; // -rw-rw----
335 if( mode == G::Process::Umask::Readable ) m = 0133 ; // -rw-r--r--
336 if( mode == G::Process::Umask::GroupOpen ) m = 0113 ;// -rw-rw-r--
337 return m ;
338 }
339 }
340
341 /// \class G::Process::Umask::UmaskImp
342 /// A pimple-pattern implementation class used by G::Process::Umask.
343 ///
344 class G::Process::Umask::UmaskImp
345 {
346 public:
347 mode_t m_old_mode ;
348 } ;
349
350 G::Process::Umask::Umask( Mode mode ) :
351 m_imp(new UmaskImp)
352 {
353 m_imp->m_old_mode = ::umask( umask_value(mode) ) ;
354 }
355
356 G::Process::Umask::~Umask()
357 {
358 mode_t rc = ::umask( m_imp->m_old_mode ) ; G_IGNORE_VARIABLE(rc) ;
359 delete m_imp ;
360 }
361
362 void G::Process::Umask::set( Mode mode )
363 {
364 mode_t rc = ::umask( umask_value(mode) ) ; G_IGNORE_VARIABLE(rc) ;
365 }
366
367 void G::Process::Umask::tighten()
368 {
369 ::umask( ::umask(2) | mode_t(7) ) ; // -xxxxxx---
370 }
371
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
G::Identity::isRoot
bool isRoot() const
Returns true if the userid is zero.
Definition: gidentity_unix.cpp:132
G::Process::Umask::UmaskImp
A pimple-pattern implementation class used by G::Process::Umask.
Definition: gprocess_unix.cpp:344
G::SignalSafe
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:36
G::Identity::root
static Identity root()
Returns the superuser identity.
Definition: gidentity_unix.cpp:117
G::Identity::real
static Identity real()
Returns the calling process's real identity.
Definition: gidentity_unix.cpp:99
glog.h
G::Identity::invalid
static Identity invalid()
Returns an invalid identity.
Definition: gidentity_unix.cpp:107
gidentity.h
G::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
G::Identity::effective
static Identity effective()
Returns the current effective identity.
Definition: gidentity_unix.cpp:91
glimits.h
G::Str::lower
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:599
gstr.h
gassert.h
gprocess.h
G::Str::isPrintableAscii
static bool isPrintableAscii(const std::string &s)
Returns true if every character is a 7-bit, non-control character (ie.
Definition: gstr.cpp:282
gpath.h
G::Process::IdImp
A private implementation class used by G::Process that wraps a process id and hides the pid_t type...
Definition: gprocess_unix.cpp:47
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Path::nullDevice
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
Definition: gpath.cpp:285