annotate data/fileio/MatrixFile.cpp @ 683:f84f147572b9

Avoid crash when generating/processing a very short file
author Chris Cannam
date Wed, 11 May 2011 11:04:02 +0100
parents dfc4dd561bb6
children b4a8d8221eaf
rev   line source
Chris@148 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@148 2
Chris@148 3 /*
Chris@148 4 Sonic Visualiser
Chris@148 5 An audio file viewer and annotation editor.
Chris@148 6 Centre for Digital Music, Queen Mary, University of London.
Chris@537 7 This file copyright 2006-2009 Chris Cannam and QMUL.
Chris@148 8
Chris@148 9 This program is free software; you can redistribute it and/or
Chris@148 10 modify it under the terms of the GNU General Public License as
Chris@148 11 published by the Free Software Foundation; either version 2 of the
Chris@148 12 License, or (at your option) any later version. See the file
Chris@148 13 COPYING included with this distribution for more information.
Chris@148 14 */
Chris@148 15
Chris@148 16 #include "MatrixFile.h"
Chris@148 17 #include "base/TempDirectory.h"
Chris@150 18 #include "system/System.h"
Chris@148 19 #include "base/Profiler.h"
Chris@148 20 #include "base/Exceptions.h"
Chris@408 21 #include "base/Thread.h"
Chris@148 22
Chris@148 23 #include <sys/types.h>
Chris@148 24 #include <sys/stat.h>
Chris@148 25 #include <fcntl.h>
Chris@148 26 #include <unistd.h>
Chris@148 27
Chris@148 28 #include <iostream>
Chris@148 29
Chris@148 30 #include <cstdio>
Chris@148 31 #include <cassert>
Chris@148 32
Chris@405 33 #include <cstdlib>
Chris@405 34
Chris@148 35 #include <QFileInfo>
Chris@148 36 #include <QDir>
Chris@148 37
Chris@570 38 //#define DEBUG_MATRIX_FILE 1
Chris@403 39 //#define DEBUG_MATRIX_FILE_READ_SET 1
Chris@148 40
Chris@148 41 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@153 42 #ifndef DEBUG_MATRIX_FILE
Chris@148 43 #define DEBUG_MATRIX_FILE 1
Chris@148 44 #endif
Chris@153 45 #endif
Chris@148 46
Chris@148 47 std::map<QString, int> MatrixFile::m_refcount;
Chris@537 48 QMutex MatrixFile::m_createMutex;
Chris@148 49
Chris@148 50 static size_t totalStorage = 0;
Chris@148 51 static size_t totalCount = 0;
Chris@537 52 static size_t openCount = 0;
Chris@148 53
Chris@148 54 MatrixFile::MatrixFile(QString fileBase, Mode mode,
Chris@537 55 size_t cellSize, size_t width, size_t height) :
Chris@148 56 m_fd(-1),
Chris@148 57 m_mode(mode),
Chris@148 58 m_flags(0),
Chris@148 59 m_fmode(0),
Chris@148 60 m_cellSize(cellSize),
Chris@537 61 m_width(width),
Chris@537 62 m_height(height),
Chris@550 63 m_headerSize(2 * sizeof(size_t)),
Chris@550 64 m_setColumns(0),
Chris@554 65 m_autoClose(false),
Chris@554 66 m_readyToReadColumn(-1)
Chris@148 67 {
Chris@148 68 Profiler profiler("MatrixFile::MatrixFile", true);
Chris@148 69
Chris@537 70 #ifdef DEBUG_MATRIX_FILE
Chris@537 71 std::cerr << "MatrixFile::MatrixFile(" << fileBase.toStdString() << ", " << int(mode) << ", " << cellSize << ", " << width << ", " << height << ")" << std::endl;
Chris@537 72 #endif
Chris@148 73
Chris@548 74 m_createMutex.lock();
Chris@148 75
Chris@148 76 QDir tempDir(TempDirectory::getInstance()->getPath());
Chris@148 77 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
Chris@148 78 bool newFile = !QFileInfo(fileName).exists();
Chris@148 79
Chris@148 80 if (newFile && m_mode == ReadOnly) {
Chris@148 81 std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode "
Chris@148 82 << "specified, but cache file does not exist" << std::endl;
Chris@148 83 throw FileNotFound(fileName);
Chris@148 84 }
Chris@148 85
Chris@537 86 if (!newFile && m_mode == WriteOnly) {
Chris@537 87 std::cerr << "ERROR: MatrixFile::MatrixFile: Write-only mode "
Chris@537 88 << "specified, but file already exists" << std::endl;
Chris@537 89 throw FileOperationFailed(fileName, "create");
Chris@148 90 }
Chris@148 91
Chris@148 92 m_flags = 0;
Chris@148 93 m_fmode = S_IRUSR | S_IWUSR;
Chris@148 94
Chris@537 95 if (m_mode == WriteOnly) {
Chris@537 96 m_flags = O_WRONLY | O_CREAT;
Chris@148 97 } else {
Chris@148 98 m_flags = O_RDONLY;
Chris@148 99 }
Chris@148 100
Chris@233 101 #ifdef _WIN32
Chris@233 102 m_flags |= O_BINARY;
Chris@233 103 #endif
Chris@233 104
Chris@148 105 #ifdef DEBUG_MATRIX_FILE
Chris@537 106 std::cerr << "MatrixFile(" << this << ")::MatrixFile: opening " << fileName.toStdString() << "..." << std::endl;
Chris@148 107 #endif
Chris@148 108
Chris@148 109 if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) {
Chris@148 110 ::perror("Open failed");
Chris@148 111 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@148 112 << "Failed to open cache file \""
Chris@148 113 << fileName.toStdString() << "\"";
Chris@537 114 if (m_mode == WriteOnly) std::cerr << " for writing";
Chris@148 115 std::cerr << std::endl;
Chris@148 116 throw FailedToOpenFile(fileName);
Chris@148 117 }
Chris@148 118
Chris@548 119 m_createMutex.unlock();
Chris@548 120
Chris@537 121 #ifdef DEBUG_MATRIX_FILE
Chris@537 122 std::cerr << "MatrixFile(" << this << ")::MatrixFile: fd is " << m_fd << std::endl;
Chris@537 123 #endif
Chris@537 124
Chris@148 125 if (newFile) {
Chris@537 126 initialise(); // write header and "unwritten" column tags
Chris@148 127 } else {
Chris@148 128 size_t header[2];
Chris@148 129 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
Chris@236 130 ::perror("MatrixFile::MatrixFile: read failed");
Chris@148 131 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@148 132 << "Failed to read header (fd " << m_fd << ", file \""
Chris@148 133 << fileName.toStdString() << "\")" << std::endl;
Chris@148 134 throw FileReadFailed(fileName);
Chris@148 135 }
Chris@537 136 if (header[0] != m_width || header[1] != m_height) {
Chris@537 137 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@537 138 << "Dimensions in file header (" << header[0] << "x"
Chris@537 139 << header[1] << ") differ from expected dimensions "
Chris@537 140 << m_width << "x" << m_height << std::endl;
Chris@537 141 throw FailedToOpenFile(fileName);
Chris@537 142 }
Chris@148 143 }
Chris@148 144
Chris@148 145 m_fileName = fileName;
Chris@148 146 ++m_refcount[fileName];
Chris@148 147
Chris@537 148 #ifdef DEBUG_MATRIX_FILE
Chris@537 149 std::cerr << "MatrixFile[" << m_fd << "]::MatrixFile: File " << fileName.toStdString() << ", ref " << m_refcount[fileName] << std::endl;
Chris@148 150
Chris@537 151 std::cerr << "MatrixFile[" << m_fd << "]::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
Chris@537 152 #endif
Chris@148 153
Chris@148 154 ++totalCount;
Chris@537 155 ++openCount;
Chris@148 156 }
Chris@148 157
Chris@148 158 MatrixFile::~MatrixFile()
Chris@148 159 {
Chris@148 160 if (m_fd >= 0) {
Chris@148 161 if (::close(m_fd) < 0) {
Chris@148 162 ::perror("MatrixFile::~MatrixFile: close failed");
Chris@148 163 }
Chris@546 164 openCount --;
Chris@148 165 }
Chris@148 166
Chris@537 167 QMutexLocker locker(&m_createMutex);
Chris@537 168
Chris@550 169 delete m_setColumns;
Chris@550 170
Chris@148 171 if (m_fileName != "") {
Chris@148 172
Chris@148 173 if (--m_refcount[m_fileName] == 0) {
Chris@148 174
Chris@148 175 if (::unlink(m_fileName.toLocal8Bit())) {
Chris@537 176 std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
Chris@148 177 } else {
Chris@537 178 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
Chris@148 179 }
Chris@148 180 }
Chris@148 181 }
Chris@537 182
Chris@537 183 if (m_mode == WriteOnly) {
Chris@537 184 totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize) + m_width);
Chris@537 185 }
Chris@148 186 totalCount --;
Chris@148 187
Chris@537 188 #ifdef DEBUG_MATRIX_FILE
Chris@537 189 std::cerr << "MatrixFile[" << m_fd << "]::~MatrixFile: " << std::endl;
Chris@537 190 std::cerr << "MatrixFile: Total storage now " << totalStorage/1024 << "K in " << totalCount << " instances (" << openCount << " open)" << std::endl;
Chris@537 191 #endif
Chris@148 192 }
Chris@148 193
Chris@148 194 void
Chris@537 195 MatrixFile::initialise()
Chris@148 196 {
Chris@537 197 Profiler profiler("MatrixFile::initialise", true);
Chris@148 198
Chris@537 199 assert(m_mode == WriteOnly);
Chris@550 200
Chris@550 201 m_setColumns = new ResizeableBitset(m_width);
Chris@148 202
Chris@537 203 off_t off = m_headerSize + (m_width * m_height * m_cellSize) + m_width;
Chris@148 204
Chris@148 205 #ifdef DEBUG_MATRIX_FILE
Chris@537 206 std::cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): cell size " << m_cellSize << ", header size " << m_headerSize << ", resizing file" << std::endl;
Chris@148 207 #endif
Chris@148 208
Chris@537 209 if (::lseek(m_fd, off - 1, SEEK_SET) < 0) {
Chris@537 210 ::perror("ERROR: MatrixFile::initialise: seek to end failed");
Chris@537 211 throw FileOperationFailed(m_fileName, "lseek");
Chris@148 212 }
Chris@148 213
Chris@537 214 unsigned char byte = 0;
Chris@537 215 if (::write(m_fd, &byte, 1) != 1) {
Chris@537 216 ::perror("ERROR: MatrixFile::initialise: write at end failed");
Chris@537 217 throw FileOperationFailed(m_fileName, "write");
Chris@537 218 }
Chris@148 219
Chris@537 220 if (::lseek(m_fd, 0, SEEK_SET) < 0) {
Chris@547 221 ::perror("ERROR: MatrixFile::initialise: Seek to write header failed");
Chris@148 222 throw FileOperationFailed(m_fileName, "lseek");
Chris@148 223 }
Chris@148 224
Chris@148 225 size_t header[2];
Chris@537 226 header[0] = m_width;
Chris@537 227 header[1] = m_height;
Chris@148 228 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
Chris@547 229 ::perror("ERROR: MatrixFile::initialise: Failed to write header");
Chris@148 230 throw FileOperationFailed(m_fileName, "write");
Chris@148 231 }
Chris@148 232
Chris@537 233 if (m_mode == WriteOnly) {
Chris@537 234 totalStorage += (m_headerSize + (m_width * m_height * m_cellSize) + m_width);
Chris@148 235 }
Chris@148 236
Chris@537 237 #ifdef DEBUG_MATRIX_FILE
Chris@547 238 std::cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): storage "
Chris@537 239 << (m_headerSize + m_width * m_height * m_cellSize + m_width) << std::endl;
Chris@148 240
Chris@537 241 std::cerr << "MatrixFile: Total storage " << totalStorage/1024 << "K" << std::endl;
Chris@148 242 #endif
Chris@148 243
Chris@554 244 seekTo(0);
Chris@148 245 }
Chris@148 246
Chris@148 247 void
Chris@537 248 MatrixFile::close()
Chris@148 249 {
Chris@537 250 #ifdef DEBUG_MATRIX_FILE
Chris@537 251 std::cerr << "MatrixFile::close()" << std::endl;
Chris@537 252 #endif
Chris@537 253 if (m_fd >= 0) {
Chris@537 254 if (::close(m_fd) < 0) {
Chris@537 255 ::perror("MatrixFile::close: close failed");
Chris@537 256 }
Chris@537 257 m_fd = -1;
Chris@537 258 -- openCount;
Chris@550 259 #ifdef DEBUG_MATRIX_FILE
Chris@550 260 std::cerr << "MatrixFile: Now " << openCount << " open instances" << std::endl;
Chris@550 261 #endif
Chris@148 262 }
Chris@148 263 }
Chris@148 264
Chris@148 265 void
Chris@148 266 MatrixFile::getColumnAt(size_t x, void *data)
Chris@148 267 {
Chris@537 268 assert(m_mode == ReadOnly);
Chris@537 269
Chris@537 270 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 271 std::cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << ")" << std::endl;
Chris@537 272 #endif
Chris@537 273
Chris@408 274 Profiler profiler("MatrixFile::getColumnAt");
Chris@148 275
Chris@554 276 ssize_t r = -1;
Chris@148 277
Chris@554 278 if (m_readyToReadColumn < 0 ||
Chris@554 279 size_t(m_readyToReadColumn) != x) {
Chris@554 280
Chris@554 281 unsigned char set = 0;
Chris@554 282 if (!seekTo(x)) {
Chris@554 283 std::cerr << "ERROR: MatrixFile::getColumnAt(" << x << "): Seek failed" << std::endl;
Chris@554 284 throw FileOperationFailed(m_fileName, "seek");
Chris@554 285 }
Chris@554 286
Chris@554 287 r = ::read(m_fd, &set, 1);
Chris@554 288 if (r < 0) {
Chris@554 289 ::perror("MatrixFile::getColumnAt: read failed");
Chris@554 290 throw FileReadFailed(m_fileName);
Chris@554 291 }
Chris@554 292 if (!set) {
Chris@554 293 std::cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << "): Column has not been set" << std::endl;
Chris@554 294 return;
Chris@554 295 }
Chris@537 296 }
Chris@537 297
Chris@537 298 r = ::read(m_fd, data, m_height * m_cellSize);
Chris@537 299 if (r < 0) {
Chris@537 300 ::perror("MatrixFile::getColumnAt: read failed");
Chris@537 301 throw FileReadFailed(m_fileName);
Chris@537 302 }
Chris@537 303 }
Chris@537 304
Chris@537 305 bool
Chris@537 306 MatrixFile::haveSetColumnAt(size_t x) const
Chris@537 307 {
Chris@550 308 if (m_mode == WriteOnly) {
Chris@550 309 return m_setColumns->get(x);
Chris@550 310 }
Chris@537 311
Chris@554 312 if (m_readyToReadColumn >= 0 &&
Chris@554 313 size_t(m_readyToReadColumn) == x) return true;
Chris@554 314
Chris@537 315 Profiler profiler("MatrixFile::haveSetColumnAt");
Chris@537 316
Chris@537 317 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 318 std::cerr << "MatrixFile[" << m_fd << "]::haveSetColumnAt(" << x << ")" << std::endl;
Chris@537 319 // std::cerr << ".";
Chris@148 320 #endif
Chris@148 321
Chris@537 322 unsigned char set = 0;
Chris@554 323 if (!seekTo(x)) {
Chris@537 324 std::cerr << "ERROR: MatrixFile::haveSetColumnAt(" << x << "): Seek failed" << std::endl;
Chris@537 325 throw FileOperationFailed(m_fileName, "seek");
Chris@537 326 }
Chris@148 327
Chris@537 328 ssize_t r = -1;
Chris@537 329 r = ::read(m_fd, &set, 1);
Chris@148 330 if (r < 0) {
Chris@537 331 ::perror("MatrixFile::haveSetColumnAt: read failed");
Chris@148 332 throw FileReadFailed(m_fileName);
Chris@148 333 }
Chris@148 334
Chris@554 335 if (set) m_readyToReadColumn = int(x);
Chris@554 336
Chris@537 337 return set;
Chris@148 338 }
Chris@148 339
Chris@148 340 void
Chris@148 341 MatrixFile::setColumnAt(size_t x, const void *data)
Chris@148 342 {
Chris@537 343 assert(m_mode == WriteOnly);
Chris@550 344 if (m_fd < 0) return; // closed
Chris@148 345
Chris@148 346 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 347 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << ")" << std::endl;
Chris@537 348 // std::cerr << ".";
Chris@148 349 #endif
Chris@148 350
Chris@148 351 ssize_t w = 0;
Chris@148 352
Chris@554 353 if (!seekTo(x)) {
Chris@537 354 std::cerr << "ERROR: MatrixFile::setColumnAt(" << x << "): Seek failed" << std::endl;
Chris@537 355 throw FileOperationFailed(m_fileName, "seek");
Chris@148 356 }
Chris@148 357
Chris@537 358 unsigned char set = 0;
Chris@537 359 w = ::write(m_fd, &set, 1);
Chris@537 360 if (w != 1) {
Chris@537 361 ::perror("WARNING: MatrixFile::setColumnAt: write failed (1)");
Chris@148 362 throw FileOperationFailed(m_fileName, "write");
Chris@537 363 }
Chris@537 364
Chris@537 365 w = ::write(m_fd, data, m_height * m_cellSize);
Chris@537 366 if (w != ssize_t(m_height * m_cellSize)) {
Chris@537 367 ::perror("WARNING: MatrixFile::setColumnAt: write failed (2)");
Chris@537 368 throw FileOperationFailed(m_fileName, "write");
Chris@537 369 }
Chris@537 370 /*
Chris@537 371 if (x == 0) {
Chris@537 372 std::cerr << "Wrote " << m_height * m_cellSize << " bytes, as follows:" << std::endl;
Chris@537 373 for (int i = 0; i < m_height * m_cellSize; ++i) {
Chris@537 374 std::cerr << (int)(((char *)data)[i]) << " ";
Chris@537 375 }
Chris@537 376 std::cerr << std::endl;
Chris@537 377 }
Chris@537 378 */
Chris@554 379 if (!seekTo(x)) {
Chris@537 380 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Seek failed" << std::endl;
Chris@148 381 throw FileOperationFailed(m_fileName, "seek");
Chris@537 382 }
Chris@537 383
Chris@537 384 set = 1;
Chris@537 385 w = ::write(m_fd, &set, 1);
Chris@537 386 if (w != 1) {
Chris@537 387 ::perror("WARNING: MatrixFile::setColumnAt: write failed (3)");
Chris@537 388 throw FileOperationFailed(m_fileName, "write");
Chris@148 389 }
Chris@550 390
Chris@550 391 m_setColumns->set(x);
Chris@550 392 if (m_autoClose) {
Chris@550 393 if (m_setColumns->isAllOn()) {
Chris@571 394 #ifdef DEBUG_MATRIX_FILE
Chris@550 395 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): All columns set: auto-closing" << std::endl;
Chris@571 396 #endif
Chris@550 397 close();
Chris@550 398 /*
Chris@550 399 } else {
Chris@550 400 int set = 0;
Chris@550 401 for (int i = 0; i < m_width; ++i) {
Chris@550 402 if (m_setColumns->get(i)) ++set;
Chris@550 403 }
Chris@550 404 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Auto-close on, but not all columns set yet (" << set << " of " << m_width << ")" << std::endl;
Chris@550 405 */
Chris@550 406 }
Chris@550 407 }
Chris@148 408 }
Chris@148 409
Chris@537 410 bool
Chris@554 411 MatrixFile::seekTo(size_t x) const
Chris@148 412 {
Chris@537 413 if (m_fd < 0) {
Chris@537 414 std::cerr << "ERROR: MatrixFile::seekTo: File not open" << std::endl;
Chris@537 415 return false;
Chris@148 416 }
Chris@148 417
Chris@554 418 m_readyToReadColumn = -1; // not ready, unless this is subsequently re-set
Chris@554 419
Chris@554 420 off_t off = m_headerSize + x * m_height * m_cellSize + x;
Chris@554 421
Chris@555 422 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@554 423 if (m_mode == ReadOnly) {
Chris@554 424 std::cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "): off = " << off << std::endl;
Chris@554 425 }
Chris@555 426 #endif
Chris@148 427
Chris@148 428 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@554 429 std::cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "): off = " << off << std::endl;
Chris@148 430 #endif
Chris@148 431
Chris@148 432 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
Chris@148 433 ::perror("Seek failed");
Chris@554 434 std::cerr << "ERROR: MatrixFile::seekTo(" << x
Chris@537 435 << ") = " << off << " failed" << std::endl;
Chris@148 436 return false;
Chris@148 437 }
Chris@148 438
Chris@148 439 return true;
Chris@148 440 }
Chris@148 441