diff --git a/Makefile b/Makefile index c7b16d8730..f9fae401dc 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,9 @@ NODE_HEADERS=$(METEOR_DEV_BUNDLE)/include/node WARNINGS=-Wall -Wextra -Wglobal-constructors -Wno-sign-compare -Wno-unused-parameter CXXFLAGS2=-std=c++1z $(WARNINGS) $(CXXFLAGS) -DSANDSTORM_BUILD=$(BUILD) -DKJ_HAS_OPENSSL -DKJ_HAS_ZLIB -DKJ_HAS_LIBDL -pthread -fPIC -I$(NODE_HEADERS) -DKJ_STD_COMPAT CFLAGS2=$(CFLAGS) -pthread -fPIC -DKJ_STD_COMPAT -LIBS2=$(LIBS) deps/libsodium/build/src/libsodium/.libs/libsodium.a deps/boringssl/build/ssl/libssl.a deps/boringssl/build/crypto/libcrypto.a -lz -ldl -pthread +# -lrt is not used by sandstorm itself, but the test app uses it. It would be +# nice if we could not link everything against it. +LIBS2=$(LIBS) deps/libsodium/build/src/libsodium/.libs/libsodium.a deps/boringssl/build/ssl/libssl.a deps/boringssl/build/crypto/libcrypto.a -lz -ldl -pthread -lrt define color printf '\033[0;34m==== $1 ====\033[0m\n' diff --git a/src/sandstorm/supervisor.c++ b/src/sandstorm/supervisor.c++ index dd9dffe888..331a6fb336 100644 --- a/src/sandstorm/supervisor.c++ +++ b/src/sandstorm/supervisor.c++ @@ -954,6 +954,12 @@ void SupervisorMain::makeCharDeviceNode( KJ_SYSCALL(mount(kj::str("/dev/", realName).cStr(), dst.cStr(), nullptr, MS_BIND, nullptr)); } +void mountTmpFs(const char *name, const char *dest) { + KJ_SYSCALL(mount(name, dest, "tmpfs", + MS_NOSUID | MS_NODEV, + "size=16m,nr_inodes=4k,mode=770")); +} + void SupervisorMain::setupFilesystem() { // The root of our mount namespace will be the app package itself. We optionally create // tmp, dev, and var. tmp is an ordinary tmpfs. dev is a read-only tmpfs that contains @@ -986,8 +992,7 @@ void SupervisorMain::setupFilesystem() { // 2) When we exit, the mount namespace disappears and the tmpfs is thus automatically // unmounted. No need for careful cleanup, and no need to implement a risky recursive // delete. - KJ_SYSCALL(mount("sandstorm-tmp", "tmp", "tmpfs", MS_NOSUID, - "size=16m,nr_inodes=4k,mode=770")); + mountTmpFs("sandstorm-tmp", "tmp"); } if (access("dev", F_OK) == 0) { KJ_SYSCALL(mount("sandstorm-dev", "dev", "tmpfs", @@ -997,6 +1002,18 @@ void SupervisorMain::setupFilesystem() { makeCharDeviceNode("zero", "zero", 1, 5); makeCharDeviceNode("random", "urandom", 1, 9); makeCharDeviceNode("urandom", "urandom", 1, 9); + + // Create /dev/shm so shm_open() and friends work. Note that even though /dev + // is already a tmpfs, we need to mount a separate tmpfs for /dev/shm, because + // the former will be read-only. + // + // TODO: it might be nice to have /dev/shm and /tmp share the same partition, + // so we don't have to strictly separate their storage capacity. We could mount + // a single tmpfs somewhere invisible, create subdirectories, and then bind-mount + // them to their final destinations. + mkdir("/dev/shm", 0700); + mountTmpFs("sandstorm-shm", "/dev/shm"); + KJ_SYSCALL(mount("dev", "dev", nullptr, MS_REMOUNT | MS_BIND | MS_NOEXEC | MS_NOSUID | MS_NODEV | MS_RDONLY, nullptr)); diff --git a/src/sandstorm/test-app/test-app.c++ b/src/sandstorm/test-app/test-app.c++ index 2942fe2b69..7ac6a62e21 100644 --- a/src/sandstorm/test-app/test-app.c++ +++ b/src/sandstorm/test-app/test-app.c++ @@ -23,6 +23,10 @@ #include #include +#include +#include +#include +#include #include #include @@ -93,6 +97,40 @@ private: // ======================================================================================= +void testSystemApi() { + // Test that some syscalls & platform APIs work as expected. Print a success + // message to the console so the test suite can verify this. + + std::cout << "Testing System APIs" << std::endl; + + auto result = kj::runCatchingExceptions([]() { + // Test use of /dev/shm: + const char *obj_name = "/shome-shm-obj"; + int shm_fd; + KJ_SYSCALL(shm_fd = shm_open(obj_name, O_RDWR|O_CREAT, 0700)); + KJ_DEFER(KJ_SYSCALL(shm_unlink(obj_name))); + + KJ_ASSERT(close(shm_fd) == 0, "Closing shm_fd failed"); + + // Make sure the mapping actually works: + int *mapped = (int *)mmap( + nullptr, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0 + ); + KJ_ASSERT(mapped != nullptr, "mmap() failed"); + KJ_SYSCALL(munmap(mapped, sizeof(int))); + }); + + KJ_IF_MAYBE(exception, result) { + auto msg = kj::str(*exception); + std::cout << msg.cStr() << std::endl; + throw(*exception); + } + + std::cout << "testSystemApi() passed." << std::endl; +} + +// ======================================================================================= + class WebSessionImpl final: public sandstorm::WebSession::Server { public: WebSessionImpl(sandstorm::UserInfo::Reader userInfo, @@ -148,6 +186,9 @@ public: httpResponse.setMimeType("text/plain"); httpResponse.getBody().setBytes(response.getText().asBytes()); }); + } else if(path == "test-system-api") { + testSystemApi(); + return kj::READY_NOW; } else if(path == "schedule") { context.getResults().initNoContent(); // Put the extra headers in a map, so we can easily look for specific ones: diff --git a/src/sandstorm/test-app/test-app.html b/src/sandstorm/test-app/test-app.html index a73f9ba9c8..674ecbe9e4 100644 --- a/src/sandstorm/test-app/test-app.html +++ b/src/sandstorm/test-app/test-app.html @@ -155,5 +155,7 @@

