/* * Copyright © 2008 Chris Wilson * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of * Chris Wilson not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior * permission. Chris Wilson makes no representations about the * suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * CHRIS WILSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS, IN NO EVENT SHALL CHRIS WILSON BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Author: Chris Wilson * * Contributor(s): * Carlos Garcia Campos * * Adapted from pdf2png.c: * Copyright © 2005 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of * Red Hat, Inc. not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior * permission. Red Hat, Inc. makes no representations about the * suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Author: Kristian Høgsberg */ #if HAVE_CONFIG_H #include "config.h" #endif #if HAVE_UNISTD_H #include #endif #include #include #include #include #include #if CAIRO_CAN_TEST_PDF_SURFACE #include #endif #if CAIRO_CAN_TEST_SVG_SURFACE #include #include #endif #if CAIRO_HAS_SPECTRE #include #endif #if HAVE_UNISTD_H && HAVE_FCNTL_H && HAVE_SIGNAL_H && HAVE_SYS_STAT_H && HAVE_SYS_SOCKET_H && HAVE_SYS_POLL_H && HAVE_SYS_UN_H #include #include #include #include #include #include #include #define SOCKET_PATH "./.any2ppm" #define TIMEOUT 60000 /* 60 seconds */ #define CAN_RUN_AS_DAEMON 1 #endif #define ARRAY_LENGTH(A) (sizeof (A) / sizeof (A[0])) static int _writen (int fd, char *buf, int len) { while (len) { int ret; ret = write (fd, buf, len); if (ret == -1) { int err = errno; switch (err) { case EINTR: case EAGAIN: continue; default: return 0; } } len -= ret; buf += ret; } return 1; } static int _write (int fd, char *buf, int maxlen, int buflen, const unsigned char *src, int srclen) { if (buflen < 0) return buflen; while (srclen) { int len; len = buflen + srclen; if (len > maxlen) len = maxlen; len -= buflen; memcpy (buf + buflen, src, len); buflen += len; srclen -= len; src += len; if (buflen == maxlen) { if (! _writen (fd, buf, buflen)) return -1; buflen = 0; } } return buflen; } static const char * write_ppm (cairo_surface_t *surface, int fd) { char buf[4096]; cairo_format_t format; const char *format_str; const unsigned char *data; int len; int width, height, stride; int i, j; data = cairo_image_surface_get_data (surface); height = cairo_image_surface_get_height (surface); width = cairo_image_surface_get_width (surface); stride = cairo_image_surface_get_stride (surface); format = cairo_image_surface_get_format (surface); if (format == CAIRO_FORMAT_ARGB32) { /* see if we can convert to a standard ppm type and trim a few bytes */ const unsigned char *alpha = data; for (j = height; j--; alpha += stride) { for (i = 0; i < width; i++) { if ((*(unsigned int *) (alpha+4*i) & 0xff000000) != 0xff000000) goto done; } } format = CAIRO_FORMAT_RGB24; done: ; } switch (format) { case CAIRO_FORMAT_ARGB32: /* XXX need true alpha for svg */ format_str = "P7"; break; case CAIRO_FORMAT_RGB24: format_str = "P6"; break; case CAIRO_FORMAT_A8: format_str = "P5"; break; case CAIRO_FORMAT_A1: default: return "unhandled image format"; } len = sprintf (buf, "%s %d %d 255\n", format_str, width, height); for (j = 0; j < height; j++) { const unsigned int *row = (unsigned int *) (data + stride * j); switch ((int) format) { case CAIRO_FORMAT_ARGB32: len = _write (fd, buf, sizeof (buf), len, (unsigned char *) row, 4 * width); break; case CAIRO_FORMAT_RGB24: for (i = 0; i < width; i++) { unsigned char rgb[3]; unsigned int p = *row++; rgb[0] = (p & 0xff0000) >> 16; rgb[1] = (p & 0x00ff00) >> 8; rgb[2] = (p & 0x0000ff) >> 0; len = _write (fd, buf, sizeof (buf), len, rgb, 3); } break; case CAIRO_FORMAT_A8: len = _write (fd, buf, sizeof (buf), len, (unsigned char *) row, width); break; } if (len < 0) return "write failed"; } if (len && ! _writen (fd, buf, len)) return "write failed"; return NULL; } static cairo_surface_t * _create_image (void *closure, cairo_content_t content, double width, double height, long uid) { cairo_surface_t **out = closure; cairo_format_t format; switch (content) { case CAIRO_CONTENT_ALPHA: format = CAIRO_FORMAT_A8; break; case CAIRO_CONTENT_COLOR: format = CAIRO_FORMAT_RGB24; break; default: case CAIRO_CONTENT_COLOR_ALPHA: format = CAIRO_FORMAT_ARGB32; break; } *out = cairo_image_surface_create (format, width, height); return cairo_surface_reference (*out); } #if CAIRO_HAS_INTERPRETER static const char * _cairo_script_render_page (const char *filename, cairo_surface_t **surface_out) { cairo_script_interpreter_t *csi; cairo_surface_t *surface = NULL; cairo_status_t status; const cairo_script_interpreter_hooks_t hooks = { .closure = &surface, .surface_create = _create_image, }; csi = cairo_script_interpreter_create (); cairo_script_interpreter_install_hooks (csi, &hooks); cairo_script_interpreter_run (csi, filename); status = cairo_script_interpreter_destroy (csi); if (surface == NULL) { return "cairo-script interpreter failed"; } if (status == CAIRO_STATUS_SUCCESS) status = cairo_surface_status (surface); if (status) { cairo_surface_destroy (surface); return cairo_status_to_string (status); } *surface_out = surface; return NULL; } static const char * cs_convert (char **argv, int fd) { const char *err; cairo_surface_t *surface = NULL; /* silence compiler warning */ err = _cairo_script_render_page (argv[0], &surface); if (err != NULL) return err; err = write_ppm (surface, fd); cairo_surface_destroy (surface); return err; } #else static const char * cs_convert (char **argv, int fd) { return "compiled without CairoScript support."; } #endif #if CAIRO_CAN_TEST_PDF_SURFACE /* adapted from pdf2png.c */ static const char * _poppler_render_page (const char *filename, const char *page_label, cairo_surface_t **surface_out) { PopplerDocument *document; PopplerPage *page; double width, height; GError *error = NULL; gchar *absolute, *uri; cairo_surface_t *surface; cairo_t *cr; cairo_status_t status; if (g_path_is_absolute (filename)) { absolute = g_strdup (filename); } else { gchar *dir = g_get_current_dir (); absolute = g_build_filename (dir, filename, (gchar *) 0); g_free (dir); } uri = g_filename_to_uri (absolute, NULL, &error); g_free (absolute); if (uri == NULL) return error->message; /* XXX g_error_free (error) */ document = poppler_document_new_from_file (uri, NULL, &error); g_free (uri); if (document == NULL) return error->message; /* XXX g_error_free (error) */ page = poppler_document_get_page_by_label (document, page_label); g_object_unref (document); if (page == NULL) return "page not found"; poppler_page_get_size (page, &width, &height); surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create (surface); poppler_page_render (page, cr); g_object_unref (page); cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER); cairo_set_source_rgb (cr, 1., 1., 1.); cairo_paint (cr); status = cairo_status (cr); cairo_destroy (cr); if (status) { cairo_surface_destroy (surface); return cairo_status_to_string (status); } *surface_out = surface; return NULL; } static const char * pdf_convert (char **argv, int fd) { const char *err; cairo_surface_t *surface = NULL; /* silence compiler warning */ err = _poppler_render_page (argv[0], argv[1], &surface); if (err != NULL) return err; err = write_ppm (surface, fd); cairo_surface_destroy (surface); return err; } #else static const char * pdf_convert (char **argv, int fd) { return "compiled without PDF support."; } #endif #if CAIRO_CAN_TEST_SVG_SURFACE static const char * _rsvg_render_page (const char *filename, cairo_surface_t **surface_out) { RsvgHandle *handle; RsvgDimensionData dimensions; GError *error = NULL; cairo_surface_t *surface; cairo_t *cr; cairo_status_t status; handle = rsvg_handle_new_from_file (filename, &error); if (handle == NULL) return error->message; /* XXX g_error_free */ rsvg_handle_get_dimensions (handle, &dimensions); surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, dimensions.width, dimensions.height); cr = cairo_create (surface); rsvg_handle_render_cairo (handle, cr); g_object_unref (handle); status = cairo_status (cr); cairo_destroy (cr); if (status) { cairo_surface_destroy (surface); return cairo_status_to_string (status); } *surface_out = surface; return NULL; } static const char * svg_convert (char **argv, int fd) { const char *err; cairo_surface_t *surface = NULL; /* silence compiler warning */ err = _rsvg_render_page (argv[0], &surface); if (err != NULL) return err; err = write_ppm (surface, fd); cairo_surface_destroy (surface); return err; } #else static const char * svg_convert (char **argv, int fd) { return "compiled without SVG support."; } #endif #if CAIRO_HAS_SPECTRE static const char * _spectre_render_page (const char *filename, const char *page_label, cairo_surface_t **surface_out) { static const cairo_user_data_key_t key; SpectreDocument *document; SpectreStatus status; int width, height, stride; unsigned char *pixels; cairo_surface_t *surface; document = spectre_document_new (); spectre_document_load (document, filename); status = spectre_document_status (document); if (status) { spectre_document_free (document); return spectre_status_to_string (status); } if (page_label) { SpectrePage *page; SpectreRenderContext *rc; page = spectre_document_get_page_by_label (document, page_label); spectre_document_free (document); if (page == NULL) return "page not found"; spectre_page_get_size (page, &width, &height); rc = spectre_render_context_new (); spectre_render_context_set_page_size (rc, width, height); spectre_page_render (page, rc, &pixels, &stride); spectre_render_context_free (rc); status = spectre_page_status (page); spectre_page_free (page); if (status) { free (pixels); return spectre_status_to_string (status); } } else { spectre_document_get_page_size (document, &width, &height); spectre_document_render (document, &pixels, &stride); spectre_document_free (document); } surface = cairo_image_surface_create_for_data (pixels, CAIRO_FORMAT_RGB24, width, height, stride); cairo_surface_set_user_data (surface, &key, pixels, (cairo_destroy_func_t) free); *surface_out = surface; return NULL; } static const char * ps_convert (char **argv, int fd) { const char *err; cairo_surface_t *surface = NULL; /* silence compiler warning */ err = _spectre_render_page (argv[0], argv[1], &surface); if (err != NULL) return err; err = write_ppm (surface, fd); cairo_surface_destroy (surface); return err; } #else static const char * ps_convert (char **argv, int fd) { return "compiled without PostScript support."; } #endif static const char * convert (char **argv, int fd) { static const struct converter { const char *type; const char *(*func) (char **, int); } converters[] = { { "cs", cs_convert }, { "pdf", pdf_convert }, { "ps", ps_convert }, { "svg", svg_convert }, { NULL, NULL } }; const struct converter *converter = converters; char *type; type = strrchr (argv[0], '.'); if (type == NULL) return "no file extension"; type++; while (converter->type) { if (strcmp (type, converter->type) == 0) return converter->func (argv, fd); converter++; } return "no converter"; } #if CAN_RUN_AS_DAEMON static int _getline (int fd, char **linep, size_t *lenp) { char *line; size_t len, i; ssize_t ret; line = *linep; if (line == NULL) { line = malloc (1024); if (line == NULL) return -1; line[0] = '\0'; len = 1024; } else len = *lenp; /* XXX simple, but ugly! */ i = 0; do { if (i == len - 1) { char *nline; nline = realloc (line, len + 1024); if (nline == NULL) goto out; line = nline; len += 1024; } ret = read (fd, line + i, 1); if (ret == -1 || ret == 0) goto out; } while (line[i++] != '\n'); out: line[i] = '\0'; *linep = line; *lenp = len; return i-1; } static int split_line (char *line, char *argv[], int max_argc) { int i = 0; max_argc--; /* leave one spare for the trailing NULL */ argv[i++] = line; while (i < max_argc && (line = strchr (line, ' ')) != NULL) { *line++ = '\0'; argv[i++] = line; } /* chomp the newline */ line = strchr (argv[i-1], '\n'); if (line != NULL) *line = '\0'; argv[i] = NULL; return i; } static int any2ppm_daemon_exists (void) { struct stat st; int fd; char buf[80]; int pid; int ret; if (stat (SOCKET_PATH, &st) < 0) return 0; fd = open (SOCKET_PATH ".pid", O_RDONLY); if (fd < 0) return 0; pid = 0; ret = read (fd, buf, sizeof (buf) - 1); if (ret > 0) { buf[ret] = '\0'; pid = atoi (buf); } close (fd); return pid > 0 && kill (pid, 0) == 0; } static int write_pid_file (void) { int fd; char buf[80]; int ret; fd = open (SOCKET_PATH ".pid", O_CREAT | O_TRUNC | O_WRONLY, 0666); if (fd < 0) return 0; ret = sprintf (buf, "%d\n", getpid ()); ret = write (fd, buf, ret) == ret; close (fd); return ret; } 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 ()) { /* refork to yield session leadership. */ 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 const char * any2ppm_daemon (void) { int timeout = TIMEOUT; struct pollfd pfd; int sk, fd; long flags; struct sockaddr_un addr; char *line = NULL; size_t len = 0; #ifdef SIGPIPE signal (SIGPIPE, SIG_IGN); #endif /* XXX racy! */ if (getenv ("ANY2PPM_FORCE") == NULL && any2ppm_daemon_exists ()) return "any2ppm daemon already running"; unlink (SOCKET_PATH); sk = socket (PF_UNIX, SOCK_STREAM, 0); if (sk == -1) return "unable to create socket"; 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 "unable to bind socket"; } flags = fcntl (sk, F_GETFL); if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) { close (sk); return "unable to set socket to non-blocking"; } if (listen (sk, 5) == -1) { close (sk); return "unable to listen on socket"; } /* ready for client connection - detach from parent/terminal */ if (getenv ("ANY2PPM_NODAEMON") == NULL && daemonize () == -1) { close (sk); return "unable to detach from parent"; } if (! write_pid_file ()) { close (sk); return "unable to write pid file"; } if (getenv ("ANY2PPM_TIMEOUT") != NULL) { timeout = atoi (getenv ("ANY2PPM_TIMEOUT")); if (timeout == 0) timeout = -1; if (timeout > 0) timeout *= 1000; /* convert env (in seconds) to milliseconds */ } pfd.fd = sk; pfd.events = POLLIN; pfd.revents = 0; /* valgrind */ while (poll (&pfd, 1, timeout) > 0) { while ((fd = accept (sk, NULL, NULL)) != -1) { if (_getline (fd, &line, &len) != -1) { char *argv[10]; if (split_line (line, argv, ARRAY_LENGTH (argv)) > 0) { const char *err; err = convert (argv, fd); if (err != NULL) { FILE *file = fopen (".any2ppm.errors", "a"); if (file != NULL) { fprintf (file, "Failed to convert '%s': %s\n", argv[0], err); fclose (file); } } } } close (fd); } } close (sk); unlink (SOCKET_PATH); unlink (SOCKET_PATH ".pid"); free (line); return NULL; } #else static const char * any2ppm_daemon (void) { return "daemon not compiled in."; } #endif int main (int argc, char **argv) { const char *err; #if CAIRO_CAN_TEST_PDF_SURFACE || CAIRO_CAN_TEST_SVG_SURFACE g_type_init (); #endif #if CAIRO_CAN_TEST_SVG_SURFACE rsvg_init (); rsvg_set_default_dpi (72.0); #endif if (argc == 1) err = any2ppm_daemon (); else err = convert (argv + 1, 1); if (err != NULL) { fprintf (stderr, "Failed to run converter: %s\n", err); return EXIT_FAILURE; } return EXIT_SUCCESS; }