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 }
MutexHolder
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
SimpleAllocator
An implementation of a very simple block allocator.
Definition: simpleAllocator.h:29
config_gobj.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
SimpleAllocatorBlock::get_start
size_t get_start() const
Returns the starting point of this block.
Definition: simpleAllocator.I:224
Thread::consider_yield
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
clockObject.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ClockObject::get_global_clock
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
Filename::get_temp_directory
static const Filename & get_temp_directory()
Returns a path to a system-defined temporary directory.
Definition: filename.cxx:539
SimpleAllocatorBlock::get_size
size_t get_size() const
Returns the size of this block.
Definition: simpleAllocator.I:234
VertexDataSaveFile::read_data
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.
Definition: vertexDataSaveFile.cxx:276
Thread::get_current_thread
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition: thread.h:109
ClockObject::get_real_time
get_real_time
Returns the actual number of seconds elapsed since the ClockObject was created, or since it was last ...
Definition: clockObject.h:92
vertexDataSaveFile.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
SimpleAllocatorBlock
A single block as returned from SimpleAllocator::alloc().
Definition: simpleAllocator.h:90
VertexDataSaveBlock
A block of bytes on the save file.
Definition: vertexDataSaveFile.h:73
mutexHolder.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PT
PT(VertexDataSaveBlock) VertexDataSaveFile
Writes a block of data to the file, and returns a handle to the block handle.
Definition: vertexDataSaveFile.cxx:197
Thread::force_yield
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition: thread.I:201
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39