diff options
Diffstat (limited to 'libs/cairo-1.16.0/util/cairo-sphinx/sphinx.c')
-rw-r--r-- | libs/cairo-1.16.0/util/cairo-sphinx/sphinx.c | 1545 |
1 files changed, 1545 insertions, 0 deletions
diff --git a/libs/cairo-1.16.0/util/cairo-sphinx/sphinx.c b/libs/cairo-1.16.0/util/cairo-sphinx/sphinx.c new file mode 100644 index 0000000..238d400 --- /dev/null +++ b/libs/cairo-1.16.0/util/cairo-sphinx/sphinx.c @@ -0,0 +1,1545 @@ +/* + * The intention for sphinx is for detection of rendering errors inside + * applications by simultaneously rendering on to the target device and on + * an image surface and comparing the two. If it found a discrepancy, it + * would then dump the trace that reproduces the error. (Then apply + * delta-debugging to reduce that down to a minimal trace.) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/poll.h> +#include <sys/un.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <assert.h> +#include <pthread.h> + +#include <cairo.h> +#include <cairo-script.h> +#include <cairo-script-interpreter.h> +#include <cairo-boilerplate.h> + +#include <glib.h> /* for checksumming */ + +#ifndef CAIRO_HAS_REAL_PTHREAD +# error "cairo-sphinx needs real pthreads" +#endif + +#ifndef MAP_NORESERVE +#define MAP_NORESERVE 0 +#endif + +#define DATA_SIZE (256 << 20) +#define SHM_PATH_XXX "/shmem-cairo-sphinx" + +struct client { + int sk; + const cairo_boilerplate_target_t *target; + cairo_surface_t *surface; + void *base; + + cairo_script_interpreter_t *csi; + struct context_closure { + struct context_closure *next; + unsigned long id; + cairo_t *context; + cairo_surface_t *surface; + cairo_surface_t *original; + } *contexts; + + unsigned long context_id; +}; + +struct surface_tag { + long width, height; +}; +static const cairo_user_data_key_t surface_tag; + +static int +client_socket (const char *socket_path); + +static int +writen (int fd, const void *ptr, int len) +{ +#if 1 + const uint8_t *data = ptr; + while (len) { + int ret = write (fd, data, len); + if (ret < 0) { + switch (errno) { + case EAGAIN: + case EINTR: + continue; + default: + return FALSE; + } + } else if (ret == 0) { + return FALSE; + } else { + data += ret; + len -= ret; + } + } + return TRUE; +#else + int ret = send (fd, ptr, len, 0); + return ret == len; +#endif +} + +static int +readn (int fd, void *ptr, int len) +{ +#if 0 + uint8_t *data = ptr; + while (len) { + int ret = read (fd, data, len); + if (ret < 0) { + switch (errno) { + case EAGAIN: + case EINTR: + continue; + default: + return FALSE; + } + } else if (ret == 0) { + return FALSE; + } else { + data += ret; + len -= ret; + } + } + return TRUE; +#else + int ret = recv (fd, ptr, len, MSG_WAITALL); + return ret == len; +#endif +} +static int +open_devnull_to_fd (int want_fd, int flags) +{ + int error; + int got_fd; + + close (want_fd); + + got_fd = open("/dev/null", flags | O_CREAT, 0700); + if (got_fd == -1) + return -1; + + error = dup2 (got_fd, want_fd); + close (got_fd); + + return error; +} + +static int +daemonize (void) +{ + void (*oldhup) (int); + + /* Let the parent go. */ + switch (fork ()) { + case -1: return -1; + case 0: break; + default: _exit (0); + } + + /* Become session leader. */ + if (setsid () == -1) + return -1; + + /* Refork to yield session leadership. */ + oldhup = signal (SIGHUP, SIG_IGN); + switch (fork ()) { + case -1: return -1; + case 0: break; + default: _exit (0); + } + signal (SIGHUP, oldhup); + + /* Establish stdio. */ + if (open_devnull_to_fd (0, O_RDONLY) == -1) + return -1; + if (open_devnull_to_fd (1, O_WRONLY | O_APPEND) == -1) + return -1; + if (dup2 (1, 2) == -1) + return -1; + + return 0; +} + +static int +server_socket (const char *socket_path) +{ + long flags; + struct sockaddr_un addr; + int sk; + + unlink (socket_path); + + sk = socket (PF_UNIX, SOCK_STREAM, 0); + if (sk == -1) + return -1; + + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strcpy (addr.sun_path, socket_path); + if (bind (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) { + close (sk); + return -1; + } + + flags = fcntl (sk, F_GETFL); + if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) { + close (sk); + return -1; + } + + if (listen (sk, 5) == -1) { + close (sk); + return -1; + } + + return sk; +} + +static int +readline (int fd, char *line, int max) +{ + int len = 0; + do { + int ret = read (fd, &line[len], 1); + if (ret <= 0) + return -1; + } while (line[len] != '\n' && ++len < max); + line[len] = '\0'; + return len; +} + +struct clients { + int count, size; + int complete; + + cairo_surface_t *recording; + unsigned long serial; + + struct client_info { + int sk; + int trace; + unsigned long image_serial; + cairo_surface_t *image; + char *name; + char *target; + char *reference; + + uint8_t *out_buf; + int out_len; + int out_size; + } *clients; + const char *shm_path; + unsigned long offset; + uint8_t *base; +}; + +static void * +clients_shm (const char *shm_path) +{ + void *base; + int fd; + + shm_unlink (shm_path); + fd = shm_open (shm_path, O_RDWR | O_EXCL | O_CREAT, 0777); + if (fd == -1) + return MAP_FAILED; + + if (ftruncate (fd, DATA_SIZE) == -1) { + close (fd); + return MAP_FAILED; + } + + base = mmap (NULL, DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + close (fd); + + return base; +} + +static int +clients_init (struct clients *clients) +{ + clients->count = 0; + clients->complete = 0; + clients->size = 4; + clients->clients = xmalloc (clients->size * sizeof (struct client_info)); + + clients->shm_path = SHM_PATH_XXX; + clients->base = clients_shm (clients->shm_path); + if (clients->base == MAP_FAILED) + return -1; + clients->offset = 0; + + clients->recording = NULL; + clients->serial = 0; + + return 0; +} + +static void +clients_add_command (struct clients *clients, int fd, char *info) +{ + struct client_info *c; + char buf[1024]; + int len; + char *str; + + if (clients->count == clients->size) { + clients->size *= 2; + clients->clients = xrealloc (clients->clients, + clients->size * sizeof (struct client_info)); + } + + c = &clients->clients[clients->count++]; + c->sk = fd; + c->trace = -1; + c->image_serial = 0; + c->image = NULL; + c->name = c->target = c->reference = NULL; + + c->out_size = 8192; + c->out_buf = xmalloc (c->out_size); + c->out_len = 0; + + str = strstr (info, "name="); + if (str != NULL) { + char *sp = strchr (str + 5, ' '); + int len; + if (sp) + len = sp - str - 5; + else + len = strlen (str + 5); + c->name = xmalloc (len + 1); + memcpy (c->name, str + 5, len); + c->name[len] = '\0'; + } + + str = strstr (info, "target="); + if (str != NULL) { + char *sp = strchr (str + 7, ' '); + int len; + if (sp) + len = sp - str - 7; + else + len = strlen (str + 7); + c->target = xmalloc (len + 1); + memcpy (c->target, str + 7, len); + c->target[len] = '\0'; + } + + str = strstr (info, "reference="); + if (str != NULL) { + char *sp = strchr (str + 10, ' '); + int len; + if (sp) + len = sp - str - 10; + else + len = strlen (str + 10); + c->reference = xmalloc (len + 1); + memcpy (c->reference, str + 10, len); + c->reference[len] = '\0'; + } + + len = sprintf (buf, "%s\n", clients->shm_path); + writen (fd, buf, len); +} + +static void +clients_add_trace (struct clients *clients, int fd, char *info) +{ + char *str, *sp; + char *name; + int i; + + str = strstr (info, "name="); + assert (str != NULL); + sp = strchr (str + 5, ' '); + if (sp) + i = sp - str - 5; + else + i = strlen (str + 5); + + name = xmalloc (i + 1); + memcpy (name, str + 5, i); + name[i] = '\0'; + + for (i = 0; i < clients->count; i++) { + struct client_info *c = &clients->clients[i]; + if (strcmp (name, c->name) == 0) { + c->trace = fd; + break; + } + } + + free (name); +} + +static int +clients_image (struct clients *clients, int fd, char *info) +{ + struct client_info *c = NULL; + int format, width, height, stride, size; + int i; + + for (i = 0; i < clients->count; i++) { + if (clients->clients[i].sk == fd) { + c = &clients->clients[i]; + break; + } + } + + if (c == NULL) + return 0; + + if (sscanf (info, "%lu %d %d %d %d", + &c->image_serial, &format, &width, &height, &stride) != 5) + { + return 0; + } + + size = height * stride; + size = (size + 4095) & -4096; + assert (clients->offset + size <= DATA_SIZE); + + c->image = + cairo_image_surface_create_for_data (clients->base + clients->offset, + format, width, height, stride); + + if (! writen (fd, &clients->offset, sizeof (clients->offset))) + return 0; + + clients->offset += size; + + return 1; +} + +static int +u8_cmp (const void *A, const void *B) +{ + const uint8_t *a = A, *b = B; + return (int) *a - (int) *b; +} + +static uint8_t +median (uint8_t *values, int count) +{ + /* XXX could use a fast median here if we cared */ + qsort (values, count, 1, u8_cmp); + return values[count/2]; +} + +static uint32_t +get_pixel32 (int x, int y, const uint8_t *data, int stride) +{ + return ((uint32_t *)(data + y * stride))[x]; +} + +static uint8_t +get_median_32 (int x, int y, int channel, + const uint8_t *data, int width, int height, int stride) +{ + uint8_t neighbourhood[25]; + int cnt = 0; + int xx, yy; + + for (yy = y - 2; yy <= y + 2; yy++) { + if (yy < 0) + continue; + if (yy >= height) + continue; + + for (xx = x - 2; xx <= x + 2; xx++) { + if (xx < 0) + continue; + if (xx >= width) + continue; + + neighbourhood[cnt++] = (get_pixel32 (xx, yy, data, stride) >> (channel*8)) & 0xff; + } + } + + return median (neighbourhood, cnt); +} + +static uint8_t +get_pixel8 (int x, int y, const uint8_t *data, int stride) +{ + return data[y * stride + x]; +} + +static uint8_t +get_median_8 (int x, int y, const uint8_t *data, int width, int height, int stride) +{ + uint8_t neighbourhood[25]; + int cnt = 0; + int xx, yy; + + for (yy = y - 2; yy <= y + 2; yy++) { + if (yy < 0) + continue; + if (yy >= height) + continue; + + for (xx = x - 2; xx <= x + 2; xx++) { + if (xx < 0) + continue; + if (xx >= width) + continue; + + neighbourhood[cnt++] = get_pixel8 (xx, yy, data, stride); + } + } + + return median (neighbourhood, cnt); +} + +static cairo_bool_t +compare_images (cairo_surface_t *a, + cairo_surface_t *b) +{ + int width, height, stride; + const uint8_t *aa, *bb; + int x, y; + + if (cairo_surface_status (a) || cairo_surface_status (b)) + return FALSE; + + if (cairo_surface_get_type (a) != cairo_surface_get_type (b)) + return FALSE; + + if (cairo_image_surface_get_format (a) != cairo_image_surface_get_format (b)) + return FALSE; + + if (cairo_image_surface_get_width (a) != cairo_image_surface_get_width (b)) + return FALSE; + + if (cairo_image_surface_get_height (a) != cairo_image_surface_get_height (b)) + return FALSE; + + if (cairo_image_surface_get_stride (a) != cairo_image_surface_get_stride (b)) + return FALSE; + + + width = cairo_image_surface_get_width (a); + height = cairo_image_surface_get_height (a); + stride = cairo_image_surface_get_stride (a); + + aa = cairo_image_surface_get_data (a); + bb = cairo_image_surface_get_data (b); + switch (cairo_image_surface_get_format (a)) { + case CAIRO_FORMAT_ARGB32: + for (y = 0; y < height; y++) { + const uint32_t *ua = (uint32_t *) aa; + const uint32_t *ub = (uint32_t *) bb; + for (x = 0; x < width; x++) { + if (ua[x] != ub[x]) { + int channel; + + for (channel = 0; channel < 4; channel++) { + unsigned va, vb, diff; + + va = (ua[x] >> (channel*8)) & 0xff; + vb = (ub[x] >> (channel*8)) & 0xff; + diff = abs (va - vb); + if (diff > 1) { + va = get_median_32 (x, y, channel, aa, width, height, stride); + vb = get_median_32 (x, y, channel, bb, width, height, stride); + diff = abs (va - vb); + if (diff > 1) + return FALSE; + } + } + } + } + aa += stride; + bb += stride; + } + break; + + case CAIRO_FORMAT_RGB24: + for (y = 0; y < height; y++) { + const uint32_t *ua = (uint32_t *) aa; + const uint32_t *ub = (uint32_t *) bb; + for (x = 0; x < width; x++) { + if ((ua[x] & 0x00ffffff) != (ub[x] & 0x00ffffff)) { + int channel; + + for (channel = 0; channel < 3; channel++) { + unsigned va, vb, diff; + + va = (ua[x] >> (channel*8)) & 0xff; + vb = (ub[x] >> (channel*8)) & 0xff; + diff = abs (va - vb); + if (diff > 1) { + va = get_median_32 (x, y, channel, aa, width, height, stride); + vb = get_median_32 (x, y, channel, bb, width, height, stride); + diff = abs (va - vb); + if (diff > 1) + return FALSE; + } + } + } + } + aa += stride; + bb += stride; + } + break; + + case CAIRO_FORMAT_A8: + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + if (aa[x] != bb[x]) { + unsigned diff = abs (aa[x] - bb[x]); + if (diff > 1) { + uint8_t va, vb; + + va = get_median_8 (x, y, aa, width, height, stride); + vb = get_median_8 (x, y, bb, width, height, stride); + diff = abs (va - vb); + if (diff > 1) + return FALSE; + } + + } + } + aa += stride; + bb += stride; + } + break; + + case CAIRO_FORMAT_A1: + width /= 8; + for (y = 0; y < height; y++) { + if (memcmp (aa, bb, width)) + return FALSE; + aa += stride; + bb += stride; + } + break; + + case CAIRO_FORMAT_INVALID: + case CAIRO_FORMAT_RGB16_565: /* XXX */ + break; + } + + return TRUE; +} + +static int +check_images (struct clients *clients) +{ + int i, j; + + for (i = 0; i < clients->count; i++) { + struct client_info *c = &clients->clients[i]; + + if (c->reference == NULL) + continue; + + for (j = 0; j < clients->count; j++) { + struct client_info *ref = &clients->clients[j]; + + if (strcmp (c->reference, ref->name)) + continue; + + if (! compare_images (c->image, ref->image)) + return 0; + } + } + + return 1; +} + +static gchar * +checksum (const char *filename) +{ + gchar *str = NULL; + gchar *data; + gsize len; + + if (g_file_get_contents (filename, &data, &len, NULL)) { + str = g_compute_checksum_for_data (G_CHECKSUM_SHA1, (guchar *) data, len); + g_free (data); + } + + return str; +} + +static void +write_trace (struct clients *clients) +{ + cairo_device_t *ctx; + gchar *csum; + char buf[4096]; + int i; + + mkdir ("output", 0777); + + ctx = cairo_script_create ("output/cairo-sphinx.trace"); + cairo_script_from_recording_surface (ctx, clients->recording); + cairo_device_destroy (ctx); + + csum = checksum ("output/cairo-sphinx.trace"); + + sprintf (buf, "output/%s.trace", csum); + if (! g_file_test (buf, G_FILE_TEST_EXISTS)) { + rename ("output/cairo-sphinx.trace", buf); + + sprintf (buf, "output/%s.recording.png", csum); + cairo_surface_write_to_png (clients->recording, buf); + + for (i = 0; i < clients->count; i++) { + struct client_info *c = &clients->clients[i]; + if (c->image != NULL) { + sprintf (buf, "output/%s.%s.png", csum, c->name); + cairo_surface_write_to_png (c->image, buf); + } + } + } +} + +static void +clients_complete (struct clients *clients, int fd) +{ + int i; + + for (i = 0; i < clients->count; i++) { + if (clients->clients[i].sk == fd) { + break; + } + } + if (i == clients->count) + return; + + if (++clients->complete != clients->count) + return; + + clients->offset = 0; + clients->complete = 0; + + if (! check_images (clients)) + write_trace (clients); + + /* ack */ + for (i = 0; i < clients->count; i++) { + struct client_info *c = &clients->clients[i]; + + cairo_surface_destroy (c->image); + c->image = NULL; + + if (! writen (c->sk, &clients->serial, sizeof (clients->serial))) + continue; + + c->image_serial = 0; + } + + clients->recording = NULL; + clients->serial = 0; +} + +static void +clients_recording (struct clients *clients, int fd, char *info) +{ + sscanf (info, "%p %lu", &clients->recording, &clients->serial); + clients_complete (clients, fd); +} + +static void +clients_remove (struct clients *clients, int fd) +{ + int i, j; + + for (i = 0; i < clients->count; i++) { + struct client_info *c = &clients->clients[i]; + if (c->sk == fd) { + free (c->out_buf); + break; + } + } + + for (j = i++; i < clients->count; i++) + clients->clients[j] = clients->clients[i]; + + clients->count = j; +} + +static void +clients_send_trace (struct clients *clients, + const char * const line, const int len) +{ + int i; + + for (i = 0; i < clients->count; i++) { + struct client_info *c = &clients->clients[i]; + int ret, rem = len; + + if (c->trace == -1) + continue; + + if (c->out_len) { + ret = write (c->trace, c->out_buf, c->out_len); + if (ret > 0) { + c->out_len -= ret; + if (c->out_len) + memmove (c->out_buf, c->out_buf + ret, c->out_len); + } + } + + if (! c->out_len) { + ret = write (c->trace, line, rem); + if (ret > 0) + rem -= ret; + } + + if (rem) { + if (c->out_len + rem > c->out_size) { + c->out_size *= 2; + c->out_buf = xrealloc (c->out_buf, c->out_size); + } + + memcpy (c->out_buf + c->out_len, line, rem); + c->out_len += rem; + } + } +} + +static void +clients_fini (struct clients *clients) +{ + shm_unlink (clients->shm_path); + munmap (clients->base, DATA_SIZE); + free (clients->clients); +} + +static int +nonblocking (int fd) +{ + long flags; + + flags = fcntl (fd, F_GETFL); + if (flags == -1) + return -1; + + return fcntl (fd, F_SETFL, flags | O_NONBLOCK); +} + +static void * +request_image (struct client *c, + struct context_closure *closure, + cairo_format_t format, + int width, int height, int stride) +{ + char buf[1024]; + unsigned long offset = -1; + int len; + + assert (format != CAIRO_FORMAT_INVALID); + + len = sprintf (buf, ".image %lu %d %d %d %d\n", + closure->id, format, width, height, stride); + writen (c->sk, buf, len); + + readn (c->sk, &offset, sizeof (offset)); + if (offset == (unsigned long) -1) + return NULL; + + return (uint8_t *) c->base + offset; +} + +static cairo_format_t +format_for_content (cairo_content_t content) +{ + switch (content) { + case CAIRO_CONTENT_ALPHA: + return CAIRO_FORMAT_A8; + case CAIRO_CONTENT_COLOR: + return CAIRO_FORMAT_RGB24; + default: + case CAIRO_CONTENT_COLOR_ALPHA: + return CAIRO_FORMAT_ARGB32; + } +} + +static void +get_surface_size (cairo_surface_t *surface, + int *width, int *height, + cairo_format_t *format) +{ + if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE) { + *width = cairo_image_surface_get_width (surface); + *height = cairo_image_surface_get_height (surface); + *format = cairo_image_surface_get_format (surface); + } else { + struct surface_tag *tag; + + tag = cairo_surface_get_user_data (surface, &surface_tag); + if (tag != NULL) { + *width = tag->width; + *height = tag->height; + } else { + double x0, x1, y0, y1; + cairo_t *cr; + + /* presumably created using cairo_surface_create_similar() */ + cr = cairo_create (surface); + cairo_clip_extents (cr, &x0, &y0, &x1, &y1); + cairo_destroy (cr); + + tag = xmalloc (sizeof (*tag)); + *width = tag->width = ceil (x1 - x0); + *height = tag->height = ceil (y1 - y0); + + if (cairo_surface_set_user_data (surface, &surface_tag, tag, free)) + exit (-1); + } + } +} + + +static void +send_surface (struct client *c, + struct context_closure *closure) +{ + cairo_surface_t *source = closure->surface; + cairo_surface_t *image; + cairo_format_t format = CAIRO_FORMAT_INVALID; + cairo_t *cr; + int width, height, stride; + void *data; + unsigned long serial; + + get_surface_size (source, &width, &height, &format); + if (format == CAIRO_FORMAT_INVALID) + format = format_for_content (cairo_surface_get_content (source)); + + stride = cairo_format_stride_for_width (format, width); + + data = request_image (c, closure, format, width, height, stride); + if (data == NULL) + exit (-1); + + image = cairo_image_surface_create_for_data (data, + format, + width, height, + stride); + cr = cairo_create (image); + cairo_surface_destroy (image); + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface (cr, source, 0, 0); + cairo_paint (cr); + cairo_destroy (cr); + + /* signal completion */ + writen (c->sk, ".complete\n", strlen (".complete\n")); + + /* wait for image check */ + serial = 0; + readn (c->sk, &serial, sizeof (serial)); + if (serial != closure->id) + exit (-1); +} + +static void +send_recording (struct client *c, + struct context_closure *closure) +{ + cairo_surface_t *source = closure->surface; + char buf[1024]; + int len; + unsigned long serial; + + assert (cairo_surface_get_type (source) == CAIRO_SURFACE_TYPE_RECORDING); + len = sprintf (buf, ".recording %p %lu\n", source, closure->id); + writen (c->sk, buf, len); + + /* wait for image check */ + + serial = 0; + readn (c->sk, &serial, sizeof (serial)); + if (serial != closure->id) + exit (-1); +} + +static cairo_surface_t * +_surface_create (void *closure, + cairo_content_t content, + double width, double height, + long uid) +{ + struct client *c = closure; + cairo_surface_t *surface; + + surface = cairo_surface_create_similar (c->surface, + content, width, height); + if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE) { + struct surface_tag *tag; + + tag = xmalloc (sizeof (*tag)); + tag->width = width; + tag->height = height; + if (cairo_surface_set_user_data (surface, &surface_tag, tag, free)) + exit (-1); + } + + return surface; +} + +static cairo_t * +_context_create (void *closure, cairo_surface_t *surface) +{ + struct client *c = closure; + struct context_closure *l; + cairo_bool_t foreign = FALSE; + + l = xmalloc (sizeof (*l)); + l->next = c->contexts; + l->surface = surface; + l->original = cairo_surface_reference (surface); + l->id = ++c->context_id; + if (l->id == 0) + l->id = ++c->context_id; + c->contexts = l; + + /* record everything, including writes to images */ + if (c->target == NULL) { + if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_RECORDING) { + cairo_format_t format; + int width, height; + + get_surface_size (surface, &width, &height, &format); + l->surface = cairo_surface_create_similar (c->surface, + cairo_surface_get_content (surface), + width, height); + foreign = TRUE; + } + } + + l->context = cairo_create (l->surface); + if (foreign) { + cairo_set_source_surface (l->context, surface, 0, 0); + cairo_paint (l->context); + } + + return l->context; +} + +static void +_context_destroy (void *closure, void *ptr) +{ + struct client *c = closure; + struct context_closure *l, **prev = &c->contexts; + + while ((l = *prev) != NULL) { + if (l->context == ptr) { + if (cairo_surface_status (l->surface) == CAIRO_STATUS_SUCCESS) { + if (c->target == NULL) + send_recording (c, l); + else + send_surface (c, l); + } else { + exit (-1); + } + + cairo_surface_destroy (l->original); + *prev = l->next; + free (l); + return; + } + prev = &l->next; + } +} + +static void * +recorder (void *arg) +{ + struct client client; + const cairo_script_interpreter_hooks_t hooks = { + .closure = &client, + .surface_create = _surface_create, + .context_create = _context_create, + .context_destroy = _context_destroy, + }; + char *buf; + int buf_size; + int len = 0, ret; + struct pollfd pfd; + + client.target = NULL; + client.sk = client_socket ("/tmp/cairo-sphinx"); + if (client.sk < 0) + return NULL; + + buf_size = 65536; + buf = xmalloc (buf_size); + + len = sprintf (buf, "client-command target=recording name=.recorder\n"); + if (! writen (client.sk, buf, len)) + return NULL; + + /* drain the shm_path */ + len = readline (client.sk, buf, buf_size); + + pfd.fd = client_socket ("/tmp/cairo-sphinx"); + if (pfd.fd < 0) + return NULL; + + len = sprintf (buf, "client-trace name=.recorder\n"); + if (! writen (pfd.fd, buf, len)) + return NULL; + + client.surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, + NULL); + + client.context_id = 0; + client.csi = cairo_script_interpreter_create (); + cairo_script_interpreter_install_hooks (client.csi, &hooks); + + nonblocking (pfd.fd); + pfd.events = POLLIN; + len = 0; + while (poll (&pfd, 1, -1) > 0) { + while ((ret = read (pfd.fd, buf + len, buf_size - len)) > 0) { + int end; + + if (ret == buf_size - len) { + buf_size *= 2; + buf = xrealloc (buf, buf_size); + } + len += ret; + + for (end = len; end > 0 && buf[--end] != '\n'; ) + ; + if (end > 0) { + buf[end] = '\0'; + cairo_script_interpreter_feed_string (client.csi, buf, end); + + len -= end + 1; + if (len) + memmove (buf, buf + end + 1, len); + } + } + if (ret == 0) + break; + if (! (errno == EAGAIN || errno == EINTR)) + break; + } + + cairo_script_interpreter_finish (client.csi); + cairo_script_interpreter_destroy (client.csi); + + cairo_surface_destroy (client.surface); + return NULL; +} + +static int +do_server (const char *path) +{ + pthread_t thread; + struct clients clients; + char line[4096]; + struct pollfd *pfd; + int num_pfd, size_pfd; + int n, cnt, ret = 1; + int sk, source = -1; + int waiter = -1, waiter_count = 0; + int len; + + signal (SIGPIPE, SIG_IGN); + + if (clients_init (&clients) < 0) { + fprintf (stderr, "Failed to initialise clients structure\n"); + return -1; + } + + sk = server_socket (path); + if (sk < 0) { + fprintf (stderr, "Failed to create server socket\n"); + return 1; + } + + if (daemonize () < 0) + return 1; + + if (pthread_create (&thread, NULL, recorder, NULL) < 0) { + fprintf (stderr, "Failed to create spawn recording thread\n"); + return 1; + } + + size_pfd = 4; + pfd = xmalloc (sizeof (*pfd) * size_pfd); + pfd[0].fd = sk; + pfd[0].events = POLLIN; + num_pfd = 1; + + while ((cnt = poll (pfd, num_pfd, -1)) > 0) { + int have_source; + + if (pfd[0].revents) { + while ((sk = accept (pfd[0].fd, NULL, NULL)) != -1) { + len = readline (sk, line, sizeof (line)); + if (strcmp (line, "source") == 0) { + + if (source != -1) + exit (1); + + source = sk; + if (nonblocking (sk) < 0) { + close (sk); + continue; + } + } else if (strncmp (line, "client-command", 14) == 0) { + if (source == -1) + clients_add_command (&clients, sk, line); + } else if (strncmp (line, "client-trace", 12) == 0) { + if (source == -1) { + clients_add_trace (&clients, sk, line); + if (nonblocking (sk) < 0) { + close (sk); + continue; + } + + if (clients.count == waiter_count) { + for (n = 1; n < num_pfd; n++) { + if (pfd[n].fd == waiter) { + pfd[n].fd = -1; + break; + } + } + close (waiter); + waiter_count = -1; + } + } + } else if (strncmp (line, "wait", 4) == 0) { + int count = atoi (line + 5) + 1; + if (clients.count == count) { + close (sk); + continue; + } else { + waiter = sk; + waiter_count = count; + } + } + + if (num_pfd == size_pfd) { + size_pfd *= 2; + pfd = xrealloc (pfd, sizeof (*pfd) * size_pfd); + } + + pfd[num_pfd].fd = sk; + pfd[num_pfd].events = POLLIN; + pfd[num_pfd].revents = 0; + num_pfd++; + } + cnt--; + } + + have_source = 0; + for (n = 1; cnt && n < num_pfd; n++) { + if (! pfd[n].revents) + continue; + cnt--; + + if (pfd[n].fd == -1) + continue; + + if (source == pfd[n].fd) { + have_source = n; + } else { + len = readline (pfd[n].fd, line, sizeof (line)); + if (len < 0) { + clients_remove (&clients, pfd[n].fd); + close (pfd[n].fd); + pfd[n].fd = -1; + continue; + } + + if (strncmp (line, ".image", 6) == 0) { + if (! clients_image (&clients, pfd[n].fd, line + 7)) { + clients_remove (&clients, pfd[n].fd); + close (pfd[n].fd); + pfd[n].fd = -1; + continue; + } + } else if (strncmp (line, ".complete", 9) == 0) { + clients_complete (&clients, pfd[n].fd); + } else if (strncmp (line, ".recording", 10) == 0) { + clients_recording (&clients, pfd[n].fd, line + 6); + } else { + printf ("do_command (%s)\n", line); + } + } + } + + if (have_source) { + do { + len = read (source, line, sizeof (line)); + if (len > 0) { + clients_send_trace (&clients, line, len); + } else if (len == 0) { + close (source); + pfd[have_source].fd = source = -1; + goto done; + } else + break; + } while (1); + } + + for (n = cnt = 1; n < num_pfd; n++) { + if (pfd[n].fd != -1) { + if (cnt != n) + pfd[cnt] = pfd[n]; + cnt++; + } + } + num_pfd = cnt; + } + +done: + ret = 0; + for (n = 0; n < num_pfd; n++) { + if (pfd[n].fd != -1) + close (pfd[n].fd); + } + free (pfd); + clients_fini (&clients); + + return ret; +} + +static void * +client_shm (const char *shm_path) +{ + void *base; + int fd; + + fd = shm_open (shm_path, O_RDWR, 0); + if (fd == -1) + return MAP_FAILED; + + base = mmap (NULL, DATA_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_NORESERVE, + fd, 0); + close (fd); + + return base; +} + +static int +client_socket (const char *socket_path) +{ + struct sockaddr_un addr; + int sk; + + sk = socket (PF_UNIX, SOCK_STREAM, 0); + if (sk == -1) + return -1; + + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strcpy (addr.sun_path, socket_path); + + if (connect (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) + return -1; + + return sk; +} + +static int +do_client (int fd, + const char *target, + const char *name, + const char *reference, + cairo_content_t content) +{ + struct client client; + const cairo_script_interpreter_hooks_t hooks = { + .closure = &client, + .surface_create = _surface_create, + .context_create = _context_create, + .context_destroy = _context_destroy, + }; + void *closure; + char *buf; + int buf_size; + int len = 0, ret; + struct pollfd pfd; + + client.sk = fd; + client.target = cairo_boilerplate_get_target_by_name (target, content); + client.context_id = 0; + + client.surface = client.target->create_surface (NULL, content, 1, 1, 1, 1, + CAIRO_BOILERPLATE_MODE_TEST, + &closure); + if (client.surface == NULL) { + fprintf (stderr, "Failed to create target surface: %s.\n", + client.target->name); + return 1; + } + + buf_size = 65536; + buf = xmalloc (buf_size); + + if (reference != NULL) { + len = sprintf (buf, + "client-command name=%s target=%s reference=%s\n", + name, target, reference); + } else { + len = sprintf (buf, + "client-command name=%s target=%s\n", + name, target); + } + if (! writen (fd, buf, len)) + return 1; + + len = readline (fd, buf, buf_size); + client.base = client_shm (buf); + if (client.base == MAP_FAILED) { + fprintf (stderr, "Failed to map shared memory segment '%s'.\n", buf); + return 1; + } + + if (daemonize () < 0) + return 1; + + pfd.fd = client_socket ("/tmp/cairo-sphinx"); + if (pfd.fd < 0) + return 1; + + len = sprintf (buf, "client-trace name=%s\n", name); + if (! writen (pfd.fd, buf, len)) + return 1; + + client.csi = cairo_script_interpreter_create (); + cairo_script_interpreter_install_hooks (client.csi, &hooks); + + nonblocking (pfd.fd); + pfd.events = POLLIN; + len = 0; + while (poll (&pfd, 1, -1) > 0) { + while ((ret = read (pfd.fd, buf + len, buf_size - len)) > 0) { + int end; + + if (ret == buf_size - len) { + buf_size *= 2; + buf = xrealloc (buf, buf_size); + } + len += ret; + + for (end = len; end > 0 && buf[--end] != '\n'; ) + ; + if (end > 0) { + buf[end] = '\0'; + cairo_script_interpreter_feed_string (client.csi, buf, end); + + len -= end + 1; + if (len) + memmove (buf, buf + end + 1, len); + } + } + if (ret == 0) + break; + if (! (errno == EAGAIN || errno == EINTR)) + break; + } + + cairo_script_interpreter_finish (client.csi); + cairo_script_interpreter_destroy (client.csi); + + cairo_surface_destroy (client.surface); + close (fd); + + return 0; +} + +static int +do_exec (int fd, char **argv) +{ + char buf[4096]; + + if (*argv == NULL) + return 0; + + snprintf (buf, sizeof (buf), "%s/cairo-trace.so", LIBDIR); + setenv ("LD_PRELOAD", buf, 1); + + snprintf (buf, sizeof (buf), "0"); + setenv ("CAIRO_TRACE_LINE_INFO", buf, 1); + + snprintf (buf, sizeof (buf), "%d", fd); + setenv ("CAIRO_TRACE_FD", buf, 1); + putenv (buf); + + return execvp (argv[0], argv); +} + +static int +do_wait (int fd) +{ + char buf; + int ret = read (fd, &buf, 1); + return ret != 0; +} + +int +main (int argc, char **argv) +{ + char buf[4096]; + int len; + int fd; + + if (argc == 1) + return do_server ("/tmp/cairo-sphinx"); + + fd = client_socket ("/tmp/cairo-sphinx"); + if (fd < 0) + return 1; + + if (strcmp (argv[1], "client") == 0) { + return do_client (fd, argv[2], argv[3], argv[4], + CAIRO_CONTENT_COLOR_ALPHA); + } + + if (strcmp (argv[1], "wait") == 0) { + len = snprintf (buf, sizeof (buf), "wait %s\n", argv[2]); + if (! writen (fd, buf, len)) + return 1; + + return do_wait (fd); + } + + if (strcmp (argv[1], "exec") == 0) { + len = snprintf (buf, sizeof (buf), "source\n"); + if (! writen (fd, buf, len)) + return 1; + + return do_exec (fd, argv+2); + } + + if (strcmp (argv[1], "replay") == 0) { + len = snprintf (buf, sizeof (buf), "replay %s\n", argv[2]); + return ! writen (fd, buf, len); + } + + return 0; +} |