More work on darlingserver

dyld now progresses to sigexc_setup (and then dies when calling the old LKM started_suspended trap).

It *seems* like Mach ports are being created properly in darlingserver, but given that there are no RPC calls that actually *use* the Mach ports yet, that's not certain yet.

Additionally, the microthreading code in darlingserver *appears* to be working, but no code that actually needs to suspend and resume has been tested yet.
This commit is contained in:
Ariel Abreu 2022-01-18 00:53:28 -05:00
parent 2bb784ed91
commit 81cada9b56
No known key found for this signature in database
GPG Key ID: D67AE16CCEA85B70
44 changed files with 488 additions and 3335 deletions

3
.gitmodules vendored
View File

@ -372,3 +372,6 @@
[submodule "src/external/pyobjc"]
path = src/external/pyobjc
url = ../darling-pyobjc.git
[submodule "src/external/darlingserver"]
path = src/external/darlingserver
url = ../darlingserver.git

View File

@ -68,7 +68,10 @@ function(mig defFileName)
-sheader ${CMAKE_CURRENT_BINARY_DIR}/${relativeName}${MIG_ARCH_SUFFIX}${MIG_SERVER_HEADER_SUFFIX}
-xtracemig ${CMAKE_CURRENT_BINARY_DIR}/${relativeName}${MIG_ARCH_SUFFIX}${MIG_XTRACE_SUFFIX}
${MIG_FLAGS}
${CMAKE_CURRENT_SOURCE_DIR}/${defFileName}
${CMAKE_CURRENT_SOURCE_DIR}/${defFileName} \;
# this is so that the xtrace file is always produced so that the command is not constantly re-run
# for MIG definitions that produce no xtrace files
touch ${CMAKE_CURRENT_BINARY_DIR}/${relativeName}${MIG_ARCH_SUFFIX}${MIG_XTRACE_SUFFIX}
DEPENDS
migexe migcom
)

View File

@ -56,7 +56,8 @@ add_subdirectory(external/cctools-port/cctools/misc)
#add_subdirectory(libdyld)
add_subdirectory(buildtools)
add_subdirectory(libelfloader/wrapgen)
add_subdirectory(darlingserver)
add_subdirectory(libsimple)
add_subdirectory(external/darlingserver)
add_subdirectory(startup)
include_directories(${CMAKE_SOURCE_DIR}/basic-headers)

View File

