The Pedigree Project 0.1
FatFilesystem-attributes.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Log.h"
9#include "pedigree/kernel/machine/Disk.h"
10#include "pedigree/kernel/syscallError.h"
11
12#include "FatFilesystem.h"
13#include "FatSymlink.h"
14#include "system/kernel/machine/mach_pc/RtcTimeAccounting.h"
15
16namespace {
17constexpr Time::Timestamp FatEpoch = 315532800;
18constexpr Time::Timestamp FatLastSecond = 4354819199ULL;
19constexpr Time::Timestamp SecondsPerDay = 86400;
20
21Time::Timestamp fatTimestamp(Time::Timestamp timestamp) {
22 if (timestamp < FatEpoch)
23 return FatEpoch;
24 if (timestamp > FatLastSecond)
25 return FatLastSecond;
26 return timestamp;
27}
28
29RtcTimeAccounting::CivilTime fatCivilTime(Time::Timestamp timestamp) {
30 timestamp = fatTimestamp(timestamp);
31 RtcTimeAccounting::CivilTime civil = {1980, 1, 1, 0, 0, 0, 0};
32 uint64_t days = (timestamp - FatEpoch) / SecondsPerDay;
33 while (days >= (RtcTimeAccounting::isLeapYear(civil.year) ? 366U : 365U)) {
34 days -= RtcTimeAccounting::isLeapYear(civil.year) ? 366U : 365U;
35 ++civil.year;
36 }
37 while (days >= RtcTimeAccounting::daysInMonth(civil.year, civil.month)) {
38 days -= RtcTimeAccounting::daysInMonth(civil.year, civil.month);
39 ++civil.month;
40 }
41 civil.day = days + 1;
42 civil.hour = (timestamp % SecondsPerDay) / 3600;
43 civil.minute = (timestamp % 3600) / 60;
44 civil.second = timestamp % 60;
45 return civil;
46}
47} // namespace
48
49bool FatFilesystem::getUuid(String& uuid) const {
50 const uint8_t signature = m_Type == FAT32 ? m_Superblock32.BS_BootSig : m_Superblock16.BS_BootSig;
51 if (signature != 0x28 && signature != 0x29) {
52 uuid = String();
53 return false;
54 }
55 const uint32_t volumeId =
56 LITTLE_TO_HOST32(m_Type == FAT32 ? m_Superblock32.BS_VolID : m_Superblock16.BS_VolID);
57 uuid.Format("%04X-%04X", volumeId >> 16, volumeId & 0xFFFF);
58 return true;
59}
60
61uint16_t FatFilesystem::getFatDate(Time::Timestamp timestamp) const {
62 const auto civil = fatCivilTime(timestamp);
63 return static_cast<uint16_t>(((civil.year - 1980) << 9) | (civil.month << 5) | civil.day);
64}
65
66uint16_t FatFilesystem::getFatTime(Time::Timestamp timestamp) const {
67 const auto civil = fatCivilTime(timestamp);
68 return static_cast<uint16_t>((civil.hour << 11) | (civil.minute << 5) | (civil.second / 2));
69}
70
71Time::Timestamp FatFilesystem::getUnixTimestamp(uint16_t time, uint16_t date) const {
72 time = LITTLE_TO_HOST16(time);
73 date = LITTLE_TO_HOST16(date);
74 const size_t year = 1980 + (date >> 9);
75 const uint8_t month = (date >> 5) & 15;
76 const uint8_t day = date & 31;
77 const uint8_t hour = time >> 11;
78 const uint8_t minute = (time >> 5) & 63;
79 const uint8_t second = (time & 31) * 2;
80 if (month < 1 || month > 12 || day < 1 || day > RtcTimeAccounting::daysInMonth(year, month) ||
81 hour > 23 || minute > 59 || second > 59)
82 return 0;
83
84 uint64_t days = day - 1;
85 for (size_t precedingYear = 1980; precedingYear < year; ++precedingYear)
86 days += RtcTimeAccounting::isLeapYear(precedingYear) ? 366 : 365;
87 for (uint8_t precedingMonth = 1; precedingMonth < month; ++precedingMonth)
88 days += RtcTimeAccounting::daysInMonth(year, precedingMonth);
89 return FatEpoch + days * SecondsPerDay + hour * 3600 + minute * 60 + second;
90}
91
92void FatFilesystem::writeEntryAttributes(File* file, Dir* entry, bool creating) {
93 // Bypass the FAT snapshot wrapper: the caller already owns the filesystem lock.
94 encodeEntryAttributes(file->File::getAttributes(), entry, creating);
95}
96
97void FatFilesystem::encodeEntryAttributes(const File::Attributes& attributes, Dir* entry,
98 bool creating) {
99 entry->DIR_WrtDate = HOST_TO_LITTLE16(getFatDate(attributes.modified));
100 entry->DIR_WrtTime = HOST_TO_LITTLE16(getFatTime(attributes.modified));
101 entry->DIR_LstAccDate = HOST_TO_LITTLE16(getFatDate(attributes.accessed));
102 if (creating || !entry->DIR_CrtDate) {
103 // The VFS changed field is ctime. FAT birth time must survive later changes.
104 const Time::Timestamp created = attributes.changed ? attributes.changed : Time::getTime();
105 entry->DIR_CrtDate = HOST_TO_LITTLE16(getFatDate(created));
106 entry->DIR_CrtTime = HOST_TO_LITTLE16(getFatTime(created));
107 const auto civil = fatCivilTime(created);
108 entry->DIR_CrtTimeTenth = (civil.second % 2) * 100;
109 }
110}
111
112bool FatFilesystem::writePendingAttributes(const PendingAttributes& pending) {
113 const uint32_t cluster = pending.slot >> 32;
114 const uint32_t offset = pending.slot;
115 Dir* entry = getDirectoryEntry(cluster, offset);
116 if (!entry)
117 return false;
118 if (entry->DIR_Name[0] == 0 || entry->DIR_Name[0] == 0xE5 ||
119 (entry->DIR_Attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) {
120 delete entry;
121 return false;
122 }
123 encodeEntryAttributes(pending.attributes, entry);
124 const bool succeeded = writeDirectoryEntry(entry, cluster, offset);
125 delete entry;
126 return succeeded;
127}
128
129bool FatFilesystem::syncNodeAttributes(File* file) {
130 if (m_bReadOnly)
131 return !m_IoFailed;
132 if (file == m_pRoot || isNodeUnlinked(file))
133 return true;
134 uint32_t cluster = 0, offset = 0;
135 if (file->isDirectory()) {
136 auto* directory = static_cast<FatDirectory*>(file);
137 cluster = directory->getDirCluster();
138 offset = directory->getDirOffset();
139 } else if (file->isSymlink()) {
140 auto* symlink = static_cast<FatSymlink*>(file);
141 cluster = symlink->getDirCluster();
142 offset = symlink->getDirOffset();
143 } else {
144 auto* regular = static_cast<FatFile*>(file);
145 cluster = regular->getDirCluster();
146 offset = regular->getDirOffset();
147 }
148 if (cluster == 0xdeadbeef || offset == 0xbeefdead)
149 return true;
150 const uint64_t slot = (uint64_t(cluster) << 32) | offset;
151 PendingAttributes* pending = m_PendingAttributes;
152 while (pending && pending->slot != slot)
153 pending = pending->next;
154 if (!pending) {
155 pending = new PendingAttributes;
156 if (!pending) {
157 m_IoFailed = true;
158 SYSCALL_ERROR(OutOfMemory);
159 return false;
160 }
161 pending->slot = slot;
162 pending->next = m_PendingAttributes;
163 m_PendingAttributes = pending;
164 }
165 pending->attributes = file->File::getAttributes();
166 if (!pending->attributes.changed)
167 pending->attributes.changed = Time::getTime();
168 if (!writePendingAttributes(*pending))
169 return false;
170 discardPendingAttributes(cluster, offset);
171 return true;
172}
173
174bool FatFilesystem::syncPendingAttributes() {
175 if (m_bReadOnly)
176 return !m_IoFailed && !m_PendingAttributes;
177 bool succeeded = !m_IoFailed;
178 PendingAttributes** link = &m_PendingAttributes;
179 while (*link) {
180 PendingAttributes* pending = *link;
181 if (!writePendingAttributes(*pending)) {
182 succeeded = false;
183 link = &pending->next;
184 continue;
185 }
186 *link = pending->next;
187 delete pending;
188 }
189 return succeeded;
190}
191
192void FatFilesystem::movePendingAttributes(uint32_t oldCluster, uint32_t oldOffset,
193 uint32_t newCluster, uint32_t newOffset) {
194 const uint64_t oldSlot = (uint64_t(oldCluster) << 32) | oldOffset;
195 const uint64_t newSlot = (uint64_t(newCluster) << 32) | newOffset;
196 if (oldSlot == newSlot)
197 return;
198 discardPendingAttributes(newCluster, newOffset);
199 for (PendingAttributes* pending = m_PendingAttributes; pending; pending = pending->next) {
200 if (pending->slot == oldSlot) {
201 pending->slot = newSlot;
202 return;
203 }
204 }
205}
206
207void FatFilesystem::discardPendingAttributes(uint32_t cluster, uint32_t offset) {
208 const uint64_t slot = (uint64_t(cluster) << 32) | offset;
209 PendingAttributes** link = &m_PendingAttributes;
210 while (*link) {
211 PendingAttributes* pending = *link;
212 if (pending->slot == slot) {
213 *link = pending->next;
214 delete pending;
215 return;
216 }
217 link = &pending->next;
218 }
219}
220
221void FatFilesystem::clearPendingAttributes() {
222 while (m_PendingAttributes) {
223 PendingAttributes* pending = m_PendingAttributes;
224 m_PendingAttributes = pending->next;
225 delete pending;
226 }
227}
228
229void FatFilesystem::fileAttributeChanged(File* file) {
231 if (!file->isDirectory() && !file->isSymlink()) {
232 auto* regular = static_cast<FatFile*>(file);
233 const auto attributes = file->File::getAttributes();
234 const auto* state = regular->m_State;
235 const bool changed = fatTimestamp(state->accessed) / SecondsPerDay !=
236 fatTimestamp(attributes.accessed) / SecondsPerDay ||
237 fatTimestamp(state->modified) / 2 != fatTimestamp(attributes.modified) / 2;
238 regular->m_State->accessed = attributes.accessed;
239 regular->m_State->modified = attributes.modified;
240 regular->m_State->changed = attributes.changed;
241 {
242 LockGuard<Mutex> registry(m_StateLock);
243 for (auto* alias = regular->m_State->aliases; alias; alias = alias->m_NextAlias)
244 alias->copyStateAttributes();
245 }
246 regular->m_MetadataDirty = regular->m_MetadataDirty || changed;
247 return;
248 }
249 if (!syncNodeAttributes(file))
250 WARNING("FAT: unable to persist file attributes");
251}
252
253uint64_t FatFile::maximumFileSize() const {
254 return UINT32_MAX;
255}
256
257void FatFile::fileAttributeChanged() {
258 static_cast<FatFilesystem*>(m_pFilesystem)->fileAttributeChanged(this);
259}
260
262 static_cast<FatFilesystem*>(m_pFilesystem)->fileAttributeChanged(this);
263}
264
265void FatSymlink::fileAttributeChanged() {
266 static_cast<FatFilesystem*>(m_pFilesystem)->fileAttributeChanged(this);
267}
268
269bool FatFilesystem::syncNode(File* file) {
271 if (m_bReadOnly)
272 return !m_IoFailed;
273 const bool metadata = syncNodeAttributes(file);
274 const bool allocation = syncFat();
275 return m_pDisk && m_pDisk->syncData() && metadata && allocation;
276}
277
279 return static_cast<FatFilesystem*>(m_pFilesystem)->syncNode(this);
280}
281
283 return static_cast<FatFilesystem*>(m_pFilesystem)->syncNode(this);
284}
virtual MUST_USE_RESULT bool syncData()
Definition Disk.cc:299
void fileAttributeChanged() override
uint64_t maximumFileSize() const override
Mutex m_FileMutationLock
Dir * getDirectoryEntry(uint32_t clus, uint32_t offset) const
bool getUuid(String &) const override
bool writeDirectoryEntry(Dir *dir, uint32_t clus, uint32_t offset)
Definition File.h:74
virtual bool isSymlink()
Definition File.cc:717
virtual bool isDirectory()
Definition File.cc:721
Disk * m_pDisk
Definition Filesystem.h:180
bool m_bReadOnly
Definition Filesystem.h:178
Definition ext2.h:201