/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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; }