userspace: Implement LibClipboard and ClipboardServer
programs can now connect to the clipboard server using libclipboard and get and set the clipboard of the current user
This commit is contained in:
parent
d60f12d3b8
commit
291f298d19
|
|
@ -1,6 +1,7 @@
|
||||||
set(USERSPACE_LIBRARIES
|
set(USERSPACE_LIBRARIES
|
||||||
LibAudio
|
LibAudio
|
||||||
LibC
|
LibC
|
||||||
|
LibClipboard
|
||||||
LibDEFLATE
|
LibDEFLATE
|
||||||
LibDL
|
LibDL
|
||||||
LibELF
|
LibELF
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
set(LIBCLIPBOARD_SOURCES
|
||||||
|
Clipboard.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(libclipboard ${LIBCLIPBOARD_SOURCES})
|
||||||
|
banan_link_library(libclipboard ban)
|
||||||
|
banan_link_library(libclipboard libc)
|
||||||
|
|
||||||
|
banan_install_headers(libclipboard)
|
||||||
|
install(TARGETS libclipboard OPTIONAL)
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
#include <LibClipboard/Clipboard.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace LibClipboard
|
||||||
|
{
|
||||||
|
|
||||||
|
static int s_server_fd = -1;
|
||||||
|
|
||||||
|
static BAN::ErrorOr<void> send_credentials(int fd)
|
||||||
|
{
|
||||||
|
char dummy = '\0';
|
||||||
|
iovec iovec {
|
||||||
|
.iov_base = &dummy,
|
||||||
|
.iov_len = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr size_t control_size = CMSG_LEN(sizeof(ucred));
|
||||||
|
uint8_t control_buffer[control_size];
|
||||||
|
|
||||||
|
cmsghdr* control = reinterpret_cast<cmsghdr*>(control_buffer);
|
||||||
|
|
||||||
|
*control = {
|
||||||
|
.cmsg_len = control_size,
|
||||||
|
.cmsg_level = SOL_SOCKET,
|
||||||
|
.cmsg_type = SCM_CREDENTIALS,
|
||||||
|
};
|
||||||
|
|
||||||
|
*reinterpret_cast<ucred*>(CMSG_DATA(control)) = {
|
||||||
|
.pid = getpid(),
|
||||||
|
.uid = getuid(),
|
||||||
|
.gid = getgid(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const msghdr message {
|
||||||
|
.msg_name = nullptr,
|
||||||
|
.msg_namelen = 0,
|
||||||
|
.msg_iov = &iovec,
|
||||||
|
.msg_iovlen = 1,
|
||||||
|
.msg_control = control,
|
||||||
|
.msg_controllen = control_size,
|
||||||
|
.msg_flags = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sendmsg(fd, &message, 0) < 0)
|
||||||
|
return BAN::Error::from_errno(errno);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static BAN::ErrorOr<void> ensure_connected()
|
||||||
|
{
|
||||||
|
if (s_server_fd != -1)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const int sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (sock == -1)
|
||||||
|
return BAN::Error::from_errno(errno);
|
||||||
|
|
||||||
|
sockaddr_un server_addr;
|
||||||
|
server_addr.sun_family = AF_UNIX;
|
||||||
|
strcpy(server_addr.sun_path, s_clipboard_server_socket.data());
|
||||||
|
|
||||||
|
if (connect(sock, reinterpret_cast<const sockaddr*>(&server_addr), sizeof(server_addr)) == -1)
|
||||||
|
{
|
||||||
|
close(sock);
|
||||||
|
return BAN::Error::from_errno(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto ret = send_credentials(sock); ret.is_error())
|
||||||
|
{
|
||||||
|
close(sock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_server_fd = sock;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static BAN::ErrorOr<void> recv_sized(void* data, size_t size)
|
||||||
|
{
|
||||||
|
ASSERT(s_server_fd != -1);
|
||||||
|
|
||||||
|
uint8_t* u8_data = static_cast<uint8_t*>(data);
|
||||||
|
|
||||||
|
size_t total_recv = 0;
|
||||||
|
while (total_recv < size)
|
||||||
|
{
|
||||||
|
const ssize_t nrecv = recv(s_server_fd, u8_data + total_recv, size - total_recv, 0);
|
||||||
|
if (nrecv <= 0)
|
||||||
|
{
|
||||||
|
const int error = nrecv ? errno : ECONNRESET;
|
||||||
|
close(s_server_fd);
|
||||||
|
s_server_fd = -1;
|
||||||
|
return BAN::Error::from_errno(error);
|
||||||
|
}
|
||||||
|
total_recv += nrecv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static BAN::ErrorOr<void> send_sized(const void* data, size_t size)
|
||||||
|
{
|
||||||
|
ASSERT(s_server_fd != -1);
|
||||||
|
|
||||||
|
const uint8_t* u8_data = static_cast<const uint8_t*>(data);
|
||||||
|
|
||||||
|
size_t total_sent = 0;
|
||||||
|
while (total_sent < size)
|
||||||
|
{
|
||||||
|
const ssize_t nsend = send(s_server_fd, u8_data + total_sent, size - total_sent, 0);
|
||||||
|
if (nsend <= 0)
|
||||||
|
{
|
||||||
|
const int error = nsend ? errno : ECONNRESET;
|
||||||
|
close(s_server_fd);
|
||||||
|
s_server_fd = -1;
|
||||||
|
return BAN::Error::from_errno(error);
|
||||||
|
}
|
||||||
|
total_sent += nsend;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BAN::ErrorOr<Clipboard::Info> Clipboard::get_clipboard()
|
||||||
|
{
|
||||||
|
TRY(ensure_connected());
|
||||||
|
|
||||||
|
{
|
||||||
|
DataType type = DataType::__get;
|
||||||
|
TRY(send_sized(&type, sizeof(type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Info info;
|
||||||
|
TRY(recv_sized(&info.type, sizeof(info.type)));
|
||||||
|
|
||||||
|
switch (info.type)
|
||||||
|
{
|
||||||
|
case DataType::__get:
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
case DataType::None:
|
||||||
|
break;
|
||||||
|
case DataType::Text:
|
||||||
|
size_t data_size;
|
||||||
|
TRY(recv_sized(&data_size, sizeof(data_size)));
|
||||||
|
|
||||||
|
TRY(info.data.resize(data_size));
|
||||||
|
TRY(recv_sized(info.data.data(), data_size));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
BAN::ErrorOr<void> Clipboard::set_clipboard(DataType type, BAN::Span<const uint8_t> data)
|
||||||
|
{
|
||||||
|
ASSERT(type != DataType::__get);
|
||||||
|
|
||||||
|
TRY(ensure_connected());
|
||||||
|
|
||||||
|
TRY(send_sized(&type, sizeof(type)));
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case DataType::__get:
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
case DataType::None:
|
||||||
|
break;
|
||||||
|
case DataType::Text:
|
||||||
|
const size_t size = data.size();
|
||||||
|
TRY(send_sized(&size, sizeof(size)));
|
||||||
|
TRY(send_sized(data.data(), size));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BAN::ErrorOr<BAN::String> Clipboard::get_clipboard_text()
|
||||||
|
{
|
||||||
|
auto info = TRY(get_clipboard());
|
||||||
|
if (info.type != DataType::Text)
|
||||||
|
return BAN::String {};
|
||||||
|
|
||||||
|
BAN::String string;
|
||||||
|
TRY(string.resize(info.data.size()));
|
||||||
|
memcpy(string.data(), info.data.data(), info.data.size());
|
||||||
|
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
BAN::ErrorOr<void> Clipboard::set_clipboard_text(BAN::StringView string)
|
||||||
|
{
|
||||||
|
return set_clipboard(DataType::Text, { reinterpret_cast<const uint8_t*>(string.data()), string.size() });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <BAN/Span.h>
|
||||||
|
#include <BAN/String.h>
|
||||||
|
#include <BAN/StringView.h>
|
||||||
|
#include <BAN/Vector.h>
|
||||||
|
|
||||||
|
namespace LibClipboard
|
||||||
|
{
|
||||||
|
|
||||||
|
static constexpr BAN::StringView s_clipboard_server_socket = "/tmp/clipboard-server.socket"_sv;
|
||||||
|
|
||||||
|
class Clipboard
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class DataType : uint32_t
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Text,
|
||||||
|
__get = UINT32_MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Info
|
||||||
|
{
|
||||||
|
DataType type = DataType::None;
|
||||||
|
BAN::Vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
static BAN::ErrorOr<Info> get_clipboard();
|
||||||
|
static BAN::ErrorOr<void> set_clipboard(DataType type, BAN::Span<const uint8_t> data);
|
||||||
|
|
||||||
|
static BAN::ErrorOr<BAN::String> get_clipboard_text();
|
||||||
|
static BAN::ErrorOr<void> set_clipboard_text(BAN::StringView string);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ set(USERSPACE_PROGRAMS
|
||||||
cat-mmap
|
cat-mmap
|
||||||
chmod
|
chmod
|
||||||
chown
|
chown
|
||||||
|
ClipboardServer
|
||||||
cp
|
cp
|
||||||
dd
|
dd
|
||||||
dhcp-client
|
dhcp-client
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
set(SOURCES
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(ClipboardServer ${SOURCES})
|
||||||
|
banan_include_headers(ClipboardServer libclipboard)
|
||||||
|
banan_link_library(ClipboardServer ban)
|
||||||
|
banan_link_library(ClipboardServer libc)
|
||||||
|
|
||||||
|
install(TARGETS ClipboardServer OPTIONAL)
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
#include <BAN/HashMap.h>
|
||||||
|
#include <BAN/Vector.h>
|
||||||
|
|
||||||
|
#include <LibClipboard/Clipboard.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static uid_t receive_credentials(int fd)
|
||||||
|
{
|
||||||
|
char dummy = '\0';
|
||||||
|
iovec iovec {
|
||||||
|
.iov_base = &dummy,
|
||||||
|
.iov_len = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr size_t control_size = CMSG_LEN(sizeof(ucred));
|
||||||
|
uint8_t control_buffer[control_size];
|
||||||
|
|
||||||
|
msghdr message {
|
||||||
|
.msg_name = nullptr,
|
||||||
|
.msg_namelen = 0,
|
||||||
|
.msg_iov = &iovec,
|
||||||
|
.msg_iovlen = 1,
|
||||||
|
.msg_control = control_buffer,
|
||||||
|
.msg_controllen = control_size,
|
||||||
|
.msg_flags = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ssize_t nrecv = recvmsg(fd, &message, 0);
|
||||||
|
if (nrecv <= 0)
|
||||||
|
{
|
||||||
|
if (nrecv < 0)
|
||||||
|
dwarnln("recvmsg: {}", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* cheader = CMSG_FIRSTHDR(&message); cheader; cheader = CMSG_NXTHDR(&message, cheader))
|
||||||
|
{
|
||||||
|
if (cheader->cmsg_level != SOL_SOCKET)
|
||||||
|
continue;
|
||||||
|
if (cheader->cmsg_type != SCM_CREDENTIALS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto* ucred = reinterpret_cast<const struct ucred*>(CMSG_DATA(cheader));
|
||||||
|
if (ucred->uid < 0)
|
||||||
|
{
|
||||||
|
dwarnln("got uid {} from SCM_CREDENTIALS");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ucred->uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
dwarnln("no credentials in client's first message");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool recv_sized(int fd, void* data, size_t size)
|
||||||
|
{
|
||||||
|
uint8_t* u8_data = static_cast<uint8_t*>(data);
|
||||||
|
|
||||||
|
size_t total_recv = 0;
|
||||||
|
while (total_recv < size)
|
||||||
|
{
|
||||||
|
const ssize_t nrecv = recv(fd, u8_data + total_recv, size - total_recv, 0);
|
||||||
|
if (nrecv <= 0)
|
||||||
|
{
|
||||||
|
const int error = nrecv ? errno : ECONNRESET;
|
||||||
|
dwarnln("recv: {}", strerror(error));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
total_recv += nrecv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool send_sized(int fd, const void* data, size_t size)
|
||||||
|
{
|
||||||
|
const uint8_t* u8_data = static_cast<const uint8_t*>(data);
|
||||||
|
|
||||||
|
size_t total_sent = 0;
|
||||||
|
while (total_sent < size)
|
||||||
|
{
|
||||||
|
const ssize_t nsend = send(fd, u8_data + total_sent, size - total_sent, 0);
|
||||||
|
if (nsend <= 0)
|
||||||
|
{
|
||||||
|
const int error = nsend ? errno : ECONNRESET;
|
||||||
|
dwarnln("send: {}", strerror(error));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
total_sent += nsend;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
using namespace LibClipboard;
|
||||||
|
|
||||||
|
int server_sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (server_sock == -1)
|
||||||
|
{
|
||||||
|
dwarnln("socket: {}", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_un server_addr;
|
||||||
|
server_addr.sun_family = AF_UNIX;
|
||||||
|
strcpy(server_addr.sun_path, LibClipboard::s_clipboard_server_socket.data());
|
||||||
|
if (bind(server_sock, reinterpret_cast<sockaddr*>(&server_addr), sizeof(server_addr)) == -1)
|
||||||
|
{
|
||||||
|
dwarnln("bind: {}", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chmod(LibClipboard::s_clipboard_server_socket.data(), 0777) == -1)
|
||||||
|
dwarnln("chmod: {}", strerror(errno));
|
||||||
|
|
||||||
|
if (listen(server_sock, SOMAXCONN) == -1)
|
||||||
|
{
|
||||||
|
dwarnln("listen: {}", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Client
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
uid_t uid = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
BAN::Vector<Client> clients;
|
||||||
|
BAN::HashMap<uid_t, Clipboard::Info> clipboards;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
|
||||||
|
int max_fd = server_sock;
|
||||||
|
FD_SET(server_sock, &fds);
|
||||||
|
|
||||||
|
for (const auto& client : clients)
|
||||||
|
{
|
||||||
|
FD_SET(client.fd, &fds);
|
||||||
|
max_fd = BAN::Math::max(client.fd, max_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (select(max_fd + 1, &fds, nullptr, nullptr, nullptr) == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (FD_ISSET(server_sock, &fds))
|
||||||
|
{
|
||||||
|
const int client = accept(server_sock, nullptr, nullptr);
|
||||||
|
if (client == -1)
|
||||||
|
dwarnln("accept: {}", strerror(errno));
|
||||||
|
else if (clients.emplace_back(client).is_error())
|
||||||
|
{
|
||||||
|
dwarnln("failed to allocate space for new client");
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < clients.size(); i++)
|
||||||
|
{
|
||||||
|
auto& client = clients[i];
|
||||||
|
if (!FD_ISSET(client.fd, &fds))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool closed = false;
|
||||||
|
|
||||||
|
if (client.uid == -1)
|
||||||
|
{
|
||||||
|
client.uid = receive_credentials(client.fd);
|
||||||
|
if (client.uid == -1)
|
||||||
|
closed = true;
|
||||||
|
else if (!clipboards.contains(client.uid) && clipboards.emplace(client.uid).is_error())
|
||||||
|
{
|
||||||
|
dwarnln("failed to allocate clipboard for {}", client.fd);
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Clipboard::DataType data_type;
|
||||||
|
|
||||||
|
auto& clipboard = clipboards[client.uid];
|
||||||
|
|
||||||
|
if (!recv_sized(client.fd, &data_type, sizeof(data_type)))
|
||||||
|
closed = true;
|
||||||
|
else switch (data_type)
|
||||||
|
{
|
||||||
|
case Clipboard::DataType::__get:
|
||||||
|
{
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
const auto data_type = clipboard.type;
|
||||||
|
if (!send_sized(client.fd, &data_type, sizeof(data_type)))
|
||||||
|
break;
|
||||||
|
|
||||||
|
const auto data_size = clipboard.data.size();
|
||||||
|
if (!send_sized(client.fd, &data_size, sizeof(data_size)))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!send_sized(client.fd, clipboard.data.data(), data_size))
|
||||||
|
break;
|
||||||
|
|
||||||
|
closed = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Clipboard::DataType::None:
|
||||||
|
clipboard = {
|
||||||
|
.type = data_type,
|
||||||
|
.data = {},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case Clipboard::DataType::Text:
|
||||||
|
{
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
// FIXME: client can hang the server if it doesn't
|
||||||
|
// send the actual clipboard data...
|
||||||
|
|
||||||
|
size_t data_size;
|
||||||
|
if (!recv_sized(client.fd, &data_size, sizeof(data_size)))
|
||||||
|
break;
|
||||||
|
|
||||||
|
BAN::Vector<uint8_t> new_clipboard;
|
||||||
|
if (new_clipboard.resize(data_size).is_error())
|
||||||
|
{
|
||||||
|
dwarnln("failed to allocate {} bytes for clipboard", data_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recv_sized(client.fd, new_clipboard.data(), data_size))
|
||||||
|
break;
|
||||||
|
|
||||||
|
clipboard = {
|
||||||
|
.type = data_type,
|
||||||
|
.data = BAN::move(new_clipboard),
|
||||||
|
};
|
||||||
|
|
||||||
|
closed = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
dwarnln("unexpected data type {}", static_cast<uint32_t>(data_type));
|
||||||
|
closed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closed)
|
||||||
|
{
|
||||||
|
close(client.fd);
|
||||||
|
clients.remove(i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue