diff --git a/headers/private/kernel/boot/vfs.h b/headers/private/kernel/boot/vfs.h index 970638e440..b937582ee2 100644 --- a/headers/private/kernel/boot/vfs.h +++ b/headers/private/kernel/boot/vfs.h @@ -61,6 +61,9 @@ class Directory : public Node { virtual status_t GetNextNode(void *cookie, Node **_node) = 0; virtual status_t Rewind(void *cookie) = 0; virtual bool IsEmpty() = 0; + + virtual status_t CreateFile(const char *name, mode_t permissions, + Node **_node); }; /** The console based nodes don't need cookies for I/O, they @@ -103,7 +106,8 @@ extern status_t register_boot_file_system(Directory *directory); extern Directory *get_boot_file_system(stage2_args *args); extern status_t mount_file_systems(stage2_args *args); extern int open_node(Node *node, int mode); -extern int open_from(Directory *directory, const char *path, int mode); +extern int open_from(Directory *directory, const char *path, int mode, + mode_t permissions = 0); extern Node *get_node_from(int fd); diff --git a/src/system/boot/loader/Jamfile b/src/system/boot/loader/Jamfile index 9ca8495f46..2e8d74d078 100644 --- a/src/system/boot/loader/Jamfile +++ b/src/system/boot/loader/Jamfile @@ -20,6 +20,7 @@ UsePrivateHeaders shared storage ; BOOT_SUPPORT_FILE_SYSTEM_BFS BOOT_SUPPORT_FILE_SYSTEM_TARFS #BOOT_SUPPORT_FILE_MAP_DISK + BOOT_SUPPORT_FILE_SYSTEM_FAT ; # Add architecture specific partition/file system modules diff --git a/src/system/boot/loader/file_systems/amiga_ffs/File.cpp b/src/system/boot/loader/file_systems/amiga_ffs/File.cpp index 3647db87f6..249d17bf9e 100644 --- a/src/system/boot/loader/file_systems/amiga_ffs/File.cpp +++ b/src/system/boot/loader/file_systems/amiga_ffs/File.cpp @@ -6,11 +6,12 @@ #include "File.h" -#include - +#include #include #include +#include + namespace FFS { @@ -18,7 +19,7 @@ class Stream { public: Stream(int device, FileBlock &node); ~Stream(); - + status_t InitCheck(); ssize_t ReadAt(off_t offset, uint8 *buffer, size_t size); @@ -55,35 +56,35 @@ Stream::~Stream() } -status_t +status_t Stream::InitCheck() { return fBlock.BlockData() != NULL ? B_OK : B_NO_MEMORY; } -int32 +int32 Stream::BlockOffset(off_t offset) const { return offset % fNode.BlockSize(); } -int32 +int32 Stream::BlockIndex(off_t offset) const { return (offset % (fNode.BlockSize() * fNode.NumDataBlocks())) / fNode.BlockSize(); } -int32 +int32 Stream::ExtensionBlockOffset(off_t offset) const { return offset / (fNode.BlockSize() * fNode.NumDataBlocks()); } -status_t +status_t Stream::ReadNextExtension() { int32 next; @@ -99,7 +100,7 @@ Stream::ReadNextExtension() } -ssize_t +ssize_t Stream::ReadAt(off_t offset, uint8 *buffer, size_t size) { if (offset < 0) @@ -139,8 +140,8 @@ Stream::ReadAt(off_t offset, uint8 *buffer, size_t size) ssize_t bytesRead = read_pos(fDevice, block * fNode.BlockSize() + blockOffset, buffer, toRead); - if (bytesRead < B_OK) - return bytesRead; + if (bytesRead < 0) + return errno; bytesLeft -= bytesRead; buffer += bytesRead; @@ -172,7 +173,7 @@ File::~File() } -status_t +status_t File::InitCheck() { if (!fNode.IsFile()) @@ -182,7 +183,7 @@ File::InitCheck() } -status_t +status_t File::Open(void **_cookie, int mode) { Stream *stream = new(nothrow) Stream(fVolume.Device(), fNode); @@ -199,7 +200,7 @@ File::Open(void **_cookie, int mode) } -status_t +status_t File::Close(void *cookie) { Stream *stream = (Stream *)cookie; @@ -209,7 +210,7 @@ File::Close(void *cookie) } -ssize_t +ssize_t File::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize) { Stream *stream = (Stream *)cookie; @@ -220,28 +221,28 @@ File::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize) } -ssize_t +ssize_t File::WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize) { return EROFS; } -status_t +status_t File::GetName(char *nameBuffer, size_t bufferSize) const { return fNode.GetName(nameBuffer, bufferSize); } -int32 +int32 File::Type() const { return S_IFREG; } -off_t +off_t File::Size() const { return fNode.Size(); diff --git a/src/system/boot/loader/file_systems/fat/CachedBlock.cpp b/src/system/boot/loader/file_systems/fat/CachedBlock.cpp index 1078f7a328..7eaf5b856a 100644 --- a/src/system/boot/loader/file_systems/fat/CachedBlock.cpp +++ b/src/system/boot/loader/file_systems/fat/CachedBlock.cpp @@ -8,16 +8,14 @@ #include "CachedBlock.h" -#include "Stream.h" -#include "Directory.h" -#include "File.h" - -#include +#include #include #include #include +#include + using namespace FATFS; @@ -47,3 +45,61 @@ CachedBlock::~CachedBlock() } +uint8 * +CachedBlock::SetTo(off_t block) +{ + status_t error = SetTo(block, READ); + return error == B_OK ? fBlock : NULL; +} + + +status_t +CachedBlock::SetTo(off_t blockNumber, uint32 flags) +{ + if (fBlock == NULL) { + fBlock = (uint8*)malloc(BlockSize()); + if (fBlock == NULL) + return B_NO_MEMORY; + } + + if (blockNumber != fBlockNumber) + flags |= FORCE; + + fBlockNumber = blockNumber; + + status_t error = B_OK; + + if ((flags & READ) != 0) { + if ((flags & FORCE) != 0) { + ssize_t bytesRead = read_pos(fVolume.Device(), + fBlockNumber << BlockShift(), fBlock, BlockSize()); + if (bytesRead < 0) + error = bytesRead; + else if (bytesRead < (ssize_t)BlockSize()) + error = B_ERROR; + } + } else if ((flags & CLEAR) != 0) + memset(fBlock, 0, BlockSize()); + + if (error != B_OK) + fBlockNumber = -1; + + return error; +} + + +status_t +CachedBlock::Flush() +{ + if (fBlockNumber < 0) + return B_BAD_VALUE; + + ssize_t written = write_pos(fVolume.Device(), fBlockNumber << BlockShift(), + fBlock, BlockSize()); + if (written < 0) + return errno; + if (written != (ssize_t)BlockSize()) + return B_ERROR; + + return B_OK; +} diff --git a/src/system/boot/loader/file_systems/fat/CachedBlock.h b/src/system/boot/loader/file_systems/fat/CachedBlock.h index 4988b42615..f19ca82036 100644 --- a/src/system/boot/loader/file_systems/fat/CachedBlock.h +++ b/src/system/boot/loader/file_systems/fat/CachedBlock.h @@ -8,6 +8,7 @@ #ifndef CACHED_BLOCK_H #define CACHED_BLOCK_H + #include #include #include @@ -15,15 +16,25 @@ #include "Volume.h" + namespace FATFS { class CachedBlock { + public: + enum { + READ = 0x01, + CLEAR = 0x02, + FORCE = 0x04, + }; + public: CachedBlock(Volume &volume); CachedBlock(Volume &volume, off_t block); ~CachedBlock(); uint8 *SetTo(off_t block); + status_t SetTo(off_t blockNumber, uint32 flags); + status_t Flush(); void Unset(); @@ -46,25 +57,6 @@ CachedBlock::Unset() } -inline uint8 * -CachedBlock::SetTo(off_t block) -{ - if (block == fBlockNumber) - return fBlock; - if (fBlock == NULL) { - fBlock = (uint8 *)malloc(BlockSize()); - if (fBlock == NULL) - return NULL; - } - - fBlockNumber = block; - if (read_pos(fVolume.Device(), block << BlockShift(), fBlock, BlockSize()) < (ssize_t)BlockSize()) - return NULL; - - return fBlock; -} - - } // namespace FATFS diff --git a/src/system/boot/loader/file_systems/fat/Directory.cpp b/src/system/boot/loader/file_systems/fat/Directory.cpp index fbadb5181c..f1e56e6f5a 100644 --- a/src/system/boot/loader/file_systems/fat/Directory.cpp +++ b/src/system/boot/loader/file_systems/fat/Directory.cpp @@ -5,16 +5,21 @@ #include "Directory.h" -#include "Volume.h" -#include "File.h" + +#include +#include +#include +#include + +#include #include #include -#include -#include -#include -#include +#include "CachedBlock.h" +#include "File.h" +#include "Volume.h" + //#define TRACE(x) dprintf x #define TRACE(x) do {} while (0) @@ -27,9 +32,12 @@ struct dir_entry { const char *Extension() const { return fExt; }; uint8 Flags() const { return fFlags; }; uint32 Cluster(int32 fatBits) const; + void SetCluster(uint32 cluster, int32 fatBits); uint32 Size() const { return B_LENDIAN_TO_HOST_INT32(fSize); }; + void SetSize(uint32 size); bool IsFile() const; bool IsDir() const; + char fName[8]; char fExt[3]; uint8 fFlags; @@ -55,6 +63,23 @@ dir_entry::Cluster(int32 fatBits) const return c; } + +void +dir_entry::SetCluster(uint32 cluster, int32 fatBits) +{ + fClusterLSB = B_HOST_TO_LENDIAN_INT16((uint16)cluster); + if (fatBits == 32) + fClusterMSB = B_HOST_TO_LENDIAN_INT16(cluster >> 16); +} + + +void +dir_entry::SetSize(uint32 size) +{ + fSize = B_HOST_TO_LENDIAN_INT32(size); +} + + bool dir_entry::IsFile() const { @@ -70,16 +95,211 @@ dir_entry::IsDir() const struct dir_cookie { + enum { + MAX_UTF16_NAME_LENGTH = 255 + }; + int32 index; struct dir_entry entry; + off_t entryOffset; + uint16 nameBuffer[MAX_UTF16_NAME_LENGTH]; + uint32 nameLength; + off_t Offset() const { return index * sizeof(struct dir_entry); } + char* Name() { return (char*)nameBuffer; } + + void ResetName(); + bool AddNameChars(const uint16* chars, uint32 count); + bool ConvertNameToUTF8(); + void Set8_3Name(const char* baseName, const char* extension); }; -Directory::Directory(Volume &volume, uint32 cluster, const char *name) +void +dir_cookie::ResetName() +{ + nameLength = 0; +} + + +bool +dir_cookie::AddNameChars(const uint16* chars, uint32 count) +{ + // If there is a null character, we ignore it and all subsequent characters. + for (uint32 i = 0; i < count; i++) { + if (chars[i] == 0) { + count = i; + break; + } + } + + if (count > 0) { + if (count > (MAX_UTF16_NAME_LENGTH - nameLength)) + return false; + + nameLength += count; + memcpy(nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength), + chars, count * 2); + } + + return true; +} + + +bool +dir_cookie::ConvertNameToUTF8() +{ + char name[B_FILE_NAME_LENGTH]; + uint32 nameOffset = 0; + + const uint16* utf16 = nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength); + + for (uint32 i = 0; i < nameLength; i++) { + uint8 utf8[4]; + uint32 count; + uint16 c = B_LENDIAN_TO_HOST_INT16(utf16[i]); + if (c < 0x80) { + utf8[0] = c; + count = 1; + } else if (c < 0xff80) { + utf8[0] = 0xc0 | (c >> 6); + utf8[1] = 0x80 | (c & 0x3f); + count = 2; + } else if ((c & 0xfc00) != 0xd800) { + utf8[0] = 0xe0 | (c >> 12); + utf8[1] = 0x80 | ((c >> 6) & 0x3f); + utf8[2] = 0x80 | (c & 0x3f); + count = 3; + } else { + // surrogate pair + if (i + 1 >= nameLength) + return false; + + uint16 c2 = B_LENDIAN_TO_HOST_INT16(utf16[++i]); + if ((c2 & 0xfc00) != 0xdc00) + return false; + + uint32 value = ((c - 0xd7c0) << 10) | (c2 & 0x3ff); + utf8[0] = 0xf0 | (value >> 18); + utf8[1] = 0x80 | ((value >> 12) & 0x3f); + utf8[2] = 0x80 | ((value >> 6) & 0x3f); + utf8[3] = 0x80 | (value & 0x3f); + count = 4; + } + + if (nameOffset + count >= sizeof(name)) + return false; + + memcpy(name + nameOffset, utf8, count); + nameOffset += count; + } + + name[nameOffset] = '\0'; + strlcpy(Name(), name, sizeof(nameBuffer)); + return true; +} + + +void +dir_cookie::Set8_3Name(const char* baseName, const char* extension) +{ + // trim base name + uint32 baseNameLength = 8; + while (baseNameLength > 0 && baseName[baseNameLength - 1] == ' ') + baseNameLength--; + + // trim extension + uint32 extensionLength = 3; + while (extensionLength > 0 && extension[extensionLength - 1] == ' ') + extensionLength--; + + // compose the name + char* name = Name(); + memcpy(name, baseName, baseNameLength); + + if (extensionLength > 0) { + name[baseNameLength] = '.'; + memcpy(name + baseNameLength + 1, extension, extensionLength); + name[baseNameLength + 1 + extensionLength] = '\0'; + } else + name[baseNameLength] = '\0'; +} + + +// #pragma mark - + + +static bool +is_valid_8_3_file_name_char(char c) +{ + if ((uint8)c >= 128) + return true; + + if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) + return true; + + return strchr("*!#$%&'()-@^_`{}~ ", c) != NULL; +} + + +static bool +check_valid_8_3_file_name(const char* name, const char*& _baseName, + uint32& _baseNameLength, const char*& _extension, uint32& _extensionLength) +{ + // check length of base name and extension + size_t nameLength = strlen(name); + const char* extension = strchr(name, '.'); + size_t baseNameLength; + size_t extensionLength; + if (extension != NULL) { + baseNameLength = extension - name; + extensionLength = nameLength - baseNameLength - 1; + if (extensionLength > 0) + extension++; + else + extension = NULL; + } else { + baseNameLength = nameLength; + extensionLength = 0; + } + + // trim trailing space + while (baseNameLength > 0 && name[baseNameLength - 1] == ' ') + baseNameLength--; + while (extensionLength > 0 && extension[extensionLength - 1] == ' ') + extensionLength--; + + if (baseNameLength == 0 || baseNameLength > 8 || extensionLength > 3) + return false; + + // check the chars + for (size_t i = 0; i < baseNameLength; i++) { + if (!is_valid_8_3_file_name_char(name[i])) + return false; + } + + for (size_t i = 0; i < extensionLength; i++) { + if (!is_valid_8_3_file_name_char(extension[i])) + return false; + } + + _baseName = name; + _baseNameLength = baseNameLength; + _extension = extension; + _extensionLength = extensionLength; + + return true; +} + + +// #pragma mark - Directory + +Directory::Directory(Volume &volume, off_t dirEntryOffset, uint32 cluster, + const char *name) : fVolume(volume), - fStream(volume, cluster, UINT32_MAX, name) + fStream(volume, cluster, UINT32_MAX, name), + fDirEntryOffset(dirEntryOffset) { TRACE(("FASFS::Directory::(, %lu, %s)\n", cluster, name)); } @@ -91,7 +311,7 @@ Directory::~Directory() } -status_t +status_t Directory::InitCheck() { status_t err; @@ -102,7 +322,7 @@ Directory::InitCheck() } -status_t +status_t Directory::Open(void **_cookie, int mode) { TRACE(("FASFS::Directory::%s(, %d)\n", __FUNCTION__, mode)); @@ -113,13 +333,14 @@ Directory::Open(void **_cookie, int mode) return B_NO_MEMORY; c->index = -1; + c->entryOffset = 0; *_cookie = (void *)c; return B_OK; } -status_t +status_t Directory::Close(void *cookie) { TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); @@ -138,67 +359,40 @@ Directory::Lookup(const char *name, bool traverseLinks) Acquire(); return this; } - char *dot = strchr(name, '.'); - int baselen = strlen(name); - if (baselen > FATFS_BASENAME_LENGTH) // !? - return NULL; - char *ext = NULL; - int extlen = 0; - if (dot) { - baselen = dot - name; - ext = dot + 1; - if (strlen(ext) > FATFS_EXTNAME_LENGTH) // !? - return NULL; - extlen = strlen(ext); - } status_t err; struct dir_cookie cookie; struct dir_cookie *c = &cookie; c->index = -1; + c->entryOffset = 0; do { err = GetNextEntry(c); if (err < B_OK) return NULL; - TRACE(("FASFS::Directory::%s: %s <> '%8.8s.%3.3s'\n", __FUNCTION__, - name, c->entry.BaseName(), c->entry.Extension())); - int i; - for (i = 0; i < FATFS_BASENAME_LENGTH; i++) - if (c->entry.BaseName()[i] == ' ') - break; - int nlen = MIN(i,FATFS_BASENAME_LENGTH); - for (i = 0; i < FATFS_EXTNAME_LENGTH; i++) - if (c->entry.Extension()[i] == ' ') - break; - int elen = MIN(i,FATFS_EXTNAME_LENGTH); - if (nlen != baselen) - continue; - if (elen != extlen) - continue; - if (strncasecmp(name, c->entry.BaseName(), nlen)) - continue; - if (strncasecmp(ext, c->entry.Extension(), elen)) - continue; - TRACE(("GOT IT!\n")); - break; + TRACE(("FASFS::Directory::%s: %s <> '%s'\n", __FUNCTION__, + name, c->Name())); + if (strcasecmp(name, c->Name()) == 0) { + TRACE(("GOT IT!\n")); + break; + } } while (true); if (c->entry.IsFile()) { TRACE(("IS FILE\n")); - return new File(fVolume, c->entry.Cluster(fVolume.FatBits()), - c->entry.Size(), name); + return new File(fVolume, c->entryOffset, + c->entry.Cluster(fVolume.FatBits()), c->entry.Size(), name); } if (c->entry.IsDir()) { TRACE(("IS DIR\n")); - return new Directory(fVolume, c->entry.Cluster(fVolume.FatBits()), - name); + return new Directory(fVolume, c->entryOffset, + c->entry.Cluster(fVolume.FatBits()), name); } return NULL; } -status_t +status_t Directory::GetNextEntry(void *cookie, char *name, size_t size) { TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); @@ -209,14 +403,12 @@ Directory::GetNextEntry(void *cookie, char *name, size_t size) if (err < B_OK) return err; - strlcpy(name, c->entry.fName, MIN(size, FATFS_BASENAME_LENGTH)); - strlcpy(name, ".", size); - strlcpy(name, c->entry.fExt, MIN(size, FATFS_EXTNAME_LENGTH)); + strlcpy(name, c->Name(), size); return B_OK; } -status_t +status_t Directory::GetNextNode(void *cookie, Node **_node) { return B_ERROR; @@ -229,6 +421,7 @@ Directory::Rewind(void *cookie) TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); struct dir_cookie *c = (struct dir_cookie *)cookie; c->index = -1; + c->entryOffset = 0; return B_OK; } @@ -241,6 +434,7 @@ Directory::IsEmpty() struct dir_cookie cookie; struct dir_cookie *c = &cookie; c->index = -1; + c->entryOffset = 0; if (GetNextEntry(c) == B_OK) return false; return true; @@ -264,36 +458,222 @@ Directory::Inode() const return fStream.FirstCluster() << 16; } -status_t + +status_t +Directory::CreateFile(const char* name, mode_t permissions, Node** _node) +{ + if (Node* node = Lookup(name, false)) { + node->Release(); + return B_FILE_EXISTS; + } + + // We only support 8.3 file names ATM. + const char* baseName; + const char* extension; + uint32 baseNameLength; + uint32 extensionLength; + if (!check_valid_8_3_file_name(name, baseName, baseNameLength, extension, + extensionLength)) { + return B_UNSUPPORTED; + } + + // prepare a directory entry for the new file + dir_entry entry; + + memset(entry.fName, ' ', 11); + // clear both base name and extension + memcpy(entry.fName, baseName, baseNameLength); + if (extensionLength > 0) + memcpy(entry.fExt, extension, extensionLength); + + entry.fFlags = 0; + entry.fReserved1 = 0; + entry.fCreateTime10ms = 199; + entry.fCreateTime = B_HOST_TO_LENDIAN_INT16((23 << 11) | (59 << 5) | 29); + // 23:59:59.9 + entry.fCreateDate = B_HOST_TO_LENDIAN_INT16((127 << 9) | (12 << 5) | 31); + // 2107-12-31 + entry.fAccessDate = entry.fCreateDate; + entry.fClusterMSB = 0; + entry.fModifiedTime = entry.fCreateTime; + entry.fModifiedDate = entry.fCreateDate; + entry.fClusterLSB = 0; + entry.fSize = 0; + + // add the entry to the directory + off_t entryOffset; + status_t error = _AddEntry(entry, entryOffset); + if (error != B_OK) + return error; + + // create a File object + File* file = new(std::nothrow) File(fVolume, entryOffset, + entry.Cluster(fVolume.FatBits()), entry.Size(), name); + if (file == NULL) + return B_NO_MEMORY; + + *_node = file; + return B_OK; +} + + +/*static*/ status_t +Directory::UpdateDirEntry(Volume& volume, off_t dirEntryOffset, + uint32 firstCluster, uint32 size) +{ + if (dirEntryOffset == 0) + return B_BAD_VALUE; + + CachedBlock cachedBlock(volume); + off_t block = volume.ToBlock(dirEntryOffset); + + status_t error = cachedBlock.SetTo(block, CachedBlock::READ); + if (error != B_OK) + return error; + + dir_entry* entry = (dir_entry*)(cachedBlock.Block() + + dirEntryOffset % volume.BlockSize()); + + entry->SetCluster(firstCluster, volume.FatBits()); + entry->SetSize(size); + + return cachedBlock.Flush(); +} + + +status_t Directory::GetNextEntry(void *cookie, uint8 mask, uint8 match) { TRACE(("FASFS::Directory::%s(, %02x, %02x)\n", __FUNCTION__, mask, match)); struct dir_cookie *c = (struct dir_cookie *)cookie; + bool hasLongName = false; + bool longNameValid = false; + do { c->index++; size_t len = sizeof(c->entry); - if (fStream.ReadAt(c->Offset(), (uint8 *)&c->entry, &len) < B_OK) + if (fStream.ReadAt(c->Offset(), (uint8 *)&c->entry, &len, + &c->entryOffset) != B_OK || len != sizeof(c->entry)) { return B_ENTRY_NOT_FOUND; + } + TRACE(("FASFS::Directory::%s: got one entry\n", __FUNCTION__)); if ((uint8)c->entry.fName[0] == 0x00) // last one return B_ENTRY_NOT_FOUND; if ((uint8)c->entry.fName[0] == 0xe5) // deleted continue; - if (c->entry.Flags() == 0x0f) // LFN entry + if (c->entry.Flags() == 0x0f) { // LFN entry + uint8* nameEntry = (uint8*)&c->entry; + if ((*nameEntry & 0x40) != 0) { + c->ResetName(); + hasLongName = true; + longNameValid = true; + } + + uint16 nameChars[13]; + memcpy(nameChars, nameEntry + 0x01, 10); + memcpy(nameChars + 5, nameEntry + 0x0e, 12); + memcpy(nameChars + 11, nameEntry + 0x1c, 4); + longNameValid |= c->AddNameChars(nameChars, 13); continue; + } if ((c->entry.Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_VOLUME) { // TODO handle Volume name (set fVolume's name) continue; } - TRACE(("FASFS::Directory::%s: checking '%8.8s.%3.3s', %02x\n", __FUNCTION__, + TRACE(("FASFS::Directory::%s: checking '%8.8s.%3.3s', %02x\n", __FUNCTION__, c->entry.BaseName(), c->entry.Extension(), c->entry.Flags())); - if ((c->entry.Flags() & mask) == match) + if ((c->entry.Flags() & mask) == match) { + if (longNameValid) + longNameValid = c->ConvertNameToUTF8(); + if (!longNameValid) { + // copy 8.3 name to name buffer + c->Set8_3Name(c->entry.BaseName(), c->entry.Extension()); + } break; + } } while (true); TRACE(("FATFS::Directory::%s: '%8.8s.%3.3s'\n", __FUNCTION__, c->entry.BaseName(), c->entry.Extension())); return B_OK; } + +status_t +Directory::_AddEntry(dir_entry& entry, off_t& _entryOffset) +{ + off_t dirSize = _GetStreamSize(); + if (dirSize < 0) + return dirSize; + + uint32 firstCluster = fStream.FirstCluster(); + + // First null-terminate the new entry list, so we don't leave the list in + // a broken state, if writing the actual entry fails. We only need to do + // that when the entry is not at the end of a cluster. + if ((dirSize + sizeof(entry)) % fVolume.ClusterSize() != 0) { + // TODO: Rather zero the complete remainder of the cluster? + size_t size = 1; + char terminator = 0; + status_t error = fStream.WriteAt(dirSize + sizeof(entry), &terminator, + &size); + if (error != B_OK) + return error; + if (size != 1) + return B_ERROR; + } + + // write the entry + size_t size = sizeof(entry); + status_t error = fStream.WriteAt(dirSize, &entry, &size, &_entryOffset); + if (error != B_OK) + return error; + if (size != sizeof(entry)) + return B_ERROR; + // TODO: Undo changes! + + fStream.SetSize(dirSize + sizeof(entry)); + + // If the directory cluster has changed (which should only happen, if the + // directory was empty before), we need to adjust the directory entry. + if (firstCluster != fStream.FirstCluster()) { + error = UpdateDirEntry(fVolume, fDirEntryOffset, fStream.FirstCluster(), + 0); + if (error != B_OK) + return error; + // TODO: Undo changes! + } + + return B_OK; +} + + +off_t +Directory::_GetStreamSize() +{ + off_t size = fStream.Size(); + if (size != UINT32_MAX) + return size; + + // iterate to the end of the directory + size = 0; + while (true) { + dir_entry entry; + size_t entrySize = sizeof(entry); + status_t error = fStream.ReadAt(size, &entry, &entrySize); + if (error != B_OK) + return error; + + if (entrySize != sizeof(entry) || entry.fName[0] == 0) + break; + + size += sizeof(entry); + } + + fStream.SetSize(size); + return size; +} + + } // namespace FATFS diff --git a/src/system/boot/loader/file_systems/fat/Directory.h b/src/system/boot/loader/file_systems/fat/Directory.h index ea5fb3b87f..5bb10d560e 100644 --- a/src/system/boot/loader/file_systems/fat/Directory.h +++ b/src/system/boot/loader/file_systems/fat/Directory.h @@ -14,16 +14,20 @@ namespace FATFS { +struct dir_entry; class Volume; class Directory : public ::Directory { public: Directory(); - Directory(Volume &volume, uint32 cluster, const char *name); + Directory(Volume &volume, off_t dirEntryOffset, uint32 cluster, + const char *name); virtual ~Directory(); status_t InitCheck(); + off_t DirEntryOffset() const { return fDirEntryOffset; } + virtual status_t Open(void **_cookie, int mode); virtual status_t Close(void *cookie); @@ -37,11 +41,21 @@ class Directory : public ::Directory { virtual status_t GetName(char *name, size_t size) const; virtual ino_t Inode() const; + virtual status_t CreateFile(const char *name, mode_t permissions, + Node **_node); + + static status_t UpdateDirEntry(Volume& volume, off_t dirEntryOffset, + uint32 firstCluster, uint32 size); + private: - status_t GetNextEntry(void *cookie, + status_t GetNextEntry(void *cookie, uint8 mask = FAT_VOLUME, uint8 match = 0); + status_t _AddEntry(dir_entry& entry, off_t& _entryOffset); + off_t _GetStreamSize(); + Volume &fVolume; Stream fStream; + off_t fDirEntryOffset; typedef ::Directory _inherited; }; diff --git a/src/system/boot/loader/file_systems/fat/File.cpp b/src/system/boot/loader/file_systems/fat/File.cpp index 833be2dcd1..5d2c779ec1 100644 --- a/src/system/boot/loader/file_systems/fat/File.cpp +++ b/src/system/boot/loader/file_systems/fat/File.cpp @@ -6,11 +6,13 @@ #include "File.h" -#include - #include #include +#include + +#include "Directory.h" + //#define TRACE(x) dprintf x #define TRACE(x) do {} while (0) @@ -19,10 +21,12 @@ namespace FATFS { -File::File(Volume &volume, uint32 cluster, off_t size, const char *name) +File::File(Volume &volume, off_t dirEntryOffset, uint32 cluster, off_t size, + const char *name) : fVolume(volume), - fStream(volume, cluster, size, name) + fStream(volume, cluster, size, name), + fDirEntryOffset(dirEntryOffset) { TRACE(("FATFS::File::()\n")); } @@ -51,14 +55,14 @@ File::Open(void **_cookie, int mode) if (fStream.InitCheck() < B_OK) return fStream.InitCheck(); - return B_OK; + return Node::Open(_cookie, mode); } status_t File::Close(void *cookie) { - return B_OK; + return Node::Close(cookie); } @@ -67,7 +71,7 @@ File::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize) { TRACE(("FATFS::File::%s(, %Ld,, %d)\n", __FUNCTION__, pos, bufferSize)); status_t err; - err = fStream.ReadAt(pos, (uint8 *)buffer, &bufferSize); + err = fStream.ReadAt(pos, buffer, &bufferSize); if (err < B_OK) return err; return bufferSize; @@ -77,7 +81,25 @@ File::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize) ssize_t File::WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize) { - return EROFS; + off_t streamSize = fStream.Size(); + uint32 firstCluster = fStream.FirstCluster(); + + // write data + size_t written = bufferSize; + status_t error = fStream.WriteAt(pos, buffer, &written); + if (error != B_OK) + return error; + + // If the file size has changed, we need to adjust the directory entry. + if (fStream.Size() > streamSize || fStream.FirstCluster() != firstCluster) { + error = Directory::UpdateDirEntry(fVolume, fDirEntryOffset, + fStream.FirstCluster(), fStream.Size()); + if (error != B_OK) + return error; + // TODO: Undo the changes! + } + + return written; } @@ -95,14 +117,14 @@ File::GetFileMap(struct file_map_run *runs, int32 *count) } -int32 +int32 File::Type() const { return S_IFREG; } -off_t +off_t File::Size() const { return fStream.Size(); diff --git a/src/system/boot/loader/file_systems/fat/File.h b/src/system/boot/loader/file_systems/fat/File.h index fcc23e2167..8a0f6b628b 100644 --- a/src/system/boot/loader/file_systems/fat/File.h +++ b/src/system/boot/loader/file_systems/fat/File.h @@ -16,11 +16,14 @@ namespace FATFS { class File : public Node { public: - File(Volume &volume, uint32 cluster, off_t size, const char *name); + File(Volume &volume, off_t dirEntryOffset, uint32 cluster, off_t size, + const char *name); virtual ~File(); status_t InitCheck(); + off_t DirEntryOffset() const { return fDirEntryOffset; } + virtual status_t Open(void **_cookie, int mode); virtual status_t Close(void *cookie); @@ -37,6 +40,7 @@ class File : public Node { Volume &fVolume; //FileBlock fNode; Stream fStream; + off_t fDirEntryOffset; }; } // namespace FATFS diff --git a/src/system/boot/loader/file_systems/fat/Stream.cpp b/src/system/boot/loader/file_systems/fat/Stream.cpp index c6d19b4fd9..a8d8754e11 100644 --- a/src/system/boot/loader/file_systems/fat/Stream.cpp +++ b/src/system/boot/loader/file_systems/fat/Stream.cpp @@ -8,17 +8,20 @@ #include "Stream.h" -#include "CachedBlock.h" -#include "Directory.h" -#include "File.h" - -#include -#include #include #include -#include #include +#include + +#include + +#include +#include + +#include "CachedBlock.h" +#include "Directory.h" +#include "File.h" //#define TRACE(x) dprintf x #define TRACE(x) do {} while (0) @@ -104,11 +107,11 @@ Stream::GetFileMap(struct file_map_run *runs, int32 *count) status_t -Stream::FindBlock(off_t pos, off_t &block, off_t &offset) +Stream::_FindCluster(off_t pos, uint32& _cluster) { //TRACE(("FATFS::Stream::%s(%Ld,,)\n", __FUNCTION__, pos)); uint32 index = (uint32)(pos / fVolume.ClusterSize()); - if (pos >= fSize || index >= fClusterCount) + if (pos > fSize || index >= fClusterCount) return B_BAD_VALUE; uint32 cluster = 0; @@ -147,23 +150,94 @@ Stream::FindBlock(off_t pos, off_t &block, off_t &offset) fClusterMapCacheLast++; fClusterMapCacheLast %= CLUSTER_MAP_CACHE_SIZE; - //cluster = fClusters[index]; - // convert to position - offset = fVolume.ToOffset(cluster); - offset += (pos %= fVolume.ClusterSize()); - // convert to block + offset - block = fVolume.ToBlock(offset); - offset %= fVolume.BlockSize(); - //TRACE(("FATFS::Stream::FindBlock: %Ld:%Ld\n", block, offset)); + _cluster = cluster; return B_OK; } status_t -Stream::ReadAt(off_t pos, uint8 *buffer, size_t *_length) +Stream::_FindOrCreateCluster(off_t pos, uint32& _cluster, bool& _added) +{ + status_t error = _FindCluster(pos, _cluster); + if (error == B_OK) { + _added = false; + return B_OK; + } + + // iterate through the cluster list + uint32 index = (uint32)(pos / fVolume.ClusterSize()); + uint32 cluster = fFirstCluster; + uint32 clusterCount = 0; + if (cluster != 0) { + uint32 nextCluster = cluster; + while (clusterCount <= index) { + if (!fVolume.IsValidCluster(nextCluster) + || fVolume.IsLastCluster(nextCluster)) { + break; + } + + cluster = nextCluster; + clusterCount++; + nextCluster = fVolume.NextCluster(nextCluster); + } + } + + if (clusterCount > index) { + // the cluster existed after all + _cluster = cluster; + _added = false; + return B_OK; + } + + while (clusterCount <= index) { + uint32 newCluster; + error = fVolume.AllocateCluster(cluster, newCluster); + if (error != B_OK) + return error; + + if (clusterCount == 0) + fFirstCluster = newCluster; + + // TODO: We should support to zero out the new cluster. Maybe make this + // and optional parameter of WriteAt(). + + cluster = newCluster; + clusterCount++; + } + + _cluster = cluster; + _added = true; + return B_OK; +} + + +status_t +Stream::FindBlock(off_t pos, off_t &block, off_t &offset) +{ + uint32 cluster; + status_t error = _FindCluster(pos, cluster); + if (error != B_OK) + return error; + + // convert to position + offset = fVolume.ClusterToOffset(cluster); + offset += (pos %= fVolume.ClusterSize()); + + // convert to block + offset + block = fVolume.ToBlock(offset); + offset %= fVolume.BlockSize(); + + return B_OK; +} + + +status_t +Stream::ReadAt(off_t pos, void *_buffer, size_t *_length, off_t *diskOffset) { TRACE(("FATFS::Stream::%s(%Ld, )\n", __FUNCTION__, pos)); + uint8* buffer = (uint8*)_buffer; + // set/check boundaries for pos/length if (pos < 0) return B_BAD_VALUE; @@ -193,6 +267,9 @@ Stream::ReadAt(off_t pos, uint8 *buffer, size_t *_length) return B_BAD_VALUE; } + if (diskOffset != NULL) + *diskOffset = fVolume.BlockToOffset(num) + offset; + uint32 bytesRead = 0; uint32 blockSize = fVolume.BlockSize(); uint8 *block; @@ -248,7 +325,7 @@ Stream::ReadAt(off_t pos, uint8 *buffer, size_t *_length) break; } - if (read_pos(fVolume.Device(), fVolume.ToOffset(num), + if (read_pos(fVolume.Device(), fVolume.BlockToOffset(num), buffer + bytesRead, fVolume.BlockSize()) < B_OK) { *_length = bytesRead; return B_BAD_VALUE; @@ -273,6 +350,82 @@ Stream::ReadAt(off_t pos, uint8 *buffer, size_t *_length) } +status_t +Stream::WriteAt(off_t pos, const void* _buffer, size_t* _length, + off_t* diskOffset) +{ + if (pos < 0) + return B_BAD_VALUE; + + const uint8* buffer = (const uint8*)_buffer; + size_t length = *_length; + size_t totalWritten = 0; + status_t error = B_OK; + + CachedBlock cachedBlock(fVolume); + + while (length > 0) { + // get the cluster + uint32 cluster; + bool added; + error = _FindOrCreateCluster(pos, cluster, added); + if (error != B_OK) + break; + + // convert to position + off_t inClusterOffset = pos % fVolume.ClusterSize(); + off_t offset = fVolume.ClusterToOffset(cluster) + inClusterOffset; + + if (diskOffset != NULL) { + *diskOffset = offset; + diskOffset = NULL; + } + + // convert to block + offset + off_t block = fVolume.ToBlock(offset); + size_t inBlockOffset = offset % fVolume.BlockSize(); + + // write + size_t toWrite = std::min(fVolume.BlockSize() - inBlockOffset, length); + if (toWrite == (size_t)fVolume.BlockSize()) { + // write the whole block + ssize_t written = write_pos(fVolume.Device(), + fVolume.BlockToOffset(block), buffer, fVolume.BlockSize()); + if (written < 0) { + error = written; + break; + } + if (written != fVolume.BlockSize()) { + error = B_ERROR; + break; + } + } else { + // write a partial block -- need to read it from disk first + error = cachedBlock.SetTo(block, CachedBlock::READ); + if (error != B_OK) + break; + + memcpy(cachedBlock.Block() + inBlockOffset, buffer, toWrite); + + error = cachedBlock.Flush(); + if (error != B_OK) + break; + } + + totalWritten += toWrite; + pos += toWrite; + buffer += toWrite; + length -= toWrite; + + if (pos > fSize) + fSize = pos; + } + + *_length = totalWritten; + return totalWritten > 0 ? B_OK : error; +} + + status_t Stream::BuildClusterList() { diff --git a/src/system/boot/loader/file_systems/fat/Stream.h b/src/system/boot/loader/file_systems/fat/Stream.h index 73a504e8c6..9c47986cbb 100644 --- a/src/system/boot/loader/file_systems/fat/Stream.h +++ b/src/system/boot/loader/file_systems/fat/Stream.h @@ -34,11 +34,20 @@ class Stream { off_t Size() const { return fSize; } uint32 FirstCluster() const { return fFirstCluster; } - status_t ReadAt(off_t pos, uint8 *buffer, size_t *length); + void SetSize(off_t size) { fSize = size; } + + status_t ReadAt(off_t pos, void *buffer, size_t *length, + off_t *diskOffset = NULL); + status_t WriteAt(off_t pos, const void *buffer, size_t *length, + off_t *diskOffset = NULL); private: status_t BuildClusterList(); + status_t _FindCluster(off_t pos, uint32& _cluster); + status_t _FindOrCreateCluster(off_t pos, uint32& _cluster, + bool& _added); status_t FindBlock(off_t pos, off_t &block, off_t &offset); + Volume &fVolume; uint32 fFirstCluster; uint32 fClusterCount; diff --git a/src/system/boot/loader/file_systems/fat/Volume.cpp b/src/system/boot/loader/file_systems/fat/Volume.cpp index 83190f9590..83ea095641 100644 --- a/src/system/boot/loader/file_systems/fat/Volume.cpp +++ b/src/system/boot/loader/file_systems/fat/Volume.cpp @@ -65,10 +65,10 @@ Volume::Volume(boot::Partition *partition) // check the signature if (((buf[0x1fe] != 0x55) || (buf[0x1ff] != 0xaa)) && (buf[0x15] == 0xf8)) goto err1; - + if (!memcmp(buf+3, "NTFS ", 8) || !memcmp(buf+3, "HPFS ", 8)) goto err1; - + TRACE(("%s: signature ok\n", __FUNCTION__)); fBytesPerSector = read16(buf,0xb); switch (fBytesPerSector) { @@ -85,7 +85,7 @@ Volume::Volume(boot::Partition *partition) goto err1; } TRACE(("%s: block shift %d\n", __FUNCTION__, fBlockShift)); - + fSectorsPerCluster = buf[0xd]; switch (fSectorsPerCluster) { case 1: case 2: case 4: case 8: @@ -101,12 +101,12 @@ Volume::Volume(boot::Partition *partition) fClusterShift += 1; } TRACE(("%s: cluster shift %d\n", __FUNCTION__, fClusterShift)); - + fReservedSectors = read16(buf,0xe); fFatCount = buf[0x10]; if ((fFatCount == 0) || (fFatCount > 8)) goto err1; - + fMediaDesc = buf[0x15]; if ((fMediaDesc != 0xf0) && (fMediaDesc < 0xf8)) goto err1; @@ -124,32 +124,36 @@ Volume::Volume(boot::Partition *partition) fRootDirCluster = read32(buf,0x2c); if (fRootDirCluster >= fTotalClusters) goto err1; + + fFSInfoSector = read16(buf, 0x30); + if (fFSInfoSector < 1 || fFSInfoSector > fTotalSectors) + goto err1; } else { // FAT12/16 // XXX:FIXME fFatBits = 16; goto err1; } - TRACE(("%s: block size %d, sector size %d, sectors/cluster %d\n", __FUNCTION__, + TRACE(("%s: block size %d, sector size %d, sectors/cluster %d\n", __FUNCTION__, fBlockSize, fBytesPerSector, fSectorsPerCluster)); - TRACE(("%s: block shift %d, sector shift %d, cluster shift %d\n", __FUNCTION__, + TRACE(("%s: block shift %d, sector shift %d, cluster shift %d\n", __FUNCTION__, fBlockShift, fSectorShift, fClusterShift)); - TRACE(("%s: reserved %d, max root entries %d, media %02x, sectors/fat %d\n", __FUNCTION__, + TRACE(("%s: reserved %d, max root entries %d, media %02x, sectors/fat %d\n", __FUNCTION__, fReservedSectors, fMaxRootEntries, fMediaDesc, fSectorsPerFat)); - - + + //if (fTotalSectors > partition->sectors_per_track * partition->cylinder_count * partition->head_count) if ((off_t)fTotalSectors * fBytesPerSector > partition->size) goto err1; - - TRACE(("%s: found fat%d filesystem, root dir at cluster %d\n", __FUNCTION__, + + TRACE(("%s: found fat%d filesystem, root dir at cluster %d\n", __FUNCTION__, fFatBits, fRootDirCluster)); - fRoot = new Directory(*this, fRootDirCluster, "/"); + fRoot = new Directory(*this, 0, fRootDirCluster, "/"); return; - + err1: - dprintf("fatfs: cannot mount (bad superblock ?)\n"); + TRACE("fatfs: cannot mount (bad superblock ?)\n"); // XXX !? this triple-faults in QEMU .. //delete fCachedBlock; } @@ -163,7 +167,7 @@ Volume::~Volume() } -status_t +status_t Volume::InitCheck() { if (fCachedBlock == NULL) @@ -175,7 +179,7 @@ Volume::InitCheck() } -status_t +status_t Volume::GetName(char *name, size_t size) const { //TODO: WRITEME @@ -184,12 +188,12 @@ Volume::GetName(char *name, size_t size) const off_t -Volume::ToOffset(uint32 cluster) const +Volume::ClusterToOffset(uint32 cluster) const { return (fDataStart << SectorShift()) + ((cluster - 2) << ClusterShift()); } -uint32 +uint32 Volume::NextCluster(uint32 cluster, uint32 skip) { //TRACE(("%s(%d, %d)\n", __FUNCTION__, cluster, skip)); @@ -221,7 +225,7 @@ again: return InvalidClusterID(); offset %= BlockSize(); - + switch (fFatBits) { case 32: next = read32(buf, offset); @@ -241,7 +245,7 @@ again: } -bool +bool Volume::IsValidCluster(uint32 cluster) const { if (cluster > 1 && cluster < fTotalClusters) @@ -255,10 +259,141 @@ Volume::IsLastCluster(uint32 cluster) const { if (cluster >= fTotalClusters && ((cluster & 0xff8) == 0xff8)) return true; - return false; + return false; } +/*! Allocates a free cluster. + If \a previousCluster is a valid cluster idnex, its chain pointer is + changed to point to the newly allocated cluster. +*/ +status_t +Volume::AllocateCluster(uint32 previousCluster, uint32& _newCluster) +{ + if (fFatBits != 32) + return B_UNSUPPORTED; + // TODO: Support FAT16 and FAT12. + + const int fatBytes = FatBits() / 8; + const uint32 blockOffsetMask = (uint32)BlockSize() - 1; + + // Iterate through the FAT to find a free cluster. + off_t offset = fBytesPerSector * fReservedSectors; + offset += 2 * fatBytes; + for (uint32 i = 2; i < fTotalClusters; i++, offset += fatBytes) { + uint8* buffer = fCachedBlock->SetTo(ToBlock(offset)); + if (buffer == NULL) + return B_ERROR; + + uint32 value = read32(buffer, offset & blockOffsetMask); + if (value == 0) { + // found one -- mark it used (end of file) + status_t error = _UpdateCluster(i, 0x0ffffff8); + if (error != B_OK) + return error; + + // If a previous cluster was given, update its list link. + if (IsValidCluster(previousCluster)) { + error = _UpdateCluster(previousCluster, i); + if (error != B_OK) { + _UpdateCluster(i, 0); + return error; + } + } + + _ClusterAllocated(i); + + _newCluster = i; + return B_OK; + } + } + + return B_DEVICE_FULL; +} + + +status_t +Volume::_UpdateCluster(uint32 cluster, uint32 value) +{ + if (fFatBits != 32 && fFatBits != 16) + return B_UNSUPPORTED; + // TODO: Support FAT12. + + if (!IsValidCluster(cluster)) + return InvalidClusterID(); + + // get the buffer we need to change + const int fatBytes = FatBits() / 8; + + for (uint8 i = 0; i < fFatCount; i++) { + off_t offset + = fBytesPerSector * (fReservedSectors + i * fSectorsPerFat); + offset += cluster * fatBytes; + + uint8* buffer = fCachedBlock->SetTo(ToBlock(offset)); + if (buffer == NULL) + return InvalidClusterID(); + + offset %= BlockSize(); + + // set the value + switch (fFatBits) { + case 32: + *(uint32*)(buffer + offset) = B_HOST_TO_LENDIAN_INT32(value); + break; + case 16: + *(uint16*)(buffer + offset) = B_HOST_TO_LENDIAN_INT16(value); + break; + default: + return InvalidClusterID(); + } + + // write the block back to disk + status_t error = fCachedBlock->Flush(); + if (error != B_OK) { + fCachedBlock->Unset(); + return error; + } + } + + return B_OK; +} + + +status_t +Volume::_ClusterAllocated(uint32 cluster) +{ + // update the FS info + + // exists only for FAT32 + if (fFatBits != 32) + return B_OK; + + off_t offset = fBytesPerSector * fFSInfoSector; + + status_t error = fCachedBlock->SetTo(offset / BlockSize(), + CachedBlock::READ); + if (error != B_OK) + return error; + + uint8* buffer = fCachedBlock->Block() + offset % BlockSize(); + + // update number of free cluster + int32 freeClusters = read32(buffer, 0x1e8); + if (freeClusters != -1) + write32(buffer, 0x1e8, freeClusters - 1); + + // update number of most recently allocated cluster + write32(buffer, 0x1ec, cluster); + + // write the block back + error = fCachedBlock->Flush(); + if (error != B_OK) + fCachedBlock->Unset(); + + return error; +} + // #pragma mark - diff --git a/src/system/boot/loader/file_systems/fat/Volume.h b/src/system/boot/loader/file_systems/fat/Volume.h index 1e5f753249..3ad06ff044 100644 --- a/src/system/boot/loader/file_systems/fat/Volume.h +++ b/src/system/boot/loader/file_systems/fat/Volume.h @@ -43,17 +43,24 @@ class Volume { int32 NumBlocks() const { return (int32)((off_t)fTotalSectors * fBytesPerSector / fBlockSize); } int32 NumSectors() const { return fTotalSectors; } int32 NumClusters() const { return fTotalClusters; } - + uint32 NextCluster(uint32 cluster, uint32 skip=0); bool IsValidCluster(uint32 cluster) const; bool IsLastCluster(uint32 cluster) const; uint32 InvalidClusterID() const { return (1 << fFatBits) - 1; } - off_t ToOffset(uint32 cluster) const; + status_t AllocateCluster(uint32 previousCluster, + uint32& _newCluster); + + off_t ClusterToOffset(uint32 cluster) const; // uint32 ToCluster(off_t offset) const { return offset >> ClusterShift(); } - off_t ToOffset(off_t block) const { return block << BlockShift(); } + off_t BlockToOffset(off_t block) const + { return block << BlockShift(); } uint32 ToBlock(off_t offset) const { return offset >> BlockShift(); } + private: + status_t _UpdateCluster(uint32 cluster, uint32 value); + status_t _ClusterAllocated(uint32 cluster); protected: int fDevice; @@ -75,7 +82,8 @@ class Volume { uint32 fDataStart; uint32 fTotalClusters; uint32 fRootDirCluster; - + uint16 fFSInfoSector; + CachedBlock *fCachedBlock; Directory *fRoot; }; diff --git a/src/system/boot/loader/file_systems/fat/fatfs.h b/src/system/boot/loader/file_systems/fat/fatfs.h index 26aef2984b..5863a9cb96 100644 --- a/src/system/boot/loader/file_systems/fat/fatfs.h +++ b/src/system/boot/loader/file_systems/fat/fatfs.h @@ -27,6 +27,12 @@ class Volume; #define read16(buffer,off) \ B_LENDIAN_TO_HOST_INT16(*(uint16 *)&buffer[off]) +#define write32(buffer, off, value) \ + *(uint32*)&buffer[off] = B_HOST_TO_LENDIAN_INT32(value) + +#define write16(buffer, off, value) \ + *(uint16*)&buffer[off] = B_HOST_TO_LENDIAN_INT16(value) + enum name_lengths { FATFS_BASENAME_LENGTH = 8, FATFS_EXTNAME_LENGTH = 3, diff --git a/src/system/boot/loader/menu.cpp b/src/system/boot/loader/menu.cpp index c26a4001a5..94c73e94aa 100644 --- a/src/system/boot/loader/menu.cpp +++ b/src/system/boot/loader/menu.cpp @@ -6,6 +6,7 @@ #include "menu.h" +#include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -359,6 +361,57 @@ Menu::Run() // #pragma mark - +static const char* +size_to_string(off_t size, char* buffer, size_t bufferSize) +{ + static const char* const kPrefixes[] = { "K", "M", "G", "T", "P", NULL }; + int32 nextIndex = 0; + int32 remainder = 0; + while (size >= 1024 && kPrefixes[nextIndex] != NULL) { + remainder = size % 1024; + size /= 1024; + nextIndex++; + + if (size < 1024) { + // Compute the decimal remainder and make sure we have at most + // 3 decimal places (or 4 for 1000 <= size <= 1023). + int32 factor; + if (size >= 100) + factor = 100; + else if (size >= 10) + factor = 10; + else + factor = 1; + + remainder = (remainder * 1000 + 5 * factor) / 1024; + + if (remainder >= 1000) { + size++; + remainder = 0; + } else + remainder /= 10 * factor; + } else + size += (remainder + 512) / 1024; + } + + if (remainder == 0) { + snprintf(buffer, bufferSize, "%" B_PRIdOFF, size); + } else { + snprintf(buffer, bufferSize, "%" B_PRIdOFF ".%" B_PRId32, size, + remainder); + } + + size_t length = strlen(buffer); + snprintf(buffer + length, bufferSize - length, " %sB", + nextIndex == 0 ? "" : kPrefixes[nextIndex - 1]); + + return buffer; +} + + +// #pragma mark - + + static bool user_menu_boot_volume(Menu *menu, MenuItem *item) { @@ -412,6 +465,58 @@ debug_menu_display_syslog(Menu *menu, MenuItem *item) } +static status_t +save_syslog_to_volume(Directory* directory) +{ + // find an unused name + char name[16]; + bool found = false; + for (int i = 0; i < 99; i++) { + snprintf(name, sizeof(name), "SYSLOG%02d.TXT", i); + Node* node = directory->Lookup(name, false); + if (node == NULL) { + found = true; + break; + } + + node->Release(); + } + + if (!found) { + printf("Failed to find an unused name for the syslog file!\n"); + return B_ERROR; + } + + printf("Writing syslog to file \"%s\" ...\n", name); + + int fd = open_from(directory, name, O_RDWR | O_CREAT | O_EXCL, 0644); + if (fd < 0) { + printf("Failed to create syslog file!\n"); + return fd; + } + + ring_buffer* syslogBuffer = (ring_buffer*)gKernelArgs.debug_output; + iovec vecs[2]; + int32 vecCount = ring_buffer_get_vecs(syslogBuffer, vecs); + if (vecCount > 0) { + size_t toWrite = ring_buffer_readable(syslogBuffer); + + ssize_t written = writev(fd, vecs, vecCount); + if (written < 0 || (size_t)written != toWrite) { + printf("Failed to write to the syslog file \"%s\"!\n", name); + close(fd); + return errno; + } + } + + close(fd); + + printf("Successfully wrote syslog file.\n"); + + return B_OK; +} + + static bool debug_menu_toggle_debug_syslog(Menu *menu, MenuItem *item) { @@ -420,6 +525,22 @@ debug_menu_toggle_debug_syslog(Menu *menu, MenuItem *item) } +static bool +debug_menu_save_syslog(Menu *menu, MenuItem *item) +{ + Directory* volume = (Directory*)item->Data(); + + console_clear_screen(); + + save_syslog_to_volume(volume); + + printf("\nPress any key to continue\n"); + console_wait_for_key(); + + return true; +} + + static Menu * add_boot_volume_menu(Directory *bootVolume) { @@ -511,10 +632,80 @@ add_safe_mode_menu() } +static Menu* +add_save_debug_syslog_menu() +{ + Menu* menu = new(nothrow) Menu(STANDARD_MENU, "Save syslog to volume ..."); + MenuItem* item; + + const char* const kHelpText = "Currently only FAT32 volumes are supported. " + "Newly plugged in removable devices are only recognized after " + "rebooting."; + + int32 itemsAdded = 0; + + void* cookie; + if (gRoot->Open(&cookie, O_RDONLY) == B_OK) { + Node* node; + while (gRoot->GetNextNode(cookie, &node) == B_OK) { + Directory* volume = static_cast(node); + Partition* partition; + if (gRoot->GetPartitionFor(volume, &partition) != B_OK) + continue; + + // we support only FAT32 volumes ATM + if (partition->content_type == NULL + || strcmp(partition->content_type, kPartitionTypeFAT32) != 0) { + continue; + } + + char name[B_FILE_NAME_LENGTH]; + if (volume->GetName(name, sizeof(name)) != B_OK) + strcpy(name, "unnamed"); + + // append offset, size, and type to the name + size_t len = strlen(name); + char offsetBuffer[32]; + char sizeBuffer[32]; + snprintf(name + len, sizeof(name) - len, + " (%s, offset %s, size %s)", partition->content_type, + size_to_string(partition->offset, offsetBuffer, + sizeof(offsetBuffer)), + size_to_string(partition->size, sizeBuffer, + sizeof(sizeBuffer))); + + item = new(nothrow) MenuItem(name); + item->SetData(volume); + item->SetTarget(&debug_menu_save_syslog); + item->SetType(MENU_ITEM_NO_CHOICE); + item->SetHelpText(kHelpText); + menu->AddItem(item); + itemsAdded++; + } + + gRoot->Close(cookie); + } + + if (itemsAdded == 0) { + menu->AddItem(item + = new(nothrow) MenuItem("No supported volumes found")); + item->SetType(MENU_ITEM_NO_CHOICE); + item->SetHelpText(kHelpText); + item->SetEnabled(false); + } + + menu->AddSeparatorItem(); + menu->AddItem(item = new(nothrow) MenuItem("Return to debug menu")); + item->SetHelpText(kHelpText); + + return menu; +} + + static Menu * add_debug_menu() { - Menu *menu = new(nothrow) Menu(SAFE_MODE_MENU, "Debug Options"); + Menu *menu = new(nothrow) Menu(STANDARD_MENU, "Debug Options"); MenuItem *item; #if DEBUG_SPINLOCK_LATENCIES @@ -558,6 +749,11 @@ add_debug_menu() item->SetType(MENU_ITEM_NO_CHOICE); item->SetHelpText( "Displays the syslog from the previous Haiku session."); + + menu->AddItem(item = new(nothrow) MenuItem( + "Save syslog from previous session", add_save_debug_syslog_menu())); + item->SetHelpText("Saves the syslog from the previous Haiku session to " + "disk. Currently only FAT32 volumes are supported."); } menu->AddSeparatorItem(); @@ -625,10 +821,9 @@ user_menu(Directory **_bootVolume) platform_add_menus(menu); menu->AddSeparatorItem(); - if (*_bootVolume == NULL) { - menu->AddItem(item = new(nothrow) MenuItem("Reboot")); - item->SetTarget(user_menu_reboot); - } + + menu->AddItem(item = new(nothrow) MenuItem("Reboot")); + item->SetTarget(user_menu_reboot); menu->AddItem(item = new(nothrow) MenuItem("Continue booting")); if (*_bootVolume == NULL) { diff --git a/src/system/boot/loader/partitions.cpp b/src/system/boot/loader/partitions.cpp index 634dc067fe..fd74561b1f 100644 --- a/src/system/boot/loader/partitions.cpp +++ b/src/system/boot/loader/partitions.cpp @@ -4,19 +4,22 @@ */ -#include "RootFileSystem.h" +#include + +#include +#include +#include #include -#include -#include #include #include #include +#include #include #include -#include -#include +#include "RootFileSystem.h" + using namespace boot; @@ -161,7 +164,8 @@ Partition::ReadAt(void *cookie, off_t position, void *buffer, size_t bufferSize) if (position + bufferSize > this->size) bufferSize = this->size - position; - return read_pos(fFD, this->offset + position, buffer, bufferSize); + ssize_t result = read_pos(fFD, this->offset + position, buffer, bufferSize); + return result < 0 ? errno : result; } @@ -177,7 +181,9 @@ Partition::WriteAt(void *cookie, off_t position, const void *buffer, if (position + bufferSize > this->size) bufferSize = this->size - position; - return write_pos(fFD, this->offset + position, buffer, bufferSize); + ssize_t result = write_pos(fFD, this->offset + position, buffer, + bufferSize); + return result < 0 ? errno : result; } @@ -231,8 +237,9 @@ Partition::_Mount(file_system_module_info *module, Directory **_fileSystem) if (_fileSystem) *_fileSystem = fileSystem; - // remember the module name that mounted us + // remember the module that mounted us fModuleName = module->module_name; + this->content_type = module->pretty_name; fIsFileSystem = true; @@ -386,7 +393,7 @@ Partition::Scan(bool mountFileSystems, bool isBootDevice) delete child; } - // remember the module name that identified us + // remember the name of the module that identified us fModuleName = bestModule->module.name; return B_OK; diff --git a/src/system/boot/loader/stdio.cpp b/src/system/boot/loader/stdio.cpp index c6d8efbc46..ca430ea730 100644 --- a/src/system/boot/loader/stdio.cpp +++ b/src/system/boot/loader/stdio.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -19,8 +20,16 @@ //extern FILE *stdout; //extern FILE *stdin; + +#undef errno int errno; -int *_errnop = &errno; + + +int* +_errnop(void) +{ + return &errno; +} int diff --git a/src/system/boot/loader/vfs.cpp b/src/system/boot/loader/vfs.cpp index bdef06bc35..2d532309c0 100644 --- a/src/system/boot/loader/vfs.cpp +++ b/src/system/boot/loader/vfs.cpp @@ -4,19 +4,25 @@ */ -#include "RootFileSystem.h" +#include + +#include +#include +#include +#include +#include #include -#include + #include #include #include #include #include +#include + +#include "RootFileSystem.h" -#include -#include -#include using namespace boot; @@ -92,7 +98,7 @@ Node::Close(void *cookie) } -status_t +status_t Node::GetName(char *nameBuffer, size_t bufferSize) const { return B_ERROR; @@ -106,14 +112,14 @@ Node::GetFileMap(struct file_map_run *runs, int32 *count) } -int32 +int32 Node::Type() const { return 0; } -off_t +off_t Node::Size() const { return 0LL; @@ -127,7 +133,7 @@ Node::Inode() const } -status_t +status_t Node::Acquire() { fRefCount++; @@ -136,7 +142,7 @@ Node::Acquire() } -status_t +status_t Node::Release() { TRACE(("%p::Release(), fRefCount = %ld\n", this, fRefCount)); @@ -182,27 +188,34 @@ Directory::Directory() } -ssize_t +ssize_t Directory::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize) { return B_ERROR; } -ssize_t +ssize_t Directory::WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize) { return B_ERROR; } -int32 +int32 Directory::Type() const { return S_IFDIR; } +status_t +Directory::CreateFile(const char *name, mode_t permissions, Node **_node) +{ + return EROFS; +} + + // #pragma mark - @@ -273,7 +286,7 @@ Descriptor::~Descriptor() } -ssize_t +ssize_t Descriptor::Read(void *buffer, size_t bufferSize) { ssize_t bytesRead = fNode->ReadAt(fCookie, fOffset, buffer, bufferSize); @@ -284,7 +297,7 @@ Descriptor::Read(void *buffer, size_t bufferSize) } -ssize_t +ssize_t Descriptor::ReadAt(off_t pos, void *buffer, size_t bufferSize) { return fNode->ReadAt(fCookie, pos, buffer, bufferSize); @@ -359,7 +372,7 @@ status_t register_boot_file_system(Directory *volume) { gRoot->AddLink("boot", volume); - + Partition *partition; status_t status = gRoot->GetPartitionFor(volume, &partition); if (status != B_OK) { @@ -497,17 +510,12 @@ mount_file_systems(stage2_args *args) } +/*! Resolves \a directory + \a path to a node. + Note that \a path will be modified by the function. +*/ static status_t -get_node_for_path(Directory *directory, const char *pathName, Node **_node) +get_node_for_path(Directory *directory, char *path, Node **_node) { - char pathBuffer[B_PATH_NAME_LENGTH]; - char *path = pathBuffer; - - if (pathName == NULL) - return B_BAD_VALUE; - - strlcpy(path, pathName, sizeof(pathBuffer)); - directory->Acquire(); // balance Acquire()/Release() calls @@ -584,7 +592,7 @@ open_node(Node *node, int mode) return B_ERROR; // get free descriptor - + int fd = 0; for (; fd < MAX_VFS_DESCRIPTORS; fd++) { if (sDescriptors[fd] == NULL) @@ -596,7 +604,7 @@ open_node(Node *node, int mode) TRACE(("got descriptor %d for node %p\n", fd, node)); // we got a free descriptor entry, now try to open the node - + void *cookie; status_t status = node->Open(&cookie, mode); if (status < B_OK) @@ -619,10 +627,10 @@ dup(int fd) { Descriptor *descriptor = get_descriptor(fd); if (descriptor == NULL) - return B_FILE_ERROR; + RETURN_AND_SET_ERRNO(B_FILE_ERROR); descriptor->Acquire(); - return fd; + RETURN_AND_SET_ERRNO(fd); } @@ -631,9 +639,9 @@ read_pos(int fd, off_t offset, void *buffer, size_t bufferSize) { Descriptor *descriptor = get_descriptor(fd); if (descriptor == NULL) - return B_FILE_ERROR; + RETURN_AND_SET_ERRNO(B_FILE_ERROR); - return descriptor->ReadAt(offset, buffer, bufferSize); + RETURN_AND_SET_ERRNO(descriptor->ReadAt(offset, buffer, bufferSize)); } @@ -642,9 +650,9 @@ read(int fd, void *buffer, size_t bufferSize) { Descriptor *descriptor = get_descriptor(fd); if (descriptor == NULL) - return B_FILE_ERROR; + RETURN_AND_SET_ERRNO(B_FILE_ERROR); - return descriptor->Read(buffer, bufferSize); + RETURN_AND_SET_ERRNO(descriptor->Read(buffer, bufferSize)); } @@ -653,9 +661,9 @@ write_pos(int fd, off_t offset, const void *buffer, size_t bufferSize) { Descriptor *descriptor = get_descriptor(fd); if (descriptor == NULL) - return B_FILE_ERROR; + RETURN_AND_SET_ERRNO(B_FILE_ERROR); - return descriptor->WriteAt(offset, buffer, bufferSize); + RETURN_AND_SET_ERRNO(descriptor->WriteAt(offset, buffer, bufferSize)); } @@ -664,32 +672,51 @@ write(int fd, const void *buffer, size_t bufferSize) { Descriptor *descriptor = get_descriptor(fd); if (descriptor == NULL) - return B_FILE_ERROR; + RETURN_AND_SET_ERRNO(B_FILE_ERROR); - return descriptor->Write(buffer, bufferSize); + RETURN_AND_SET_ERRNO(descriptor->Write(buffer, bufferSize)); +} + + +ssize_t +writev(int fd, const struct iovec* vecs, size_t count) +{ + size_t totalWritten = 0; + + for (size_t i = 0; i < count; i++) { + ssize_t written = write(fd, vecs[i].iov_base, vecs[i].iov_len); + if (written < 0) + return totalWritten == 0 ? written : totalWritten; + + totalWritten += written; + + if ((size_t)written != vecs[i].iov_len) + break; + } + + return totalWritten; } int open(const char *name, int mode, ...) { + mode_t permissions = 0; + if ((mode & O_CREAT) != 0) { + va_list args; + va_start(args, mode); + permissions = va_arg(args, int) /*& ~__gUmask*/; + // adapt the permissions as required by POSIX + va_end(args); + } + // we always start at the top (there is no notion of a current directory (yet?)) - if (name[0] == '/') - name++; - - Node *node; - if (get_node_for_path(gRoot, name, &node) < B_OK) - return B_ENTRY_NOT_FOUND; - - int fd = open_node(node, mode); - - node->Release(); - return fd; + RETURN_AND_SET_ERRNO(open_from(gRoot, name, mode, permissions)); } int -open_from(Directory *directory, const char *name, int mode) +open_from(Directory *directory, const char *name, int mode, mode_t permissions) { if (name[0] == '/') { // ignore the directory and start from root if we are asked to do that @@ -697,9 +724,51 @@ open_from(Directory *directory, const char *name, int mode) name++; } + char path[B_PATH_NAME_LENGTH]; + if (strlcpy(path, name, sizeof(path)) >= sizeof(path)) + return B_NAME_TOO_LONG; + Node *node; - if (get_node_for_path(directory, name, &node) < B_OK) - return B_ENTRY_NOT_FOUND; + status_t error = get_node_for_path(directory, path, &node); + if (error != B_OK) { + if (error != B_ENTRY_NOT_FOUND) + return error; + + if ((mode & O_CREAT) == 0) + return B_ENTRY_NOT_FOUND; + + // try to resolve the parent directory + strlcpy(path, name, sizeof(path)); + if (char* lastSlash = strrchr(path, '/')) { + if (lastSlash[1] == '\0') + return B_ENTRY_NOT_FOUND; + + lastSlash = '\0'; + name = lastSlash + 1; + + // resolve the directory + if (get_node_for_path(directory, path, &node) != B_OK) + return B_ENTRY_NOT_FOUND; + + if (node->Type() != S_IFDIR) { + node->Release(); + return B_NOT_A_DIRECTORY; + } + + directory = static_cast(node); + } else + directory->Acquire(); + + // create the file + error = directory->CreateFile(name, permissions, &node); + directory->Release(); + + if (error != B_OK) + return error; + } else if ((mode & O_EXCL) != 0) { + node->Release(); + return B_FILE_EXISTS; + } int fd = open_node(node, mode); @@ -729,13 +798,13 @@ close(int fd) { Descriptor *descriptor = get_descriptor(fd); if (descriptor == NULL) - return B_FILE_ERROR; + RETURN_AND_SET_ERRNO(B_FILE_ERROR); status_t status = descriptor->Release(); if (!descriptor->RefCount()) free_descriptor(fd); - return status; + RETURN_AND_SET_ERRNO(status); } @@ -748,11 +817,11 @@ fstat(int fd, struct stat *stat) #endif { if (stat == NULL) - return B_BAD_VALUE; + RETURN_AND_SET_ERRNO(B_BAD_VALUE); Descriptor *descriptor = get_descriptor(fd); if (descriptor == NULL) - return B_FILE_ERROR; + RETURN_AND_SET_ERRNO(B_FILE_ERROR); - return descriptor->Stat(*stat); + RETURN_AND_SET_ERRNO(descriptor->Stat(*stat)); }