From 323d132c40a74c5ac7a91731a0c43def2a8be7f1 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 6 Sep 2025 22:30:08 +0900 Subject: [PATCH] system/mkdir: use syscall dispatcher This enables mkdir op methods to be instrumented. Signed-off-by: Ophestra --- system/dispatcher.go | 10 ++- system/dispatcher_test.go | 14 ++++ system/mkdir.go | 17 ++-- system/mkdir_test.go | 162 +++++++++++++++++++++++--------------- 4 files changed, 129 insertions(+), 74 deletions(-) diff --git a/system/dispatcher.go b/system/dispatcher.go index 5d5c82b..038231b 100644 --- a/system/dispatcher.go +++ b/system/dispatcher.go @@ -15,6 +15,10 @@ type syscallDispatcher interface { // just synchronising access is not enough, as this is for test instrumentation. new(f func(k syscallDispatcher)) + // mkdir provides os.Mkdir. + mkdir(name string, perm os.FileMode) error + // chmod provides os.Chmod. + chmod(name string, mode os.FileMode) error // link provides os.Link. link(oldname, newname string) error // remove provides os.Remove. @@ -44,8 +48,10 @@ type direct struct{} func (k direct) new(f func(k syscallDispatcher)) { go f(k) } -func (k direct) link(oldname, newname string) error { return os.Link(oldname, newname) } -func (k direct) remove(name string) error { return os.Remove(name) } +func (k direct) mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) } +func (k direct) chmod(name string, mode os.FileMode) error { return os.Chmod(name, mode) } +func (k direct) link(oldname, newname string) error { return os.Link(oldname, newname) } +func (k direct) remove(name string) error { return os.Remove(name) } func (k direct) aclUpdate(name string, uid int, perms ...acl.Perm) error { return acl.Update(name, uid, perms...) diff --git a/system/dispatcher_test.go b/system/dispatcher_test.go index c459de4..3497387 100644 --- a/system/dispatcher_test.go +++ b/system/dispatcher_test.go @@ -192,6 +192,20 @@ type kstub struct{ *stub.Stub[syscallDispatcher] } func (k *kstub) new(f func(k syscallDispatcher)) { k.Helper(); k.New(f) } +func (k *kstub) mkdir(name string, perm os.FileMode) error { + k.Helper() + return k.Expects("mkdir").Error( + stub.CheckArg(k.Stub, "name", name, 0), + stub.CheckArg(k.Stub, "perm", perm, 1)) +} + +func (k *kstub) chmod(name string, mode os.FileMode) error { + k.Helper() + return k.Expects("chmod").Error( + stub.CheckArg(k.Stub, "name", name, 0), + stub.CheckArg(k.Stub, "mode", mode, 1)) +} + func (k *kstub) link(oldname, newname string) error { k.Helper() return k.Expects("link").Error( diff --git a/system/mkdir.go b/system/mkdir.go index 64b0249..97673f0 100644 --- a/system/mkdir.go +++ b/system/mkdir.go @@ -28,32 +28,31 @@ type mkdirOp struct { func (m *mkdirOp) Type() Enablement { return m.et } -func (m *mkdirOp) apply(*I) error { - msg.Verbose("ensuring directory", m) +func (m *mkdirOp) apply(sys *I) error { + sys.verbose("ensuring directory", m) - // create directory - if err := os.Mkdir(m.path, m.perm); err != nil { + if err := sys.mkdir(m.path, m.perm); err != nil { if !errors.Is(err, os.ErrExist) { return newOpError("mkdir", err, false) } // directory exists, ensure mode - return newOpError("mkdir", os.Chmod(m.path, m.perm), false) + return newOpError("mkdir", sys.chmod(m.path, m.perm), false) } else { return nil } } -func (m *mkdirOp) revert(_ *I, ec *Criteria) error { +func (m *mkdirOp) revert(sys *I, ec *Criteria) error { if !m.ephemeral { // skip non-ephemeral dir and do not log anything return nil } if ec.hasType(m.Type()) { - msg.Verbose("destroying ephemeral directory", m) - return newOpError("mkdir", os.Remove(m.path), true) + sys.verbose("destroying ephemeral directory", m) + return newOpError("mkdir", sys.remove(m.path), true) } else { - msg.Verbose("skipping ephemeral directory", m) + sys.verbose("skipping ephemeral directory", m) return nil } } diff --git a/system/mkdir_test.go b/system/mkdir_test.go index 2ead2e1..dd7104b 100644 --- a/system/mkdir_test.go +++ b/system/mkdir_test.go @@ -4,72 +4,108 @@ import ( "os" "testing" - "hakurei.app/container" + "hakurei.app/container/stub" ) -func TestEnsure(t *testing.T) { - testCases := []struct { - name string - perm os.FileMode - }{ - {"/tmp/hakurei.1971", 0701}, - {"/tmp/hakurei.1971/tmpdir", 0700}, - {"/tmp/hakurei.1971/tmpdir/150", 0700}, - {"/run/user/1971/hakurei", 0700}, - } - for _, tc := range testCases { - t.Run(tc.name+"_"+tc.perm.String(), func(t *testing.T) { - sys := New(t.Context(), 150) - sys.Ensure(tc.name, tc.perm) - (&tcOp{User, tc.name}).test(t, sys.ops, []Op{&mkdirOp{User, tc.name, tc.perm, false}}, "Ensure") - }) - } -} +func TestMkdirOp(t *testing.T) { + checkOpBehaviour(t, []opBehaviourTestCase{ + {"mkdir", 0xdeadbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}}}, nil, nil), + call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, stub.UniqueError(2)), + }, &OpError{Op: "mkdir", Err: stub.UniqueError(2)}, nil, nil}, -func TestEphemeral(t *testing.T) { - testCases := []struct { - perm os.FileMode - tcOp - }{ - {0700, tcOp{Process, "/run/user/1971/hakurei/ec07546a772a07cde87389afc84ffd13"}}, - {0701, tcOp{Process, "/tmp/hakurei.1971/ec07546a772a07cde87389afc84ffd13"}}, - } - for _, tc := range testCases { - t.Run(tc.path+"_"+tc.perm.String()+"_"+TypeString(tc.et), func(t *testing.T) { - sys := New(t.Context(), 150) - sys.Ephemeral(tc.et, tc.path, tc.perm) - tc.test(t, sys.ops, []Op{&mkdirOp{tc.et, tc.path, tc.perm, true}}, "Ephemeral") - }) - } -} + {"chmod", 0xdeadbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}}}, nil, nil), + call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, os.ErrExist), + call("chmod", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, stub.UniqueError(1)), + }, &OpError{Op: "mkdir", Err: stub.UniqueError(1)}, nil, nil}, -func TestMkdirString(t *testing.T) { - testCases := []struct { - want string - ephemeral bool - et Enablement - }{ - {"ensure", false, User}, - {"ensure", false, Process}, - {"ensure", false, EWayland}, + {"remove", 0xdeadbeef, 0xff, &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil), + call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil), + }, nil, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil), + call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, stub.UniqueError(0)), + }, &OpError{Op: "mkdir", Err: stub.UniqueError(0), Revert: true}}, - {"wayland", true, EWayland}, - {"x11", true, EX11}, - {"dbus", true, EDBus}, - {"pulseaudio", true, EPulse}, - } - for _, tc := range testCases { - t.Run(tc.want, func(t *testing.T) { - m := &mkdirOp{ - et: tc.et, - path: container.Nonexistent, - perm: 0701, - ephemeral: tc.ephemeral, - } - want := "mode: " + os.FileMode(0701).String() + " type: " + tc.want + ` path: "/proc/nonexistent"` - if got := m.String(); got != want { - t.Errorf("String() = %v, want %v", got, want) - } - }) - } + {"success exist chmod", 0xdeadbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}}}, nil, nil), + call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, os.ErrExist), + call("chmod", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil), + }, nil, nil, nil}, + + {"success ensure", 0xdeadbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}}}, nil, nil), + call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil), + }, nil, nil, nil}, + + {"success skip", 0xdeadbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil), + call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil), + }, nil, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"skipping ephemeral directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil), + }, nil}, + + {"success", 0xdeadbeef, 0xff, &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil), + call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil), + }, nil, []stub.Call{ + call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil), + call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, nil), + }, nil}, + }) + + checkOpsBuilder(t, "EnsureEphemeral", []opsBuilderTestCase{ + {"ensure", 0xcafebabe, func(_ *testing.T, sys *I) { + sys.Ensure("/tmp/hakurei.0", 0700) + }, []Op{ + &mkdirOp{User, "/tmp/hakurei.0", 0700, false}, + }, stub.Expect{}}, + + {"ephemeral", 0xcafebabe, func(_ *testing.T, sys *I) { + sys.Ephemeral(Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711) + }, []Op{ + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}, + }, stub.Expect{}}, + }) + + checkOpIs(t, []opIsTestCase{ + {"nil", (*mkdirOp)(nil), (*mkdirOp)(nil), false}, + {"zero", new(mkdirOp), new(mkdirOp), true}, + + {"ephemeral differs", + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, false}, + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + false}, + + {"perm differs", + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0701, true}, + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + false}, + + {"path differs", + &mkdirOp{Process, "/tmp/hakurei.1/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + false}, + + {"et differs", + &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + false}, + + {"equals", + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + true}, + }) + + checkOpMeta(t, []opMetaTestCase{ + {"ensure", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, false}, + User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", + `mode: -rwx------ type: ensure path: "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"`}, + + {"ephemeral", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true}, + User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", + `mode: -rwx------ type: user path: "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"`}, + }) }