@ -1,60 +0,0 @@
project(darlingserver)
cmake_minimum_required(VERSION 3.10)
include_directories(
include
internal-include
${CMAKE_CURRENT_BINARY_DIR}/include
${CMAKE_CURRENT_BINARY_DIR}/internal-include
)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/include/darlingserver/rpc.h
${CMAKE_CURRENT_BINARY_DIR}/internal-include/darlingserver/rpc.internal.h
${CMAKE_CURRENT_BINARY_DIR}/src/rpc.c
COMMAND
${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate-rpc-wrappers.py
${CMAKE_CURRENT_BINARY_DIR}/include/darlingserver/rpc.h
${CMAKE_CURRENT_BINARY_DIR}/internal-include/darlingserver/rpc.internal.h
${CMAKE_CURRENT_BINARY_DIR}/src/rpc.c
\"../include/darlingserver/rpc.h\"
VERBATIM
MAIN_DEPENDENCY
${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate-rpc-wrappers.py
)
add_custom_target(generate_dserver_rpc_wrappers
ALL
DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/include/darlingserver/rpc.h
${CMAKE_CURRENT_BINARY_DIR}/internal-include/darlingserver/rpc.internal.h
${CMAKE_CURRENT_BINARY_DIR}/src/rpc.c
)
add_executable(darlingserver
src/darlingserver.cpp
src/server.cpp
src/message.cpp
src/call.cpp
src/registry.cpp
src/logging.cpp
src/process.cpp
src/thread.cpp
)
add_dependencies(darlingserver
generate_dserver_rpc_wrappers
)
target_compile_options(darlingserver PRIVATE -pthread -std=c++17)
target_link_options(darlingserver PRIVATE -pthread)
install(TARGETS darlingserver DESTINATION bin)
#file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/${DARLING_SDK_RELATIVE_PATH}/usr/include/darlingserver")
#create_symlink(
# "${DARLING_ROOT_RELATIVE_TO_SDK}/../../../src/darlingserver/include/darlingserver/rpc.h"
# "${CMAKE_BINARY_DIR}/${DARLING_SDK_RELATIVE_PATH}/usr/include/darlingserver/rpc.h"
#)

View File

@ -1,65 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DARLINGSERVER_CALL_HPP_
#define _DARLINGSERVER_CALL_HPP_
#include <darlingserver/rpc.h>
#include <darlingserver/rpc.internal.h>
#include <darlingserver/message.hpp>
#include <darlingserver/registry.hpp>
#include <memory>
namespace DarlingServer {
class CallWithReply;
class Call {
public:
enum class Number {
Invalid = dserver_callnum_invalid,
DSERVER_ENUM_VALUES
};
protected:
std::weak_ptr<Thread> _thread;
MessageQueue& _replyQueue;
Address _replyAddress;
void _stopPending();
public:
Call(MessageQueue& replyQueue, std::shared_ptr<Thread> thread, Address replyAddress);
virtual ~Call();
static std::shared_ptr<Call> callFromMessage(Message&& requestMessage, MessageQueue& replyQueue);
virtual Number number() const = 0;
std::shared_ptr<Thread> thread() const;
virtual void processCall() = 0;
DSERVER_CLASS_DECLS;
};
DSERVER_CLASS_DEFS;
};
#endif // _DARLINGSERVER_CALL_HPP_

View File

@ -1,88 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DARLINGSERVER_LOGGING_HPP_
#define _DARLINGSERVER_LOGGING_HPP_
#include <fstream>
#include <string>
#include <sstream>
namespace DarlingServer {
static struct EndLog {} endLog;
class Log {
private:
std::string _category;
enum class Type {
Debug,
Info,
Warning,
Error,
};
void _log(Type type, std::string message);
static std::string _typeToString(Type type);
public:
Log(std::string category);
Log(const Log&) = delete;
Log& operator=(const Log&) = delete;
Log(Log&&) = delete;
Log& operator=(Log&&) = delete;
class Stream;
friend class Stream;
Stream debug();
Stream info();
Stream warning();
Stream error();
};
class Log::Stream {
friend class Log;
private:
Type _type;
Log& _log;
std::ostringstream _buffer;
Stream(Type type, Log& log);
public:
Stream(const Stream&) = delete;
Stream& operator=(const Stream&) = delete;
Stream(Stream&&) = delete;
Stream& operator=(Stream&&) = delete;
template<class T>
Stream& operator<<(T value) {
_buffer << value;
return *this;
};
template<>
Stream& operator<<<EndLog>(EndLog value);
};
};
#endif // _DARLINGSERVER_LOGGING_HPP_

View File

@ -1,174 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DARLINGSERVER_MESSAGE_HPP_
#define _DARLINGSERVER_MESSAGE_HPP_
#include <sys/socket.h>
#include <sys/un.h>
#include <vector>
#include <cstdint>
#include <deque>
#include <mutex>
#include <optional>
#include <functional>
namespace DarlingServer {
class Address {
private:
struct sockaddr_un _address;
size_t _size;
public:
Address();
Address(const struct sockaddr_un& rawAddress, size_t addressSize = sizeof(struct sockaddr_un));
struct sockaddr_un& raw();
const struct sockaddr_un& raw() const;
size_t rawSize() const;
void setRawSize(size_t newRawSize);
};
/**
* A C++ wrapper for local (Unix) SOCK_DGRAM/SOCK_SEQPACKET socket messages, with support for file descriptors (SCM_RIGHTS) and credentials (SCM_CREDENTIALS).
*/
class Message {
private:
struct msghdr _header;
struct iovec _dataDescriptor;
std::vector<uint8_t> _buffer;
struct cmsghdr* _controlHeader = nullptr;
Address _socketAddress;
void _initWithOther(Message&&);
void _cleanupSelf();
struct cmsghdr* _credentialsHeader();
const struct cmsghdr* _credentialsHeader() const;
struct cmsghdr* _descriptorHeader();
const struct cmsghdr* _descriptorHeader() const;
size_t _descriptorSpace() const;
void _ensureCredentialsHeader();
void _ensureDescriptorHeader(size_t descriptorSpace);
public:
/**
* Default-initializes a Message.
*
* By default, the data buffer holds 256 bytes and the control data buffer has space for 4 descriptors.
*
* If this Message is being used to receive a message from a socket, you must preallocate
* the buffers for the recvmsg/recvmmsg call. The buffers are already preallocated to the
* sizes specified by in this constructor, however, if you wish to grow the data buffer,
* you can call pushData() with a null buffer pointer and a non-zero size. If you wish to
* grow the control data buffer, you can call pushDescriptor() with a descriptor value of `-1`.
*/
Message(size_t bufferSpace = 256, size_t descriptorSpace = 4);
~Message();
Message(Message&&);
Message& operator=(Message&&);
Message(const Message&) = delete;
Message& operator=(const Message&) = delete;
struct msghdr& rawHeader();
const struct msghdr& rawHeader() const;
std::vector<uint8_t>& data();
const std::vector<uint8_t>& data() const;
Address address() const;
void setAddress(Address address);
bool copyCredentialsOut(struct ucred& outputCredentials) const;
void copyCredentialsIn(const struct ucred& inputCredentials);
pid_t pid() const;
void setPID(pid_t pid);
uid_t uid() const;
void setUID(uid_t uid);
gid_t gid() const;
void setGID(gid_t gid);
/**
* Returns an array of all the descritors currently owned by this Message.
*
* Note that the descriptors are still owned by this Message during and after this call.
* Callers should NOT close the descriptors they receive in the returned vector.
*
* See extractDescriptor() for a method that will transfer ownership of a descriptor to the caller.
*/
std::vector<int> descriptors() const;
/**
* Acquires ownership of the given descriptor.
*
* Note that callers should NOT close the descriptor after a call to this method.
* This Message becomes the owner of the descriptor and will take care of closing it.
*/
void pushDescriptor(int descriptor);
/**
* Extracts a single descriptor from this Message, transferring ownership of it to the caller.
*
* @returns The descriptor that was extracted, or `-1` if it was not found.
*/
int extractDescriptor(int descriptor);
/**
* Like extractDescriptor(), but extracts it based on its index in the vector returned by descriptors().
*/
int extractDescriptorAtIndex(size_t index);
/**
* Gives up ownership of the descriptors previously held by this Message and acquires ownership
* of the descriptors passed in the given vector.
*
* Note that this method does not return the old descriptors in any way,
* so if the caller wishes to know what they were, they must call descriptors() before calling this method.
*/
void replaceDescriptors(const std::vector<int>& newDescriptors);
};
/**
* A thread-safe Message queue with methods for sending and receiving messages.
*/
class MessageQueue {
private:
std::deque<Message> _messages;
std::mutex _lock;
std::function<void()> _messageArrivalNotificationCallback = nullptr;
public:
void setMessageArrivalNotificationCallback(std::function<void()> messageArrivalNotificationCallback);
void push(Message&& message);
std::optional<Message> pop();
bool sendMany(int socket);
bool receiveMany(int socket);
};
};
#endif // _DARLINGSERVER_MESSAGE_HPP_

View File

@ -1,76 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DARLINGSERVER_PROCESS_HPP_
#define _DARLINGSERVER_PROCESS_HPP_
#include <sys/types.h>
#include <memory>
#include <vector>
#include <mutex>
#include <shared_mutex>
namespace DarlingServer {
class Thread;
class Server;
class Process {
friend class Thread;
friend class Server;
private:
pid_t _pid;
pid_t _nspid;
int _pidfd;
mutable std::shared_mutex _rwlock;
std::vector<std::weak_ptr<Thread>> _threads;
std::string _vchrootPath;
void _unregisterThreads();
public:
using ID = pid_t;
using NSID = ID;
Process(ID id, NSID nsid);
~Process();
Process(const Process&) = delete;
Process& operator=(const Process&) = delete;
Process(Process&&) = delete;
Process& operator=(Process&&) = delete;
/**
* The PID of this Process as seen from darlingserver's namespace.
*/
ID id() const;
/**
* The PID of this Process as seen from within the container (i.e. launchd's namespace).
*/
NSID nsid() const;
std::vector<std::shared_ptr<Thread>> threads() const;
std::string vchrootPath() const;
void setVchrootPath(std::string path);
};
};
#endif // _DARLINGSERVER_PROCESS_HPP_

View File

@ -1,171 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DARLINGSERVER_REGISTRY_HPP_
#define _DARLINGSERVER_REGISTRY_HPP_
#include <vector>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <shared_mutex>
#include <optional>
#include <functional>
#include <sys/types.h>
#include <darlingserver/message.hpp>
#include <darlingserver/process.hpp>
#include <darlingserver/thread.hpp>
namespace DarlingServer {
template<class Entry>
class Registry {
private:
std::unordered_map<typename Entry::ID, std::shared_ptr<Entry>> _map;
std::unordered_map<typename Entry::NSID, std::shared_ptr<Entry>> _nsmap;
std::shared_mutex _rwlock;
public:
using ID = typename Entry::ID;
using NSID = typename Entry::NSID;
std::shared_ptr<Entry> registerIfAbsent(NSID nsid, std::function<std::shared_ptr<Entry>()> entryFactory) {
std::unique_lock lock(_rwlock);
auto it2 = _nsmap.find(nsid);
if (it2 != _nsmap.end()) {
return (*it2).second;
}
auto entry = entryFactory();
_map[entry->id()] = entry;
_nsmap[entry->nsid()] = entry;
return entry;
};
bool registerEntry(std::shared_ptr<Entry> entry, bool replace = false) {
std::unique_lock lock(_rwlock);
if (!replace && (_map.find(entry->id()) != _map.end() || _nsmap.find(entry->nsid()) != _nsmap.end())) {
return false;
}
_map[entry->id()] = entry;
_nsmap[entry->nsid()] = entry;
return true;
};
bool unregisterEntryByID(ID id) {
std::unique_lock lock(_rwlock);
auto it = _map.find(id);
if (it == _map.end()) {
return false;
}
std::shared_ptr<Entry> entry = (*it).second;
auto it2 = _nsmap.find(entry->nsid());
if (it2 == _nsmap.end()) {
return false;
}
_map.erase(it);
_nsmap.erase(it2);
return true;
};
bool unregisterEntryByNSID(NSID nsid) {
std::unique_lock lock(_rwlock);
auto it2 = _nsmap.find(nsid);
if (it2 == _nsmap.end()) {
return false;
}
std::shared_ptr<Entry> entry = (*it2).second;
auto it = _map.find(entry->id());
if (it == _map.end()) {
return false;
}
_map.erase(it);
_nsmap.erase(it2);
return true;
};
/**
* Unregisters the given entry from this Registry.
*
* This is the recommended method for unregistering entries as it will
* actually compare pointers to ensure the entry being unregistered is
* the same as the one currently registered with the same IDs.
*/
bool unregisterEntry(std::shared_ptr<Entry> entry) {
std::unique_lock lock(_rwlock);
auto it = _map.find(entry->id());
auto it2 = _nsmap.find(entry->nsid());
if (it == _map.end() || it2 == _nsmap.end()) {
return false;
}
// note that we *want* pointer-to-pointer comparison
if ((*it).second != entry || (*it2).second != entry) {
return false;
}
_map.erase(it);
_nsmap.erase(it2);
return true;
};
std::optional<std::shared_ptr<Entry>> lookupEntryByID(ID id) {
std::shared_lock lock(_rwlock);
auto it = _map.find(id);
if (it == _map.end()) {
return std::nullopt;
}
return *it;
};
std::optional<std::shared_ptr<Entry>> lookupEntryByNSID(ID nsid) {
std::shared_lock lock(_rwlock);
auto it2 = _nsmap.find(nsid);
if (it2 == _nsmap.end()) {
return std::nullopt;
}
return *it2;
};
};
Registry<Process>& processRegistry();
Registry<Thread>& threadRegistry();
};
#endif // _DARLINGSERVER_REGISTRY_HPP_

View File

@ -1,68 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DARLINGSERVER_SERVER_HPP_
#define _DARLINGSERVER_SERVER_HPP_
#include <string>
#include <sys/epoll.h>
#include <thread>
#include <darlingserver/message.hpp>
#include <darlingserver/workers.hpp>
#include <darlingserver/call.hpp>
#include <darlingserver/registry.hpp>
namespace DarlingServer {
// NOTE: server instances MUST be created with `new` rather than as a normal local/stack variable
class Server {
private:
int _listenerSocket;
std::string _prefix;
std::string _socketPath;
int _epollFD;
MessageQueue _inbox;
MessageQueue _outbox;
WorkQueue<Message> _workQueue;
bool _canRead = false;
bool _canWrite = true;
int _wakeupFD;
void _worker(DarlingServer::Message message);
public:
Server(std::string prefix);
~Server();
Server(const Server&) = delete;
Server& operator=(const Server&) = delete;
Server(Server&&) = delete;
Server& operator=(Server&&) = delete;
void start();
void monitorProcess(std::shared_ptr<Process> process);
std::string prefix() const;
static Server& sharedInstance();
};
};
#endif // _DARLINGSERVER_SERVER_HPP_

View File

@ -1,79 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DARLINGSERVER_THREAD_HPP_
#define _DARLINGSERVER_THREAD_HPP_
#include <memory>
#include <sys/types.h>
#include <mutex>
#include <shared_mutex>
#include <darlingserver/message.hpp>
namespace DarlingServer {
class Process;
class Call;
class Thread: public std::enable_shared_from_this<Thread> {
friend class Process;
private:
pid_t _tid;
pid_t _nstid;
std::weak_ptr<Process> _process;
std::shared_ptr<Call> _pendingCall;
Address _address;
mutable std::shared_mutex _rwlock;
public:
using ID = pid_t;
using NSID = ID;
Thread(std::shared_ptr<Process> process, NSID nsid);
~Thread() noexcept(false);
void registerWithProcess();
Thread(const Thread&) = delete;
Thread& operator=(const Thread&) = delete;
Thread(Thread&&) = delete;
Thread& operator=(Thread&&) = delete;
std::shared_ptr<Process> process() const;
std::shared_ptr<Call> pendingCall() const;
void setPendingCall(std::shared_ptr<Call> newPendingCall);
/**
* The TID of this Thread as seen from darlingserver's namespace.
*/
ID id() const;
/**
* The TID of this Thread as seen from within the container (i.e. launchd's namespace).
*/
NSID nsid() const;
Address address() const;
void setAddress(Address address);
};
};
#endif // _DARLINGSERVER_THREAD_HPP_

View File

@ -1,176 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DARLINGSERVER_WORKERS_HPP_
#define _DARLINGSERVER_WORKERS_HPP_
#include <queue>
#include <functional>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#include <chrono>
#include <optional>
namespace DarlingServer {
template<class WorkItem>
class WorkQueue {
private:
std::mutex _queueMutex;
std::queue<WorkItem> _workItems;
std::function<void(WorkItem)> _workerFunction;
std::unordered_map<std::thread::id, std::thread> _workerThreads;
size_t _threadsAvailable = 0;
std::condition_variable _cv;
bool _dying = false;
static constexpr unsigned int minimumThreads() {
return 2;
};
static unsigned int maximumThreads() {
unsigned int hw = std::thread::hardware_concurrency();
if (hw > 0 && hw - 1 > minimumThreads()) {
// minus one for the main thread
return hw - 1;
} else {
return minimumThreads();
}
};
static constexpr std::chrono::seconds temporaryWorkerWaitPeriod() {
return std::chrono::seconds(15);
};
void worker(bool permanent) {
std::optional<WorkItem> workItem = std::nullopt;
std::unique_lock lock(_queueMutex, std::defer_lock);
while (true) {
// acquire the lock for the condition variable
lock.lock();
// we're going to wait for work, so we're available
++_threadsAvailable;
bool waitResult;
auto predicate = [&, this]() {
// if we're dying, stop waiting
if (_dying) {
--_threadsAvailable;
return true;
}
// if we have a work item ready, take it and stop waiting
if (_workItems.size() > 0) {
workItem = std::move(_workItems.front());
_workItems.pop();
--_threadsAvailable;
return true;
}
// otherwise, let's keep waiting
return false;
};
if (permanent) {
// permanent workers just wait until there's work available (or the queue is being destroyed)
_cv.wait(lock, predicate);
waitResult = true;
} else {
// temporary workers wait for a certain period of time for work; if there's no work available after that period, they die
waitResult = _cv.wait_for(lock, temporaryWorkerWaitPeriod(), predicate);
}
// if the queue is being destroyed, let the thread die
if (_dying) {
return;
}
// if we failed to find any work items, let's die
if (!waitResult) {
auto thisThread = std::move(_workerThreads[std::this_thread::get_id()]);
_workerThreads.erase(thisThread.get_id());
thisThread.detach();
return;
}
// drop the lock to perform the work
lock.unlock();
// perform the work
_workerFunction(std::move(workItem.value()));
workItem = std::nullopt;
}
};
public:
WorkQueue(std::function<void(WorkItem)> workerFunction):
_workerFunction(workerFunction)
{
std::scoped_lock lock(_queueMutex);
// start the permanent worker threads
for (size_t i = 0; i < minimumThreads(); ++i) {
std::thread worker = std::thread(&WorkQueue::worker, this, true);
_workerThreads[worker.get_id()] = std::move(worker);
}
};
~WorkQueue() {
// tell all the threads that we're being destroyed
std::unique_lock lock(_queueMutex);
_dying = true;
lock.unlock();
_cv.notify_all();
// now wait for all of the threads to die
for (auto& [id, thread]: _workerThreads) {
thread.join();
}
};
WorkQueue(const WorkQueue&) = delete;
WorkQueue& operator=(const WorkQueue&) = delete;
WorkQueue(WorkQueue&&) = delete;
WorkQueue& operator=(WorkQueue&&) = delete;
void push(WorkItem workItem) {
std::unique_lock lock(_queueMutex);
_workItems.push(std::move(workItem));
// if there are no threads available to perform the work AND we have less than the
// maximum number of worker threads currently active, spawn a temporary worker thread.
if (_threadsAvailable == 0 && _workerThreads.size() < maximumThreads()) {
std::thread worker = std::thread(&WorkQueue::worker, this, false);
_workerThreads[worker.get_id()] = std::move(worker);
// note that we don't need to notify anyone in this case, since we want the new worker thread
// to take care of the new work item (it's not a problem if another thread gets to it first, though).
} else {
// otherwise, notify one of the available threads that there's work available
lock.unlock();
_cv.notify_one();
}
};
};
};
#endif // _DARLINGSERVER_WORKERS_HPP_

View File

@ -1,563 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from collections import OrderedDict
import textwrap
from datetime import datetime
# NOTE: in Python 3.7+, we can rely on dictionaries having their items in insertion order.
# unfortunately, we can't expect everyone building Darling to have Python 3.7+ installed.
calls = [
('checkin', [], []),
('checkout', [], []),
('vchroot_path', [
('buffer', 'char*'),
('buffer_size', 'size_t'),
], [
('length', 'size_t'),
]),
]
if len(sys.argv) < 5:
sys.exit("Usage: " + sys.argv[0] + " <public-header-path> <internal-header-path> <library-source-path> <library-import>")
os.makedirs(os.path.dirname(sys.argv[1]), exist_ok=True)
os.makedirs(os.path.dirname(sys.argv[2]), exist_ok=True)
os.makedirs(os.path.dirname(sys.argv[3]), exist_ok=True)
def to_camel_case(snake_str):
components = snake_str.split('_')
return ''.join(x.title() for x in components)
public_header = open(sys.argv[1], "w")
internal_header = open(sys.argv[2], "w")
library_source = open(sys.argv[3], "w")
library_import = sys.argv[4]
license_header = """\
// This file has been auto-generated by generate-rpc-wrappers.py for use with darlingserver
/**
* This file is part of Darling.
*
* Copyright (C) {} Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
""".format(datetime.now().year)
public_header.write(license_header)
library_source.write(license_header)
internal_header.write(license_header)
public_header.write("""\
#ifndef _DARLINGSERVER_API_H_
#define _DARLINGSERVER_API_H_
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
""")
public_header.write("enum dserver_callnum {\n")
public_header.write("\tdserver_callnum_invalid,\n")
for call in calls:
call_name = call[0]
call_parameters = call[1]
reply_parameters = call[2]
public_header.write("\tdserver_callnum_" + call_name + ",\n")
public_header.write("};\n")
public_header.write("""\
typedef enum dserver_callnum dserver_callnum_t;
typedef struct dserver_rpc_callhdr {
pid_t pid;
pid_t tid;
dserver_callnum_t number;
} dserver_rpc_callhdr_t;
typedef struct dserver_rpc_replyhdr {
dserver_callnum_t number;
int code;
} dserver_rpc_replyhdr_t;
""")
library_source.write("""\
#include {}
#if !defined(dserver_rpc_hooks_msghdr_t) || !defined(dserver_rpc_hooks_iovec_t) || !defined(dserver_rpc_hooks_cmsghdr_t) || !defined(DSERVER_RPC_HOOKS_CMSG_SPACE) || !defined(DSERVER_RPC_HOOKS_CMSG_FIRSTHDR) || !defined(DSERVER_RPC_HOOKS_SOL_SOCKET) || !defined(DSERVER_RPC_HOOKS_SCM_RIGHTS) || !defined(DSERVER_RPC_HOOKS_CMSG_LEN) || !defined(DSERVER_RPC_HOOKS_CMSG_DATA) || !defined(DSERVER_RPC_HOOKS_ATTRIBUTE)
#error Missing definitions
#endif
#ifndef dserver_rpc_hooks_get_pid
DSERVER_RPC_HOOKS_ATTRIBUTE pid_t dserver_rpc_hooks_get_pid(void);
#endif
#ifndef dserver_rpc_hooks_get_tid
DSERVER_RPC_HOOKS_ATTRIBUTE pid_t dserver_rpc_hooks_get_tid(void);
#endif
#ifndef dserver_rpc_hooks_get_server_address
DSERVER_RPC_HOOKS_ATTRIBUTE void* dserver_rpc_hooks_get_server_address(void);
#endif
#ifndef dserver_rpc_hooks_get_server_address_length
DSERVER_RPC_HOOKS_ATTRIBUTE size_t dserver_rpc_hooks_get_server_address_length(void);
#endif
#ifndef dserver_rpc_hooks_memcpy
DSERVER_RPC_HOOKS_ATTRIBUTE void* dserver_rpc_hooks_memcpy(void* destination, const void* source, size_t length);
#endif
#ifndef dserver_rpc_hooks_send_message
DSERVER_RPC_HOOKS_ATTRIBUTE long int dserver_rpc_hooks_send_message(int socket, const dserver_rpc_hooks_msghdr_t* message);
#endif
#ifndef dserver_rpc_hooks_receive_message
DSERVER_RPC_HOOKS_ATTRIBUTE long int dserver_rpc_hooks_receive_message(int socket, dserver_rpc_hooks_msghdr_t* out_message);
#endif
#ifndef dserver_rpc_hooks_get_bad_message_status
DSERVER_RPC_HOOKS_ATTRIBUTE int dserver_rpc_hooks_get_bad_message_status(void);
#endif
#ifndef dserver_rpc_hooks_get_communication_error_status
DSERVER_RPC_HOOKS_ATTRIBUTE int dserver_rpc_hooks_get_communication_error_status(void);
#endif
#ifndef dserver_rpc_hooks_get_broken_pipe_status
DSERVER_RPC_HOOKS_ATTRIBUTE int dserver_rpc_hooks_get_broken_pipe_status(void);
#endif
#ifndef dserver_rpc_hooks_close_fd
DSERVER_RPC_HOOKS_ATTRIBUTE void dserver_rpc_hooks_close_fd(int fd);
#endif
#ifndef dserver_rpc_hooks_get_socket
DSERVER_RPC_HOOKS_ATTRIBUTE int dserver_rpc_hooks_get_socket(void);
#endif
""".format(library_import))
internal_header.write("#define DSERVER_VALID_CALLNUM_CASES \\\n")
for call in calls:
call_name = call[0]
call_parameters = call[1]
reply_parameters = call[2]
internal_header.write("\tcase dserver_callnum_" + call_name + ": \\\n")
internal_header.write("\n")
internal_header.write("#define DSERVER_CONSTRUCT_CASES \\\n")
for call in calls:
call_name = call[0]
call_parameters = call[1]
reply_parameters = call[2]
camel_name = to_camel_case(call_name)
internal_header.write("\tCALL_CASE(" + call_name + ", " + camel_name + "); \\\n")
internal_header.write("\n")
internal_header.write("#define DSERVER_ENUM_VALUES \\\n")
for call in calls:
call_name = call[0]
call_parameters = call[1]
reply_parameters = call[2]
camel_name = to_camel_case(call_name)
internal_header.write("\t" + camel_name + " = dserver_callnum_" + call_name + ", \\\n")
internal_header.write("\n")
internal_header.write("#define DSERVER_CLASS_DECLS \\\n")
for call in calls:
call_name = call[0]
call_parameters = call[1]
reply_parameters = call[2]
camel_name = to_camel_case(call_name)
internal_header.write("\tclass " + camel_name + "; \\\n")
internal_header.write("\n")
internal_header.write("#define DSERVER_CLASS_DEFS \\\n")
for call in calls:
call_name = call[0]
call_parameters = call[1]
reply_parameters = call[2]
camel_name = to_camel_case(call_name)
fd_count_in_reply = 0
internal_header.write(textwrap.indent(textwrap.dedent("""\
class Call::{1}: public Call {{ \\
friend class Call; \\
private: \\
{2}
public: \\
{1}(MessageQueue& replyQueue, std::shared_ptr<Thread> thread, dserver_rpc_call_{0}_t* data, Message&& requestMessage): \\
Call(replyQueue, thread, requestMessage.address()){3} \\
{4}
{{ \\
"""), '\t').format(
call_name,
camel_name,
("dserver_call_" + call_name + "_t _body; \\") if len(call_parameters) > 0 else "\\",
"," if len(call_parameters) > 0 else "",
"_body(data->body) \\" if len(call_parameters) > 0 else "\\"
)
)
for param in call_parameters:
param_name = param[0]
param_type = param[1]
if param_type != "@fd":
continue
param_type = "int"
internal_header.write("\t\t\t_body." + param_name + " = requestMessage.extractDescriptorAtIndex(_body." + param_name + "); \\\n")
internal_header.write("\t\t}; \\\n")
internal_header.write("\t\t~" + camel_name + "() { \\\n")
for param in call_parameters:
param_name = param[0]
param_type = param[1]
if param_type != "@fd":
continue
param_type = "int"
internal_header.write("\t\t\tif (_body." + param_name + " != -1) { \\\n")
internal_header.write("\t\t\t\tclose(_body." + param_name + "); \\\n")
internal_header.write("\t\t} \\\n")
internal_header.write(textwrap.indent(textwrap.dedent("""\
}}; \\
virtual Call::Number number() const {{ \\
return Call::Number::{0}; \\
}}; \\
virtual void processCall(); \\
private: \\
"""), '\t').format(camel_name))
internal_header.write("\t\tvoid _sendReply(int resultCode")
for param in reply_parameters:
param_name = param[0]
param_type = param[1]
if param_type == "@fd":
param_type = "int"
fd_count_in_reply += 1
internal_header.write(", " + param_type + " " + param_name)
internal_header.write(") { \\\n")
internal_header.write(textwrap.indent(textwrap.dedent("""\
Message reply(sizeof(dserver_rpc_reply_{0}_t), {1}); \\
reply.setAddress(_replyAddress); \\
auto replyStruct = reinterpret_cast<dserver_rpc_reply_{0}_t*>(reply.data().data()); \\
replyStruct->header.number = dserver_callnum_{0}; \\
replyStruct->header.code = resultCode; \\
"""), '\t\t\t').format(call_name, fd_count_in_reply))
fd_index = 0
for param in reply_parameters:
param_name = param[0]
param_type = param[1]
val = param_name
if param_type == "@fd":
param_type = "int"
val = str(fd_index)
internal_header.write("\t\t\treply.pushDescriptor(" + param_name + "); \\\n")
internal_header.write("\t\t\treplyStruct->body." + param_name + " = " + val + "; \\\n")
internal_header.write("\t\t\t_replyQueue.push(std::move(reply)); \\\n")
internal_header.write("\t\t}; \\\n")
internal_header.write("\t}; \\\n")
internal_header.write("\n")
for call in calls:
call_name = call[0]
call_parameters = call[1]
reply_parameters = call[2]
fd_count_in_call = 0
fd_count_in_reply = 0
# define the RPC call body structure
if len(call_parameters) > 0:
public_header.write("typedef struct dserver_call_" + call_name + " dserver_call_" + call_name + "_t;\n")
public_header.write("struct dserver_call_" + call_name + " {\n")
for param in call_parameters:
param_name = param[0]
param_type = param[1]
if param_type == "@fd":
param_type = "int"
fd_count_in_call += 1
public_header.write("\t" + param_type + " " + param_name + ";\n")
public_header.write("};\n")
# define the RPC call structure
public_header.write(textwrap.dedent("""\
typedef struct dserver_rpc_call_{0} dserver_rpc_call_{0}_t;
struct dserver_rpc_call_{0} {{
dserver_rpc_callhdr_t header;
""").format(call_name))
if len(call_parameters) > 0:
public_header.write("\tdserver_call_" + call_name + "_t body;\n")
public_header.write("};\n")
# define the RPC reply body structure
if len(reply_parameters) > 0:
public_header.write("typedef struct dserver_reply_" + call_name + " dserver_reply_" + call_name + "_t;\n")
public_header.write("struct dserver_reply_" + call_name + " {\n")
for param in reply_parameters:
param_name = param[0]
param_type = param[1]
if param_type == "@fd":
param_type = "int"
fd_count_in_reply += 1
public_header.write("\t" + param_type + " " + param_name + ";\n")
public_header.write("};\n")
# define the RPC reply structure
public_header.write(textwrap.dedent("""\
typedef struct dserver_rpc_reply_{0} dserver_rpc_reply_{0}_t;
struct dserver_rpc_reply_{0} {{
dserver_rpc_replyhdr_t header;
""").format(call_name))
if len(reply_parameters) > 0:
public_header.write("\tdserver_reply_" + call_name + "_t body;\n")
public_header.write("};\n")
# declare the RPC call wrapper function
# (and output the prototype part of the function definition)
tmp = "int dserver_rpc_" + call_name + "("
public_header.write(tmp)
library_source.write(tmp)
is_first = True
for param in call_parameters:
param_name = param[0]
param_type = param[1]
if param_type == "@fd":
param_type = "int"
if is_first:
is_first = False
tmp = ""
else:
tmp = ", "
tmp += param_type + " " + param_name
public_header.write(tmp)
library_source.write(tmp)
for param in reply_parameters:
param_name = param[0]
param_type = param[1]
if param_type == "@fd":
param_type = "int"
if is_first:
is_first = False
tmp = ""
else:
tmp = ", "
tmp += param_type + "* out_" + param_name
public_header.write(tmp)
library_source.write(tmp)
public_header.write(");\n\n")
library_source.write(") {\n")
# define the RPC call wrapper function
library_source.write(textwrap.indent(textwrap.dedent("""\
int status = 0;
dserver_rpc_call_{0}_t call = {{
.header = {{
.pid = dserver_rpc_hooks_get_pid(),
.tid = dserver_rpc_hooks_get_tid(),
.number = dserver_callnum_{0},
}},
"""), '\t').format(call_name))
if len(call_parameters) > 0:
library_source.write("\t\t.body = {\n")
fd_index = 0
for param in call_parameters:
param_name = param[0]
param_type = param[1]
val = param_name
if param_type == "@fd":
param_type = "int"
val = "(" + param_name + " < 0) ? -1 : " + str(fd_index)
fd_index += 1
library_source.write("\t\t\t." + param_name + " = " + val + ",\n")
library_source.write("\t\t},\n")
library_source.write("\t};\n")
library_source.write("\tdserver_rpc_reply_" + call_name + "_t reply;\n")
if fd_count_in_call > 0 or fd_count_in_reply > 0:
library_source.write("\tint fds[" + max(fd_count_in_call, fd_count_in_reply) + "]")
if fd_count_in_call > 0:
library_source.write(" = { ")
is_first = True
for param in call_parameters:
param_name = param[0]
param_type = param[1]
if param_type != "@fd":
continue
if is_first:
is_first = False
else:
library_source.write(", ")
library_source.write(param_name)
library_source.write(" }")
library_source.write(";\n")
library_source.write("\tchar controlbuf[DSERVER_RPC_HOOKS_CMSG_SPACE(sizeof(fds))];\n")
library_source.write(textwrap.indent(textwrap.dedent("""\
dserver_rpc_hooks_iovec_t call_data = {
.iov_base = &call,
.iov_len = sizeof(call),
};
dserver_rpc_hooks_msghdr_t callmsg = {
.msg_name = dserver_rpc_hooks_get_server_address(),
.msg_namelen = dserver_rpc_hooks_get_server_address_length(),
.msg_iov = &call_data,
.msg_iovlen = 1,
"""), '\t'))
if fd_count_in_call == 0:
library_source.write("\t\t.msg_control = NULL,\n")
library_source.write("\t\t.msg_controllen = 0,\n")
else:
library_source.write("\t\t.msg_control = controlbuf,\n")
library_source.write("\t\t.msg_controllen = sizeof(controlbuf),\n")
library_source.write("\t};\n")
if fd_count_in_call > 0:
library_source.write(textwrap.indent(textwrap.dedent("""\
dserver_rpc_hooks_cmsghdr_t* call_cmsg = DSERVER_RPC_HOOKS_CMSG_FIRSTHDR(&callmsg);
call_cmsg->cmsg_level = DSERVER_RPC_HOOKS_SOL_SOCKET;
call_cmsg->cmsg_type = DSERVER_RPC_HOOKS_SCM_RIGHTS;
call_cmsg->cmsg_len = DSERVER_RPC_HOOKS_CMSG_LEN(sizeof(int) * {0});
dserver_rpc_hooks_memcpy(DSERVER_RPC_HOOKS_CMSG_DATA(call_cmsg), fds, sizeof(int) * {0});
"""), '\t').format(fd_count_in_call))
library_source.write(textwrap.indent(textwrap.dedent("""\
dserver_rpc_hooks_iovec_t reply_data = {
.iov_base = &reply,
.iov_len = sizeof(reply),
};
dserver_rpc_hooks_msghdr_t replymsg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = &reply_data,
.msg_iovlen = 1,
"""), '\t'))
if fd_count_in_reply == 0:
library_source.write("\t\t.msg_control = NULL,\n")
library_source.write("\t\t.msg_controllen = 0,\n")
else:
library_source.write("\t\t.msg_control = controlbuf,\n")
library_source.write("\t\t.msg_controllen = sizeof(controlbuf),\n")
library_source.write("\t};\n\n")
library_source.write("\tint socket = dserver_rpc_hooks_get_socket();\n")
library_source.write("\tif (socket < 0) {")
library_source.write("\t\treturn dserver_rpc_hooks_get_broken_pipe_status();\n")
library_source.write("\t}\n\n")
library_source.write("\tlong int long_status = dserver_rpc_hooks_send_message(socket, &callmsg);\n")
library_source.write("\tif (long_status < 0) {\n")
library_source.write("\t\treturn (int)long_status;\n")
library_source.write("\t}\n\n")
library_source.write("\tif (long_status != sizeof(call)) {\n")
library_source.write("\t\treturn dserver_rpc_hooks_get_communication_error_status();\n")
library_source.write("\t}\n\n")
library_source.write("\tlong_status = dserver_rpc_hooks_receive_message(socket, &replymsg);\n")
library_source.write("\tif (long_status < 0) {\n")
library_source.write("\t\treturn (int)long_status;\n")
library_source.write("\t}\n\n")
library_source.write("\tif (long_status != sizeof(reply)) {\n")
library_source.write("\t\treturn dserver_rpc_hooks_get_communication_error_status();\n")
library_source.write("\t}\n\n")
if fd_count_in_reply != 0:
library_source.write(textwrap.indent(textwrap.dedent("""\
dserver_rpc_hooks_cmsghdr_t* reply_cmsg = DSERVER_RPC_HOOKS_CMSG_FIRSTHDR(&replymsg);
if (!reply_cmsg || reply_cmsg->cmsg_level != DSERVER_RPC_HOOKS_SOL_SOCKET || reply_cmsg->cmsg_type != DSERVER_RPC_HOOKS_SCM_RIGHTS || reply_cmsg->cmsg_len != DSERVER_RPC_HOOKS_CMSG_LEN(sizeof(int) * {0})) {{
status = dserver_rpc_hooks_get_bad_message_status();
return status;
}}
dserver_rpc_hooks_memcpy(fds, DSERVER_RPC_HOOKS_CMSG_DATA(reply_cmsg), sizeof(int) * {0});
"""), '\t').format(fd_count_in_reply))
for param in reply_parameters:
param_name = param[0]
param_type = param[1]
if param_type == "@fd":
param_type = "int"
library_source.write("\tif (out_" + param_name + ") {\n")
library_source.write("\t\t*out_" + param_name + " = fds[reply.body." + param_name + "];\n")
library_source.write("\t} else {\n")
library_source.write("\t\tdserver_rpc_hooks_close_fd(fds[reply.body." + param_name + "]);\n")
library_source.write("\t}\n")
else:
library_source.write("\tif (out_" + param_name + ") {\n")
library_source.write("\t\t*out_" + param_name + " = reply.body." + param_name + ";\n")
library_source.write("\t}\n")
library_source.write("\treturn status;\n")
library_source.write("};\n\n")
public_header.write("""\
#ifdef __cplusplus
};
#endif
#endif // _DARLINGSERVER_API_H_
""")
public_header.close()
internal_header.close()
library_source.close()

View File

@ -1,162 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE 1
#include <darlingserver/call.hpp>
#include <darlingserver/server.hpp>
#include <sys/uio.h>
std::shared_ptr<DarlingServer::Call> DarlingServer::Call::callFromMessage(Message&& requestMessage, MessageQueue& replyQueue) {
if (requestMessage.data().size() < sizeof(dserver_rpc_callhdr_t)) {
throw std::invalid_argument("Message buffer was too small for call header");
}
dserver_rpc_callhdr_t* header = reinterpret_cast<dserver_rpc_callhdr_t*>(requestMessage.data().data());
std::shared_ptr<Call> result = nullptr;
std::shared_ptr<Process> process = nullptr;
std::shared_ptr<Thread> thread = nullptr;
// first, make sure we know this call number
switch (header->number) {
DSERVER_VALID_CALLNUM_CASES
break;
default:
throw std::invalid_argument("Invalid call number");
}
// now let's lookup (and possibly create) the process and thread making this call
process = processRegistry().registerIfAbsent(header->pid, [&]() {
auto tmp = std::make_shared<Process>(requestMessage.pid(), header->pid);
Server::sharedInstance().monitorProcess(tmp);
return tmp;
});
thread = threadRegistry().registerIfAbsent(header->tid, [&]() {
auto tmp = std::make_shared<Thread>(process, header->tid);
tmp->setAddress(requestMessage.address());
tmp->registerWithProcess();
return tmp;
});
// finally, let's construct the call class
#define CALL_CASE(_callName, _className) \
case dserver_callnum_ ## _callName: { \
if (requestMessage.data().size() < sizeof(dserver_rpc_call_ ## _callName ## _t)) { \
throw std::invalid_argument("Message buffer was too small for dserver_call_" #_callName "_t"); \
} \
result = std::make_shared<_className>(replyQueue, thread, reinterpret_cast<dserver_rpc_call_ ## _callName ## _t*>(header), std::move(requestMessage)); \
} break;
switch (header->number) {
DSERVER_CONSTRUCT_CASES
default:
throw std::invalid_argument("Invalid call number");
}
#undef CALL_CASE
thread->setPendingCall(result);
return result;
};
DarlingServer::Call::Call(MessageQueue& replyQueue, std::shared_ptr<Thread> thread, Address replyAddress):
_replyQueue(replyQueue),
_thread(thread),
_replyAddress(replyAddress)
{};
DarlingServer::Call::~Call() {};
/**
* Note that you MUST NOT have any local variables referencing `this` when this method is called.
*
* This Call object MAY be destroyed upon the return of this method.
*/
void DarlingServer::Call::_stopPending() {
// we're done processing this call; dump it
if (auto thread = _thread.lock()) {
thread->setPendingCall(nullptr);
}
};
void DarlingServer::Call::Checkin::processCall() {
// the Call creation already took care of registering the process and thread
_sendReply(0);
_stopPending();
};
void DarlingServer::Call::Checkout::processCall() {
int code = 0;
if (auto thread = _thread.lock()) {
if (auto process = thread->process()) {
threadRegistry().unregisterEntry(thread);
if (thread->id() == process->id()) {
processRegistry().unregisterEntry(process);
}
} else {
code = -ESRCH;
}
} else {
code = -ESRCH;
}
_sendReply(code);
_stopPending();
};
void DarlingServer::Call::VchrootPath::processCall() {
int code = 0;
size_t fullLength = 0;
if (auto thread = _thread.lock()) {
if (auto process = thread->process()) {
if (_body.buffer_size > 0) {
struct iovec local;
struct iovec remote;
auto tmpstr = process->vchrootPath().substr(0, _body.buffer_size - 1);
auto len = std::min(tmpstr.length() + 1, _body.buffer_size);
fullLength = process->vchrootPath().length();
// note that, despite the const cast, this is safe because the local iovec data is not modified by the call
local.iov_base = const_cast<char*>(tmpstr.c_str());
local.iov_len = len;
remote.iov_base = _body.buffer;
remote.iov_len = len;
if (process_vm_writev(process->id(), &local, 1, &remote, 1, 0) < 0) {
code = -errno;
}
}
} else {
code = -ESRCH;
}
} else {
code = -ESRCH;
}
_sendReply(code, fullLength);
_stopPending();
};

View File

@ -1,532 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE 1
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <sys/prctl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <pwd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <iostream>
#include <linux/sched.h>
#include <sys/syscall.h>
#include <sys/signal.h>
#include <darling-config.h>
#include <darlingserver/server.hpp>
// TODO: most of the code here was ported over from startup/darling.c; we should C++-ify it.
#define MLDR_PATH LIBEXEC_PATH "/usr/libexec/darling/mldr"
void fixPermissionsRecursive(const char* path, uid_t originalUID, gid_t originalGID)
{
DIR* dir;
struct dirent* ent;
if (chown(path, originalUID, originalGID) == -1)
fprintf(stderr, "Cannot chown %s: %s\n", path, strerror(errno));
dir = opendir(path);
if (!dir)
return;
while ((ent = readdir(dir)) != NULL)
{
if (ent->d_type == DT_DIR)
{
char* subdir;
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
subdir = (char*) malloc(strlen(path) + 2 + strlen(ent->d_name));
sprintf(subdir, "%s/%s", path, ent->d_name);
fixPermissionsRecursive(subdir, originalUID, originalGID);
free(subdir);
}
}
closedir(dir);
}
const char* xdgDirectory(const char* name)
{
static char dir[4096];
char* cmd = (char*) malloc(16 + strlen(name));
sprintf(cmd, "xdg-user-dir %s", name);
FILE* proc = popen(cmd, "r");
free(cmd);
if (!proc)
return NULL;
fgets(dir, sizeof(dir)-1, proc);
pclose(proc);
size_t len = strlen(dir);
if (len <= 1)
return NULL;
if (dir[len-1] == '\n')
dir[len-1] = '\0';
return dir;
}
void setupUserHome(const char* prefix, uid_t originalUID)
{
char buf[4096], buf2[4096];
snprintf(buf, sizeof(buf), "%s/Users", prefix);
// Remove the old /Users symlink that may exist
unlink(buf);
// mkdir /Users
mkdir(buf, 0777);
// mkdir /Users/Shared
strcat(buf, "/Shared");
mkdir(buf, 0777);
const char* home = getenv("HOME");
const char* login = NULL;
struct passwd* pw = getpwuid(originalUID);
if (pw != NULL)
login = pw->pw_name;
if (!login)
login = getlogin();
if (!login)
{
fprintf(stderr, "Cannot determine your user name\n");
exit(1);
}
if (!home)
{
fprintf(stderr, "Cannot determine your home directory\n");
exit(1);
}
snprintf(buf, sizeof(buf), "%s/Users/%s", prefix, login);
// mkdir /Users/$LOGIN
mkdir(buf, 0755);
snprintf(buf2, sizeof(buf2), "/Volumes/SystemRoot%s", home);
strcat(buf, "/LinuxHome");
unlink(buf);
// symlink /Users/$LOGIN/LinuxHome -> $HOME
symlink(buf2, buf);
static const char* xdgmap[][2] = {
{ "DESKTOP", "Desktop" },
{ "DOWNLOAD", "Downloads" },
{ "PUBLICSHARE", "Public" },
{ "DOCUMENTS", "Documents" },
{ "MUSIC", "Music" },
{ "PICTURES", "Pictures" },
{ "VIDEOS", "Movies" },
};
for (int i = 0; i < sizeof(xdgmap) / sizeof(xdgmap[0]); i++)
{
const char* dir = xdgDirectory(xdgmap[i][0]);
if (!dir)
continue;
snprintf(buf2, sizeof(buf2), "/Volumes/SystemRoot%s", dir);
snprintf(buf, sizeof(buf), "%s/Users/%s/%s", prefix, login, xdgmap[i][1]);
unlink(buf);
symlink(buf2, buf);
}
}
void setupCoredumpPattern(void)
{
FILE* f = fopen("/proc/sys/kernel/core_pattern", "w");
if (f != NULL)
{
// This is how macOS saves core dumps
fputs("/cores/core.%p\n", f);
fclose(f);
}
}
static void wipeDir(const char* dirpath)
{
char path[4096];
struct dirent* ent;
DIR* dir = opendir(dirpath);
if (!dir)
return;
while ((ent = readdir(dir)) != NULL)
{
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name);
if (ent->d_type == DT_DIR)
{
wipeDir(path);
rmdir(path);
}
else
unlink(path);
}
closedir(dir);
}
void darlingPreInit(const char* prefix)
{
// TODO: Run /usr/libexec/makewhatis
const char* dirs[] = {
"/var/tmp",
"/var/run"
};
char fullpath[4096];
strcpy(fullpath, prefix);
const size_t prefixLen = strlen(fullpath);
for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); i++)
{
fullpath[prefixLen] = 0;
strcat(fullpath, dirs[i]);
wipeDir(fullpath);
}
}
void spawnLaunchd(const char* prefix)
{
puts("Bootstrapping the container with launchd...");
// putenv("KQUEUE_DEBUG=1");
auto tmp = (std::string(prefix) + "/var/run/darlingserver.sock");
setenv("DYLD_ROOT_PATH", LIBEXEC_PATH, 1);
setenv("__mldr_sockpath", tmp.c_str(), 1);
execl(MLDR_PATH, "mldr!" LIBEXEC_PATH "/usr/libexec/darling/vchroot", "vchroot", prefix, "/sbin/launchd", NULL);
fprintf(stderr, "Failed to exec launchd: %s\n", strerror(errno));
abort();
}
static bool testEnvVar(const char* var_name) {
const char* var = getenv(var_name);
if (!var) {
return false;
}
auto len = strlen(var);
if (len > 0 && (var[0] == '1' || var[0] == 't' || var[0] == 'T')) {
return true;
}
return false;
};
static void temp_drop_privileges(uid_t uid, gid_t gid) {
// it's important to drop GID first, because non-root users can't change their GID
if (setresgid(gid, gid, 0) < 0) {
fprintf(stderr, "Failed to temporarily drop group privileges\n");
exit(1);
}
if (setresuid(uid, uid, 0) < 0) {
fprintf(stderr, "Failed to temporarily drop user privileges\n");
exit(1);
}
};
static void perma_drop_privileges(uid_t uid, gid_t gid) {
if (setresgid(gid, gid, gid) < 0) {
fprintf(stderr, "Failed to drop group privileges\n");
exit(1);
}
if (setresuid(uid, uid, uid) < 0) {
fprintf(stderr, "Failed to drop user privileges\n");
exit(1);
}
};
static void regain_privileges() {
if (seteuid(0) < 0) {
fprintf(stderr, "Failed to regain root EUID\n");
exit(1);
}
if (setegid(0) < 0) {
fprintf(stderr, "Failed to regain root EGID\n");
exit(1);
}
if (setresuid(0, 0, 0) < 0) {
fprintf(stderr, "Failed to regain root privileges\n");
exit(1);
}
if (setresgid(0, 0, 0) < 0) {
fprintf(stderr, "Failed to regain root privileges\n");
exit(1);
}
};
int main(int argc, char** argv) {
const char* prefix = NULL;
uid_t originalUID = -1;
gid_t originalGID = -1;
int pipefd = -1;
bool fix_permissions = false;
pid_t launchdGlobalPID = -1;
size_t prefix_length = 0;
struct rlimit default_limit;
struct rlimit increased_limit;
FILE* nr_open_file = NULL;
int childWaitFDs[2];
char *opts;
char putOld[4096];
char *p;
if (argc < 6) {
fprintf(stderr, "darlingserver is not meant to be started manually\n");
exit(1);
}
prefix = argv[1];
sscanf(argv[2], "%d", &originalUID);
sscanf(argv[3], "%d", &originalGID);
sscanf(argv[4], "%d", &pipefd);
if (argv[5][0] == '1') {
fix_permissions = true;
}
prefix_length = strlen(prefix);
if (getuid() != 0 || getgid() != 0) {
fprintf(stderr, "darlingserver needs to start as root\n");
exit(1);
}
// temporarily drop privileges to perform some prefix work
temp_drop_privileges(originalUID, originalGID);
setupUserHome(prefix, originalUID);
//setupCoredumpPattern();
regain_privileges();
// read the default rlimit so we can restore it for our children
if (getrlimit(RLIMIT_NOFILE, &default_limit) != 0) {
fprintf(stderr, "Failed to read default FD rlimit: %s\n", strerror(errno));
exit(1);
}
// read the system maximum
nr_open_file = fopen("/proc/sys/fs/nr_open", "r");
if (nr_open_file == NULL) {
fprintf(stderr, "Failed to open /proc/sys/fs/nr_open: %s\n", strerror(errno));
exit(1);
}
if (fscanf(nr_open_file, "%lu", &increased_limit.rlim_max) != 1) {
fprintf(stderr, "Failed to read /proc/sys/fs/nr_open: %s\n", strerror(errno));
exit(1);
}
increased_limit.rlim_cur = increased_limit.rlim_max;
if (fclose(nr_open_file) != 0) {
fprintf(stderr, "Failed to close /proc/sys/fs/nr_open: %s\n", strerror(errno));
exit(1);
}
// now set our increased rlimit
if (setrlimit(RLIMIT_NOFILE, &increased_limit) != 0) {
fprintf(stderr, "Failed to increase FD rlimit: %s\n", strerror(errno));
exit(1);
}
// Since overlay cannot be mounted inside user namespaces, we have to setup a new mount namespace
// and do the mount while we can be root
if (unshare(CLONE_NEWNS) != 0)
{
fprintf(stderr, "Cannot unshare PID and mount namespaces: %s\n", strerror(errno));
exit(1);
}
// Because systemd marks / as MS_SHARED and we would inherit this into the overlay mount,
// causing it not to be unmounted once the init process dies.
if (mount(NULL, "/", NULL, MS_REC | MS_SLAVE, NULL) != 0)
{
fprintf(stderr, "Cannot remount / as slave: %s\n", strerror(errno));
exit(1);
}
umount("/dev/shm");
if (mount("tmpfs", "/dev/shm", "tmpfs", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0)
{
fprintf(stderr, "Cannot mount new /dev/shm: %s\n", strerror(errno));
exit(1);
}
opts = (char*) malloc(strlen(prefix)*2 + sizeof(LIBEXEC_PATH) + 100);
const char* opts_fmt = "lowerdir=%s,upperdir=%s,workdir=%s.workdir,index=off";
sprintf(opts, opts_fmt, LIBEXEC_PATH, prefix, prefix);
// Mount overlay onto our prefix
if (mount("overlay", prefix, "overlay", 0, opts) != 0)
{
fprintf(stderr, "Cannot mount overlay: %s\n", strerror(errno));
exit(1);
}
free(opts);
// This is executed once at prefix creation
if (fix_permissions) {
const char* extra_paths[] = {
"/private/etc/passwd",
"/private/etc/master.passwd",
"/private/etc/group",
};
char path[4096];
fixPermissionsRecursive(prefix, originalUID, originalGID);
path[sizeof(path) - 1] = '\0';
strncpy(path, prefix, sizeof(path) - 1);
for (size_t i = 0; i < sizeof(extra_paths) / sizeof(*extra_paths); ++i) {
path[prefix_length] = '\0';
strncat(path, extra_paths[i], sizeof(path) - 1);
fixPermissionsRecursive(path, originalUID, originalGID);
}
}
snprintf(putOld, sizeof(putOld), "%s/proc", prefix);
// mount procfs for our new PID namespace
if (mount("proc", putOld, "proc", 0, "") != 0)
{
fprintf(stderr, "Cannot mount procfs: %s\n", strerror(errno));
exit(1);
}
// temporarily drop privileges and do some prefix work
temp_drop_privileges(originalUID, originalGID);
darlingPreInit(prefix);
regain_privileges();
// Tell the parent we're ready
write(pipefd, ".", 1);
close(pipefd);
if (pipe(childWaitFDs) != 0) {
std::cerr << "Failed to create child waiting pipe: " << strerror(errno) << std::endl;
exit(1);
}
// we have to use `clone` rather than `fork` to create the process in its own PID namespace
// and still be able to spawn new processes and threads of our own
struct clone_args launchdCloneArgs;
launchdCloneArgs.flags = CLONE_NEWPID;
launchdCloneArgs.pidfd = 0;
launchdCloneArgs.child_tid = 0;
launchdCloneArgs.parent_tid = 0;
launchdCloneArgs.exit_signal = SIGCHLD;
launchdCloneArgs.stack = 0;
launchdCloneArgs.stack_size = 0;
launchdCloneArgs.tls = 0;
launchdCloneArgs.set_tid = 0;
launchdCloneArgs.set_tid_size = 0;
launchdCloneArgs.cgroup = 0;
launchdGlobalPID = syscall(SYS_clone3, &launchdCloneArgs, sizeof(launchdCloneArgs));
// drop privileges in both parent and child
perma_drop_privileges(originalUID, originalGID);
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
if (launchdGlobalPID < 0) {
fprintf(stderr, "Failed to fork to start launchd: %s\n", strerror(errno));
exit(1);
} else if (launchdGlobalPID == 0) {
// this is the child
char buf[1];
close(childWaitFDs[1]);
// decrease the FD limit back to the default
if (setrlimit(RLIMIT_NOFILE, &default_limit) != 0) {
fprintf(stderr, "Failed to decrease FD limit back down for launchd: %s\n", strerror(errno));
exit(1);
}
// wait for the parent to give us the green light
read(childWaitFDs[0], buf, 1);
close(childWaitFDs[0]);
spawnLaunchd(prefix);
__builtin_unreachable();
}
// this is the parent
close(childWaitFDs[0]);
// create the server
auto server = new DarlingServer::Server(prefix);
// tell the child to go ahead; the socket has been created
write(childWaitFDs[1], ".", 1);
close(childWaitFDs[1]);
// start the main loop
server->start();
// this should never happen
std::cerr << "Server exited main loop!" << std::endl;
delete server;
return 1;
};

View File

@ -1,87 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include <darlingserver/logging.hpp>
#include <darlingserver/server.hpp>
#include <filesystem>
#include <fcntl.h>
#include <unistd.h>
DarlingServer::Log::Log(std::string category):
_category(category)
{};
DarlingServer::Log::Stream::Stream(Type type, Log& log):
_type(type),
_log(log)
{};
template<>
DarlingServer::Log::Stream& DarlingServer::Log::Stream::operator<<<EndLog>(EndLog value) {
_log._log(_type, _buffer.str());
return *this;
};
DarlingServer::Log::Stream DarlingServer::Log::debug() {
return Stream(Type::Debug, *this);
};
DarlingServer::Log::Stream DarlingServer::Log::info() {
return Stream(Type::Info, *this);
};
DarlingServer::Log::Stream DarlingServer::Log::warning() {
return Stream(Type::Warning, *this);
};
DarlingServer::Log::Stream DarlingServer::Log::error() {
return Stream(Type::Error, *this);
};
std::string DarlingServer::Log::_typeToString(Type type) {
switch (type) {
case Type::Debug:
return "Debug";
case Type::Info:
return "Info";
case Type::Warning:
return "Warning";
case Type::Error:
return "Error";
default:
return "Unknown";
}
};
void DarlingServer::Log::_log(Type type, std::string message) {
// NOTE: we use POSIX file APIs because we want to append each message to the log file atomically,
// and as far as i can tell, C++ fstreams provide no such guarantee (that they won't write in chunks).
static int logFile = []() {
std::filesystem::path path(Server::sharedInstance().prefix() + "/private/var/log/");
std::filesystem::create_directories(path.parent_path());
return open(path.c_str(), O_WRONLY | O_APPEND);
}();
struct timespec time;
clock_gettime(CLOCK_REALTIME, &time);
double secs = (double)time.tv_sec + ((double)time.tv_nsec / 1.0e9);
std::string messageToLog = "[" + std::to_string(secs) + "](" + _category + ", " + _typeToString(type) + ") " + message + "\n";
write(logFile, messageToLog.c_str(), messageToLog.size());
};

View File

@ -1,568 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include <darlingserver/message.hpp>
#include <unistd.h>
#include <cstdlib>
#include <stdexcept>
#include <new>
#include <mutex>
DarlingServer::Address::Address() {
_address.sun_family = AF_UNIX;
memset(_address.sun_path, 0, sizeof(_address.sun_path));
_size = sizeof(_address);
};
DarlingServer::Address::Address(const struct sockaddr_un& rawAddress, size_t addressLength) {
_address = rawAddress;
_size = addressLength;
};
struct sockaddr_un& DarlingServer::Address::raw() {
return _address;
};
const struct sockaddr_un& DarlingServer::Address::raw() const {
return _address;
};
size_t DarlingServer::Address::rawSize() const {
return _size;
};
void DarlingServer::Address::setRawSize(size_t newRawSize) {
_size = newRawSize;
};
DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace) {
size_t controlLen = CMSG_SPACE(sizeof(struct ucred)) + (descriptorSpace > 0 ? CMSG_SPACE(sizeof(int) * descriptorSpace) : 0);
_controlHeader = static_cast<decltype(_controlHeader)>(malloc(controlLen));
if (!_controlHeader) {
throw std::bad_alloc();
}
_controlHeader->cmsg_len = CMSG_LEN(sizeof(struct ucred));
_controlHeader->cmsg_level = SOL_SOCKET;
_controlHeader->cmsg_type = SCM_CREDENTIALS;
struct ucred ourCreds;
ourCreds.pid = getpid();
ourCreds.uid = getuid();
ourCreds.gid = getgid();
// the cmsg man page says memcpy should be used with CMSG_DATA instead of direct pointer access
memcpy(CMSG_DATA(_controlHeader), &ourCreds, sizeof(ourCreds));
if (descriptorSpace > 0) {
auto fdHeader = reinterpret_cast<decltype(_controlHeader)>(reinterpret_cast<char*>(_controlHeader) + CMSG_SPACE(sizeof(struct ucred)));
fdHeader->cmsg_len = CMSG_LEN(sizeof(int) * descriptorSpace);
fdHeader->cmsg_level = SOL_SOCKET;
fdHeader->cmsg_type = SCM_RIGHTS;
for (size_t i = 0; i < descriptorSpace; ++i) {
const int fd = -1;
memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &fd, sizeof(int));
}
}
_buffer.resize(bufferSpace);
_header.msg_name = &_socketAddress.raw();
_header.msg_namelen = _socketAddress.rawSize();
_header.msg_iov = &_dataDescriptor;
_header.msg_iovlen = 1;
_header.msg_control = _controlHeader;
_header.msg_controllen = controlLen;
_header.msg_flags = 0;
_dataDescriptor.iov_base = _buffer.data();
_dataDescriptor.iov_len = _buffer.capacity();
};
void DarlingServer::Message::_initWithOther(Message&& other) {
_buffer = std::move(other._buffer);
_socketAddress = std::move(other._socketAddress);
_controlHeader = std::move(other._controlHeader);
other._controlHeader = nullptr;
_header = std::move(other._header);
_header.msg_name = &_socketAddress.raw();
_header.msg_iov = &_dataDescriptor;
_dataDescriptor.iov_base = _buffer.data();
_dataDescriptor.iov_len = _buffer.capacity();
};
void DarlingServer::Message::_cleanupSelf() {
if (_controlHeader) {
auto fdHeader = _descriptorHeader();
if (fdHeader) {
for (size_t i = 0; i < _descriptorSpace(); ++i) {
int fd = -1;
memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int));
if (fd != -1) {
close(fd);
}
}
}
free(_controlHeader);
_controlHeader = nullptr;
}
};
struct cmsghdr* DarlingServer::Message::_credentialsHeader() {
return const_cast<struct cmsghdr*>(const_cast<const Message*>(this)->_credentialsHeader());
};
const struct cmsghdr* DarlingServer::Message::_credentialsHeader() const {
const struct cmsghdr* hdr = CMSG_FIRSTHDR(&_header);
while (hdr) {
if (hdr->cmsg_level == SOL_SOCKET && hdr->cmsg_type == SCM_CREDENTIALS) {
return hdr;
}
hdr = CMSG_NXTHDR(const_cast<struct msghdr*>(&_header), const_cast<struct cmsghdr*>(hdr));
}
return nullptr;
};
struct cmsghdr* DarlingServer::Message::_descriptorHeader() {
return const_cast<struct cmsghdr*>(const_cast<const Message*>(this)->_descriptorHeader());
};
const struct cmsghdr* DarlingServer::Message::_descriptorHeader() const {
const struct cmsghdr* hdr = CMSG_FIRSTHDR(&_header);
while (hdr) {
if (hdr->cmsg_level == SOL_SOCKET && hdr->cmsg_type == SCM_RIGHTS) {
return hdr;
}
hdr = CMSG_NXTHDR(const_cast<struct msghdr*>(&_header), const_cast<struct cmsghdr*>(hdr));
}
return nullptr;
};
size_t DarlingServer::Message::_descriptorSpace() const {
auto hdr = _descriptorHeader();
if (!hdr) {
return 0;
}
return (hdr->cmsg_len - (reinterpret_cast<uintptr_t>(CMSG_DATA(hdr)) - reinterpret_cast<uintptr_t>(hdr))) / sizeof(int);
};
DarlingServer::Message::~Message() {
_cleanupSelf();
};
DarlingServer::Message::Message(Message&& other) {
_initWithOther(std::move(other));
};
DarlingServer::Message& DarlingServer::Message::operator=(Message&& other) {
_cleanupSelf();
_initWithOther(std::move(other));
return *this;
};
struct msghdr& DarlingServer::Message::rawHeader() {
return _header;
};
const struct msghdr& DarlingServer::Message::rawHeader() const {
return _header;
};
std::vector<uint8_t>& DarlingServer::Message::data() {
return _buffer;
};
const std::vector<uint8_t>& DarlingServer::Message::data() const {
return _buffer;
};
DarlingServer::Address DarlingServer::Message::address() const {
return _socketAddress;
};
void DarlingServer::Message::setAddress(Address address) {
_socketAddress = address;
_header.msg_namelen = _socketAddress.rawSize();
};
std::vector<int> DarlingServer::Message::descriptors() const {
std::vector<int> descriptors;
auto fdHeader = _descriptorHeader();
if (fdHeader) {
for (size_t i = 0; i < _descriptorSpace(); ++i) {
int fd = -1;
memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int));
if (fd != -1) {
descriptors.push_back(fd);
}
}
}
return descriptors;
};
void DarlingServer::Message::pushDescriptor(int descriptor) {
if (auto fdHeader = _descriptorHeader()) {
for (size_t i = 0; i < _descriptorSpace(); ++i) {
int fd = -1;
memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int));
if (fd != -1) {
continue;
}
memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &descriptor, sizeof(int));
return;
}
}
auto oldSpace = _descriptorSpace();
_ensureDescriptorHeader(oldSpace + 1);
memcpy(CMSG_DATA(_descriptorHeader()) + (sizeof(int) * oldSpace), &descriptor, sizeof(int));
};
int DarlingServer::Message::extractDescriptor(int descriptor) {
if (auto fdHeader = _descriptorHeader()) {
for (size_t i = 0; i < _descriptorSpace(); ++i) {
int fd = -1;
memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int));
if (fd != descriptor) {
continue;
}
fd = -1;
memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &fd, sizeof(int));
return descriptor;
}
}
return -1;
};
int DarlingServer::Message::extractDescriptorAtIndex(size_t index) {
if (auto fdHeader = _descriptorHeader()) {
for (size_t i = 0, presentIndex = 0; i < _descriptorSpace(); ++i) {
int fd = -1;
memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int));
if (fd == -1) {
continue;
}
if (presentIndex++ != index) {
continue;
}
int tmp = -1;
memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &tmp, sizeof(int));
return fd;
}
}
return -1;
};
void DarlingServer::Message::replaceDescriptors(const std::vector<int>& newDescriptors) {
_ensureDescriptorHeader(newDescriptors.size());
memcpy(CMSG_DATA(_descriptorHeader()), newDescriptors.data(), sizeof(int) * newDescriptors.size());
};
bool DarlingServer::Message::copyCredentialsOut(struct ucred& outputCredentials) const {
auto credHeader = _credentialsHeader();
if (credHeader) {
memcpy(&outputCredentials, CMSG_DATA(credHeader), sizeof(outputCredentials));
return true;
} else {
return false;
}
};
void DarlingServer::Message::copyCredentialsIn(const struct ucred& inputCredentials) {
_ensureCredentialsHeader();
memcpy(CMSG_DATA(_credentialsHeader()), &inputCredentials, sizeof(inputCredentials));
};
void DarlingServer::Message::_ensureCredentialsHeader() {
if (_credentialsHeader()) {
return;
}
auto fdHeader = _descriptorHeader();
auto descSpace = _descriptorSpace();
size_t controlLen = CMSG_SPACE(sizeof(struct ucred)) + (descSpace > 0 ? CMSG_SPACE(sizeof(int) * descSpace) : 0);
auto tmp = static_cast<decltype(_controlHeader)>(malloc(controlLen));
auto old = _controlHeader;
struct cmsghdr* credHeader = nullptr;
if (!tmp) {
throw std::bad_alloc();
}
_controlHeader = tmp;
_header.msg_control = _controlHeader;
credHeader = _controlHeader;
if (descSpace > 0) {
auto newFdHeader = reinterpret_cast<decltype(_controlHeader)>(reinterpret_cast<char*>(_controlHeader) + CMSG_SPACE(sizeof(struct ucred)));
memcpy(newFdHeader, fdHeader, CMSG_SPACE(sizeof(int) * descSpace));
}
free(old);
_header.msg_controllen = controlLen;
credHeader->cmsg_len = CMSG_LEN(sizeof(struct ucred));
credHeader->cmsg_level = SOL_SOCKET;
credHeader->cmsg_type = SCM_CREDENTIALS;
struct ucred creds;
creds.pid = getpid();
creds.uid = getuid();
creds.gid = getgid();
memcpy(CMSG_DATA(credHeader), &creds, sizeof(struct ucred));
};
void DarlingServer::Message::_ensureDescriptorHeader(size_t newSpace) {
if (_descriptorSpace() == newSpace) {
return;
}
auto credHeader = _credentialsHeader();
size_t oldSpace = _descriptorSpace();
size_t controlLen = (credHeader ? CMSG_SPACE(sizeof(struct ucred)) : 0) + (newSpace == 0 ? 0 : CMSG_SPACE(sizeof(int) * newSpace));
if (controlLen == 0) {
free(_controlHeader);
_controlHeader = nullptr;
_header.msg_control = nullptr;
_header.msg_controllen = 0;
return;
}
auto tmp = static_cast<decltype(_controlHeader)>(malloc(controlLen));
auto old = _controlHeader;
struct cmsghdr* fdHeader = nullptr;
if (!tmp) {
throw std::bad_alloc();
}
_controlHeader = tmp;
_header.msg_control = _controlHeader;
if (credHeader) {
memcpy(_controlHeader, credHeader, CMSG_SPACE(sizeof(struct ucred)));
fdHeader = reinterpret_cast<decltype(_controlHeader)>(reinterpret_cast<char*>(_controlHeader) + CMSG_SPACE(sizeof(struct ucred)));
} else {
fdHeader = _controlHeader;
}
free(old);
_header.msg_controllen = controlLen;
fdHeader->cmsg_len = CMSG_LEN(sizeof(int) * newSpace);
fdHeader->cmsg_level = SOL_SOCKET;
fdHeader->cmsg_type = SCM_RIGHTS;
for (size_t i = oldSpace; i < newSpace; ++i) {
int fd = -1;
memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &fd, sizeof(int));
}
};
pid_t DarlingServer::Message::pid() const {
struct ucred creds;
if (copyCredentialsOut(creds)) {
return creds.pid;
} else {
return -1;
}
};
void DarlingServer::Message::setPID(pid_t pid) {
_ensureCredentialsHeader();
struct ucred creds;
copyCredentialsOut(creds);
creds.pid = pid;
copyCredentialsIn(creds);
};
uid_t DarlingServer::Message::uid() const {
struct ucred creds;
if (copyCredentialsOut(creds)) {
return creds.uid;
} else {
return -1;
}
};
void DarlingServer::Message::setUID(uid_t uid) {
_ensureCredentialsHeader();
struct ucred creds;
copyCredentialsOut(creds);
creds.uid = uid;
copyCredentialsIn(creds);
};
gid_t DarlingServer::Message::gid() const {
struct ucred creds;
if (copyCredentialsOut(creds)) {
return creds.gid;
} else {
return -1;
}
};
void DarlingServer::Message::setGID(gid_t gid) {
_ensureCredentialsHeader();
struct ucred creds;
copyCredentialsOut(creds);
creds.gid = gid;
copyCredentialsIn(creds);
};
void DarlingServer::MessageQueue::push(Message&& message) {
std::unique_lock lock(_lock);
_messages.push_back(std::move(message));
if (_messages.size() > 0) {
auto callback = _messageArrivalNotificationCallback;
lock.unlock();
if (callback) {
callback();
}
}
};
std::optional<DarlingServer::Message> DarlingServer::MessageQueue::pop() {
std::scoped_lock lock(_lock);
if (_messages.size() > 0) {
Message message = std::move(_messages.front());
_messages.pop_front();
return message;
} else {
return std::nullopt;
}
};
bool DarlingServer::MessageQueue::sendMany(int socket) {
bool canSendMore = true;
std::scoped_lock lock(_lock);
struct mmsghdr mmsgs[16];
size_t len = 0;
int ret = 0;
while (ret >= 0) {
len = 0;
for (size_t i = 0; i < _messages.size() && i < sizeof(mmsgs) / sizeof(*mmsgs); ++i) {
mmsgs[i].msg_hdr = _messages[i].rawHeader();
mmsgs[i].msg_len = 0;
++len;
}
if (len == 0) {
break;
}
ret = sendmmsg(socket, mmsgs, len, MSG_DONTWAIT);
if (ret < 0) {
if (errno == EAGAIN) {
canSendMore = false;
break;
} else if (errno == EINTR) {
ret = 0;
} else {
throw std::system_error(errno, std::generic_category(), "Failed to send messages through socket");
}
}
for (size_t i = 0; i < ret; ++i) {
_messages.pop_front();
}
}
return canSendMore;
};
bool DarlingServer::MessageQueue::receiveMany(int socket) {
bool canReadMore = true;
std::unique_lock lock(_lock);
struct mmsghdr mmsgs[16];
int ret = 0;
while (ret >= 0) {
std::array<Message, sizeof(mmsgs) / sizeof(*mmsgs)> messages;
for (size_t i = 0; i < messages.size(); ++i) {
mmsgs[i].msg_hdr = messages[i].rawHeader();
mmsgs[i].msg_len = 0;
}
ret = recvmmsg(socket, mmsgs, sizeof(mmsgs) / sizeof(*mmsgs), MSG_CMSG_CLOEXEC | MSG_DONTWAIT, nullptr);
if (ret < 0) {
if (errno == EAGAIN) {
canReadMore = false;
break;
} else if (errno == EINTR) {
ret = 0;
} else {
throw std::system_error(errno, std::generic_category(), "Failed to receive messages through socket");
}
}
for (size_t i = 0; i < ret; ++i) {
messages[i].rawHeader() = mmsgs[i].msg_hdr;
messages[i].data().resize(mmsgs[i].msg_len);
messages[i].setAddress(Address(*(const struct sockaddr_un*)mmsgs[i].msg_hdr.msg_name, mmsgs[i].msg_hdr.msg_namelen));
_messages.push_back(std::move(messages[i]));
}
}
if (_messages.size() > 0) {
auto callback = _messageArrivalNotificationCallback;
lock.unlock();
if (callback) {
callback();
}
}
return canReadMore;
};
void DarlingServer::MessageQueue::setMessageArrivalNotificationCallback(std::function<void()> messageArrivalNotificationCallback) {
std::unique_lock lock(_lock);
_messageArrivalNotificationCallback = messageArrivalNotificationCallback;
};

