#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace LittleEndian; using namespace Pascal; enum { kDLE = 16 }; /* * _lastByte is in the range 1..512 and indicates how many bytes in the last * block are in use. * _lastBlock is the block *after* the actual last block. * _maxFileSize is the maximum file size the file can grow to. * */ unsigned FileEntry::ValidName(const char *cp) { return Entry::ValidName(cp, 15); } FileEntryPointer FileEntry::Open(void *vp) { //return FileEntryPointer(new FileEntry(vp)); return MAKE_SHARED(FileEntry, vp); } FileEntryPointer FileEntry::Create(const char *name, unsigned fileKind) { //return FileEntryPointer(new FileEntry(name, fileKind)); return MAKE_SHARED(FileEntry, name, fileKind); } FileEntry::FileEntry(void *vp) : Entry(vp) { _status = Read8(vp, 0x05) & 0x01; _fileNameLength = Read8(vp, 0x06); std::memset(_fileName, 0, 16); std::memcpy(_fileName, 0x07 + (uint8_t *)vp, _fileNameLength); _lastByte = Read16(vp, 0x16); _modification = Date(Read16(vp, 0x18)); _fileSize = 0; _pageSize = NULL; _maxFileSize = 0; } FileEntry::FileEntry(const char *name, unsigned fileKind) { #undef __METHOD__ #define __METHOD__ "FileEntry::FileEntry" unsigned length = ValidName(name); if (!length) throw ::Exception(__METHOD__ ": Invalid file name."); _fileKind = fileKind; _status = 0; _fileNameLength = length; std::memset(_fileName, 0, sizeof(_fileName)); for (unsigned i = 0; i < length; ++i) _fileName[i] = std::toupper(name[i]); _modification = Date::Today(); _lastByte = 0; _fileSize = 0; _pageSize = NULL; _maxFileSize = 0; } FileEntry::~FileEntry() { delete _pageSize; } void FileEntry::setFileKind(unsigned kind) { _fileKind = kind; if (_pageSize) { delete _pageSize; _fileSize = 0; _pageSize = NULL; } VolumeEntryPointer v = parent().lock(); // throw if expired? if (v) v->writeEntry(this); } void FileEntry::setName(const char *name) { #undef __METHOD__ #define __METHOD__ "FileEntry::setName" unsigned length = ValidName(name); if (!length) throw ProDOS::Exception(__METHOD__ ": Invalid file name.", ProDOS::badPathSyntax); _fileNameLength = length; for (unsigned i = 0; i < length; ++i) _fileName[i] = std::toupper(name[i]); // parent's responsibility. //parent()->writeEntry(this); } unsigned FileEntry::fileSize() { switch(fileKind()) { case kTextFile: return textFileSize(); break; default: return dataFileSize(); break; } } void FileEntry::writeDirectoryEntry(IOBuffer *b) { Entry::writeDirectoryEntry(b); b->write8(_status ? 0x01 : 0x00); b->write8(_fileNameLength); b->writeBytes(_fileName, 15); b->write16(_lastByte); b->write16(_modification); } int FileEntry::read(uint8_t *buffer, unsigned size, unsigned offset) { unsigned fsize = fileSize(); if (offset + size > fsize) size = fsize - offset; if (offset >= fsize) return 0; switch(fileKind()) { case kTextFile: return textRead(buffer, size, offset); break; default: return dataRead(buffer, size, offset); break; } } int FileEntry::truncate(unsigned newSize) { #undef __METHOD__ #define __METHOD__ "FileEntry::truncate" unsigned currentSize = fileSize(); if (currentSize == newSize) return 0; if (fileKind() == kTextFile) { if (newSize) { errno = EINVAL; return -1; } newSize = 2; // text files have a 2-page scratch buffer for the editor. if (_pageSize) { _pageSize->clear(); _fileSize = 0; } } if (truncateCommon(newSize) != 0) return -1; _modification = Date::Today(); VolumeEntryPointer v = parent().lock(); if (v) v->writeEntry(this); return 0; } /* * truncateCommon -- common truncation code. * updates _lastByte and _lastBlock but does * not update _modification or commit to disk. */ int FileEntry::truncateCommon(unsigned newSize) { #undef __METHOD__ #define __METHOD__ "FileEntry::truncateCommon" unsigned currentSize = fileSize(); VolumeEntryPointer v = parent().lock(); if (newSize == currentSize) return 0; if (newSize > currentSize) { if (newSize > _maxFileSize) { errno = ENOSPC; return -1; } unsigned remainder = newSize - currentSize; unsigned block = _lastBlock - 1; if (_lastByte != 512) { // last page not full unsigned count = std::min(512 - _lastByte, remainder); if (v) { uint8_t *address = (uint8_t *)v->loadBlock(block); std::memset(address + _lastByte, 0, count); v->unloadBlock(block, true); } remainder -= count; } block++; while (remainder) { unsigned count = std::min(512u, remainder); if (v) { uint8_t *address = (uint8_t *)v->loadBlock(block); std::memset(address, 0, count); v->unloadBlock(block, true); } remainder -= count; block++; } } setFileSize(newSize); return 0; } int FileEntry::write(TextWriter &text) { unsigned blocks = text.blocks(); VolumeEntryPointer v = parent().lock(); if (!v) { errno = EROFS; return -1; } if (v->readOnly()) { errno = EROFS; return -1; } if (fileKind() != kTextFile) { errno = EINVAL; return -1; } if (blocks * 512 > _maxFileSize) { errno = ENOSPC; return -1; } for (unsigned i = 0; i < blocks; ++i) { void *buffer = text.data(i); v->writeBlock(_firstBlock + i, buffer); } _modification = Date::Today(); setFileSize(blocks * 512); v->writeEntry(this); v->sync(); return blocks * 512; } int FileEntry::write(const uint8_t *buffer, unsigned size, unsigned offset) { #undef __METHOD__ #define __METHOD__ "FileEntry::write" VolumeEntryPointer v = parent().lock(); if (!v) { errno = EROFS; return -1; } if (v->readOnly()) { errno = EROFS; return -1; } if (fileKind() == kTextFile) { errno = EINVAL; return -1; } unsigned currentSize = fileSize(); unsigned newSize = std::max(offset + size, currentSize); if (newSize > _maxFileSize) { errno = ENOSPC; return -1; } if (offset > currentSize) { if (truncateCommon(offset) != 0) return -1; } // now write the data... unsigned block = firstBlock() + offset / 512; unsigned start = offset % 512; unsigned remainder = size; if (start) { unsigned count = std::min(512 - start, remainder); uint8_t *address = (uint8_t *)v->loadBlock(block); std::memcpy(address + start, buffer, count); v->unloadBlock(block, true); remainder -= count; buffer += count; block++; } while (remainder) { uint8_t *address = (uint8_t *)v->loadBlock(block); unsigned count = std::min(512u, size); std::memcpy(address, buffer, count); v->unloadBlock(block, true); remainder -= count; buffer += count; block++; } if (newSize > currentSize) setFileSize(newSize); _modification = Date::Today(); v->writeEntry(this); return size; } /* * private * set the file size. Does not check if > _maxFileSize. * */ void FileEntry::setFileSize(unsigned size) { if (size == 0) { // TODO -- verify how 0 byte files are handled. _lastBlock = _firstBlock + 1; _lastByte = 0; return; } _lastBlock = 1 + _firstBlock + size / 512; _lastByte = size % 512; if (_lastByte == 0) _lastByte = 512; } unsigned FileEntry::dataFileSize() { return blocks() * 512 - 512 + _lastByte; } unsigned FileEntry::textFileSize() { if (!_pageSize) textInit(); return _fileSize; } int FileEntry::dataRead(uint8_t *buffer, unsigned size, unsigned offset) { uint8_t tmp[512]; unsigned count = 0; unsigned block = 0; VolumeEntryPointer v = parent().lock(); if (!v) { errno = EROFS; return 0; } block = _firstBlock + (offset / 512); // returned value (count) is equal to size at this point. // (no partial reads). /* * 1. Block align everything */ if (offset % 512) { unsigned bytes = std::min(offset % 512, size); v->readBlock(block++, tmp); std::memcpy(buffer, tmp + 512 - bytes, bytes); buffer += bytes; count += bytes; size -= bytes; } /* * 2. read full blocks into the buffer. */ while (size >= 512) { v->readBlock(block++, buffer); buffer += 512; count += 512; size -= 512; } /* * 3. Read any trailing blocks. */ if (size) { v->readBlock(block, tmp); std::memcpy(buffer, tmp, size); count += size; } return count; } int FileEntry::textRead(uint8_t *buffer, unsigned size, unsigned offset) { unsigned page = 0; unsigned to = 0; unsigned block; unsigned l; unsigned count = 0; ::auto_array tmp; unsigned tmpSize = 0; if (!_pageSize) textInit(); l = _pageSize->size(); // find the first page. for (page = 0; page < l; ++page) { unsigned pageSize = (*_pageSize)[page]; if (to + pageSize > offset) { break; } to += pageSize; } // first 2 pages are spare, for editor use, not actually text. block = _firstBlock + 2 + (page * 2); // offset not needed anymore, // convert to offset from *this* page. offset -= to; while (size) { unsigned pageSize = (*_pageSize)[page]; unsigned bytes = std::min(size, pageSize - offset); if (pageSize > tmpSize) { tmp.reset(new uint8_t[pageSize]); tmpSize = pageSize; } // can decode straight to buffer if size >= bytes && offset = 0. textDecodePage(block, tmp.get()); std::memcpy(buffer, tmp.get() + offset, bytes); block += 2; page += 1; size -= bytes; buffer += bytes; count += bytes; offset = 0; } return count; } unsigned FileEntry::textDecodePage(unsigned block, uint8_t *out) { uint8_t buffer[1024]; unsigned size = 0; bool dle = false; unsigned bytes = textReadPage(block, buffer); for (unsigned i = 0; i < bytes; ++i) { uint8_t c = buffer[i]; if (!c) continue; if (dle) { if (c > 32) { unsigned x = c - 32; size += x; if (out) for (unsigned j = 0; j < x; ++j) *out++ = ' '; } dle = false; continue; } if (c == kDLE) { dle = true; continue; } //if (c & 0x80) continue; // ascii only. if (c == 0x0d) c = 0x0a; // convert to unix format. if (out) *out++ = c; size += 1; } return size; } unsigned FileEntry::textReadPage(unsigned block, uint8_t *in) { // reads up to 2 blocks. // assumes block within _startBlock ... _lastBlock - 1 VolumeEntryPointer v = parent().lock(); if (!v) { errno = EROFS; return 0; } v->readBlock(block, in); if (block + 1 == _lastBlock) { return _lastByte; } v->readBlock(block + 1, in + 512); if (block +2 == _lastBlock) { return 512 + _lastByte; } return 1024; } void FileEntry::textInit() { // calculate the file size and page offsets. _pageSize = new std::vector; _pageSize->reserve((_lastBlock - _firstBlock + 1 - 2) / 2); _fileSize = 0; for (unsigned block = _firstBlock + 2; block < _lastBlock; block += 2) { unsigned size = textDecodePage(block, NULL); //printf("%u: %u\n", block, size); _fileSize += size; _pageSize->push_back(size); } } /* * compress white space into a dle. * returns true if altered. * nb -- only leading white space is compressed. */ bool FileEntry::Compress(std::string& text) { std::string out; size_t pos; size_t count; if (text.length() < 3) return false; if (text[0] != ' ') return false; pos = text.find_first_not_of(' '); if (pos == std::string::npos) count = text.length(); else count = pos; if (count < 3) return false; count = std::min((int)count, 255 - 32); out.push_back(kDLE); out.push_back(32 + count); out.append(text.begin() + count, text.end()); text.swap(out); return true; } /* * dle will only occur at start. * */ bool FileEntry::Uncompress(std::string& text) { std::string out; unsigned c; if (text.length() < 2) return false; if (text[0] != kDLE) return false; c = text[1]; if (c < 32) c = 32; out.append(c - 32, ' '); out.append(text.begin() + 2, text.end()); text.swap(out); return true; }