Extend NvServices and implement IDirectory (#107)

* Fix alignment handling in NvHostAsGpu::AllocSpace

* Implement Ioctl{2,3} ioctls

These were added in HOS 3.0.0 in order to ease handling ioctl buffers.

* Introduce support for GPU address space remapping

* Fix nvdrv and am service bugs

Syncpoints are supposed to be allocated from ID 1, they were allocated
at 0 before. The ioctl functions were also missing from the service map

* Fix friend:u service name

* Stub NVGPU_IOCTL_CHANNEL_SET_TIMESLICE

* Stub IManagerForApplication::CheckAvailability

* Add OsFileSystem Directory support and add a size field to directory entries

The size field will be needed by the incoming HOS IDirectory support.

* Implement support for IDirectory

This is used by applications to list the contents of a directory.

* Address feedback
This commit is contained in:
Billy Laws 2020-11-03 09:40:42 +00:00 committed by GitHub
parent 7ad86ec46f
commit 85d5dd3619
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 311 additions and 61 deletions

View File

@ -114,6 +114,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/services/fssrv/IFileSystem.cpp
${source_DIR}/skyline/services/fssrv/IFile.cpp
${source_DIR}/skyline/services/fssrv/IStorage.cpp
${source_DIR}/skyline/services/fssrv/IDirectory.cpp
${source_DIR}/skyline/services/nvdrv/INvDrvServices.cpp
${source_DIR}/skyline/services/nvdrv/driver.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvdevice.cpp

View File

@ -14,9 +14,9 @@ namespace skyline::gpu::vmm {
chunks.push_back(baseChunk);
}
std::optional<ChunkDescriptor> MemoryManager::FindChunk(u64 size, ChunkState state) {
auto chunk{std::find_if(chunks.begin(), chunks.end(), [size, state](const ChunkDescriptor &chunk) -> bool {
return chunk.size > size && chunk.state == state;
std::optional<ChunkDescriptor> MemoryManager::FindChunk(ChunkState state, u64 size, u64 alignment) {
auto chunk{std::find_if(chunks.begin(), chunks.end(), [state, size, alignment](const ChunkDescriptor &chunk) -> bool {
return (alignment ? util::IsAligned(chunk.address, alignment) : true) && chunk.size > size && chunk.state == state;
})};
if (chunk != chunks.end())
@ -83,9 +83,9 @@ namespace skyline::gpu::vmm {
throw exception("Failed to insert chunk into GPU address space!");
}
u64 MemoryManager::ReserveSpace(u64 size) {
u64 MemoryManager::ReserveSpace(u64 size, u64 alignment) {
size = util::AlignUp(size, constant::GpuPageSize);
auto newChunk{FindChunk(size, ChunkState::Unmapped)};
auto newChunk{FindChunk(ChunkState::Unmapped, size, alignment)};
if (!newChunk)
return 0;
@ -98,7 +98,7 @@ namespace skyline::gpu::vmm {
u64 MemoryManager::ReserveFixed(u64 address, u64 size) {
if (!util::IsAligned(address, constant::GpuPageSize))
return false;
return 0;
size = util::AlignUp(size, constant::GpuPageSize);
@ -107,7 +107,7 @@ namespace skyline::gpu::vmm {
u64 MemoryManager::MapAllocate(u64 address, u64 size) {
size = util::AlignUp(size, constant::GpuPageSize);
auto mappedChunk{FindChunk(size, ChunkState::Unmapped)};
auto mappedChunk{FindChunk(ChunkState::Unmapped, size)};
if (!mappedChunk)
return 0;
@ -121,26 +121,22 @@ namespace skyline::gpu::vmm {
u64 MemoryManager::MapFixed(u64 address, u64 cpuAddress, u64 size) {
if (!util::IsAligned(address, constant::GpuPageSize))
return false;
return 0;
size = util::AlignUp(size, constant::GpuPageSize);
return InsertChunk(ChunkDescriptor(address, size, cpuAddress, ChunkState::Mapped));
}
bool MemoryManager::Unmap(u64 address) {
bool MemoryManager::Unmap(u64 address, u64 size) {
if (!util::IsAligned(address, constant::GpuPageSize))
return false;
auto chunk{std::find_if(chunks.begin(), chunks.end(), [address](const ChunkDescriptor &chunk) -> bool {
return chunk.address == address;
})};
if (chunk == chunks.end())
try {
InsertChunk(ChunkDescriptor(address, size, 0, ChunkState::Unmapped));
} catch (const std::exception &e) {
return false;
chunk->state = ChunkState::Reserved;
chunk->cpuAddress = 0;
}
return true;
}

View File

@ -43,11 +43,12 @@ namespace skyline {
/**
* @brief Finds a chunk of the specified type in the GPU address space that is larger than the given size
* @param state The state of the chunk to find
* @param size The minimum size of the chunk to find
* @param state The state desired state of the chunk to find
* @return The first unmapped chunk in the GPU address space that fits the requested size
* @param alignment The alignment of the chunk to find
* @return The first unmapped chunk in the GPU address space that fulfils the requested conditions
*/
std::optional<ChunkDescriptor> FindChunk(u64 size, ChunkState state);
std::optional<ChunkDescriptor> FindChunk(ChunkState state, u64 size, u64 alignment = 0);
/**
* @brief Inserts a chunk into the chunk list, resizing and splitting as necessary
@ -62,9 +63,10 @@ namespace skyline {
/**
* @brief Reserves a region of the GPU address space so it will not be chosen automatically when mapping
* @param size The size of the region to reserve
* @param alignment The alignment of the region to reserve
* @return The virtual GPU base address of the region base
*/
u64 ReserveSpace(u64 size);
u64 ReserveSpace(u64 size, u64 alignment);
/**
* @brief Reserves a fixed region of the GPU address space so it will not be chosen automatically when mapping
@ -92,10 +94,10 @@ namespace skyline {
u64 MapFixed(u64 address, u64 cpuAddress, u64 size);
/**
* @brief Unmaps the chunk that starts at 'offset' from the GPU address space
* @brief Unmaps all chunks in the given region from the GPU address space
* @return Whether the operation succeeded
*/
bool Unmap(u64 address);
bool Unmap(u64 address, u64 size);
void Read(u8 *destination, u64 address, u64 size) const;

View File

@ -5,4 +5,8 @@
namespace skyline::service::account {
IManagerForApplication::IManagerForApplication(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {}
Result IManagerForApplication::CheckAvailability(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return {};
}
}

View File

@ -13,5 +13,14 @@ namespace skyline::service::account {
class IManagerForApplication : public BaseService {
public:
IManagerForApplication(const DeviceState &state, ServiceManager &manager);
/**
* @brief This checks if the given user has access to online services
*/
Result CheckAvailability(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
SERVICE_DECL(
SFUNC(0x0, IManagerForApplication, CheckAvailability)
)
};
}
}

View File

@ -27,7 +27,7 @@ namespace skyline::service::am {
Result IStorageAccessor::Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto offset{request.Pop<i64>()};
auto size{std::min(static_cast<i64>(request.inputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
auto size{std::min(static_cast<i64>(request.outputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
if (offset > parent->content.size())
return result::OutOfBounds;

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#include "results.h"
#include "IDirectory.h"
namespace skyline::service::fssrv {
struct __attribute__((packed)) DirectoryEntry {
std::array<char, 0x301> name;
struct {
bool directory : 1;
bool archive : 1;
u8 _pad_ : 6;
} attributes;
u16 _pad0_;
vfs::Directory::EntryType type;
u8 _pad1_[3];
u64 size;
};
IDirectory::IDirectory(std::shared_ptr<vfs::Directory> backing, std::shared_ptr<vfs::FileSystem> backingFs, const DeviceState &state, ServiceManager &manager) : backing(backing), backingFs(backingFs), BaseService(state, manager) {}
Result IDirectory::Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto entries{backing->Read()};
auto outputEntries{request.inputBuf.at(0).cast<DirectoryEntry>()};
size_t i{};
for (; i < std::min(entries.size(), outputEntries.size()); i++) {
auto &entry{entries.at(i)};
outputEntries[i] = {
.type = entry.type,
.attributes.directory = (entry.type == vfs::Directory::EntryType::Directory),
.size = entry.size,
};
span(outputEntries[i].name).copy_from(entry.name);
}
response.Push<u64>(i);
return {};
}
}

View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <services/base_service.h>
#include <services/serviceman.h>
#include <vfs/directory.h>
#include <vfs/filesystem.h>
namespace skyline::service::fssrv {
/**
* @brief IDirectory is an interface for accessing directory contents
* @url https://switchbrew.org/wiki/Filesystem_services#IDirectory
*/
class IDirectory : public BaseService {
private:
std::shared_ptr<vfs::Directory> backing; //!< Backing directory of the IDirectory
std::shared_ptr<vfs::FileSystem> backingFs; //!< Backing filesystem of the IDirectory
public:
IDirectory(std::shared_ptr<vfs::Directory> backing, std::shared_ptr<vfs::FileSystem> backingFs, const DeviceState &state, ServiceManager &manager);
/**
* @brief Reads the contents of an IDirectory
*/
Result Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
SERVICE_DECL(
SFUNC(0x0, IDirectory, Read),
)
};
}

View File

@ -3,13 +3,14 @@
#include "results.h"
#include "IFile.h"
#include "IDirectory.h"
#include "IFileSystem.h"
namespace skyline::service::fssrv {
IFileSystem::IFileSystem(std::shared_ptr<vfs::FileSystem> backing, const DeviceState &state, ServiceManager &manager) : backing(backing), BaseService(state, manager) {}
Result IFileSystem::CreateFile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::string path{request.inputBuf.at(0).as<char>()};
std::string path(request.inputBuf.at(0).as_string(true));
auto mode{request.Pop<u64>()};
auto size{request.Pop<u32>()};
@ -17,7 +18,7 @@ namespace skyline::service::fssrv {
}
Result IFileSystem::GetEntryType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::string path{request.inputBuf.at(0).as<char>()};
std::string path(request.inputBuf.at(0).as_string(true));
auto type{backing->GetEntryType(path)};
@ -31,7 +32,7 @@ namespace skyline::service::fssrv {
}
Result IFileSystem::OpenFile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::string path{request.inputBuf.at(0).as<char>()};
std::string path(request.inputBuf.at(0).as_string(true));
auto mode{request.Pop<vfs::Backing::Mode>()};
if (!backing->FileExists(path))
@ -46,6 +47,19 @@ namespace skyline::service::fssrv {
return {};
}
Result IFileSystem::OpenDirectory(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::string path(request.inputBuf.at(0).as_string(true));
if (!path.ends_with("/"))
path += "/";
auto listMode{request.Pop<vfs::Directory::ListMode>()};
auto directory{backing->OpenDirectory(path, listMode)};
manager.RegisterService(std::make_shared<IDirectory>(directory, backing, state, manager), session, response);
return {};
}
Result IFileSystem::Commit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return {};
}

View File

@ -35,6 +35,11 @@ namespace skyline::service::fssrv {
*/
Result OpenFile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns an IDirectory handle for the requested path and mask (https://switchbrew.org/wiki/Filesystem_services#OpenDirectory)
*/
Result OpenDirectory(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Commits all changes to the filesystem
* @url https://switchbrew.org/wiki/Filesystem_services#Commit
@ -45,6 +50,7 @@ namespace skyline::service::fssrv {
SFUNC(0x0, IFileSystem, CreateFile),
SFUNC(0x7, IFileSystem, GetEntryType),
SFUNC(0x8, IFileSystem, OpenFile),
SFUNC(0x9, IFileSystem, OpenDirectory),
SFUNC(0xA, IFileSystem, Commit)
)
};

View File

@ -80,6 +80,8 @@ namespace skyline::service::nvdrv {
SFUNC(0x3, INvDrvServices, Initialize),
SFUNC(0x4, INvDrvServices, QueryEvent),
SFUNC(0x8, INvDrvServices, SetAruid),
SFUNC(0xB, INvDrvServices, Ioctl2),
SFUNC(0xC, INvDrvServices, Ioctl3),
SFUNC(0xD, INvDrvServices, SetGraphicsFirmwareMemoryMarginEnabled)
)
};

View File

@ -78,7 +78,7 @@ namespace skyline::service::nvdrv::device {
* @return The name of the class
* @note The lifetime of the returned string is tied to that of the class
*/
const std::string& GetName();
const std::string &GetName();
/**
* @brief Handles IOCTL calls for devices

View File

@ -7,6 +7,14 @@
#include "nvhost_as_gpu.h"
namespace skyline::service::nvdrv::device {
struct MappingFlags {
bool fixed : 1;
u8 _pad0_ : 7;
bool remap : 1;
u32 _pad1_ : 23;
};
static_assert(sizeof(MappingFlags) == sizeof(u32));
NvHostAsGpu::NvHostAsGpu(const DeviceState &state) : NvDevice(state) {}
NvStatus NvHostAsGpu::BindChannel(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
@ -15,22 +23,22 @@ namespace skyline::service::nvdrv::device {
NvStatus NvHostAsGpu::AllocSpace(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 pages; // In
u32 pageSize; // In
u32 flags; // In
u32 pages; // In
u32 pageSize; // In
MappingFlags flags; // In
u32 _pad_;
union {
u64 offset; // InOut
u64 align; // In
u64 offset; // InOut
u64 align; // In
};
} region = buffer.as<Data>();
} &region = buffer.as<Data>();
u64 size{static_cast<u64>(region.pages) * static_cast<u64>(region.pageSize)};
if (region.flags & 1)
if (region.flags.fixed)
region.offset = state.gpu->memoryManager.ReserveFixed(region.offset, size);
else
region.offset = state.gpu->memoryManager.ReserveSpace(size);
region.offset = state.gpu->memoryManager.ReserveSpace(size, region.align);
if (region.offset == 0) {
state.logger->Warn("Failed to allocate GPU address space region!");
@ -43,32 +51,67 @@ namespace skyline::service::nvdrv::device {
NvStatus NvHostAsGpu::UnmapBuffer(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
u64 offset{buffer.as<u64>()};
if (!state.gpu->memoryManager.Unmap(offset))
state.logger->Warn("Failed to unmap chunk at 0x{:X}", offset);
try {
auto region{regionMap.at(offset)};
// Non-fixed regions are unmapped so that they can be used by future non-fixed mappings
if (!region.fixed)
if (!state.gpu->memoryManager.Unmap(offset, region.size))
state.logger->Warn("Failed to unmap region at 0x{:X}", offset);
regionMap.erase(offset);
} catch (const std::out_of_range &e) {
state.logger->Warn("Couldn't find region to unmap at 0x{:X}", offset);
}
return NvStatus::Success;
}
NvStatus NvHostAsGpu::Modify(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 flags; // In
u32 kind; // In
u32 nvmapHandle; // In
u32 pageSize; // InOut
u64 bufferOffset; // In
u64 mappingSize; // In
u64 offset; // InOut
} &data = buffer.as<Data>();
MappingFlags flags; // In
u32 kind; // In
u32 nvmapHandle; // In
u32 pageSize; // InOut
u64 bufferOffset; // In
u64 mappingSize; // In
u64 offset; // InOut
} &data = buffer.as<Data>();
try {
auto driver{nvdrv::driver.lock()};
auto nvmap{driver->nvMap.lock()};
auto mapping{nvmap->handleTable.at(data.nvmapHandle)};
if (data.flags.remap) {
auto region{regionMap.upper_bound(data.offset)};
if ((region == regionMap.begin()) || (region == regionMap.end())) {
state.logger->Warn("Cannot remap an unmapped GPU address space region: 0x{:X}", data.offset);
return NvStatus::BadParameter;
}
region--; // Upper bound gives us the region after the one we want
if (region->second.size < data.mappingSize) {
state.logger->Warn("Cannot remap an partially mapped GPU address space region: 0x{:X}", data.offset);
return NvStatus::BadParameter;
}
u64 gpuAddress{data.offset + data.bufferOffset};
u64 cpuAddress{region->second.cpuAddress + data.bufferOffset};
if (state.gpu->memoryManager.MapFixed(gpuAddress, cpuAddress, data.mappingSize)) {
state.logger->Warn("Failed to remap GPU address space region: 0x{:X}", gpuAddress);
return NvStatus::BadParameter;
}
return NvStatus::Success;
}
u64 mapPhysicalAddress{data.bufferOffset + mapping->address};
u64 mapSize{data.mappingSize ? data.mappingSize : mapping->size};
if (data.flags & 1)
if (data.flags.fixed)
data.offset = state.gpu->memoryManager.MapFixed(data.offset, mapPhysicalAddress, mapSize);
else
data.offset = state.gpu->memoryManager.MapAllocate(mapPhysicalAddress, mapSize);
@ -78,6 +121,8 @@ namespace skyline::service::nvdrv::device {
return NvStatus::BadParameter;
}
regionMap[data.offset] = {mapPhysicalAddress, mapSize, data.flags.fixed};
return NvStatus::Success;
} catch (const std::out_of_range &) {
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.nvmapHandle);

View File

@ -11,6 +11,15 @@ namespace skyline::service::nvdrv::device {
* @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu
*/
class NvHostAsGpu : public NvDevice {
private:
struct AddressSpaceRegion {
u64 cpuAddress;
u64 size;
bool fixed;
};
std::map<u64, AddressSpaceRegion> regionMap; //!< This maps the base addresses of mapped buffers to their total sizes and mapping type, this is needed as what was originally a single buffer may have been split into multiple GPU side buffers with the remap flag.
public:
NvHostAsGpu(const DeviceState &state);

View File

@ -54,7 +54,12 @@ namespace skyline::service::nvdrv::device {
throw exception("Waiting on a fence through SubmitGpfifo is unimplemented");
}
state.gpu->gpfifo.Push(span(state.process->GetPointer<gpu::gpfifo::GpEntry>(data.address), data.numEntries));
state.gpu->gpfifo.Push([&]() {
if (type == IoctlType::Ioctl2)
return inlineBuffer.cast<gpu::gpfifo::GpEntry>();
else
return span(state.process->GetPointer<gpu::gpfifo::GpEntry>(data.address), data.numEntries);
}());
data.fence.id = channelFence.id;
@ -113,6 +118,10 @@ namespace skyline::service::nvdrv::device {
return NvStatus::Success;
}
NvStatus NvHostChannel::SetTimeslice(IoctlType type, std::span<u8> buffer, std::span<u8> inlineBuffer) {
return NvStatus::Success;
}
NvStatus NvHostChannel::SetUserData(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::Success;
}

View File

@ -76,6 +76,12 @@ namespace skyline::service::nvdrv::device {
*/
NvStatus AllocGpfifoEx2(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Sets the timeslice of the channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_TIMESLICE)
*/
NvStatus SetTimeslice(IoctlType type, std::span<u8> buffer, std::span<u8> inlineBuffer);
/**
* @brief Sets the user specific data
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_USER_DATA
@ -93,6 +99,7 @@ namespace skyline::service::nvdrv::device {
NVFUNC(0x480C, NvHostChannel, SetErrorNotifier),
NVFUNC(0x480D, NvHostChannel, SetPriority),
NVFUNC(0x481A, NvHostChannel, AllocGpfifoEx2),
NVFUNC(0x481B, NvHostChannel, SubmitGpfifo), // Our SubmitGpfifo implementation also handles SubmitGpfifoEx
NVFUNC(0x4714, NvHostChannel, SetUserData)
)
};

View File

@ -112,7 +112,7 @@ namespace skyline::service::nvdrv::device {
userEventId = FindFreeEvent(data.fence.id);
}
auto& event{*events.at(userEventId)};
auto &event{*events.at(userEventId)};
if (event.state == NvHostEvent::State::Cancelled || event.state == NvHostEvent::State::Available || event.state == NvHostEvent::State::Signaled) {
state.logger->Debug("Now waiting on nvhost event: {} with fence: {}", userEventId, data.fence.id);
event.Wait(state.gpu, data.fence);

View File

@ -77,7 +77,13 @@ namespace skyline::service::nvdrv::device {
if (data.gpuCharacteristicsBufSize < sizeof(GpuCharacteristics))
return NvStatus::InvalidSize;
data.gpuCharacteristics = GpuCharacteristics{};
// The IOCTL3 version of GetCharacteristics additionally outputs to the inline output buffer
if (type == IoctlType::Ioctl3) {
auto &inlineCharacteristics{inlineBuffer.as<GpuCharacteristics>()};
data.gpuCharacteristics = inlineCharacteristics = GpuCharacteristics{};
} else {
data.gpuCharacteristics = GpuCharacteristics{};
}
data.gpuCharacteristicsBufSize = sizeof(GpuCharacteristics);
return NvStatus::Success;
@ -90,8 +96,14 @@ namespace skyline::service::nvdrv::device {
u64 maskBuf; // Out
} &data = buffer.as<Data>();
if (data.maskBufSize)
data.maskBuf = 0x3;
if (data.maskBufSize) {
if (type == IoctlType::Ioctl3) {
auto &inlineMask{inlineBuffer.as<u32>()};
data.maskBuf = inlineMask = 0x3;
} else {
data.maskBuf = 0x3;
}
}
return NvStatus::Success;
}

View File

@ -28,7 +28,7 @@ namespace skyline::service::nvdrv {
}
u32 NvHostSyncpoint::FindFreeSyncpoint() {
for (u32 i{}; i < constant::MaxHwSyncpointCount; i++)
for (u32 i{1}; i < constant::MaxHwSyncpointCount; i++)
if (!syncpoints[i].reserved)
return i;

View File

@ -73,7 +73,7 @@ namespace skyline::service {
SERVICE_CASE(pctl::IParentalControlServiceFactory, "pctl:r")
SERVICE_CASE(lm::ILogService, "lm")
SERVICE_CASE(account::IAccountServiceForApplication, "acc:u0")
SERVICE_CASE(friends::IServiceCreator, "friend")
SERVICE_CASE(friends::IServiceCreator, "friend:u")
SERVICE_CASE(nfp::IUserManager, "nfp:user")
SERVICE_CASE(nifm::IStaticService, "nifm:u")
SERVICE_CASE(socket::IClient, "bsd:u")

View File

@ -19,6 +19,7 @@ namespace skyline::vfs {
struct Entry {
std::string name;
EntryType type;
size_t size; //!< 0 if a directory
};
/**
@ -29,7 +30,7 @@ namespace skyline::vfs {
bool directory : 1; //!< The directory listing will contain subdirectories
bool file : 1; //!< The directory listing will contain files
};
u32 raw;
u32 raw{};
};
static_assert(sizeof(ListMode) == 0x4);

View File

@ -3,7 +3,6 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include "os_backing.h"
@ -84,4 +83,45 @@ namespace skyline::vfs {
return std::nullopt;
}
std::shared_ptr<Directory> OsFileSystem::OpenDirectory(const std::string &path, Directory::ListMode listMode) {
return std::make_shared<OsFileSystemDirectory>(basePath + path, listMode);
}
OsFileSystemDirectory::OsFileSystemDirectory(const std::string &path, Directory::ListMode listMode) : Directory(listMode), path(path) {}
std::vector<Directory::Entry> OsFileSystemDirectory::Read() {
if (!listMode.file && !listMode.directory)
return {};
std::vector<Directory::Entry> outputEntries;
struct dirent *entry;
auto directory{opendir(path.c_str())};
if (!directory)
throw exception("Failed to open directory: {}, error: {}", path, strerror(errno));
while ((entry = readdir(directory))) {
struct stat entryInfo;
if (stat((path + std::string(entry->d_name)).c_str(), &entryInfo))
throw exception("Failed to stat directory entry: {}, error: {}", entry->d_name, strerror(errno));
std::string name(entry->d_name);
if (S_ISDIR(entryInfo.st_mode) && listMode.directory && (name != ".") && (name != "..")) {
outputEntries.push_back(Directory::Entry{
.type = Directory::EntryType::Directory,
.name = std::string(entry->d_name),
.size = 0,
});
} else if (S_ISREG(entryInfo.st_mode) && listMode.file) {
outputEntries.push_back(Directory::Entry{
.type = Directory::EntryType::File,
.name = std::string(entry->d_name),
.size = static_cast<size_t>(entryInfo.st_size),
});
}
}
return outputEntries;
}
}

View File

@ -23,5 +23,19 @@ namespace skyline::vfs {
std::shared_ptr<Backing> OpenFile(const std::string &path, Backing::Mode mode = {true, false, false});
std::optional<Directory::EntryType> GetEntryType(const std::string &path);
std::shared_ptr<Directory> OpenDirectory(const std::string &path, Directory::ListMode listMode);
};
}
/**
* @brief OsFileSystemDirectory abstracts access to a native linux directory through the VFS APIs
*/
class OsFileSystemDirectory : public Directory {
private:
std::string path;
public:
OsFileSystemDirectory(const std::string &path, ListMode listMode);
std::vector<Entry> Read();
};}

View File

@ -54,7 +54,7 @@ namespace skyline::vfs {
std::vector<Directory::Entry> fileList;
for (const auto &file : fileMap)
fileList.emplace_back(Directory::Entry{file.first, Directory::EntryType::File});
fileList.emplace_back(Directory::Entry{file.first, Directory::EntryType::File, file.second.size});
return std::make_shared<PartitionFileSystemDirectory>(fileList, listMode);
}

View File

@ -22,7 +22,7 @@ namespace skyline::vfs {
*/
RegionBacking(const std::shared_ptr<vfs::Backing> &backing, size_t offset, size_t size, Mode mode = {true, false, false}) : Backing(mode, size), backing(backing), baseOffset(offset) {};
virtual size_t Read(span<u8> output, size_t offset = 0) {
size_t Read(span<u8> output, size_t offset = 0) {
if (!mode.read)
throw exception("Attempting to read a backing that is not readable");
if (size - offset < output.size())

View File

@ -92,7 +92,7 @@ namespace skyline::vfs {
std::vector<char> name(romFsFileEntry.nameSize);
backing->Read(span(name).cast<u8>(), header.fileMetaTableOffset + offset + sizeof(RomFileSystem::RomFsFileEntry));
contents.emplace_back(Entry{std::string(name.data(), romFsFileEntry.nameSize), EntryType::File});
contents.emplace_back(Entry{std::string(name.data(), romFsFileEntry.nameSize), EntryType::File, romFsFileEntry.size});
}
offset = romFsFileEntry.siblingOffset;