View File

@ -1,84 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include <darlingserver/process.hpp>
#include <darlingserver/registry.hpp>
#include <sys/syscall.h>
#include <unistd.h>
DarlingServer::Process::Process(ID id, NSID nsid):
_pid(id),
_nspid(nsid)
{
_pidfd = syscall(SYS_pidfd_open, _pid, 0);
if (_pidfd < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to open pidfd for process");
}
};
DarlingServer::Process::~Process() {
close(_pidfd);
_unregisterThreads();
};
void DarlingServer::Process::_unregisterThreads() {
std::unique_lock lock(_rwlock);
while (!_threads.empty()) {
auto thread = _threads.back().lock();
lock.unlock();
if (thread) {
thread->_process = std::weak_ptr<Process>();
threadRegistry().unregisterEntry(thread);
}
lock.lock();
_threads.pop_back();
}
};
DarlingServer::Process::ID DarlingServer::Process::id() const {
return _pid;
};
DarlingServer::Process::NSID DarlingServer::Process::nsid() const {
return _nspid;
};
std::vector<std::shared_ptr<DarlingServer::Thread>> DarlingServer::Process::threads() const {
std::vector<std::shared_ptr<DarlingServer::Thread>> result;
std::shared_lock lock(_rwlock);
for (auto& maybeThread: _threads) {
if (auto thread = maybeThread.lock()) {
result.push_back(thread);
}
}
return result;
};
std::string DarlingServer::Process::vchrootPath() const {
std::shared_lock lock(_rwlock);
return _vchrootPath;
};
void DarlingServer::Process::setVchrootPath(std::string path) {
std::unique_lock lock(_rwlock);
_vchrootPath = path;
};

