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