1 using System;
2 using System.IO;
3 using cmsx.machine;
4 using cmsx.util;
5
6 namespace cmsx.kernel
7 {
8 public enum PathToINodeFlags : byte
9 {
10 none = 0u,
11 createEntry = 1u << 0u,
12 ignoreMountPoint = 1u << 1u
13 }
14
15 public const int statBufSize = 56;
16
17 public abstract class FileSystem
18 {
19 public nothrow FileSystem(const string& name_, int index_, bool readOnly_) : name(name_), index(index_), readOnly(readOnly_)
20 {
21 }
22 public virtual default ~FileSystem();
23 public virtual nothrow bool IsRootFileSystem() const
24 {
25 return false;
26 }
27 public virtual nothrow bool IsHostFileSystem() const
28 {
29 return false;
30 }
31 public abstract void Init();
32 public int Create(Process* process, const string& path, int mode, bool truncate)
33 {
34 if (Log())
35 {
36 LogMessage("fs.create", path);
37 }
38 DirectorySlot freeDirectorySlot;
39 INode* parent = null;
40 string name;
41 INode* inode = GetINodeManager()->PathToINode(process, path, PathToINodeFlags.createEntry, parent, freeDirectorySlot, name);
42 if (inode != null && inode->Type() != FileType.regular)
43 {
44 throw SystemError(EINVAL, "cannot create: file type of '" + path + "' is " + FileTypeStr(inode->Type()));
45 }
46 INodePutter parentPutter(parent);
47 INodePutter inodePutter;
48 if (inode != parent)
49 {
50 inodePutter.ResetINode(inode);
51 }
52 if (name.IsEmpty())
53 {
54 throw SystemError(EINVAL, "invalid path name '" + path + "'");
55 }
56 if (parent == null)
57 {
58 throw SystemError(EINVAL, "parent path of path '" + path + "' not found ");
59 }
60 FileSystem* fs = GetMountTable().GetFileSystem(parent->Key().fsNumber);
61 if (fs->IsReadOnly())
62 {
63 throw SystemError(EINVAL, "cannot create file " + path + "': file system '" + fs->Name() + "' is read-only");
64 }
65 if (inode != null)
66 {
67 if (truncate)
68 {
69 GetBlockManager()->FreeBlocks(inode);
70 inode->SetFileSize(0);
71 inode->SetMTime();
72 }
73 }
74 else
75 {
76 try
77 {
78 parent->CheckPermissions(process->uid, process->gid, Access.write);
79 }
80 catch (const SystemError& ex)
81 {
82 throw SystemError(EPERM, "cannot write to directory '" + Path.GetDirectoryName(path) + "' using uid " + ToString(process->uid) + ": " + ex.message);
83 }
84 FileSystem* fs = GetMountTable().GetFileSystem(parent->Key().fsNumber);
85 int inodeNumber = fs->GetFreeINodeNumber();
86 INodeKey key(inodeNumber, parent->Key().fsNumber);
87 inode = GetINodeManager()->GetINode(key);
88 inodePutter.ResetINode(inode);
89 inode->SetType(FileType.regular);
90 if (mode == 0)
91 {
92 mode = EncodeMode(INode.Flags.none, FileType.regular, cast<Access>(Access.read | Access.write), cast<Access>(Access.read | Access.write), cast<Access>(Access.read | Access.write));
93 }
94 mode = mode & ~process->umask;
95 inode->SetAccessMode(mode);
96 inode->SetUID(process->uid);
97 inode->SetGID(process->gid);
98 inode->SetFileSize(0);
99 inode->SetMTime();
100 Block* block = null;
101 if (freeDirectorySlot.blockNumber != invalidBlockNumber)
102 {
103 block = GetBlockManager()->ReadBlock(BlockKey(freeDirectorySlot.blockNumber, parent->Key().fsNumber), null);
104 }
105 else
106 {
107 int blockNumber = 0;
108 int blockOffset = 0;
109 GetBlockManager()->GetBlockNumber(parent, freeDirectorySlot.offset, blockNumber, blockOffset, true);
110 block = GetBlockManager()->GetBlock(BlockKey(blockNumber, parent->Key().fsNumber), null);
111 block->Clear();
112 }
113 BlockPutter blockPutter(block);
114 DirectoryBlock* directoryBlock = cast<DirectoryBlock*>(block);
115 bool created = false;
116 for (int i = 0; i < numDirectoryEntriesInBlock; ++i;)
117 {
118 DirectoryEntry entry = directoryBlock->GetDirectoryEntry(i);
119 if (entry.inodeNumber == 0)
120 {
121 entry.inodeNumber = inode->Key().inodeNumber;
122 entry.name = name.Substring(0, nameMax - 1);
123 directoryBlock->SetDirectoryEntry(i, entry);
124 if (freeDirectorySlot.blockNumber == invalidBlockNumber || freeDirectorySlot.offset >= parent->GetFileSize())
125 {
126 parent->SetFileSize(parent->GetFileSize() + directoryEntrySize);
127 }
128 created = true;
129 break;
130 }
131 }
132 if (created)
133 {
134 directoryBlock->SetFlag(Block.Flags.dirty);
135 GetBlockManager()->WriteBlock(directoryBlock, null);
136 parent->SetMTime();
137 }
138 else
139 {
140 throw SystemError(EFAIL, "no room for directory entry");
141 }
142 }
143 INodeLock lock(inode);
144 int fd = process->fileTable.GetEmptyFileSlot();
145 FilePtr* filePtr = new FilePtr(inode, cast<OpenFlags>(OpenFlags.read | OpenFlags.write | OpenFlags.create | OpenFlags.truncate));
146 RegularFile* regularFile = new RegularFile(name, filePtr);
147 process->fileTable.SetFile(fd, regularFile);
148 inodePutter.ResetINode();
149 return fd;
150 }
151 public int Open(Process* process, const string& path, OpenFlags flags, int mode)
152 {
153 if (Log())
154 {
155 LogMessage("fs.open", path);
156 }
157 if ((flags & OpenFlags.create) != OpenFlags.none)
158 {
159 return Create(process, path, mode, (flags & OpenFlags.truncate) != OpenFlags.none);
160 }
161 INode* inode = GetINodeManager()->PathToINode(process, path);
162 if (inode == null)
163 {
164 throw SystemError(ENOENT, "path '" + path + "' does not exist");
165 }
166 INodePutter inodePutter(inode);
167 if (inode->Type() != FileType.regular && (flags & OpenFlags.write) != OpenFlags.none)
168 {
169 throw SystemError(EINVAL, "cannot open '" + path + "': for writing, file type is " + FileTypeStr(inode->Type()));
170 }
171 INodeLock lock(inode);
172 FileSystem* fs = GetMountTable().GetFileSystem(inode->Key().fsNumber);
173 if (fs->IsReadOnly() && (flags & (OpenFlags.write | OpenFlags.append)) != OpenFlags.none)
174 {
175 throw SystemError(EINVAL, "cannot open '" + path + "' for writing: file system '" + fs->Name() + "' is read-only");
176 }
177 return fs->Open(process, inode, flags, path, inodePutter);
178 }
179 public virtual int Open(Process* process, INode* inode, OpenFlags flags, const string& path, INodePutter& inodePutter)
180 {
181 int fd = process->fileTable.GetEmptyFileSlot();
182 FilePtr* filePtr = new FilePtr(inode, flags);
183 RegularFile* regularFile = new RegularFile(Path.GetFileName(path), filePtr);
184 process->fileTable.SetFile(fd, regularFile);
185 inodePutter.ResetINode();
186 return fd;
187 }
188 public void Link(Process* process, const string& sourcePath, const string& targetPath)
189 {
190 if (Log())
191 {
192 LogMessage("fs.link", sourcePath + "->" + targetPath);
193 }
194 INode* source = GetINodeManager()->PathToINode(process, sourcePath);
195 if (source == null)
196 {
197 throw SystemError(ENOENT, "source path '" + sourcePath + "' does not exist");
198 }
199 if (Log())
200 {
201 LogMessage("fs.link", "source=" + source->ToString());
202 }
203 INodePutter sourcePutter(source);
204 DirectorySlot freeDirectorySlot;
205 INode* parent = null;
206 string name;
207 INode* inode = GetINodeManager()->PathToINode(process, targetPath, PathToINodeFlags.createEntry, parent, freeDirectorySlot, name);
208 INodePutter parentPutter(parent);
209 if (inode != null)
210 {
211 throw SystemError(EEXIST, "target path '" + targetPath + "' already exists");
212 }
213 if (name.IsEmpty())
214 {
215 throw SystemError(EINVAL, "invalid path name '" + targetPath + "'");
216 }
217 if (parent == null)
218 {
219 throw SystemError(EINVAL, "parent path of path '" + targetPath + "' not found ");
220 }
221 if (Log())
222 {
223 LogMessage("fs.link", "parent=" + parent->ToString());
224 }
225 try
226 {
227 parent->CheckPermissions(process->uid, process->gid, Access.write);
228 }
229 catch (const SystemError& ex)
230 {
231 throw SystemError(EPERM, "cannot write to directory '" + Path.GetDirectoryName(targetPath) + "' using uid " + ToString(process->uid) + ": " + ex.message);
232 }
233 FileSystem* fs = GetMountTable().GetFileSystem(parent->Key().fsNumber);
234 if (fs->IsReadOnly())
235 {
236 throw SystemError(EINVAL, "cannot link to path '" + targetPath + "': target file system '" + fs->Name() + "' is read-only");
237 }
238 if (source->Key().fsNumber != parent->Key().fsNumber)
239 {
240 throw SystemError(EINVAL, "cannot link across file systems: source file system number: " + ToString(source->Key().fsNumber) + ", target file system number: " + ToString(parent->Key().fsNumber));
241 }
242 Block* block = null;
243 if (freeDirectorySlot.blockNumber != invalidBlockNumber)
244 {
245 block = GetBlockManager()->ReadBlock(BlockKey(freeDirectorySlot.blockNumber, parent->Key().fsNumber), null);
246 }
247 else
248 {
249 int blockNumber = 0;
250 int blockOffset = 0;
251 GetBlockManager()->GetBlockNumber(parent, freeDirectorySlot.offset, blockNumber, blockOffset, true);
252 block = GetBlockManager()->GetBlock(BlockKey(blockNumber, parent->Key().fsNumber), null);
253 block->Clear();
254 }
255 BlockPutter blockPutter(block);
256 DirectoryBlock* directoryBlock = cast<DirectoryBlock*>(block);
257 bool created = false;
258 for (int i = 0; i < numDirectoryEntriesInBlock; ++i;)
259 {
260 DirectoryEntry entry = directoryBlock->GetDirectoryEntry(i);
261 if (entry.inodeNumber == 0)
262 {
263 entry.inodeNumber = source->Key().inodeNumber;
264 entry.name = name.Substring(0, nameMax - 1);
265 directoryBlock->SetDirectoryEntry(i, entry);
266 if (freeDirectorySlot.blockNumber == invalidBlockNumber || freeDirectorySlot.offset >= parent->GetFileSize())
267 {
268 parent->SetFileSize(parent->GetFileSize() + directoryEntrySize);
269 }
270 created = true;
271 break;
272 }
273 }
274 if (created)
275 {
276 directoryBlock->SetFlag(Block.Flags.dirty);
277 GetBlockManager()->WriteBlock(directoryBlock, null);
278 parent->SetMTime();
279 parent->SetFlag(INode.Flags.dirty);
280 source->SetNumLinks(source->GetNumLinks() + 1);
281 }
282 else
283 {
284 throw SystemError(EFAIL, "no room for target directory entry");
285 }
286 }
287 public void Unlink(Process* process, const string& path)
288 {
289 if (Log())
290 {
291 LogMessage("fs.unlink", path);
292 }
293 string parentPath = Path.GetDirectoryName(path);
294 if (parentPath.IsEmpty())
295 {
296 if (path.StartsWith("/"))
297 {
298 parentPath = "/";
299 }
300 else
301 {
302 parentPath = ".";
303 }
304 }
305 string entryName = Path.GetFileName(path);
306 INode* parent = GetINodeManager()->PathToINode(process, parentPath);
307 if (parent == null)
308 {
309 throw SystemError(ENOENT, "directory '" + parentPath + "' does not exist");
310 }
311 INodePutter parentPutter(parent);
312 parent->ResetFlag(INode.Flags.locked);
313 INode* inode = GetINodeManager()->PathToINode(process, path);
314 parent->SetFlag(INode.Flags.locked);
315 if (inode == null)
316 {
317 throw SystemError(ENOENT, "path '" + path + "' does not exist");
318 }
319 FileSystem* fs = GetMountTable().GetFileSystem(inode->Key().fsNumber);
320 if (fs->IsReadOnly())
321 {
322 throw SystemError(EINVAL, "cannot unlink: file system '" + fs->Name() + "' is read-only");
323 }
324 try
325 {
326 parent->CheckPermissions(process->uid, process->gid, Access.write);
327 }
328 catch (const SystemError& ex)
329 {
330 throw SystemError(EPERM, "cannot write to directory '" + parentPath + "' using uid " + ToString(process->uid) + ": " + ex.message);
331 }
332 INodePutter inodePutter(inode);
333 if (Log())
334 {
335 LogMessage("fs.unlink", "parent=" + parent->ToString());
336 LogMessage("fs.unlink", "inode=" + inode->ToString());
337 }
338 long offset = 0;
339 bool entryReset = false;
340 while (!entryReset && offset < parent->GetFileSize())
341 {
342 int blockNumber = 0;
343 int blockOffset = 0;
344 GetBlockManager()->GetBlockNumber(parent, offset, blockNumber, blockOffset, false);
345 if (blockNumber == 0)
346 {
347 throw SystemError(EFAIL, "directory block number not found from inode of '" + parentPath + "'");
348 }
349 Block* block = GetBlockManager()->ReadBlock(BlockKey(blockNumber, parent->Key().fsNumber), null);
350 BlockPutter putter(block);
351 DirectoryBlock* directoryBlock = cast<DirectoryBlock*>(block);
352 for (int index = blockOffset / directoryEntrySize; index < numDirectoryEntriesInBlock && offset < parent->GetFileSize(); ++index;)
353 {
354 DirectoryEntry entry = directoryBlock->GetDirectoryEntry(index);
355 if (entry.name == entryName)
356 {
357 entry.inodeNumber = 0;
358 directoryBlock->SetDirectoryEntry(index, entry);
359 block->SetFlag(Block.Flags.dirty);
360 GetBlockManager()->WriteBlock(block, null);
361 parent->SetFlag(INode.Flags.dirty);
362 entryReset = true;
363 break;
364 }
365 offset = offset + directoryEntrySize;
366 }
367 }
368 if (!entryReset)
369 {
370 throw SystemError(EFAIL, "directory entry for '" + path + "' not found");
371 }
372 inode->SetNumLinks(inode->GetNumLinks() - 1);
373 if (Log())
374 {
375 LogMessage("fs.unlink", "return.inode=" + inode->ToString());
376 }
377 }
378 public void Rename(Process* process, const string& sourcePath, const string& targetPath)
379 {
380 if (Log())
381 {
382 LogMessage("fs.rename", "source=" + sourcePath + ".target=" + targetPath);
383 }
384 string sourceParentPath = Path.GetDirectoryName(sourcePath);
385 if (sourceParentPath.IsEmpty())
386 {
387 if (sourcePath.StartsWith("/"))
388 {
389 sourceParentPath = "/";
390 }
391 else
392 {
393 sourceParentPath = ".";
394 }
395 }
396 string sourceEntryName = Path.GetFileName(sourcePath);
397 string targetParentPath = Path.GetDirectoryName(targetPath);
398 if (targetParentPath.IsEmpty())
399 {
400 if (targetPath.StartsWith("/"))
401 {
402 targetParentPath = "/";
403 }
404 else
405 {
406 targetParentPath = ".";
407 }
408 }
409 string targetEntryName = Path.GetFileName(targetPath);
410 bool sameDirectory = false;
411 if (GetFullPath(sourceParentPath) == GetFullPath(targetParentPath))
412 {
413 sameDirectory = true;
414 }
415 INode* sourceParent = GetINodeManager()->PathToINode(process, sourceParentPath);
416 if (sourceParent == null)
417 {
418 throw SystemError(ENOENT, "directory '" + sourceParentPath + "' does not exist");
419 }
420 INodePutter sourceParentPutter(sourceParent);
421 FileSystem* sourceFS = GetMountTable().GetFileSystem(sourceParent->Key().fsNumber);
422 if (sourceFS->IsReadOnly())
423 {
424 throw SystemError(EINVAL, "cannot rename: file system '" + sourceFS->Name() + "' is read-only");
425 }
426 try
427 {
428 sourceParent->CheckPermissions(process->uid, process->gid, Access.write);
429 }
430 catch (const SystemError& ex)
431 {
432 throw SystemError(EPERM, "cannot write to directory '" + sourceParentPath + "' using uid " + ToString(process->uid) + ": " + ex.message);
433 }
434 BlockPutter sourcePutter;
435 DirectoryBlock* sourceDirectoryBlock = null;
436 int sourceIndex = -1;
437 long sourceOffset = 0;
438 bool sourceEntryFound = false;
439 while (!sourceEntryFound && sourceOffset < sourceParent->GetFileSize())
440 {
441 int blockNumber = 0;
442 int blockOffset = 0;
443 GetBlockManager()->GetBlockNumber(sourceParent, sourceOffset, blockNumber, blockOffset, false);
444 if (blockNumber == 0)
445 {
446 throw SystemError(EFAIL, "directory block number not found from inode of '" + sourceParentPath + "'");
447 }
448 Block* block = GetBlockManager()->ReadBlock(BlockKey(blockNumber, sourceParent->Key().fsNumber), null);
449 BlockPutter putter(block);
450 DirectoryBlock* directoryBlock = cast<DirectoryBlock*>(block);
451 for (int index = blockOffset / directoryEntrySize; index < numDirectoryEntriesInBlock && sourceOffset < sourceParent->GetFileSize(); ++index;)
452 {
453 DirectoryEntry entry = directoryBlock->GetDirectoryEntry(index);
454 if (entry.name == sourceEntryName)
455 {
456 if (sameDirectory)
457 {
458 entry.name = targetEntryName.Substring(0, nameMax - 1);
459 directoryBlock->SetDirectoryEntry(index, entry);
460 directoryBlock->SetFlag(Block.Flags.dirty);
461 GetBlockManager()->WriteBlock(directoryBlock, null);
462 sourceParent->SetMTime();
463 sourceParent->SetFlag(INode.Flags.dirty);
464 return;
465 }
466 else
467 {
468 sourcePutter.Reset(putter.Release());
469 sourceDirectoryBlock = directoryBlock;
470 sourceIndex = index;
471 sourceEntryFound = true;
472 break;
473 }
474 }
475 sourceOffset = sourceOffset + directoryEntrySize;
476 }
477 }
478 DirectorySlot freeDirectorySlot;
479 INode* targetParent = null;
480 string name;
481 INode* target = GetINodeManager()->PathToINode(process, targetPath, PathToINodeFlags.createEntry, targetParent, freeDirectorySlot, name);
482 INodePutter targetParentPutter(targetParent);
483 INodePutter targetPutter;
484 if (target != null)
485 {
486 targetPutter.ResetINode(target);
487 }
488 if (targetParent == null)
489 {
490 throw SystemError(EINVAL, "directory path '" + targetParentPath + "' not found ");
491 }
492 try
493 {
494 targetParent->CheckPermissions(process->uid, process->gid, Access.write);
495 }
496 catch (const Exception& ex)
497 {
498 throw SystemError(EPERM, "cannot write to directory '" + targetParentPath + "' using uid " + ToString(process->uid) + ": " + ex.Message());
499 }
500 FileSystem* targetFS = GetMountTable().GetFileSystem(targetParent->Key().fsNumber);
501 if (targetFS->IsReadOnly())
502 {
503 throw SystemError(EINVAL, "cannot rename: target file system '" + targetFS->Name() + "' is read-only");
504 }
505 if (sourceParent->Key().fsNumber != targetParent->Key().fsNumber)
506 {
507 throw SystemError(EINVAL, "cannot rename across file systems: source file system number: " + ToString(sourceParent->Key().fsNumber) + ", target file system number: " + ToString(targetParent->Key().fsNumber));
508 }
509 if (target == null)
510 {
511 Block* targetBlock = null;
512 if (freeDirectorySlot.blockNumber != invalidBlockNumber)
513 {
514 targetBlock = GetBlockManager()->ReadBlock(BlockKey(freeDirectorySlot.blockNumber, targetParent->Key().fsNumber), null);
515 }
516 else
517 {
518 int blockNumber = 0;
519 int blockOffset = 0;
520 GetBlockManager()->GetBlockNumber(targetParent, freeDirectorySlot.offset, blockNumber, blockOffset, true);
521 targetBlock = GetBlockManager()->GetBlock(BlockKey(blockNumber, targetParent->Key().fsNumber), null);
522 targetBlock->Clear();
523 }
524 BlockPutter targetBlockPutter(targetBlock);
525 DirectoryBlock* targetDirectoryBlock = cast<DirectoryBlock*>(targetBlock);
526 bool created = false;
527 for (int i = 0; i < numDirectoryEntriesInBlock; ++i;)
528 {
529 DirectoryEntry targetEntry = targetDirectoryBlock->GetDirectoryEntry(i);
530 if (targetEntry.inodeNumber == 0)
531 {
532 DirectoryEntry sourceEntry = sourceDirectoryBlock->GetDirectoryEntry(sourceIndex);
533 targetEntry.inodeNumber = sourceEntry.inodeNumber;
534 targetEntry.name = targetEntryName.Substring(0, nameMax - 1);
535 targetDirectoryBlock->SetDirectoryEntry(i, targetEntry);
536 targetDirectoryBlock->SetFlag(Block.Flags.dirty);
537 if (freeDirectorySlot.blockNumber == invalidBlockNumber || freeDirectorySlot.offset >= targetParent->GetFileSize())
538 {
539 targetParent->SetFileSize(targetParent->GetFileSize() + directoryEntrySize);
540 }
541 created = true;
542 break;
543 }
544 }
545 if (created)
546 {
547 sourceDirectoryBlock->SetFlag(Block.Flags.dirty);
548 GetBlockManager()->WriteBlock(sourceDirectoryBlock, null);
549 targetDirectoryBlock->SetFlag(Block.Flags.dirty);
550 GetBlockManager()->WriteBlock(targetDirectoryBlock, null);
551 sourceParent->SetFlag(INode.Flags.dirty);
552 sourceParent->SetMTime();
553 targetParent->SetFlag(INode.Flags.dirty);
554 targetParent->SetMTime();
555 }
556 else
557 {
558 throw SystemError(EFAIL, "no room for target directory entry");
559 }
560 }
561 else
562 {
563 long offset = 0;
564 bool entrySet = false;
565 DirectoryBlock* targetDirectoryBlock = null;
566 while (!entrySet && offset < targetParent->GetFileSize())
567 {
568 int blockNumber = 0;
569 int blockOffset = 0;
570 GetBlockManager()->GetBlockNumber(targetParent, offset, blockNumber, blockOffset, false);
571 if (blockNumber == 0)
572 {
573 throw SystemError(EFAIL, "directory block number not found from inode of '" + targetParentPath + "'");
574 }
575 Block* targetBlock = GetBlockManager()->ReadBlock(BlockKey(blockNumber, targetParent->Key().fsNumber), null);
576 BlockPutter putter(targetBlock);
577 #assert(targetBlock is DirectoryBlock*);
578 targetDirectoryBlock = cast<DirectoryBlock*>(targetBlock);
579 for (int index = blockOffset / directoryEntrySize; index < numDirectoryEntriesInBlock && offset < targetParent->GetFileSize(); ++index;)
580 {
581 DirectoryEntry targetEntry = targetDirectoryBlock->GetDirectoryEntry(index);
582 if (targetEntry.inodeNumber == target->Key().inodeNumber)
583 {
584 target->SetNumLinks(target->GetNumLinks() - 1);
585 DirectoryEntry sourceEntry = sourceDirectoryBlock->GetDirectoryEntry(sourceIndex);
586 targetEntry.inodeNumber = sourceEntry.inodeNumber;
587 targetDirectoryBlock->SetDirectoryEntry(index, targetEntry);
588 entrySet = true;
589 break;
590 }
591 offset = offset + directoryEntrySize;
592 }
593 }
594 if (entrySet)
595 {
596 sourceDirectoryBlock->SetFlag(Block.Flags.dirty);
597 GetBlockManager()->WriteBlock(sourceDirectoryBlock, null);
598 targetDirectoryBlock->SetFlag(Block.Flags.dirty);
599 GetBlockManager()->WriteBlock(targetDirectoryBlock, null);
600 sourceParent->SetMTime();
601 targetParent->SetMTime();
602 }
603 else
604 {
605 throw SystemError(EFAIL, "directory entry for '" + targetPath + "' not found");
606 }
607 }
608 }
609 public void MakeDirectory(Process* process, const string& path, int mode)
610 {
611 if (Log())
612 {
613 LogMessage("fs.makedirectory", path);
614 }
615 if (process != null && path == "/")
616 {
617 throw SystemError(EEXIST, "directory '" + path + "' already exists");
618 }
619 DirectorySlot freeDirectorySlot;
620 INode* parent = null;
621 string name;
622 INode* inode = GetINodeManager()->PathToINode(process, path, PathToINodeFlags.createEntry, parent, freeDirectorySlot, name);
623 INodePutter parentPutter(parent);
624 if (Log())
625 {
626 if (parent != null)
627 {
628 LogMessage("fs.makedirectory", "parent=" + parent->ToString());
629 }
630 }
631 if (parent != null)
632 {
633 FileSystem* fs = GetMountTable().GetFileSystem(parent->Key().fsNumber);
634 if (fs->IsReadOnly())
635 {
636 throw SystemError(EINVAL, "cannot make directory: file system '" + fs->Name() + "' is read-only");
637 }
638 }
639 INodePutter inodePutter;
640 if (inode != parent)
641 {
642 inodePutter.ResetINode(inode);
643 }
644 if (path != "/")
645 {
646 if (name.IsEmpty())
647 {
648 throw SystemError(EINVAL, "invalid path name '" + path + "'");
649 }
650 if (inode != null)
651 {
652 throw SystemError(EEXIST, "directory '" + path + "' already exists");
653 }
654 }
655 if (process != null)
656 {
657 try
658 {
659 parent->CheckPermissions(process->uid, process->gid, Access.write);
660 }
661 catch (const SystemError& ex)
662 {
663 throw SystemError(EPERM, "cannot write to directory '" + Path.GetDirectoryName(path) + "' using uid " + ToString(process->uid) + ": " + ex.message);
664 }
665 }
666 if (!name.IsEmpty())
667 {
668 FileSystem* fs = GetMountTable().GetFileSystem(parent->Key().fsNumber);
669 int inodeNumber = fs->GetFreeINodeNumber();
670 INodeKey key(inodeNumber, parent->Key().fsNumber);
671 inode = GetINodeManager()->GetINode(key);
672 inodePutter.ResetINode(inode);
673 Block* block = null;
674 if (freeDirectorySlot.blockNumber != invalidBlockNumber)
675 {
676 block = GetBlockManager()->ReadBlock(BlockKey(freeDirectorySlot.blockNumber, parent->Key().fsNumber), null);
677 }
678 else
679 {
680 int blockNumber = 0;
681 int blockOffset = 0;
682 GetBlockManager()->GetBlockNumber(parent, freeDirectorySlot.offset, blockNumber, blockOffset, true);
683 block = GetBlockManager()->GetBlock(BlockKey(blockNumber, parent->Key().fsNumber), null);
684 block->Clear();
685 }
686 BlockPutter blockPutter(block);
687 DirectoryBlock* directoryBlock = cast<DirectoryBlock*>(block);
688 bool created = false;
689 for (int i = 0; i < numDirectoryEntriesInBlock; ++i;)
690 {
691 DirectoryEntry entry = directoryBlock->GetDirectoryEntry(i);
692 if (entry.inodeNumber == 0)
693 {
694 entry.inodeNumber = inode->Key().inodeNumber;
695 entry.name = name.Substring(0, nameMax - 1);
696 directoryBlock->SetDirectoryEntry(i, entry);
697 if (freeDirectorySlot.blockNumber == invalidBlockNumber || freeDirectorySlot.offset >= parent->GetFileSize())
698 {
699 parent->SetFileSize(parent->GetFileSize() + directoryEntrySize);
700 }
701 created = true;
702 break;
703 }
704 }
705 if (created)
706 {
707 directoryBlock->SetFlag(Block.Flags.dirty);
708 GetBlockManager()->WriteBlock(directoryBlock, null);
709 parent->SetMTime();
710 parent->SetFlag(INode.Flags.dirty);
711 }
712 else
713 {
714 throw SystemError(EFAIL, "no room for directory entry");
715 }
716 }
717 inode->SetType(FileType.directory);
718 if (path == "/" && process == null)
719 {
720 inode->SetUID(0);
721 inode->SetGID(0);
722 }
723 else
724 {
725 if (process == null)
726 {
727 inode->SetUID(0);
728 inode->SetGID(0);
729 }
730 else
731 {
732 inode->SetUID(process->uid);
733 inode->SetGID(process->gid);
734 }
735 }
736 if (mode == 0)
737 {
738 mode = EncodeMode(INode.Flags.none, FileType.directory,
739 cast<Access>(Access.read | Access.write | Access.execute),
740 cast<Access>(Access.read | Access.write | Access.execute),
741 cast<Access>(Access.read | Access.write | Access.execute));
742 }
743 if (process != null)
744 {
745 mode = mode & ~process->umask;
746 }
747 inode->SetAccessMode(mode);
748 int blockNumber = 0;
749 int blockOffset = 0;
750 GetBlockManager()->GetBlockNumber(inode, 0, blockNumber, blockOffset, true);
751 Block* blck = GetBlockManager()->GetBlock(BlockKey(blockNumber, inode->Key().fsNumber), null);
752 blck->Clear();
753 BlockPutter putter(blck);
754 DirectoryBlock* dirBlock = cast<DirectoryBlock*>(blck);
755 int n = 0;
756 bool crtd = false;
757 for (int i = 0; i < numDirectoryEntriesInBlock; ++i;)
758 {
759 DirectoryEntry entry = dirBlock->GetDirectoryEntry(i);
760 if (entry.inodeNumber == 0 && n == 0)
761 {
762 entry.inodeNumber = parent->Key().inodeNumber;
763 entry.name = "..";
764 dirBlock->SetDirectoryEntry(i, entry);
765 ++n;
766 }
767 else if (entry.inodeNumber == 0 && n == 1)
768 {
769 entry.inodeNumber = inode->Key().inodeNumber;
770 entry.name = ".";
771 dirBlock->SetDirectoryEntry(i, entry);
772 ++n;
773 crtd = true;
774 break;
775 }
776 }
777 if (crtd)
778 {
779 dirBlock->SetFlag(Block.Flags.dirty);
780 GetBlockManager()->WriteBlock(dirBlock, null);
781 inode->SetFileSize(inode->GetFileSize() + n * directoryEntrySize);
782 inode->SetMTime();
783 }
784 else
785 {
786 throw SystemError(EFAIL, "no room for directory entries");
787 }
788 }
789 public int OpenDirectory(Process* process, const string& path)
790 {
791 if (Log())
792 {
793 LogMessage("fs.opendirectory", path);
794 }
795 INode* dirINode = GetINodeManager()->PathToINode(process, path);
796 if (dirINode == null)
797 {
798 throw SystemError(ENOENT, "path '" + path + "' does not exist");
799 }
800 INodePutter inodePutter(dirINode);
801 if (dirINode->Type() != FileType.directory)
802 {
803 throw SystemError(EINVAL, "cannot open directory '" + path + "': file type is " + FileTypeStr(dirINode->Type()));
804 }
805 FileSystem* fs = GetMountTable().GetFileSystem(dirINode->Key().fsNumber);
806 return fs->OpenDirectory(process, path, dirINode, inodePutter);
807 }
808 public abstract int OpenDirectory(Process* process, const string& path, INode* dirINode, INodePutter& inodePutter);
809 public abstract int ReadDirectory(Process* process, DirectoryFile* dirFile, ulong inodeNumberAddress, ulong entryNameAddress);
810 public string GetCurrentWorkingDirectory(Process* process)
811 {
812 if (Log())
813 {
814 LogMessage("fs.getcurrentworkingdirectory", "begin");
815 }
816 INode* rootINode = GetINodeManager()->GetINode(process->rootDirINodeKey);
817 INodePutter rootPutter(rootINode);
818 INode* dirINode = rootINode;
819 INodePutter wdNodePutter;
820 INodePutter parentPutter;
821 if (process->workingDirINodeKey != process->rootDirINodeKey)
822 {
823 dirINode = GetINodeManager()->GetINode(process->workingDirINodeKey);
824 wdNodePutter.ResetINode(dirINode);
825 }
826 INode* prevINode = null;
827 bool first = true;
828 string currentWorkindDirectoryPath;
829 while (prevINode != rootINode)
830 {
831 INode* parentINode = null;
832 bool found = false;
833 for (long offset = 0; !found && offset < dirINode->GetFileSize();;)
834 {
835 int blockNumber = 0;
836 int blockOffset = 0;
837 GetBlockManager()->GetBlockNumber(dirINode, offset, blockNumber, blockOffset, false);
838 if (blockNumber == 0)
839 {
840 throw SystemError(EFAIL, "could not get block number for directory inode");
841 }
842 Block* block = GetBlockManager()->ReadBlock(BlockKey(blockNumber, dirINode->Key().fsNumber), null);
843 BlockPutter putter(block);
844 DirectoryBlock* directoryBlock = cast<DirectoryBlock*>(block);
845 for (int index = blockOffset / directoryEntrySize; index < numDirectoryEntriesInBlock && offset < dirINode->GetFileSize(); ++index;)
846 {
847 DirectoryEntry directoryEntry = directoryBlock->GetDirectoryEntry(index);
848 if (!first && directoryEntry.inodeNumber == prevINode->Key().inodeNumber)
849 {
850 currentWorkindDirectoryPath = Path.Combine(directoryEntry.name, currentWorkindDirectoryPath);
851 found = true;
852 }
853 if (directoryEntry.name == "..")
854 {
855 INodeKey key(directoryEntry.inodeNumber, dirINode->Key().fsNumber);
856 if (key != dirINode->Key())
857 {
858 if (key != rootINode->Key())
859 {
860 parentINode = GetINodeManager()->GetINode(key);
861 parentPutter.ResetINode(parentINode);
862 }
863 else
864 {
865 parentINode = rootINode;
866 }
867 }
868 else
869 {
870 parentINode = dirINode;
871 }
872 }
873 offset = offset + directoryEntrySize;
874 }
875 }
876 if (parentINode == null)
877 {
878 throw SystemError(EFAIL, "parent inode entry not found");
879 }
880 if (found)
881 {
882 prevINode = dirINode;
883 dirINode = parentINode;
884 }
885 else if (first)
886 {
887 prevINode = dirINode;
888 dirINode = parentINode;
889 first = false;
890 }
891 else
892 {
893 throw SystemError(EFAIL, "could not find matching directory inode from parent inode");
894 }
895 }
896 currentWorkindDirectoryPath = Path.Combine("/", currentWorkindDirectoryPath);
897 if (Log())
898 {
899 LogMessage("fs.getcurrentworkingdirectory", "end.cwd=" + currentWorkindDirectoryPath);
900 }
901 return currentWorkindDirectoryPath;
902 }
903 public void ChDir(Process* process, const string& path)
904 {
905 INode* inode = GetINodeManager()->PathToINode(process, path);
906 if (inode == null)
907 {
908 throw SystemError(ENOENT, "path '" + path + "' does not exist");
909 }
910 INodePutter putter(inode);
911 process->workingDirINodeKey = inode->Key();
912 }
913 public void Stat(Process* process, const string& path, ulong statBufAddress)
914 {
915 INode* inode = GetINodeManager()->PathToINode(process, path);
916 if (inode == null)
917 {
918 throw SystemError(ENOENT, "path '" + path + "' does not exist");
919 }
920 INodePutter putter(inode);
921 FileSystem* fs = GetMountTable().GetFileSystem(inode->Key().fsNumber);
922 fs->FillINode(inode);
923 Machine& machine = GetMachine();
924 byte[statBufSize] statBuf;
925 MemoryWriter writer(&statBuf[0], statBuf.Length());
926 writer.Write(inode->Key().inodeNumber);
927 writer.Write(inode->Key().fsNumber);
928 writer.Write(inode->Mode());
929 writer.Write(inode->UID());
930 writer.Write(inode->GID());
931 writer.Write(inode->GetNumLinks());
932 writer.Write(inode->GetFileSize());
933 writer.Write(inode->CTime());
934 writer.Write(inode->MTime());
935 writer.Write(inode->ATime());
936 WriteProcessMemory(machine, process, statBufAddress, &statBuf[0], cast<ulong>(statBuf.Length()), Protection.write);
937 }
938 public void ChangeMode(Process* process, const string& path, int mode)
939 {
940 INode* inode = GetINodeManager()->PathToINode(process, path);
941 if (inode == null)
942 {
943 throw SystemError(ENOENT, "path '" + path + "' does not exist");
944 }
945 INodePutter putter(inode);
946 FileSystem* fs = GetMountTable().GetFileSystem(inode->Key().fsNumber);
947 fs->ChangeMode(process, path, inode, mode);
948 }
949 public virtual void ChangeMode(Process* process, const string& path, INode* inode, int mode)
950 {
951 throw SystemError(EINVAL, "error changing mode of '" + path + "': file system '" + name + "' does not support chmod");
952 }
953 public void ChangeOwner(Process* process, const string& path, int uid, int gid)
954 {
955 INode* inode = GetINodeManager()->PathToINode(process, path);
956 if (inode == null)
957 {
958 throw SystemError(ENOENT, "path '" + path + "' does not exist");
959 }
960 INodePutter putter(inode);
961 FileSystem* fs = GetMountTable().GetFileSystem(inode->Key().fsNumber);
962 fs->ChangeOwner(process, path, inode, uid, gid);
963 }
964 public virtual void ChangeOwner(Process* process, const string& path, INode* inode, int uid, int gid)
965 {
966 throw SystemError(EINVAL, "error changing owner of '" + path + "': file system '" + name + "' does not support chown");
967 }
968 public void UpdateFileTimes(Process* process, const string& path, const DateTime& atime, const DateTime& mtime)
969 {
970 INode* inode = GetINodeManager()->PathToINode(process, path);
971 if (inode == null)
972 {
973 throw SystemError(ENOENT, "path '" + path + "' does not exist");
974 }
975 INodePutter putter(inode);
976 FileSystem* fs = GetMountTable().GetFileSystem(inode->Key().fsNumber);
977 fs->UpdateFileTimes(process, path, inode, atime, mtime);
978 }
979 public virtual void UpdateFileTimes(Process* process, const string& path, INode* inode, const DateTime& atime, const DateTime& mtime)
980 {
981 throw SystemError(EINVAL, "error updating file times of '" + path + "': file system '" + name + "' does not support utime");
982 }
983 public virtual void FillINode(INode* inode)
984 {
985 }
986 public abstract HostFile* GetHostFile(int fsNumber) const;
987 public abstract int AllocateBlockNumber();
988 public abstract void SetBlockFree(int blockNumber);
989 public abstract int GetFreeINodeNumber();
990 public abstract void SetFreeINodeNumber(int inodeNumber);
991 public abstract int GetFirstINodeBlockNumber() const;
992 public abstract INodeKey GetRootDirINodeKey() const;
993 public abstract int LastBlockNumber() const;
994 public abstract void SetLastBlockNumber(int blockNumber, SuperBlock* sb);
995 public abstract nothrow bool HasMountDirKey(const INodeKey& mountDirKey) const;
996 public abstract nothrow BlockManager* GetBlockManager();
997 public abstract nothrow INodeManager* GetINodeManager();
998 public inline nothrow const string& Name() const
999 {
1000 return name;
1001 }
1002 public inline nothrow int Index() const
1003 {
1004 return index;
1005 }
1006 public inline nothrow bool IsReadOnly() const
1007 {
1008 return readOnly;
1009 }
1010 private string name;
1011 private int index;
1012 private bool readOnly;
1013 }
1014
1015 public abstract class BlockManager
1016 {
1017 public virtual default ~BlockManager();
1018 public Block* GetBlock(const BlockKey& key, SuperBlock* sb)
1019 {
1020 return GetBlock(key, sb, true, true);
1021 }
1022 public abstract Block* GetBlock(const BlockKey& key, SuperBlock* sb, bool sleep, bool setOwner);
1023 public abstract nothrow void PutBlock(Block* block);
1024 public abstract nothrow Block* ReadBlock(const BlockKey& key, SuperBlock* sb);
1025 public abstract void WriteBlock(Block* block, SuperBlock* superBlock);
1026 public abstract void FreeBlocks(INode* inode);
1027 public abstract void GetBlockNumber(INode* inode, long offset, int& blockNumber, int& blockOffset, bool allocate);
1028 public abstract int GetBlockNumber(INode* inode, int logicalBlockNumber) const;
1029 public abstract void Flush();
1030 public abstract nothrow string Name() const;
1031 }
1032
1033 public abstract class INodeManager
1034 {
1035 public virtual default ~INodeManager();
1036 public abstract INode* GetINode(const INodeKey& key);
1037 public abstract void PutINode(INode* inode);
1038 public INode* CallPathToINode(INodeManager* imgr, Process* process, const string& path)
1039 {
1040 return imgr->PathToINode(process, path);
1041 }
1042 public abstract INode* PathToINode(Process* process, const string& path);
1043 public abstract INode* PathToINode(Process* process, const string& path, PathToINodeFlags flags, INode*& parent, DirectorySlot& freeDirectorySlot, string& name);
1044 public abstract nothrow string Name() const;
1045 }
1046 }