VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gsharedmemory.cpp
Go to the documentation of this file.
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 // gsharedmemory.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gsharedmemory.h"
23 #include "gprocess.h"
24 #include "groot.h"
25 #include "gstr.h"
26 #include "gdirectory.h"
27 #include "gassert.h"
28 #include <algorithm>
29 #include <vector>
30 #include <sys/mman.h> // shm_open()
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <errno.h>
34 
35 namespace
36 {
37  static std::string s_prefix ; // prefix for o/s-name, eg. "foo-" -- set by prefix(...)
38  static std::string s_help ; // eg. "try deleting %S" -- set by help(...)
39  static std::string s_filetext ; // eg. "# this is a shared memory name placeholder file" -- set by filetext(...)
40 }
41 
42 namespace
43 {
44  std::string get_help( const std::string & name , const std::string & os_name )
45  {
46  static const char * help_linux = ": try deleting /dev/shm/%S*" ;
47  static const char * help_bsd = ": try this (ayor)... "
48  "perl -e 'syscall($ARGV[0],$ARGV[1])' `awk '/shm_unlink/{print $NF}' /usr/include/sys/syscall.h` /%S" ;
49 
50  std::string help = s_help ;
51  if( help.empty() )
52  help = std::string( G::is_linux() ? help_linux : help_bsd ) ;
53 
54  G::Str::replaceAll( help , "%s" , name ) ;
55  G::Str::replaceAll( help , "%S" , os_name ) ;
56  return help ;
57  }
58 }
59 
60 G::SharedMemory::Imp::Imp() :
61  m_fd(-1) ,
62  m_p(nullptr) ,
63  m_size(0U)
64 {
65 }
66 
67 G::SharedMemory::Imp::~Imp()
68 {
69  if( m_p != nullptr )
70  ::munmap( m_p , m_size ) ;
71 
72  if( m_fd != -1 )
73  ::close( m_fd ) ;
74 
75  unlink() ;
76 }
77 
78 void G::SharedMemory::Imp::swap( Imp & other )
79 {
80  using std::swap ;
81  swap( m_fd , other.m_fd ) ;
82  swap( m_unlink_name , other.m_unlink_name ) ;
83  swap( m_p , other.m_p ) ;
84  swap( m_size , other.m_size ) ;
85 }
86 
87 int G::SharedMemory::Imp::fd() const
88 {
89  return m_fd ;
90 }
91 
92 void G::SharedMemory::Imp::unlink()
93 {
94  if( !m_unlink_name.empty() )
95  {
96  G::Root claim_root ;
97  ::shm_unlink( m_unlink_name.c_str() ) ;
98  m_unlink_name.clear() ;
99  }
100 }
101 
102 // ==
103 
105 {
106  G_ASSERT( size != 0U ) ;
107  init( std::string() , -1 , size , O_RDWR , false ) ;
108 }
109 
111 {
112  G_ASSERT( fd >= 0 ) ;
113  init( std::string() , fd , 0U , O_RDWR , false ) ;
114 }
115 
116 G::SharedMemory::SharedMemory( const std::string & name )
117 {
118  G_ASSERT( !name.empty() ) ;
119  init( name , -1 , 0U , O_RDWR , false ) ;
120 }
121 
122 G::SharedMemory::SharedMemory( const std::string & name , size_t size )
123 {
124  G_ASSERT( !name.empty() ) ;
125  G_ASSERT( size != 0U ) ;
126  bool exclusive = true ;
127  init( name , -1 , size , O_RDWR | O_CREAT | ( exclusive ? O_EXCL : O_TRUNC ) , false ) ;
128 }
129 
131 {
132  G_ASSERT( size != 0U ) ;
133  init( std::string() , -1 , size , O_RDWR , true ) ;
134 }
135 
137 {
138  G_ASSERT( fd >= 0 ) ;
139  init( std::string() , fd , 0U , O_RDWR , true ) ;
140 }
141 
142 G::SharedMemory::SharedMemory( const std::string & name , Control )
143 {
144  G_ASSERT( !name.empty() ) ;
145  init( name , -1 , 0U , O_RDWR , true ) ;
146 }
147 
148 G::SharedMemory::SharedMemory( const std::string & name , size_t size , Control )
149 {
150  G_ASSERT( !name.empty() ) ;
151  G_ASSERT( size != 0U ) ;
152  bool exclusive = true ;
153  init( name , -1 , size , O_RDWR | O_CREAT | ( exclusive ? O_EXCL : O_TRUNC ) , true ) ;
154 }
155 
157 {
158 }
159 
160 void G::SharedMemory::init( const std::string & name_in , int fd , size_t size , int open_flags , bool control_segment )
161 {
162  // see "man shm_overview" and "man sem_overview"
163 
164  int mmap_flags = 0 ;
165  std::string os_name = osName( name_in ) ;
166 
167  if( os_name.empty() && fd == -1 ) // new anonymous segment
168  {
169  G_ASSERT( size != 0U ) ;
170  G::Root claim_root ;
171  Imp imp ;
172  imp.m_size = size ;
173  imp.m_p = ::mmap( nullptr , imp.m_size , PROT_READ | PROT_WRITE , MAP_SHARED | MAP_ANONYMOUS | mmap_flags , -1 , 0 ) ;
174  if( imp.m_p == nullptr )
175  throw Error( "mmap error" ) ;
176  m_imp.swap( imp ) ;
177  }
178  else
179  {
180  Imp imp ;
181  if( os_name.empty() ) // inherited anonymous segment
182  {
183  imp.m_fd = fd ;
184  }
185  else // named segment, new or pre-existing
186  {
187  int e = 0 ;
188  {
189  G::Root claim_root ;
190  imp.m_fd = ::shm_open( os_name.c_str() , open_flags , 0777 ) ;
191  e = errno ;
192  }
193  if( imp.fd() == -1 )
194  {
195  std::stringstream ss ;
196  ss << "shm_open() failed for [" << os_name << "]" ;
197  ss << ": " << G::Process::strerror(e) ;
198  if( (open_flags & O_CREAT) && e == EEXIST )
199  ss << get_help(name_in,os_name) ;
200  throw Error( ss.str() ) ;
201  }
202  if( open_flags & O_CREAT )
203  {
204  imp.m_unlink_name = os_name ;
205  if( control_segment )
206  createMarker( os_name ) ;
207  }
208  }
209 
210  if( size == 0U ) // pre-existing named segment - determine its size from the file system
211  {
212  G::Root claim_root ;
213  struct ::stat statbuf ;
214  if( 0 != fstat( imp.fd() , &statbuf ) )
215  throw Error( os_name , "fstat error" ) ;
216  if( statbuf.st_size == 0 )
217  throw Error( os_name , "empty shared memory file" ) ; // never gets here?
218  imp.m_size = statbuf.st_size ;
219  }
220  else // new named segment - set its size
221  {
222  G::Root claim_root ;
223  if( 0 != ::ftruncate( imp.fd() , size ) )
224  throw Error( os_name , "ftruncate error" ) ;
225  imp.m_size = size ;
226  }
227 
228  {
229  G::Root claim_root ;
230  imp.m_p = ::mmap( nullptr , imp.m_size , PROT_READ | PROT_WRITE , MAP_SHARED | mmap_flags , imp.m_fd , 0 ) ;
231  if( imp.m_p == nullptr )
232  throw Error( os_name , "mmap error" ) ;
233  }
234 
235  m_imp.swap( imp ) ;
236  }
237 }
238 
239 bool G::SharedMemory::remap( size_t new_size , bool may_move , bool no_throw )
240 {
241  G::Root claim_root ;
242  if( m_imp.m_fd != -1 && 0 != ::ftruncate( m_imp.fd() , new_size ) )
243  throw Error( "ftruncate error" ) ;
244 
245  // mremap is linux only - stubbed out in gdef.h otherwise
246  void * new_p = ::mremap( m_imp.m_p , m_imp.m_size , new_size , may_move ? MREMAP_MAYMOVE : 0 ) ;
247  if( new_p == MAP_FAILED ) // -1, not nullptr
248  {
249  if( !no_throw )
250  throw Error( "cannot resize the shared memory segment" ) ;
251  return false ;
252  }
253  m_imp.m_p = new_p ;
254  m_imp.m_size = new_size ;
255  return true ;
256 }
257 
258 std::string G::SharedMemory::osName( const std::string & name_in )
259 {
260  if( name_in.empty() )
261  return std::string() ;
262  else if( name_in.at(0U) != '/' && !G::is_linux() )
263  return "/" + s_prefix + name_in ;
264  else
265  return s_prefix + name_in ;
266 }
267 
268 void G::SharedMemory::cleanup( SignalSafe signal_safe , const char * os_name , void (*fn)(SignalSafe,void*) )
269 {
270  G::Identity identity = G::Root::start( signal_safe ) ;
271  int fd = ::shm_open( os_name , O_RDWR , 0 ) ;
272  ::shm_unlink( os_name ) ;
273  if( fd >= 0 )
274  {
275  struct ::stat statbuf ;
276  if( fstat(fd,&statbuf) == 0 && statbuf.st_size > 0 )
277  {
278  void * p = ::mmap( nullptr , statbuf.st_size , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0 ) ;
279  if( fn && p ) fn( signal_safe , p ) ;
280  ::munmap( p , statbuf.st_size ) ;
281  }
282  ::close( fd ) ;
283  }
284  G::Root::stop( signal_safe , identity ) ;
285  deleteMarker( signal_safe , os_name ) ;
286 }
287 
288 void G::SharedMemory::trap( SignalSafe signal_safe , const char * os_name , void (*fn)(SignalSafe,void*) )
289 {
290  G::Identity identity = G::Root::start( signal_safe ) ;
291  int fd = ::shm_open( os_name , O_RDWR , 0 ) ;
292  if( fd >= 0 )
293  {
294  struct ::stat statbuf ;
295  if( fstat(fd,&statbuf) == 0 && statbuf.st_size > 0 )
296  {
297  void * p = ::mmap( nullptr , statbuf.st_size , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0 ) ;
298  if( fn && p ) fn( signal_safe , p ) ;
299  }
300  ::close( fd ) ;
301  }
302  G::Root::stop( signal_safe , identity ) ;
303  deleteMarker( signal_safe , os_name ) ;
304 }
305 
307 {
308  ::fcntl( m_imp.fd() , F_SETFD , ::fcntl(m_imp.fd(),F_GETFD) & ~FD_CLOEXEC ) ; // no close on exec
309 }
310 
311 void * G::SharedMemory::ptr() const
312 {
313  return m_imp.m_p ;
314 }
315 
317 {
318  return m_imp.m_fd ;
319 }
320 
321 size_t G::SharedMemory::size() const
322 {
323  return m_imp.m_size ;
324 }
325 
327 {
328  m_imp.unlink() ;
329 }
330 
331 void G::SharedMemory::createMarker( const std::string & os_name )
332 {
333  std::string path = "/tmp/" + os_name + ".x" ;
334  int fd = -1 ;
335  {
336  G::Root claim_root ;
337  G_IGNORE_RETURN( int , ::unlink( path.c_str() ) ) ; // delete so that ownership gets updated
338  fd = ::open( path.c_str() , O_WRONLY | O_CREAT , 0666 ) ;
339  }
340  if( fd != -1 )
341  {
342  if( !s_filetext.empty() )
343  {
344  G_IGNORE_RETURN( int , ::write( fd , s_filetext.data() , s_filetext.length() ) ) ;
345  }
346  ::close( fd ) ;
347  }
348 }
349 
350 void G::SharedMemory::deleteMarker( const std::string & os_name )
351 {
352  std::string path = "/tmp/" + os_name + ".x" ;
353  {
354  G::Root claim_root ;
355  G_IGNORE_RETURN( int , ::unlink( path.c_str() ) ) ;
356  }
357 }
358 
359 void G::SharedMemory::deleteMarker( SignalSafe signal_safe , const char * os_name )
360 {
361  if( os_name )
362  {
363  std::string path = std::string("/tmp/") + os_name + ".x" ;
364  {
365  G::Identity identity = G::Root::start( signal_safe ) ;
366  G_IGNORE_RETURN( int , ::unlink( path.c_str() ) ) ;
367  G::Root::stop( signal_safe , identity ) ;
368  }
369  }
370 }
371 
372 std::vector<std::string> G::SharedMemory::list( bool os_names )
373 {
374  std::vector<std::string> result ;
375  listImp( result , "/tmp" , "x" , os_names ) ; // createMarker()
376  listImp( result , "/run/shm" , "" , os_names ) ; // linux
377  std::sort( result.begin() , result.end() ) ;
378  result.erase( std::unique(result.begin(),result.end()) , result.end() ) ;
379  return result ;
380 }
381 
382 void G::SharedMemory::listImp( std::vector<std::string> & out , const std::string & dir , const std::string & x , bool os_names )
383 {
384  for( G::DirectoryIterator p( (G::Directory(dir)) ) ; p.more() ; )
385  {
386  if( !p.isDir() && p.filePath().extension() == x &&
387  ( prefix().empty() || p.fileName().find(prefix()) == 0U ) )
388  {
389  std::string basename = p.filePath().withoutExtension().basename() ;
390  std::string sname = prefix().empty() ? basename : basename.substr(prefix().length()) ;
391  out.push_back( os_names ? osName(sname) : sname ) ;
392  }
393  }
394 }
395 
396 std::string G::SharedMemory::delete_( const std::string & name , bool control )
397 {
398  int e , rc = 0 ;
399  {
400  G::Root claim_root ;
401  rc = ::shm_unlink( osName(name).c_str() ) ;
402  e = errno ;
403  }
404  if( rc == 0 || e == ENOENT )
405  {
406  if( control )
407  deleteMarker( osName(name) ) ;
408  return std::string() ;
409  }
410  else
411  {
412  return G::Process::strerror(e) ;
413  }
414 }
415 
416 void G::SharedMemory::help( const std::string & s )
417 {
418  s_help = s ;
419 }
420 
421 void G::SharedMemory::prefix( const std::string & s )
422 {
423  G_ASSERT( s.find('/') == std::string::npos ) ;
424  if( s.find("__") != 0U )
425  s_prefix = s ;
426 }
427 
429 {
430  return s_prefix ;
431 }
432 
433 void G::SharedMemory::filetext( const std::string & s )
434 {
435  s_filetext = s ;
436 }
437 
438 /// \file gsharedmemory.cpp
void * ptr() const
Returns the mapped address.
static void help(const std::string &)
Sets some error-message help text for the case when named shared memory cannot be created because it ...
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:36
static std::string prefix()
Returns the prefix, as set by by a call to the other overload.
void inherit()
Marks fd() as inherited across exec() ie. no-close-on-exec.
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
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:49
static std::string delete_(const std::string &name, bool control_segment)
Unlinks the segment from the filesystem.
static Identity start(SignalSafe)
A signal-safe alternative to construction.
Definition: groot.cpp:80
static std::vector< std::string > list(bool os_names=false)
Returns a list of possible control segment names.
int fd() const
Returns the file descriptor. Returns -1 for anonymous segments.
static void stop(SignalSafe, Identity)
A signal-safe alternative to destruction.
Definition: groot.cpp:86
Overload discriminator for G::SharedMemory.
Definition: gsharedmemory.h:45
~SharedMemory()
Destructor.
An encapsulation of a file system directory that allows for iterating through the set of contained fi...
Definition: gdirectory.h:45
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurances of sub-string 'from' with 'to'...
Definition: gstr.cpp:157
bool remap(size_t, bool may_move, bool no_throw=false)
Resizes and remaps the shared memory.
static void trap(SignalSafe, const char *os_name, void(*fn)(SignalSafe, void *))
An exit handler that calls the user's function on the shared memory contents.
SharedMemory(size_t size)
Constructor for a new anonymous segment, typically used across a fork().
void unlink()
Unlinks the segment from the filesystem.
bool more()
Returns true if more and advances by one.
A Directory iterator.
Definition: gdirectory.h:102
static void cleanup(SignalSafe, const char *os_name, void(*fn)(SignalSafe, void *))
An exit handler that unlinks the named shared memory segment and calls the user's function for the me...
static std::string osName(const std::string &name)
Converts the segment name to an internal form that can be passed to cleanup() or trap().
static void filetext(const std::string &)
Sets some help text that gets written into the placeholder files.
size_t size() const
Returns the segment size. This is affected by remap().