diff options
Diffstat (limited to 'pkg/sentry/fsimpl/proc/task_files.go')
-rw-r--r-- | pkg/sentry/fsimpl/proc/task_files.go | 821 |
1 files changed, 0 insertions, 821 deletions
diff --git a/pkg/sentry/fsimpl/proc/task_files.go b/pkg/sentry/fsimpl/proc/task_files.go deleted file mode 100644 index 8c743df8d..000000000 --- a/pkg/sentry/fsimpl/proc/task_files.go +++ /dev/null @@ -1,821 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package proc - -import ( - "bytes" - "fmt" - "io" - "sort" - "strings" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fsbridge" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/limits" - "gvisor.dev/gvisor/pkg/sentry/mm" - "gvisor.dev/gvisor/pkg/sentry/usage" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserror" - "gvisor.dev/gvisor/pkg/usermem" -) - -// mm gets the kernel task's MemoryManager. No additional reference is taken on -// mm here. This is safe because MemoryManager.destroy is required to leave the -// MemoryManager in a state where it's still usable as a DynamicBytesSource. -func getMM(task *kernel.Task) *mm.MemoryManager { - var tmm *mm.MemoryManager - task.WithMuLocked(func(t *kernel.Task) { - if mm := t.MemoryManager(); mm != nil { - tmm = mm - } - }) - return tmm -} - -// getMMIncRef returns t's MemoryManager. If getMMIncRef succeeds, the -// MemoryManager's users count is incremented, and must be decremented by the -// caller when it is no longer in use. -func getMMIncRef(task *kernel.Task) (*mm.MemoryManager, error) { - if task.ExitState() == kernel.TaskExitDead { - return nil, syserror.ESRCH - } - var m *mm.MemoryManager - task.WithMuLocked(func(t *kernel.Task) { - m = t.MemoryManager() - }) - if m == nil || !m.IncUsers() { - return nil, io.EOF - } - return m, nil -} - -type bufferWriter struct { - buf *bytes.Buffer -} - -// WriteFromBlocks writes up to srcs.NumBytes() bytes from srcs and returns -// the number of bytes written. It may return a partial write without an -// error (i.e. (n, nil) where 0 < n < srcs.NumBytes()). It should not -// return a full write with an error (i.e. srcs.NumBytes(), err) where err -// != nil). -func (w *bufferWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) { - written := srcs.NumBytes() - for !srcs.IsEmpty() { - w.buf.Write(srcs.Head().ToSlice()) - srcs = srcs.Tail() - } - return written, nil -} - -// auxvData implements vfs.DynamicBytesSource for /proc/[pid]/auxv. -// -// +stateify savable -type auxvData struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -var _ dynamicInode = (*auxvData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (d *auxvData) Generate(ctx context.Context, buf *bytes.Buffer) error { - m, err := getMMIncRef(d.task) - if err != nil { - return err - } - defer m.DecUsers(ctx) - - // Space for buffer with AT_NULL (0) terminator at the end. - auxv := m.Auxv() - buf.Grow((len(auxv) + 1) * 16) - for _, e := range auxv { - var tmp [8]byte - usermem.ByteOrder.PutUint64(tmp[:], e.Key) - buf.Write(tmp[:]) - - usermem.ByteOrder.PutUint64(tmp[:], uint64(e.Value)) - buf.Write(tmp[:]) - } - return nil -} - -// execArgType enumerates the types of exec arguments that are exposed through -// proc. -type execArgType int - -const ( - cmdlineDataArg execArgType = iota - environDataArg -) - -// cmdlineData implements vfs.DynamicBytesSource for /proc/[pid]/cmdline. -// -// +stateify savable -type cmdlineData struct { - kernfs.DynamicBytesFile - - task *kernel.Task - - // arg is the type of exec argument this file contains. - arg execArgType -} - -var _ dynamicInode = (*cmdlineData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (d *cmdlineData) Generate(ctx context.Context, buf *bytes.Buffer) error { - m, err := getMMIncRef(d.task) - if err != nil { - return err - } - defer m.DecUsers(ctx) - - // Figure out the bounds of the exec arg we are trying to read. - var ar usermem.AddrRange - switch d.arg { - case cmdlineDataArg: - ar = usermem.AddrRange{ - Start: m.ArgvStart(), - End: m.ArgvEnd(), - } - case environDataArg: - ar = usermem.AddrRange{ - Start: m.EnvvStart(), - End: m.EnvvEnd(), - } - default: - panic(fmt.Sprintf("unknown exec arg type %v", d.arg)) - } - if ar.Start == 0 || ar.End == 0 { - // Don't attempt to read before the start/end are set up. - return io.EOF - } - - // N.B. Technically this should be usermem.IOOpts.IgnorePermissions = true - // until Linux 4.9 (272ddc8b3735 "proc: don't use FOLL_FORCE for reading - // cmdline and environment"). - writer := &bufferWriter{buf: buf} - if n, err := m.CopyInTo(ctx, usermem.AddrRangeSeqOf(ar), writer, usermem.IOOpts{}); n == 0 || err != nil { - // Nothing to copy or something went wrong. - return err - } - - // On Linux, if the NULL byte at the end of the argument vector has been - // overwritten, it continues reading the environment vector as part of - // the argument vector. - if d.arg == cmdlineDataArg && buf.Bytes()[buf.Len()-1] != 0 { - if end := bytes.IndexByte(buf.Bytes(), 0); end != -1 { - // If we found a NULL character somewhere else in argv, truncate the - // return up to the NULL terminator (including it). - buf.Truncate(end) - return nil - } - - // There is no NULL terminator in the string, return into envp. - arEnvv := usermem.AddrRange{ - Start: m.EnvvStart(), - End: m.EnvvEnd(), - } - - // Upstream limits the returned amount to one page of slop. - // https://elixir.bootlin.com/linux/v4.20/source/fs/proc/base.c#L208 - // we'll return one page total between argv and envp because of the - // above page restrictions. - if buf.Len() >= usermem.PageSize { - // Returned at least one page already, nothing else to add. - return nil - } - remaining := usermem.PageSize - buf.Len() - if int(arEnvv.Length()) > remaining { - end, ok := arEnvv.Start.AddLength(uint64(remaining)) - if !ok { - return syserror.EFAULT - } - arEnvv.End = end - } - if _, err := m.CopyInTo(ctx, usermem.AddrRangeSeqOf(arEnvv), writer, usermem.IOOpts{}); err != nil { - return err - } - - // Linux will return envp up to and including the first NULL character, - // so find it. - if end := bytes.IndexByte(buf.Bytes()[ar.Length():], 0); end != -1 { - buf.Truncate(end) - } - } - - return nil -} - -// +stateify savable -type commInode struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -func newComm(task *kernel.Task, ino uint64, perm linux.FileMode) *kernfs.Dentry { - inode := &commInode{task: task} - inode.DynamicBytesFile.Init(task.Credentials(), ino, &commData{task: task}, perm) - - d := &kernfs.Dentry{} - d.Init(inode) - return d -} - -func (i *commInode) CheckPermissions(ctx context.Context, creds *auth.Credentials, ats vfs.AccessTypes) error { - // This file can always be read or written by members of the same thread - // group. See fs/proc/base.c:proc_tid_comm_permission. - // - // N.B. This check is currently a no-op as we don't yet support writing and - // this file is world-readable anyways. - t := kernel.TaskFromContext(ctx) - if t != nil && t.ThreadGroup() == i.task.ThreadGroup() && !ats.MayExec() { - return nil - } - - return i.DynamicBytesFile.CheckPermissions(ctx, creds, ats) -} - -// commData implements vfs.DynamicBytesSource for /proc/[pid]/comm. -// -// +stateify savable -type commData struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -var _ dynamicInode = (*commData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (d *commData) Generate(ctx context.Context, buf *bytes.Buffer) error { - buf.WriteString(d.task.Name()) - buf.WriteString("\n") - return nil -} - -// idMapData implements vfs.DynamicBytesSource for /proc/[pid]/{gid_map|uid_map}. -// -// +stateify savable -type idMapData struct { - kernfs.DynamicBytesFile - - task *kernel.Task - gids bool -} - -var _ dynamicInode = (*idMapData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (d *idMapData) Generate(ctx context.Context, buf *bytes.Buffer) error { - var entries []auth.IDMapEntry - if d.gids { - entries = d.task.UserNamespace().GIDMap() - } else { - entries = d.task.UserNamespace().UIDMap() - } - for _, e := range entries { - fmt.Fprintf(buf, "%10d %10d %10d\n", e.FirstID, e.FirstParentID, e.Length) - } - return nil -} - -// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps. -// -// +stateify savable -type mapsData struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -var _ dynamicInode = (*mapsData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (d *mapsData) Generate(ctx context.Context, buf *bytes.Buffer) error { - if mm := getMM(d.task); mm != nil { - mm.ReadMapsDataInto(ctx, buf) - } - return nil -} - -// smapsData implements vfs.DynamicBytesSource for /proc/[pid]/smaps. -// -// +stateify savable -type smapsData struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -var _ dynamicInode = (*smapsData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (d *smapsData) Generate(ctx context.Context, buf *bytes.Buffer) error { - if mm := getMM(d.task); mm != nil { - mm.ReadSmapsDataInto(ctx, buf) - } - return nil -} - -// +stateify savable -type taskStatData struct { - kernfs.DynamicBytesFile - - task *kernel.Task - - // If tgstats is true, accumulate fault stats (not implemented) and CPU - // time across all tasks in t's thread group. - tgstats bool - - // pidns is the PID namespace associated with the proc filesystem that - // includes the file using this statData. - pidns *kernel.PIDNamespace -} - -var _ dynamicInode = (*taskStatData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (s *taskStatData) Generate(ctx context.Context, buf *bytes.Buffer) error { - fmt.Fprintf(buf, "%d ", s.pidns.IDOfTask(s.task)) - fmt.Fprintf(buf, "(%s) ", s.task.Name()) - fmt.Fprintf(buf, "%c ", s.task.StateStatus()[0]) - ppid := kernel.ThreadID(0) - if parent := s.task.Parent(); parent != nil { - ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup()) - } - fmt.Fprintf(buf, "%d ", ppid) - fmt.Fprintf(buf, "%d ", s.pidns.IDOfProcessGroup(s.task.ThreadGroup().ProcessGroup())) - fmt.Fprintf(buf, "%d ", s.pidns.IDOfSession(s.task.ThreadGroup().Session())) - fmt.Fprintf(buf, "0 0 " /* tty_nr tpgid */) - fmt.Fprintf(buf, "0 " /* flags */) - fmt.Fprintf(buf, "0 0 0 0 " /* minflt cminflt majflt cmajflt */) - var cputime usage.CPUStats - if s.tgstats { - cputime = s.task.ThreadGroup().CPUStats() - } else { - cputime = s.task.CPUStats() - } - fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime)) - cputime = s.task.ThreadGroup().JoinedChildCPUStats() - fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime)) - fmt.Fprintf(buf, "%d %d ", s.task.Priority(), s.task.Niceness()) - fmt.Fprintf(buf, "%d ", s.task.ThreadGroup().Count()) - - // itrealvalue. Since kernel 2.6.17, this field is no longer - // maintained, and is hard coded as 0. - fmt.Fprintf(buf, "0 ") - - // Start time is relative to boot time, expressed in clock ticks. - fmt.Fprintf(buf, "%d ", linux.ClockTFromDuration(s.task.StartTime().Sub(s.task.Kernel().Timekeeper().BootTime()))) - - var vss, rss uint64 - s.task.WithMuLocked(func(t *kernel.Task) { - if mm := t.MemoryManager(); mm != nil { - vss = mm.VirtualMemorySize() - rss = mm.ResidentSetSize() - } - }) - fmt.Fprintf(buf, "%d %d ", vss, rss/usermem.PageSize) - - // rsslim. - fmt.Fprintf(buf, "%d ", s.task.ThreadGroup().Limits().Get(limits.Rss).Cur) - - fmt.Fprintf(buf, "0 0 0 0 0 " /* startcode endcode startstack kstkesp kstkeip */) - fmt.Fprintf(buf, "0 0 0 0 0 " /* signal blocked sigignore sigcatch wchan */) - fmt.Fprintf(buf, "0 0 " /* nswap cnswap */) - terminationSignal := linux.Signal(0) - if s.task == s.task.ThreadGroup().Leader() { - terminationSignal = s.task.ThreadGroup().TerminationSignal() - } - fmt.Fprintf(buf, "%d ", terminationSignal) - fmt.Fprintf(buf, "0 0 0 " /* processor rt_priority policy */) - fmt.Fprintf(buf, "0 0 0 " /* delayacct_blkio_ticks guest_time cguest_time */) - fmt.Fprintf(buf, "0 0 0 0 0 0 0 " /* start_data end_data start_brk arg_start arg_end env_start env_end */) - fmt.Fprintf(buf, "0\n" /* exit_code */) - - return nil -} - -// statmData implements vfs.DynamicBytesSource for /proc/[pid]/statm. -// -// +stateify savable -type statmData struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -var _ dynamicInode = (*statmData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (s *statmData) Generate(ctx context.Context, buf *bytes.Buffer) error { - var vss, rss uint64 - s.task.WithMuLocked(func(t *kernel.Task) { - if mm := t.MemoryManager(); mm != nil { - vss = mm.VirtualMemorySize() - rss = mm.ResidentSetSize() - } - }) - - fmt.Fprintf(buf, "%d %d 0 0 0 0 0\n", vss/usermem.PageSize, rss/usermem.PageSize) - return nil -} - -// statusData implements vfs.DynamicBytesSource for /proc/[pid]/status. -// -// +stateify savable -type statusData struct { - kernfs.DynamicBytesFile - - task *kernel.Task - pidns *kernel.PIDNamespace -} - -var _ dynamicInode = (*statusData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (s *statusData) Generate(ctx context.Context, buf *bytes.Buffer) error { - fmt.Fprintf(buf, "Name:\t%s\n", s.task.Name()) - fmt.Fprintf(buf, "State:\t%s\n", s.task.StateStatus()) - fmt.Fprintf(buf, "Tgid:\t%d\n", s.pidns.IDOfThreadGroup(s.task.ThreadGroup())) - fmt.Fprintf(buf, "Pid:\t%d\n", s.pidns.IDOfTask(s.task)) - ppid := kernel.ThreadID(0) - if parent := s.task.Parent(); parent != nil { - ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup()) - } - fmt.Fprintf(buf, "PPid:\t%d\n", ppid) - tpid := kernel.ThreadID(0) - if tracer := s.task.Tracer(); tracer != nil { - tpid = s.pidns.IDOfTask(tracer) - } - fmt.Fprintf(buf, "TracerPid:\t%d\n", tpid) - var fds int - var vss, rss, data uint64 - s.task.WithMuLocked(func(t *kernel.Task) { - if fdTable := t.FDTable(); fdTable != nil { - fds = fdTable.Size() - } - if mm := t.MemoryManager(); mm != nil { - vss = mm.VirtualMemorySize() - rss = mm.ResidentSetSize() - data = mm.VirtualDataSize() - } - }) - fmt.Fprintf(buf, "FDSize:\t%d\n", fds) - fmt.Fprintf(buf, "VmSize:\t%d kB\n", vss>>10) - fmt.Fprintf(buf, "VmRSS:\t%d kB\n", rss>>10) - fmt.Fprintf(buf, "VmData:\t%d kB\n", data>>10) - fmt.Fprintf(buf, "Threads:\t%d\n", s.task.ThreadGroup().Count()) - creds := s.task.Credentials() - fmt.Fprintf(buf, "CapInh:\t%016x\n", creds.InheritableCaps) - fmt.Fprintf(buf, "CapPrm:\t%016x\n", creds.PermittedCaps) - fmt.Fprintf(buf, "CapEff:\t%016x\n", creds.EffectiveCaps) - fmt.Fprintf(buf, "CapBnd:\t%016x\n", creds.BoundingCaps) - fmt.Fprintf(buf, "Seccomp:\t%d\n", s.task.SeccompMode()) - // We unconditionally report a single NUMA node. See - // pkg/sentry/syscalls/linux/sys_mempolicy.go. - fmt.Fprintf(buf, "Mems_allowed:\t1\n") - fmt.Fprintf(buf, "Mems_allowed_list:\t0\n") - return nil -} - -// ioUsage is the /proc/[pid]/io and /proc/[pid]/task/[tid]/io data provider. -type ioUsage interface { - // IOUsage returns the io usage data. - IOUsage() *usage.IO -} - -// +stateify savable -type ioData struct { - kernfs.DynamicBytesFile - - ioUsage -} - -var _ dynamicInode = (*ioData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (i *ioData) Generate(ctx context.Context, buf *bytes.Buffer) error { - io := usage.IO{} - io.Accumulate(i.IOUsage()) - - fmt.Fprintf(buf, "char: %d\n", io.CharsRead) - fmt.Fprintf(buf, "wchar: %d\n", io.CharsWritten) - fmt.Fprintf(buf, "syscr: %d\n", io.ReadSyscalls) - fmt.Fprintf(buf, "syscw: %d\n", io.WriteSyscalls) - fmt.Fprintf(buf, "read_bytes: %d\n", io.BytesRead) - fmt.Fprintf(buf, "write_bytes: %d\n", io.BytesWritten) - fmt.Fprintf(buf, "cancelled_write_bytes: %d\n", io.BytesWriteCancelled) - return nil -} - -// oomScoreAdj is a stub of the /proc/<pid>/oom_score_adj file. -// -// +stateify savable -type oomScoreAdj struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -var _ vfs.WritableDynamicBytesSource = (*oomScoreAdj)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (o *oomScoreAdj) Generate(ctx context.Context, buf *bytes.Buffer) error { - if o.task.ExitState() == kernel.TaskExitDead { - return syserror.ESRCH - } - fmt.Fprintf(buf, "%d\n", o.task.OOMScoreAdj()) - return nil -} - -// Write implements vfs.WritableDynamicBytesSource.Write. -func (o *oomScoreAdj) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { - if src.NumBytes() == 0 { - return 0, nil - } - - // Limit input size so as not to impact performance if input size is large. - src = src.TakeFirst(usermem.PageSize - 1) - - var v int32 - n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts) - if err != nil { - return 0, err - } - - if o.task.ExitState() == kernel.TaskExitDead { - return 0, syserror.ESRCH - } - if err := o.task.SetOOMScoreAdj(v); err != nil { - return 0, err - } - - return n, nil -} - -// exeSymlink is an symlink for the /proc/[pid]/exe file. -// -// +stateify savable -type exeSymlink struct { - kernfs.InodeAttrs - kernfs.InodeNoopRefCount - kernfs.InodeSymlink - - task *kernel.Task -} - -var _ kernfs.Inode = (*exeSymlink)(nil) - -func newExeSymlink(task *kernel.Task, ino uint64) *kernfs.Dentry { - inode := &exeSymlink{task: task} - inode.Init(task.Credentials(), ino, linux.ModeSymlink|0777) - - d := &kernfs.Dentry{} - d.Init(inode) - return d -} - -// Readlink implements kernfs.Inode. -func (s *exeSymlink) Readlink(ctx context.Context) (string, error) { - if !kernel.ContextCanTrace(ctx, s.task, false) { - return "", syserror.EACCES - } - - // Pull out the executable for /proc/[pid]/exe. - exec, err := s.executable() - if err != nil { - return "", err - } - defer exec.DecRef() - - return exec.PathnameWithDeleted(ctx), nil -} - -func (s *exeSymlink) executable() (file fsbridge.File, err error) { - s.task.WithMuLocked(func(t *kernel.Task) { - mm := t.MemoryManager() - if mm == nil { - // TODO(b/34851096): Check shouldn't allow Readlink once the - // Task is zombied. - err = syserror.EACCES - return - } - - // The MemoryManager may be destroyed, in which case - // MemoryManager.destroy will simply set the executable to nil - // (with locks held). - file = mm.Executable() - if file == nil { - err = syserror.ENOENT - } - }) - return -} - -// forEachMountSource runs f for the process root mount and each mount that is -// a descendant of the root. -func forEachMount(t *kernel.Task, fn func(string, *fs.Mount)) { - var fsctx *kernel.FSContext - t.WithMuLocked(func(t *kernel.Task) { - fsctx = t.FSContext() - }) - if fsctx == nil { - // The task has been destroyed. Nothing to show here. - return - } - - // All mount points must be relative to the rootDir, and mounts outside - // will be excluded. - rootDir := fsctx.RootDirectory() - if rootDir == nil { - // The task has been destroyed. Nothing to show here. - return - } - defer rootDir.DecRef() - - mnt := t.MountNamespace().FindMount(rootDir) - if mnt == nil { - // Has it just been unmounted? - return - } - ms := t.MountNamespace().AllMountsUnder(mnt) - sort.Slice(ms, func(i, j int) bool { - return ms[i].ID < ms[j].ID - }) - for _, m := range ms { - mroot := m.Root() - if mroot == nil { - continue // No longer valid. - } - mountPath, desc := mroot.FullName(rootDir) - mroot.DecRef() - if !desc { - // MountSources that are not descendants of the chroot jail are ignored. - continue - } - fn(mountPath, m) - } -} - -// mountInfoData is used to implement /proc/[pid]/mountinfo. -// -// +stateify savable -type mountInfoData struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -var _ dynamicInode = (*mountInfoData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (i *mountInfoData) Generate(ctx context.Context, buf *bytes.Buffer) error { - forEachMount(i.task, func(mountPath string, m *fs.Mount) { - mroot := m.Root() - if mroot == nil { - return // No longer valid. - } - defer mroot.DecRef() - - // Format: - // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) - - // (1) MountSource ID. - fmt.Fprintf(buf, "%d ", m.ID) - - // (2) Parent ID (or this ID if there is no parent). - pID := m.ID - if !m.IsRoot() && !m.IsUndo() { - pID = m.ParentID - } - fmt.Fprintf(buf, "%d ", pID) - - // (3) Major:Minor device ID. We don't have a superblock, so we - // just use the root inode device number. - sa := mroot.Inode.StableAttr - fmt.Fprintf(buf, "%d:%d ", sa.DeviceFileMajor, sa.DeviceFileMinor) - - // (4) Root: the pathname of the directory in the filesystem - // which forms the root of this mount. - // - // NOTE(b/78135857): This will always be "/" until we implement - // bind mounts. - fmt.Fprintf(buf, "/ ") - - // (5) Mount point (relative to process root). - fmt.Fprintf(buf, "%s ", mountPath) - - // (6) Mount options. - flags := mroot.Inode.MountSource.Flags - opts := "rw" - if flags.ReadOnly { - opts = "ro" - } - if flags.NoAtime { - opts += ",noatime" - } - if flags.NoExec { - opts += ",noexec" - } - fmt.Fprintf(buf, "%s ", opts) - - // (7) Optional fields: zero or more fields of the form "tag[:value]". - // (8) Separator: the end of the optional fields is marked by a single hyphen. - fmt.Fprintf(buf, "- ") - - // (9) Filesystem type. - fmt.Fprintf(buf, "%s ", mroot.Inode.MountSource.FilesystemType) - - // (10) Mount source: filesystem-specific information or "none". - fmt.Fprintf(buf, "none ") - - // (11) Superblock options, and final newline. - fmt.Fprintf(buf, "%s\n", superBlockOpts(mountPath, mroot.Inode.MountSource)) - }) - return nil -} - -func superBlockOpts(mountPath string, msrc *fs.MountSource) string { - // gVisor doesn't (yet) have a concept of super block options, so we - // use the ro/rw bit from the mount flag. - opts := "rw" - if msrc.Flags.ReadOnly { - opts = "ro" - } - - // NOTE(b/147673608): If the mount is a cgroup, we also need to include - // the cgroup name in the options. For now we just read that from the - // path. - // TODO(gvisor.dev/issues/190): Once gVisor has full cgroup support, we - // should get this value from the cgroup itself, and not rely on the - // path. - if msrc.FilesystemType == "cgroup" { - splitPath := strings.Split(mountPath, "/") - cgroupType := splitPath[len(splitPath)-1] - opts += "," + cgroupType - } - return opts -} - -// mountsData is used to implement /proc/[pid]/mounts. -// -// +stateify savable -type mountsData struct { - kernfs.DynamicBytesFile - - task *kernel.Task -} - -var _ dynamicInode = (*mountInfoData)(nil) - -// Generate implements vfs.DynamicBytesSource.Generate. -func (i *mountsData) Generate(ctx context.Context, buf *bytes.Buffer) error { - forEachMount(i.task, func(mountPath string, m *fs.Mount) { - // Format: - // <special device or remote filesystem> <mount point> <filesystem type> <mount options> <needs dump> <fsck order> - // - // We use the filesystem name as the first field, since there - // is no real block device we can point to, and we also should - // not expose anything about the remote filesystem. - // - // Only ro/rw option is supported for now. - // - // The "needs dump"and fsck flags are always 0, which is allowed. - root := m.Root() - if root == nil { - return // No longer valid. - } - defer root.DecRef() - - flags := root.Inode.MountSource.Flags - opts := "rw" - if flags.ReadOnly { - opts = "ro" - } - fmt.Fprintf(buf, "%s %s %s %s %d %d\n", "none", mountPath, root.Inode.MountSource.FilesystemType, opts, 0, 0) - }) - return nil -} |