View File

@ -1,30 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include <darlingserver/registry.hpp>
DarlingServer::Registry<DarlingServer::Process>& DarlingServer::processRegistry() {
static Registry<Process> registry;
return registry;
};
DarlingServer::Registry<DarlingServer::Thread>& DarlingServer::threadRegistry() {
static Registry<Thread> registry;
return registry;
};

View File

@ -1,196 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include <darlingserver/server.hpp>
#include <sys/socket.h>
#include <stdexcept>
#include <errno.h>
#include <cstring>
#include <unistd.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <system_error>
#include <thread>
#include <array>
#include <darlingserver/registry.hpp>
#include <sys/eventfd.h>
static DarlingServer::Server* sharedInstancePointer = nullptr;
DarlingServer::Server::Server(std::string prefix):
_prefix(prefix),
_socketPath(_prefix + "/var/run/darlingserver.sock"),
_workQueue(std::bind(&Server::_worker, this, std::placeholders::_1))
{
sharedInstancePointer = this;
// remove the old socket (if it exists)
unlink(_socketPath.c_str());
// create the socket
_listenerSocket = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
if (_listenerSocket < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to create socket");
}
int passCred = 1;
if (setsockopt(_listenerSocket, SOL_SOCKET, SO_PASSCRED, &passCred, sizeof(passCred)) < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set SO_PASSCRED on socket");
}
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
strncpy(addr.sun_path, _socketPath.c_str(), sizeof(addr.sun_path) - 1);
if (bind(_listenerSocket, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to bind socket");
}
_wakeupFD = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (_wakeupFD < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to create eventfd for on-demand epoll wakeups");
}
_epollFD = epoll_create1(EPOLL_CLOEXEC);
if (_epollFD < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to create epoll context");
}
struct epoll_event settings;
settings.data.ptr = this;
settings.events = EPOLLIN | EPOLLOUT | EPOLLET;
if (epoll_ctl(_epollFD, EPOLL_CTL_ADD, _listenerSocket, &settings) < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to add listener socket to epoll context");
}
settings.data.ptr = &_wakeupFD;
settings.events = EPOLLIN | EPOLLONESHOT;
if (epoll_ctl(_epollFD, EPOLL_CTL_ADD, _wakeupFD, &settings) < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to add eventfd to epoll context");
}
_outbox.setMessageArrivalNotificationCallback([this]() {
// we don't really have to worry about the eventfd overflowing;
// if it does, that means the main loop has been waiting a LONG time for the listener socket to become writable again.
// in that case, we don't really care if the eventfd is being incremented; we can't send anything anyways.
// once the socket becomes writable again, the eventfd will be monitored again.
eventfd_write(_wakeupFD, 1);
});
};
DarlingServer::Server::~Server() {
close(_epollFD);
close(_wakeupFD);
close(_listenerSocket);
unlink(_socketPath.c_str());
};
void DarlingServer::Server::start() {
while (true) {
if (_canRead) {
_canRead = _inbox.receiveMany(_listenerSocket);
// TODO: receive messages directly onto the work queue
while (auto msg = _inbox.pop()) {
_workQueue.push(std::move(msg.value()));
}
}
if (_canWrite) {
// reset the eventfd by reading from it
eventfd_t value;
eventfd_read(_wakeupFD, &value);
_canWrite = _outbox.sendMany(_listenerSocket);
}
struct epoll_event settings;
settings.data.ptr = &_wakeupFD;
settings.events = (_canWrite) ? (EPOLLIN | EPOLLONESHOT) : 0;
if (epoll_ctl(_epollFD, EPOLL_CTL_MOD, _wakeupFD, &settings) < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to modify eventfd in epoll context");
}
struct epoll_event events[16];
int ret = epoll_wait(_epollFD, events, 16, -1);
if (ret < 0) {
if (errno == EINTR) {
continue;
}
throw std::system_error(errno, std::generic_category(), "Failed to wait on epoll context");
}
for (size_t i = 0; i < ret; ++i) {
struct epoll_event* event = &events[i];
if (event->data.ptr == this) {
if (event->events & EPOLLIN) {
_canRead = true;
}
if (event->events & EPOLLOUT) {
_canWrite = true;
}
} else if (event->data.ptr == &_wakeupFD) {
// we allow the loop to go back to the top and try to send some messages
// (if _canWrite is true, the eventfd will be reset; otherwise, there's no point in resetting it)
} else if (event->events & EPOLLIN) {
std::shared_ptr<Process>& process = *reinterpret_cast<std::shared_ptr<Process>*>(event->data.ptr);
if (epoll_ctl(_epollFD, EPOLL_CTL_DEL, process->_pidfd, NULL) < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to remove process handle from epoll context");
}
process->_unregisterThreads();
processRegistry().unregisterEntry(process);
delete &process;
}
}
}
};
void DarlingServer::Server::monitorProcess(std::shared_ptr<Process> process) {
struct epoll_event settings;
settings.data.ptr = new std::shared_ptr<Process>(process);
settings.events = EPOLLIN;
if (epoll_ctl(_epollFD, EPOLL_CTL_ADD, process->_pidfd, &settings) < 0) {
throw std::system_error(errno, std::generic_category(), "Failed to add process descriptor to epoll context");
}
};
DarlingServer::Server& DarlingServer::Server::sharedInstance() {
return *sharedInstancePointer;
};
std::string DarlingServer::Server::prefix() const {
return _prefix;
};
void DarlingServer::Server::_worker(DarlingServer::Message message) {
auto call = DarlingServer::Call::callFromMessage(std::move(message), _outbox);
call->processCall();
};

View File

@ -1,122 +0,0 @@
/**
* This file is part of Darling.
*
* Copyright (C) 2021 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include <darlingserver/thread.hpp>
#include <darlingserver/process.hpp>
#include <filesystem>
#include <fstream>
DarlingServer::Thread::Thread(std::shared_ptr<Process> process, NSID nsid):
_nstid(nsid),
_process(process)
{
_tid = -1;
for (const auto& entry: std::filesystem::directory_iterator("/proc/" + std::to_string(process->id()) + "/task")) {
std::ifstream statusFile(entry.path() / "status");
std::string line;
while (std::getline(statusFile, line)) {
if (line.substr(0, sizeof("NSpid") - 1) == "NSpid") {
auto pos = line.find_last_of('\t');
std::string id;
if (pos != line.npos) {
id = line.substr(pos + 1);
}
if (id.empty()) {
throw std::runtime_error("Failed to parse thread ID");
}
if (std::stoi(id) != _nstid) {
continue;
}
_tid = std::stoi(entry.path().filename().string());
break;
}
}
}
if (_tid == -1) {
throw std::runtime_error("Failed to find thread ID within darlingserver's namespace");
}
};
void DarlingServer::Thread::registerWithProcess() {
auto process = _process.lock();
std::unique_lock lock(process->_rwlock);
process->_threads.push_back(shared_from_this());
};
DarlingServer::Thread::~Thread() noexcept(false) {
auto process = _process.lock();
if (!process) {
// the process is unregistering us
return;
}
std::unique_lock lock(process->_rwlock);
auto it = process->_threads.begin();
while (it != process->_threads.end()) {
if (auto thread = it->lock()) {
if (thread.get() == this) {
break;
}
}
}
if (it == process->_threads.end()) {
throw std::runtime_error("Thread was not registered with Process");
}
process->_threads.erase(it);
};
DarlingServer::Thread::ID DarlingServer::Thread::id() const {
return _tid;
};
DarlingServer::Thread::NSID DarlingServer::Thread::nsid() const {
return _nstid;
};
std::shared_ptr<DarlingServer::Process> DarlingServer::Thread::process() const {
return _process.lock();
};
std::shared_ptr<DarlingServer::Call> DarlingServer::Thread::pendingCall() const {
std::shared_lock lock(_rwlock);
return _pendingCall;
};
void DarlingServer::Thread::setPendingCall(std::shared_ptr<Call> newPendingCall) {
std::unique_lock lock(_rwlock);
_pendingCall = newPendingCall;
};
DarlingServer::Address DarlingServer::Thread::address() const {
std::shared_lock lock(_rwlock);
return _address;
};
void DarlingServer::Thread::setAddress(Address address) {
std::unique_lock lock(_rwlock);
_address = address;
};

View File

@ -124,7 +124,8 @@ static int sLastErrorNo;
#endif
#ifdef DARLING
extern "C" int mach_driver_get_fd(void);
extern "C" int mach_driver_get_dyld_fd(void);
extern "C" void* elfcalls_get_pointer(void);
#endif
// In 10.3.x and earlier all the NSObjectFileImage API's were implemeneted in libSystem.dylib
@ -259,7 +260,8 @@ static const struct dyld_func dyld_funcs[] = {
{"__dyld_link_module", (void*)_dyld_link_module },
#endif
#ifdef DARLING
{"__dyld_get_mach_driver_fd", (void*)mach_driver_get_fd },
{"__dyld_get_mach_driver_fd", (void*)mach_driver_get_dyld_fd },
{"__dyld_get_elfcalls", (void*)elfcalls_get_pointer },
#endif
#pragma clang diagnostic pop
#endif //DEPRECATED_APIS_SUPPORTED
@ -1283,7 +1285,7 @@ bool _dyld_launched_prebound()
if ( dyld::gLogAPIs )
dyld::log("%s()\n", __func__);
// ¥¥¥Êif we deprecate prebinding, we may want to consider always returning true or false here
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>if we deprecate prebinding, we may want to consider always returning true or false here
return dyld::mainExecutablePrebound();
}

View File

@ -84,7 +84,7 @@ static int sLastErrorNo;
#endif
#ifdef DARLING
extern "C" int mach_driver_get_fd(void);
extern "C" int mach_driver_get_dyld_fd(void);
extern "C" bool darling_am_i_ptraced(void);
#endif
@ -214,7 +214,7 @@ static struct dyld_func dyld_funcs[] = {
{"__dyld_link_module", (void*)_dyld_link_module },
#endif
#ifdef DARLING
{"__dyld_get_mach_driver_fd", (void*)mach_driver_get_fd },
{"__dyld_get_mach_driver_fd", (void*)mach_driver_get_dyld_fd },
{"__dyld_am_i_ptraced", (void*)darling_am_i_ptraced },
#endif
#endif //DEPRECATED_APIS_SUPPORTED
@ -1233,7 +1233,7 @@ bool _dyld_launched_prebound()
if ( dyld::gLogAPIs )
dyld::log("%s()\n", __func__);
// ¥¥¥Êif we deprecate prebinding, we may want to consider always returning true or false here
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>if we deprecate prebinding, we may want to consider always returning true or false here
return dyld::mainExecutablePrebound();
}

1
src/external/darlingserver vendored Submodule

@ -0,0 +1 @@
Subproject commit 3bfc652c9b307ababa4f4d72fddd876704e6395b

View File

@ -21,6 +21,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_BINARY_DIR}/src/startup
${CMAKE_BINARY_DIR}/src/kernel/libsyscall
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_BINARY_DIR}/src/external/darlingserver/include
)
mig(signal/mach_exc.defs)
@ -301,16 +302,22 @@ set(emulation_sources
vchroot_userspace.c
syscalls-table.S
linux-syscall.S
${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c
)
set_source_files_properties(signal/duct_signals.c PROPERTIES COMPILE_FLAGS "-nostdinc")
set_source_files_properties(signal/sigexc.c PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/signal/exc.h)
set_source_files_properties(${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c PROPERTIES
GENERATED TRUE
COMPILE_FLAGS "-include ${CMAKE_CURRENT_SOURCE_DIR}/resources/dserver-rpc-defs.h"
)
add_darling_object_library(emulation ${emulation_sources})
add_darling_object_library(emulation_dyld ${emulation_sources})
set_target_properties(emulation_dyld PROPERTIES COMPILE_FLAGS "-ffunction-sections -DVARIANT_DYLD")
add_dependencies(emulation rtsig_h)
add_dependencies(emulation_dyld rtsig_h)
add_dependencies(emulation rtsig_h generate_dserver_rpc_wrappers)
add_dependencies(emulation_dyld rtsig_h generate_dserver_rpc_wrappers)
make_fat(emulation)
make_fat(emulation_dyld)

View File

@ -76,3 +76,6 @@ int native_dlclose(void* module)
return elfcalls()->dlclose(module);
}
const void* __dserver_socket_address(void) {
return elfcalls()->dserver_socket_address();
};

View File

@ -28,6 +28,7 @@ int __darling_thread_terminate(void* stackaddr,
void* __darling_thread_get_stack(void);
const void* __dserver_socket_address(void);
#ifdef __cplusplus
}

View File

@ -8,6 +8,7 @@ include_directories(
${CMAKE_SOURCE_DIR}/src/kernel/emulation/linux/ext
${CMAKE_SOURCE_DIR}/src/startup
${CMAKE_BINARY_DIR}/src/startup
${CMAKE_BINARY_DIR}/src/external/darlingserver/include
)
set(mach_server_client_sources

View File

@ -26,12 +26,10 @@ static int driver_fd = -1;
VISIBLE
struct elf_calls* _elfcalls;
static bool use_per_thread_driver_fd = false;
void mach_driver_init(const char** applep)
{
// DARLINGSERVER/MLDR TESTING
__simple_printf("We're being initialized...\n");
__builtin_unreachable();
#ifdef VARIANT_DYLD
if (applep != NULL)
{
@ -55,8 +53,15 @@ void mach_driver_init(const char** applep)
_libkernel_functions->dyld_func_lookup("__dyld_get_mach_driver_fd", (void**) &p);
driver_fd = (*p)();
// ask for elfcalls already set up by dyld
void* (*p2)(void);
_libkernel_functions->dyld_func_lookup("__dyld_get_elfcalls", (void**)&p2);
_elfcalls = p2();
#endif
#if 0
// If mach_driver_init() is being called in the fork child, the LKM will now
// swap out driver_fd for a new one.
if (__real_ioctl(driver_fd, NR_get_api_version, 0) != DARLING_MACH_API_VERSION)
@ -65,11 +70,17 @@ void mach_driver_init(const char** applep)
sys_write(2, msg, strlen(msg));
sys_kill(0, 6);
}
#endif
// mach_driver_init() gets called at several points in application's lifetime:
// 1) When dyld loads
// 2) When libc initializes
// 3) After forking
// this was previously done when the LKM was in-use.
// now with per-thread dserver FDs, this is more complicated to do.
// TODO: find a suitable way of doing this.
#if 0
#ifdef VARIANT_DYLD
struct rlimit lim;
if (sys_getrlimit(RLIMIT_NOFILE, &lim) == 0 && driver_fd != lim.rlim_cur)
@ -84,23 +95,45 @@ void mach_driver_init(const char** applep)
driver_fd = d;
}
#endif
#endif
}
void mach_driver_init_pthread(void) {
_os_tsd_set_direct(__TSD_DSERVER_RPC_FD, (void*)(intptr_t)driver_fd);
use_per_thread_driver_fd = true;
};
__attribute__((visibility("default")))
int lkm_call(int call_nr, void* arg)
{
return __real_ioctl(driver_fd, call_nr, arg);
__simple_printf("Something called the old LKM API (nr = %d)\n", call_nr);
__builtin_unreachable();
}
__attribute__((visibility("default")))
int lkm_call_raw(int call_nr, void* arg)
{
return __real_ioctl_raw(driver_fd, call_nr, arg);
__simple_printf("Something called the old LKM API (nr = %d)\n", call_nr);
__builtin_unreachable();
}
__attribute__((visibility("default")))
int mach_driver_get_fd(void)
int mach_driver_get_dyld_fd(void)
{
return driver_fd;
}
VISIBLE
int mach_driver_get_fd(void) {
if (use_per_thread_driver_fd) {
return (int)(intptr_t)_os_tsd_get_direct(__TSD_DSERVER_RPC_FD);
} else {
return driver_fd;
}
};
VISIBLE
void* elfcalls_get_pointer(void) {
return _elfcalls;
};

View File

@ -1,10 +1,18 @@
#ifndef _LKM_H
#define _LKM_H
#include <os/tsd.h>
// TSD slot 6 is reserved by Apple for Windows/WINE compatibility.
// Since WINE can never run under Darling, we can use that slot for our own purposes.
// Namely, we use it to store the the per-thread darlingserver socket.
#define __TSD_DSERVER_RPC_FD 6
void mach_driver_init(const char** applep);
int lkm_call(int call_nr, void* arg);
int lkm_call_raw(int call_nr, void* arg); // w/o errno translation
int mach_driver_get_fd(void);
int mach_driver_get_dyld_fd(void);
#endif

View File

@ -10,12 +10,22 @@
#include "mach_traps.h"
#include <mach/mach_init.h>
#include "../ext/mremap.h"
#include <darlingserver/rpc.h>
#include "../simple.h"
#define UNIMPLEMENTED_TRAP() { char msg[] = "Called unimplemented Mach trap: "; write(2, msg, sizeof(msg)-1); write(2, __FUNCTION__, sizeof(__FUNCTION__)-1); write(2, "\n", 1); }
mach_port_name_t mach_reply_port_impl(void)
{
return lkm_call(NR_mach_reply_port, 0);
__simple_printf("Got to mach_reply_port_impl\n");
unsigned int port_name;
if (dserver_rpc_mach_reply_port(&port_name) != 0) {
__simple_printf("mach_reply_port_impl RPC failed\n");
port_name = MACH_PORT_NULL;
}
__simple_printf("Returning from mach_reply_port_impl: %d\n", port_name);
//__simple_abort();
return port_name;
}
mach_port_name_t thread_self_trap_impl(void)
@ -649,7 +659,15 @@ kern_return_t syscall_thread_switch_impl(
mach_port_name_t task_self_trap_impl(void)
{
return lkm_call(NR_task_self_trap, 0);
__simple_printf("Got to task_self_trap_impl\n");
unsigned int port_name;
if (dserver_rpc_task_self_trap(&port_name) != 0) {
__simple_printf("task_self_trap_impl RPC failed\n");
port_name = MACH_PORT_NULL;
}
__simple_printf("Returning from task_self_trap_impl: %d\n", port_name);
//__simple_abort();
return port_name;
}
/*

View File

@ -0,0 +1,68 @@
#include "../network/recvmsg.h"
#include "../network/sendmsg.h"
#include "../network/getsockopt.h"
#include <stddef.h>
#include <sys/_types/_iovec_t.h>
#include <linux-syscalls/linux.h>
#include "../base.h"
#include "../duct_errno.h"
#include "../mach/lkm.h"
#include "../elfcalls_wrapper.h"
extern void* memcpy(void* dest, const void* src, __SIZE_TYPE__ n);
struct linux_sockaddr_un {
unsigned short int sun_family;
char sun_path[108];
};
#define dserver_rpc_hooks_msghdr_t struct linux_msghdr
#define dserver_rpc_hooks_iovec_t struct iovec
#define dserver_rpc_hooks_cmsghdr_t struct linux_cmsghdr
#define DSERVER_RPC_HOOKS_CMSG_SPACE LINUX_CMSG_SPACE
#define DSERVER_RPC_HOOKS_CMSG_FIRSTHDR(msghdr) ({ \
struct linux_msghdr* _msghdr = (msghdr); \
(_msghdr->msg_controllen >= sizeof(struct linux_cmsghdr)) ? ((struct linux_cmsghdr*)(_msghdr->msg_control)) : NULL; \
})
#define DSERVER_RPC_HOOKS_SOL_SOCKET LINUX_SOL_SOCKET
#define DSERVER_RPC_HOOKS_SCM_RIGHTS 1
#define DSERVER_RPC_HOOKS_CMSG_LEN LINUX_CMSG_LEN
#define DSERVER_RPC_HOOKS_CMSG_DATA(cmsghdr) (&(cmsghdr)->cmsg_data[0])
#define DSERVER_RPC_HOOKS_ATTRIBUTE static
#define dserver_rpc_hooks_get_pid() ((pid_t)LINUX_SYSCALL0(__NR_getpid))
#define dserver_rpc_hooks_get_tid() ((pid_t)LINUX_SYSCALL(__NR_gettid))
#define dserver_rpc_hooks_get_server_address() ((void*)__dserver_socket_address())
#define dserver_rpc_hooks_get_server_address_length() sizeof(struct linux_sockaddr_un)
#define dserver_rpc_hooks_memcpy memcpy
static long int dserver_rpc_hooks_send_message(int socket, const dserver_rpc_hooks_msghdr_t* message) {
#ifdef __NR_socketcall
return LINUX_SYSCALL(__NR_socketcall, LINUX_SYS_SENDMSG, ((long[6]) { socket, message, 0 }));
#else
return LINUX_SYSCALL(__NR_sendmsg, socket, message, 0);
#endif
};
static long int dserver_rpc_hooks_receive_message(int socket, dserver_rpc_hooks_msghdr_t* out_message) {
#ifdef __NR_socketcall
return LINUX_SYSCALL(__NR_socketcall, LINUX_SYS_RECVMSG, ((long[6]) { socket, out_message, 0 }));
#else
return LINUX_SYSCALL(__NR_recvmsg, socket, out_message, 0);
#endif
};
#define dserver_rpc_hooks_get_bad_message_status() LINUX_EBADMSG
#define dserver_rpc_hooks_get_communication_error_status() LINUX_ECOMM
#define dserver_rpc_hooks_get_broken_pipe_status() LINUX_EPIPE
#define dserver_rpc_hooks_close_fd(fd) ((int)LINUX_SYSCALL1(__NR_close, fd))
#define dserver_rpc_hooks_get_socket mach_driver_get_fd

View File

@ -7,6 +7,7 @@
#include "mach/lkm.h"
#include "signal/kill.h"
#include <sys/signal.h>
#include <darlingserver/rpc.h>
extern char* memchr(char* buf, int c, __SIZE_TYPE__ n);
@ -374,14 +375,10 @@ void __simple_printf(const char* format, ...)
__attribute__ ((visibility ("default")))
void __simple_kprintf(const char* format, ...)
{
char buffer[512];
va_list vl;
va_start(vl, format);
__simple_vsnprintf(buffer, sizeof(buffer), format, vl);
__simple_vkprintf(format, vl);
va_end(vl);
lkm_call(NR_kernel_printk, buffer);
}
__attribute__ ((visibility ("default")))
@ -410,7 +407,7 @@ void __simple_vkprintf(const char* format, va_list args)
{
char buffer[512];
__simple_vsnprintf(buffer, sizeof(buffer), format, args);
lkm_call(NR_kernel_printk, buffer);
dserver_rpc_kprintf(buffer, __simple_strlen(buffer));
}
__attribute__ ((visibility ("default")))

View File

@ -17,7 +17,7 @@ long sys_close_nocancel(int fd)
{
int ret;
if (fd == mach_driver_get_fd()) {
if (fd == mach_driver_get_dyld_fd()) {
__simple_kprintf("*** Someone tried to close the special LKM fd! ***");
return 0;
}

View File

@ -164,6 +164,11 @@ int _mach_fork_parent(void) {
#endif
#endif
#ifdef DARLING
extern void mach_driver_init(const char** applep);
extern void mach_driver_init_pthread(void);
#endif
void
#ifdef DARLING
mach_init_doit(const char** applep)
@ -205,4 +210,8 @@ mach_init_doit(void)
_init_cpu_capabilities();
_pthread_set_self(0);
#ifdef DARLING
mach_driver_init_pthread();
#endif
}

View File

@ -0,0 +1,13 @@
project(libsimple)
add_library(libsimple_darlingserver
src/lock.c
)
target_include_directories(libsimple_darlingserver PUBLIC
include
)
target_compile_definitions(libsimple_darlingserver PRIVATE
LIBSIMPLE_LINUX=1
)

4
src/libsimple/README.md Normal file
View File

@ -0,0 +1,4 @@
# libsimple
A library that provides basic functions and utilities for code that runs under Darling.
Some of these utilities are also available outside of a Darling environment (for use in darlingserver).

View File

@ -0,0 +1,14 @@
#ifndef _LIBSIMPLE_BASE_H_
#define _LIBSIMPLE_BASE_H_
#if __cplusplus
#define LIBSIMPLE_DECLARATIONS_BEGIN extern "C" {
#define LIBSIMPLE_DECLARATIONS_END }
#else
#define LIBSIMPLE_DECLARATIONS_BEGIN
#define LIBSIMPLE_DECLARATIONS_END
#endif
#define LIBSIMPLE_INLINE __attribute__((always_inline))
#endif // _LIBSIMPLE_BASE_H_

View File

@ -0,0 +1,49 @@
#ifndef _LIBSIMPLE_LOCK_H_
#define _LIBSIMPLE_LOCK_H_
#include <stdint.h>
#include "base.h"
LIBSIMPLE_DECLARATIONS_BEGIN;
//
// lock
//
typedef struct libsimple_lock {
uint32_t state;
} libsimple_lock_t;
#define LIBSIMPLE_LOCK_INITIALIZER {0}
LIBSIMPLE_INLINE
static void libsimple_lock_init(libsimple_lock_t* lock) {
lock->state = 0;
};
void libsimple_lock_lock(libsimple_lock_t* lock);
void libsimple_lock_unlock(libsimple_lock_t* lock);
//
// once
//
typedef struct libsimple_once {
uint32_t state;
} libsimple_once_t;
typedef void (*libsimple_once_callback)(void);
#define LIBSIMPLE_ONCE_INITIALIZER {0}
LIBSIMPLE_INLINE
static void libsimple_once_init(libsimple_once_t* once) {
once->state = 0;
};
void libsimple_once(libsimple_once_t* once, libsimple_once_callback callback);
LIBSIMPLE_DECLARATIONS_END;
#endif // _LIBSIMPLE_LOCK_H_

216
src/libsimple/src/lock.c Normal file
View File

@ -0,0 +1,216 @@
#include <stdatomic.h>
#include <limits.h>
#include <stdbool.h>
#include <libsimple/lock.h>
#ifndef LIBSIMPLE_LOCK_DEBUG
#define LIBSIMPLE_LOCK_DEBUG 0
#endif
#if LIBSIMPLE_LOCK_DEBUG
#define libsimple_lock_debug_internal(x, ...) libsimple_log(x "\n", ## __VA_ARGS__)
#undef LIBSIMPLE_INLINE
#define LIBSIMPLE_INLINE
#else
#define libsimple_lock_debug_internal(x, ...)
#endif
#define libsimple_lock_debug(x, ...) libsimple_lock_debug_internal("lock %p: " x, lock, ## __VA_ARGS__)
#define libsimple_once_debug(x, ...) libsimple_lock_debug_internal("once %p: " x, once, ## __VA_ARGS__)
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
struct timespec;
#if LIBSIMPLE_LINUX
#include <sys/syscall.h>
#include <unistd.h>
#endif
static int linux_futex(int* uaddr, int op, int val, const struct timespec* timeout, int* uaddr2, int val3) {
#if LIBSIMPLE_LINUX
return syscall(SYS_futex, uaddr, op, val, timeout, uaddr2, val3);
#else
#error linux_futex not implemented for this platform
#endif
};
//
// libsimple_lock
//
// simple lock implementation on top of futexes, based on https://github.com/eliben/code-for-blog/blob/master/2018/futex-basics/mutex-using-futex.cpp
//
// used to hide the use of C11 atomics from external code (e.g. C++ code, which can't use C11 atomics).
// it's *technically* undefined behavior to cast the external one to this one, but that's okay;
// we know our exact environment and it works in it
typedef struct libsimple_lock_internal {
_Atomic uint32_t state;
} libsimple_lock_internal_t;
enum libsimple_lock_state {
// the lock is unlocked (duh)
libsimple_lock_state_unlocked = 0,
// the lock is locked, but no one is waiting for it
libsimple_lock_state_locked_uncontended = 1,
// the locked is locked and there are waiters
libsimple_lock_state_locked_contended = 2,
};
LIBSIMPLE_INLINE
uint32_t cmpxchg_wrapper_u32(_Atomic uint32_t* atom, uint32_t expected, uint32_t desired) {
atomic_compare_exchange_strong_explicit(atom, &expected, desired, memory_order_acq_rel, memory_order_acquire);
return expected;
};
void libsimple_lock_lock(libsimple_lock_t* _lock) {
libsimple_lock_internal_t* lock = (libsimple_lock_internal_t*)_lock;
libsimple_lock_debug("locking");
// we do the initial exchange using `libsimple_lock_state_locked_uncontended` because
// if it was previously unlocked, it is now locked but nobody is waiting for it
uint32_t prev = cmpxchg_wrapper_u32(&lock->state, libsimple_lock_state_unlocked, libsimple_lock_state_locked_uncontended);
libsimple_lock_debug("previous state was %u", prev);
// if it was not unlocked, we need to do some waiting
if (prev != libsimple_lock_state_unlocked) {
do {
// we can skip the cmpxchg if the lock was already contended
// also, when we do the cmpxchg, we need to make sure it's still locked
// we use `libsimple_lock_state_locked_contended` because it was either uncontended (but it is now) or it was already contended
if (prev == libsimple_lock_state_locked_contended || cmpxchg_wrapper_u32(&lock->state, libsimple_lock_state_locked_uncontended, libsimple_lock_state_locked_contended) != libsimple_lock_state_unlocked) {
libsimple_lock_debug("going to wait");
// do the actual sleeping
// we expect `libsimple_lock_state_locked_contended` because we don't want to sleep if it's not contended
linux_futex((int*)&lock->state, FUTEX_WAIT, libsimple_lock_state_locked_contended, NULL, 0, 0);
libsimple_lock_debug("awoken");
}
// loop as long as the lock was not unlocked
// we use `libsimple_lock_state_locked_contended` for the same reason as before
} while ((prev = cmpxchg_wrapper_u32(&lock->state, libsimple_lock_state_unlocked, libsimple_lock_state_locked_contended)) != libsimple_lock_state_unlocked);
libsimple_lock_debug("lock acquired after sleeping");
}
libsimple_lock_debug("lock acquired");
};
void libsimple_lock_unlock(libsimple_lock_t* _lock) {
libsimple_lock_internal_t* lock = (libsimple_lock_internal_t*)_lock;
libsimple_lock_debug("unlocking");
uint32_t prev = atomic_exchange_explicit(&lock->state, libsimple_lock_state_unlocked, memory_order_acq_rel);
libsimple_lock_debug("previous state was %u", prev);
// if it was previously contended, then we need to wake someone up
if (prev == libsimple_lock_state_locked_contended) {
libsimple_lock_debug("waking someone up");
linux_futex((int*)&lock->state, FUTEX_WAKE, 1, NULL, 0, 0);
}
};
//
// libsimple_once
//
// see https://github.com/bugaevc/serenity/blob/1fc3f2ba84d9c75c6c30e62014dabe4fcd62aae1/Libraries/LibPthread/pthread_once.cpp
//
typedef struct libsimple_once_internal {
_Atomic uint32_t state;
} libsimple_once_internal_t;
enum libsimple_once_state {
// the once hasn't been triggered yet
libsimple_once_state_untriggered = 0,
// the once has been triggered, but no one is waiting for it
libsimple_once_state_triggered_uncontended = 1,
// the once has been triggered and there are waiters
libsimple_once_state_triggered_contended = 2,
// the once has been triggered and has completed execution
libsimple_once_state_completed = 3,
};
void libsimple_once(libsimple_once_t* _once, libsimple_once_callback callback) {
libsimple_once_internal_t* once = (libsimple_once_internal_t*)_once;
libsimple_once_debug("evaluating...");
uint32_t prev = libsimple_once_state_untriggered;
bool exchanged = atomic_compare_exchange_strong_explicit(&once->state, &prev, libsimple_once_state_triggered_uncontended, memory_order_acq_rel, memory_order_acquire);
libsimple_once_debug("previous state was %u", prev);
if (exchanged) {
libsimple_once_debug("performing callback...");
// we had `libsimple_once_state_untriggered` and now we have `libsimple_once_state_triggered_uncontended`, so let's do the callback
callback();
libsimple_once_debug("callback done; updating state...");
// now let's update the state to tell everyone we're done
prev = atomic_exchange_explicit(&once->state, libsimple_once_state_completed, memory_order_acq_rel);
libsimple_once_debug("previous state was %u", prev);
switch (prev) {
// no one was waiting, so we have no one to wake up
case libsimple_once_state_triggered_uncontended: break;
case libsimple_once_state_triggered_contended: {
libsimple_once_debug("waking up all waiters...");
// otherwise, we have to wake someone up
linux_futex((int*)&once->state, FUTEX_WAKE, INT_MAX, NULL, 0, 0);
} break;
}
return;
}
libsimple_once_debug("someone else is performing the callback");
while (true) {
switch (prev) {
case libsimple_once_state_completed: {
// we're done
libsimple_once_debug("callback completed by someone else; returning...");
return;
}; // not reached
case libsimple_once_state_triggered_uncontended: {
// somebody is already perfoming the callback;
// now we're waiting, so let's update the state for that
libsimple_once_debug("someone is already performing the callback with no waiters; becoming first waiter...");
exchanged = atomic_compare_exchange_strong_explicit(&once->state, &prev, libsimple_once_state_triggered_contended, memory_order_acq_rel, memory_order_acquire);
if (!exchanged) {
libsimple_once_debug("state changed from uncontended; re-evaluating...");
// something changed, let's re-evaluate
continue;
}
prev = libsimple_once_state_triggered_contended;
}; // fall through
case libsimple_once_state_triggered_contended: {
// somebody is already performing the callback and there are already waiters;
// let's wait
libsimple_once_debug("someone is already performing the callback with waiters; going to wait...");
linux_futex((int*)&once->state, FUTEX_WAIT, prev, NULL, 0, 0);
libsimple_once_debug("woken up");
// we got woken up, but that may have been spurious;
// let's loop again to re-evaluate
prev = atomic_load_explicit(&once->state, memory_order_acquire);
libsimple_once_debug("previous state was %u; going to re-evaluate...", prev);
} break;
}
}
};

View File

@ -18,7 +18,7 @@ add_executable(darling darling.c)
target_link_libraries(darling -lutil)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/src/darlingserver/include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/src/external/darlingserver/include)
install(TARGETS darling DESTINATION bin
PERMISSIONS

View File

@ -4,12 +4,12 @@ cmake_minimum_required(VERSION 3.10)
include_directories(include)
set_source_files_properties(${CMAKE_BINARY_DIR}/src/darlingserver/src/rpc.c PROPERTIES
set_source_files_properties(${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c PROPERTIES
GENERATED TRUE
)
add_library(mldr_dserver_rpc ${CMAKE_BINARY_DIR}/src/darlingserver/src/rpc.c)
add_library(mldr32_dserver_rpc ${CMAKE_BINARY_DIR}/src/darlingserver/src/rpc.c)
add_library(mldr_dserver_rpc ${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c)
add_library(mldr32_dserver_rpc ${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c)
add_dependencies(mldr_dserver_rpc generate_dserver_rpc_wrappers)
add_dependencies(mldr32_dserver_rpc generate_dserver_rpc_wrappers)

View File

@ -480,15 +480,19 @@ static void setup_space(struct load_results* lr, bool is_64_bit) {
}
static char vchroot_buffer[4096];
size_t vchroot_path_length = 0;
uint64_t vchroot_path_length = 0;
if (dserver_rpc_vchroot_path(vchroot_buffer, sizeof(vchroot_buffer), &vchroot_path_length) < 0) {
fprintf(stderr, "Failed to retrieve vchroot path from darlingserver\n");
int code = dserver_rpc_vchroot_path(vchroot_buffer, sizeof(vchroot_buffer), &vchroot_path_length);
if (code < 0) {
fprintf(stderr, "Failed to retrieve vchroot path from darlingserver: %d\n", code);
exit(1);
}
if (vchroot_path_length > 0) {
lr->root_path = vchroot_buffer;
} else if (vchroot_path_length >= sizeof(vchroot_buffer)) {
fprintf(stderr, "Vchroot path is too large for buffer\n");
exit(1);
}
};

View File

@ -3,7 +3,6 @@
#include <stdbool.h>
#include "lock.h"
#include <darling/emulation/simple.h>
#ifndef XTRACE_LOCK_DEBUG
#define XTRACE_LOCK_DEBUG 0