master
cpp 1,147 lines 32.3 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2 #include "precomp.h"
3 #include "p9errors.h"
4 #include "p9file.h"
5 #include "p9util.h"
6 #include "p9commonutil.h"
7 #include "p9xattr.h"
8 #include <mountutilcpp.h>
9 #include <sys/syscall.h>
10 #include <sys/sysmacros.h>
11
12 using namespace std::string_view_literals;
13
14 namespace p9fs {
15
16 constexpr std::string_view c_drvfsFsType = "drvfs"sv;
17 constexpr std::string_view c_p9FsType = "9p"sv;
18 constexpr std::string_view c_virtioFsType = "virtiofs"sv;
19
20 struct OpenFlagMapping
21 {
22 OpenFlags P9Flag;
23 int LinuxFlag;
24 };
25
26 const OpenFlagMapping c_openFlagsMapping[] = {
27 {OpenFlags::WriteOnly, O_WRONLY},
28 {OpenFlags::ReadWrite, O_RDWR},
29 {OpenFlags::Create, O_CREAT},
30 {OpenFlags::Exclusive, O_EXCL},
31 {OpenFlags::NoCTTY, O_NOCTTY},
32 {OpenFlags::Truncate, O_TRUNC},
33 {OpenFlags::Append, O_APPEND},
34 {OpenFlags::NonBlock, O_NONBLOCK},
35 {OpenFlags::DSync, O_DSYNC},
36 {OpenFlags::FAsync, O_ASYNC},
37 {OpenFlags::Direct, O_DIRECT},
38 {OpenFlags::LargeFile, O_LARGEFILE},
39 {OpenFlags::Directory, O_DIRECTORY},
40 {OpenFlags::NoFollow, O_NOFOLLOW},
41 {OpenFlags::NoAccessTime, O_NOATIME},
42 {OpenFlags::CloseOnExec, O_CLOEXEC},
43 {OpenFlags::Sync, O_SYNC}};
44
45 Expected<std::tuple<std::shared_ptr<Fid>, Qid>> CreateFile(std::shared_ptr<const IRoot> root, LX_UID_T uid)
46 {
47 auto realRoot = std::static_pointer_cast<const Root>(root);
48 auto file = std::make_shared<File>(realRoot);
49 auto qid = file->Initialize();
50 if (!qid)
51 {
52 return qid.Unexpected();
53 }
54
55 return std::tuple<std::shared_ptr<Fid>, Qid>{std::move(file), qid.Get()};
56 }
57
58 QidType ModeToQidType(mode_t mode)
59 {
60 if (S_ISLNK(mode))
61 {
62 return QidType::Symlink;
63 }
64
65 if (S_ISDIR(mode))
66 {
67 return QidType::Directory;
68 }
69
70 return QidType::File;
71 }
72
73 // Converts the result of a stat system call to a qid value.
74 Qid StatToQid(const struct stat& st)
75 {
76 return {st.st_ino, 0, ModeToQidType(st.st_mode)};
77 }
78
79 // Get the qid for a file.
80 // N.B. The caller is responsible for setting the right thread uid/gid before calling this.
81 Expected<Qid> GetFileQidByPath(int fd, const std::string& path)
82 {
83 struct stat st;
84 int result = fstatat(fd, path.c_str(), &st, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH);
85 if (result < 0)
86 {
87 return LxError{-errno};
88 }
89
90 return StatToQid(st);
91 }
92
93 // Appends a valid Linux path segment to a Win32 path. It's assumed that the path has already been
94 // scanned for internal NUL and / characters.
95 void AppendPath(std::string& Base, std::string_view Name)
96 {
97 // No need for a delimiter if the base path is empty or already ends in one.
98 if (!Base.empty() && Base.back() != '/')
99 {
100 Base += '/';
101 }
102
103 Base += Name;
104 }
105
106 // Converts 9P2000.L open flags to Linux open flags.
107 // N.B. 9P2000.L and Linux flag values may be identical on some platforms, but not all.
108 int OpenFlagsToLinuxFlags(OpenFlags flags)
109 {
110 // Since OpenFlags::ReadOnly is zero, it's omitted from the mapping array. This is safe as long as O_RDONLY is also
111 // zero. If it's not, it would have to be handled separately.
112 static_assert(O_RDONLY == 0);
113
114 int result = 0;
115 for (const auto& flag : c_openFlagsMapping)
116 {
117 if (WI_AreAllFlagsSet(flags, flag.P9Flag))
118 {
119 WI_SetAllFlags(result, flag.LinuxFlag);
120 }
121 }
122
123 return result;
124 }
125
126 // Get the stat information of this file.
127 // N.B. The caller is responsible for setting the right thread uid/gid before calling this.
128 Expected<struct stat> File::Stat()
129 {
130 struct stat st;
131 // Acquire the lock to prevent the file name from changing.
132 std::shared_lock<std::shared_mutex> lock{m_Lock};
133 int result = fstatat(m_Root->RootFd, m_FileName.c_str(), &st, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH);
134 if (result < 0)
135 {
136 return LxError{-errno};
137 }
138
139 return st;
140 }
141
142 bool File::IsOnRoot(const std::shared_ptr<const IRoot>& root)
143 {
144 return m_Root == root;
145 }
146
147 // Opens the file.
148 // N.B. The caller is responsible for setting the right thread uid/gid before calling this.
149 Expected<wil::unique_fd> File::OpenFile(int openFlags)
150 {
151 // Acquire the lock to prevent the file name from changing.
152 std::shared_lock<std::shared_mutex> lock{m_Lock};
153 return util::OpenAt(m_Root->RootFd, m_FileName, openFlags | O_NOFOLLOW);
154 }
155
156 // Validates that this file exists and sets the m_Qid member.
157 // N.B. The caller is responsible for setting the right thread uid/gid before calling this.
158 LX_INT File::ValidateExists()
159 {
160 auto st = Stat();
161 if (!st)
162 {
163 return st.Error();
164 }
165
166 m_Qid = StatToQid(st.Get());
167 m_Device = st->st_dev;
168 return {};
169 }
170
171 // Initializes a file to a Win32 path.
172 Expected<Qid> File::Initialize()
173 {
174 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
175 LX_INT error = ValidateExists();
176 if (error < 0)
177 {
178 return LxError{error};
179 }
180
181 // No locking needed because initialize is only called on fids not yet
182 // reachable by other threads.
183 return m_Qid;
184 }
185
186 File::File(std::shared_ptr<const Root> root) : m_Root{root}
187 {
188 }
189
190 // Copies a file. This does not clone the open file state, just the name and metadata.
191 File::File(const File& file) : m_FileName{file.m_FileName}, m_Root{file.m_Root}, m_Qid{file.m_Qid}, m_Device{file.m_Device}
192 {
193 }
194
195 // Updates the path to a child file entry in a directory. Must be called with a newly
196 // constructed file, not one that has been opened.
197 Expected<Qid> File::Walk(std::string_view name)
198 {
199 // TODO: This is not safe if walk is called multiple times. While
200 // we verify that the item is not a symlink in this step, the file could've
201 // been replaced with a symlink since the qid was determined.
202 // The only way to make this foolproof is to open an fd for every file, and
203 // use fstatat for the next level. A chroot environment can be used to
204 // prevent the links from escaping the share root, but it can't avoid
205 // accidentally following links at all.
206 if (!WI_IsFlagSet(m_Qid.Type, QidType::Directory))
207 {
208 return LxError{LX_ENOTDIR};
209 }
210
211 // No lock is taken here; this function is only called on fid's that have
212 // not yet been inserted in the list and are therefore not reachable from
213 // other threads.
214 AppendPath(m_FileName, name);
215
216 // Revert to the old info on error.
217 const auto oldQid = m_Qid;
218 const auto oldDevice = m_Device;
219 auto restoreName = wil::scope_exit([&]() {
220 m_Qid = oldQid;
221 m_Device = oldDevice;
222 const auto index = m_FileName.find_last_of('/');
223 if (index == std::string::npos)
224 {
225 m_FileName.resize(0);
226 }
227 else
228 {
229 m_FileName.resize(index);
230 }
231 });
232
233 // TODO: Maybe handle multiple items in a single walk call so changing ids is done only once.
234 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
235 const auto parentDevice = m_Device;
236 LX_INT err = ValidateExists();
237 if (err != 0)
238 {
239 return LxError{err};
240 }
241
242 // Check if this is a mount point, and if so if it's a drvfs or 9p mount.
243 if (parentDevice != m_Device)
244 {
245 try
246 {
247 // Because this thread might not be in the same mount namespace than the rest of the process,
248 // look at /proc/<tid>/mountinfo instead of /proc/self/
249 const std::string mountInfoPath = std::format("/proc/{}/mountinfo", gettid());
250 mountutil::MountEnum mountEnum(mountInfoPath.c_str());
251 bool found = mountEnum.FindMount([this](auto entry) { return entry.Device == m_Device; });
252
253 // If the mount was found and it's a drvfs mount, deny access.
254 if (found && (mountEnum.Current().FileSystemType == c_drvfsFsType || mountEnum.Current().FileSystemType == c_p9FsType ||
255 mountEnum.Current().FileSystemType == c_virtioFsType))
256 {
257 return LxError{LX_EACCES};
258 }
259 }
260 CATCH_LOG()
261 }
262
263 restoreName.release();
264 return m_Qid;
265 }
266
267 // Reads the attributes of a file or directory.
268 Expected<std::tuple<UINT64, Qid, StatResult>> File::GetAttr(UINT64 mask)
269 {
270 std::string fileName;
271 Qid qid;
272 {
273 // Retrieve the qid and open a handle under lock.
274 std::shared_lock<std::shared_mutex> lock{m_Lock};
275 qid = m_Qid;
276 fileName = m_FileName;
277 }
278
279 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
280 struct stat stat;
281 int error = fstatat(m_Root->RootFd, fileName.c_str(), &stat, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH);
282 if (error < 0)
283 {
284 return LxError{-errno};
285 }
286
287 StatResult result{};
288 UINT64 valid = GetAttrIno;
289 if (WI_IsFlagSet(mask, GetAttrMode))
290 {
291 result.Mode = stat.st_mode;
292 WI_SetFlag(valid, GetAttrMode);
293 }
294
295 if (WI_IsFlagSet(mask, GetAttrNlink))
296 {
297 result.NLink = stat.st_nlink;
298 WI_SetFlag(valid, GetAttrNlink);
299 }
300
301 if (WI_IsFlagSet(mask, GetAttrRdev))
302 {
303 result.RDev = stat.st_rdev;
304 WI_SetFlag(valid, GetAttrRdev);
305 }
306
307 if (WI_IsFlagSet(mask, GetAttrSize))
308 {
309 result.Size = stat.st_size;
310 WI_SetFlag(valid, GetAttrSize);
311 }
312
313 if (WI_IsFlagSet(mask, GetAttrBlocks))
314 {
315 result.BlockSize = stat.st_blksize;
316 result.Blocks = stat.st_blocks;
317 WI_SetFlag(valid, GetAttrBlocks);
318 }
319
320 if (WI_IsFlagSet(mask, GetAttrAtime))
321 {
322 result.AtimeSec = stat.st_atim.tv_sec;
323 result.AtimeNsec = stat.st_atim.tv_nsec;
324 WI_SetFlag(valid, GetAttrAtime);
325 }
326
327 if (WI_IsFlagSet(mask, GetAttrMtime))
328 {
329 result.MtimeSec = stat.st_mtim.tv_sec;
330 result.MtimeNsec = stat.st_mtim.tv_nsec;
331 WI_SetFlag(valid, GetAttrMtime);
332 }
333
334 if (WI_IsFlagSet(mask, GetAttrCtime))
335 {
336 result.CtimeSec = stat.st_ctim.tv_sec;
337 result.CtimeNsec = stat.st_ctim.tv_nsec;
338 WI_SetFlag(valid, GetAttrCtime);
339 }
340
341 if (WI_IsFlagSet(mask, GetAttrUid))
342 {
343 result.Uid = stat.st_uid;
344 WI_SetFlag(valid, GetAttrUid);
345 }
346
347 if (WI_IsFlagSet(mask, GetAttrGid))
348 {
349 result.Gid = stat.st_gid;
350 WI_SetFlag(valid, GetAttrGid);
351 }
352
353 return std::make_tuple(valid, qid, result);
354 }
355
356 // Sets the attributes for a file or directory.
357 LX_INT File::SetAttr(UINT32 valid, const StatResult& stat)
358 {
359 if (m_Root->ReadOnly())
360 {
361 return LX_EROFS;
362 }
363
364 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
365
366 // Multiple operations may be performed, so it would be preferable to open the file. However,
367 // most operations don't support O_PATH and any other flags will check for permissions that the
368 // operation may not need.
369 const auto fileName = GetFileName();
370
371 // Ctime is updated by most of the operations below, so don't explicitly
372 // update it if not needed.
373 bool needCTimeUpdate = WI_IsFlagSet(valid, SetAttrCtime);
374
375 if (WI_IsFlagSet(valid, SetAttrSize))
376 {
377 // Open the file to truncate because truncate will always follow symlinks and there is no
378 // ftruncateat.
379 auto file = OpenFile(O_WRONLY);
380 if (!file)
381 {
382 return file.Error();
383 }
384
385 int error = ftruncate(file->get(), stat.Size);
386 if (error < 0)
387 {
388 return -errno;
389 }
390
391 needCTimeUpdate = false;
392 }
393
394 if (WI_IsFlagSet(valid, SetAttrMode))
395 {
396 int error = fchmodat(m_Root->RootFd, fileName.c_str(), stat.Mode, AT_SYMLINK_NOFOLLOW);
397 if (error < 0)
398 {
399 return -errno;
400 }
401
402 needCTimeUpdate = false;
403 }
404
405 if (WI_IsAnyFlagSet(valid, SetAttrUid | SetAttrGid))
406 {
407 uid_t uid = WI_IsFlagSet(valid, SetAttrUid) ? stat.Uid : -1;
408 uid_t gid = WI_IsFlagSet(valid, SetAttrGid) ? stat.Gid : -1;
409 int error = fchownat(m_Root->RootFd, fileName.c_str(), uid, gid, AT_SYMLINK_NOFOLLOW);
410 if (error < 0)
411 {
412 return -errno;
413 }
414
415 needCTimeUpdate = false;
416 }
417
418 if (WI_IsAnyFlagSet(valid, SetAttrAtime | SetAttrMtime))
419 {
420 struct timespec times[2]{{0, UTIME_OMIT}, {0, UTIME_OMIT}};
421
422 //
423 // For atime and mtime, the time is set to the current time unless the
424 // respective "set" flag is set.
425 //
426
427 if (WI_IsFlagSet(valid, SetAttrAtime))
428 {
429 if (WI_IsFlagSet(valid, SetAttrAtimeSet))
430 {
431 times[0].tv_sec = stat.AtimeSec;
432 times[0].tv_nsec = stat.AtimeNsec;
433 }
434 else
435 {
436 times[0].tv_nsec = UTIME_NOW;
437 }
438 }
439
440 if (WI_IsFlagSet(valid, SetAttrMtime))
441 {
442 if (WI_IsFlagSet(valid, SetAttrMtimeSet))
443 {
444 times[1].tv_sec = stat.MtimeSec;
445 times[1].tv_nsec = stat.MtimeNsec;
446 }
447 else
448 {
449 times[1].tv_nsec = UTIME_NOW;
450 }
451 }
452
453 int error = utimensat(m_Root->RootFd, fileName.c_str(), times, AT_SYMLINK_NOFOLLOW);
454 if (error < 0)
455 {
456 return -errno;
457 }
458
459 needCTimeUpdate = false;
460 }
461
462 // If a ctime update was requested but didn't already happen, perform a no-op
463 // operation that has a ctime update as a side-effect.
464 if (needCTimeUpdate)
465 {
466 int error = fchownat(m_Root->RootFd, fileName.c_str(), -1, -1, AT_SYMLINK_NOFOLLOW);
467 if (error < 0)
468 {
469 return -errno;
470 }
471 }
472
473 return {};
474 }
475
476 // Opens a file or directory for read/write access.
477 Expected<Qid> File::Open(OpenFlags flags)
478 {
479 // Acquire the lock to protect the file name and to guard against
480 // concurrent open attempts.
481 std::lock_guard<std::shared_mutex> lock{m_Lock};
482 if (IsOpen())
483 {
484 return LxError{LX_EINVAL};
485 }
486
487 WI_ClearFlag(flags, OpenFlags::Create);
488 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
489 // Don't use OpenHandle because the lock is already held.
490 auto file{util::OpenAt(m_Root->RootFd, m_FileName, OpenFlagsToLinuxFlags(flags) | O_NOFOLLOW)};
491 if (!file)
492 {
493 return file.Unexpected();
494 }
495
496 m_Io = CoroutineIoIssuer(file->get());
497 m_File = std::move(file.Get());
498 return m_Qid;
499 }
500
501 // Creates a file in a directory, updating this object to point to the new file.
502 Expected<Qid> File::Create(std::string_view name, OpenFlags flags, UINT32 mode, UINT32 /* gid */)
503 {
504 // Acquire the lock exclusive because the file name will be modified,
505 // and to protect against concurrent opens and creates.
506 std::lock_guard<std::shared_mutex> lock{m_Lock};
507 if (IsOpen())
508 {
509 return LxError{LX_EINVAL};
510 }
511
512 if (m_Root->ReadOnly())
513 {
514 return LxError{LX_EROFS};
515 }
516
517 // The specified gid is currently ignored. Supporting it would be possible, but it would be
518 // necessary to make sure that the user is a member of the specified group.
519 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
520 auto newFileName = ChildPathWithLockHeld(name);
521 auto file{util::OpenAt(m_Root->RootFd, newFileName, OpenFlagsToLinuxFlags(flags) | O_CREAT | O_NOFOLLOW, mode)};
522 if (!file)
523 {
524 return file.Unexpected();
525 }
526
527 struct stat st;
528 int result = fstat(file->get(), &st);
529 if (result < 0)
530 {
531 return LxError{-errno};
532 }
533
534 m_FileName = std::move(newFileName);
535 m_Io = CoroutineIoIssuer(file->get());
536 m_File = std::move(file.Get());
537 m_Qid = StatToQid(st);
538 m_Device = st.st_dev;
539 return m_Qid;
540 }
541
542 // Creates a subdirectory.
543 Expected<Qid> File::MkDir(std::string_view name, UINT32 mode, UINT32 /* gid */)
544 {
545 const auto newFileName = ChildPath(name);
546
547 // The specified gid is currently ignored. Supporting it would be possible, but it would be
548 // necessary to make sure that the user is a member of the specified group.
549 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
550 int result = mkdirat(m_Root->RootFd, newFileName.c_str(), mode);
551 if (result < 0)
552 {
553 return LxError{-errno};
554 }
555
556 return GetFileQidByPath(m_Root->RootFd, newFileName);
557 }
558
559 // Reads the contents of a directory, starting at the specified offset.
560 LX_INT File::ReadDir(UINT64 offset, SpanWriter& writer, bool includeAttributes)
561 {
562 if (!IsOpen())
563 {
564 return LX_EBADF;
565 }
566
567 // Acquire an exclusive lock to protect enumerator state.
568 std::lock_guard<std::shared_mutex> lock{m_Lock};
569 if (!m_Enumerator)
570 {
571 m_Enumerator.reset(new DirectoryEnumerator(m_File.get()));
572 // The fd is now owned by the enumerator.
573 m_File.release();
574 }
575
576 m_Enumerator->Seek(offset);
577
578 bool dirEntriesWritten = false;
579 for (;;)
580 {
581 auto entry = m_Enumerator->Next();
582 if (entry == nullptr)
583 {
584 break;
585 }
586
587 StatResult attributes;
588 StatResult* attributesToUse = nullptr;
589 if (includeAttributes)
590 {
591 attributesToUse = &attributes;
592 struct stat st;
593 const char* name = entry->d_name;
594
595 // Return attributes of the directory for both . and ..
596 if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
597 {
598 name = "";
599 }
600
601 int result = fstatat(m_Enumerator->Fd(), name, &st, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH);
602 if (result < 0)
603 {
604 // Fill out basic attributes if real attributes can't be determined.
605 attributes = {};
606 attributes.Mode = util::DirEntryTypeToMode(entry->d_type);
607 attributes.NLink = 1;
608 }
609 else
610 {
611 attributes.Mode = st.st_mode;
612 attributes.Uid = st.st_uid;
613 attributes.Gid = st.st_gid;
614 attributes.NLink = st.st_nlink;
615 attributes.RDev = st.st_rdev;
616 attributes.Size = st.st_size;
617 attributes.BlockSize = st.st_blksize;
618 attributes.Blocks = st.st_blocks;
619 attributes.AtimeSec = st.st_atim.tv_sec;
620 attributes.AtimeNsec = st.st_atim.tv_nsec;
621 attributes.MtimeSec = st.st_mtim.tv_sec;
622 attributes.MtimeNsec = st.st_mtim.tv_nsec;
623 attributes.CtimeSec = st.st_ctim.tv_sec;
624 attributes.CtimeNsec = st.st_ctim.tv_nsec;
625 }
626 }
627
628 Qid qid{};
629 qid.Path = entry->d_ino;
630 qid.Type = util::DirEntryTypeToQidType(entry->d_type);
631 if (!util::SpanWriteDirectoryEntry(writer, entry->d_name, qid, entry->d_off, entry->d_type, attributesToUse))
632 {
633 if (!dirEntriesWritten)
634 {
635 return LX_EINVAL;
636 }
637
638 break;
639 }
640
641 dirEntriesWritten = true;
642 }
643
644 return {};
645 }
646
647 // Reads the contents of an open file.
648 Task<Expected<UINT32>> File::Read(UINT64 offset, gsl::span<gsl::byte> buffer)
649 {
650 // No locking needed; once open, the file will not be closed until the
651 // object is destructed, and the caller holds a reference.
652 if (!m_File)
653 {
654 co_return LxError{LX_EBADF};
655 }
656
657 CancelToken token;
658 auto result = co_await ReadAsync(m_Io, offset, buffer, token);
659 if (result.Error != 0 && result.Error != LX_EOVERFLOW)
660 {
661 co_return LxError{result.Error};
662 }
663
664 co_return static_cast<UINT32>(result.BytesTransferred);
665 }
666
667 // Writes to an open file.
668 Task<Expected<UINT32>> File::Write(UINT64 offset, gsl::span<const gsl::byte> buffer)
669 {
670 // Since the file could not have been opened for write on a read-only file
671 // system, there is no reason to check that here.
672
673 // No locking needed; once open, the file will not be closed until the
674 // object is destructed, and the caller holds a reference.
675 if (!m_File)
676 {
677 co_return LxError{LX_EBADF};
678 }
679
680 CancelToken token;
681 auto result = co_await WriteAsync(m_Io, offset, buffer, token);
682 if (result.Error != 0)
683 {
684 co_return LxError{result.Error};
685 }
686
687 co_return result.BytesTransferred;
688 }
689
690 // Unlinks a directory entry.
691 LX_INT File::UnlinkAt(std::string_view name, UINT32 flags)
692 {
693 if (m_Root->ReadOnly())
694 {
695 return LX_EROFS;
696 }
697
698 const auto fileName = ChildPath(name);
699 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
700
701 // TODO: it's unclear whether this is the correct usage of the
702 // flags field. The Windows implementation unlinks either directory or
703 // file regardless of flags.
704 int result = unlinkat(m_Root->RootFd, fileName.c_str(), flags);
705 if (result < 0)
706 {
707 return -errno;
708 }
709
710 return {};
711 }
712
713 // Removes the directory entry represented by the current fid.
714 LX_INT File::Remove()
715 {
716 if (m_Root->ReadOnly())
717 {
718 return LX_EROFS;
719 }
720
721 int flags = 0;
722 WI_SetFlagIf(flags, AT_REMOVEDIR, WI_IsFlagSet(m_Qid.Type, QidType::Directory));
723 const std::string fileName = GetFileName();
724 if (fileName.length() == 0)
725 {
726 // Can't unlink the root.
727 return LX_EPERM;
728 }
729
730 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
731 const int result = unlinkat(m_Root->RootFd, fileName.c_str(), flags);
732 if (result < 0)
733 {
734 return -errno;
735 }
736
737 return {};
738 }
739
740 // Gets a copy of the file name, taking the lock to retrieve it.
741 std::string File::GetFileName() const
742 {
743 std::shared_lock<std::shared_mutex> lock{m_Lock};
744 return m_FileName;
745 }
746
747 // Constructs a child path of the current path from a valid Linux path segment.
748 std::string File::ChildPath(std::string_view name)
749 {
750 auto result = GetFileName();
751 AppendPath(result, name);
752 return result;
753 }
754
755 std::string File::ChildPathWithLockHeld(std::string_view name)
756 {
757 std::string result{m_FileName};
758 AppendPath(result, name);
759 return result;
760 }
761
762 // Renames a directory entry.
763 LX_INT File::RenameAt(std::string_view oldName, Fid& newParent, std::string_view newName)
764 {
765 if (!newParent.IsFile() || !newParent.IsOnRoot(m_Root))
766 {
767 return LX_EINVAL;
768 }
769
770 if (m_Root->ReadOnly())
771 {
772 return LX_EROFS;
773 }
774
775 auto newParentFile = static_cast<File&>(newParent);
776 const auto oldPath = ChildPath(oldName);
777 const auto newPath = newParentFile.ChildPath(newName);
778 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
779 int result = renameat(m_Root->RootFd, oldPath.c_str(), m_Root->RootFd, newPath.c_str());
780 if (result < 0)
781 {
782 return -errno;
783 }
784
785 return {};
786 }
787
788 // Renames the current directory entry.
789 LX_INT File::Rename(Fid& newParent, std::string_view newName)
790 {
791 if (!newParent.IsFile() || !newParent.IsOnRoot(m_Root))
792 {
793 return LX_EINVAL;
794 }
795
796 if (m_Root->ReadOnly())
797 {
798 return LX_EROFS;
799 }
800
801 auto newParentFile = static_cast<File&>(newParent);
802 // Take an exclusive lock because the file name will be changed.
803 std::lock_guard<std::shared_mutex> lock{m_Lock};
804 if (m_FileName.length() == 0)
805 {
806 // Can't rename the root.
807 return LX_EPERM;
808 }
809
810 const auto oldPath = m_FileName;
811 auto newPath = newParentFile.ChildPath(newName);
812 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
813 int result = renameat(m_Root->RootFd, oldPath.c_str(), m_Root->RootFd, newPath.c_str());
814 if (result < 0)
815 {
816 return -errno;
817 }
818
819 m_FileName = newPath;
820 return {};
821 }
822
823 // Creates a symbolic link in a directory.
824 Expected<Qid> File::SymLink(std::string_view name, std::string_view target, UINT32 /* gid */)
825 {
826 if (m_Root->ReadOnly())
827 {
828 return LxError{LX_EROFS};
829 }
830
831 // TODO: Gid is being ignored.
832 const auto linkName = ChildPath(name);
833 // Need a null-terminated string:
834 const std::string linkTarget{target.data(), target.size()};
835
836 // The specified gid is currently ignored. Supporting it would be possible, but it would be
837 // necessary to make sure that the user is a member of the specified group.
838 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
839 int result = symlinkat(linkTarget.c_str(), m_Root->RootFd, linkName.c_str());
840 if (result < 0)
841 {
842 return LxError{-errno};
843 }
844
845 return GetFileQidByPath(m_Root->RootFd, linkName);
846 }
847
848 // Reads the target of a symbolic link.
849 Expected<UINT32> File::ReadLink(gsl::span<char> name)
850 {
851 const auto fileName = GetFileName();
852 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
853 ssize_t result = readlinkat(m_Root->RootFd, fileName.c_str(), name.data(), name.size());
854 if (result < 0)
855 {
856 return LxError{-errno};
857 }
858
859 return result;
860 }
861
862 // Creates a hard link in a directory to another file.
863 LX_INT File::Link(std::string_view newName, Fid& target)
864 {
865 if (!target.IsFile() || !target.IsOnRoot(m_Root))
866 {
867 return LX_EINVAL;
868 }
869
870 if (m_Root->ReadOnly())
871 {
872 return LX_EROFS;
873 }
874
875 const auto targetFile = static_cast<File&>(target);
876
877 // Construct the new name relative to the share root.
878 const auto newLinkName = ChildPath(newName);
879 const auto targetName = targetFile.GetFileName();
880 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
881 int result = linkat(m_Root->RootFd, targetName.c_str(), m_Root->RootFd, newLinkName.c_str(), 0);
882 if (result < 0)
883 {
884 return -errno;
885 }
886
887 return {};
888 }
889
890 // Creates a device object in a directory.
891 Expected<Qid> File::MkNod(std::string_view name, UINT32 mode, UINT32 major, UINT32 minor, UINT32 gid)
892 {
893 if (m_Root->ReadOnly())
894 {
895 return LxError{LX_EROFS};
896 }
897
898 const auto path = ChildPath(name);
899
900 // The specified gid is currently ignored. Supporting it would be possible, but it would be
901 // necessary to make sure that the user is a member of the specified group.
902 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
903 int result = mknodat(m_Root->RootFd, path.c_str(), mode, makedev(major, minor));
904 if (result < 0)
905 {
906 return LxError{-errno};
907 }
908
909 return GetFileQidByPath(m_Root->RootFd, path);
910 }
911
912 // Flushes a file's buffers.
913 LX_INT File::Fsync()
914 {
915 if (!m_File)
916 {
917 return LX_EINVAL;
918 }
919
920 int result = fsync(m_File.get());
921 if (result < 0)
922 {
923 return -errno;
924 }
925
926 return {};
927 }
928
929 // Retrieves the file system attributes.
930 Expected<StatFsResult> File::StatFs()
931 {
932 // Open the file because there is no statfsat.
933 auto file{OpenFile(O_PATH)};
934 if (!file)
935 {
936 return file.Unexpected();
937 }
938
939 struct statfs statFs;
940 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
941 int result = fstatfs(file->get(), &statFs);
942 if (result < 0)
943 {
944 return LxError{-errno};
945 }
946
947 StatFsResult statFsResult;
948 statFsResult.Type = static_cast<UINT32>(statFs.f_type);
949 statFsResult.BlockSize = static_cast<UINT32>(statFs.f_bsize);
950 statFsResult.Blocks = statFs.f_blocks;
951 statFsResult.BlocksFree = statFs.f_bfree;
952 statFsResult.BlocksAvailable = statFs.f_bavail;
953 statFsResult.Files = statFs.f_files;
954 statFsResult.FilesFree = statFs.f_ffree;
955 statFsResult.NameLength = static_cast<UINT32>(statFs.f_namelen);
956
957 static_assert(sizeof(statFsResult.FsId) == sizeof(statFs.f_fsid));
958 // These two fields should be the same size (asserted above).
959 memcpy(&statFsResult.FsId, &statFs.f_fsid, sizeof(statFsResult.FsId));
960 return statFsResult;
961 }
962
963 // Locks a range of the file.
964 Expected<LockStatus> File::Lock(LockType, UINT32, UINT64, UINT64, UINT32, const std::string_view)
965 {
966 // The file has to be open for lock to work.
967 if (!IsOpen())
968 {
969 return LxError{LX_EBADF};
970 }
971
972 // This implementation always returns success. The Linux kernel still
973 // provides proper file locking, and this call seems to only be used
974 // to check for server locking between multiple clients. That means a
975 // no-op implementation works for a single client.
976 //
977 // TODO: Implement server-side locks.
978 return LockStatus::Success;
979 }
980
981 // Gets information about the current lock on the file.
982 Expected<std::tuple<LockType, UINT64, UINT64, UINT32, std::string_view>> File::GetLock(
983 LockType, UINT64 Start, UINT64 Length, UINT32 ProcId, const std::string_view ClientId)
984 {
985 // The file has to be open for getlock to work.
986 if (!IsOpen())
987 {
988 return LxError{LX_EBADF};
989 }
990
991 // This implementation always returns unlocked, and echoes the rest of
992 // the values back to the client. The Linux kernel still provides
993 // proper file locking, and returns the correct information even if the
994 // server says unlocked. That means a no-op implementation works for a
995 // single client.
996 //
997 // TODO: Implement server-side locks.
998 return std::make_tuple(LockType::Unlock, Start, Length, ProcId, ClientId);
999 }
1000
1001 // Created a new Fid representing an extended attribute.
1002 Expected<std::shared_ptr<XAttrBase>> File::XattrWalk(const std::string& name)
1003 {
1004 // N.B. There is no *xattrat or equivalent, so f*xattr must be used
1005 // to avoid constructing the full file name. However, f*xattr doesn't work
1006 // on file descriptors opened with O_PATH, so they can't be used on symlinks,
1007 // even though the various l*xattr functions do allow manipulating xattrs
1008 // on symlinks. This means there's no way to support xattrs on symlinks
1009 // without using the full file name, which is less than ideal.
1010 // TODO: Use a chroot environment to make this safer.
1011 auto path = util::GetFdPath(m_Root->RootFd);
1012 AppendPath(path, GetFileName());
1013 std::shared_ptr<XAttrBase> xattr = std::make_shared<XAttr>(m_Root, path, name, XAttr::Access::Read);
1014 return xattr;
1015 }
1016
1017 Expected<std::shared_ptr<XAttrBase>> File::XattrCreate(const std::string& name, UINT64 size, UINT32 flags)
1018 {
1019 if (m_Root->ReadOnly())
1020 {
1021 return LxError{LX_EROFS};
1022 }
1023
1024 // Since the caller will end up replacing the original fid with the one
1025 // returned, make sure this wasn't an open fid.
1026 if (IsOpen())
1027 {
1028 return LxError{LX_EINVAL};
1029 }
1030
1031 // See above for the reason for doing this.
1032 auto path = util::GetFdPath(m_Root->RootFd);
1033 AppendPath(path, GetFileName());
1034 std::shared_ptr<XAttrBase> xattr = std::make_shared<XAttr>(m_Root, path, name, XAttr::Access::Write, size, flags);
1035 return xattr;
1036 }
1037
1038 LX_INT File::Access(AccessFlags flags)
1039 {
1040 AccessFlags flagsWithoutDelete = flags;
1041 WI_ClearFlag(flagsWithoutDelete, AccessFlags::Delete);
1042 const auto name = GetFileName();
1043 util::FsUserContext userContext{m_Root->Uid, m_Root->Gid, m_Root->Groups};
1044 LX_INT result = util::AccessHelper(m_Root->RootFd, name, static_cast<int>(flagsWithoutDelete));
1045 if (result < 0)
1046 {
1047 return result;
1048 }
1049
1050 // No delete check requested? Done!
1051 if (!WI_IsFlagSet(flags, AccessFlags::Delete))
1052 {
1053 return {};
1054 }
1055
1056 if (name.length() == 0)
1057 {
1058 // Can't delete the root.
1059 return LX_EACCES;
1060 }
1061
1062 std::string parentPath;
1063 const auto index = name.find_last_of('/');
1064 if (index != std::string::npos)
1065 {
1066 parentPath = name.substr(0, index);
1067 }
1068
1069 // Check for write access to the parent.
1070 result = util::AccessHelper(m_Root->RootFd, parentPath, W_OK);
1071 if (result < 0)
1072 {
1073 return result;
1074 }
1075
1076 // Get the parent's attributes.
1077 struct stat st;
1078 result = fstatat(m_Root->RootFd, parentPath.c_str(), &st, AT_EMPTY_PATH);
1079 if (result < 0)
1080 {
1081 return -errno;
1082 }
1083
1084 // No sticky bit? Done!
1085 if (!WI_IsFlagSet(st.st_mode, S_ISVTX))
1086 {
1087 return {};
1088 }
1089
1090 // Check if this process has CAP_FOWNER, which means it can bypass the
1091 // sticky bit.
1092 result = util::CheckFOwnerCapability();
1093 if (result == 0)
1094 {
1095 return {};
1096 }
1097 else if (result != LX_EPERM)
1098 {
1099 return result;
1100 }
1101
1102 // Check for ownership of the parent directory.
1103 uid_t uid = geteuid();
1104 if (uid == st.st_uid)
1105 {
1106 return {};
1107 }
1108
1109 // Check for ownership of the child.
1110 result = fstatat(m_Root->RootFd, name.c_str(), &st, AT_EMPTY_PATH);
1111 if (result < 0)
1112 {
1113 return -errno;
1114 }
1115
1116 if (uid == st.st_uid)
1117 {
1118 return {};
1119 }
1120
1121 // Stick bit checks failed.
1122 return LX_EACCES;
1123 }
1124
1125 std::shared_ptr<Fid> File::Clone() const
1126 {
1127 // Requires the lock to protect the file name.
1128 std::shared_lock<std::shared_mutex> lock{m_Lock};
1129 return std::make_shared<File>(*this);
1130 }
1131
1132 bool File::IsOpen() const
1133 {
1134 return bool{m_File} || m_Enumerator;
1135 }
1136
1137 bool File::IsFile() const
1138 {
1139 return true;
1140 }
1141
1142 Qid File::GetQid() const
1143 {
1144 return m_Qid;
1145 }
1146
1147 } // namespace p9fs