/* * @(#)Store.cpp * * This file is part of webCDwriter - Network CD/DVD Writing. * * Copyright (C) 1999-2005 Jörg P. M. Haeger * * webCDwriter is free software. See CDWserver.cpp for details. */ #include #include #include #include #ifdef FreeBSD #include #include #else #include #endif #include "File.h" #include "Config.h" #include "Server.h" #include "Session.h" #include "Store.h" #include "Queue.h" Store::Store() { pthread_mutex_init(&mutex, NULL); spoolDir = new File(config.getSpoolDir()); maxOpenSessions = config.getMaxOpenSessions(); sessionsNum = 0; sessions = new Session *[maxOpenSessions]; int i; for (i = 0; i < maxOpenSessions; i++) sessions[i] = NULL; queue = new Queue(); // get blocksize of filesystem struct statfs statfsBuf; statfs(config.getSpoolDir(), &statfsBuf); fsBlockSize = statfsBuf.f_bsize; int64 n = 1024 * 1024 * config.getReservedMBytes(); n /= fsBlockSize; reservedBlocks = n; maxSizeInKBytes = 1024 * config.getMaxMBytesInSpoolDir(); sizeInKBytes = 0; imageInSpoolDirFlags = new boolean[maxNumOfWriters]; if (!config.getImageOnTheFly()) for (int i = 0; i < config.getNumOfWriters(); i++) { FILE *imageFile = fopen(config.getImageFile(i), "w"); if (imageFile == NULL) throw new Exception("can't create imageFile"); fclose(imageFile); dev_t imageFileDevice, spoolDirDevice; struct stat statBuf; if (stat(config.getImageFile(i), &statBuf) != 0) throw new Exception("can't stat imageFile"); imageFileDevice = statBuf.st_dev; if (stat(config.getSpoolDir(), &statBuf) != 0) throw new Exception("can't stat spoolDir"); spoolDirDevice = statBuf.st_dev; log.put(5, S.e + "imageFileDevice = " + (int) imageFileDevice + ", spoolDirDevice = " + (int) spoolDirDevice); imageInSpoolDirFlags[i] = imageFileDevice == spoolDirDevice && !config.isReadOnlyDevice(i); if (imageInSpoolDirFlags[i]) { int maxImageSize = min(config.getMaxMBytesPerSession(), 800); maxSizeInKBytes -= 1024 * maxImageSize; } } DIR *dir = opendir(config.getSpoolDir()); if (dir == NULL) throw new Exception("spoolDir does not exist"); sessionsTotal = 0; struct dirent *dirEntry; while ((dirEntry = readdir(dir)) != NULL) { if (strcmp(dirEntry->d_name, ".") == 0 || strcmp(dirEntry->d_name, "..") == 0) continue; File dir(*spoolDir, dirEntry->d_name); if (!Session::isSessionDir(dir)) continue; sessionsTotal++; sizeInKBytes += Session::getSessionSize(dir); } closedir(dir); log.put("sessionsTotal=%d, sizeInKBytes = %d, reservedBlocks = %d", sessionsTotal, sizeInKBytes, reservedBlocks); } Store::~Store() { } Session *Store::createSession(Server *server, const char *user, const char *password, const char *name, int overwrite) { char ID[64 + 1]; makeSessionID(user, password, name, ID, sizeof ID); queue->add(server->getNo()); try { return createSession2(server, ID, overwrite); } catch (Exception *e) { queue->remove(server->getNo()); throw e; } } Session *Store::createSession2(Server *server, const char *ID, int overwrite) { int cancel = server->waitForService(queue, maxOpenSessions - 1); if (cancel) throw new Exception("Waiting for session cancelled."); pthread_mutex_lock(&mutex); try { Session *session = createSession3(server, ID, overwrite); pthread_mutex_unlock(&mutex); return session; } catch (Exception *e) { pthread_mutex_unlock(&mutex); throw e; } } Session *Store::createSession3(Server *server, const char *ID, int overwrite) { if (isSessionIDInUse(ID)) throw new Exception("The session ID is already active."); File dir(config.getSpoolDir(), ID); int sessionExists = Session::isSessionDir(dir); if (sessionExists) { if (overwrite) { removeSession2(ID); sessionExists = 0; } else { time_t t; time(&t); if (t > 0) dir.setLastModified(t); } } else if (dir.exists()) throw new Exception("Cannot get session directory."); Session *session = new Session(server, ID, dir.getPath().getBytes()); if (!sessionExists) sessionsTotal++; int i; for (i = 0; i < maxOpenSessions && sessions[i] != NULL; i++); if (i < maxOpenSessions) { sessions[i] = session; sessionsNum++; } else throw new Exception("internal error in Store::createSession3"); return session; } void Store::freeSpace() { while (1) { struct statfs statfsBuf; statfs(config.getSpoolDir(), &statfsBuf); long free = statfsBuf.f_bavail; long reserved = reservedBlocks + getReservedBlocksForImageFile(); if (free > reserved && sizeInKBytes <= maxSizeInKBytes) break; log.putv("freeSpace: free = %d, reserved = %d", free, reserved); if (sizeInKBytes > maxSizeInKBytes) log.putv("sizeInKBytes = %d, maxSizeInKBytes = %d", sizeInKBytes, maxSizeInKBytes); removeSessionLRU(); } } void Store::getInfo( const char *user, const char *password, const char *name, int &files, int &tracks, int &size_KB) { char ID[64 + 1]; makeSessionID(user, password, name, ID, sizeof ID); Session::getInfo(ID, files, tracks, size_KB); } int Store::getReservedBlocksForImageFile() { if (config.getImageOnTheFly()) return 0; long n = 0; for (int i = 0; i < config.getNumOfWriters(); i++) { if (!imageInSpoolDirFlags[i]) continue; int maxImageSize = min(config.getMaxMBytesPerSession(), 800); n += sizeToBlocks(1024 * 1024 * maxImageSize); struct stat statBuf; if (stat(config.getImageFile(i), &statBuf) == 0) n -= sizeToBlocks(statBuf.st_size); } return n; } File *Store::getSessionDir(const char *sessionID) { return new File(config.getSpoolDir(), sessionID); } int Store::isSessionIDInUse(const char *sessionID) { int i; for (i = 0; i < maxOpenSessions; i++) if (sessions[i] != NULL) if (strcmp(sessionID, sessions[i]->getID()) == 0) break; return i < maxOpenSessions; } int Store::list(const char *userID, const char *userPassword, const char **sessionIDs, int max) { char ID[64 + 1]; makeSessionID(userID, userPassword, "", ID, sizeof ID); DIR *dir = opendir(spoolDir->getPath().getBytes()); if (dir == NULL) throw new Exception("can't open spoolDir"); int count = 0; struct dirent *dirEntry; while (count < max && (dirEntry = readdir(dir)) != NULL) { char *name = dirEntry->d_name; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; if (strstr(name, ID) != name) continue; sessionIDs[count++] = strDup(&name[strlen(ID)]); } closedir(dir); return count; } char *Store::makeSessionID(const char *user, const char *password, const char *name, char *ID, int IDSize) { int i = 0; /* for (; i < IDSize - 2 && *password != 0; i++) { char ch = *password++; if (ch == '/' || ch == '-') ID[i]= '_'; else ID[i] = ch; } ID[i++] = '-'; */ for (; i < IDSize - 2 && *user != 0; i++) { char ch = *user++; if (ch == '/' || ch == '-') ID[i]= '_'; else ID[i] = ch; } ID[i++] = '-'; for (; i < IDSize - 1 && *name != 0; i++) { char ch = *name++; if (ch == '/' || ch == '-') ID[i]= '_'; else ID[i] = ch; } ID[i] = 0; return ID; } void Store::removeFile(const char *pathName) { waitUntilAllFIFOsFilled(); struct stat statBuf; if (lstat(pathName, &statBuf) != 0) return; if (!S_ISDIR(statBuf.st_mode)) { unlink(pathName); return; } DIR *dir = opendir(pathName); if (dir == NULL) return; struct dirent *dirEntry; while ((dirEntry = readdir(dir)) != NULL) { if (strcmp(dirEntry->d_name, ".") == 0 || strcmp(dirEntry->d_name, "..") == 0) continue; char tmpPathName[strlen(pathName) + 1 + strlen(dirEntry->d_name) + 1]; sprintf(tmpPathName, "%s/%s", pathName, dirEntry->d_name); removeFile(tmpPathName); } closedir(dir); rmdir(pathName); } void Store::removeSession(const char *ID) { try { pthread_mutex_lock(&mutex); if (isSessionIDInUse(ID)) throw new Exception("Cannot remove an active session."); removeSession2(ID); pthread_mutex_unlock(&mutex); } catch (Exception *e) { pthread_mutex_unlock(&mutex); throw e; } } void Store::removeSession2(const char *ID) { File dir(config.getSpoolDir(), ID); if (!Session::isSessionDir(dir)) { log.put(1, S.e + "FIXME: Try to remove " + dir.getPath()); if (sizeInKBytes >= 1024) sizeInKBytes -= 1024; return; } int size_KB = Session::getSessionSize(dir); log.put(5, S.e + "Store::removeSession " + size_KB + " KB"); sessionsTotal--; sizeInKBytes -= size_KB; if (sizeInKBytes < 0) sizeInKBytes = 0; log.put(5, S.e + "remove(" + dir.getPath() + ")..."); removeFile(dir.getPath().getBytes()); log.put(3, S.e + "remove(" + dir.getPath() + ")... done"); log.put(3, S.e + "Store: " + sessionsTotal + " sessions, " + sizeInKBytes + " KB"); } void Store::removeSessionLRU() { DIR *dir = opendir(spoolDir->getPath().getBytes()); if (dir == NULL) throw new Exception("can't open spoolDir"); const char *ID_LRU = NULL; int t; struct dirent *dirEntry; while ((dirEntry = readdir(dir)) != NULL) { if (strcmp(dirEntry->d_name, ".") == 0 || strcmp(dirEntry->d_name, "..") == 0) continue; if (isSessionIDInUse(dirEntry->d_name)) continue; File sessionDir(spoolDir, dirEntry->d_name); if (!Session::isSessionDir(sessionDir)) continue; struct stat statBuf; if (stat(sessionDir.getPath().getBytes(), &statBuf) != 0) continue; if (ID_LRU == NULL || statBuf.st_mtime < t) { if (ID_LRU != NULL) delete[] ID_LRU; ID_LRU = strDup(dirEntry->d_name); t = statBuf.st_mtime; } } closedir(dir); if (ID_LRU != NULL) { removeSession2(ID_LRU); delete[] ID_LRU; } else throw new Exception("out of disk space"); } int32 Store::sizeToBlocks(int64 size) { if (size > 0) return (size - 1) / fsBlockSize + 1; else return 0; } void Store::unregister(Session *session) { int i; for (i = 0; i < maxOpenSessions && sessions[i] != session; i++); if (i < maxOpenSessions) { sessions[i] = NULL; sessionsNum--; } else log.put("internal error in Store::unregister"); queue->remove(session->getServer()->getNo()); } void Store::waitUntilAllFIFOsFilled() { int quit = 0; while (!quit) { quit = 1; for (int i = 0; i < config.getNumOfWriters(); i++) if (Server::getWriterStatus(i) == writing && Server::getWriterFifoPercent(i) < 100) { sleep(1); quit = 0; break; } } } void Store::write(const char *buf, int size, int fd) { pthread_mutex_lock(&mutex); try { waitUntilAllFIFOsFilled(); sizeInKBytes += (size + 1023) / 1024; freeSpace(); while (size > 0) { int n = ::write(fd, buf, size); if (n < 0) if (errno == EBADF) throw new Exception("bad file descriptor"); else if (errno == EFBIG) throw new Exception("file to big"); else if (errno == EIO) throw new Exception("hardware error"); else if (errno == ENOSPC) { freeSpace(); continue; } else throw new Exception("unknown error"); buf += n; size -= n; } pthread_mutex_unlock(&mutex); } catch (Exception *e) { pthread_mutex_unlock(&mutex); throw e; } } Store *store;