YSTest: DSIO.cpp 源文件

YSTest PreAlpha_b900_20201012 The YSLib Test Project

DSIO.cpp
浏览该文件的文档.
1 /*
2 © 2015-2020 FrankHB.
3
4 This file is part of the YSLib project, and may only be used,
5 modified, and distributed under the terms of the YSLib project
6 license, LICENSE.TXT. By continuing to use, modify, or distribute
7 this file you indicate that you have read the license and
8 understand and accept it fully.
9 */
10
29 #include "YCLib/YModules.h"
30 #include YFM_DS_YCLib_DSIO // for platform::Descriptions, std::runtime_error,
31 // ystdex::throw_error, std::system_error, string, string_view,
32 // ystdex::retry_on_cond, YAssertNonnull, FetchSeparator, ystdex::id,
33 // std::bind, std::placeholder::_1, ystdex::exchange, ystdex::invoke, std::ref,
34 // YTraceDe, std::exception, ystdex::trivially_copy_n, ptrdiff_t,
35 // DISC_INTERFACE, unique_raw, ystdex::aligned_store_cast;
36 #if YCL_DS
37 # include "YSLib/Core/YModules.h"
38 # include YFM_YSLib_Core_YException // for YSLib::TryInvoke,
39 // YSLib::FilterExceptions;
40 # include <ystdex/functional.hpp> // for std::all_of,
41 // ystdex::common_nonvoid_t, ystdex::invoke_result_t, ystdex::invoke_for_value;
42 # include "CHRLib/YModules.h"
43 # include YFM_CHRLib_CharacterProcessing // for ystdex::read_uint_le,
44 // ystdex::write_uint_le, CHRLib::MakeUCS2LE, ystdex::ntctsicmp,
45 // ystdex::ntctsncpy;
46 # include <ystdex/string.hpp> // for ystdex::ltrim;
47 # include <cerrno> // for ENOMEM, ENOTSUP, EFBIG, EINVAL;
48 # include YFM_YCLib_NativeAPI // for OpenMode;
49 # include <ystdex/scope_guard.hpp> // for ystdex::make_guard;
50 # include <sys/iosupport.h> // for ::_reent, ::size_t, ::ssize_t, ::off_t,
51 // sturct ::stat, struct ::statvfs, ::DIR_ITER, ::devoptab_t;
52 #endif
53
54 namespace platform_ex
55 {
56
57 using namespace platform;
58 using namespace Descriptions;
59
60 #if YCL_DS
61 SectorCache::SectorCache(size_t pages, size_t spps, Disc d, ::sec_t eop,
62 size_t bps)
63 : disc(d), number_of_pages(std::max<size_t>(pages, 2)),
64 sectors_per_page_shift(std::max<size_t>(spps, 3)), end_of_partition(eop),
65 bytes_per_sector(bps),
66 entries(number_of_pages)
67 {
68 entries.flush = [this](UsedListCache::value_type& pr) ynothrow{
69 FlushEntry(pr);
70 };
71 }
72 SectorCache::~SectorCache()
73 {
74 Flush();
75 }
76
77 size_t
78 SectorCache::GetBlockCount(::sec_t sec) const ynothrowv
79 {
80 const auto end_sec(std::min<::sec_t>(
81 sec + (1 << sectors_per_page_shift), end_of_partition));
82
83 YAssert(sec <= end_sec, "Invalid range found.");
84 return end_sec - sec;
85 }
86 ystdex::block_buffer*
87 SectorCache::GetPage(::sec_t key) ynothrowv
88 {
89 YAssert(GetKey(key) == key, "Invalid key found.");
90
91 return YSLib::TryInvoke([this, key]{
92 return &ystdex::cache_lookup(entries, key, [this, key]{
93 const auto size(bytes_per_sector << sectors_per_page_shift);
94 // TODO: Use aligned allocation for cache implementation.
95 // NOTE: Intentionally uninitialized as LibFAT.
96 ystdex::block_buffer entry(make_unique_default_init<byte[]>(size));
97 const auto n(GetBlockCount(key));
98
99 YAssert(n <= size, "Invalid cache creation parameter found.");
100 if(YB_UNLIKELY(!disc.ReadSectors(key, n, entry.get())))
101 throw std::runtime_error(
102 "Failed reading on creating file system cache entry.");
103 return entry;
104 });
105 });
106 }
107
108 bool
109 SectorCache::EraseWritePartialSector(::sec_t sec, size_t offset,
110 const void* p_buf, size_t n) ynothrowv
111 {
112 return PerformPartialSectorIO(
113 [this, offset, p_buf, n](ystdex::block_buffer& entry, size_t sec_bytes){
114 ystdex::trivially_fill_n(entry.get() + sec_bytes, bytes_per_sector);
115 entry.write(sec_bytes + offset, p_buf, n);
116 }, sec, offset, n);
117 }
118
119 bool
120 SectorCache::FillPartialSector(::sec_t sec, size_t offset, size_t n, byte val)
121 ynothrowv
122 {
123 return PerformPartialSectorIO(
124 [this, offset, n, val](ystdex::block_buffer& entry, size_t sec_bytes){
125 entry.fill(sec_bytes + offset, n, val);
126 }, sec, offset, n);
127 }
128
129 bool
130 SectorCache::FillSectors(::sec_t sec, size_t n, byte val) ynothrow
131 {
132 return PerformSectorsIO([&](ystdex::block_buffer& entry, size_t sec_off,
133 size_t secs_to_process){
134 entry.fill(sec_off * bytes_per_sector,
135 secs_to_process * bytes_per_sector, val);
136 }, sec, n);
137 }
138
139 bool
140 SectorCache::Flush() ynothrow
141 {
142 // TODO: Flush all?
143 return std::all_of(entries.begin(), entries.end(),
144 std::bind(&SectorCache::FlushEntry, this, std::placeholders::_1));
145 }
146
147 bool
148 SectorCache::FlushEntry(UsedListCache::value_type& pr) ynothrow
149 {
150 if(pr.second.modified())
151 {
152 if(!disc.WriteSectors(pr.first, GetBlockCount(pr.first),
153 pr.second.get()))
154 return {};
155 pr.second.flush();
156 }
157 return true;
158 }
159
160 bool
161 SectorCache::ReadPartialSector(void* p_buf, ::sec_t sec, size_t offset,
162 size_t n) ynothrowv
163 {
164 return PerformPartialSectorIO(
165 [this, p_buf, offset, n](ystdex::block_buffer& entry, size_t sec_bytes){
166 entry.read(p_buf, sec_bytes + offset, n);
167 }, sec, offset, n);
168 }
169
170 bool
171 SectorCache::ReadSectors(void* p_buf, ::sec_t sec, size_t n) ynothrowv
172 {
173 auto dst(static_cast<byte*>(p_buf));
174
175 return PerformSectorsIO([&](ystdex::block_buffer& entry, size_t sec_off,
176 size_t secs_to_process){
177 entry.read(dst, sec_off * bytes_per_sector,
178 secs_to_process * bytes_per_sector);
179 dst += secs_to_process * bytes_per_sector;
180 }, sec, n);
181 }
182
183 bool
184 SectorCache::WritePartialSector(::sec_t sec, size_t offset, const void* p_buf,
185 size_t n) ynothrowv
186 {
187 return PerformPartialSectorIO(
188 [this, offset, p_buf, n](ystdex::block_buffer& entry, size_t sec_bytes){
189 entry.write(sec_bytes + offset, p_buf, n);
190 }, sec, offset, n);
191 }
192
193 bool
194 SectorCache::WriteSectors(::sec_t sec, const void* p_buf, size_t n)
195 ynothrow
196 {
197 auto src(static_cast<const byte*>(p_buf));
198
199 return PerformSectorsIO([&](ystdex::block_buffer& entry, size_t sec_off,
200 size_t secs_to_process){
201 entry.write(sec_off * bytes_per_sector, src,
202 secs_to_process * bytes_per_sector);
203 src += secs_to_process * bytes_per_sector;
204 }, sec, n);
205 }
206
207 namespace FAT
208 {
209
211
212 // TODO: Support big endian implementations.
213 using ystdex::read_uint_le;
214 using ystdex::write_uint_le;
215 using ystdex::throw_error;
217
218 using std::system_error;
219 using std::errc;
220 // XXX: Coupled string types.
221 using CHRLib::MakeUCS2LE;
223
224 namespace
225 {
226
227 template<size_t _vWidth>
228 bool
229 ReadLEVal(SectorCache& cache, std::uint32_t& value, ::sec_t sec, size_t offset)
230 ynothrow
231 {
232 static yconstexpr const size_t
233 n(_vWidth / std::numeric_limits<unsigned char>::digits);
234 byte buf[n];
235
236 if(cache.ReadPartialSector(buf, sec, offset, n))
237 {
238 value = ystdex::read_uint_le<_vWidth>(buf);
239 return true;
240 }
241 return {};
242 }
243
244 template<size_t _vWidth>
245 bool
246 WriteLEVal(SectorCache& cache, std::uint32_t value, ::sec_t sec, size_t offset)
247 ynothrow
248 {
249 static yconstexpr const size_t
250 n(_vWidth / std::numeric_limits<unsigned char>::digits);
251 byte buf[n]{};
252
253 write_uint_le<_vWidth>(buf, value);
254 return cache.WritePartialSector(sec, offset, buf, n);
255 }
256
257
259
260 void
261 CheckThrowEIO(bool b) ythrow(system_error)
262 {
263 if(YB_UNLIKELY(!b))
264 throw_error(errc::io_error);
265 }
266
268 void
269 CheckThrowENOSPC(bool b) ythrow(system_error)
270 {
271 if(YB_UNLIKELY(!b))
272 throw_error(errc::no_space_on_device);
273 }
274
275 template<class _tPos>
276 YB_ATTR_nodiscard ::sec_t
277 PosToSec(const AllocationTable& tbl, const _tPos& pos) ynothrow
278 {
279 return tbl.ClusterToSector(pos.GetCluster()) + pos.GetSector();
280 }
282
284
285 PDefH(void, CheckFillPartialSectorOff, const AllocationTable& tbl,
286 const FilePosition& pos, size_t offset, size_t n) ythrow(system_error)
287 ImplExpr(CheckThrowEIO(
288 tbl.Cache.FillPartialSector(PosToSec(tbl, pos), offset, n)))
289
290 PDefH(void, CheckFillPartialSector, const AllocationTable& tbl,
291 const FilePosition& pos, size_t n) ythrow(system_error)
292 ImplExpr(CheckFillPartialSectorOff(tbl, pos, pos.GetByte(), n))
293
294 PDefH(void, CheckFillSectors, const AllocationTable& tbl,
295 const FilePosition& pos) ythrow(system_error)
296 ImplExpr(CheckThrowEIO(tbl.Cache.FillSectors(PosToSec(tbl, pos))))
297
298 template<class _tPos>
299 void
300 CheckReadPartialSectorOff(const AllocationTable& tbl, const _tPos& pos,
301 void* data, size_t offset, size_t n) ythrow(system_error)
302 {
303 CheckThrowEIO(
304 tbl.Cache.ReadPartialSector(data, PosToSec(tbl, pos), offset, n));
305 }
306
307 PDefH(void, CheckReadPartialSector, const AllocationTable& tbl,
308 const DEntryPosition& pos, void* data) ythrow(system_error)
309 ImplExpr(CheckReadPartialSectorOff(tbl, pos, data, pos.GetByte(),
310 EntryDataSize))
311 PDefH(void, CheckReadPartialSector, const AllocationTable& tbl,
312 const FilePosition& pos, void* data, size_t n) ythrow(system_error)
313 ImplExpr(CheckReadPartialSectorOff(tbl, pos, data, pos.GetByte(), n))
314
315 PDefH(void, CheckReadSectors, const AllocationTable& tbl, ClusterIndex c,
316 void* buf, size_t n) ythrow(system_error)
317 ImplExpr(CheckThrowEIO(
318 tbl.Cache.ReadSectors(buf, tbl.ClusterToSector(c), n)))
319 PDefH(void, CheckReadSectors, const AllocationTable& tbl,
320 const FilePosition& pos, void* buf, size_t n) ythrow(system_error)
321 ImplExpr(CheckThrowEIO(
322 tbl.Cache.ReadSectors(buf, PosToSec(tbl, pos), n)))
323
324 template<class _tPos>
325 void
326 CheckWritePartialSectorOff(const AllocationTable& tbl, const _tPos& pos,
327 const void* data, size_t offset, size_t n) ythrow(system_error)
328 {
329 CheckThrowEIO(
330 tbl.Cache.WritePartialSector(PosToSec(tbl, pos), offset, data, n));
331 }
332
333 PDefH(void, CheckWritePartialSector, const AllocationTable& tbl,
334 const DEntryPosition& pos, const void* data) ythrow(system_error)
335 ImplExpr(CheckWritePartialSectorOff(tbl, pos, data, pos.GetByte(),
336 EntryDataSize))
337 PDefH(void, CheckWritePartialSector, const AllocationTable& tbl, const
338 FilePosition& pos, const void* data, size_t n) ythrow(system_error)
339 ImplExpr(CheckWritePartialSectorOff(tbl, pos, data, pos.GetByte(), n))
340
341 PDefH(void, CheckWriteSectors, const AllocationTable& tbl, ClusterIndex c,
342 const void* buf, size_t n) ythrow(system_error) ImplRet(CheckThrowEIO(
343 tbl.Cache.WriteSectors(tbl.ClusterToSector(c), buf, n)))
344 PDefH(void, CheckWriteSectors, const AllocationTable& tbl, const FilePosition&
345 pos, const void* buf, size_t n) ythrow(system_error)
346 ImplRet(CheckThrowEIO(tbl.Cache.WriteSectors(PosToSec(tbl, pos), buf, n)))
348
350 YB_ATTR_nodiscard bool
351 CompareEntryNames(EntryData& data, string_view comp, const string& name)
352 ynothrow
353 {
354 try
355 {
356 if(comp.length() == name.length()
357 && ystdex::ntctsicmp(MakeUCS2LE(comp).c_str(),
358 MakeUCS2LE(name).c_str(), comp.length()) == 0)
359 return true;
360 }
361 CatchIgnore(...)
362 try
363 {
364 if(data.FindAlias(comp))
365 return true;
366 }
367 CatchIgnore(...)
368 return {};
369 }
370
372 void
373 AssignValidCluster(Partition& part, FilePosition& pos, ClusterIndex next_start)
374 ythrow(system_error)
375 {
376 CheckThrowEIO(part.Table.IsValid(next_start));
377 pos = {next_start, 0, pos.GetByte()};
378 }
379
380 } // unnamed namespace;
382
383 AllocationTable::AllocationTable(::sec_t start_sector, const byte* sec_buf,
384 size_t pages, size_t sectors_per_page_shift, Disc d)
385 ynothrowv
386 : sectors_per_cluster(sec_buf[BPB_SecPerClus]),
387 bytes_per_sector([sec_buf, this]{
388 const auto t(read_uint_le<16>(sec_buf + BPB_BytsPerSec));
389
390 // TODO: Use interval API.
391 if(t < MinSectorSize || bytes_per_sector > MaxSectorSize)
392 throw std::runtime_error("Unsupported sector size found.");
393 return t;
394 }()), bytes_per_cluster(bytes_per_sector * sectors_per_cluster),
395 table_start(start_sector + read_uint_le<16>(sec_buf + BPB_RsvdSecCnt)),
396 table_size([sec_buf]() ynothrowv{
397 // NOTE: last_cluster = cluster_count + Clusters::First - 1.
398 const auto t(read_uint_le<16>(sec_buf + BPB_FATSz16));
399
400 return t == 0 ? size_t(
401 read_uint_le<32>(sec_buf + BPB_FATSz32)) : size_t(t);
402 }()), total_sectors_num([sec_buf]() ynothrowv{
403 const auto t(read_uint_le<16>(sec_buf + BPB_TotSec16));
404
405 return t == 0 ? read_uint_le<32>(sec_buf + BPB_TotSec32)
406 : std::uint32_t(t);
407 }()), limit(total_sectors_num / sectors_per_cluster),
408 root_dir_start(table_start + sec_buf[BPB_NumFATs] * table_size),
409 // NOTE: See Microsoft FAT Specification Section 3.5.
410 root_dir_sectors_num((read_uint_le<16>(sec_buf + BPB_RootEntCnt)
411 * EntryDataSize + (bytes_per_sector - 1)) / bytes_per_sector),
412 data_sectors_num(total_sectors_num - size_t(root_dir_start
413 + root_dir_sectors_num - start_sector)),
414 fs_type([start_sector, this]() ynothrowv -> FileSystemType{
415 // NOTE: See Microsoft FAT Specification Section 3.5.
416 ClusterIndex cluster_count(data_sectors_num / sectors_per_cluster);
417
418 last_cluster = cluster_count + Clusters::First - 1;
419 // TODO: Distinguish part type (e.g. FAT16B) with file system type.
420 if(cluster_count < Clusters::PerFAT12)
421 return FileSystemType::FAT12;
422 if(cluster_count < Clusters::PerFAT16)
423 return FileSystemType::FAT16;
424 return FileSystemType::FAT32;
425 }()), root_dir_cluster([sec_buf, this]() ynothrowv -> ClusterIndex{
426 if(fs_type != FileSystemType::FAT32)
427 return Clusters::FAT16RootDirectory;
428 // NOTE: Check if FAT mirroring is enabled.
429 if((sec_buf[BPB_ExtFlags] & 0x80) == 0)
430 // NOTE: Use the active FAT.
431 table_start += table_size * (sec_buf[BPB_ExtFlags] & 0x0F);
432 return read_uint_le<32>(sec_buf + BPB_RootClus);
433 }()), Cache(pages, sectors_per_page_shift, d,
434 start_sector + total_sectors_num, bytes_per_sector)
435 {}
436
437 bool
438 AllocationTable::ClearLinks(ClusterIndex c) ynothrow
439 {
440 if(IsValid(c))
441 {
442 if(c < first_free)
443 first_free = c;
444 while(IsValid(c))
445 {
446 WriteEntry(ystdex::exchange(c, QueryNext(c)), Clusters::Free);
447 if(free_count < limit)
448 ++free_count;
449 }
450 return true;
451 }
452 return {};
453 }
454
455 ClusterCount
456 AllocationTable::CountFreeCluster() const ynothrow
457 {
458 size_t count(0);
459
460 for(ClusterIndex cur_cluster(Clusters::First); cur_cluster <= last_cluster;
461 ++cur_cluster)
462 if(QueryNext(cur_cluster) == Clusters::Free)
463 ++count;
464 return count;
465 }
466
467 ClusterIndex
468 AllocationTable::LinkFree(ClusterIndex c) ythrow(system_error)
469 {
470 // TODO: Remove redundant check of Clusters::Error.
471 if(c <= last_cluster)
472 {
473 const auto cur_link(QueryNext(c));
474
475 if(IsValid(cur_link))
476 {
477 if(cur_link != Clusters::Error)
478 return cur_link;
479 else
480 throw_error(errc::no_space_on_device);
481 }
482
483 auto first_valid(std::max<ClusterIndex>(first_free, Clusters::First));
484 bool loop_around = {};
485
486 while(QueryNext(first_valid) != Clusters::Free)
487 if(last_cluster < ++first_valid)
488 {
489 if(loop_around)
490 {
491 first_free = first_valid;
492 throw_error(errc::no_space_on_device);
493 }
494 else
495 {
496 first_valid = Clusters::First;
497 loop_around = true;
498 }
499 }
500 first_free = first_valid;
501 if(free_count != 0)
502 --free_count;
503 last_alloc_cluster = first_valid;
504 if(IsValid(c))
505 WriteEntry(c, first_valid);
506 WriteEntry(first_valid, Clusters::EndOfFile);
507 if(IsValid(first_valid) && first_valid != Clusters::Error)
508 return first_valid;
509 }
510 throw_error(errc::no_space_on_device);
511 }
512
513 ClusterIndex
514 AllocationTable::QueryNext(ClusterIndex c) const ynothrowv
515 {
516 YAssert(IsFreeOrValid(c), "Invalid allocated cluster found.");
517
518 ClusterIndex next_cluster(Clusters::Free);
519
520 if(c != Clusters::Free)
521 switch(fs_type)
522 {
523 case FileSystemType::FAT12:
524 {
525 const auto c32(c * 3 / 2);
526 ::sec_t sector(table_start + c32 / bytes_per_sector);
527 size_t offset(c32 % bytes_per_sector);
528
529 ReadLEVal<8>(Cache, next_cluster, sector, offset);
530 if(++offset == bytes_per_sector)
531 {
532 offset = 0;
533 ++sector;
534 }
535
536 std::uint32_t next_cluster_h(0);
537
538 ReadLEVal<8>(Cache, next_cluster_h, sector, offset);
539 next_cluster |= next_cluster_h << 8;
540 }
541 if(c & 0x01)
542 next_cluster = next_cluster >> 4;
543 else
544 next_cluster &= 0x0FFF;
545 if(Clusters::MaxValid12 < next_cluster)
546 next_cluster = Clusters::EndOfFile;
547 break;
548 case FileSystemType::FAT16:
549 ReadLEVal<16>(Cache, next_cluster, table_start + (c << 1)
550 / bytes_per_sector, c % (bytes_per_sector >> 1) << 1);
551 if(Clusters::MaxValid16 < next_cluster)
552 next_cluster = Clusters::EndOfFile;
553 break;
554 case FileSystemType::FAT32:
555 ReadLEVal<32>(Cache, next_cluster, table_start + (c << 2)
556 / bytes_per_sector, c % (bytes_per_sector >> 2) << 2);
557 if(Clusters::MaxValid32 < next_cluster)
558 next_cluster = Clusters::EndOfFile;
559 break;
560 default:
561 return Clusters::Error;
562 }
563 return next_cluster;
564 }
565
566 ClusterIndex
567 AllocationTable::QueryLast(ClusterIndex c) const ynothrow
568 {
569 return ystdex::retry_on_cond([&, this]() ynothrow{
570 return IsValid(c);
571 }, [&]() ynothrow{
572 return ystdex::exchange(c, QueryNext(c));
573 });
574 }
575
576 bool
577 AllocationTable::SynchronizeClusters(byte* sec_buf) ynothrowv
578 {
579 free_count = read_uint_le<32>(sec_buf + FSI_Free_Count);
580 if(free_count == Clusters::UnknownFreeCount)
581 {
582 UpdateFSInfo(sec_buf);
583 return true;
584 }
585 else
586 last_alloc_cluster = read_uint_le<32>(sec_buf + FSI_Nxt_Free);
587 return {};
588 }
589
590 ClusterIndex
591 AllocationTable::TrimChain(ClusterIndex start, size_t len) ythrow(system_error)
592 {
593 auto next(QueryNext(start));
594
595 while(len-- != 0)
596 {
597 CheckThrowEIO(IsValid(next));
598 start = next;
599 next = QueryNext(start);
600 }
601 if(IsValid(next))
602 ClearLinks(next);
603 WriteEntry(start, Clusters::EndOfFile);
604 return start;
605 }
606
607 ClusterIndex
608 AllocationTable::LinkFreeCleared(ClusterIndex c) ythrow(system_error)
609 {
610 const auto t(LinkFree(c));
611
612 YAssert(t != Clusters::Error, "Invalid cluster found.");
613 for(size_t i(0); i < sectors_per_cluster; ++i)
614 CheckThrowEIO(Cache.FillSectors(ClusterToSector(t) + i));
615 return t;
616 }
617
618 void
619 AllocationTable::UpdateFSInfo(byte* sec_buf) ynothrowv
620 {
621 YAssertNonnull(sec_buf);
622 free_count = CountFreeCluster();
623 WriteClusters(sec_buf);
624 }
625
626 void
627 AllocationTable::WriteClusters(byte* sec_buf) const ynothrowv
628 {
629 write_uint_le<32>(sec_buf + FSI_Free_Count, free_count),
630 write_uint_le<32>(sec_buf + FSI_Nxt_Free, last_alloc_cluster);
631 }
632
633 bool
634 AllocationTable::WriteEntry(ClusterIndex c, std::uint32_t val) const ynothrow
635 {
636 if(IsValid(c))
637 {
638 switch(fs_type)
639 {
640 case FileSystemType::FAT12:
641 {
642 const auto c32(c * 3 / 2);
643 ::sec_t sector(table_start + (c32 / bytes_per_sector));
644 size_t offset(c32 % bytes_per_sector);
645 std::uint32_t old_val;
646
647 if((c & 0x01) != 0)
648 {
649 ReadLEVal<8>(Cache, old_val, sector, offset);
650 val = val << 4 | (old_val & 0x0F);
651 WriteLEVal<8>(Cache, byte(val), sector, offset);
652 if(++offset >= bytes_per_sector)
653 yunseq(offset = 0, ++sector);
654 WriteLEVal<8>(Cache, byte(val >> 8), sector, offset);
655 }
656 else
657 {
658 WriteLEVal<8>(Cache, val, sector, offset);
659 if(++offset >= bytes_per_sector)
660 yunseq(offset = 0, ++sector);
661 ReadLEVal<8>(Cache, old_val, sector, offset);
662 val = ((val >> 8) & 0x0F) | (old_val & 0xF0);
663 WriteLEVal<8>(Cache, val, sector, offset);
664 }
665 }
666 break;
667 case FileSystemType::FAT16:
668 WriteLEVal<16>(Cache, val, table_start + (c << 1)
669 / bytes_per_sector, (c % (bytes_per_sector >> 1)) << 1);
670 break;
671 case FileSystemType::FAT32:
672 WriteLEVal<32>(Cache, val, table_start + (c << 2)
673 / bytes_per_sector, (c % (bytes_per_sector >> 2)) << 2);
674 break;
675 default:
676 return {};
677 }
678 return true;
679 }
680 return {};
681 }
682
683
684 void
685 FilePosition::Decompose(std::uint32_t pos, size_t bytes_per_cluster,
686 size_t bytes_per_sector) ynothrowv
687 {
688 YAssert(bytes_per_cluster != 0, "Invalid parameter found."),
689 YAssert(bytes_per_sector != 0, "Invalid parameter found.");
690 yunseq(sector = pos % bytes_per_cluster / bytes_per_sector,
691 byte = pos % bytes_per_sector);
692 }
693
694
696 namespace
697 {
698
699 YB_ATTR_nodiscard ClusterIndex
700 ReadClusterFromEntry_FAT12OrFAT16(const AllocationTable& table,
701 const EntryData& data) ythrow(system_error)
702 {
703 const auto dst(data.data());
704 ClusterIndex c(read_uint_le<16>(dst + EntryData::Cluster));
705
706 CheckThrowEIO(table.IsFreeOrValid(c));
707 return c;
708 }
709
710 YB_ATTR_nodiscard ClusterIndex
711 ReadClusterFromEntry_FAT32(const AllocationTable& table,
712 const EntryData& data) ythrow(system_error)
713 {
714 const auto dst(data.data());
715 // NOTE: High 16 bits of start cluster are only used when they are certainly
716 // correctly defined.
717 ClusterIndex c(read_uint_le<16>(dst + EntryData::Cluster)
718 | read_uint_le<16>(dst + EntryData::ClusterHigh) << 16);
719
720 CheckThrowEIO(table.IsFreeOrValid(c));
721 return c;
722 }
723
724 } // unnamed namespace;
725
726
727 DEntry::DEntry(const Partition& part)
728 : NamePos{DEntryPosition{0}, DEntryPosition{0}}, name(".")
729 {
730 // TODO: Optimize?
731 Data.SetupRoot(part.GetRootDirCluster());
732 }
733 DEntry::DEntry(Partition& part, const NamePosition& name_pos)
734 : NamePos(name_pos), name([&]{
735 auto pos(name_pos[0]);
736 char16_t long_name[LFN::MaxLength];
737
738 ystdex::retry_on_cond([&](bool finished){
739 part.ExtendPosition(pos);
740 return !finished;
741 }, [&]() -> bool{
742 EntryData edata;
743
744 CheckReadPartialSector(part.Table, pos, edata.data());
745 if(pos == name_pos[1])
746 return Data = edata, true;
747 else
748 edata.CopyLFN(long_name);
749 return {};
750 });
751 return name_pos[0] == name_pos[1] ? Data.GenerateAlias()
752 : LFN::ConvertToMBCS(long_name);
753 }())
754 {}
755 DEntry::DEntry(Partition& part, string_view sv, LeafAction act,
756 ClusterIndex& dclus)
757 {
758 if(IsSimpleAbsolute(sv))
759 {
760 const auto pos(sv.find_first_not_of(FetchSeparator<char>()));
761
762 if(pos == string_view::npos)
763 goto found_root;
764 sv.remove_prefix(pos),
765 dclus = part.GetRootDirCluster();
766 }
767 else
768 dclus = part.GetCWDCluster();
769 ystdex::retry_on_cond(ystdex::id<>(), [&]() -> bool{
770 const auto next_pos(sv.find(FetchSeparator<char>()));
771
772 if(next_pos == string_view::npos)
773 {
774 ConsLeafNoCallback(part, sv, act, dclus);
775 return {};
776 }
777 return ConsBranch(part, sv, next_pos, dclus);
778 });
779 if(IsFAT32Root(part))
780 {
781 found_root:
782 ConsReset(part);
783 if(act == LeafAction::EnsureDirectory)
784 dclus = part.GetRootDirCluster();
785 }
786 }
787 DEntry::DEntry(Partition& part, string_view sv, LeafAction act,
788 ystdex::function<void(DEntry&)> add_entry, ClusterIndex& dclus)
789 {
790 YAssertNonnull(add_entry);
791 YAssert(act != LeafAction::EnsureDirectory, "Unsupported action found.");
792 if(IsSimpleAbsolute(sv))
793 {
794 const auto pos(sv.find_first_not_of(FetchSeparator<char>()));
795
796 if(pos == string_view::npos)
797 throw_error(errc::invalid_argument);
798 sv.remove_prefix(pos),
799 dclus = part.GetRootDirCluster();
800 }
801 else
802 dclus = part.GetCWDCluster();
803 ystdex::retry_on_cond(ystdex::id<>(), [&]() -> bool{
804 const auto next_pos(sv.find(FetchSeparator<char>()));
805
806 if(next_pos == string_view::npos)
807 {
808 ConsLeafWithCallback(part, sv, act, dclus);
809 return {};
810 }
811 ConsBranch(part, sv, next_pos, dclus);
812 });
813 // XXX: For a file system with external link to other file systems, the
814 // order is significant: this check cannot be moved earlier. However, this
815 // is not the case in file systems not supporting symbolic links like FAT.
816 // Just keep it now.
817 if(part.IsReadOnly())
818 throw_error(errc::read_only_file_system);
819 # if false
820 ystdex::ltrim(name, ' ');
821 # endif
822 if(YB_UNLIKELY(LFN::MaxMBCSLength < name.length()))
823 throw_error(errc::filename_too_long);
824 if(name.empty()
825 || name.find_first_of(LFN::IllegalCharacters) != string::npos)
826 throw_error(errc::invalid_argument);
827 add_entry(*this);
828
829 const auto name_len(name.length());
830
831 yunused(name_len);
832 YAssert(name_len == ystdex::ntctslen(name.c_str())
833 && !(LFN::MaxMBCSLength < name_len) && name_len != 0
834 && name.find_first_of(LFN::IllegalCharacters)
835 == string::npos, "Invalid name found.");
836 if(YB_UNLIKELY(!LFN::ValidateName(name)))
837 throw_error(errc::invalid_argument);
838 if(!part.EntryExists(name, dclus))
839 {
840 EntryDataUnit alias_check_sum(0);
841 size_t entry_size;
842
843 std::tie(alias_check_sum, entry_size) = Data.FillNewName(name,
844 std::bind(&Partition::EntryExists, &part, std::placeholders::_1,
845 std::ref(dclus)));
846 FindEntryGap(part, dclus, entry_size);
847 YAssert(entry_size != 0, "Invalid entry size found.");
848
849 auto cur_entry_pos(NamePos[0]);
850 EntryData long_name_entry;
851 const auto& long_name(MakeUCS2LE(name));
852 size_t i(entry_size);
853
854 ystdex::retry_on_cond([&](bool cont) ynothrow{
855 return part.IncrementPosition(cur_entry_pos)
856 == ExtensionResult::Success && cont;
857 }, [&]() -> bool{
858 if(i > 1)
859 {
860 long_name_entry[LFN::Ordinal] = (i - 1)
861 | (i == entry_size ? LFN::LastLongEntry : 0);
862 for(size_t j(0); j < LFN::EntryLength; ++j)
863 // NOTE: Padding vs terminating null character.
864 write_uint_le<16>(long_name_entry.data()
865 + LFN::OffsetTable[j], long_name[(i - 2)
866 * LFN::EntryLength + j] == char16_t() ? (j > 1
867 && long_name[(i - 2) * LFN::EntryLength + j - 1]
868 == char16_t() ? 0xFFFF : 0x0000)
869 : long_name[(i - 2) * LFN::EntryLength + j]);
870 yunseq(
871 long_name_entry[LFN::CheckSum] = alias_check_sum,
872 long_name_entry[LFN::Flag]
873 = EntryDataUnit(Attribute::LongFileName),
874 long_name_entry[LFN::Reserved1] = 0
875 );
876 write_uint_le<16>(
877 long_name_entry.data() + LFN::Reserved2, 0);
878 CheckWritePartialSector(part.Table, cur_entry_pos,
879 long_name_entry.data());
880 --i;
881 return true;
882 }
883 else
884 CheckWritePartialSector(part.Table, cur_entry_pos,
885 Data.data());
886 return {};
887 });
888 return;
889 }
890 else
891 throw_error(errc::file_exists);
892 if(IsFAT32Root(part))
893 throw_error(errc::invalid_argument);
894 }
895
896 bool
897 DEntry::IsFAT32Root(const Partition& part) const
898 {
899 // NOTE: On FAT32 an actual cluster should be specified for the root
900 // entry, not cluster 0 as on FAT16.
901 return part.GetFileSystemType() == FileSystemType::FAT32
902 && Data.IsDirectory() && ReadClusterFromEntry_FAT32(part.Table, Data)
903 == Clusters::Root;
904 }
905
906 bool
907 DEntry::ConsBranch(Partition& part, string_view& sv, size_t next_pos,
908 ClusterIndex& dclus)
909 {
910 YAssertNonnull(sv.data());
911 string_view comp(sv.data(), next_pos);
912
913 if(LFN::MaxMBCSLength < comp.length())
914 throw_error(errc::filename_too_long);
915 if(dclus == part.GetRootDirCluster() && (comp == "." || comp == ".."))
916 ConsReset(part);
917 else
918 {
919 name = string(ystdex::rtrim(comp, ' '));
920 NamePos = GenerateBeforeFirstNamePos(dclus);
921
922 string comp_name;
923
924 ystdex::retry_on_cond(ystdex::id<>(),
925 [&, this]() -> bool{
926 if(!QueryNextFrom(part))
927 throw_error(errc::no_such_file_or_directory);
928 return !(Data.IsDirectory() && CompareEntryNames(Data, comp, name));
929 });
930 }
931 ConsCheckDirectory(part, dclus);
932 sv.remove_prefix(next_pos);
933 sv.remove_prefix(std::min(
934 sv.find_first_not_of(FetchSeparator<char>()), sv.length()));
935 return !sv.empty();
936 }
937
938 void
939 DEntry::ConsCheckDirectory(const Partition& part, ClusterIndex& dclus)
940 {
941 if(Data.IsDirectory())
942 {
943 dclus = part.ReadClusterFromEntry(part.Table, Data);
944 if(dclus == Clusters::Root)
945 dclus = part.GetRootDirCluster();
946 }
947 else
948 throw_error(errc::not_a_directory);
949 }
950
951 void
952 DEntry::ConsLeafNoCallback(Partition& part, string_view comp, LeafAction act,
953 ClusterIndex& dclus)
954 {
955 YAssertNonnull(comp.data());
956
957 if(LFN::MaxMBCSLength < comp.length())
958 throw_error(errc::filename_too_long);
959 if(dclus == part.GetRootDirCluster() && (comp == "." || comp == ".."))
960 ConsReset(part);
961 else
962 {
963 name = string(ystdex::rtrim(comp, ' '));
964 NamePos = GenerateBeforeFirstNamePos(dclus);
965
966 string comp_name;
967
968 ystdex::retry_on_cond(ystdex::id<>(),
969 [&, this]() -> bool{
970 if(!QueryNextFrom(part))
971 throw_error(errc::no_such_file_or_directory);
972 return !CompareEntryNames(Data, comp, name);
973 });
974 }
975 if(act == LeafAction::ThrowExisted)
976 throw_error(errc::file_exists);
977 if(act != LeafAction::Return)
978 ConsCheckDirectory(part, dclus);
979 }
980
981 void
982 DEntry::ConsLeafWithCallback(Partition& part, string_view comp, LeafAction act,
983 ClusterIndex& dclus)
984 {
985 YAssertNonnull(comp.data());
986 YAssert(act != LeafAction::EnsureDirectory, "Unsupported action found.");
987
988 if(LFN::MaxMBCSLength < comp.length())
989 throw_error(errc::filename_too_long);
990 if(dclus == part.GetRootDirCluster() && (comp == "." || comp == ".."))
991 throw_error(errc::invalid_argument);
992 name = string(ystdex::rtrim(comp, ' '));
993 // NOTE: The check is not performed here. Let it go into the callback.
994 if(act == LeafAction::ThrowExisted)
995 {
996 NamePos = GenerateBeforeFirstNamePos(dclus);
997 string comp_name(name);
998
999 ystdex::retry_on_cond(ystdex::id<>(),
1000 [&, this]() -> bool{
1001 if(!QueryNextFrom(part))
1002 {
1003 name = comp_name;
1004 return {};
1005 }
1006 return !CompareEntryNames(Data, comp, name);
1007 });
1008 }
1009 }
1010
1011 void
1012 DEntry::FindEntryGap(Partition& part, ClusterIndex dclus, size_t size)
1013 ythrow(system_error)
1014 {
1015 // NOTE: Scan for free entry.
1016 DEntryPosition gap_end(dclus), gap_start(gap_end);
1017 bool end_of_dir{};
1018 auto dentry_remain(size);
1019 EntryData edata;
1020
1021 if(size != 0)
1022 while(true)
1023 {
1024 CheckReadPartialSector(part.Table, gap_end, edata.data());
1025 if(edata[0] == EntryData::Last || edata[0] == EntryData::Free)
1026 {
1027 if(dentry_remain == size)
1028 gap_start = gap_end;
1029 --dentry_remain;
1030 end_of_dir = edata[0] == EntryData::Last;
1031 }
1032 else
1033 dentry_remain = size;
1034 if(!end_of_dir && dentry_remain > 0)
1035 part.ExtendPosition(gap_end);
1036 else
1037 break;
1038 }
1039 NamePos[0] = gap_start;
1040 if(end_of_dir)
1041 {
1042 edata.FillLast();
1043 // NOTE: Increase by one to take account of End Of Directory Marker.
1044 ++dentry_remain;
1045 while(dentry_remain > 0)
1046 {
1047 NamePos[1] = gap_end;
1048 part.ExtendPosition(gap_end);
1049 // NOTE: Fill the entry with blanks.
1050 CheckWritePartialSector(part.Table, gap_end, edata.data());
1051 --dentry_remain;
1052 }
1053 }
1054 else
1055 NamePos[1] = gap_end;
1056 }
1057
1058 bool
1059 DEntry::QueryNextFrom(Partition& part) ythrow(system_error)
1060 {
1061 auto estart(NamePos[1]);
1062
1063 if(estart.IsFAT16RootCluster())
1064 estart.SetCluster(part.GetRootDirCluster());
1065
1066 auto eend(estart);
1067 EntryData edata;
1068 char16_t long_name[LFN::MaxLength];
1069 bool has_long_name = {};
1070 EntryDataUnit chk_sum = 0;
1071
1072 while(part.IncrementPosition(eend) == ExtensionResult::Success)
1073 {
1074 CheckReadPartialSector(part.Table, eend, edata.data());
1075 if(edata.IsLongFileName())
1076 {
1077 const auto ord(edata[LFN::Ordinal]);
1078
1079 // NOTE: See Microsoft FAT Specification Section 7.1.
1080 if((ord & 0x80) != 0)
1081 // NOTE: Corrupted.
1082 has_long_name = {};
1083 else if((ord & LFN::LastLongEntry) != 0)
1084 yunseq(
1085 estart = eend, has_long_name = true,
1086 long_name[std::min<size_t>((ord & ~LFN::LastLongEntry)
1087 * LFN::EntryLength, LFN::MaxLength - 1)] = char16_t(),
1088 chk_sum = edata[LFN::CheckSum]
1089 );
1090 else if(chk_sum != edata[LFN::CheckSum])
1091 has_long_name = {};
1092 if(has_long_name)
1093 edata.CopyLFN(long_name);
1094 }
1095 else if(edata.IsVolume())
1096 ;
1097 else if(edata[0] == EntryData::Last)
1098 break;
1099 else if(edata[0] != EntryData::Free && edata[0] > 0x20)
1100 {
1101 if(has_long_name
1102 && LFN::GenerateAliasChecksum(edata.data()) != chk_sum)
1103 {
1104 has_long_name = {};
1105 name.clear();
1106 }
1107 if(has_long_name)
1108 TryExpr(name = LFN::ConvertToMBCS(long_name))
1109 CatchRet(..., {})
1110 else
1111 {
1112 estart = eend;
1113 TryExpr(name = edata.GenerateAlias())
1114 CatchExpr(..., name.clear())
1115 }
1116 yunseq(NamePos[0] = estart, NamePos[1] = eend, Data = edata);
1117 return true;
1118 }
1119 }
1120 return {};
1121 }
1122
1123
1124 Partition::Partition(Disc d, size_t pages, size_t sectors_per_page_shift,
1125 ::sec_t start_sector)
1126 // TODO: Use aligned allocation.
1127 // NOTE: Uninitialized intentionally here to make it behave as LibFAT.
1128 // TODO: Pool allocation? Use temporary buffer?
1129 : Partition(make_unique_default_init<byte[]>(MaxSectorSize).get(), d, pages,
1130 sectors_per_page_shift, start_sector)
1131 {}
1132 Partition::Partition(byte* sec_buf, Disc d, size_t pages,
1133 size_t sectors_per_page_shift, ::sec_t start_sector)
1134 : disc(d), Table([&, sec_buf]{
1135 // NOTE: LibFAT uses uncached reading here.
1136 // NOTE: LibFAT ignores the error and return sector number as 0 here.
1137 // NOTE: Different to LibFAT 1.1.2, there is no need to read TWL
1138 // partitions in DSi NAND, so unnecessary restrictions of TWL
1139 // signatures are avoided. Thus, %FAT::CheckValidMBR is used directly.
1140 // This is also conforming to Microsoft FAT specification.
1141 try
1142 {
1143 CheckThrowEIO(ReadSector(start_sector, sec_buf));
1144 if(sec_buf[Signature_word] != 0x55
1145 || sec_buf[Signature_word + 1] != 0xAA)
1146 throw std::runtime_error("Invalid MBR or boot sector found in"
1147 " start sector specified for partition.");
1148 if(start_sector == 0 && !CheckValidMBR(sec_buf))
1149 {
1150 start_sector = FindFirstValidPartition(sec_buf);
1151 CheckThrowEIO(ReadSector(start_sector, sec_buf));
1152 }
1153 if(!CheckValidMBR(sec_buf))
1154 throw std::runtime_error("Failed verifying FAT partition.");
1155 }
1156 catch(...)
1157 {
1158 std::throw_with_nested(
1159 std::runtime_error("Failed reading partition."));
1160 }
1161 GetMutexRef().lock();
1162 ystdex::ntctsncpy(label.data(), sec_buf + (MatchFATSignature(sec_buf
1163 + FAT16::BS_FilSysType) ? size_t(FAT16::BS_VolLab)
1164 : size_t(FAT32::BS_VolLab)), label.size());
1165 return start_sector;
1166 }(), sec_buf, pages, sectors_per_page_shift, d),
1167 ReadClusterFromEntry(GetFileSystemType() == FileSystemType::FAT32
1168 ? ReadClusterFromEntry_FAT32 : ReadClusterFromEntry_FAT12OrFAT16),
1169 fs_info_sector(start_sector + (read_uint_le<16>(sec_buf + BPB_FSInfo)
1170 ? read_uint_le<16>(sec_buf + BPB_FSInfo) : 1)),
1171 read_only((disc.GetFeatures() & FEATURE_MEDIUM_CANWRITE) == 0),
1172 cwd_cluster(GetRootDirCluster())
1173 {
1174 if(GetFileSystemType() == FileSystemType::FAT32)
1175 ReadFSInfo();
1176 GetMutexRef().unlock();
1177 }
1178 Partition::~Partition()
1179 {
1180 YSLib::FilterExceptions([this]{
1181 lock_guard<mutex> lck(GetMutexRef());
1182
1183 for(const auto& file : open_files)
1184 // XXX: Flush after exceptions occurred?
1185 TryExpr(file.get().SyncToDisc())
1186 CatchExpr(system_error&, YTraceDe(Err,
1187 "Synchronization of file failed @ Partition::~Partition."))
1188 if(GetFileSystemType() == FileSystemType::FAT32)
1189 WriteFSInfo();
1190 }, yfsig, Warning);
1191 }
1192
1193 void
1194 Partition::CheckPositionForNextCluster(FilePosition& pos) ythrow(system_error)
1195 {
1196 const auto s(pos.GetSector());
1197 const auto spc(GetSectorsPerCluster());
1198
1199 if(YB_UNLIKELY(spc < s))
1200 // XXX: Internal error.
1201 throw_error(errc::invalid_argument);
1202
1203 const auto c(pos.GetCluster());
1204
1205 if(YB_UNLIKELY(!Table.IsFreeOrValid(c)))
1206 // XXX: Internal error.
1207 throw_error(errc::invalid_argument);
1208 if(s == spc)
1209 {
1210 auto t(Table.QueryNext(c));
1211
1212 pos = {Clusters::IsFreeOrEOF(t)
1213 ? Table.LinkFree(pos.GetCluster()) : t, 0, pos.GetByte()};
1214 }
1215 }
1216
1217 void
1218 Partition::ChangeDir(string_view path) ythrow(system_error)
1219 {
1220 DEntry(*this, path, LeafAction::EnsureDirectory, cwd_cluster);
1221 }
1222
1223 void
1224 Partition::CreateFSInfo()
1225 {
1226 YAssert(GetFileSystemType() == FileSystemType::FAT32,
1227 "Only FAT32 is supported.");
1228 if(!read_only)
1229 {
1230 // TODO: Use aligned allocation.
1231 auto p_sector_buf(make_unique<byte[]>(GetBytesPerSector()));
1232 const auto sec_buf(p_sector_buf.get());
1233
1234 write_uint_le<32>(sec_buf + FSI_LeadSig, FSI_LeadSig_Value),
1235 write_uint_le<32>(sec_buf + FSI_StrucSig, FSI_StrucSig_Value),
1236 write_uint_le<32>(sec_buf + FSI_TrailSig, FSI_TrailSig_Value),
1237 Table.UpdateFSInfo(sec_buf);
1238 }
1239 }
1240
1241 bool
1242 Partition::EntryExists(string_view comp, ClusterIndex dclus)
1243 ythrow(system_error)
1244 {
1245 if(comp.length() < LFN::MaxMBCSLength)
1246 {
1247 DEntry entry(dclus);
1248
1249 while(entry.QueryNextFrom(*this))
1250 if(CompareEntryNames(entry.Data, comp, entry.GetName()))
1251 return true;
1252 }
1253 return {};
1254 }
1255
1256 void
1257 Partition::ExtendPosition(DEntryPosition& entry_pos) ythrow(system_error)
1258 {
1259 switch(IncrementPosition(entry_pos))
1260 {
1261 case ExtensionResult::NoSpace:
1262 throw_error(errc::no_space_on_device);
1263 case ExtensionResult::EndOfFile:
1264 entry_pos.SetCluster(
1265 Table.LinkFreeCleared(entry_pos.GetCluster()));
1266 default:
1267 break;
1268 }
1269 }
1270
1271 ::sec_t
1272 Partition::FindFirstValidPartition(byte* sec_buf) const
1273 ythrow(std::system_error)
1274 {
1275 CheckThrowEIO(ReadSector(0, sec_buf));
1276
1277 byte part_table[16 * 4];
1278 byte* ptr(part_table);
1279
1280 // TODO: Resolve magic numbers.
1281 ystdex::trivially_copy_n(sec_buf + 0x1BE, sizeof(part_table),
1282 part_table);
1283 for(size_t i(0); i < 4; yunseq(++i, ptr += 16))
1284 {
1285 const ::sec_t part_lba(read_uint_le<32>(ptr + 0x8));
1286
1287 // NOTE: For MBR vaildity, see the comments in the constructor above.
1288 if(CheckValidMBR(sec_buf))
1289 return part_lba;
1290 if(ptr[4] == 0)
1291 continue;
1292 if(ptr[4] == 0x0F)
1293 {
1294 ::sec_t part_lba2(part_lba), next_lba2(0);
1295
1296 // NOTE: Up to 8 logic partitions.
1297 for(size_t n(0); n < 8; ++n)
1298 {
1299 CheckThrowEIO(ReadSector(part_lba + next_lba2, sec_buf));
1300 // TODO: Resolve magic numbers.
1301 part_lba2 = part_lba + next_lba2
1302 + read_uint_le<32>(sec_buf + 0x1C6);
1303 next_lba2 = read_uint_le<32>(sec_buf + 0x1D6);
1304 CheckThrowEIO(ReadSector(part_lba2, sec_buf));
1305 if(CheckValidMBR(sec_buf))
1306 return part_lba2;
1307 if(next_lba2 == 0)
1308 break;
1309 }
1310 }
1311 else
1312 {
1313 CheckThrowEIO(ReadSector(part_lba, sec_buf));
1314 if(CheckValidMBR(sec_buf))
1315 return part_lba;
1316 }
1317 }
1318 return 0;
1319 }
1320
1321 ExtensionResult
1322 Partition::IncrementPosition(DEntryPosition& entry_pos) ynothrowv
1323 {
1324 // TODO: Reevaluate if it is the wrong on writing.
1325 YAssert(Table.IsFreeOrValid(entry_pos.GetCluster()),
1326 "Invalid allocated cluster found.");
1327
1328 auto pos(entry_pos);
1329
1330 if(pos.IncOffset() == GetBytesPerSector() / EntryDataSize)
1331 {
1332 pos.IncSectorAndResetOffset();
1333
1334 const auto s(pos.GetSector());
1335 const bool root(pos.IsFAT16RootCluster());
1336
1337 if(!root && s == GetSectorsPerCluster())
1338 {
1339 pos.SetSector(0);
1340
1341 const auto c(Table.QueryNext(pos.GetCluster()));
1342
1343 // NOTE: Cover 'c == Clusters::EndOfFile'.
1344 if(!Table.IsValid(c))
1345 return ExtensionResult::EndOfFile;
1346 pos.SetCluster(c);
1347 }
1348 else if(root && s == Table.GetRootDirSectorsNum())
1349 return ExtensionResult::NoSpace;
1350 }
1351 entry_pos = pos;
1352 return ExtensionResult::Success;
1353 }
1354
1355 void
1356 Partition::MakeDir(string_view path) ythrow(system_error)
1357 {
1358 ClusterIndex parent_clus, dclus;
1359
1360 DEntry(*this, path, LeafAction::Return,
1361 [&](DEntry& de){
1362 de.Data.Clear();
1363 de.Data.SetDirectoryAttribute();
1364 de.Data.WriteDateTime();
1365 dclus = Table.LinkFreeCleared(Clusters::Free);
1366 de.Data.WriteCluster(dclus);
1367 // XXX: Only %ENOSPC may be expected.
1368 }, parent_clus);
1369
1370 // NOTE: Create the dot entry within the directory.
1371 EntryData edata;
1372 auto& cache(GetCacheRef());
1373 const auto dir_sector(Table.ClusterToSector(dclus));
1374
1375 edata.SetupRoot(dclus);
1376 // XXX: POSIX does not require %EIO here.
1377 CheckThrowENOSPC(cache.EraseWritePartialSector(dir_sector, 0,
1378 edata.data(), EntryDataSize));
1379 // NOTE: Create the double dot entry within the directory.
1380 edata.SetDot(EntryData::Name + 1);
1381 // NOTE: if parent directory is root then ".." always link to
1382 // Cluster 0.
1383 edata.WriteCluster(parent_clus == GetRootDirCluster()
1384 ? ClusterIndex(Clusters::FAT16RootDirectory) : parent_clus);
1385 CheckThrowENOSPC(cache.WritePartialSector(dir_sector, EntryDataSize,
1386 edata.data(), EntryDataSize));
1387 Flush();
1388 }
1389
1390 void
1391 Partition::ReadFSInfo()
1392 {
1393 YAssert(GetFileSystemType() == FileSystemType::FAT32,
1394 "Only FAT32 supported.");
1395
1396 // TODO: Use aligned allocation.
1397 auto p_sector_buf(make_unique<byte[]>(GetBytesPerSector()));
1398 const auto sec_buf(p_sector_buf.get());
1399
1400 if(ReadFSInfoSector(sec_buf))
1401 {
1402 if(read_uint_le<32>(sec_buf + FSI_LeadSig) != FSI_LeadSig_Value
1403 || read_uint_le<32>(sec_buf + FSI_StrucSig) != FSI_StrucSig_Value
1404 || read_uint_le<32>(sec_buf + FSI_Free_Count) == 0)
1405 CreateFSInfo();
1406 else if(Table.SynchronizeClusters(sec_buf))
1407 {
1408 if(YB_UNLIKELY(!disc.WriteSectors(fs_info_sector, 1, sec_buf)))
1409 YTraceDe(Err, "Writing sectors failed on creating file system"
1410 " information.");
1411 }
1412 }
1413 }
1414
1415 void
1416 Partition::Rename(string_view old, string_view new_name) ythrow(system_error)
1417 {
1418 YAssertNonnull(new_name.data());
1419 if(!new_name.empty())
1420 {
1421 if(FetchPartitionFromPath(new_name.data()) == this)
1422 {
1423 // FIXME: %errc::invalid_argument: The old pathname names an
1424 // ancestor directory of the new pathname.
1425 DEntry old_dir_entry(*this, old);
1426
1427 DEntry(*this, new_name, LeafAction::Return,
1428 [&](DEntry& de) ynothrow{
1429 yunseq(de.Data = old_dir_entry.Data,
1430 de.NamePos = old_dir_entry.NamePos);
1431 // XXX: Only %ENOSPC may be expected?
1432 });
1433 // FIXME: Exception safety.
1434 RemoveEntry(old_dir_entry.NamePos);
1435 }
1436 else
1437 throw_error(errc::cross_device_link);
1438 }
1439 else
1440 throw_error(errc::no_such_file_or_directory);
1441 }
1442
1443 void
1444 Partition::StatFS(struct ::statvfs& st)
1445 {
1446 if(GetFileSystemType() == FileSystemType::FAT32)
1447 ReadFSInfo();
1448
1449 const auto free_count(Table.GetFreeClusters());
1450
1451 yunseq(
1452 // NOTE: FAT clusters = POSIX blocks.
1453 st.f_bsize = GetBytesPerCluster(),
1454 st.f_frsize = GetBytesPerCluster(),
1455 st.f_blocks = Table.GetUsedClusters(),
1456 st.f_bfree = free_count,
1457 st.f_bavail = free_count,
1458 // NOTE: Treat requests for info on inodes as clusters.
1459 st.f_files = Table.GetUsedClusters(),
1460 st.f_ffree = free_count,
1461 st.f_favail = free_count,
1462 st.f_fsid = disc.GetType(),
1463 // NOTE: No support for %ST_ISUID and %ST_ISGID.
1464 st.f_flag = ST_NOSUID | (read_only ? ST_RDONLY : 0),
1465 st.f_namemax = LFN::MaxMBCSLength
1466 );
1467 }
1468
1469 void
1470 Partition::RemoveEntry(const DEntry::NamePosition& np) ythrow(system_error)
1471 {
1472 // NOTE: Create an empty directory entry to overwrite the old ones.
1473 auto name_pos(np);
1474 EntryData edata;
1475
1476 ystdex::retry_on_cond([&]() ynothrow{
1477 return !(name_pos[0] == name_pos[1]);
1478 }, [&]{
1479 CheckReadPartialSector(Table, name_pos[0], edata.data());
1480 edata[0] = EntryData::Free;
1481 CheckWritePartialSector(Table, name_pos[0], edata.data());
1482 CheckThrowEIO(
1483 IncrementPosition(name_pos[0]) == ExtensionResult::Success);
1484 });
1485 Flush();
1486 }
1487
1488 void
1489 Partition::StatFromEntry(const EntryData& data, struct ::stat& st) const
1490 ythrow(system_error)
1491 {
1492 const auto dst(data.data());
1493
1494 st.st_ino = ::ino_t(ReadClusterFromEntry(Table, data));
1495 yunseq(
1496 st.st_dev = disc.GetType(),
1497 // NOTE: The file serial number is the start cluster.
1498 st.st_mode = mode_t(data.IsDirectory() ? Mode::Directory : Mode::Regular)
1499 | mode_t(Mode::Read) | (data.IsWritable() ? mode_t(Mode::Write)
1500 : mode_t()),
1501 // NOTE: Always one hard link on a FAT file.
1502 st.st_nlink = 1,
1503 // NOTE: Faked for FAT.
1504 st.st_uid = 1, st.st_gid = 2,
1505 st.st_size = read_uint_le<32>(dst + EntryData::FileSize),
1506 st.st_atime = ConvertFileTime(read_uint_le<16>(dst + EntryData::ADate), 0),
1507 st.st_mtime = ConvertFileTime(read_uint_le<16>(
1508 dst + EntryData::MDate), read_uint_le<16>(dst + EntryData::MTime)),
1509 st.st_ctime = ConvertFileTime(read_uint_le<16>(
1510 dst + EntryData::CDate), read_uint_le<16>(dst + EntryData::CTime)),
1511 // XXX: See https://github.com/devkitPro/newlib/commit/72ff9acad2ab54e80a19ddaec0106065c817e3f6
1512 // and https://sourceware.org/legacy-ml/newlib/2019/msg00490.html.
1513 #if __NEWLIB__ * 100 + __NEWLIB_MINOR__ > 301
1514 // XXX: This is now more accruate than libfat. See also https://github.com/devkitPro/libfat/issues/19.
1515 st.st_atim.tv_nsec = 0, st.st_mtim.tv_nsec = 0, st.st_ctim.tv_nsec = 0,
1516 #endif
1517 st.st_blksize = GetBytesPerSector()
1518 );
1519
1520 const auto bps(GetBytesPerSector());
1521
1522 yunseq(st.st_rdev = st.st_dev, st.st_blocks = (st.st_size + bps - 1) / bps);
1523 }
1524
1525 void
1526 Partition::Unlink(string_view path) ythrow(system_error)
1527 {
1528 if(!read_only)
1529 {
1530 DEntry dentry(*this, path);
1531 const auto c(ReadClusterFromEntry(Table, dentry.Data));
1532
1533 if(dentry.Data.IsDirectory())
1534 {
1535 DEntry dir_contents(c);
1536
1537 while(dir_contents.QueryNextFrom(*this))
1538 if(!dir_contents.IsDot())
1539 // XXX: Currently there is no %::unlinkat implementation
1540 // in newlib. See also https://github.com/devkitPro/libfat/issues/10.
1541 throw_error(errc::operation_not_permitted);
1542 }
1543 if(Table.ClearLinks(c))
1544 {
1545 // FIXME: Exception safety.
1546 RemoveEntry(dentry.NamePos);
1547 return;
1548 }
1549 throw_error(errc::io_error);
1550 }
1551 throw_error(errc::read_only_file_system);
1552 }
1553
1554 void
1555 Partition::WriteFSInfo() const
1556 {
1557 YAssert(GetFileSystemType() == FileSystemType::FAT32,
1558 "Only FAT32 supported.");
1559
1560 // TODO: Use aligned allocation.
1561 auto p_sector_buf(make_unique<byte[]>(GetBytesPerSector()));
1562 const auto sec_buf(p_sector_buf.get());
1563
1564 if(!ReadFSInfoSector(sec_buf))
1565 throw std::runtime_error(
1566 "Failed reading first sector on writing file system information.");
1567 // TODO: Support big endian implementations.
1568 if(read_uint_le<32>(sec_buf + FSI_LeadSig) != FSI_LeadSig_Value
1569 || read_uint_le<32>(sec_buf + FSI_StrucSig) != FSI_StrucSig_Value)
1570 throw std::runtime_error("Failed validating FSInfo signature on"
1571 " writing file system information.");
1572 Table.WriteClusters(sec_buf);
1573 if(!WriteSector(fs_info_sector, sec_buf))
1574 throw std::runtime_error(
1575 "Failed writing file system information sector.");
1576 }
1577
1578
1579 Partition*
1580 FetchPartitionFromPath(const char* path) ynothrowv
1581 {
1582 // NOTE: A %::chdir or %::setDefaultDevice call is needed before relative
1583 // paths available here.
1584 if(const auto p_devops = ::GetDeviceOpTab(Nonnull(path)))
1585 return static_cast<Partition*>(p_devops->deviceData);
1586 return {};
1587 }
1588
1589
1590 DirState::DirState(Partition& part, string_view path) ythrow(system_error)
1591 ythrow(system_error)
1592 : part_ref(part), start_cluster([&]{
1593 ClusterIndex dclus;
1594
1595 DEntry(part, path, LeafAction::EnsureDirectory, dclus);
1596 return dclus;
1597 }()), current_entry(start_cluster),
1598 valid_entry(current_entry.QueryNextFrom(GetPartitionRef()))
1599 {}
1600
1601 int
1602 DirState::Iterate(char* filename, struct ::stat* filestat) ythrow(system_error)
1603 {
1604 if(valid_entry)
1605 {
1606 auto& part(GetPartitionRef());
1607
1608 // TODO: Optimize.
1609 ystdex::ntctsncpy(Nonnull(filename), current_entry.GetName().c_str(),
1610 std::min<size_t>(current_entry.GetName().length() + 1, NAME_MAX));
1611 if(filestat)
1612 part.StatFromEntry(current_entry.Data, *filestat);
1613 valid_entry = current_entry.QueryNextFrom(part);
1614 return 0;
1615 }
1616 return -1;
1617 // throw_error(errc::no_such_file_or_directory);
1618 // XXX: Even this throws (with %ENOENT) here, it is still conforming to
1619 // POSIX which specifies that reaching end of directory stream shall not
1620 // change %errno in the %::readdir call, because the implementation in the
1621 // newlib (libsysbase, as of devkitARM r55) would restore the old %errno in
1622 // such case. Such manner is relied on to get the end of directory state
1623 // with %op_dir_locked. Returning directly saves a throw/catch in such
1624 // case, so it is preferred now. This avoids the dependency on the behavior
1625 // of restoring %errno of the %::readdir implemention, also eventually same
1626 // to LibFAT at current, see https://github.com/devkitPro/libfat/pull/9.
1627 // Note anyway, there is no chance to distinguish the condition with a
1628 // broken file system/media.
1629 }
1630
1631 void
1632 DirState::Reset() ythrow(system_error)
1633 {
1634 current_entry.NamePos = DEntry::GenerateBeforeFirstNamePos(start_cluster);
1635 valid_entry = current_entry.QueryNextFrom(GetPartitionRef());
1636 }
1637
1638
1639 FileInfo::FileInfo(Partition& part, string_view path, int flags)
1640 {
1641 const auto oflags((OpenMode(flags)));
1642
1643 switch(oflags & OpenMode::AccessMode)
1644 {
1645 case OpenMode::ReadWrite:
1646 attr.set(WriteBit);
1647 YB_ATTR_fallthrough;
1648 case OpenMode::Read:
1649 attr.set(ReadBit);
1650 break;
1651 case OpenMode::Write:
1652 attr.set(WriteBit);
1653 break;
1654 default:
1655 throw_error(errc::permission_denied);
1656 }
1657 // NOTE: %O_CREAT when file not exist would be check later.
1658 if((CanWrite() || bool(oflags & OpenMode::Truncate)) && part.IsReadOnly())
1659 throw_error(errc::read_only_file_system);
1660
1661 lock_guard<mutex> lck(part.GetMutexRef());
1662 const auto do_init([&, this](DEntry& de){
1663 if(de.Data.IsDirectory() && CanWrite())
1664 throw_error(errc::is_a_directory);
1665 file_size = de.Data.ReadFileSize();
1666 # if false
1667 // NOTE: Allow LARGEFILEs with undefined results. Make sure that the
1668 // file size can fit in the available space.
1669 if(!(flags & OpenMode::LargeFile) && file_size >= (1 << 31))
1670 throw_error(errc::file_too_large);
1671 # endif
1672 // XXX: Extension on file level.
1673 if(CanWrite() && !de.Data.IsWritable())
1674 throw_error(errc::read_only_file_system);
1675 yunseq(part_ptr = &part,
1676 start_cluster = part.ReadClusterFromEntry(part.Table, de.Data));
1677 if(bool(oflags & OpenMode::Truncate) && CanWrite()
1678 && start_cluster != 0)
1679 {
1680 part.Table.ClearLinks(start_cluster);
1681 yunseq(start_cluster = Clusters::Free, file_size = 0,
1682 attr.set(ModifiedBit));
1683 }
1684 yunseq(name_position = de.NamePos, current_position = 0,
1685 rw_position = {start_cluster});
1686 if(bool(oflags & OpenMode::Append))
1687 {
1688 attr.set(AppendBit);
1689 // Set append pointer to the end of the file
1690 // TODO: Use %FilePosition::Decompose.
1691 append_position = {part.Table.QueryLast(start_cluster), file_size
1692 % part.GetBytesPerCluster() / part.GetBytesPerSector(),
1693 file_size % part.GetBytesPerSector()};
1694 // NOTE: Check if the end of the file is on the end of a cluster.
1695 if(file_size > 0 && file_size % part.GetBytesPerCluster() == 0)
1696 // NOTE: Set flag to allocate a new cluster
1697 append_position = {append_position.GetCluster(),
1698 part.GetSectorsPerCluster(), 0};
1699 }
1700 else
1701 // NOTE: Guarantee all members initialized.
1702 append_position = rw_position;
1703 Deref(part.LockOpenFiles()).insert(*this);
1704 });
1705
1706 const auto act((oflags & OpenMode::CreateExclusive)
1707 == OpenMode::CreateExclusive ? LeafAction::ThrowExisted
1708 : LeafAction::Return);
1709
1710 if(bool(oflags & OpenMode::Create))
1711 {
1712 DEntry dentry(part, path, act, [this](DEntry& de) ynothrow{
1713 attr.set(ModifiedBit);
1714 de.Data.Clear();
1715 de.Data.WriteCDateTime();
1716 // FIXME: Only %ENOSPC may be expected.
1717 });
1718
1719 do_init(dentry);
1720 }
1721 else
1722 {
1723 DEntry dentry(part, path, act);
1724
1725 do_init(dentry);
1726 }
1727 }
1728
1729 void
1730 FileInfo::CheckAppend() ynothrow
1731 {
1732 if(CanAppend())
1733 append_position = rw_position;
1734 }
1735
1736 void
1737 FileInfo::Extend() ythrow(system_error)
1738 {
1739 auto& part(GetPartitionRef());
1740 // NOTE: It is assumed that %start_cluster is always valid. This will be
1741 // true when %Extend is called from %write_r.
1742 // TODO: Use %FilePosition::Decompose?
1743 const auto bpc(part.GetBytesPerCluster());
1744 FilePosition file_pos{part.Table.QueryLast(start_cluster),
1745 file_size % bpc / part.GetBytesPerSector(), file_size % bpc};
1746
1747 YAssert(file_size < current_position, "Invalid position found.");
1748
1749 size_t remain(current_position - file_size);
1750
1751 if(remain > 0 && file_size > 0 && file_pos.GetSector() == 0
1752 && file_pos.GetByte() == 0)
1753 file_pos = {part.Table.LinkFree(file_pos.GetCluster()), 0,
1754 file_pos.GetByte()};
1755 if(remain + file_pos.GetByte() < part.GetBytesPerSector())
1756 {
1757 // NOTE: Only need to clear to the end of the sector.
1758 CheckFillPartialSector(part.Table, file_pos, remain);
1759 file_pos.AddByte(remain);
1760 }
1761 else
1762 {
1763 if(file_pos.GetByte() > 0)
1764 {
1765 const auto delta(part.GetBytesPerSector() - file_pos.GetByte());
1766
1767 CheckFillPartialSector(part.Table, file_pos, delta);
1768 remain -= delta;
1769 file_pos.IncSectorAndResetOffset();
1770 }
1771 while(remain >= part.GetBytesPerSector())
1772 {
1773 if(file_pos.GetSector() >= part.GetSectorsPerCluster())
1774 file_pos = {part.Table.LinkFree(file_pos.GetCluster()),
1775 0, file_pos.GetByte()};
1776 CheckFillSectors(part.Table, file_pos);
1777 remain -= part.GetBytesPerSector();
1778 file_pos.IncSector();
1779 }
1780 if(remain != 0)
1781 {
1782 part.CheckPositionForNextCluster(file_pos);
1783 file_pos.SetByte(remain);
1784 CheckFillPartialSectorOff(part.Table, file_pos, 0, remain);
1785 }
1786 }
1787 rw_position = file_pos;
1788 file_size = current_position;
1789 }
1790
1791 ::ssize_t
1792 FileInfo::Read(char* buf, size_t nbyte) ythrow(system_error)
1793 {
1794 auto& part(GetPartitionRef());
1795
1796 if(nbyte != 0 && current_position < file_size
1797 && start_cluster != Clusters::Free)
1798 {
1799 if(file_size < nbyte + current_position)
1800 nbyte = file_size - current_position;
1801
1802 auto remain(nbyte);
1803 auto pos(rw_position);
1804 // NOTE: Align to sector.
1805 auto tmp(std::min<size_t>(part.GetBytesPerSector() - pos.GetByte(),
1806 remain));
1807
1808 if(tmp < part.GetBytesPerSector())
1809 {
1810 CheckReadPartialSector(part.Table, pos, buf, tmp);
1811 yunseq(remain -= tmp, buf += tmp),
1812 pos.AddByte(tmp);
1813 if(pos.GetByte() >= part.GetBytesPerSector())
1814 pos.IncSectorAndResetOffset();
1815 }
1816 // NPTE: Align to cluster, %tmp is number of sectors to read.
1817 tmp = remain > (part.GetSectorsPerCluster() - pos.GetSector())
1818 * part.GetBytesPerSector() ? part.GetSectorsPerCluster()
1819 - pos.GetSector() : remain / part.GetBytesPerSector();
1820 if(tmp > 0)
1821 {
1822 CheckReadSectors(part.Table, pos, buf, tmp);
1823
1824 const auto n(tmp * part.GetBytesPerSector());
1825
1826 yunseq(buf += n, remain -= n),
1827 pos.AddSector(tmp);
1828 }
1829 // NOTE: Move onto next cluster. It should get to here without reading
1830 // anything if a cluster is due to be allocated.
1831 if(pos.GetSector() >= part.GetSectorsPerCluster())
1832 {
1833 const auto next_start(part.Table.QueryNext(pos.GetCluster()));
1834
1835 if(remain == 0 && next_start == Clusters::EndOfFile)
1836 pos.SetSector(part.GetSectorsPerCluster());
1837 else
1838 AssignValidCluster(part, pos, next_start);
1839 }
1840 // NOTE: Read in whole clusters, contiguous blocks at a time.
1841 while(remain >= part.GetBytesPerCluster())
1842 {
1843 size_t chunk_size(0);
1844 ClusterIndex next_start(pos.GetCluster()), chunk_end(
1845 ystdex::retry_on_cond([&](ClusterIndex end) ynothrow{
1846 return next_start == end + 1
1847 && chunk_size + part.GetBytesPerCluster() <= remain;
1848 }, [&]() ynothrow{
1849 chunk_size += part.GetBytesPerCluster();
1850 return ystdex::exchange(next_start,
1851 part.Table.QueryNext(next_start));
1852 }));
1853
1854 CheckReadSectors(part.Table, pos.GetCluster(), buf,
1855 chunk_size / part.GetBytesPerSector());
1856 yunseq(buf += chunk_size, remain -= chunk_size);
1857 if(remain == 0 && next_start == Clusters::EndOfFile)
1858 pos = {chunk_end, part.GetSectorsPerCluster(), pos.GetByte()};
1859 else
1860 AssignValidCluster(part, pos, next_start);
1861 }
1862 // NOTE: %tmp is number of sectors left.
1863 tmp = remain / part.GetBytesPerSector();
1864 if(tmp > 0)
1865 {
1866 CheckReadSectors(part.Table, pos.GetCluster(), buf, tmp);
1867
1868 const auto n(tmp * part.GetBytesPerSector());
1869
1870 yunseq(buf += n, remain -= n),
1871 pos.AddSector(tmp);
1872 }
1873 if(remain > 0)
1874 {
1875 CheckReadPartialSectorOff(part.Table, pos, buf, 0, remain);
1876 pos.AddByte(remain);
1877 remain = 0;
1878 }
1879 nbyte -= remain;
1880 yunseq(rw_position = pos, current_position += nbyte);
1881 return nbyte;
1882 }
1883 return 0;
1884 }
1885
1886 ::off_t
1887 FileInfo::Seek(::off_t offset, int whence) ythrow(system_error)
1888 {
1889 ::off_t new_pos(offset);
1890
1891 switch(whence)
1892 {
1893 case SEEK_SET:
1894 break;
1895 case SEEK_CUR:
1896 new_pos += ::off_t(current_position);
1897 break;
1898 case SEEK_END:
1899 new_pos += ::off_t(file_size);
1900 break;
1901 default:
1902 throw_error(errc::invalid_argument);
1903 }
1904
1905 auto& part(GetPartitionRef());
1906
1907 if(!(0 < offset && new_pos < 0))
1908 {
1909 // NOTE: %new_pos can only be larger than the %MaxFileSize on platforms
1910 // where ::off_t is larger than 32 bits.
1911 if(!(new_pos < 0
1912 || (sizeof(new_pos) > 4 && new_pos > ::off_t(MaxFileSize))))
1913 {
1914 auto pos(static_cast<std::uint32_t>(new_pos));
1915
1916 if(pos <= file_size && start_cluster != Clusters::Free)
1917 {
1918 ptrdiff_t clus_count(pos / part.GetBytesPerCluster());
1919 ClusterIndex cluster(start_cluster);
1920
1921 if(pos >= current_position)
1922 {
1923 ptrdiff_t
1924 cur_count(current_position / part.GetBytesPerCluster());
1925 if(rw_position.GetSector() == part.GetSectorsPerCluster())
1926 --cur_count;
1927 yunseq(clus_count -= cur_count,
1928 cluster = rw_position.GetCluster());
1929 }
1930 rw_position.Decompose(pos, part.GetBytesPerCluster(),
1931 part.GetBytesPerSector());
1932 for(auto next_cluster(part.Table.QueryNext(cluster));
1933 clus_count > 0 && part.Table.IsValid(next_cluster);
1934 next_cluster = part.Table.QueryNext(cluster))
1935 {
1936 --clus_count;
1937 cluster = next_cluster;
1938 }
1939 if(clus_count > 0)
1940 {
1941 if(clus_count == 1 && file_size == pos
1942 && rw_position.GetSector() == 0)
1943 rw_position = {rw_position.GetCluster(),
1944 part.GetSectorsPerCluster(), 0};
1945 else
1946 throw_error(errc::invalid_argument);
1947 }
1948 rw_position.SetCluster(cluster);
1949 }
1950 current_position = pos;
1951 return pos;
1952 }
1953 throw_error(errc::invalid_argument);
1954 }
1955 throw_error(errc::value_too_large);
1956 }
1957
1958 void
1959 FileInfo::Stat(struct ::stat& st) const
1960 {
1961 auto& part(GetPartitionRef());
1962 EntryData edata;
1963
1964 CheckReadPartialSector(part.Table, name_position[1], edata.data());
1965 part.StatFromEntry(edata, st);
1966 // NOTE: Update fields that have changed since the file was opened.
1967 yunseq(st.st_ino = ::ino_t(start_cluster), st.st_size = file_size);
1968 }
1969
1970 void
1971 FileInfo::SyncToDisc() ythrow(system_error)
1972 {
1973 if(CanWrite() && IsModified())
1974 {
1975 auto& part(GetPartitionRef());
1976 EntryData dir_entry_data;
1977 const auto data(dir_entry_data.data());
1978 const auto& dir_entry_end(GetNamePosition()[1]);
1979 const auto c(GetStartCluster());
1980 const auto date_time(FetchDateTime());
1981
1982 CheckReadPartialSector(part.Table, dir_entry_end, data);
1983 write_uint_le<32>(data + EntryData::FileSize, GetSize()),
1984 dir_entry_data.WriteCluster(c),
1985 write_uint_le<16>(data + EntryData::MTime, date_time.second),
1986 write_uint_le<16>(data + EntryData::MDate, date_time.first),
1987 write_uint_le<16>(data + EntryData::ADate, date_time.first);
1988 data[EntryData::Attributes] |= EntryDataUnit(Attribute::Archive);
1989 CheckWritePartialSector(part.Table, dir_entry_end, data);
1990 part.GetCacheRef().Flush();
1991 }
1992 attr.set(ModifiedBit, {});
1993 }
1994
1995 void
1996 FileInfo::Truncate(FileSize new_size) ythrow(system_error)
1997 {
1998 auto& part(GetPartitionRef());
1999
2000 if(new_size > GetSize())
2001 {
2002 if(start_cluster == Clusters::Free)
2003 {
2004 ClusterIndex t(part.Table.LinkFree(Clusters::Free));
2005
2006 yunseq(start_cluster = t, rw_position = {t});
2007 }
2008
2009 const FilePosition saved_position(GetRWPosition());
2010 const FileSize saved_offset(GetCurrentPosition());
2011
2012 SetCurrentPosition(new_size);
2013 Extend();
2014 CheckAppend();
2015 SetRWPosition(saved_position),
2016 SetCurrentPosition(saved_offset);
2017 }
2018 else if(new_size < GetSize())
2019 {
2020 if(new_size == 0)
2021 {
2022 part.Table.ClearLinks(GetStartCluster());
2023 yunseq(start_cluster = Clusters::Free,
2024 append_position = {Clusters::Free});
2025 }
2026 else
2027 {
2028 // NOTE: Trim the file down to the required size. Drop the unneeded
2029 // end of the cluster chain. If the end falls on a cluster
2030 // boundary, drop that cluster too, then set a flag to allocate
2031 // a cluster as needed.
2032 const auto last_cluster(part.Table.TrimChain(GetStartCluster(),
2033 (new_size - 1) / part.GetBytesPerCluster()));
2034
2035 if(CanAppend())
2036 {
2037 const auto re(new_size % part.GetBytesPerCluster());
2038
2039 SetAppendPosition({last_cluster, re == 0
2040 ? part.GetSectorsPerCluster()
2041 : re / part.GetBytesPerSector(),
2042 new_size % part.GetBytesPerSector()});
2043 }
2044 }
2045 }
2046 UpdateSize(new_size);
2047 }
2048
2049 ::ssize_t
2050 FileInfo::Write(const char* buf, size_t nbyte) ythrow(system_error)
2051 {
2052 // FIXME: Only write up to the maximum file size, taking wrap-around of
2053 // integers into account. Overflow?
2054 if(nbyte + file_size > MaxFileSize || nbyte + file_size < file_size)
2055 nbyte = MaxFileSize - file_size;
2056 if(nbyte != 0)
2057 {
2058 auto& part(GetPartitionRef());
2059 size_t remain(nbyte);
2060
2061 if(start_cluster == Clusters::Free)
2062 {
2063 start_cluster = part.Table.LinkFree(Clusters::Free);
2064 yunseq(append_position = {start_cluster},
2065 rw_position = {start_cluster});
2066 }
2067
2068 bool flag_appending(CanAppend());
2069 auto pos(flag_appending ? append_position : rw_position);
2070
2071 if(!flag_appending)
2072 {
2073 if(current_position > file_size)
2074 Extend();
2075 if(nbyte + current_position > file_size)
2076 flag_appending = true;
2077 }
2078 if(remain != 0)
2079 part.CheckPositionForNextCluster(pos);
2080
2081 // NOTE: Align to sector.
2082 size_t tmp(std::min<size_t>(part.GetBytesPerSector() - pos.GetByte(),
2083 remain));
2084
2085 if(tmp < part.GetBytesPerSector())
2086 {
2087 CheckWritePartialSector(part.Table, pos, buf, tmp);
2088 yunseq(remain -= tmp, buf += tmp);
2089 pos.AddByte(tmp);
2090 if(pos.GetByte() >= part.GetBytesPerSector())
2091 pos.IncSectorAndResetOffset();
2092 }
2093 // NOTE: Align to sector. %tmp is number of sectors to write.
2094 tmp = remain > (part.GetSectorsPerCluster() - pos.GetSector())
2095 * part.GetBytesPerSector() ? part.GetSectorsPerCluster()
2096 - pos.GetSector() : remain / part.GetBytesPerSector();
2097 if(tmp > 0 && tmp < part.GetSectorsPerCluster())
2098 {
2099 CheckWriteSectors(part.Table, pos, buf, tmp);
2100
2101 const auto n(tmp * part.GetBytesPerSector());
2102
2103 yunseq(buf += n, remain -= n),
2104 pos.AddSector(tmp);
2105 }
2106 while(remain >= part.GetBytesPerCluster())
2107 {
2108 if(remain != 0)
2109 part.CheckPositionForNextCluster(pos);
2110
2111 auto chunk_end(pos.GetCluster()), next_start(chunk_end);
2112 size_t chunk_size(part.GetBytesPerCluster());
2113 FilePosition next_position(pos);
2114
2115 // NOTE: Group consecutive clusters.
2116 while(chunk_size + part.GetBytesPerCluster() < remain)
2117 {
2118 // NOTE: Pretend to use up all sectors in %next_position.
2119 next_position.SetSector(part.GetSectorsPerCluster());
2120 // TODO: != ?
2121 if(remain > chunk_size)
2122 part.CheckPositionForNextCluster(next_position);
2123 next_start = next_position.GetCluster();
2124 if(next_start != chunk_end + 1)
2125 break;
2126 yunseq(chunk_end = next_start,
2127 chunk_size += part.GetBytesPerCluster());
2128 }
2129 CheckWriteSectors(part.Table, pos.GetCluster(), buf,
2130 chunk_size / part.GetBytesPerSector());
2131 yunseq(buf += chunk_size, remain -= chunk_size);
2132 if(chunk_end != next_start && part.Table.IsValid(next_start))
2133 // NOTE: New cluster is already allocated (because it was not
2134 // consecutive).
2135 pos = {next_start, 0, pos.GetByte()};
2136 else
2137 pos = {chunk_end, part.GetSectorsPerCluster(), pos.GetByte()};
2138 }
2139 if(remain != 0)
2140 part.CheckPositionForNextCluster(pos);
2141 // NOTE: %tmp is number of sectors left.
2142 tmp = remain / part.GetBytesPerSector();
2143 if(tmp > 0)
2144 {
2145 CheckWriteSectors(part.Table, pos.GetCluster(), buf, tmp);
2146
2147 const auto n(tmp * part.GetBytesPerSector());
2148
2149 yunseq(buf += n, remain -= n),
2150 pos.AddSector(tmp);
2151 }
2152 if(remain > 0)
2153 {
2154 if(flag_appending)
2155 CheckThrowEIO(part.GetCacheRef().EraseWritePartialSector(
2156 PosToSec(part.Table, pos), 0, buf, remain));
2157 else
2158 CheckWritePartialSectorOff(part.Table, pos, buf, 0, remain);
2159 pos.AddByte(remain);
2160 remain = 0;
2161 }
2162 yunseq(nbyte -= remain, attr.set(ModifiedBit));
2163 if(CanAppend())
2164 {
2165 append_position = pos;
2166 file_size += nbyte;
2167 }
2168 else
2169 {
2170 rw_position = pos;
2171 current_position += nbyte;
2172 if(file_size < current_position)
2173 file_size = current_position;
2174 }
2175 }
2176 return nbyte;
2177 }
2178
2180 namespace
2181 {
2182
2183 template<typename _func, typename... _tParams>
2184 using FilterRes = ystdex::common_nonvoid_t<
2185 ystdex::invoke_result_t<_func, _tParams...>, int>;
2186
2187 inline PDefH(int, seterr, int& re, int e) ynothrow
2188 ImplRet(re = e, -1)
2189 YB_NONNULL(1) inline PDefH(int, seterr, ::_reent* r, int e) ynothrow
2190 // NOTE: The newlib reentrant environment pointer shall not be null in a
2191 // %::devoptab_t operation.
2192 ImplRet(Deref(r)._errno = e, -1)
2193
2194 template<typename _func>
2195 auto
2196 FilterDevOps(::_reent* r, _func f) ynothrowv -> FilterRes<_func>
2197 {
2198 using fres_t = decltype(f());
2199 using res_t = ystdex::common_nonvoid_t<fres_t, int>;
2200 static yconstexpr const auto
2201 de_val(std::is_pointer<fres_t>::value ? res_t() : res_t(-1));
2202
2203 TryRet(ystdex::invoke_for_value(0, f))
2204 CatchExpr(std::system_error& e, seterr(r, e.code().value()))
2205 CatchExpr(std::bad_alloc&, seterr(r, ENOMEM))
2206 CatchExpr(..., YAssert(false, "Invalid exception found."))
2207 return de_val;
2208 }
2209
2211 template<typename _fCallable>
2212 YB_NONNULL(1) auto
2213 op_path(::_reent* r, const char*& path, _fCallable f) ynothrowv
2214 -> FilterRes<_fCallable, Partition&>
2215 {
2216 return FilterDevOps(r, [=, &path]{
2217 if(Deref(path) != char())
2218 {
2219 if(const auto p_part = FetchPartitionFromPath(path))
2220 {
2221 // XXX: %EINVAL is not a POSIX error for path.
2222 path = CheckColons(path);
2223 return ystdex::invoke(f, *p_part);
2224 }
2225 // XXX: This is not a POSIX error.
2226 throw_error(errc::no_such_device);
2227 }
2228 throw_error(errc::no_such_file_or_directory);
2229 });
2230 }
2231
2233
2234 template<class _tHolder, typename _fCallable, class _type, typename... _tParams>
2235 inline auto
2236 locked_invoke(_tHolder& holder, _fCallable&& f, _type& x, _tParams&&... args)
2237 -> decltype(ystdex::invoke(yforward(f), x, yforward(args)...))
2238 {
2239 lock_guard<mutex> lck(holder.GetMutexRef());
2240
2241 return ystdex::invoke(yforward(f), x, yforward(args)...);
2242 }
2243
2244 template<typename _fCallable, typename... _tParams>
2245 YB_NONNULL(1) auto
2246 op_path_locked(::_reent* r, const char*& path, _fCallable f, _tParams&&... args)
2247 ynothrowv -> FilterRes<_fCallable, Partition&, _tParams&&...>
2248 {
2249 return op_path(r, path, [&, f](Partition& part){
2250 // XXX: Blocked. 'yforward' cause G++ 5.3 crash: internal compiler
2251 // error: Segmentation fault.
2252 return locked_invoke(part, f, part, std::forward<_tParams>(args)...);
2253 });
2254 }
2255
2256 template<typename _fCallable, typename... _tParams>
2257 YB_NONNULL(1) auto
2258 op_dir_locked(::_reent* r, ::DIR_ITER* dir_state, _fCallable f,
2259 _tParams&&... args) ynothrowv
2260 -> FilterRes<_fCallable, DirState&, _tParams&&...>
2261 {
2262 auto& state(Deref(static_cast<DirState*>(Deref(dir_state).dirStruct)));
2263
2264 return FilterDevOps(r, [&, f]{
2265 // XXX: Extended partition mutex to lock directory states.
2266 // XXX: Blocked. 'yforward' cause G++ 5.3 crash: internal compiler
2267 // error: Segmentation fault.
2268 return locked_invoke(state.GetPartitionRef(), f, state,
2269 std::forward<_tParams>(args)...);
2270 });
2271 }
2273
2275 template<typename _fCheck, typename _fCallable, typename... _tParams>
2276 YB_NONNULL(1) auto
2277 op_file_checked(::_reent* r, void* fh, _fCheck check, _fCallable f,
2278 _tParams&&... args) ynothrowv
2279 -> FilterRes<_fCallable, FileInfo&, _tParams&&...>
2280 {
2281 auto& file(Deref(static_cast<FileInfo*>(fh)));
2282
2283 return FilterDevOps(r, [&, check, f]{
2284 // NOTE: Check of %file is similar to %::close_r.
2285 if(ystdex::invoke(check, file))
2286 // XXX: Blocked. 'yforward' cause G++ 5.3 crash: internal compiler
2287 // error: Segmentation fault.
2288 return locked_invoke(file, f, file,
2289 std::forward<_tParams>(args)...);
2290 throw_error(errc::bad_file_descriptor);
2291 });
2292 }
2293
2295 template<typename _fCallable, typename... _tParams>
2296 YB_NONNULL(1) auto
2297 op_file_locked(::_reent* r, void* fh, _fCallable f, _tParams&&... args)
2298 ynothrowv -> FilterRes<_fCallable, FileInfo&, _tParams&&...>
2299 {
2300 auto& file(Deref(static_cast<FileInfo*>(fh)));
2301
2302 return FilterDevOps(r, [&, f]{
2303 // XXX: Blocked. 'yforward' cause G++ 5.3 crash: internal compiler
2304 // error: Segmentation fault.
2305 return locked_invoke(file, f, file, std::forward<_tParams>(args)...);
2306 });
2307 }
2308
2309 // NOTE: %REENTRANT_SYSCALLS_PROVIDED is configured for libgloss for
2310 // arm-*-*-eabi targets in devkitPro ports. See source
2311 // 'newlib/libgloss/configure'.
2312
2313 const ::devoptab_t dotab_fat{
2314 "fat", sizeof(FileInfo), [](::_reent* r, void* file_struct,
2315 const char* path, int flags, int)
2316 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3))){
2317 // NOTE: Before the call of %::devoptab_t::open_r, the parameter
2318 // %file_struct is set as a pointer after handling in implementation
2319 // of devkitPro port of newlib (libsysbase) %::_open_r, which is
2320 // assumed to be not null.
2321 return op_path(r, path, [=, &path](Partition& part){
2322 ::new(Nonnull(file_struct)) FileInfo(part, path, flags);
2323
2324 return int(file_struct);
2325 });
2326 }, [](::_reent* r, void* fh)
2327 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_NONNULL(2)){
2328 // NOTE: The parameter %fd is actually cast from the file structure
2329 // pointer stored by %devoptab_t::open_r. This function is called
2330 // when the reference count in the handle decreased to zero. Since this
2331 // pointer would not be null for non-predefined streams unless
2332 // corrupted, check for null (yield %EBADF) is not performed, as same
2333 // as LibFAT does. Also note the name is %fd, not POSIX %filedes.
2334 return FilterDevOps(r, [=]{
2335 auto& file(Deref(static_cast<FileInfo*>(fh)));
2336 auto& part(file.GetPartitionRef());
2337 const auto gd(ystdex::make_guard([&]() ynothrowv{
2338 Deref(part.LockOpenFiles()).erase(file);
2339 file.~FileInfo();
2340 }));
2341 lock_guard<mutex> lck(part.GetMutexRef());
2342
2343 file.SyncToDisc();
2344 });
2345 }, [](::_reent* r, void* fh, const char* buf, size_t nbyte)
2346 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 4))){
2347 return op_file_checked(r, fh, &FileInfo::CanWrite, &FileInfo::Write,
2348 buf, nbyte);
2349 }, [](::_reent* r, void* fh, char* buf, size_t nbyte)
2350 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 4))){
2351 return op_file_checked(r, fh, &FileInfo::CanRead, &FileInfo::Read, buf,
2352 nbyte);
2353 }, [](::_reent* r, void* fh, ::off_t offset, int whence)
2354 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_NONNULL(2)){
2355 return op_file_locked(r, fh, &FileInfo::Seek, offset, whence);
2356 }, [](::_reent* r, void* fh, struct ::stat* buf)
2357 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 4))){
2358 return op_file_locked(r, fh, &FileInfo::Stat, Deref(buf));
2359 }, [](::_reent* r, const char* path, struct ::stat* buf)
2360 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3, 4))){
2361 return op_path_locked(r, path, &Partition::Stat, Deref(buf), path);
2362 }, [](::_reent* r, const char*, const char*)
2363 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3, 4))){
2364 return seterr(r, ENOTSUP);
2365 }, [](::_reent* r, const char* path)
2366 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3))){
2367 return op_path_locked(r, path, &Partition::Unlink, path);
2368 }, [](::_reent* r, const char* path)
2369 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3))){
2370 return op_path_locked(r, path, &Partition::ChangeDir, path);
2371 }, [](::_reent* r, const char* old, const char* new_name)
2372 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3, 4))){
2373 return op_path_locked(r, old, &Partition::Rename, old, new_name);
2374 }, [](::_reent* r, const char* path, int)
2375 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3))){
2376 return op_path_locked(r, path, &Partition::MakeDir, path);
2377 }, sizeof(DirState),
2378 [](::_reent* r, ::DIR_ITER* dir_state, const char* path)
2379 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3, 4))){
2380 return op_path_locked(r, path, [=, &path](Partition& part)
2381 -> ::DIR_ITER*{
2382 const auto p(dir_state->dirStruct);
2383
2384 ::new(Nonnull(p)) DirState(part, path);
2385 return static_cast<::DIR_ITER*>(p);
2386 });
2387 }, [](::_reent* r, ::DIR_ITER* dir_state)
2388 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3))){
2389 return op_dir_locked(r, dir_state, &DirState::Reset);
2390 }, [](::_reent* r, ::DIR_ITER* dir_state, char* filename,
2391 struct ::stat* filestat)
2392 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3, 4))){
2393 // NOTE: The filename is of %NAME_MAX characters in newlib DS port.
2394 return
2395 op_dir_locked(r, dir_state, &DirState::Iterate, filename, filestat);
2396 }, [](::_reent* r, ::DIR_ITER* dir_state)
2397 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3))){
2398 return op_dir_locked(r, dir_state, [](DirState& state) ynothrow{
2399 state.~DirState();
2400 });
2401 }, [](::_reent* r, const char* path, struct ::statvfs* buf)
2402 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR(nonnull(2, 3, 4))){
2403 return op_path_locked(r, path, &Partition::StatFS, Deref(buf));
2404 }, [](::_reent* r, void* fh, ::off_t length)
2405 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR_LAMBDA(nonnull(2))) -> int{
2406 return length >= 0 ? (sizeof(length) <= 4 || length <= ::off_t(
2407 MaxFileSize) ? op_file_checked(r, fh, &FileInfo::CanWrite,
2408 &FileInfo::Truncate, std::uint32_t(length)) : EFBIG) : EINVAL;
2409 }, [](::_reent* r, void* fh)
2410 YB_ATTR_LAMBDA_QUAL(ynothrowv, YB_ATTR_LAMBDA(nonnull(2))) -> int{
2411 return op_file_locked(r, fh, &FileInfo::SyncToDisc);
2412 }, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
2413 };
2414
2415 } // unnamed namespace;
2416
2417 bool
2418 Mount(string_view name, const ::DISC_INTERFACE& intf, ::sec_t start_sector,
2419 size_t pages, size_t sectors_per_page_shift)
2420 {
2421 YAssertNonnull(name.data());
2422 if(intf.startup() && intf.isInserted())
2423 {
2424 const auto devname(string(name) + ':');
2425
2426 if(::FindDevice(devname.c_str()) >= 0)
2427 return true;
2428 if(auto p = unique_raw(static_cast<::devoptab_t*>(::operator new(
2429 sizeof(::devoptab_t) + name.length() + 1)),
2430 static_cast<void(&)(void*)>(::operator delete)))
2431 if(auto p_part = make_unique<Partition>(intf, pages,
2432 sectors_per_page_shift, start_sector))
2433 {
2434 const auto
2435 p_name(ystdex::aligned_store_cast<char*>(p.get() + 1));
2436
2437 yunseq(ystdex::trivially_copy_n(&dotab_fat, 1, p.get()),
2438 ystdex::ntctscpy(p_name, name.data(), name.length()),
2439 p->name = p_name, p->deviceData = p_part.get());
2440 // NOTE: LibFAT has no error handling here.
2441 if(YB_UNLIKELY(::AddDevice(p.get())) == -1)
2442 throw std::runtime_error("Adding device failed.");
2443 p_part.release();
2444 p.release();
2445 return true;
2446 }
2447 YTraceDe(Alert, "Memory allocation failure @ FATMount.");
2448 }
2449 return {};
2450 }
2451
2452 bool
2453 Unmount(const char* name) ynothrow
2454 {
2455 if(const auto p_devops = ::GetDeviceOpTab(Nonnull(name)))
2456 if(p_devops->open_r == dotab_fat.open_r && ::RemoveDevice(name) != -1)
2457 {
2458 delete static_cast<Partition*>(p_devops->deviceData);
2459 ::operator delete(const_cast<::devoptab_t*>(p_devops));
2460 return true;
2461 }
2462 return {};
2463 }
2464
2465 } // namespace FAT;
2466 #endif
2467
2468 } // namespace platform_ex;
2469
platform::FAT::LFN::Reserved2
@ Reserved2
Definition: YCLib/FileSystem.h:1142
ystdex::make_unique_default_init
enable_if_t<!is_array< _type >::value, std::unique_ptr< _type > > make_unique_default_init()
Definition: memory.hpp:788
YB_ATTR_nodiscard
#define YB_ATTR_nodiscard
标记返回值不被忽略的属性。
Definition: ydef.h:715
platform::FAT::ConvertFileTime
YB_ATTR_nodiscard YF_API std::time_t ConvertFileTime(Timestamp, Timestamp) ynothrow
转换日期和时间的时间戳为标准库时间类型。
Definition: YCLib/FileSystem.cpp:523
platform::FAT::FSI_TrailSig_Value
yconstexpr const std::uint32_t FSI_TrailSig_Value(0xAA550000)
CHRLib::e
const char const char * e
Definition: CharacterProcessing.h:112
platform::FAT::FSI_TrailSig
@ FSI_TrailSig
Definition: YCLib/FileSystem.h:1009
x
const EncapsulationBase & x
Definition: NPLA1Forms.cpp:1536
platform::FAT::CheckColons
const char * CheckColons(const char *path)
Definition: YCLib/FileSystem.cpp:869
platform::FAT::MatchFATSignature
YB_ATTR_nodiscard MatchFATSignature
Definition: YCLib/FileSystem.h:978
string.hpp
ISO C++ 标准字符串扩展。
platform::Nonnull
YB_PURE _type && Nonnull(_type &&p) ynothrowv
断言并返回非空参数。
Definition: Debug.h:518
size
size_t size
Definition: memory_resource.cpp:295
platform::ssize_t
ystdex::make_signed_t< std::size_t > ssize_t
Definition: NativeAPI.h:94
platform::FAT::LFN::Reserved1
@ Reserved1
Definition: YCLib/FileSystem.h:1132
ystdex::make_guard
guard< _type > make_guard(_type f)
Definition: scope_guard.hpp:84
PDefH
YB_ATTR_nodiscard YB_PURE PDefH(TermNode &, ReferenceTerm, TermNode &term) ynoexcept_spec(std YB_ATTR_nodiscard YB_PURE yconstfn PDefH(ResolvedTermReferencePtr, ResolveToTermReferencePtr, observer_ptr< const TermReference > p) ynothrow ImplRet(p.get()) YB_ATTR_nodiscard YB_PURE inline PDefH(bool
访问项并取解析 TermReference 间接值后的引用。
Definition: NPLA.h:1279
YSLib::TryInvoke
ystdex::nonvoid_result_t< ystdex::invoke_result_t< _fCallable, _tParams... > > TryInvoke(_fCallable &&f, _tParams &&... args) ynothrow
调用函数并尝试返回。
Definition: YException.h:206
platform::FAT::FAT16::Signature_word
@ Signature_word
Definition: YCLib/FileSystem.h:934
platform::FAT::LFN::MaxMBCSLength
@ MaxMBCSLength
UTF-8 项最大长度。
Definition: YCLib/FileSystem.h:1163
CatchIgnore
#define CatchIgnore(_ex)
指定异常处理器忽略指定异常。
Definition: YBaseMacro.h:133
platform::FAT::FAT16::BS_FilSysType
@ BS_FilSysType
Definition: YCLib/FileSystem.h:932
CHRLib::c
c
Definition: CharacterProcessing.h:84
platform::FAT::FAT32::BPB_FATSz32
@ BPB_FATSz32
Definition: YCLib/FileSystem.h:950
ystdex::invoke
invoke_result_t< _fCallable, _tParams... > invoke(_fCallable &&f, _tParams &&... args)
调用可调用对象。
Definition: invoke.hpp:538
yconstexpr
#define yconstexpr
指定编译时常量表达式。
Definition: ydef.h:1072
platform::FAT::FSI_Free_Count
@ FSI_Free_Count
Definition: YCLib/FileSystem.h:1006
NPL::Decompose
LexemeList Decompose(string_view src, LexemeList::allocator_type a)
Definition: Lexical.cpp:299
platform::mode_t
::mode_t mode_t
文件模式类型。
Definition: FileIO.h:116
platform_ex
平台扩展命名空间。
Definition: Android.h:46
YB_ATTR_LAMBDA_QUAL
#define YB_ATTR_LAMBDA_QUAL(_q,...)
允许在 lambda 表达式的参数列表后和限定符共用的属性。
Definition: ydef.h:619
ystdex::cpp2014::min
const _type & min(const _type &a, const _type &b, _fComp comp=less< _type >())
取较小的元素。
Definition: algorithm.hpp:614
platform::FAT::LFN::GenerateAliasChecksum
EntryDataUnit GenerateAliasChecksum(const EntryDataUnit *p) ynothrowv
Definition: YCLib/FileSystem.cpp:635
platform::FAT::BPB_TotSec32
@ BPB_TotSec32
Definition: YCLib/FileSystem.h:907
ystdex::retry_on_cond
invoke_result_t< _fCallable, _tParams... > retry_on_cond(_fCond cond, _fCallable &&f, _tParams &&... args)
循环重复调用：代替直接使用 do-while 语句以避免过多引入作用域外的变量。
Definition: functional.hpp:554
YSLib::Drawing::r
The boundary is out of the const Graphics const Rect const Point const Point Color c const Rect const Rect & r
Definition: YDraw.h:174
platform::FAT::LFN::ValidateName
YB_ATTR_nodiscard YF_API YB_PURE bool ValidateName(string_view) ynothrowv
检查名称是否为合法的可被 UCS-2 表示的非控制字符组成。
Definition: YCLib/FileSystem.cpp:650
platform::FAT::LFN::IllegalCharacters
const char IllegalCharacters[]
非法长文件名字符。
Definition: YCLib/FileSystem.h:1184
platform::FAT::BPB_RootEntCnt
@ BPB_RootEntCnt
Definition: YCLib/FileSystem.h:900
ystdex::block_buffer::fill
void fill(size_t offset, size_t n=1, byte value={})
填充字节到缓冲区。
Definition: cstdio.cpp:176
yfsig
#define yfsig
展开为表示函数签名的预定义变量的宏。
Definition: ydef.h:1133
ystdex::cpp2017::string_view
basic_string_view< char > string_view
Definition: string_view.hpp:437
platform::FAT::FSI_LeadSig
@ FSI_LeadSig
Definition: YCLib/FileSystem.h:1003
platform::FAT::FAT32::BPB_RootClus
@ BPB_RootClus
Definition: YCLib/FileSystem.h:953
ystdex::function_base
函数包装类模板。
Definition: function.hpp:578
platform::FAT::CheckValidMBR
bool CheckValidMBR(const byte *sec_buf)
Definition: YCLib/FileSystem.cpp:513
platform::size_t
YB_ATTR_nodiscard YF_API size_t
Definition: FileIO.h:1345
ystdex::trivially_fill_n
_type * trivially_fill_n(_type *first, size_t n=1, byte value={})
填充字节序列。
Definition: algorithm.hpp:444
YModules.h
YSLib 核心模块配置文件。
YSLib::IO::int
YF_API int
Definition: File.h:51
ystdex::invoke_for_value
auto invoke_for_value(_type &&val, _fCallable &&f, _tParams &&... args) -> common_nonvoid_t< invoke_result_t< _fCallable, _tParams... >, _type >
调用第二参数起指定的函数对象，若返回空类型则使用第一参数的值为返回值。
Definition: functional.hpp:458
platform::FAT::FileSize
std::uint32_t FileSize
文件大小：字节数。
Definition: YCLib/FileSystem.h:1090
next
tidy_ptr< slink > next
Definition: memory_resource.cpp:223
platform::if
if(!p) p
CHRLib::d
_tDst * d
Definition: CharacterProcessing.h:142
YModules.h
CHRLib 库模块配置文件。
ystdex::cpp2014::max
const _type & max(const _type &a, const _type &b, _fComp comp=less< _type >())
取较大的元素。
Definition: algorithm.hpp:635
ystdex::common_nonvoid_t
cond_or_t< is_void< _type >, _type, common_type_t, _types... > common_nonvoid_t
取公共非空类型：若第一参数为非空类型则为第一参数，否则从其余参数推断。
Definition: type_op.hpp:274
YB_ATTR_fallthrough
#define YB_ATTR_fallthrough
用于 switch 语句的 fallthrough 属性。
Definition: ydef.h:655
ythrow
#define ythrow(...)
YSLib 动态异常规范：根据是否使用异常规范宏指定或忽略动态异常规范。
Definition: ydef.h:1256
platform::FAT::FSI_LeadSig_Value
yconstexpr const std::uint32_t FSI_LeadSig_Value(0x41615252)
ystdex::read_uint_le
make_width_int< _vWidth >::unsigned_type read_uint_le(const byte *buf)
从字节缓冲区读取指定宽的小端序无符号整数。
Definition: cstdint.hpp:528
platform::FAT::FSI_StrucSig_Value
yconstexpr const std::uint32_t FSI_StrucSig_Value(0x61417272)
ystdex::block_buffer::get
byte * get() const
Definition: ystdex/cstdio.h:332
platform_ex::Windows::Invalid
@ Invalid
Definition: Win32/include/YCLib/MinGW32.h:485
platform::FAT::FSI_StrucSig
@ FSI_StrucSig
Definition: YCLib/FileSystem.h:1005
CHRLib::f
f(d, s)
YAssert
#define YAssert(_expr, _msg)
Definition: cassert.h:154
ystdex::ntctslen
size_t ntctslen(const _tChar *s)
计算简单 NTCTS 长度。
Definition: cstring.h:456
CHRLib::MakeUCS2LE
YB_ATTR_nodiscard YB_PURE _tDst MakeUCS2LE(basic_string_view< char > sv, _tParams &&... args)
Definition: CharacterProcessing.h:649
CHRLib::ConversionState::PDefH
#define PDefH(_t, _n,...)
CHRLib::args
YB_ATTR_nodiscard _tParams && args
Definition: CharacterProcessing.h:649
act
Action act
Definition: NPLA1.cpp:580
platform::FAT::LFN::OffsetTable
yconstexpr const size_t OffsetTable[]
组成长文件名中的 UCS-2 字符在项中的偏移量表。
Definition: YCLib/FileSystem.h:1148
NPL::LexemeCategory::Data
@ Data
数据字面量。
platform::Descriptions::Alert
@ Alert
Definition: YCommon.h:180
platform::FAT::LFN::CheckSum
@ CheckSum
短文件名（别名）校验和。
Definition: YCLib/FileSystem.h:1134
ystdex::unique_raw
std::unique_ptr< _type > unique_raw(_pSrc *p)
Definition: memory.hpp:645
platform::FAT::LFN::EntryLength
@ EntryLength
Definition: YCLib/FileSystem.h:1165
YSLib::IO::EnsureDirectory
EnsureDirectory
Definition: YSLib/Service/FileSystem.h:558
yforward
#define yforward(_expr)
根据参数类型使用 std::forward 转发对应参数。
Definition: ydef.h:1508
ystdex::ltrim
_tString && ltrim(_tString &&str, typename string_traits< _tString >::value_type c)
删除字符串中指定的连续前缀字符。
Definition: string.hpp:1423
ystdex::cpp2017::data
auto data(_tRange &c) -> decltype(c.data())
Definition: range.hpp:581
YSLib::Drawing::src
src
Definition: YGDI.h:442
scope_guard.hpp
作用域守卫。
ImplRet
#define ImplRet(...)
Definition: YBaseMacro.h:100
platform::FAT::BPB_TotSec16
@ BPB_TotSec16
Definition: YCLib/FileSystem.h:901
ystdex::cpp2017::end
auto end(_tRange &c) -> decltype(c.end())
Definition: range.hpp:345
platform::FAT::EntryDataUnit
byte EntryDataUnit
Definition: YCLib/FileSystem.h:878
ystdex::rtrim
_tString && rtrim(_tString &&str, typename string_traits< _tString >::value_type c)
删除字符串中指定的连续后缀字符。
Definition: string.hpp:1454
std
Definition: optional.h:982
YB_ATTR
#define YB_ATTR(...)
GNU 风格属性。
Definition: ydef.h:567
YAssertNonnull
#define YAssertNonnull(_expr)
Definition: cassert.h:165
platform::FAT::LFN::MaxLength
@ MaxLength
UCS-2 项最大长度。
Definition: YCLib/FileSystem.h:1157
functional.hpp
函数和可调用对象。
platform::FAT::LFN::LastLongEntry
@ LastLongEntry
Ordinal 中标记结束的项。
Definition: YCLib/FileSystem.h:1180
YSLib::i
return i
Definition: YCoreUtilities.h:177
ystdex::throw_error
void throw_error(std::error_code ec, _tParams &&... args)
抛出错误： std::system_error 或允许相同构造函数参数的异常。
Definition: ystdex/exception.h:241
YSLib::Drawing::GetSize
dst GetSize()
ystdex::ntctsicmp
int ntctsicmp(const _tChar *s1, const _tChar *s2)
按字典序比较简单 NTCTS （忽略大小写）。
Definition: cstring.h:213
yunseq
#define yunseq
无序列依赖表达式组求值。
Definition: ydef.h:1535
YTraceDe
#define YTraceDe(...)
YCLib 默认调试跟踪。
Definition: Debug.h:407
platform::FAT::ClusterIndex
std::uint32_t ClusterIndex
Definition: YCLib/FileSystem.h:879
YSLib::FilterExceptions
bool FilterExceptions(_func f, const char *desc={}, RecordLevel lv=Alert, ExceptionTracer trace=ExtractAndTrace) ynothrow
调用函数并过滤宿主异常。
Definition: YException.h:225
platform::FAT::LFN::ConvertToMBCS
string ConvertToMBCS(const char16_t *path)
Definition: YCLib/FileSystem.cpp:627
platform::Descriptions::Err
@ Err
Definition: YCommon.h:182
platform_ex::Windows::Directory
@ Directory
Definition: Win32/include/YCLib/MinGW32.h:466
ystdex::trivially_copy_n
_type * trivially_copy_n(const _type *first, size_t n, _type *result)
复制不覆盖的序列。
Definition: algorithm.hpp:469
platform::Descriptions::Warning
@ Warning
Definition: YCommon.h:183
YSLib::path
YB_ATTR_nodiscard YF_API const char * path
Definition: Initialization.h:116
ystdex::cpp2017::invoke_result_t
_t< invoke_result< _fCallable, _tParams... > > invoke_result_t
Definition: invoke.hpp:393
CatchExpr
#define CatchExpr(_ex,...)
指定表达式作为异常处理器。
Definition: YBaseMacro.h:129
platform
默认平台命名空间。
Definition: Container.h:56
platform::FAT::EntryDataSize
yconstexpr const size_t EntryDataSize(0x20)
目录项数据大小。
CHRLib::s
_tDst const _tSrc * s
Definition: CharacterProcessing.h:143
ystdex::ntctsncpy
enable_if_irreplaceable_char_t< _tChar, _tChar * > ntctsncpy(_tChar *s1, const _tChar *s2, size_t n)
复制不超过指定长度的 NTCTS 。
Definition: cstring.h:315
platform::FAT::LFN::Flag
@ Flag
Definition: YCLib/FileSystem.h:1130
ystdex::cache_lookup
auto cache_lookup(_tMap &cache, const _tKey &key, _func init, _tParams &&... args) -> decltype((cache.begin() ->second))
以指定的关键字查找作为缓存的无序关联容器， 若没有找到使用指定的可调用对象和参数初始化内容。
Definition: cache.hpp:380
platform::FAT::BPB_RsvdSecCnt
@ BPB_RsvdSecCnt
Definition: YCLib/FileSystem.h:898
NPL::A1::name
const char * name
Definition: NPLA1.h:1876
platform::Deref
YB_PURE yconstfn auto Deref(_type &&p) -> decltype(*p)
断言并解引用非空指针。
Definition: Debug.h:547
platform::FAT::FAT32::BPB_ExtFlags
@ BPB_ExtFlags
Definition: YCLib/FileSystem.h:951
CHRLib::char16_t
YB_ATTR_nodiscard YB_PURE char16_t((*c_ptr<< CHAR_BIT)|c_ptr[1])) YB_ATTR_nodiscard YB_PURE inline PDefH(char16_t
取 c_ptr 指向的小端序双字节字符。
platform::FAT::ClusterCount
std::uint32_t ClusterCount
Definition: YCLib/FileSystem.h:881
ynothrowv
#define ynothrowv
YSLib 无异常抛出保证验证：有条件地使用无异常抛出规范。
Definition: ydef.h:1219
YSLib::Text::buf
YB_ATTR_nodiscard YB_PURE const char * buf
Definition: TextFile.h:83
NPL::A1::Forms::First
YF_API ReductionStatus First(TermNode &)
取参数指定的列表中的第一元素的引用值。
Definition: NPLA1Forms.cpp:2197
platform::FAT::FetchDateTime
YB_ATTR_nodiscard YF_API pair< Timestamp, Timestamp > FetchDateTime() ynothrow
取以实时时钟的文件日期和时间。
Definition: YCLib/FileSystem.cpp:540
platform::FAT::LFN::Ordinal
@ Ordinal
长文件名序数。
Definition: YCLib/FileSystem.h:1123
ystdex::block_buffer::read
void read(void *dst, size_t offset, size_t n) const
读取缓冲区内容到外部存储。
Definition: cstdio.cpp:185
YSLib::_tParams
YB_ATTR_nodiscard YB_PURE typename _tParams
Definition: ValueNode.h:1294
ystdex::write_uint_le
void write_uint_le(byte *buf, typename make_width_int< _vWidth >::unsigned_type val)
向字节缓冲区写入指定宽的小端序无符号整数。
Definition: cstdint.hpp:552
YSLib::IO::Path::YB_NONNULL
PDefH(String, GetExtensionOf, const Path &pth) ImplRet(pth.empty() ? String() YF_API YB_NONNULL(1) bool VerifyDirectory(const char *)
判断路径表示绝对路径。
Definition: YSLib/Service/FileSystem.h:516
CatchRet
#define CatchRet(_ex,...)
指定返回语句作为异常处理器。
Definition: YBaseMacro.h:137
YModules.h
YCLib 库模块配置文件。
ystdex::id
恒等仿函数。
Definition: functor.hpp:94
platform::FAT::FSI_Nxt_Free
@ FSI_Nxt_Free
Definition: YCLib/FileSystem.h:1007
NPL::c_str
YF_API const char src c_str())) YF_API void InstallDirectory(const string &
安装目录：复制目录树。
Definition: Dependency.h:140
ImplExpr
#define ImplExpr(...)
简单通用函数实现。
Definition: YBaseMacro.h:96
ystdex::ntctscpy
enable_if_irreplaceable_char_t< _tChar, _tChar * > ntctscpy(_tChar *s1, const _tChar *s2)
复制 NTCTS 。
Definition: cstring.h:250
ystdex::block_buffer
块缓冲。
Definition: ystdex/cstdio.h:284
platform::FAT::BPB_SecPerClus
@ BPB_SecPerClus
Definition: YCLib/FileSystem.h:897
platform_ex::Windows::Archive
@ Archive
Definition: Win32/include/YCLib/MinGW32.h:467
ystdex::cpp2014::exchange
_type exchange(_type &obj, _type2 &&new_val)
交换值并返回旧值。
Definition: swap.hpp:535
ystdex::cpp2014::make_unique
enable_if_t<!is_array< _type >::value, std::unique_ptr< _type > > make_unique(_tParams &&... args)
Definition: memory.hpp:760
platform::FAT::MaxSectorSize
yconstexpr const size_t MaxSectorSize(4096)
YB_UNLIKELY
#define YB_UNLIKELY(_expr)
分支预测提示。
Definition: ydef.h:828
ystdex::byte
unsigned char byte
字节类型。
Definition: ydef.h:1283
platform::FAT::BPB_BytsPerSec
@ BPB_BytsPerSec
Definition: YCLib/FileSystem.h:896
platform::p
return p p
Definition: YCLib/FileSystem.h:209
platform::FAT::MaxFileSize
static yconstexpr const auto MaxFileSize(FileSize(0xFFFFFFFF))
最大文件大小。
platform::FAT::FAT32::BPB_FSInfo
@ BPB_FSInfo
Definition: YCLib/FileSystem.h:954
const
const Decapsulate const Decapsulate &y ynothrow ContextNode &ctx const
Definition: NPLA1Forms.cpp:1629
NPL::dst
YF_API const char src const string const char * dst
Definition: Dependency.h:96
ystdex::cpp2017::string
basic_string< char > string
Definition: string.hpp:772
ynothrow
#define ynothrow
YSLib 无异常抛出保证：若支持 noexcept 关键字， 指定特定的 noexcept 异常规范。
Definition: ydef.h:1199
platform::FAT::BPB_FATSz16
@ BPB_FATSz16
Definition: YCLib/FileSystem.h:903
TryRet
#define TryRet(...)
指定表达式在 try 块中求值并返回。
Definition: YBaseMacro.h:124
TryExpr
#define TryExpr(...)
指定表达式在 try 块中求值。
Definition: YBaseMacro.h:120
yunused
#define yunused(...)
标记未使用的表达式。
Definition: ydef.h:1458
ystdex::block_buffer::write
void write(size_t offset, const void *src, size_t n)
写入外部字节序列的内容到缓冲区。
Definition: cstdio.cpp:193
NPL::get
PDefH(void, LiftTermRef, TermNode &term, const TermNode &tm) ImplExpr(NPL ref get()
Definition: NPLA.h:1737
ystdex::ref
void ref(const _type &&)=delete
YB_ATTR_LAMBDA
#define YB_ATTR_LAMBDA(...)
允许在 lambda 表达式的参数列表后使用的属性。
Definition: ydef.h:581
platform_ex::Reset
unsigned long Reset(COMPtr< _iCOM > &ptr) ynothrow
Definition: Win32/include/YCLib/COM.h:406
platform::FAT::BPB_NumFATs
@ BPB_NumFATs
Definition: YCLib/FileSystem.h:899
platform::FAT::FAT16::BS_VolLab
@ BS_VolLab
Definition: YCLib/FileSystem.h:931