Merge remote-tracking branch 'remotes/origin/cairocanvas' into windows
[ardour.git] / libs / pbd / file_manager.cc
1 /*
2     Copyright (C) 2010 Paul Davis
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 2 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, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <sys/time.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <cassert>
25 #include <cstdio>
26
27 #include <glib.h>
28 #include <glib/gstdio.h>
29
30 #ifdef __APPLE__
31 #include <mach/mach_time.h>
32 #endif
33
34 #include "pbd/compose.h"
35 #include "pbd/file_manager.h"
36 #include "pbd/resource.h"
37 #include "pbd/debug.h"
38
39 using namespace std;
40 using namespace PBD;
41
42 FileManager* FileDescriptor::_manager;
43
44 FileManager::FileManager ()
45         : _open (0)
46 {
47         struct ResourceLimit rl;
48         
49         /* XXX: this is a bit arbitrary */
50         if (get_resource_limit (OpenFiles, rl)) {
51                 _max_open = rl.current_limit - 64;
52         } else {
53                 _max_open = 256;
54         }
55
56         DEBUG_TRACE (DEBUG::FileManager, string_compose ("FileManager can open up to %1 files.\n", _max_open));
57 }
58
59 void
60 FileManager::add (FileDescriptor* d)
61 {
62         Glib::Threads::Mutex::Lock lm (_mutex);
63         _files.push_back (d);
64 }
65
66 /** @return true on error, otherwise false */
67 bool
68 FileManager::allocate (FileDescriptor* d)
69 {
70         Glib::Threads::Mutex::Lock lm (_mutex);
71
72         if (!d->is_open()) {
73                 
74                 /* this file needs to be opened */
75                 
76                 if (_open == _max_open) {
77
78                         /* We already have the maximum allowed number of files opened, so we must try to close one.
79                            Find the unallocated, open file with the lowest last_used time.
80                         */
81
82                         double lowest_last_used = DBL_MAX;
83                         list<FileDescriptor*>::iterator oldest = _files.end ();
84
85                         for (list<FileDescriptor*>::iterator i = _files.begin(); i != _files.end(); ++i) {
86                                 if ((*i)->is_open() && (*i)->_refcount == 0) {
87                                         if ((*i)->_last_used < lowest_last_used) {
88                                                 lowest_last_used = (*i)->_last_used;
89                                                 oldest = i;
90                                         }
91                                 }
92                         }
93
94                         if (oldest == _files.end()) {
95                                 /* no unallocated and open files exist, so there's nothing we can do */
96                                 return true;
97                         }
98
99                         close (*oldest);
100                         DEBUG_TRACE (
101                                 DEBUG::FileManager,
102                                 string_compose (
103                                         "closed file for %1 to release file handle; now have %2 of %3 open\n",
104                                         (*oldest)->_path, _open, _max_open
105                                         )
106                                 );
107                 }
108
109                 if (d->open ()) {
110                         DEBUG_TRACE (DEBUG::FileManager, string_compose ("open of %1 failed.\n", d->_path));
111                         return true;
112                 }
113
114                 _open++;
115
116                 DEBUG_TRACE (DEBUG::FileManager, string_compose ("opened file for %1; now have %2 of %3 open.\n", d->_path, _open, _max_open));
117         }
118
119 #ifdef __APPLE__
120         d->_last_used = mach_absolute_time();
121 #elif defined(_POSIX_TIMERS) && defined(_POSIX_MONOTONIC_CLOCK)
122         struct timespec t;
123         clock_gettime (CLOCK_MONOTONIC, &t);
124         d->_last_used = t.tv_sec + (double) t.tv_nsec / 10e9;
125 #else
126         struct timeval now;
127         gettimeofday (&now, NULL);
128         d->_last_used = now.tv_sec + (double) now.tv_usec / 10e6;
129 #endif
130
131         d->_refcount++;
132         
133         return false;
134 }
135
136 /** Tell FileManager that a FileDescriptor is no longer needed for a given handle */
137 void
138 FileManager::release (FileDescriptor* d)
139 {
140         Glib::Threads::Mutex::Lock lm (_mutex);
141
142         d->_refcount--;
143         assert (d->_refcount >= 0);
144 }
145
146 /** Remove a file from our lists.  It will be closed if it is currently open. */
147 void
148 FileManager::remove (FileDescriptor* d)
149 {
150         Glib::Threads::Mutex::Lock lm (_mutex);
151
152         if (d->is_open ()) {
153                 close (d);
154                 DEBUG_TRACE (
155                         DEBUG::FileManager,
156                         string_compose ("closed file for %1; file is being removed; now have %2 of %3 open\n", d->_path, _open, _max_open)
157                         );
158         }
159
160         _files.remove (d);
161 }
162
163 void
164 FileManager::close (FileDescriptor* d)
165 {
166         /* we must have a lock on our mutex */
167
168         d->close ();
169         d->Closed (); /* EMIT SIGNAL */
170         _open--;
171 }
172
173 FileDescriptor::FileDescriptor (string const & n, bool w)
174         : _refcount (0)
175         , _last_used (0)
176         , _path (n)
177         , _writeable (w)
178 {
179
180 }
181
182 FileManager*
183 FileDescriptor::manager ()
184 {
185         if (_manager == 0) {
186                 _manager = new FileManager;
187         }
188
189         return _manager;
190 }
191
192 /** Release a previously allocated handle to this file */
193 void
194 FileDescriptor::release ()
195 {
196         manager()->release (this);
197 }
198
199
200
201 /** @param file_name Filename.
202  *  @param writeable true to open writeable, otherwise false.
203  *  @param mode Open mode for the file.
204  */
205
206 FdFileDescriptor::FdFileDescriptor (string const & file_name, bool writeable, mode_t mode)
207         : FileDescriptor (file_name, writeable)
208         , _fd (-1)
209         , _mode (mode)
210 {
211         manager()->add (this);
212 }
213
214 FdFileDescriptor::~FdFileDescriptor ()
215 {
216         manager()->remove (this);
217 }
218
219 bool
220 FdFileDescriptor::is_open () const
221 {
222         /* we must have a lock on the FileManager's mutex */
223
224         return _fd != -1;
225 }
226
227 bool
228 FdFileDescriptor::open ()
229 {
230         /* we must have a lock on the FileManager's mutex */
231
232         /* files must be opened with O_BINARY flag on windows
233          * or it treats the file as a text stream and puts in
234          * line endings in etc
235          */
236 #ifdef WIN32
237 #define WRITE_FLAGS O_RDWR | O_CREAT | O_BINARY
238 #define READ_FLAGS O_RDONLY | O_BINARY
239 #else
240 #define WRITE_FLAGS O_RDWR | O_CREAT
241 #define READ_FLAGS O_RDONLY
242 #endif
243         _fd = ::g_open (_path.c_str(), _writeable ? WRITE_FLAGS : READ_FLAGS, _mode);
244         return (_fd == -1);
245 }
246
247 void
248 FdFileDescriptor::close ()
249 {
250         /* we must have a lock on the FileManager's mutex */
251
252         ::close (_fd);
253         _fd = -1;
254 }
255
256 /** @return fd, or -1 on error */
257 int
258 FdFileDescriptor::allocate ()
259 {
260         bool const f = manager()->allocate (this);
261         if (f) {
262                 return -1;
263         }
264
265         /* this is ok thread-wise because allocate () has incremented
266            the Descriptor's refcount, so the file will not be closed
267         */
268         return _fd;
269 }
270
271
272 void
273 FileDescriptor::set_path (const string& p)
274 {
275         _path = p;
276 }
277
278 /** @param file_name Filename.
279  *  @param mode Mode to pass to fopen.
280  */
281
282 StdioFileDescriptor::StdioFileDescriptor (string const & file_name, std::string const & mode)
283         : FileDescriptor (file_name, false)
284         , _file (0)
285         , _mode (mode)
286 {
287         manager()->add (this);
288 }
289
290 StdioFileDescriptor::~StdioFileDescriptor ()
291 {
292         manager()->remove (this);
293 }
294
295 bool
296 StdioFileDescriptor::is_open () const
297 {
298         /* we must have a lock on the FileManager's mutex */
299
300         return _file != 0;
301 }
302
303 bool
304 StdioFileDescriptor::open ()
305 {
306         /* we must have a lock on the FileManager's mutex */
307         
308         _file = fopen (_path.c_str(), _mode.c_str());
309         return (_file == 0);
310 }
311
312 void
313 StdioFileDescriptor::close ()
314 {
315         /* we must have a lock on the FileManager's mutex */
316
317         fclose (_file);
318         _file = 0;
319 }
320
321 /** @return FILE*, or 0 on error */
322 FILE*
323 StdioFileDescriptor::allocate ()
324 {
325         bool const f = manager()->allocate (this);
326         if (f) {
327                 return 0;
328         }
329
330         /* this is ok thread-wise because allocate () has incremented
331            the Descriptor's refcount, so the file will not be closed
332         */
333         return _file;
334 }