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