1 // Copyright 2018 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <boost/serialization/unique_ptr.hpp>
6 #include "common/archives.h"
7 #include "common/logging/log.h"
8 #include "core/core.h"
9 #include "core/file_sys/errors.h"
10 #include "core/file_sys/file_backend.h"
11 #include "core/hle/ipc_helpers.h"
12 #include "core/hle/kernel/client_port.h"
13 #include "core/hle/kernel/client_session.h"
14 #include "core/hle/kernel/event.h"
15 #include "core/hle/kernel/server_session.h"
16 #include "core/hle/service/fs/file.h"
17 
18 SERIALIZE_EXPORT_IMPL(Service::FS::File)
19 SERIALIZE_EXPORT_IMPL(Service::FS::FileSessionSlot)
20 
21 namespace Service::FS {
22 
23 template <class Archive>
serialize(Archive & ar,const unsigned int)24 void File::serialize(Archive& ar, const unsigned int) {
25     ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
26     ar& path;
27     ar& backend;
28 }
29 
File()30 File::File() : File(Core::Global<Kernel::KernelSystem>()) {}
31 
File(Kernel::KernelSystem & kernel,std::unique_ptr<FileSys::FileBackend> && backend,const FileSys::Path & path)32 File::File(Kernel::KernelSystem& kernel, std::unique_ptr<FileSys::FileBackend>&& backend,
33            const FileSys::Path& path)
34     : File(kernel) {
35     this->backend = std::move(backend);
36     this->path = path;
37 }
38 
File(Kernel::KernelSystem & kernel)39 File::File(Kernel::KernelSystem& kernel)
40     : ServiceFramework("", 1), path(""), backend(nullptr), kernel(kernel) {
41     static const FunctionInfo functions[] = {
42         {0x08010100, &File::OpenSubFile, "OpenSubFile"},
43         {0x080200C2, &File::Read, "Read"},
44         {0x08030102, &File::Write, "Write"},
45         {0x08040000, &File::GetSize, "GetSize"},
46         {0x08050080, &File::SetSize, "SetSize"},
47         {0x08080000, &File::Close, "Close"},
48         {0x08090000, &File::Flush, "Flush"},
49         {0x080A0040, &File::SetPriority, "SetPriority"},
50         {0x080B0000, &File::GetPriority, "GetPriority"},
51         {0x080C0000, &File::OpenLinkFile, "OpenLinkFile"},
52     };
53     RegisterHandlers(functions);
54 }
55 
Read(Kernel::HLERequestContext & ctx)56 void File::Read(Kernel::HLERequestContext& ctx) {
57     IPC::RequestParser rp(ctx, 0x0802, 3, 2);
58     u64 offset = rp.Pop<u64>();
59     u32 length = rp.Pop<u32>();
60     auto& buffer = rp.PopMappedBuffer();
61     LOG_TRACE(Service_FS, "Read {}: offset=0x{:x} length=0x{:08X}", GetName(), offset, length);
62 
63     const FileSessionSlot* file = GetSessionData(ctx.Session());
64 
65     if (file->subfile && length > file->size) {
66         LOG_WARNING(Service_FS, "Trying to read beyond the subfile size, truncating");
67         length = static_cast<u32>(file->size);
68     }
69 
70     // This file session might have a specific offset from where to start reading, apply it.
71     offset += file->offset;
72 
73     if (offset + length > backend->GetSize()) {
74         LOG_ERROR(Service_FS,
75                   "Reading from out of bounds offset=0x{:x} length=0x{:08X} file_size=0x{:x}",
76                   offset, length, backend->GetSize());
77     }
78 
79     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
80 
81     std::vector<u8> data(length);
82     ResultVal<std::size_t> read = backend->Read(offset, data.size(), data.data());
83     if (read.Failed()) {
84         rb.Push(read.Code());
85         rb.Push<u32>(0);
86     } else {
87         buffer.Write(data.data(), 0, *read);
88         rb.Push(RESULT_SUCCESS);
89         rb.Push<u32>(static_cast<u32>(*read));
90     }
91     rb.PushMappedBuffer(buffer);
92 
93     std::chrono::nanoseconds read_timeout_ns{backend->GetReadDelayNs(length)};
94     ctx.SleepClientThread("file::read", read_timeout_ns, nullptr);
95 }
96 
Write(Kernel::HLERequestContext & ctx)97 void File::Write(Kernel::HLERequestContext& ctx) {
98     IPC::RequestParser rp(ctx, 0x0803, 4, 2);
99     u64 offset = rp.Pop<u64>();
100     u32 length = rp.Pop<u32>();
101     u32 flush = rp.Pop<u32>();
102     auto& buffer = rp.PopMappedBuffer();
103     LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flush=0x{:x}", GetName(), offset,
104               length, flush);
105 
106     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
107 
108     FileSessionSlot* file = GetSessionData(ctx.Session());
109 
110     // Subfiles can not be written to
111     if (file->subfile) {
112         rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS);
113         rb.Push<u32>(0);
114         rb.PushMappedBuffer(buffer);
115         return;
116     }
117 
118     std::vector<u8> data(length);
119     buffer.Read(data.data(), 0, data.size());
120     ResultVal<std::size_t> written = backend->Write(offset, data.size(), flush != 0, data.data());
121 
122     // Update file size
123     file->size = backend->GetSize();
124 
125     if (written.Failed()) {
126         rb.Push(written.Code());
127         rb.Push<u32>(0);
128     } else {
129         rb.Push(RESULT_SUCCESS);
130         rb.Push<u32>(static_cast<u32>(*written));
131     }
132     rb.PushMappedBuffer(buffer);
133 }
134 
GetSize(Kernel::HLERequestContext & ctx)135 void File::GetSize(Kernel::HLERequestContext& ctx) {
136     IPC::RequestParser rp(ctx, 0x0804, 0, 0);
137 
138     const FileSessionSlot* file = GetSessionData(ctx.Session());
139 
140     IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
141     rb.Push(RESULT_SUCCESS);
142     rb.Push<u64>(file->size);
143 }
144 
SetSize(Kernel::HLERequestContext & ctx)145 void File::SetSize(Kernel::HLERequestContext& ctx) {
146     IPC::RequestParser rp(ctx, 0x0805, 2, 0);
147     u64 size = rp.Pop<u64>();
148 
149     FileSessionSlot* file = GetSessionData(ctx.Session());
150 
151     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
152 
153     // SetSize can not be called on subfiles.
154     if (file->subfile) {
155         rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS);
156         return;
157     }
158 
159     file->size = size;
160     backend->SetSize(size);
161     rb.Push(RESULT_SUCCESS);
162 }
163 
Close(Kernel::HLERequestContext & ctx)164 void File::Close(Kernel::HLERequestContext& ctx) {
165     IPC::RequestParser rp(ctx, 0x0808, 0, 0);
166 
167     // TODO(Subv): Only close the backend if this client is the only one left.
168     if (connected_sessions.size() > 1)
169         LOG_WARNING(Service_FS, "Closing File backend but {} clients still connected",
170                     connected_sessions.size());
171 
172     backend->Close();
173     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
174     rb.Push(RESULT_SUCCESS);
175 }
176 
Flush(Kernel::HLERequestContext & ctx)177 void File::Flush(Kernel::HLERequestContext& ctx) {
178     IPC::RequestParser rp(ctx, 0x0809, 0, 0);
179 
180     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
181 
182     const FileSessionSlot* file = GetSessionData(ctx.Session());
183 
184     // Subfiles can not be flushed.
185     if (file->subfile) {
186         rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS);
187         return;
188     }
189 
190     backend->Flush();
191     rb.Push(RESULT_SUCCESS);
192 }
193 
SetPriority(Kernel::HLERequestContext & ctx)194 void File::SetPriority(Kernel::HLERequestContext& ctx) {
195     IPC::RequestParser rp(ctx, 0x080A, 1, 0);
196 
197     FileSessionSlot* file = GetSessionData(ctx.Session());
198     file->priority = rp.Pop<u32>();
199 
200     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
201     rb.Push(RESULT_SUCCESS);
202 }
203 
GetPriority(Kernel::HLERequestContext & ctx)204 void File::GetPriority(Kernel::HLERequestContext& ctx) {
205     IPC::RequestParser rp(ctx, 0x080B, 0, 0);
206     const FileSessionSlot* file = GetSessionData(ctx.Session());
207 
208     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
209     rb.Push(RESULT_SUCCESS);
210     rb.Push(file->priority);
211 }
212 
OpenLinkFile(Kernel::HLERequestContext & ctx)213 void File::OpenLinkFile(Kernel::HLERequestContext& ctx) {
214     LOG_WARNING(Service_FS, "(STUBBED) File command OpenLinkFile {}", GetName());
215     using Kernel::ClientSession;
216     using Kernel::ServerSession;
217     IPC::RequestParser rp(ctx, 0x080C, 0, 0);
218     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
219     auto [server, client] = kernel.CreateSessionPair(GetName());
220     ClientConnected(server);
221 
222     FileSessionSlot* slot = GetSessionData(std::move(server));
223     const FileSessionSlot* original_file = GetSessionData(ctx.Session());
224 
225     slot->priority = original_file->priority;
226     slot->offset = 0;
227     slot->size = backend->GetSize();
228     slot->subfile = false;
229 
230     rb.Push(RESULT_SUCCESS);
231     rb.PushMoveObjects(client);
232 }
233 
OpenSubFile(Kernel::HLERequestContext & ctx)234 void File::OpenSubFile(Kernel::HLERequestContext& ctx) {
235     IPC::RequestParser rp(ctx, 0x0801, 4, 0);
236     s64 offset = rp.PopRaw<s64>();
237     s64 size = rp.PopRaw<s64>();
238 
239     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
240 
241     const FileSessionSlot* original_file = GetSessionData(ctx.Session());
242 
243     if (original_file->subfile) {
244         // OpenSubFile can not be called on a file which is already as subfile
245         rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS);
246         return;
247     }
248 
249     if (offset < 0 || size < 0) {
250         rb.Push(FileSys::ERR_WRITE_BEYOND_END);
251         return;
252     }
253 
254     std::size_t end = offset + size;
255 
256     // TODO(Subv): Check for overflow and return ERR_WRITE_BEYOND_END
257 
258     if (end > original_file->size) {
259         rb.Push(FileSys::ERR_WRITE_BEYOND_END);
260         return;
261     }
262 
263     using Kernel::ClientSession;
264     using Kernel::ServerSession;
265     auto [server, client] = kernel.CreateSessionPair(GetName());
266     ClientConnected(server);
267 
268     FileSessionSlot* slot = GetSessionData(std::move(server));
269     slot->priority = original_file->priority;
270     slot->offset = offset;
271     slot->size = size;
272     slot->subfile = true;
273 
274     rb.Push(RESULT_SUCCESS);
275     rb.PushMoveObjects(client);
276 }
277 
Connect()278 std::shared_ptr<Kernel::ClientSession> File::Connect() {
279     auto [server, client] = kernel.CreateSessionPair(GetName());
280     ClientConnected(server);
281 
282     FileSessionSlot* slot = GetSessionData(std::move(server));
283     slot->priority = 0;
284     slot->offset = 0;
285     slot->size = backend->GetSize();
286     slot->subfile = false;
287 
288     return client;
289 }
290 
GetSessionFileOffset(std::shared_ptr<Kernel::ServerSession> session)291 std::size_t File::GetSessionFileOffset(std::shared_ptr<Kernel::ServerSession> session) {
292     const FileSessionSlot* slot = GetSessionData(std::move(session));
293     ASSERT(slot);
294     return slot->offset;
295 }
296 
GetSessionFileSize(std::shared_ptr<Kernel::ServerSession> session)297 std::size_t File::GetSessionFileSize(std::shared_ptr<Kernel::ServerSession> session) {
298     const FileSessionSlot* slot = GetSessionData(std::move(session));
299     ASSERT(slot);
300     return slot->size;
301 }
302 
303 } // namespace Service::FS
304