/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Rosegarden A MIDI and audio sequencer and musical notation editor. Copyright 2000-2011 the Rosegarden development team. This file originally from Sonic Visualiser, copyright 2006 Chris Cannam. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "TempDirectory.h" #include "misc/Strings.h" #include #include #include #include #include #include #include #include #include "misc/ConfigGroups.h" namespace Rosegarden { DirectoryCreationFailed::DirectoryCreationFailed(QString directory) throw() : m_directory(directory) { std::cerr << "ERROR: Directory creation failed for directory: " << qstrtostr( directory ) << std::endl; } const char * DirectoryCreationFailed::what() const throw() { return QString("Directory creation failed for \"%1\"") .arg(m_directory).toLocal8Bit().data(); } DirectoryCreationFailed::~DirectoryCreationFailed() throw() { } TempDirectory * TempDirectory::m_instance = new TempDirectory; TempDirectory * TempDirectory::getInstance() { return m_instance; } TempDirectory::TempDirectory() : m_tmpdir("") { } TempDirectory::~TempDirectory() { std::cerr << "TempDirectory::~TempDirectory" << std::endl; cleanup(); } void TempDirectory::cleanup() { cleanupDirectory(""); } QString TempDirectory::getPath() { QMutexLocker locker(&m_mutex); if (m_tmpdir != "") return m_tmpdir; QSettings settings; settings.beginGroup(TempDirectoryConfigGroup); QString rgDirParent = settings.value("create-in", "$HOME").toString(); settings.endGroup(); rgDirParent.replace("$HOME", QDir::home().absolutePath()); QString rgDirBase = ".rosegarden.tmp"; QString rgDir = QDir(rgDirParent).filePath(rgDirBase); if (!QFileInfo(rgDir).exists()) { if (!QDir(rgDirParent).mkdir(rgDirBase)) { throw DirectoryCreationFailed(QString("%1 directory in %2") .arg(rgDirBase).arg(rgDirParent)); } } else if (!QFileInfo(rgDir).isDir()) { throw DirectoryCreationFailed(QString("%1/%2 is not a directory") .arg(rgDirParent).arg(rgDirBase)); } cleanupAbandonedDirectories(rgDir); return createTempDirectoryIn(rgDir); } QString TempDirectory::createTempDirectoryIn(QString dir) { // Entered with mutex held. QDir tempDirBase(dir); // Generate a temporary directory. Qt4.1 doesn't seem to be able // to do this for us, and mkdtemp is not standard. This method is // based on the way glibc does mkdtemp. static QString chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; QString suffix; int padlen = 6, attempts = 100; unsigned int r = time(0) ^ getpid(); for (int i = 0; i < padlen; ++i) { suffix += "X"; } for (int j = 0; j < attempts; ++j) { unsigned int v = r; for (int i = 0; i < padlen; ++i) { suffix[i] = chars[v % 62]; v /= 62; } QString candidate = QString("rg_%1").arg(suffix); if (tempDirBase.mkpath(candidate)) { m_tmpdir = tempDirBase.filePath(candidate); break; } r = r + 7777; } if (m_tmpdir == "") { throw DirectoryCreationFailed(QString("temporary subdirectory in %1") .arg(tempDirBase.canonicalPath())); } QString pidpath = QDir(m_tmpdir).filePath(QString("%1.pid").arg(getpid())); QFile pidfile(pidpath); if (!pidfile.open(QIODevice::WriteOnly)) { throw DirectoryCreationFailed(QString("pid file creation in %1") .arg(m_tmpdir)); } else { pidfile.close(); } return m_tmpdir; } QString TempDirectory::getSubDirectoryPath(QString subdir) { QString tmpdirpath = getPath(); QMutexLocker locker(&m_mutex); QDir tmpdir(tmpdirpath); QFileInfo fi(tmpdir.filePath(subdir)); if (!fi.exists()) { if (!tmpdir.mkdir(subdir)) { throw DirectoryCreationFailed(fi.filePath()); } else { return fi.filePath(); } } else if (fi.isDir()) { return fi.filePath(); } else { throw DirectoryCreationFailed(fi.filePath()); } } void TempDirectory::cleanupDirectory(QString tmpdir) { bool isRoot = false; if (tmpdir == "") { m_mutex.lock(); isRoot = true; tmpdir = m_tmpdir; if (tmpdir == "") { m_mutex.unlock(); return; } } QDir dir(tmpdir); dir.setFilter(QDir::Dirs | QDir::Files); for (unsigned int i = 0; i < dir.count(); ++i) { if (dir[i] == "." || dir[i] == "..") continue; QFileInfo fi(dir.filePath(dir[i])); if (fi.isDir()) { cleanupDirectory(fi.absoluteFilePath()); } else { if (!QFile(fi.absoluteFilePath()).remove()) { std::cerr << "WARNING: TempDirectory::cleanup: " << "Failed to unlink file \"" << qstrtostr(fi.absoluteFilePath()) << "\"" << std::endl; } } } QString dirname = dir.dirName(); if (dirname != "") { if (!dir.cdUp()) { std::cerr << "WARNING: TempDirectory::cleanup: " << "Failed to cd to parent directory of " << qstrtostr(tmpdir) << std::endl; return; } if (!dir.rmdir(dirname)) { std::cerr << "WARNING: TempDirectory::cleanup: " << "Failed to remove directory " << qstrtostr(dirname) << std::endl; } } if (isRoot) { m_tmpdir = ""; m_mutex.unlock(); } } void TempDirectory::cleanupAbandonedDirectories(QString rgDir) { QDir dir(rgDir, "rg_*", QDir::Name, QDir::Dirs); for (unsigned int i = 0; i < dir.count(); ++i) { QDir subdir(dir.filePath(dir[i]), "*.pid", QDir::Name, QDir::Files); for (unsigned int j = 0; j < subdir.count(); ++j) { bool ok = false; int pid = QFileInfo(subdir[j]).baseName().toInt(&ok); if (!ok) continue; if (kill(getpid(), 0) == 0 && kill(pid, 0) != 0) { std::cerr << "INFO: Found abandoned temporary directory from " << "a previous, defunct process\n(pid=" << pid << ", directory=\"" << qstrtostr( dir.filePath(dir[i]) ) << "\"). Removing it..." << std::endl; cleanupDirectory(dir.filePath(dir[i])); std::cerr << "...done." << std::endl; break; } } } } }