Skip to content
Commits on Source (24)
variables:
UPSTREAM_REPO: mstoeckl/waypipe
DEBIAN_TAG: '2019-06-25.1'
DEBIAN_TAG: '2019-06-25.1'
DEBIAN_VERSION: buster
DEBIAN_EXEC: 'bash .gitlab-ci/debian-install.sh'
DEBIAN_CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG
DEBIAN32_TAG: 'deb32-2019-07-04.2'
DEBIAN32_VERSION: buster
DEBIAN32_EXEC: 'bash .gitlab-ci/debian32-install.sh'
DEBIAN32_CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/debian/$DEBIAN32_VERSION:$DEBIAN32_TAG
include:
- project: 'wayland/ci-templates'
ref: 96912c7331cbc6da41fbf22c4217aa541176f063
file: '/templates/debian.yml'
- project: 'mstoeckl/waypipe'
file: '/.gitlab-ci/debian32.yml'
ref: master
stages:
- container_prep
- build
container_prep:
debian_container_prep:
extends: .debian@container-ifnot-exists
stage: container_prep
build:
debian32_container_prep:
extends: .debian32@container-ifnot-exists
stage: container_prep
.build-debian-all:
stage: build
image: $DEBIAN_CONTAINER_IMAGE
before_script:
- export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)"
- export BUILD_ID="$CI_COMMIT_SHA-$CI_JOB_ID"
- export PREFIX="$(pwd)/p-$BUILD_ID"
- export BUILDDIR="$(pwd)/b-$BUILD_ID"
- mkdir "$BUILDDIR" "$PREFIX"
script:
- export PATH=~/.local/bin:$PATH
- cd "$BUILDDIR"
......@@ -41,4 +47,29 @@ build:
when: always
paths:
- b-*/meson-logs
- b-*/test
- p-*
debian-build:
before_script:
- export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)"
- export BUILD_ID="$CI_COMMIT_SHA-$CI_JOB_ID"
- export PREFIX="$(pwd)/p-$BUILD_ID"
- export BUILDDIR="$(pwd)/b-$BUILD_ID"
- mkdir "$BUILDDIR" "$PREFIX"
- export CC=gcc
extends: .build-debian-all
image: $DEBIAN_CONTAINER_IMAGE
debian32-build:
before_script:
- export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)"
- export BUILD_ID="$CI_COMMIT_SHA-$CI_JOB_ID"
- export PREFIX="$(pwd)/p-$BUILD_ID"
- export BUILDDIR="$(pwd)/b-$BUILD_ID"
- mkdir "$BUILDDIR" "$PREFIX"
- export CC=/usr/bin/gcc-8
- export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig/
extends: .build-debian-all
image: $DEBIAN32_CONTAINER_IMAGE
#!/bin/bash
set -o xtrace
set -e -o xtrace
apt-get update
apt-get -y --no-install-recommends install \
......
#!/bin/bash
set -e -o xtrace
dpkg --print-foreign-architectures
dpkg --add-architecture i386
apt-get update
# Actual package dependencies
apt-get -y --no-install-recommends install wayland-protocols:i386 pkg-config:i386 libwayland-dev:i386 libgbm-dev:i386 liblz4-dev:i386 libzstd-dev:i386 libavcodec-dev:i386 libavutil-dev:i386 libswscale-dev:i386 weston:i386 libdrm-dev:i386
apt-get -y --no-install-recommends install gcc-8-multilib:i386 gcc-8:i386 make:i386
# Build scripts, architecture doesn't matter
apt-get -y --no-install-recommends install git python3-pip python3-wheel python3-setuptools ninja-build scdoc
pip3 install --user git+https://github.com/mesonbuild/meson.git@0.47
# vim: set expandtab shiftwidth=2 tabstop=8 textwidth=0:
# This template will create a debian image based on the following variables:
#
# - DEBIAN_VERSION: the debian version (stretch, sid, etc...)
# - DEBIAN_DEBS: if set, list of packages that needs to be installed
# - DEBIAN_EXEC: if set, this command will be run once the packages have
# been installed
# - UPSTREAM_REPO: the upstream project on this gitlab instance where we might
# find the given tag (for example: `wayland/weston`)
# - DEBIAN_TAG: tag to copy the image from the upstream registry. If the
# tag does not exist, create a new build and tag it
#
# The resulting image will be pushed in the local registry, under:
# $CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG
#
# Two flavors of templates are available:
# - `.debian32@container-build`: this will force rebuild a new container
# and tag it with $DEBIAN_TAG without checks
# - `.debian32@container-ifnot-exists`: this will rebuild a new container
# only if $DEBIAN_TAG is not available in the local registry or
# in the $UPSTREAM_REPO registry
# we can not reuse exported variables in after_script,
# so have a common definition
.debian32_vars: &distro_vars |
# exporting templates variables
# https://gitlab.com/gitlab-com/support-forum/issues/4349
export BUILDAH_FORMAT=docker
# The '32' version should run multilib
export DISTRO=debian
export DISTRO_TAG=$DEBIAN32_TAG
export DISTRO_VERSION=$DEBIAN32_VERSION
export DISTRO_EXEC=$DEBIAN32_EXEC
# Do not use this template directly, you can not reuse the produced image
# as it is tagged with $CI_JOB_ID
..debian32@container-template:
image: $CI_REGISTRY/wayland/ci-templates/buildah:latest
stage: build
before_script:
# log in to the registry
- podman login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- *distro_vars
script:
- *distro_vars
- echo Building $DISTRO/$DISTRO_VERSION:$DISTRO_TAG from $DISTRO:$DISTRO_VERSION
# initial set up: take the base image, update it and install the packages
- buildcntr=$(buildah from $DISTRO:$DISTRO_VERSION)
- buildmnt=$(buildah mount $buildcntr)
- buildah run $buildcntr cat /etc/apt/sources.list
- echo 'path-exclude=/usr/share/doc/*' > $buildmnt/etc/dpkg/dpkg.cfg.d/99-exclude-cruft
- echo 'path-exclude=/usr/share/locale/*' >> $buildmnt/etc/dpkg/dpkg.cfg.d/99-exclude-cruft
- echo 'path-exclude=/usr/share/man/*' >> $buildmnt/etc/dpkg/dpkg.cfg.d/99-exclude-cruft
- echo 'APT::Install-Recommends "false";' > $buildmnt/etc/apt/apt.conf
- echo '#!/bin/sh' > $buildmnt/usr/sbin/policy-rc.d
- echo 'exit 101' >> $buildmnt/usr/sbin/policy-rc.d
- chmod +x $buildmnt/usr/sbin/policy-rc.d
- buildah run $buildcntr env DEBIAN_FRONTEND=noninteractive apt-get update
- buildah run $buildcntr env DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade
- if [[ x"$DEBIAN_DEBS" != x"" ]] ;
then
buildah run $buildcntr env DEBIAN_FRONTEND=noninteractive apt-get -y install $DEBIAN_DEBS ;
fi
# check if there is an optional post install script and run it
- if [[ x"$DISTRO_EXEC" != x"" ]] ;
then
echo Running $DISTRO_EXEC ;
set -x ;
mkdir $buildmnt/tmp/clone ;
pushd $buildmnt/tmp/clone ;
git init ;
git remote add origin $CI_REPOSITORY_URL ;
git fetch --depth 1 origin $CI_COMMIT_SHA ;
git checkout FETCH_HEAD > /dev/null;
buildah config --workingdir /tmp/clone $buildcntr ;
buildah run $buildcntr bash -c "set -x ; $DISTRO_EXEC" ;
popd ;
rm -rf $buildmnt/tmp/clone ;
set +x ;
fi
# do not store the packages database, it's pointless
- buildah run $buildcntr env DEBIAN_FRONTEND=noninteractive apt-get clean
- rm -f $buildmnt/var/lib/apt/lists/*.lz4
# set up the working directory
- buildah config --workingdir /app $buildcntr
# umount the container, not required, but, heh
- buildah unmount $buildcntr
# tag the current container
- buildah commit $buildcntr $CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$CI_JOB_ID
# clean up the working container
- buildah rm $buildcntr
# push the container image to the registry
# There is a bug when pushing 2 tags in the same repo with the same base:
# this may fail. Just retry it after.
- podman push $CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$CI_JOB_ID || true
- sleep 2
- podman push $CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$CI_JOB_ID
# mark the current stage as successed, to get the result in after_script
- touch .success
.debian32@container-build:
extends: ..debian32@container-template
after_script:
# if we did not build, or if there was a failure, exit
# (the exit status does not matter here)
- if [[ ! -e .success ]] ;
then
exit 0;
fi
- *distro_vars
- skopeo copy docker://$CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$CI_JOB_ID
docker://$CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$DISTRO_TAG
.debian32@container-ifnot-exists:
extends: .debian32@container-build
before_script:
# log in to the registry
- podman login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- *distro_vars
# check if our image is already in the current registry
- skopeo inspect docker://$CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$DISTRO_TAG > /dev/null && exit 0 || true
# copy the original image into the current project registry namespace
- skopeo copy docker://$CI_REGISTRY/$UPSTREAM_REPO/$DISTRO/$DISTRO_VERSION:$DISTRO_TAG
docker://$CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$DISTRO_TAG && exit 0 || true
......@@ -41,12 +41,11 @@ Build with meson[0]. A typical incantation is
Requirements:
* libffi
* meson (build, >= 0.47. and its dependencies `ninja` and `pkg-config`)
* wayland (build, >= 1.15, to support absolute paths in WAYLAND_DISPLAY)
* meson (build, >= 0.47. with dependencies `ninja`, `pkg-config`, `python3`)
* wayland (build, >= 1.10 for the `wl_surface::damage_buffer` request)
* wayland-protocols (build, >= 1.12, for the xdg-shell protocol, and others)
* libzstd (optional)
* liblz4 (optional)
* libzstd (optional, >= 1.4.0)
* libgbm (optional, to support programs using OpenGL via DMABUFs)
* libdrm (optional, same as for libgbm)
* ffmpeg (optional, >=3.1, needs avcodec/avutil/swscale for lossy video encoding)
......
......@@ -2,5 +2,6 @@
clang-format -style=file --assume-filename=C -i \
util.h \
waypipe.c server.c handlers.c client.c util.c parsing.c dmabuf.c shadow.c mainloop.c interval.c video.c \
test/diff_roundtrip.c test/damage_merge.c test/fd_mirror.c test/wire_parse.c
black -q test/headless.py test/startup_failure.py
test/diff_roundtrip.c test/damage_merge.c test/fd_mirror.c test/wire_parse.c test/fuzz_hook.c
black -q test/headless.py test/startup_failure.py \
protocols/symgen.py
......@@ -169,7 +169,7 @@ static long get_dmabuf_fd_size(int fd)
strerror(errno));
return -1;
}
if (lseek(fd, SEEK_SET, 0) == -1) {
if (lseek(fd, 0, SEEK_SET) == -1) {
wp_log(WP_ERROR, "Failed to reset dmabuf offset with lseek: %s",
strerror(errno));
return -1;
......@@ -283,6 +283,11 @@ struct gbm_bo *make_dmabuf(struct render_data *rd, const char *data,
/* Set modifiers to linear, the most likely/portable format */
bo = gbm_bo_create(rd->dev, width, height, format,
GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING);
if (!bo) {
wp_log(WP_ERROR, "Failed to make dmabuf: %s",
strerror(errno));
return NULL;
}
} else {
uint64_t modifiers[2] = {info->modifier, GBM_BO_USE_RENDERING};
// assuming the format is a very standard one which can be
......@@ -304,6 +309,12 @@ struct gbm_bo *make_dmabuf(struct render_data *rd, const char *data,
*/
bo = gbm_bo_create_with_modifiers(rd->dev, info->width,
info->height, info->format, modifiers, 2);
if (!bo) {
wp_log(WP_ERROR,
"Failed to make dmabuf (with modifier %lx): %s",
info->modifier, strerror(errno));
return NULL;
}
int tfd = gbm_bo_get_fd(bo);
long csize = get_dmabuf_fd_size(tfd);
close(tfd);
......@@ -337,10 +348,6 @@ struct gbm_bo *make_dmabuf(struct render_data *rd, const char *data,
}
}
}
if (!bo) {
wp_log(WP_ERROR, "Failed to make dmabuf: %s", strerror(errno));
return NULL;
}
void *handle = NULL;
// unfortunately, there is no easy way to estimate the writeable region
......
This diff is collapsed.
......@@ -128,7 +128,9 @@ void merge_mergesort(const int old_count, struct interval *old_list,
for (int jn = 0; jn < new_count; jn++) {
struct ext_interval e = new_list[jn];
if (e.width == 0 || e.rep == 0) {
/* ignore invalid intervals -- also, if e.start is close to
* INT32_MIN, the stream merge breaks */
if (e.width <= 0 || e.rep <= 0 || e.start < 0) {
continue;
}
/* To limit CPU time, if it is very likely that an interval
......@@ -138,6 +140,12 @@ void merge_mergesort(const int old_count, struct interval *old_list,
bool force_combine = (absorbed > 10000) ||
10 * remaining < src_count;
long end = e.start + e.stride * (long)(e.rep - 1) + e.width;
if (end >= INT32_MAX) {
/* overflow protection */
e.width = INT32_MAX - 1 - e.start;
e.rep = 1;
}
/* Remove internal gaps are smaller than the margin and hence
* would need to be merged away anyway. */
if (e.width > e.stride - merge_margin || force_combine) {
......
This diff is collapsed.
......@@ -8,7 +8,7 @@ project(
'warning_level=3',
'werror=true',
],
version: '0.2.0',
version: '0.3.0',
)
cc = meson.get_compiler('c')
......@@ -38,16 +38,11 @@ else
)
endif
wayland_client = dependency('wayland-client', version: '>=1.15') # scanner headers
wayland_server = dependency('wayland-server', version: '>=1.15') # scanner headers
wayland_scanner = dependency('wayland-scanner', native: true, version: '>=1.15') # public-code
wayland_protos = dependency('wayland-protocols', version: '>=1.12') # xdg-shell
libgbm = dependency('gbm', required: get_option('with_dmabuf'))
libdrm = dependency('libdrm', required: get_option('with_dmabuf'))
if libgbm.found() and libdrm.found()
add_project_arguments('-DHAS_DMABUF=1', language: 'c')
endif
libffi = dependency('libffi')
pthreads = dependency('threads')
rt = cc.find_library('rt')
# XXX dtrace -G (Solaris, FreeBSD, NetBSD) isn't supported yet
......@@ -76,14 +71,12 @@ subdir('protocols')
waypipe_source_files = ['client.c', 'dmabuf.c', 'handlers.c', 'mainloop.c', 'parsing.c', 'server.c', 'shadow.c', 'interval.c', 'util.c', 'video.c']
waypipe_dependencies = [
libgbm, # General GPU buffer creation, aligned with dmabuf proto
libffi, # To call wayland protocol functions
liblz4, # Fast compression option
libzstd, # Slow compression option
libavcodec,libavutil,libswscale, # Video encoding
pthreads, # To run expensive computations in parallel
protos, # Wayland protocol data
rt, # For shared memory
wayland_client.partial_dependency(compile_args: true), # protocol headers need these
]
waypipe_includes = []
......@@ -144,29 +137,72 @@ test_mirror = executable(
)
# disable leak checking, because library code is often responsible
test('How well file descriptors are replicated', test_mirror, env: ['ASAN_OPTIONS=detect_leaks=0'])
gen_path = join_paths(meson.current_source_dir(), 'protocols/symgen.py')
test_fnlist = files('test/test_fnlist.txt')
testproto_src = custom_target(
'test-proto code',
output: '@BASENAME@-data.c',
input: 'test/test-proto.xml',
command: [python3, gen_path, 'data', test_fnlist, '@INPUT@', '@OUTPUT@'],
)
testproto_header = custom_target(
'test-proto client-header',
output: '@BASENAME@-defs.h',
input: 'test/test-proto.xml',
command: [python3, gen_path, 'header', test_fnlist, '@INPUT@', '@OUTPUT@'],
)
test_parse = executable(
'wire_parse',
['test/wire_parse.c'],
['test/wire_parse.c', testproto_src, testproto_header],
link_with: lib_waypipe_src,
dependencies: [libffi, wayland_client.partial_dependency(compile_args: true)]
dependencies: [protos]
)
test('That protocol parsing fails cleanly', test_parse)
weston_dep = dependency('weston', required: false)
testprog_paths = []
if weston_dep.found()
# Sometimes weston's test clients are installed here instead
testprog_paths += weston_dep.get_pkgconfig_variable('libexecdir')
endif
weston_prog = find_program('weston', required: false)
weston_shm_prog = find_program('weston-simple-shm', required: false)
weston_egl_prog = find_program('weston-simple-egl', required: false)
weston_dma_prog = find_program('weston-simple-dmabuf-drm', required: false)
envlist = [
'TEST_WAYPIPE_PATH=@0@'.format(waypipe_prog.full_path()),
]
if weston_prog.found() and weston_shm_prog.found() and weston_egl_prog.found()
modenv = envlist
modenv += 'TEST_WESTON_PATH=@0@'.format(weston_prog.path())
modenv += 'TEST_WESTON_SHM_PATH=@0@'.format(weston_shm_prog.path())
modenv += 'TEST_WESTON_EGL_PATH=@0@'.format(weston_egl_prog.path())
modenv += 'TEST_WESTON_DMA_PATH=@0@'.format(weston_dma_prog.path())
test_headless = join_paths(meson.current_source_dir(), 'test/headless.py')
test('If clients crash when run with weston via waypipe', python3, args: test_headless, env: modenv)
if weston_prog.found()
envlist += 'TEST_WESTON_PATH=@0@'.format(weston_prog.path())
endif
test_programs = [
['TEST_WESTON_SHM_PATH', 'weston-simple-shm'],
['TEST_WESTON_EGL_PATH', 'weston-simple-egl'],
['TEST_WESTON_DMA_PATH', 'weston-simple-dmabuf-drm'],
['TEST_WESTON_TERM_PATH', 'weston-terminal'],
['TEST_WESTON_PRES_PATH', 'weston-presentation-shm'],
['TEST_WESTON_SUBSURF_PATH', 'weston-subsurfaces'],
]
have_test_progs = false
foreach t : test_programs
test_prog = find_program(t[1], required: false)
foreach p : testprog_paths
if not test_prog.found()
test_prog = find_program(join_paths(p, t[1]), required: false)
endif
endforeach
if test_prog.found()
have_test_progs = true
envlist += '@0@=@1@'.format(t[0], test_prog.path())
endif
endforeach
if weston_prog.found() and have_test_progs
test_headless = join_paths(meson.current_source_dir(), 'test/headless.py')
test('If clients crash when run with weston via waypipe', python3, args: test_headless, env: envlist)
endif
test_startup = join_paths(meson.current_source_dir(), 'test/startup_failure.py')
test('That waypipe exits cleanly given a bad setup', python3, args: test_startup, env: envlist)
fuzz_hook = executable(
'fuzz_hook',
['test/fuzz_hook.c'],
link_with: lib_waypipe_src,
dependencies: [pthreads]
)
......@@ -25,9 +25,7 @@
#include "util.h"
#include <ffi.h>
#include <wayland-util.h>
#include <protocols/symgen_types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
......@@ -55,20 +53,22 @@ void listset_insert(struct fd_translation_map *map, struct obj_list *lst,
}
for (int i = 0; i < lst->nobj; i++) {
if (lst->objs[i]->obj_id == obj->obj_id) {
if (lst->objs[i]->is_zombie) {
/* Zombie objects (server allocated, client
* deleted) are only acknowledged destroyed by
* the server when they are replaced */
destroy_wp_object(map, lst->objs[i]);
lst->objs[i] = obj;
return;
/* We /always/ replace the object, to ensure that map
* elements are never duplicated and make the deletion
* process cause crashes */
if (!lst->objs[i]->is_zombie) {
wp_log(WP_ERROR,
"Replacing object @%u that already exists: old type %s, new type %s",
obj->obj_id,
get_type_name(lst->objs[i]),
get_type_name(obj));
}
wp_log(WP_ERROR,
"Inserting object @%u that already exists: old type %s, new type %s",
obj->obj_id,
get_type_name(lst->objs[i]),
get_type_name(obj));
/* Zombie objects (server allocated, client deleted) are
* only acknowledged destroyed by the server when they
* are replaced. */
destroy_wp_object(map, lst->objs[i]);
lst->objs[i] = obj;
return;
}
if (lst->objs[i]->obj_id > obj->obj_id) {
memmove(lst->objs + i + 1, lst->objs + i,
......@@ -109,111 +109,11 @@ struct wp_object *listset_get(struct obj_list *lst, uint32_t id)
}
return NULL;
}
static int count_num_args(const struct wl_message *msg)
{
int n = 0;
for (const char *c = msg->signature; *c; c++) {
n += (strchr("afhionsu", *c) != NULL);
}
return n;
}
static int count_subhandler_call_types(
int n, const void *fns, const struct wl_message *msgs)
{
int m = 0;
for (int k = 0; k < n; k++) {
void (*fn)(void) = ((void (*const *)(void))fns)[k];
if (!fn) {
continue;
}
m += count_num_args(&msgs[k]) + 2;
}
return m;
}
static int compute_num_call_types(int *nhandlers, int *nfunctions)
{
int num_call_types = 0;
int i = 0;
int nfuncs = 0;
for (; handlers[i].interface; i++) {
// todo: modify generator produce symmetric tables with no
// event/req distinction
const struct msg_handler *handler = &handlers[i];
if (handler->event_handlers) {
num_call_types += count_subhandler_call_types(
handler->interface->event_count,
handler->event_handlers,
handler->interface->events);
nfuncs += handler->interface->event_count;
}
if (handler->request_handlers) {
num_call_types += count_subhandler_call_types(
handler->interface->method_count,
handler->request_handlers,
handler->interface->methods);
nfuncs += handler->interface->method_count;
}
}
*nhandlers = i;
*nfunctions = nfuncs;
return num_call_types;
}
static int setup_subhandler_cif(int n, const void *fns,
const struct wl_message *msgs, int *i_types, ffi_type **types,
ffi_cif *cifs, bool is_event)
struct wp_object *get_object(struct message_tracker *mt, uint32_t id,
const struct wp_interface *intf)
{
int nused = 0;
for (int k = 0; k < n; k++) {
void (*fn)(void) = ((void (*const *)(void))fns)[k];
if (!fn) {
continue;
}
ffi_type **type_offset = &types[*i_types];
/* The first two types are reserved here for &context,NULL */
types[(*i_types)++] = &ffi_type_pointer;
types[(*i_types)++] = &ffi_type_pointer;
int nargs = count_num_args(&msgs[k]);
for (const char *c = msgs[k].signature; *c; c++) {
switch (*c) {
case 'a':
types[(*i_types)++] = &ffi_type_pointer;
break;
case 'f':
types[(*i_types)++] = &ffi_type_sint32;
break;
case 'h':
types[(*i_types)++] = &ffi_type_sint32;
break;
case 'i':
types[(*i_types)++] = &ffi_type_sint32;
break;
case 'o':
types[(*i_types)++] = &ffi_type_pointer;
break;
case 'n':
types[(*i_types)++] =
is_event ? &ffi_type_pointer
: &ffi_type_uint32;
break;
case 's':
types[(*i_types)++] = &ffi_type_pointer;
break;
case 'u':
types[(*i_types)++] = &ffi_type_uint32;
break;
default:
break;
}
}
ffi_prep_cif(&cifs[k], FFI_DEFAULT_ABI,
(unsigned int)(nargs + 2), &ffi_type_void,
type_offset);
nused++;
}
return nused;
(void)intf;
return listset_get(&mt->objects, id);
}
void init_message_tracker(struct message_tracker *mt)
......@@ -222,50 +122,6 @@ void init_message_tracker(struct message_tracker *mt)
listset_insert(NULL, &mt->objects,
create_wp_object(1, the_display_interface));
/* Precompute the ffi_cif structures, as ffi_prep_cif takes about as
* much time as ffi_call_cif,
* and for dense (i.e, damage heavy) message streams both are called
* /very/ often */
int nhandlers = 0;
int nfunctions = 0;
int num_call_types = compute_num_call_types(&nhandlers, &nfunctions);
mt->cif_arg_table = calloc((size_t)num_call_types, sizeof(ffi_type *));
mt->cif_table = calloc((size_t)nfunctions, sizeof(ffi_cif));
mt->event_cif_cache = calloc((size_t)nhandlers, sizeof(ffi_cif *));
mt->request_cif_cache = calloc((size_t)nhandlers, sizeof(ffi_cif *));
ffi_cif *cif_table = mt->cif_table;
int i_calltypes = 0;
int i_functions = 0;
int k = 0;
for (int i_handler = 0; handlers[i_handler].interface; i_handler++) {
const struct msg_handler *handler = &handlers[i_handler];
if (handler->event_handlers) {
ffi_cif **cache = (ffi_cif **)mt->event_cif_cache;
k += setup_subhandler_cif(
handler->interface->event_count,
handler->event_handlers,
handler->interface->events,
&i_calltypes, mt->cif_arg_table,
&cif_table[i_functions], true);
cache[i_handler] = &cif_table[i_functions];
i_functions += handler->interface->event_count;
}
if (handler->request_handlers) {
ffi_cif **cache = (ffi_cif **)mt->request_cif_cache;
k += setup_subhandler_cif(
handler->interface->method_count,
handler->request_handlers,
handler->interface->methods,
&i_calltypes, mt->cif_arg_table,
&cif_table[i_functions], false);
cache[i_handler] = &cif_table[i_functions];
i_functions += handler->interface->method_count;
}
}
wp_log(WP_DEBUG, "Set up %d ffi functions, with %d types total", k,
i_calltypes);
}
void cleanup_message_tracker(
struct fd_translation_map *map, struct message_tracker *mt)
......@@ -274,13 +130,9 @@ void cleanup_message_tracker(
destroy_wp_object(map, mt->objects.objs[i]);
}
free(mt->objects.objs);
free(mt->cif_table);
free(mt->cif_arg_table);
free(mt->event_cif_cache);
free(mt->request_cif_cache);
}
static int get_handler_idx_for_interface(const struct wl_interface *intf)
static int get_handler_idx_for_interface(const struct wp_interface *intf)
{
for (int i = 0; handlers[i].interface; i++) {
if (handlers[i].interface == intf) {
......@@ -290,222 +142,92 @@ static int get_handler_idx_for_interface(const struct wl_interface *intf)
return -1;
}
struct uarg {
union {
uint32_t ui;
int32_t si;
const char *str;
struct wp_object *obj;
wl_fixed_t fxd; // from wayland-util.h
struct {
struct wl_array *arrptr;
struct wl_array arr; // from wayland-util.h
};
};
};
static bool word_has_empty_bytes(uint32_t v)
{
return ((v & 0xFF) == 0) || ((v & 0xFF00) == 0) ||
((v & 0xFF0000) == 0) || ((v & 0xFF000000) == 0);
}
/* Parse the message payload and apply it to a function, creating new objects
* and consuming fds */
void invoke_msg_handler(ffi_cif *cif, const struct wl_interface *intf,
const struct wl_message *msg, bool is_event,
const uint32_t *const payload, const int paylen,
const int *const fd_list, const int fdlen, int *fds_used,
void (*const func)(void), struct context *ctx,
struct message_tracker *mt, struct fd_translation_map *map)
bool size_check(const struct msg_data *data, const uint32_t *payload,
unsigned int true_length, int fd_length)
{
/* The types to match these arguments are set up once in
* `setup_subhandler_cif` */
void *call_args_ptr[30];
if (strlen(msg->signature) > 30) {
wp_log(WP_ERROR, "Overly long signature for %s.%s: %s",
intf->name, msg->name, msg->signature);
if (data->n_fds > fd_length) {
wp_log(WP_ERROR, "Msg overflow, not enough fds %d > %d",
data->n_fds, fd_length);
return false;
}
struct uarg call_args_val[30];
void *nullptr = NULL;
void *addr_of_ctx = ctx;
call_args_ptr[0] = &addr_of_ctx;
call_args_ptr[1] = &nullptr;
int nargs = 2;
int i = 0; // index in message
int k = 0; // current argument index
const char *c = msg->signature; // current argument value
unsigned int len = data->base_gap;
if (len > true_length) {
wp_log(WP_ERROR, "Msg overflow, not enough words %d > %d", len,
true_length);
return false;
}
for (; *c; c++, k++) {
// Skip over version specifications, and over null object
// permission flags
while ((*c >= '0' && *c <= '9') || *c == '?') {
c++;
for (int i = 0; i < data->n_stretch; i++) {
/* For strings, the string length /includes/ the null terminator
*/
uint32_t x_words = (payload[len - 1] + 3) / 4;
/* string termination validation */
if (data->stretch_is_string[i] && x_words) {
unsigned int end_idx = len + x_words - 1;
if (end_idx < true_length &&
!word_has_empty_bytes(
payload[end_idx])) {
wp_log(WP_ERROR,
"Msg overflow, string termination %d < %d, %d, %x %d",
len, true_length, x_words,
payload[end_idx],
word_has_empty_bytes(
payload[end_idx]));
return false;
}
}
if (!*c) {
break;
len += x_words;
len += data->trail_gap[i];
if (len > true_length) {
wp_log(WP_ERROR, "Msg overflow, post string %d %d > %d",
i, len, true_length);
return false;
}
switch (*c) {
case 'a': {
if (i >= paylen) {
goto len_overflow;
}
uint32_t len = payload[i++];
int reqd_len = i + ((int)len + 3) / 4;
if (reqd_len > paylen) {
goto len_overflow;
}
// pass in pointer
call_args_val[nargs].arr.data = (void *)&payload[i];
call_args_val[nargs].arr.size = len;
// fixed allocation. waypipe won't reallocate anyway
call_args_val[nargs].arr.alloc = (size_t)-1;
call_args_ptr[nargs] = &call_args_val[nargs].arr;
i += ((len + 3) / 4);
} break;
case 'h': {
if (*fds_used >= fdlen) {
goto fd_overflow;
}
call_args_val[nargs].si = fd_list[(*fds_used)++];
call_args_ptr[nargs] = &call_args_val[nargs].si;
nargs++;
} break;
case 'f': {
if (i >= paylen) {
goto len_overflow;
}
wl_fixed_t v = *((const wl_fixed_t *)&payload[i++]);
call_args_val[nargs].fxd = v;
call_args_ptr[nargs] = &call_args_val[nargs].fxd;
nargs++;
} break;
case 'i': {
if (i >= paylen) {
goto len_overflow;
}
int32_t v = (int32_t)payload[i++];
call_args_val[nargs].si = v;
call_args_ptr[nargs] = &call_args_val[nargs].si;
nargs++;
} break;
case 'o': {
if (i >= paylen) {
goto len_overflow;
}
uint32_t id = payload[i++];
struct wp_object *lo = listset_get(&mt->objects, id);
// May always be null, in case client messes up
call_args_val[nargs].obj = lo;
call_args_ptr[nargs] = &call_args_val[nargs].obj;
nargs++;
}
return true;
}
} break;
case 'n': {
if (i >= paylen) {
goto len_overflow;
/* Given a size-checked request, try to construct all the new objects
* that the request requires. Return true if successful, false otherwise.
*
* The argument `caller_obj` should be the object on which the request was
* invoked; this function checks to make sure that object is not
* overwritten by accident/corrupt input.
*/
static bool build_new_objects(const struct msg_data *data,
const uint32_t *payload, struct fd_translation_map *map,
struct message_tracker *mt, const struct wp_object *caller_obj)
{
unsigned int pos = 0;
int gap_no = 0;
for (int k = 0; k < data->new_vec_len; k++) {
if (data->new_obj_idxs[k] == (unsigned int)-1) {
pos += gap_no == 0 ? data->base_gap
: data->trail_gap[gap_no - 1];
pos += (payload[pos - 1] + 3) / 4;
} else {
uint32_t new_id = payload[pos + data->new_obj_idxs[k]];
if (new_id == caller_obj->obj_id) {
wp_log(WP_ERROR,
"In %s.%s, tried to create object id=%u conflicting with object being called, also id=%u",
caller_obj->type->name,
data->name, new_id,
caller_obj->obj_id);
return false;
}
uint32_t v = payload[i++];
/* We create the object unconditionally, although
* while server requests are handled with the object id,
* the client's events are fed the object pointer. */
struct wp_object *new_obj =
create_wp_object(v, msg->types[k]);
struct wp_object *new_obj = create_wp_object(
new_id, data->new_obj_types[k]);
listset_insert(map, &mt->objects, new_obj);
if (ctx->obj->is_zombie) {
/* todo: handle misc data ? */
new_obj->is_zombie = true;
}
if (is_event) {
call_args_val[nargs].obj = new_obj;
call_args_ptr[nargs] =
&call_args_val[nargs].obj;
nargs++;
} else {
call_args_val[nargs].ui = new_obj->obj_id;
call_args_ptr[nargs] = &call_args_val[nargs].ui;
nargs++;
}
} break;
case 's': {
if (i >= paylen) {
goto len_overflow;
}
uint32_t len = payload[i++];
int reqd_len = i + ((int)len + 3) / 4;
if (reqd_len > paylen) {
goto len_overflow;
}
const char *str = (const char *)&payload[i];
call_args_val[nargs].str = str;
call_args_ptr[nargs] = &call_args_val[nargs].str;
nargs++;
i += ((len + 3) / 4);
} break;
case 'u': {
if (i >= paylen) {
goto len_overflow;
}
uint32_t v = payload[i++];
call_args_val[nargs].ui = v;
call_args_ptr[nargs] = &call_args_val[nargs].ui;
nargs++;
} break;
default:
wp_log(WP_DEBUG,
"For %s@%u.%s, unidentified argument type %c",
intf->name, ctx->obj->obj_id, msg->name,
*c);
break;
}
continue;
const char *overflow_type = NULL;
len_overflow:
overflow_type = "byte";
goto overflow;
fd_overflow:
overflow_type = "fd";
overflow:
wp_log(WP_ERROR,
"Message %x %s@%u.%s parse length overflow (for %ss), bytes=%d/%d, fds=%d/%d, c=%c",
payload, intf->name, ctx->obj->obj_id,
msg->name, overflow_type, 4 * i, 4 * paylen,
*fds_used, fdlen, *c);
return;
}
if (i != paylen) {
wp_log(WP_ERROR,
"Parse error length mismatch for %s.%s: used %d expected %d",
intf->name, msg->name, i * 4, paylen * 4);
}
if (func) {
ffi_call(cif, func, NULL, call_args_ptr);
}
if (ctx->obj->obj_id >= 0xff000000 && !strcmp(msg->name, "destroy")) {
/* Unfortunately, the wayland server library does not explicitly
* acknowledge the client requested deletion of objects that the
* wayland server has created; the client assumes success,
* except by creating a new object that overrides the existing
* id.
*
* To correctly vanish all events in flight, we mark the element
* as having been zombified; it will only be destroyed when a
* new element is created to take its place, since there could
* still be e.g. data transfers in the channel, and it's best
* that those only vanish when needed.
*
* TODO: early struct shadow_fd closure for all deletion
* requests, with a matching zombie flag to vanish transfers;
*
* TODO: avert the zombie apocalypse, where the compositor
* sends creation notices for a full hierarchy of objects
* before it receives the root's .destroy request.
*/
ctx->obj->is_zombie = true;
}
return true;
}
int peek_message_size(const void *data)
......@@ -536,24 +258,15 @@ enum parse_state handle_message(struct globals *g, bool display_side,
return PARSE_UNKNOWN;
}
const struct wl_interface *intf = objh->type;
const struct wl_message *msg = NULL;
if (from_client) {
if (meth < intf->method_count && meth >= 0) {
msg = &intf->methods[meth];
} else {
wp_log(WP_DEBUG,
"Unidentified request #%d (of %d) on interface %s",
meth, intf->method_count, intf->name);
}
const struct wp_interface *intf = objh->type;
int type_idx = from_client ? 0 : 1;
const struct msg_data *msg = NULL;
if (meth < intf->nfuncs[type_idx] && meth >= 0) {
msg = &intf->funcs[type_idx][meth];
} else {
if (meth < intf->event_count && meth >= 0) {
msg = &intf->events[meth];
} else {
wp_log(WP_ERROR,
"Unidentified event #%d on interface %s",
meth, intf->name);
}
wp_log(WP_DEBUG,
"Unidentified request #%d (of %d) on interface %s",
meth, intf->nfuncs[type_idx], intf->name);
}
if (!msg) {
wp_log(WP_DEBUG, "Unidentified %s from known object",
......@@ -562,26 +275,28 @@ enum parse_state handle_message(struct globals *g, bool display_side,
}
const int handler_idx = get_handler_idx_for_interface(objh->type);
void (*fn)(void) = NULL;
ffi_cif *cif = NULL;
wp_callfn_t call_fn = NULL;
if (handler_idx >= 0) {
const struct msg_handler *handler = &handlers[handler_idx];
if (from_client && handler->request_handlers) {
fn = ((void (*const *)(
void))handler->request_handlers)[meth];
ffi_cif *cif_list = g->tracker.request_cif_cache
[handler_idx];
cif = &cif_list[meth];
}
if (!from_client && handler->event_handlers) {
fn = ((void (*const *)(
void))handler->event_handlers)[meth];
ffi_cif *cif_list =
g->tracker.event_cif_cache[handler_idx];
cif = &cif_list[meth];
const void *funcs = from_client ? handler->request_handlers
: handler->event_handlers;
if (funcs) {
call_fn = ((const wp_callfn_t *)funcs)[meth];
}
}
const uint32_t *payload = header + 2;
if (!size_check(msg, payload, len / 4 - 2,
fds->zone_end - fds->zone_start)) {
wp_log(WP_ERROR, "Message %x %s@%u.%s parse length overflow",
payload, intf->name, objh->obj_id, msg->name);
return PARSE_UNKNOWN;
}
if (!build_new_objects(msg, payload, &g->map, &g->tracker, objh)) {
return PARSE_UNKNOWN;
}
int fds_used = 0;
struct context ctx = {
.g = g,
......@@ -596,11 +311,38 @@ enum parse_state handle_message(struct globals *g, bool display_side,
.fds = fds,
.fds_changed = false,
};
const uint32_t *payload = ctx.message + 2;
invoke_msg_handler(cif, intf, msg, !from_client, payload, len / 4 - 2,
&fds->data[fds->zone_start],
fds->zone_end - fds->zone_start, &fds_used, fn, &ctx,
&g->tracker, &g->map);
if (call_fn) {
(*call_fn)(&ctx, payload, &fds->data[fds->zone_start],
&g->tracker);
}
fds_used += msg->n_fds;
if (objh->obj_id >= 0xff000000 && !strcmp(msg->name, "destroy")) {
/* Unfortunately, the wayland server library does not explicitly
* acknowledge the client requested deletion of objects that the
* wayland server has created; the client assumes success,
* except by creating a new object that overrides the existing
* id.
*
* To correctly vanish all events in flight, we mark the element
* as having been zombified; it will only be destroyed when a
* new element is created to take its place, since there could
* still be e.g. data transfers in the channel, and it's best
* that those only vanish when needed.
*
* Fortunately, wl_registry::bind objects are all client
* produced.
*
* TODO: early struct shadow_fd closure for all deletion
* requests, with a matching zombie flag to vanish transfers;
*
* TODO: avert the zombie apocalypse, where the compositor
* sends creation notices for a full hierarchy of objects
* before it receives the root's .destroy request.
*/
objh->is_zombie = true;
}
if (ctx.drop_this_msg) {
wp_log(WP_DEBUG, "Dropping %s.%s, with %d fds", intf->name,
msg->name, fds_used);
......
gtk_primary_selection_offer_req_receive
gtk_primary_selection_source_evt_send
wl_buffer_evt_release
wl_data_offer_req_receive
wl_data_source_evt_send
wl_display_evt_delete_id
wl_display_evt_error
wl_display_req_get_registry
wl_display_req_sync
wl_drm_evt_device
wl_drm_req_create_prime_buffer
wl_keyboard_evt_keymap
wl_registry_evt_global
wl_registry_evt_global_remove
wl_registry_req_bind
wl_shm_req_create_pool
wl_shm_pool_req_create_buffer
wl_shm_pool_req_resize
wl_surface_req_attach
wl_surface_req_commit
wl_surface_req_damage
wl_surface_req_damage_buffer
wl_surface_req_set_buffer_transform
wl_surface_req_set_buffer_scale
wp_presentation_evt_clock_id
wp_presentation_feedback_evt_presented
wp_presentation_req_feedback
zwlr_data_control_offer_v1_req_receive
zwlr_data_control_source_v1_evt_send
zwlr_export_dmabuf_frame_v1_evt_frame
zwlr_export_dmabuf_frame_v1_evt_object
zwlr_export_dmabuf_frame_v1_evt_ready
zwlr_screencopy_frame_v1_evt_ready
zwlr_screencopy_frame_v1_req_copy
zwp_linux_buffer_params_v1_evt_created
zwp_linux_buffer_params_v1_req_add
zwp_linux_buffer_params_v1_req_create
zwp_linux_buffer_params_v1_req_create_immed
zwp_linux_dmabuf_v1_evt_modifier
wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
wayland_dir = wayland_client.get_pkgconfig_variable('pkgdatadir')
wayland_scanner_prog = find_program(wayland_scanner.get_pkgconfig_variable('wayland_scanner'))
# todo: make function list dependency explicit
gen_path = join_paths(meson.current_source_dir(), 'symgen.py')
fn_list = join_paths(meson.current_source_dir(), 'function_list.txt')
wayland_protos = dependency('wayland-protocols', version: '>=1.12') # xdg-shell
wayland_dep = dependency('wayland-client', required: false)
if not wayland_dep.found()
wayland_dep = dependency('wayland-server', required: true)
endif
wayland_dir = wayland_dep.get_pkgconfig_variable('pkgdatadir')
wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
protocols = [
wayland_dir + '/wayland.xml',
......@@ -19,24 +28,19 @@ protocols = [
protocols_src = []
protocols_headers = []
protocols_headers += configure_file(input: 'symgen_types.h', copy: true, output: 'symgen_types.h')
foreach xml : protocols
protocols_src += custom_target(
'@0@ code'.format(xml.underscorify()),
output: '@BASENAME@-data.c',
input: xml,
command: [wayland_scanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'],
command: [python3, gen_path, 'data', fn_list, '@INPUT@', '@OUTPUT@'],
)
protocols_headers += custom_target(
'@0@ client-header'.format(xml.underscorify()),
output: '@BASENAME@-client-defs.h',
input: xml,
command: [wayland_scanner_prog, 'client-header', '@INPUT@', '@OUTPUT@'],
)
protocols_headers += custom_target(
'@0@ server-header'.format(xml.underscorify()),
output: '@BASENAME@-server-defs.h',
output: '@BASENAME@-defs.h',
input: xml,
command: [wayland_scanner_prog, 'server-header', '@INPUT@', '@OUTPUT@'],
command: [python3, gen_path, 'header', fn_list, '@INPUT@', '@OUTPUT@'],
)
endforeach
......@@ -44,7 +48,6 @@ endforeach
lib_protocols = static_library(
'protocols',
protocols_src,
dependencies: wayland_client.partial_dependency(compile_args: true),
)
protos = declare_dependency(
link_with: lib_protocols,
......
#!/usr/bin/env python3
import os, sys, fnmatch
import xml.etree.ElementTree as ET
"""
A static protocol code generator.
"""
wltype_to_ctypes = {
"uint": "uint32_t ",
"fixed": "uint32_t ",
"int": "int32_t ",
"object": "struct wp_object *",
"new_id": "struct wp_object *",
"string": "const char *",
"fd": "int ",
}
def write_enum(is_header, ostream, iface_name, enum):
if not is_header:
return
enum_name = enum.attrib["name"]
is_bitfield = "bitfield" in enum.attrib and enum.attrib["bitfield"] == "true"
for entry in enum:
if entry.tag != "entry":
continue
entry_name = entry.attrib["name"]
entry_value = entry.attrib["value"]
full_name = (iface_name + "_" + enum_name + "_" + entry_name).upper()
print("#define {} {}".format(full_name, entry_value), file=ostream)
def is_exportable(func_name, export_list):
for e in export_list:
if fnmatch.fnmatchcase(func_name, e):
return True
return False
def write_func(is_header, ostream, iface_name, func, is_request, export_list):
func_name = (
iface_name + "_" + ("req" if is_request else "evt") + "_" + func.attrib["name"]
)
for_export = is_exportable(func_name, export_list)
c_sig = ["struct context *ctx"]
w_args = []
num_fd_args = 0
num_reg_args = 0
num_obj_args = 0
num_new_args = 0
num_stretch_args = 0
for arg in func:
if arg.tag != "arg":
continue
arg_name = arg.attrib["name"]
arg_type = arg.attrib["type"]
arg_interface = arg.attrib["interface"] if "interface" in arg.attrib else None
if arg_type == "new_id" and arg_interface is None:
# Special case, for wl_registry_bind
c_sig.append("const char *interface")
c_sig.append("uint32_t version")
c_sig.append("struct wp_object *id")
w_args.append(("interface", "string", None))
w_args.append(("version", "uint", None))
w_args.append((arg_name, "new_id", None))
num_obj_args += 1
num_new_args += 1
num_reg_args += 3
num_stretch_args += 1
continue
if arg_type == "array":
c_sig.append("int " + arg_name + "_count")
c_sig.append("const uint8_t *" + arg_name + "_val")
else:
c_sig.append(wltype_to_ctypes[arg_type] + arg_name)
w_args.append((arg_name, arg_type, arg_interface))
if arg_type == "fd":
num_fd_args += 1
else:
num_reg_args += 1
if arg_type == "object" or arg_type == "new_id":
num_obj_args += 1
if arg_type == "new_id":
num_new_args += 1
if arg_type in ("array", "string"):
num_stretch_args += 1
do_signature = "void do_{}({});".format(func_name, ", ".join(c_sig))
handle_signature = "void call_{}(struct context *ctx, const uint32_t *payload, const int *fds, struct message_tracker *mt)".format(
func_name
)
W = lambda *x: print(*x, file=ostream)
if for_export:
W(do_signature)
if is_header and for_export:
W(handle_signature + ";")
elif for_export:
# Write function definition
W(handle_signature + " {")
if num_reg_args > 0:
W("\tunsigned int i = 0;")
if num_fd_args > 0:
W("\tunsigned int k = 0;")
tmp_names = ["ctx"]
n_fds_left = num_fd_args
n_reg_left = num_reg_args
for i, (arg_name, arg_type, arg_interface) in enumerate(w_args):
if arg_type == "array":
n_reg_left -= 1
W(
"\tconst uint8_t *arg{}_b = (const uint8_t *)&payload[i + 1];".format(
i
)
)
W("\tuint32_t arg{}_a = (uint32_t)payload[i];".format(i))
if n_reg_left > 0:
W("\ti += 1 + ((arg{}_a + 0x3) >> 2);".format(i))
tmp_names.append("arg{}_a".format(i))
tmp_names.append("arg{}_b".format(i))
continue
tmp_names.append("arg{}".format(i))
if arg_type == "fd":
n_fds_left -= 1
W("\tint arg{} = fds[{}];".format(i, "k++" if n_fds_left > 0 else "k"))
continue
n_reg_left -= 1
if arg_type == "string":
W("\tconst char *arg{} = (const char *)&payload[i + 1];".format(i))
W("\tif (!payload[i]) arg{} = NULL;".format(i))
if n_reg_left > 0:
W("\ti += 1 + ((payload[i] + 0x3) >> 2);")
continue
i_incr = "i++" if n_reg_left > 0 else "i"
if arg_type == "object" or arg_type == "new_id":
if arg_interface is None:
intf_str = "NULL"
else:
intf_str = "&intf_" + arg_interface
W(
"\tstruct wp_object *arg{} = get_object(mt, payload[{}], {});".format(
i, i_incr, intf_str
)
)
elif arg_type == "int":
W("\tint32_t arg{} = (int32_t)payload[{}];".format(i, i_incr))
elif arg_type == "uint" or arg_type == "fixed":
W("\tuint32_t arg{} = payload[{}];".format(i, i_incr))
W("\tdo_{}({});".format(func_name, ", ".join(tmp_names)))
if num_obj_args == 0:
W("\t(void)mt;")
if num_fd_args == 0:
W("\t(void)fds;")
if num_reg_args == 0:
W("\t(void)payload;")
W("}")
if is_header:
msg_data_args = None
else:
gaps = [0]
nta = []
newvec_idxs = []
newvec_types = []
for arg_name, arg_type, arg_interface in w_args:
if arg_type == "fd":
continue
## New type string uses offsets relative last stretch end
if arg_type == "new_id":
newvec_idxs.append(str(gaps[-1]))
newvec_types.append(
"&intf_" + arg_interface if arg_interface is not None else "NULL"
)
# Every non-fd element begins with at least one word
gaps[-1] += 1
if arg_type in ("string", "array"):
gaps.append(0)
nta.append("true" if arg_type == "string" else "false")
newvec_idxs.append("-1")
newvec_types.append("NULL")
base_g = str(gaps[0])
nts = [str(x) for x in gaps[1:]]
if len(nts) > 0:
W("static const bool sis_" + func_name + "[] = {")
W("\t" + ", ".join(nta))
W("};")
W("static const unsigned int gap_" + func_name + "[] = {")
W("\t" + ", ".join(nts))
W("};")
if num_new_args > 0:
W("static const unsigned int noi_" + func_name + "[] = {")
W("\t" + ", ".join(newvec_idxs))
W("};")
W("static const struct wp_interface *not_" + func_name + "[] = {")
W("\t" + ", ".join(newvec_types))
W("};")
# Write message metadata, for length check/new object initialization
msg_data_args = []
msg_data_args.append('"{}"'.format(func.attrib["name"]))
msg_data_args.append(str(num_stretch_args))
msg_data_args.append(str(base_g))
if len(nts) > 0:
msg_data_args.append("gap_{}".format(func_name))
msg_data_args.append("sis_{}".format(func_name))
else:
msg_data_args.append("NULL")
msg_data_args.append("NULL")
msg_data_args.append(str(num_fd_args))
if num_new_args > 0:
msg_data_args.append(str(len(newvec_types)))
msg_data_args.append("noi_{}".format(func_name))
msg_data_args.append("not_{}".format(func_name))
else:
msg_data_args.append("0")
msg_data_args.append("NULL")
msg_data_args.append("NULL")
return (is_request, func_name, func.attrib["name"], msg_data_args)
def write_interface(is_header, ostream, iface_name, func_data):
reqs, evts = [], []
for is_req, name, short_name, msg_data_args in func_data:
if is_req:
reqs.append((name, short_name, msg_data_args))
else:
evts.append((name, short_name, msg_data_args))
W = lambda *x: print(*x, file=ostream)
if is_header:
# Define 'header' type listing functions
if len(reqs) > 0:
W("struct req_map_" + iface_name + " {")
for name, short_name, mda in reqs:
W("\twp_callfn_t " + short_name + ";")
W("};")
if len(evts) > 0:
W("struct evt_map_" + iface_name + " {")
for name, short_name, mda in evts:
W("\twp_callfn_t " + short_name + ";")
W("};")
else:
rcn, ecn = "NULL", "NULL"
if len(reqs) > 0:
W("static const struct msg_data reqs_" + iface_name + "[] = {")
for name, short_name, mda in reqs:
W("\t{" + ", ".join(mda) + "},")
W("};")
rcn = "reqs_" + iface_name
if len(evts) > 0:
W("static const struct msg_data evts_" + iface_name + "[] = {")
for name, short_name, mda in evts:
W("\t{" + ", ".join(mda) + "},")
W("};")
ecn = "evts_" + iface_name
W("const struct wp_interface intf_" + iface_name + " = {")
W('\t"{}",'.format(iface_name))
W("\t{" + rcn + ", " + ecn + "},")
W("\t{" + str(len(reqs)) + ", " + str(len(evts)) + "},")
W("};")
if __name__ == "__main__":
mode, req_file, source, dest = sys.argv[1:]
export_list = open(req_file).read().split("\n")
is_header = {"data": False, "header": True}[mode]
tree = ET.parse(source)
root = tree.getroot()
proto_name = root.attrib["name"]
header_guard = "PROTOCOL_{}_H".format(proto_name.upper())
intfset = {
interface.attrib["name"] for interface in root if interface.tag == "interface"
}
for intf in root:
if intf.tag == "interface":
for func in intf:
for arg in func:
if "interface" in arg.attrib:
intfset.add(arg.attrib["interface"])
interfaces = sorted(intfset)
with open(dest, "w") as ostream:
W = lambda *x: print(*x, file=ostream)
if is_header:
W("#ifndef {}".format(header_guard))
W("#define {}".format(header_guard))
W()
W('#include "symgen_types.h"')
if not is_header:
W("#include <stddef.h>")
for intf in interfaces:
W("extern const struct wp_interface intf_{};".format(intf))
for interface in root:
if interface.tag != "interface":
continue
iface_name = interface.attrib["name"]
func_data = []
for item in interface:
if item.tag == "enum":
write_enum(is_header, ostream, iface_name, item)
elif item.tag == "request" or item.tag == "event":
func_data.append(
write_func(
is_header,
ostream,
iface_name,
item,
item.tag == "request",
export_list,
)
)
elif item.tag == "description":
pass
else:
raise Exception(item.tag)
write_interface(is_header, ostream, iface_name, func_data)
if is_header:
W()
W("#endif /* {} */".format(header_guard))
#ifndef SYMGEN_TYPES_H
#define SYMGEN_TYPES_H
#include <stdbool.h>
#include <stdint.h>
struct context;
struct message_tracker;
struct wp_object;
typedef void (*wp_callfn_t)(struct context *ctx, const uint32_t *payload, const int *fds, struct message_tracker *mt);
struct msg_data {
const char *name;
const int n_stretch;
const unsigned int base_gap;
const unsigned int *trail_gap;
const bool *stretch_is_string;
const int n_fds;
const int new_vec_len;
const unsigned int *new_obj_idxs;/* there are special 'spacer' fields at each string/arr, =-1*/
const struct wp_interface **new_obj_types;
};
struct wp_interface {
const char *name;
/* 0=request, 1=event */
const struct msg_data *funcs[2];
const int nfuncs[2];
};
/* User should define this function. */
struct wp_object *get_object(struct message_tracker *mt, uint32_t id, const struct wp_interface *intf);
#endif /* SYMGEN_TYPES_H */
......@@ -38,8 +38,9 @@
#include <sys/wait.h>
#include <unistd.h>
int run_server(const char *socket_path, const struct main_config *config,
bool oneshot, bool unlink_at_end, const char *application,
int run_server(const char *socket_path, const char *wayland_display,
const struct main_config *config, bool oneshot,
bool unlink_at_end, const char *application,
char *const app_argv[])
{
wp_log(WP_DEBUG, "I'm a server on %s, running: %s", socket_path,
......@@ -52,10 +53,24 @@ int run_server(const char *socket_path, const struct main_config *config,
socket_path);
return EXIT_FAILURE;
}
char display_path[256];
if (!oneshot) {
if (wayland_display[0] == '/') {
snprintf(display_path, 256, "%s", wayland_display);
} else {
const char *xdg_dir = getenv("XDG_RUNTIME_DIR");
if (!xdg_dir) {
wp_log(WP_ERROR,
"Env. var XDG_RUNTIME_DIR not available, cannot place display socket for WAYLAND_DISPLAY=\"%s\"",
wayland_display);
return EXIT_FAILURE;
}
snprintf(display_path, 256, "%s/%s", xdg_dir,
wayland_display);
}
}
// Setup connection to program
char displaypath[256];
sprintf(displaypath, "%s.disp.sock", socket_path);
int wayland_socket = -1, server_link = -1, wdisplay_socket = -1;
if (oneshot) {
int csockpair[2];
......@@ -69,7 +84,7 @@ int run_server(const char *socket_path, const struct main_config *config,
} else {
// Bind a socket for WAYLAND_DISPLAY, and listen
int nmaxclients = 128;
wdisplay_socket = setup_nb_socket(displaypath, nmaxclients);
wdisplay_socket = setup_nb_socket(display_path, nmaxclients);
if (wdisplay_socket == -1) {
// Error messages already made
return EXIT_FAILURE;
......@@ -81,7 +96,7 @@ int run_server(const char *socket_path, const struct main_config *config,
if (pid == -1) {
wp_log(WP_ERROR, "Fork failed");
if (!oneshot) {
unlink(displaypath);
unlink(display_path);
}
return EXIT_FAILURE;
} else if (pid == 0) {
......@@ -98,7 +113,7 @@ int run_server(const char *socket_path, const struct main_config *config,
// Since Wayland 1.15, absolute paths are supported in
// WAYLAND_DISPLAY
unsetenv("WAYLAND_SOCKET");
setenv("WAYLAND_DISPLAY", displaypath, 1);
setenv("WAYLAND_DISPLAY", wayland_display, 1);
close(wdisplay_socket);
}
......@@ -205,7 +220,7 @@ int run_server(const char *socket_path, const struct main_config *config,
if (unlink_at_end) {
unlink(socket_path);
}
unlink(displaypath);
unlink(display_path);
close(wdisplay_socket);
// Wait for child processes to exit
......
......@@ -511,7 +511,7 @@ struct shadow_fd *translate_fd(struct fd_translation_map *map,
sfd->fd_local = fd;
sfd->mem_local = NULL;
sfd->mem_mirror = NULL;
sfd->buffer_size = (size_t)-1;
sfd->buffer_size = 0;
sfd->remote_id = (map->max_local_id++) * map->local_sign;
sfd->type = type;
// File changes must be propagated
......@@ -526,7 +526,12 @@ struct shadow_fd *translate_fd(struct fd_translation_map *map,
wp_log(WP_DEBUG, "Creating new shadow buffer for local fd %d", fd);
if (sfd->type == FDC_FILE) {
// We have a file-like object
if (file_sz > FILE_SIZE_SIZE_MASK) {
wp_log(WP_ERROR,
"Failed to create shadow structure, file size %ld too large to transfer",
(uint64_t)file_sz);
return sfd;
}
sfd->buffer_size = file_sz;
// both r/w permissions, because the size the allocates
// the memory does not always have to be the size that
......@@ -791,20 +796,24 @@ void apply_diff(size_t size, char *__restrict__ target1,
}
}
struct transfer *setup_single_block_transfer(int *ntransfers,
struct transfer transfers[], int *nblocks,
struct bytebuf blocks[], size_t size, const char *data)
struct transfer *setup_single_block_transfer(struct transfer_stack *transfers,
struct bytebuf_stack *blocks, size_t size, const char *data)
{
int nt = (*ntransfers)++;
int nb = (*nblocks)++;
transfers[nt].type = FDC_UNKNOWN;
transfers[nt].obj_id = 0;
transfers[nt].special.raw = 0;
transfers[nt].nblocks = 1;
transfers[nt].subtransfers = &blocks[nb];
blocks[nb].size = size;
blocks[nb].data = (char *)data;
return &transfers[nt];
buf_ensure_size(transfers->count + 1, sizeof(struct transfer),
&transfers->size, (void **)&transfers->data);
buf_ensure_size(blocks->count + 1, sizeof(struct bytebuf),
&blocks->size, (void **)&blocks->data);
struct transfer *tf = &transfers->data[transfers->count++];
tf->type = FDC_UNKNOWN;
tf->obj_id = 0;
tf->special.raw = 0;
tf->nblocks = 1;
tf->subtransfer_idx = blocks->count;
struct bytebuf *bl = &blocks->data[blocks->count++];
bl->size = size;
bl->data = (char *)data;
return tf;
}
/* Construct and optionally compress a diff between sfd->mem_mirror and
......@@ -896,9 +905,8 @@ static void wait_thread_task(struct fd_translation_map *map)
/* Optionally compress the data in mem_mirror, and set up the initial
* transfer blocks */
static void add_initial_compressed_block(struct fd_translation_map *map,
struct shadow_fd *sfd, int *ntransfers,
struct transfer transfers[], int *nblocks,
struct bytebuf blocks[])
struct shadow_fd *sfd, struct transfer_stack *transfers,
struct bytebuf_stack *blocks)
{
// new transfer, we send file contents verbatim
bool use_threads =
......@@ -918,11 +926,17 @@ static void add_initial_compressed_block(struct fd_translation_map *map,
wait_thread_task(map);
}
struct transfer *tf = &transfers[(*ntransfers)++];
buf_ensure_size(transfers->count + 1, sizeof(struct transfer),
&transfers->size, (void **)&transfers->data);
buf_ensure_size(blocks->count + map->nthreads + 1,
sizeof(struct bytebuf), &blocks->size,
(void **)&blocks->data);
struct transfer *tf = &transfers->data[transfers->count++];
tf->type = sfd->type;
tf->obj_id = sfd->remote_id;
tf->nblocks = 0;
tf->subtransfers = &blocks[*nblocks];
tf->subtransfer_idx = blocks->count;
tf->special.block_meta = 0;
if (sfd->type == FDC_DMABUF) {
/* Add metadata section to ensure proper remote
......@@ -930,12 +944,12 @@ static void add_initial_compressed_block(struct fd_translation_map *map,
*/
struct bytebuf meta = {.data = (char *)&sfd->dmabuf_info,
.size = sizeof(struct dmabuf_slice_data)};
blocks[(*nblocks)++] = meta;
blocks->data[blocks->count++] = meta;
tf->nblocks++;
}
if (cd_actual_size0) {
tf->special.block_meta += cd_actual_size0;
blocks[(*nblocks)++] = cd_dst0;
blocks->data[blocks->count++] = cd_dst0;
tf->nblocks++;
}
if (use_threads) {
......@@ -943,16 +957,16 @@ static void add_initial_compressed_block(struct fd_translation_map *map,
if (map->threads[i].cd_actual_size) {
tf->special.block_meta +=
map->threads[i].cd_actual_size;
blocks[(*nblocks)++] = map->threads[i].cd_dst;
blocks->data[blocks->count++] =
map->threads[i].cd_dst;
tf->nblocks++;
}
}
}
}
static void add_updated_diff_block(struct fd_translation_map *map,
struct shadow_fd *sfd, int *ntransfers,
struct transfer transfers[], int *nblocks,
struct bytebuf blocks[], int total_damaged_area)
struct shadow_fd *sfd, struct transfer_stack *transfers,
struct bytebuf_stack *blocks, int total_damaged_area)
{
DTRACE_PROBE2(waypipe, diffcomp_start, total_damaged_area,
sfd->buffer_size);
......@@ -984,21 +998,27 @@ static void add_updated_diff_block(struct fd_translation_map *map,
if (actual_diff_size > 0) {
/* Only make the transfer if there were changes */
struct transfer *tf = &transfers[(*ntransfers)++];
buf_ensure_size(transfers->count + 1, sizeof(struct transfer),
&transfers->size, (void **)&transfers->data);
buf_ensure_size(blocks->count + map->nthreads,
sizeof(struct bytebuf), &blocks->size,
(void **)&blocks->data);
struct transfer *tf = &transfers->data[transfers->count++];
tf->type = sfd->type;
tf->obj_id = sfd->remote_id;
tf->nblocks = 0;
tf->subtransfers = &blocks[*nblocks];
tf->special.block_meta = actual_diff_size;
tf->subtransfer_idx = blocks->count;
tf->special.block_meta = (uint32_t)actual_diff_size;
if (cd_actual_size0) {
blocks[(*nblocks)++] = cd_dst0;
blocks->data[blocks->count++] = cd_dst0;
tf->nblocks++;
}
if (use_threads) {
for (int i = 0; i < map->nthreads - 1; i++) {
if (map->threads[i].cd_actual_size) {
blocks[(*nblocks)++] =
blocks->data[blocks->count++] =
map->threads[i].cd_dst;
tf->nblocks++;
}
......@@ -1024,8 +1044,7 @@ static void get_max_diff_space_usage(struct fd_translation_map *map,
}
void collect_update(struct fd_translation_map *map, struct shadow_fd *sfd,
int *ntransfers, struct transfer transfers[], int *nblocks,
struct bytebuf blocks[])
struct transfer_stack *transfers, struct bytebuf_stack *blocks)
{
if (sfd->type == FDC_FILE) {
if (!sfd->is_dirty) {
......@@ -1054,8 +1073,25 @@ void collect_update(struct fd_translation_map *map, struct shadow_fd *sfd,
1)
: NULL;
add_initial_compressed_block(map, sfd, ntransfers,
transfers, nblocks, blocks);
add_initial_compressed_block(
map, sfd, transfers, blocks);
}
if (sfd->has_been_extended) {
sfd->has_been_extended = false;
buf_ensure_size(transfers->count + 1,
sizeof(struct transfer),
&transfers->size,
(void **)&transfers->data);
struct transfer *tf =
&transfers->data[transfers->count++];
tf->type = sfd->type;
tf->obj_id = sfd->remote_id;
tf->nblocks = 0;
tf->subtransfer_idx = -1;
tf->special.block_meta = FILE_SIZE_EXTEND_FLAG |
(uint32_t)sfd->buffer_size;
}
int total_area = get_damage_area(&sfd->damage);
......@@ -1076,8 +1112,7 @@ void collect_update(struct fd_translation_map *map, struct shadow_fd *sfd,
calloc(sfd->compress_space, 1);
}
}
add_updated_diff_block(map, sfd, ntransfers, transfers, nblocks,
blocks, total_area);
add_updated_diff_block(map, sfd, transfers, blocks, total_area);
} else if (sfd->type == FDC_DMABUF) {
// If buffer is clean, do not check for changes
......@@ -1124,8 +1159,8 @@ void collect_update(struct fd_translation_map *map, struct shadow_fd *sfd,
if (sfd->video_codec && sfd->video_context &&
sfd->video_reg_frame && sfd->video_packet) {
memcpy(sfd->mem_mirror, data, sfd->buffer_size);
collect_video_from_mirror(sfd, ntransfers, transfers,
nblocks, blocks, first);
collect_video_from_mirror(
sfd, transfers, blocks, first);
return;
}
......@@ -1134,8 +1169,8 @@ void collect_update(struct fd_translation_map *map, struct shadow_fd *sfd,
// only touching data once
memcpy(sfd->mem_mirror, data, sfd->buffer_size);
add_initial_compressed_block(map, sfd, ntransfers,
transfers, nblocks, blocks);
add_initial_compressed_block(
map, sfd, transfers, blocks);
} else {
if (!sfd->diff_buffer) {
......@@ -1161,8 +1196,8 @@ void collect_update(struct fd_translation_map *map, struct shadow_fd *sfd,
* significantly faster. */
// TODO: Either autodetect when this happens, or write a
// faster/vectorizable diff routine
add_updated_diff_block(map, sfd, ntransfers, transfers,
nblocks, blocks, (int)sfd->buffer_size);
add_updated_diff_block(map, sfd, transfers, blocks,
(int)sfd->buffer_size);
sfd->mem_local = NULL;
}
if (unmap_dmabuf(sfd->dmabuf_bo, handle) == -1) {
......@@ -1184,8 +1219,7 @@ void collect_update(struct fd_translation_map *map, struct shadow_fd *sfd,
? 'Y'
: 'n');
struct transfer *tf = setup_single_block_transfer(
ntransfers, transfers, nblocks, blocks,
sfd->pipe_recv.used,
transfers, blocks, sfd->pipe_recv.used,
sfd->pipe_recv.data);
tf->type = sfd->type;
tf->obj_id = sfd->remote_id;
......@@ -1204,7 +1238,8 @@ void collect_update(struct fd_translation_map *map, struct shadow_fd *sfd,
}
void create_from_update(struct fd_translation_map *map,
struct render_data *render, const struct transfer *transf)
struct render_data *render, const struct transfer *transf,
const struct bytebuf *block)
{
wp_log(WP_DEBUG, "Introducing new fd, remoteid=%d", transf->obj_id);
......@@ -1224,7 +1259,7 @@ void create_from_update(struct fd_translation_map *map,
if (sfd->type == FDC_FILE) {
sfd->mem_local = NULL;
sfd->buffer_size = (size_t)(transf->special.block_meta &
~FILE_SIZE_VIDEO_FLAG);
FILE_SIZE_SIZE_MASK);
sfd->mem_mirror = calloc(
(size_t)align((int)sfd->buffer_size, 8), 1);
......@@ -1241,8 +1276,7 @@ void create_from_update(struct fd_translation_map *map,
} else {
size_t act_size = 0;
const char *act_buffer = NULL;
uncompress_buffer(map, transf->subtransfers[0].size,
transf->subtransfers[0].data,
uncompress_buffer(map, block->size, block->data,
sfd->buffer_size, sfd->compress_buffer,
&act_size, &act_buffer);
......@@ -1326,15 +1360,14 @@ void create_from_update(struct fd_translation_map *map,
bool use_video = transf->special.block_meta &
FILE_SIZE_VIDEO_FLAG;
sfd->buffer_size = (size_t)(transf->special.block_meta &
~FILE_SIZE_VIDEO_FLAG);
FILE_SIZE_SIZE_MASK);
size_t diff_space = 0;
get_max_diff_space_usage(map, sfd->buffer_size, &diff_space,
&sfd->compress_space);
sfd->compress_buffer = calloc(sfd->compress_space, 1);
struct bytebuf block = transf->subtransfers[0];
struct dmabuf_slice_data *info =
(struct dmabuf_slice_data *)block.data;
(struct dmabuf_slice_data *)block->data;
const char *contents = NULL;
size_t contents_size = sfd->buffer_size;
if (use_video) {
......@@ -1353,10 +1386,10 @@ void create_from_update(struct fd_translation_map *map,
(int)info->format);
// Apply first frame, if available
if (block.size > sizeof(struct dmabuf_slice_data)) {
if (block->size > sizeof(struct dmabuf_slice_data)) {
apply_video_packet_to_mirror(sfd,
block.size - sizeof(struct dmabuf_slice_data),
block.data + sizeof(struct dmabuf_slice_data));
block->size - sizeof(struct dmabuf_slice_data),
block->data + sizeof(struct dmabuf_slice_data));
} else {
memset(sfd->mem_mirror, 213, sfd->buffer_size);
}
......@@ -1364,12 +1397,12 @@ void create_from_update(struct fd_translation_map *map,
} else {
sfd->mem_mirror = calloc(sfd->buffer_size, 1);
const char *compressed_contents =
block.data +
block->data +
sizeof(struct dmabuf_slice_data);
size_t szcheck = 0;
uncompress_buffer(map,
block.size - sizeof(struct dmabuf_slice_data),
block->size - sizeof(struct dmabuf_slice_data),
compressed_contents, sfd->buffer_size,
sfd->compress_buffer, &szcheck,
&contents);
......@@ -1401,25 +1434,68 @@ void create_from_update(struct fd_translation_map *map,
}
}
static void increase_buffer_sizes(struct fd_translation_map *map,
struct shadow_fd *sfd, size_t new_size)
{
size_t old_size = sfd->buffer_size;
munmap(sfd->mem_local, old_size);
sfd->buffer_size = new_size;
sfd->mem_local = mmap(NULL, sfd->buffer_size, PROT_READ | PROT_WRITE,
MAP_SHARED, sfd->fd_local, 0);
if (!sfd->mem_local) {
wp_log(WP_ERROR, "Mmap failed!");
return;
}
// todo: handle allocation failures
sfd->mem_mirror = realloc(sfd->mem_mirror, align(sfd->buffer_size, 8));
/* set extension bytes to zero so that the standard diff procedure has
* a known state to work against */
memset(sfd->mem_mirror + old_size, 0,
align(sfd->buffer_size, 8) - old_size);
size_t diff_space = 0;
get_max_diff_space_usage(map, sfd->buffer_size, &diff_space,
&sfd->compress_space);
if (sfd->diff_buffer) {
sfd->diff_buffer = realloc(sfd->diff_buffer, diff_space);
}
if (sfd->compress_buffer && sfd->compress_space) {
sfd->compress_buffer =
realloc(sfd->compress_buffer, diff_space);
}
}
void apply_update(struct fd_translation_map *map, struct render_data *render,
const struct transfer *transf)
const struct transfer *transf, const struct bytebuf *block)
{
struct shadow_fd *sfd = get_shadow_for_rid(map, transf->obj_id);
if (!sfd) {
create_from_update(map, render, transf);
create_from_update(map, render, transf, block);
return;
}
struct bytebuf block = transf->subtransfers[0];
if (sfd->type == FDC_FILE) {
if (transf->type != sfd->type) {
wp_log(WP_ERROR, "Transfer type mismatch %d %d",
transf->type, sfd->type);
}
uint32_t m_size = transf->special.block_meta &
FILE_SIZE_SIZE_MASK;
if (transf->special.block_meta & FILE_SIZE_EXTEND_FLAG) {
/* Zero-extend the buffer to have the new size */
if (ftruncate(sfd->fd_local, m_size) == -1) {
wp_log(WP_ERROR,
"Failed to resize file buffer: %s",
strerror(errno));
}
increase_buffer_sizes(map, sfd, m_size);
return;
}
const char *act_buffer = NULL;
size_t act_size = 0;
uncompress_buffer(map, block.size, block.data,
transf->special.block_meta,
uncompress_buffer(map, block->size, block->data, m_size,
sfd->compress_buffer, &act_size, &act_buffer);
// `memsize+8*remote_nthreads` is the worst-case diff
......@@ -1442,7 +1518,7 @@ void apply_update(struct fd_translation_map *map, struct render_data *render,
transf->type, sfd->type);
}
ssize_t netsize = sfd->pipe_send.used + (ssize_t)block.size;
ssize_t netsize = sfd->pipe_send.used + (ssize_t)block->size;
if (sfd->pipe_send.size <= 1024) {
sfd->pipe_send.size = 1024;
}
......@@ -1455,9 +1531,9 @@ void apply_update(struct fd_translation_map *map, struct render_data *render,
} else {
sfd->pipe_send.data = calloc(sfd->pipe_send.size, 1);
}
memcpy(sfd->pipe_send.data + sfd->pipe_send.used, block.data,
block.size);
sfd->pipe_send.used += (ssize_t)block.size;
memcpy(sfd->pipe_send.data + sfd->pipe_send.used, block->data,
block->size);
sfd->pipe_send.used += (ssize_t)block->size;
// The pipe itself will be flushed/or closed later by
// flush_writable_pipes
......@@ -1476,7 +1552,7 @@ void apply_update(struct fd_translation_map *map, struct render_data *render,
if (sfd->video_codec) {
apply_video_packet_to_mirror(
sfd, block.size, block.data);
sfd, block->size, block->data);
// this frame is applied via memcpy
......@@ -1496,7 +1572,7 @@ void apply_update(struct fd_translation_map *map, struct render_data *render,
const char *act_buffer = NULL;
size_t act_size = 0;
uncompress_buffer(map, block.size, block.data,
uncompress_buffer(map, block->size, block->data,
transf->special.block_meta,
sfd->compress_buffer, &act_size,
&act_buffer);
......@@ -1745,6 +1821,42 @@ void close_rclosed_pipes(struct fd_translation_map *map)
}
}
void extend_shm_shadow(struct fd_translation_map *map, struct shadow_fd *sfd,
size_t new_size)
{
if (sfd->buffer_size >= new_size) {
return;
}
// Verify that the file size actually increased
struct stat st;
int fs = fstat(sfd->fd_local, &st);
if (fs == -1) {
wp_log(WP_ERROR, "Checking file size failed: %s",
strerror(errno));
return;
}
if ((size_t)st.st_size < new_size) {
wp_log(WP_ERROR,
"Trying to resize file larger (%d) than the actual file size (%d), ignoring",
(int)new_size, (int)st.st_size);
return;
}
size_t old_size = sfd->buffer_size;
increase_buffer_sizes(map, sfd, new_size);
sfd->has_been_extended = true;
sfd->is_dirty = true;
struct ext_interval fresh = {
.start = (int)old_size,
.width = (int)(new_size - old_size),
.rep = 1,
.stride = 0,
};
merge_damage_records(&sfd->damage, 1, &fresh);
}
static void *worker_thread_main(void *arg)
{
struct thread_data *data = arg;
......
......@@ -78,7 +78,7 @@ static int update_file(int file_fd, struct gbm_bo *bo, size_t sz, int seqno)
memset((char *)data + start, seqno, end - start);
munmap(data, sz);
return end - start;
return (int)(end - start);
}
static int update_dmabuf(int file_fd, struct gbm_bo *bo, size_t sz, int seqno)
......@@ -105,25 +105,26 @@ static int update_dmabuf(int file_fd, struct gbm_bo *bo, size_t sz, int seqno)
memset((char *)data + start, seqno, end - start);
unmap_dmabuf(bo, map_handle);
return end - start;
return (int)(end - start);
}
static void combine_transfer_blocks(struct transfer *transfer,
struct bytebuf *ret_block, int nblocks, struct bytebuf *blocks)
static void combine_transfer_blocks(struct bytebuf_stack *blocks)
{
size_t net_size = 0;
for (int i = 0; i < nblocks; i++) {
net_size += blocks[i].size;
for (int i = 0; i < blocks->count; i++) {
net_size += blocks->data[i].size;
}
ret_block->size = net_size;
ret_block->data = malloc(net_size);
struct bytebuf ret_block;
ret_block.size = net_size;
ret_block.data = malloc(net_size);
size_t pos = 0;
for (int i = 0; i < nblocks; i++) {
memcpy(ret_block->data + pos, blocks[i].data, blocks[i].size);
pos += blocks[i].size;
for (int i = 0; i < blocks->count; i++) {
memcpy(ret_block.data + pos, blocks->data[i].data,
blocks->data[i].size);
pos += blocks->data[i].size;
}
transfer->subtransfers = ret_block;
transfer->nblocks = 1;
blocks->data[0] = ret_block;
blocks->count = 1;
}
static bool check_match(int orig_fd, int copy_fd, struct gbm_bo *orig_bo,
......@@ -184,35 +185,44 @@ static bool test_transfer(struct fd_translation_map *src_map,
struct fd_translation_map *dst_map, int rid, int ndiff,
struct render_data *render_data)
{
int ntransfers = 0, nblocks = 0;
struct transfer transfers[10];
struct bytebuf blocks[20];
struct transfer_stack transfers;
transfers.size = 1;
transfers.count = 0;
transfers.data = calloc(1, sizeof(struct transfer));
struct bytebuf_stack blocks;
blocks.size = 1;
blocks.count = 0;
blocks.data = calloc(1, sizeof(struct bytebuf));
struct shadow_fd *src_shadow = get_shadow_for_rid(src_map, rid);
collect_update(src_map, src_shadow, &ntransfers, transfers, &nblocks,
blocks);
collect_update(src_map, src_shadow, &transfers, &blocks);
if (ndiff == 0) {
if (ntransfers > 0) {
free(transfers.data);
free(blocks.data);
if (transfers.count > 0) {
wp_log(WP_ERROR,
"Collecting updates gave a transfer when none was expected",
ntransfers);
transfers.count);
return false;
}
return true;
}
if (ntransfers != 1) {
if (transfers.count != 1) {
wp_log(WP_ERROR,
"Collecting updates gave a unexpected number (%d) of transfers",
ntransfers);
transfers.count);
free(transfers.data);
free(blocks.data);
return false;
}
struct transfer res_transfer = transfers[0];
struct bytebuf ret_block;
combine_transfer_blocks(&res_transfer, &ret_block, nblocks, blocks);
apply_update(dst_map, render_data, &res_transfer);
free(ret_block.data);
combine_transfer_blocks(&blocks);
apply_update(dst_map, render_data, &transfers.data[0], &blocks.data[0]);
free(blocks.data[0].data);
free(transfers.data);
free(blocks.data);
/* first round, this only exists after the transfer */
struct shadow_fd *dst_shadow = get_shadow_for_rid(dst_map, rid);
......
/*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#define _GNU_SOURCE
#include "util.h"
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
struct copy_setup {
int conn;
int wayl;
bool is_display_side;
struct main_config *mc;
};
void *start_looper(void *data)
{
struct copy_setup *setup = (struct copy_setup *)data;
main_interface_loop(setup->conn, setup->wayl, setup->mc,
setup->is_display_side);
return NULL;
}
static void atomic_logger(const char *file, int line, enum log_level level,
const char *fmt, ...)
{
pthread_t tid = pthread_self();
char msg[1024];
int nwri = 0;
nwri += sprintf(msg + nwri, "%lx [%s:%3d] ", (long)tid, file, line);
va_list args;
va_start(args, fmt);
nwri += vsnprintf(msg + nwri, (size_t)(1022 - nwri), fmt, args);
va_end(args);
msg[nwri++] = '\n';
msg[nwri] = 0;
write(STDOUT_FILENO, msg, (size_t)nwri);
(void)level;
}
log_handler_func_t log_funcs[2] = {NULL, NULL};
int main(int argc, char **argv)
{
if (argc == 1 || !strcmp(argv[1], "--help")) {
printf("Usage: ./fuzz_hook [--log] {input_file}\n");
printf("A program to run and control inputs for a linked client/server pair, from a file.\n");
return EXIT_FAILURE;
}
if (argc > 1 && !strcmp(argv[1], "--log")) {
log_funcs[0] = atomic_logger;
log_funcs[1] = atomic_logger;
argc--;
argv++;
}
int fd = open(argv[1], O_RDONLY);
if (fd == -1) {
printf("Failed to open '%s'", argv[1]);
return EXIT_FAILURE;
}
long len = lseek(fd, 0, SEEK_END);
if (len == 0) {
close(fd);
return EXIT_SUCCESS;
}
lseek(fd, 0, SEEK_SET);
char *buf = malloc(len);
read(fd, buf, len);
close(fd);
printf("Loaded %ld bytes\n", len);
int srv_fds[2], cli_fds[2], conn_fds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, srv_fds) == -1 ||
socketpair(AF_UNIX, SOCK_STREAM, 0, cli_fds) == -1 ||
socketpair(AF_UNIX, SOCK_STREAM, 0, conn_fds) == -1) {
printf("Socketpair failed\n");
return EXIT_FAILURE;
}
struct main_config config = {.drm_node = NULL,
.n_worker_threads = 1,
.compression = COMP_NONE,
.no_gpu = true, /* until we can construct dmabufs here
*/
.linear_dmabuf = false,
.video_if_possible = true};
pthread_t thread_a, thread_b;
struct copy_setup server_conf = {.conn = conn_fds[0],
.wayl = srv_fds[1],
.is_display_side = true,
.mc = &config};
struct copy_setup client_conf = {.conn = conn_fds[1],
.wayl = cli_fds[1],
.is_display_side = false,
.mc = &config};
if (pthread_create(&thread_a, NULL, start_looper, &server_conf) == -1) {
printf("Thread failed\n");
}
if (pthread_create(&thread_b, NULL, start_looper, &client_conf) == -1) {
printf("Thread failed\n");
}
char *ignore_buf = malloc(65536);
/* Main loop: RW from socketpairs with sendmsg, with short wait */
long file_nwords = len / 4;
long cursor = 0;
uint32_t *data = (uint32_t *)buf;
char template[256];
while (cursor < file_nwords) {
uint32_t header = data[cursor++];
bool to_server = header & 0x1;
bool add_file = header & 0x2;
int new_fileno = -1;
if (add_file && cursor < file_nwords) {
uint32_t fsize = data[cursor++];
if (fsize == 0) {
/* 'copy' sink */
new_fileno = open("/dev/null", O_WRONLY);
if (new_fileno == -1) {
wp_log(WP_ERROR,
"Failed to open /dev/null");
}
} else {
/* avoid buffer overflow */
fsize = fsize > 1000000 ? 1000000 : fsize;
/* This should be a tmpfs */
#if defined(__linux__)
sprintf(template, "%x:%x", (uint32_t)cursor,
fsize);
new_fileno = memfd_create(template, 0);
#else
/* WARNING: this can be rather file-system
* intensive */
strcpy(template, "/tmp/fuzz_hook_XXXXXX");
new_fileno = mkstemp(template);
unlink(template);
#endif
if (new_fileno == -1) {
wp_log(WP_ERROR, "Failed to mkstemp");
} else if (ftruncate(new_fileno, fsize) == -1) {
wp_log(WP_ERROR,
"Failed to resize tempfile");
close(new_fileno);
new_fileno = -1;
}
}
}
uint32_t packet_size = header >> 2;
if ((long)packet_size > file_nwords - cursor) {
packet_size = (uint32_t)(file_nwords - cursor);
}
if (packet_size > 2048) {
packet_size = 2048;
}
/* 2 msec max delay for 8KB of data, assuming no system
* interference, should be easily attainable */
int max_write_delay_ms = 1;
int max_read_delay_ms = 2;
int send_fd = to_server ? srv_fds[0] : cli_fds[0];
/* Write packet to stream */
struct pollfd write_pfd;
write_pfd.fd = send_fd;
write_pfd.events = POLLOUT;
int nw;
retry_poll:
nw = poll(&write_pfd, 1, max_write_delay_ms);
if (nw == -1) {
if (new_fileno != -1) {
close(new_fileno);
}
if (errno == EINTR) {
goto retry_poll;
}
printf("Poll error\n");
break;
} else if (nw == 1) {
/* Send message */
struct iovec the_iovec;
the_iovec.iov_len = packet_size * 4;
the_iovec.iov_base = (char *)&data[cursor];
struct msghdr msg;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &the_iovec;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
union {
char buf[CMSG_SPACE(sizeof(int))];
struct cmsghdr align;
} uc;
memset(uc.buf, 0, sizeof(uc.buf));
if (new_fileno != -1) {
msg.msg_control = uc.buf;
msg.msg_controllen = sizeof(uc.buf);
struct cmsghdr *frst = CMSG_FIRSTHDR(&msg);
frst->cmsg_level = SOL_SOCKET;
frst->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(frst), &new_fileno,
sizeof(int));
frst->cmsg_len = CMSG_LEN(sizeof(int));
msg.msg_controllen = CMSG_SPACE(sizeof(int));
}
int target_fd = to_server ? srv_fds[0] : cli_fds[0];
ssize_t ret = sendmsg(target_fd, &msg, 0);
if (ret == -1) {
wp_log(WP_ERROR, "Error in sendmsg");
break;
}
} else {
wp_log(WP_ERROR,
"Failed to send message before timeout");
}
if (new_fileno != -1) {
close(new_fileno);
}
/* Wait up to max_delay for a response. Almost all packets
* should be passed on unmodified; a very small fraction
* are dropped */
struct pollfd read_pfds[2];
read_pfds[0].fd = srv_fds[0];
read_pfds[1].fd = cli_fds[0];
read_pfds[0].events = POLLIN;
read_pfds[1].events = POLLIN;
int nr = poll(read_pfds, 2,
packet_size > 0 ? max_read_delay_ms : 0);
if (nr == -1) {
if (errno == EINTR) {
continue;
}
printf("Poll error\n");
break;
} else if (nr == 0) {
wp_log(WP_DEBUG, "No reply to sent packet %d",
packet_size);
}
for (int i = 0; i < 2; i++) {
if (read_pfds[i].revents & POLLIN) {
char cmsgdata[(CMSG_LEN(28 * sizeof(int32_t)))];
struct iovec the_iovec;
the_iovec.iov_len = 65536;
the_iovec.iov_base = ignore_buf;
struct msghdr msg;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &the_iovec;
msg.msg_iovlen = 1;
msg.msg_control = &cmsgdata;
msg.msg_controllen = sizeof(cmsgdata);
msg.msg_flags = 0;
ssize_t ret = recvmsg(read_pfds[i].fd, &msg, 0);
if (ret == -1) {
wp_log(WP_ERROR, "Error in recvmsg");
}
}
}
cursor += packet_size;
}
close(srv_fds[0]);
close(cli_fds[0]);
pthread_join(thread_a, NULL);
pthread_join(thread_b, NULL);
free(buf);
free(ignore_buf);
return EXIT_SUCCESS;
}