Test App

+ +

diff --git a/tests/tests/system-api.js b/tests/tests/system-api.js new file mode 100644 index 0000000000..3d5d01d3d6 --- /dev/null +++ b/tests/tests/system-api.js @@ -0,0 +1,47 @@ +// Sandstorm - Personal Cloud Sandbox +// Copyright (c) 2020 Sandstorm Development Group, Inc. and contributors +// All rights reserved. +// +// 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. + +"use strict"; + +const { short_wait } = require('../utils'); + +module.exports = {}; + +module.exports["Test system api"] = function(browser) { + const selector = "#do-test-system-api"; + browser + .init() + .loginDevAccount() + .uploadTestApp() + .assert.containsText("#grainTitle", "Untitled Sandstorm Test App instance") + // Start opening this now, so we don't have to wait for it later when we + // want to use it: + .click("#openDebugLog") + .grainFrame() + .waitForElementPresent(selector, short_wait) + .click(selector) + .pause(short_wait) + .windowHandles(windows => browser.switchWindow(windows.value[1])) + .waitForElementVisible(".grainlog-contents > pre", short_wait) + .assert.containsText(".grainlog-contents > pre", "testSystemApi() passed.") + + // Close the grain log, and switch back to to the main window, to avoid + // confusing future tests: + browser.windowHandles(windows => { + browser.closeWindow() + browser.switchWindow(windows.value[0]) + }) +}