Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: cpud: initial darwin support #296

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions cmds/cpud/init_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2018-2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This is init code for the case that cpu finds itself as pid 1.
// This is duplicative of the real init, but we're implementing it
// as a duplicate so we can get some idea of:
// what an init package should have
// what an init interface should have
// So we take a bit of duplication now to better understand these
// things. We also assume for now this is a busybox environment.
// It is unusual (I guess?) for cpu to be an init in anything else.
// So far, the case for an init pkg is not as strong as I thought
// it might be.
package main

import (
"log"
"runtime"
"syscall"
"time"

"github.com/u-root/u-root/pkg/libinit"
)

func cpuSetup() error {
// The process reaper runs from here, and needs to run
// as PID 1.
runtime.LockOSThread()
log.Printf(`

#### ##### # # ##
# # # # # # ##
# # # # # ##
# ##### # # ##
# # # # #
#### # #### ##
`)
//libinit.SetEnv()
//libinit.CreateRootfs()
libinit.NetInit()
// Wait for orphans, forever.
// Since there is no way of knowning when we are
// done for good, our work here is never done.
// A complication is that for long periods of time, there
// may be no orphans.In that case, sleep for one second,
// and try again. This background load is hardly enough
// to matter. And, in general, it will happen by definition
// when there is nothing to wait for, i.e. there is nothing
// on the node to be upset about.
// Were this ever to be a concern, an option is to kick off
// a process that will never exit, such that wait4 will always
// block and always return when any child process exits.
go func() {
var numReaped int
for {
var (
s syscall.WaitStatus
r syscall.Rusage
)
p, err := syscall.Wait4(-1, &s, 0, &r)
// Once per second, Wait 4 returns if there's nothing
// else to do.
if err != nil && err.Error() == "no child processes" {
continue
}
verbose("orphan reaper: returns with %v", p)
if p == -1 {
verbose("Nothing to wait for, %d wait for so far", numReaped)
time.Sleep(time.Second)
}
if err != nil {
log.Printf("CPUD: a process exited with %v, status %v, rusage %v, err %v", p, s, r, err)
}
numReaped++
}
}()

runtime.UnlockOSThread()
return nil
}
123 changes: 121 additions & 2 deletions cmds/cpud/main_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,128 @@
package main

import (
"fmt"
"flag"
"log"
"os"
"time"

// We use this ssh because it implements port redirection.
// It can not, however, unpack password-protected keys yet.

"github.com/u-root/cpu/session"
)

var (
// For the ssh server part
hostKeyFile = flag.String("hk", "" /*"/etc/ssh/ssh_host_rsa_key"*/, "file for host key")
pubKeyFile = flag.String("pk", "key.pub", "file for public key")
port = flag.String("sp", "17010", "cpu default port")

debug = flag.Bool("d", false, "enable debug prints")
runAsInit = flag.Bool("init", false, "run as init (Debug only; normal test is if we are pid 1")
// v allows debug printing.
// Do not call it directly, call verbose instead.
v = func(string, ...interface{}) {}
remote = flag.Bool("remote", false, "indicates we are the remote side of the cpu session")
network = flag.String("net", "tcp", "network to use")
port9p = flag.String("port9p", "", "port9p # on remote machine for 9p mount")
klog = flag.Bool("klog", false, "Log cpud messages in kernel log, not stdout")

// Some networks are not well behaved, and for them we implement registration.
registerAddr = flag.String("register", "", "address and port to register with after listen on cpu server port")
registerTO = flag.Duration("registerTO", time.Duration(5*time.Second), "time.Duration for Dial address for registering")

// if we start up too quickly, mDNS won't work correctly.
// This sleep may be useful for other cases, so it is here,
// not specifically for mDNS uses.
sleepBeforeServing = flag.Duration("sleepBeforeServing", 0, "add a sleep before serving -- usually only needed if cpud runs as init with mDNS")

pid1 bool
)

func verbose(f string, a ...interface{}) {
if *remote {
v("CPUD(remote):"+f+"\r\n", a...)
} else {
v("CPUD:"+f, a...)
}
}

// There are three distinct cases to cover.
// 1. running as init (indicated by pid == 1 OR -init=true switch
// 2. running as server. pid != 1 AND -remote=true AND -init=false
// 3. running as 'remote', i.e. the thing that starts a command for
// a client. Indicated by remote=true.
//
// case (3) overrides case 2 and 1.
// This has evolved over the years, and, likely, the init and remote
// switches ought to be renamed to 'role'. But so it goes.
// The rules on arguments are very strict now. In the remote case,
// os.Args[1] MUST be remote; no other invocation is accepted, because
// the args to remote and the args to server are different.
// This invocation requirement is known to the server package.
func main() {
fmt.Println("No support on Darwin right now")
log.Println("WARNING: cpud support on Darwin currently limited (no private namespaces or remote fs)")
if len(os.Args) > 1 && (os.Args[1] == "-remote" || os.Args[1] == "-remote=true") {
*remote = true
}

if *remote {
// remote has far fewer args. Since they are specified by the client,
// we want to limit the set of args it can set.
flag.CommandLine = flag.NewFlagSet("cpud-remote", flag.ExitOnError)
debug = flag.Bool("d", false, "enable debug prints")
remote = flag.Bool("remote", false, "indicates we are the remote side of the cpu session")
port9p = flag.String("port9p", "", "port9p # on remote machine for 9p mount")

flag.Parse()
if *debug {
v = log.Printf
session.SetVerbose(verbose)
}
// If we are here, no matter what they may set, *remote must be true.
// sadly, cpud -d -remote=true -remote=false ... works.
*remote = true
} else {
flag.Parse()
// If we are here, no matter what they may set, *remote must be false.
*remote = false
if err := commonsetup(); err != nil {
log.Fatal(err)
}
}
pid := os.Getpid()
pid1 = pid == 1
*runAsInit = *runAsInit || pid1
verbose("Args %v pid %d *runasinit %v *remote %v env %v", os.Args, pid, *runAsInit, *remote, os.Environ())
args := flag.Args()
if *remote {
verbose("args %q, port9p %v", args, *port9p)

// This can happen if the user gets clever and
// invokes cpu with, e.g., nothing but switches.
if len(args) == 0 {
shell, ok := os.LookupEnv("SHELL")
if !ok {
log.Fatal("No arguments and $SHELL is not set")
}
args = []string{shell}
}
s := session.New(*port9p, args[0], args[1:]...)
if err := s.Run(); err != nil {
log.Fatalf("CPUD(remote): %v", err)
}
} else {
log.Printf("CPUD:PID(%d):running as a server (a.k.a. starter of cpud's for sessions)", pid)
if *runAsInit {
log.Printf("CPUD:also running as init")
if err := initsetup(); err != nil {
log.Fatal(err)
}
}
time.Sleep(*sleepBeforeServing)
if err := serve(os.Args[0]); err != nil {
log.Fatal(err)
}
}
}
130 changes: 130 additions & 0 deletions mount/mount_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2018-2022 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mount

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
"syscall"
"unsafe"

