Panda3D
vertexDataSaveFile.cxx
1 // Filename: vertexDataSaveFile.cxx
2 // Created by: drose (12May07)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "vertexDataSaveFile.h"
16 #include "mutexHolder.h"
17 #include "clockObject.h"
18 #include "config_gobj.h"
19 
20 #ifndef _WIN32
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <errno.h>
25 #endif // _WIN32
26 
27 ////////////////////////////////////////////////////////////////////
28 // Function: VertexDataSaveFile::Constructor
29 // Access: Public
30 // Description:
31 ////////////////////////////////////////////////////////////////////
32 VertexDataSaveFile::
33 VertexDataSaveFile(const Filename &directory, const string &prefix,
34  size_t max_size) :
35  SimpleAllocator(max_size, _lock)
36 {
37  Filename dir;
38  if (directory.empty()) {
40  } else {
41  dir = directory;
42  }
43 
44  _is_valid = false;
45  _total_file_size = 0;
46 
47  // Try to open and lock a writable temporary filename.
48  int index = 0;
49  while (true) {
50  ++index;
51  ostringstream strm;
52  strm << prefix << "_" << index << ".dat";
53 
54  string basename = strm.str();
55  _filename = Filename(dir, basename);
56  string os_specific = _filename.to_os_specific();
57 
58  if (gobj_cat.is_debug()) {
59  gobj_cat.debug()
60  << "Creating vertex data save file " << os_specific << "\n";
61  }
62 
63 #ifdef _WIN32
64  // Windows case.
65  DWORD flags = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_RANDOM_ACCESS;
66 #if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
67  // In SIMPLE_THREADS mode, we use "overlapped" I/O.
68  flags |= FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING;
69 #endif
70  _handle = CreateFile(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
71  0, NULL, CREATE_ALWAYS, flags, NULL);
72  if (_handle != INVALID_HANDLE_VALUE) {
73  // The file was successfully opened and locked.
74  break;
75 
76  } else {
77  // Couldn't open the file. Either the directory was bad, or the
78  // file was already locked by another.
79  DWORD err = GetLastError();
80 
81  if (err != ERROR_SHARING_VIOLATION) {
82  // File couldn't be opened; permission problem or bogus directory.
83  if (!dir.empty()) {
84  // Try the current directory, once.
85  dir = Filename();
86  } else {
87  gobj_cat.error()
88  << "Couldn't open vertex data save file.\n";
89  return;
90  }
91  }
92 
93  // Couldn't lock the file. Try the next one.
94  }
95 
96 #else
97  // Posix case.
98  int flags = O_RDWR | O_CREAT;
99 #if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
100  // In SIMPLE_THREADS mode, we use non-blocking I/O.
101  flags |= O_NONBLOCK;
102 #endif
103 
104  _fd = ::open(os_specific.c_str(), flags, 0666);
105  while (_fd == -1 && errno == EAGAIN) {
107  _fd = ::open(os_specific.c_str(), flags, 0666);
108  }
109 
110  if (_fd == -1) {
111  // Couldn't open the file: permissions problem or bad directory.
112  if (!_filename.exists()) {
113  // It must be a bad directory.
114  if (!dir.empty()) {
115  // Try the current directory, once.
116  dir = Filename();
117  } else {
118  gobj_cat.error()
119  << "Couldn't open vertex data save file.\n";
120  return;
121  }
122  }
123 
124  // If it's a permissions problem, it might be a user-level
125  // permissions issue. Continue to the next.
126  continue;
127  }
128 
129  // Now try to lock the file, so we can be sure that no other
130  // process is simultaneously writing to the same save file.
131 #ifdef HAVE_LOCKF
132  int result = lockf(_fd, F_TLOCK, 0);
133 #else
134  int result = flock(_fd, LOCK_EX | LOCK_NB);
135 #endif
136  if (result == 0) {
137  // We've got the file. Truncate it first, for good measure, in
138  // case there's an old version of the file we picked up.
139  if (ftruncate(_fd, 0) < 0) {
140  gobj_cat.warning()
141  << "Couldn't truncate vertex data save file.\n";
142  }
143 
144  // On Unix, it's safe to unlink (delete) the temporary file
145  // after it's been opened. The file remains open, but
146  // disappears from the directory. This is kind of like
147  // DELETE_ON_CLOSE, to ensure the temporary file won't
148  // accidentally get left behind, except it's a little more
149  // proactive.
150  unlink(os_specific.c_str());
151  _filename = Filename();
152  break;
153  }
154 
155  // Try the next file.
156  close(_fd);
157 #endif // _WIN32
158  }
159 
160  _is_valid = true;
161 }
162 
163 ////////////////////////////////////////////////////////////////////
164 // Function: VertexDataSaveFile::Destructor
165 // Access: Public
166 // Description:
167 ////////////////////////////////////////////////////////////////////
168 VertexDataSaveFile::
169 ~VertexDataSaveFile() {
170 #ifdef _WIN32
171  if (_handle != NULL) {
172  CloseHandle(_handle);
173  }
174 #else
175  if (_fd != -1) {
176  close(_fd);
177  }
178 #endif // _WIN32
179 
180  // No need to remove the file, since in both above cases we have
181  // already removed it. And removing it now, after we have closed
182  // and unlocked it, might accidentally remove someone else's copy.
183  /*
184  if (!_filename.empty()) {
185  _filename.unlink();
186  }
187  */
188 }
189 
190 ////////////////////////////////////////////////////////////////////
191 // Function: VertexDataSaveFile::write_data
192 // Access: Public
193 // Description: Writes a block of data to the file, and returns a
194 // handle to the block handle. Returns NULL if the data
195 // cannot be written (e.g. no remaining space on the
196 // file).
197 ////////////////////////////////////////////////////////////////////
198 PT(VertexDataSaveBlock) VertexDataSaveFile::
199 write_data(const unsigned char *data, size_t size, bool compressed) {
200  MutexHolder holder(_lock);
201 
202  if (!_is_valid) {
203  return NULL;
204  }
205 
206  PT(VertexDataSaveBlock) block = (VertexDataSaveBlock *)SimpleAllocator::do_alloc(size);
207  if (block != (VertexDataSaveBlock *)NULL) {
208  _total_file_size = max(_total_file_size, block->get_start() + size);
209  block->set_compressed(compressed);
210 
211 #ifdef _WIN32
212  OVERLAPPED overlapped;
213  memset(&overlapped, 0, sizeof(overlapped));
214  overlapped.Offset = block->get_start();
215 
216  DWORD bytes_written = 0;
217  double start_time = ClockObject::get_global_clock()->get_real_time();
218  int num_passes = 0;
219  BOOL success = WriteFile(_handle, data, size, &bytes_written, &overlapped);
220  while (!success) {
221  DWORD error = GetLastError();
222  if (error == ERROR_IO_INCOMPLETE || error == ERROR_IO_PENDING) {
223  // Wait for more later.
225  ++num_passes;
226  } else {
227  gobj_cat.error()
228  << "Error writing " << size
229  << " bytes to save file, windows error code 0x" << hex
230  << error << dec << ". Disk full?\n";
231  return NULL;
232  }
233  success = GetOverlappedResult(_handle, &overlapped, &bytes_written, false);
234  }
235  nassertr(bytes_written == size, NULL);
236  double finish_time = ClockObject::get_global_clock()->get_real_time();
237  if (gobj_cat.is_debug()) {
238  gobj_cat.debug()
239  << "Wrote " << size << " bytes in " << *Thread::get_current_thread() << " over " << floor((finish_time - start_time) * 1000.0) << " ms and " << num_passes << " passes.\n";
240  }
241 #else
242  // Posix case.
243  if (lseek(_fd, block->get_start(), SEEK_SET) == -1) {
244  gobj_cat.error()
245  << "Error seeking to position " << block->get_start() << " in save file.\n";
246  return NULL;
247  }
248 
249  while (size > 0) {
250  ssize_t result = ::write(_fd, data, size);
251  if (result < 0) {
252  if (errno == EAGAIN) {
254  } else {
255  gobj_cat.error()
256  << "Error writing " << size << " bytes to save file. Disk full?\n";
257  return NULL;
258  }
259  continue;
260  }
261 
263  data += result;
264  size -= result;
265  }
266 #endif // _WIN32
267  }
268 
269  return block;
270 }
271 
272 ////////////////////////////////////////////////////////////////////
273 // Function: VertexDataSaveFile::read_data
274 // Access: Public
275 // Description: Reads a block of data from the file, and returns true
276 // on success, false on failure.
277 ////////////////////////////////////////////////////////////////////
279 read_data(unsigned char *data, size_t size, VertexDataSaveBlock *block) {
280  MutexHolder holder(_lock);
281 
282  if (!_is_valid) {
283  return false;
284  }
285 
286  nassertr(size == block->get_size(), false);
287 
288  /*
289  static ConfigVariableBool allow_mainthread_read("allow-mainthread-read", 1);
290  if (!allow_mainthread_read) {
291  nassertr(Thread::get_current_thread() != Thread::get_main_thread(), false);
292  }
293  */
294 
295 #ifdef _WIN32
296  OVERLAPPED overlapped;
297  memset(&overlapped, 0, sizeof(overlapped));
298  overlapped.Offset = block->get_start();
299 
300  DWORD bytes_read = 0;
301  double start_time = ClockObject::get_global_clock()->get_real_time();
302  int num_passes = 0;
303  BOOL success = ReadFile(_handle, data, size, &bytes_read, &overlapped);
304  while (!success) {
305  DWORD error = GetLastError();
306  if (error == ERROR_IO_INCOMPLETE || error == ERROR_IO_PENDING) {
307  // Wait for more later.
309  ++num_passes;
310  } else {
311  gobj_cat.error()
312  << "Error reading " << size
313  << " bytes from save file, windows error code 0x" << hex
314  << error << dec << ".\n";
315  return NULL;
316  }
317  success = GetOverlappedResult(_handle, &overlapped, &bytes_read, false);
318  }
319  nassertr(bytes_read == size, NULL);
320  double finish_time = ClockObject::get_global_clock()->get_real_time();
321  if (gobj_cat.is_debug()) {
322  gobj_cat.debug()
323  << "Read " << size << " bytes in " << *Thread::get_current_thread() << " over " << floor((finish_time - start_time) * 1000.0) << " ms and " << num_passes << " passes.\n";
324  }
325 
326 #else
327  // Posix case.
328  if (lseek(_fd, block->get_start(), SEEK_SET) == -1) {
329  gobj_cat.error()
330  << "Error seeking to position " << block->get_start() << " in save file.\n";
331  return false;
332  }
333  while (size > 0) {
334  ssize_t result = read(_fd, data, size);
335  if (result == -1) {
336  if (errno == EAGAIN) {
338  } else {
339  gobj_cat.error()
340  << "Error reading " << size << " bytes from save file.\n";
341  return false;
342  }
343  }
344 
346  data += result;
347  size -= result;
348  }
349 #endif // _WIN32
350 
351  return true;
352 }
353 
354 ////////////////////////////////////////////////////////////////////
355 // Function: VertexDataSaveFile::make_block
356 // Access: Protected, Virtual
357 // Description: Creates a new SimpleAllocatorBlock object. Override
358 // this function to specialize the block type returned.
359 ////////////////////////////////////////////////////////////////////
360 SimpleAllocatorBlock *VertexDataSaveFile::
361 make_block(size_t start, size_t size) {
362  return new VertexDataSaveBlock(this, start, size);
363 }
364 
A block of bytes on the save file.
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:271
An implementation of a very simple block allocator.
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:29
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition: thread.I:263
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition: thread.I:248
static Thread * get_current_thread()
Returns a pointer to the currently-executing Thread object.
Definition: thread.I:145
A single block as returned from SimpleAllocator::alloc().
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
double get_real_time() const
Returns the actual number of seconds elapsed since the ClockObject was created, or since it was last ...
Definition: clockObject.I:68
static const Filename & get_temp_directory()
Returns a path to a system-defined temporary directory.
Definition: filename.cxx:556
size_t get_start() const
Returns the starting point of this block.
bool read_data(unsigned char *data, size_t size, VertexDataSaveBlock *block)
Reads a block of data from the file, and returns true on success, false on failure.
string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1196
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
size_t get_size() const
Returns the size of this block.