Add `opendoc' command
[shigofumi.git] / src / io.c
blobed7dbba174ea39a7af0b023abc201b2a05005f6b
1 #define _XOPEN_SOURCE 500
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <stdio.h>
6 #include <unistd.h>
7 #include <string.h>
8 #include <errno.h>
9 #include <sys/mman.h>
10 #include <stdint.h>
11 #include <magic.h>
12 #include <limits.h> /* SSIZE_MAX */
13 #include <isds.h>
14 #include <libxml/xmlsave.h> /* For XML serializator */
15 #include <libxml/xpath.h> /* For XPath */
17 #include "shigofumi.h"
19 #if ENABLE_XATTR
20 #include <attr/xattr.h>
21 #endif
24 /* Guess MIME type of @file by content.
25 * @type is automatically allocated. Return 0, -1 and * NULL @type in case
26 * of error */
27 static int get_mime_type_by_magic(const char *file, char **type) {
28 magic_t magic;
29 const char *magic_guess;
31 if (!type) return -1;
32 *type = NULL;
33 if (!file || !*file) return -1;
35 if (!(magic = magic_open(MAGIC_MIME_TYPE))) {
36 fprintf(stderr, _("Could not initialize libmagic\n"));
37 return -1;
39 if (magic_load(magic, NULL)) {
40 fprintf(stderr, _("Could not load magic database\n"));
41 return -1;
44 if (!(magic_guess = magic_file(magic, file))) {
45 fprintf(stderr, _("%s: Could not guess MIME type: %s\n"),
46 file, magic_error(magic));
47 magic_close(magic);
48 return -1;
51 if (!(*type = strdup(magic_guess))) {
52 fprintf(stderr, _("Not enough memory\n"));
53 magic_close(magic);
54 return -1;
57 magic_close(magic);
58 return 0;
62 #if ENABLE_XATTR
63 #define XATTR_MIME_TYPE "user.mime_type"
65 /* Get MIME type from file extended attribute.
66 * @fd is valid file descriptor or -1
67 * @type is automatically allocated. Return 0, -1 and * NULL @type in case
68 * of error */
69 /* XXX: This is Linux specific. There is a library, but it supports IRIX
70 * in addition only. */
71 static int get_mime_type_by_xattr(int fd, char **type) {
72 char *buffer = NULL;
73 ssize_t length = 0, ret;
75 if (!type) return -1;
76 *type = NULL;
77 if (fd < 0) return -1;
79 /* Get initial size */
80 length = fgetxattr(fd, XATTR_MIME_TYPE, NULL, 0);
81 if (length <= 0) return -1;
83 /* Grow buffer as needed */
84 for (ret = -1, errno = ERANGE; ret < 0 && errno == ERANGE; length *= 2) {
85 buffer = realloc(*type, length + 1);
86 if (!buffer) {
87 fprintf(stderr, _("Not enough memory\n"));
88 zfree(*type);
89 return -1;
91 *type = buffer;
92 ret = fgetxattr(fd, XATTR_MIME_TYPE, *type, length);
94 /* Break on length overflow */
95 if (length >= SSIZE_MAX / 2) {
96 fprintf(stderr, _("Extended attribute too long\n"));
97 zfree(*type);
98 return -1;
102 if (ret <= 0) {
103 zfree(*type);
104 return -1;
107 (*type)[ret] = '\0';
108 return 0;
110 #endif /* ENABLE_XATTR */
113 /* Get MIME type of file.
114 * File can be specified by name or descriptor or both.
115 * @fd is open file descriptor of the file or -1
116 * @name is name of the same file or NULL
117 * @type is automatically allocated.
118 * Return 0; -1 and NULL @type in case of error */
119 static int get_mime_type(int fd, const char *name, char **type) {
121 if (!type) return -1;
122 *type = NULL;
123 if ((!name || !*name) && fd == -1) return -1;
125 #ifdef ENABLE_XATTR
126 if (get_mime_type_by_xattr(fd, type))
127 #endif
128 if (get_mime_type_by_magic(name, type))
129 return -1;
131 return 0;
135 /* Annotate MIME type to a file
136 * @fd is descriptor of the file
137 * @type is mime_type in UTF-8
138 * Return 0 on success, -1 on failure */
139 /* XXX: Linux specific */
140 static int save_mime_type(int fd, const char *type) {
141 if (!type || !*type) return -1;
143 #if ENABLE_XATTR
144 if (fsetxattr(fd, XATTR_MIME_TYPE, type, strlen(type), 0)) {
145 fprintf(stderr,
146 _("Could not save MIME type into extended attribute: %s\n"),
147 strerror(errno));
148 return -1;
150 #endif /* ENABLE_XATTR */
151 return 0;
154 #undef XATTR_MIME_TYPE
157 /* Open file and return descriptor. Return -1 in case of error. */
158 int open_file_for_writing(const char *file, _Bool truncate, _Bool overwrite) {
159 int fd;
161 if (!file) return -1;
163 fd = open(file, O_WRONLY|O_CREAT|O_APPEND |
164 ((truncate) ? O_TRUNC : 0) |
165 ((overwrite) ? 0 : O_EXCL),
166 0666);
167 if (fd == -1) {
168 fprintf(stderr, _("%s: Could not open file for writing: %s\n"),
169 file, strerror(errno));
172 return fd;
176 /* Return 0, -1 in case of error */
177 int mmap_file(const char *file, int *fd, void **buffer, size_t *length) {
178 struct stat file_info;
180 if (!file || !fd || !buffer || !length) return -1;
183 *fd = open(file, O_RDONLY);
184 if (*fd == -1) {
185 fprintf(stderr, _("%s: Could not open file: %s\n"),
186 file, strerror(errno));
187 return -1;
190 if (-1 == fstat(*fd, &file_info)) {
191 fprintf(stderr, _("%s: Could not get file size: %s\n"), file,
192 strerror(errno));
193 close(*fd);
194 return -1;
196 if (file_info.st_size < 0) {
197 fprintf(stderr, _("File `%s' has negative size: %jd\n"), file,
198 (intmax_t) file_info.st_size);
199 close(*fd);
200 return -1;
202 *length = file_info.st_size;
204 if (!*length) {
205 /* Empty region cannot be mmapped */
206 *buffer = NULL;
207 } else {
208 *buffer = mmap(NULL, *length, PROT_READ, MAP_PRIVATE, *fd, 0);
209 if (*buffer == MAP_FAILED) {
210 fprintf(stderr, _("%s: Could not map file to memory: %s\n"), file,
211 strerror(errno));
212 close(*fd);
213 return -1;
217 return 0;
221 /* Return 0, -1 in case of error */
222 int munmap_file(int fd, void *buffer, size_t length) {
223 int err = 0;
224 long int page_size = sysconf(_SC_PAGE_SIZE);
225 size_t pages = (length % page_size) ?
226 ((length / page_size) + 1) * page_size:
227 length;
229 if (length) {
230 err = munmap(buffer, pages);
231 if (err) {
232 fprintf(stderr,
233 _("Could not unmap memory at %p and length %zu: %s\n"),
234 buffer, pages, strerror(errno));
238 err = close(fd);
239 if (err) {
240 fprintf(stderr, _("Could close file descriptor %d: %s\n"), fd,
241 strerror(errno));
244 return err;
248 /* Return 0, -1 in case of error.
249 * @length and @mime_type are optional. */
250 int load_data_from_file(const char *file, void **data, size_t *length,
251 char **mime_type) {
252 int fd;
253 void *buffer;
254 size_t map_length;
256 if (!file || !data) return -1;
258 if (mmap_file(file, &fd, &buffer, &map_length)) return -1;
260 printf(ngettext("Reading %zu byte from file `%s'...\n",
261 "Reading %zu bytes from file `%s'...\n", map_length),
262 map_length, file);
263 *data = malloc(map_length);
264 if (!*data) {
265 fprintf(stderr, _("Error: Not enough memory\n"));
266 munmap_file(fd, buffer, map_length);
267 return -1;
269 memcpy(*data, buffer, map_length);
270 if (length) *length = map_length;
272 if (mime_type) {
273 if (get_mime_type(fd, file, mime_type))
274 fprintf(stderr, _("Warning: %s: Could not determine MIME type\n"),
275 file);
277 } else {
278 *mime_type = NULL;
281 munmap_file(fd, buffer, map_length);
282 printf(_("Done.\n"));
283 return 0;
287 /* Save @data to file specified by descriptor @fd. If @fd is negative, @file
288 * file will be opened first. Descriptor is closed at the end of this
289 * function. Supply @file name even if @fd is positive, the name could be used
290 * in messages. */
291 int save_data_to_file(const char *file, int fd, const void *data,
292 const size_t length, const char *mime_type, _Bool overwrite) {
293 ssize_t written, left = length;
295 if (fd < 0 && !file) return -1;
296 if (length > 0 && !data) return -1;
298 if (fd < 0) {
299 fd = open_file_for_writing(file, 1, overwrite);
300 if (fd == -1) return -1;
303 if (file)
304 printf(ngettext("Writing %zu byte to file `%s'...\n",
305 "Writing %zu bytes to file `%s'...\n", length),
306 length, file);
307 else
308 printf(ngettext("Writing %zu byte to file desciptor %d...\n",
309 "Writing %zu bytes to file descriptor %d...\n", length),
310 length, fd);
311 while (left) {
312 written = write(fd, data + length - left, left);
313 if (written == -1) {
314 if (file)
315 fprintf(stderr, _("%s: Could not save file: %s\n"),
316 file, strerror(errno));
317 else
318 fprintf(stderr, _("Descriptor %d: Could not write into: %s\n"),
319 fd, strerror(errno));
320 close(fd);
321 return -1;
323 left-=written;
326 save_mime_type(fd, mime_type);
328 if (-1 == close(fd)) {
329 if (file)
330 fprintf(stderr, _("%s: Closing file failed: %s\n"),
331 file, strerror(errno));
332 else
333 fprintf(stderr, _("Descript %d: Closing failed: %s\n"),
334 fd, strerror(errno));
335 return -1;
338 printf(_("Done.\n"));
339 return 0;
343 /* @node_list is pointer to by-function allocated weak copy of libxml node
344 * pointers list. *NULL means empty list. It will be freed and NULLed in case
345 * of error.
346 * @xpat_expr is UTF-8 encoded XPath expression. */
347 static int xpath2nodelist(xmlNodePtr *node_list, xmlXPathContextPtr xpath_ctx,
348 const xmlChar *xpath_expr) {
349 xmlXPathObjectPtr result = NULL;
350 xmlNodePtr node = NULL, prev_node = NULL;
352 if (!node_list || !xpath_ctx || !xpath_expr) return -1;
354 result = xmlXPathEvalExpression(xpath_expr, xpath_ctx);
355 if (!result) {
356 char *xpath_expr_locale = utf82locale((const char*) xpath_expr);
357 fprintf(stderr, _("Error while evaluating XPath expression `%s'\n"),
358 xpath_expr_locale);
359 free(xpath_expr_locale);
360 return -1;
363 if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
364 /* Empty match, returning empty node list */
365 *node_list = NULL;
366 } else {
367 /* Convert node set to list of siblings */
368 for (int i = 0; i < result->nodesetval->nodeNr; i++) {
369 /* Make weak copy of the node */
370 node = malloc(sizeof(*node));
371 if (!node) {
372 fprintf(stderr, _("Not enough memory\n"));
373 xmlXPathFreeObject(result);
374 for (node = *node_list; node; node = node->next)
375 free(node);
376 *node_list = NULL;
377 return -1;
379 memcpy(node, result->nodesetval->nodeTab[i], sizeof(*node));
381 /* Add node to node_list */
382 node->prev = prev_node;
383 node->next = NULL;
384 if (prev_node)
385 prev_node->next = node;
386 else
387 *node_list = node;
388 prev_node = node;
390 /* Debug */
391 /*printf("* Embeding node #%d:\n", i);
392 xmlDebugDumpNode(stdout, node, 2);*/
396 xmlXPathFreeObject(result);
397 return 0;
401 /* Parse @buffer as XML document and return @node_list specified by
402 * @xpath_expression.
403 * @node_list is weak copy that must be non-recursively freed by caller. Caller
404 * must free XML document (accessible through @node_list member) on its own.
405 * In case of error @node_list value will be invalid.
406 * If @node_list is returned empty, function freed parsed document already.
407 * @xpat_expr is UTF-8 encoded XPath expression */
408 int load_xml_subtree_from_memory(const void *buffer, size_t length,
409 xmlNodePtr *node_list, const char *xpath_expr) {
410 int retval = 0;
411 xmlDocPtr xml = NULL;
412 xmlXPathContextPtr xpath_ctx = NULL;
414 if (!buffer || !length) {
415 fprintf(stderr,
416 _("Empty XML document, XPath expression cannot be evaluated\n"));
417 return -1;
419 if (!node_list || !xpath_expr)
420 return -1;
421 *node_list = NULL;
423 /* Create XML documents */
424 xml = xmlParseMemory(buffer, length);
425 if (!xml) {
426 fprintf(stderr, _("Error while parsing document\n"));
427 return -1;
430 xpath_ctx = xmlXPathNewContext(xml);
431 if (!xpath_ctx) {
432 fprintf(stderr, _("Error while creating XPath context\n"));
433 retval = -1;
434 goto leave;
437 if (xpath2nodelist(node_list, xpath_ctx, (const xmlChar *)xpath_expr)) {
438 char *xpath_expr_locale = utf82locale(xpath_expr);
439 fprintf(stderr, _("Could not convert XPath result to node list: %s\n"),
440 xpath_expr_locale);
441 free(xpath_expr_locale);
442 retval = -1;
443 goto leave;
446 leave:
447 xmlXPathFreeContext(xpath_ctx);
448 if (retval || !*node_list) xmlFreeDoc(xml);
449 return retval;
453 /* Deallocate struct isds_document with embedded XML recursively and NULL it */
454 void free_document_with_xml_node_list(struct isds_document **document) {
455 /* Free document xml_node_lists and associated XML document because
456 * they are weak copies and isds_document_free() does not free it. */
457 if (!document || !*document) return;
459 if ((*document)->is_xml && (*document)->xml_node_list) {
460 xmlFreeDoc((*document)->xml_node_list->doc);
461 for (xmlNodePtr node = (*document)->xml_node_list; node;
462 node = node->next)
463 free(node);
466 isds_document_free(document);
470 /* Serialize XML @node_list to automatically rellaocated libxml @buffer */
471 int serialize_xml_to_buffer(xmlBufferPtr *buffer, const xmlNodePtr node_list) {
472 int retval = 0;
473 xmlSaveCtxtPtr save_ctx = NULL;
474 xmlNodePtr element, subtree_copy = NULL;
475 xmlDocPtr subtree_doc = NULL;
477 if (!buffer) {
478 retval = -1;
479 goto leave;
481 if (*buffer) xmlBufferFree(*buffer);
483 /* Prepare buffer to serialize into */
484 *buffer = xmlBufferCreate();
485 if (!*buffer) {
486 fprintf(stderr,
487 _("Could not create buffer to serialize XML tree into\n"));
488 retval = -1;
489 goto leave;
491 save_ctx = xmlSaveToBuffer(*buffer, "UTF-8", 0);
492 if (!save_ctx) {
493 fprintf(stderr, _("Could not create XML serializer\n"));
494 retval = -1;
495 goto leave;
499 /* Select node and serialize it */
500 if (!node_list->next && node_list->type == XML_TEXT_NODE) {
501 /* One text node becomes plain text file */
502 /* TODO: Is CDATA expanded as text? Are entities expanded? */
503 /* XXX: According LibXML documentation, this function does not return
504 * meaningful value yet */
505 xmlSaveTree(save_ctx, node_list);
506 } else {
507 /* Serialize element */
508 if (!node_list->next && node_list->type == XML_ELEMENT_NODE) {
509 /* One element becomes root */
510 element = node_list;
511 } else {
512 /* Parent becomes root */
513 if (!node_list->parent) {
514 fprintf(stderr,
515 _("XML node list to serialize is missing parent node\n"));
516 retval = -1;
517 goto leave;
519 element = node_list->parent;
522 /* Use temporary XML document to resolve name space definitions */
523 /* XXX: We can not use xmlNodeDump() nor xmlSaveTree because it dumps
524 * the subtree as is. It can result in not well-formed on invalid XML
525 * tree (e.g. name space prefix definition can miss.) */
526 subtree_doc = xmlNewDoc(BAD_CAST "1.0");
527 if (!subtree_doc) {
528 fprintf(stderr, _("Could not build temporary XML document\n"));
529 retval = -1;
530 goto leave;
532 /* XXX: Copy subtree and attach the copy to document.
533 * One node can not bee attached into more document at the same time. */
534 subtree_copy = xmlCopyNodeList(element);
535 if (!subtree_copy) {
536 fprintf(stderr, _("Could not copy XML subtree\n"));
537 retval = -1;
538 goto leave;
540 xmlDocSetRootElement(subtree_doc, subtree_copy);
541 /* Only this way we get name space definition as @xmlns:isds,
542 * otherwise we get name space prefix without definition */
543 /* FIXME: Don't overwrite original default name space */
544 /*isds_ns = xmlNewNs(subtree_copy, BAD_CAST ISDS_NS, NULL);
545 if(!isds_ns) {
546 isds_log_message(context, _("Could not create ISDS name space"));
547 err = IE_ERROR;
548 goto leave;
550 xmlSetNs(subtree_copy, isds_ns);*/
552 /* XXX: According LibXML documentation, this function does not return
553 * meaningful value yet */
554 xmlSaveDoc(save_ctx, subtree_doc);
557 /* Flush XML to buffer. After this call we are sure all data have been
558 * processed successfully. */
559 if (-1 == xmlSaveFlush(save_ctx)) {
560 fprintf(stderr, _("Could not serialize XML tree\n"));
561 retval = -1;
562 goto leave;
565 leave:
566 xmlFreeDoc(subtree_doc); /* Frees subtree_copy */
567 xmlSaveClose(save_ctx);
569 if (retval) {
570 xmlBufferFree(*buffer);
571 *buffer = NULL;
574 return retval;
578 /* Save @node_list to file specified by descriptor @fd. If @fd is negative, @file
579 * file will be opened first. Descriptor is closed at the end of this
580 * function. Supply @file name even if @fd is positive, the name could be used
581 * in messages. */
582 int save_xml_to_file(const char *file, int fd, const xmlNodePtr node_list,
583 const char *mime_type, _Bool overwrite) {
584 int retval = 0;
585 xmlBufferPtr buffer = NULL;
587 retval = serialize_xml_to_buffer(&buffer, node_list);
589 if (!retval) {
590 /* And save buffer into file */
591 retval = save_data_to_file(file, fd, buffer->content, buffer->use,
592 mime_type, overwrite);
593 xmlBufferFree(buffer);
596 return retval;
600 /* Return 0 if @path is directory, 1 if not, -1 if error occurred */
601 int is_directory(const char *path) {
602 struct stat dir_stat;
604 if (!path) return -1;
605 if (stat(path, &dir_stat)) return -1;
606 if S_ISDIR(dir_stat.st_mode) return 0;
607 else return 1;