"golang.org/x/sys/unix"
)

func init() {
for k, v := range map[string]uintptr{
"mnt_async": unix.MNT_ASYNC,
"mnt_automounted": unix.MNT_AUTOMOUNTED,
"mnt_cmdflags": unix.MNT_CMDFLAGS,
"mnt_exported": unix.MNT_EXPORTED,
"mnt_force": unix.MNT_FORCE,
"mnt_local": unix.MNT_LOCAL,
"mnt_multilabel": unix.MNT_MULTILABEL,
"mnt_noatime": unix.MNT_NOATIME,
"mnt_noexec": unix.MNT_NOEXEC,
"mnt_nosuid": unix.MNT_NOSUID,
"mnt_nowait": unix.MNT_NOWAIT,
"mnt_quota": unix.MNT_QUOTA,
"mnt_rdonly": unix.MNT_RDONLY,
"mnt_reload": unix.MNT_RELOAD,
"mnt_rootfs": unix.MNT_ROOTFS,
"mnt_snapshot": unix.MNT_SNAPSHOT,
"mnt_synchronous": unix.MNT_SYNCHRONOUS,
"mnt_union": unix.MNT_UNION,
"mnt_update": unix.MNT_UPDATE,
"mnt_visflagmask": unix.MNT_VISFLAGMASK,
"mnt_wait": unix.MNT_WAIT,
} {
convert[k] = v
}
}

// iov returns an iovec for a string.
// there is no official package, and it is simple
// enough, that we just create it here.
func iovstring(val string) syscall.Iovec {
s := val + "\x00"
vec := syscall.Iovec{Base: (*byte)(unsafe.Pointer(&[]byte(s)[0]))}
vec.SetLen(len(s))
return vec
}

// Mount takes a full fstab as a string and does whatever mounts are needed.
// It ignores comment lines, and lines with less than 6 fields. In principal,
// Mount should be able to do a full remount with the contents of /proc/mounts.
// Mount makes a best-case effort to mount the mounts passed in a
// string formatted to the fstab standard. Callers should not die on
// a returned error, but be left in a situation in which further
// diagnostics are possible. i.e., follow the "Boots not Bricks"
// principle.
// Freebsd has very different ways of working than linux, so
// we shell out to mount for now.
func Mount(fstab string) error {
f, err := ioutil.TempFile("", "cpu")
if err != nil {
return err
}
defer f.Close()

if _, err := io.WriteString(f, fstab); err != nil {
return err
}

//if o, err := exec.Command("mount", "-a", "-F", f.Name()).CombinedOutput(); err != nil {
// return fmt.Errorf("mount -F %q:%s:%w", f.Name(), string(o), err)
//}

return nil
var lineno int
s := bufio.NewScanner(strings.NewReader(fstab))
for s.Scan() {
lineno++
l := s.Text()
if strings.HasPrefix(l, "#") {
continue
}
f := strings.Fields(l)
// fstab is historical, pretty free format.
// Users may have dropped a random fstab in and we need
// to be forgiving.
// The last two fields no longer have any meaning or use.
if len(f) < 6 {
continue
}

// fstab fields:
// /dev/disk/by-uuid/c0d2b09d-5330-4d08-a787-6e0e95592bf3 /boot ext4 defaults 0 0
// what to mount, where to mount, fstype, options
// We do need NOT to set MS_PRIVATE, since we've done a successful unshare.
// This note is here in case someone gets confused in the future.
// Setting MS_PRIVATE will get an EINVAL.
dev, where, fstype, opts := f[0], f[1], f[2], f[3]

// surprise! It turns out that correct behavior from mount is to follow symlinks
// on where and device and use that. That's why /bin -> /usr/bin gets mounted
// correctly.
if w, err := filepath.EvalSymlinks(where); err == nil {
where = w
}
if w, err := filepath.EvalSymlinks(dev); err == nil {
dev = w
}

// The man page implies that the Linux kernel handles flags of "defaults"
// we do no further manipulation of opts.
flags, data := parse(opts)

fmt.Println("WARNING: DARWIN can't mount", dev, where, fstype, flags, data)
err = nil
//if _, e := mount.Mount(dev, where, fstype, data, flags); e != nil {
// err = errors.Join(err, fmt.Errorf("Mount(%q, %q, %q, %q=>(%#x, %q)): %w", dev, where, fstype, opts, flags, data, e))
//}
}
return err
}
Loading
Loading