The Pedigree Project 0.1
Ext2Rename.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, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 */
8
9#include "pedigree/kernel/LockGuard.h"
10#include "pedigree/kernel/syscallError.h"
11#include "pedigree/kernel/utilities/utility.h"
12
13#include <stddef.h>
14
15#include "Ext2Directory.h"
16#include "Ext2File.h"
17#include "Ext2Filesystem.h"
18#include "Ext2Symlink.h"
19#include "ext2.h"
20
21Ext2Directory::RenameRecord::RenameRecord()
22 : owner(nullptr),
23 block(0),
24 buffer(0),
25 offset(0),
26 entry(nullptr),
27 split(nullptr),
28 splitLength(0),
29 length(0) {}
30
31Ext2Directory::RenameRecord::~RenameRecord() {
32 if (buffer) {
33 owner->m_pExt2Fs->unpinBlock(block);
34 }
35}
36
37bool Ext2Directory::prepareRenameRecord(const String& name, uint32_t inode, RenameRecord& record) {
38 for (size_t offset = 0; offset < m_nSize;) {
39 ParsedEntry entry;
40 if (readEntry(offset, entry) != ReadStatus::Complete) {
41 SYSCALL_ERROR(IoError);
42 return false;
43 }
44 if (entry.inode == inode && name.length() == entry.nameLength &&
45 !StringCompareN(name.cstr(), entry.name, entry.nameLength)) {
46 const size_t blockIndex = offset / m_pExt2Fs->m_BlockSize;
47 if (!ensureBlockLoaded(blockIndex)) {
48 SYSCALL_ERROR(IoError);
49 return false;
50 }
51 record.owner = this;
52 record.block = m_Blocks[blockIndex];
53 record.buffer = m_pExt2Fs->readBlock(record.block);
54 if (!record.buffer) {
55 SYSCALL_ERROR(IoError);
56 return false;
57 }
58 record.offset = offset;
59 record.entry = reinterpret_cast<Dir*>(record.buffer + offset % m_pExt2Fs->m_BlockSize);
60 record.length = entry.recordLength;
61 return true;
62 }
63 offset += entry.recordLength;
64 }
65 SYSCALL_ERROR(DoesNotExist);
66 return false;
67}
68
69bool Ext2Directory::prepareRenameSpace(const String& name, RenameRecord& record,
70 const RenameRecord* source) {
71 const size_t required = (offsetof(Dir, d_name) + name.length() + 3) & ~static_cast<size_t>(3);
72 if (source && source->length >= required) {
73 record.owner = this;
74 record.block = source->block;
75 record.buffer = m_pExt2Fs->readBlock(record.block);
76 if (!record.buffer) {
77 SYSCALL_ERROR(IoError);
78 return false;
79 }
80 record.offset = source->offset;
81 record.entry = reinterpret_cast<Dir*>(record.buffer + source->offset % m_pExt2Fs->m_BlockSize);
82 record.length = source->length;
83 return true;
84 }
85 for (size_t offset = 0; offset < m_nSize;) {
86 ParsedEntry entry;
87 if (readEntry(offset, entry) != ReadStatus::Complete) {
88 SYSCALL_ERROR(IoError);
89 return false;
90 }
91 const size_t minimum = (offsetof(Dir, d_name) + entry.nameLength + 3) & ~static_cast<size_t>(3);
92 const bool usable =
93 !entry.inode ? entry.recordLength >= required : entry.recordLength - minimum >= required;
94 if (usable && (!source || source->offset != offset)) {
95 const size_t blockIndex = offset / m_pExt2Fs->m_BlockSize;
96 if (!ensureBlockLoaded(blockIndex)) {
97 SYSCALL_ERROR(IoError);
98 return false;
99 }
100 record.owner = this;
101 record.block = m_Blocks[blockIndex];
102 record.buffer = m_pExt2Fs->readBlock(record.block);
103 if (!record.buffer) {
104 SYSCALL_ERROR(IoError);
105 return false;
106 }
107 record.offset = offset;
108 record.entry = reinterpret_cast<Dir*>(record.buffer + offset % m_pExt2Fs->m_BlockSize);
109 record.length = entry.recordLength;
110 if (entry.inode) {
111 record.split = record.entry;
112 record.splitLength = minimum;
113 record.length -= minimum;
114 record.entry = reinterpret_cast<Dir*>(reinterpret_cast<uintptr_t>(record.entry) + minimum);
115 }
116 return true;
117 }
118 offset += entry.recordLength;
119 }
120
121 const size_t oldBlocks = m_Blocks.count();
122 const size_t entries = m_pExt2Fs->m_BlockSize / sizeof(uint32_t);
123 if (oldBlocks >= 12 + entries + entries * entries) {
124 SYSCALL_ERROR(FileTooLarge);
125 return false;
126 }
127 record.owner = this;
128 record.block = m_pExt2Fs->findFreeBlock(getInodeNumber());
129 if (!record.block) {
130 return false;
131 }
132 record.buffer = m_pExt2Fs->readBlock(record.block);
133 if (!record.buffer) {
134 m_pExt2Fs->releaseBlock(record.block, m_InodeNumber);
135 SYSCALL_ERROR(IoError);
136 return false;
137 }
138 if (!addBlock(record.block)) {
139 m_pExt2Fs->releaseBlock(record.block, m_InodeNumber);
140 trimToBlocks(oldBlocks);
141 return false;
142 }
143 ByteSet(reinterpret_cast<void*>(record.buffer), 0, m_pExt2Fs->m_BlockSize);
144 record.entry = reinterpret_cast<Dir*>(record.buffer);
145 record.length = m_pExt2Fs->m_BlockSize;
146 record.entry->d_reclen = HOST_TO_LITTLE16(record.length);
147 record.offset = oldBlocks * m_pExt2Fs->m_BlockSize;
148 m_nSize = m_Blocks.count() * m_pExt2Fs->m_BlockSize;
149 setSize(m_nSize);
151 m_pExt2Fs->writeBlock(record.block);
152 return true;
153}
154
155bool Ext2Filesystem::renameNode(Directory* oldParent, const String& oldName, File* source,
156 Directory* newParent, const String& newName, File* replaced) {
157 LockGuard<Mutex> quotaNamespace(m_QuotaNamespaceLock);
158 if (isQuotaFile(source->getInode()) || (replaced && isQuotaFile(replaced->getInode()))) {
159 SYSCALL_ERROR(NotEnoughPermissions);
160 return false;
161 }
162 if (oldName.length() > 255 || newName.length() > 255) {
163 SYSCALL_ERROR(NameTooLong);
164 return false;
165 }
166 Ext2Directory* oldDirectory = static_cast<Ext2Directory*>(oldParent);
167 Ext2Directory* newDirectory = static_cast<Ext2Directory*>(newParent);
168 Ext2Directory* movedDirectory =
169 source->isDirectory() ? static_cast<Ext2Directory*>(source) : nullptr;
170 Ext2Directory* replacedDirectory =
171 replaced && replaced->isDirectory() ? static_cast<Ext2Directory*>(replaced) : nullptr;
172 const bool oldFirst =
173 reinterpret_cast<uintptr_t>(oldDirectory) < reinterpret_cast<uintptr_t>(newDirectory);
174 Ext2Directory* first = oldFirst ? oldDirectory : newDirectory;
175 Ext2Directory* second = oldFirst ? newDirectory : oldDirectory;
176 LockGuard<Mutex> firstGuard(first->m_DirectoryLock);
177 LockGuard<Mutex> secondGuard(second->m_DirectoryLock, second != first);
178 LockGuard<Mutex> movedGuard(
179 movedDirectory ? movedDirectory->m_DirectoryLock : first->m_DirectoryLock,
180 movedDirectory != nullptr);
181 LockGuard<Mutex> replacedGuard(
182 replacedDirectory ? replacedDirectory->m_DirectoryLock : first->m_DirectoryLock,
183 replacedDirectory != nullptr);
184 if (oldDirectory->m_Removed || newDirectory->m_Removed ||
185 (movedDirectory && movedDirectory->m_Removed) ||
186 (replacedDirectory && replacedDirectory->m_Removed)) {
187 SYSCALL_ERROR(DoesNotExist);
188 return false;
189 }
190 if (movedDirectory && oldDirectory != newDirectory && !replacedDirectory &&
191 LITTLE_TO_HOST16(newDirectory->Ext2Node::getInode()->i_links_count) == 0xffff) {
192 SYSCALL_ERROR(TooManyLinks);
193 return false;
194 }
195
198 Ext2Directory::RenameRecord parentRecord;
199 Ext2Directory::RenameRecord replacedDot;
200 Ext2Directory::RenameRecord replacedParent;
201 if (!oldDirectory->prepareRenameRecord(oldName, source->getInode(), oldRecord)) {
202 return false;
203 }
204 if (movedDirectory && oldDirectory != newDirectory &&
205 !movedDirectory->prepareRenameRecord(String(".."), oldDirectory->getInodeNumber(),
206 parentRecord)) {
207 return false;
208 }
209 if (replacedDirectory && (!replacedDirectory->prepareRenameRecord(
210 String("."), replacedDirectory->getInodeNumber(), replacedDot) ||
211 !replacedDirectory->prepareRenameRecord(
212 String(".."), newDirectory->getInodeNumber(), replacedParent))) {
213 return false;
214 }
215 if (replaced) {
216 if (!newDirectory->prepareRenameRecord(newName, replaced->getInode(), newRecord)) {
217 return false;
218 }
219 } else if (!newDirectory->prepareRenameSpace(
220 newName, newRecord, oldDirectory == newDirectory ? &oldRecord : nullptr)) {
221 return false;
222 }
223
224 if (oldRecord.entry != newRecord.entry) {
225 ByteSet(oldRecord.entry, 0, oldRecord.length);
226 oldRecord.entry->d_reclen = HOST_TO_LITTLE16(oldRecord.length);
227 }
228 if (newRecord.split) {
229 newRecord.split->d_reclen = HOST_TO_LITTLE16(newRecord.splitLength);
230 }
231 ByteSet(newRecord.entry, 0, newRecord.length);
232 newRecord.entry->d_inode = HOST_TO_LITTLE32(source->getInode());
233 newRecord.entry->d_reclen = HOST_TO_LITTLE16(newRecord.length);
234 newRecord.entry->d_namelen = newName.length();
235 if (checkRequiredFeature(2)) {
236 newRecord.entry->d_file_type = source->isDirectory() ? EXT2_DIRECTORY
237 : source->isSymlink() ? EXT2_SYMLINK
238 : EXT2_FILE;
239 }
240 MemoryCopy(newRecord.entry->d_name, newName.cstr(), newName.length());
241 if (parentRecord.entry) {
242 parentRecord.entry->d_inode = HOST_TO_LITTLE32(newDirectory->getInodeNumber());
243 writeBlock(parentRecord.block);
244 oldDirectory->queueSyncDependency(parentRecord.block);
245 newDirectory->queueSyncDependency(parentRecord.block);
246 }
247 writeBlock(oldRecord.block);
248 if (newRecord.block != oldRecord.block) {
249 writeBlock(newRecord.block);
250 }
251 if (replacedDirectory) {
252 replacedDirectory->m_Removed = true;
253 ByteSet(replacedDot.entry, 0, replacedDot.length);
254 replacedDot.entry->d_reclen = HOST_TO_LITTLE16(replacedDot.length);
255 ByteSet(replacedParent.entry, 0, replacedParent.length);
256 replacedParent.entry->d_reclen = HOST_TO_LITTLE16(replacedParent.length);
257 writeBlock(replacedDot.block);
258 writeBlock(replacedParent.block);
259 releaseInode(newDirectory->getInodeNumber(), newDirectory);
260 releaseInode(replacedDirectory->getInodeNumber(), replacedDirectory);
261 releaseInode(replacedDirectory->getInodeNumber(), replacedDirectory);
262 {
264 const uint32_t group = (replacedDirectory->getInodeNumber() - 1) /
265 LITTLE_TO_HOST32(m_pSuperblock->s_inodes_per_group);
266 GroupDesc* descriptor = m_pGroupDescriptors[group];
267 descriptor->bg_used_dirs_count =
268 HOST_TO_LITTLE16(LITTLE_TO_HOST16(descriptor->bg_used_dirs_count) - 1);
269 const uint32_t descriptorBlock = LITTLE_TO_HOST32(m_pSuperblock->s_first_data_block) + 1;
270 writeBlock(descriptorBlock + (group * sizeof(GroupDesc)) / m_BlockSize);
271 }
272 } else if (replaced) {
273 Ext2Node* node = replaced->isSymlink()
274 ? static_cast<Ext2Node*>(static_cast<Ext2Symlink*>(replaced))
275 : static_cast<Ext2Node*>(static_cast<Ext2File*>(replaced));
276 releaseInode(node->getInodeNumber(), node);
277 }
278 if (parentRecord.entry) {
279 increaseInodeRefcount(newDirectory->getInodeNumber());
280 releaseInode(oldDirectory->getInodeNumber(), oldDirectory);
281 }
282 return true;
283}
void fileAttributeChanged() override
void writeBlock(uint32_t block)
uintptr_t readBlock(uint32_t block)
virtual bool renameNode(Directory *oldParent, const String &oldName, File *source, Directory *newParent, const String &newName, File *replaced)
Definition File.h:74
virtual bool isSymlink()
Definition File.cc:732
virtual bool isDirectory()
Definition File.cc:736
Mutex m_WriteLock
Definition File.h:669
size_t count() const
Definition Vector.h:270
Definition ext2.h:201