1 #define _XOPEN_SOURCE 500
2 #define _SVID_SOURCE /* For mkstemps(3) */
13 #include <limits.h> /* SSIZE_MAX */
14 #include <stdlib.h> /* mkstemps(3) */
16 #include <libxml/xmlsave.h> /* For XML serializator */
17 #include <libxml/xpath.h> /* For XPath */
19 #include "shigofumi.h"
22 #include <attr/xattr.h>
26 /* Guess MIME type of @file by content.
27 * @type is automatically allocated. Return 0, -1 and * NULL @type in case
29 static int get_mime_type_by_magic(const char *file
, char **type
) {
31 const char *magic_guess
;
35 if (!file
|| !*file
) return -1;
37 if (!(magic
= magic_open(MAGIC_MIME_TYPE
))) {
38 fprintf(stderr
, _("Could not initialize libmagic\n"));
41 if (magic_load(magic
, NULL
)) {
42 fprintf(stderr
, _("Could not load magic database\n"));
46 if (!(magic_guess
= magic_file(magic
, file
))) {
47 fprintf(stderr
, _("%s: Could not guess MIME type: %s\n"),
48 file
, magic_error(magic
));
53 if (!(*type
= strdup(magic_guess
))) {
54 fprintf(stderr
, _("Not enough memory\n"));
65 #define XATTR_MIME_TYPE "user.mime_type"
67 /* Get MIME type from file extended attribute.
68 * @fd is valid file descriptor or -1
69 * @type is automatically allocated. Return 0, -1 and * NULL @type in case
71 /* XXX: This is Linux specific. There is a library, but it supports IRIX
72 * in addition only. */
73 static int get_mime_type_by_xattr(int fd
, char **type
) {
75 ssize_t length
= 0, ret
;
79 if (fd
< 0) return -1;
81 /* Get initial size */
82 length
= fgetxattr(fd
, XATTR_MIME_TYPE
, NULL
, 0);
83 if (length
<= 0) return -1;
85 /* Grow buffer as needed */
86 for (ret
= -1, errno
= ERANGE
; ret
< 0 && errno
== ERANGE
; length
*= 2) {
87 buffer
= realloc(*type
, length
+ 1);
89 fprintf(stderr
, _("Not enough memory\n"));
94 ret
= fgetxattr(fd
, XATTR_MIME_TYPE
, *type
, length
);
96 /* Break on length overflow */
97 if (length
>= SSIZE_MAX
/ 2) {
98 fprintf(stderr
, _("Extended attribute too long\n"));
112 #endif /* ENABLE_XATTR */
115 /* Get MIME type of file.
116 * File can be specified by name or descriptor or both.
117 * @fd is open file descriptor of the file or -1
118 * @name is name of the same file or NULL
119 * @type is automatically allocated.
120 * Return 0; -1 and NULL @type in case of error */
121 static int get_mime_type(int fd
, const char *name
, char **type
) {
123 if (!type
) return -1;
125 if ((!name
|| !*name
) && fd
== -1) return -1;
128 if (get_mime_type_by_xattr(fd
, type
))
130 if (get_mime_type_by_magic(name
, type
))
137 /* Annotate MIME type to a file
138 * @fd is descriptor of the file
139 * @type is mime_type in UTF-8
140 * Return 0 on success, -1 on failure */
141 /* XXX: Linux specific */
142 static int save_mime_type(int fd
, const char *type
) {
143 if (!type
|| !*type
) return -1;
146 if (fsetxattr(fd
, XATTR_MIME_TYPE
, type
, strlen(type
), 0)) {
148 _("Could not save MIME type into extended attribute: %s\n"),
152 #endif /* ENABLE_XATTR */
156 #undef XATTR_MIME_TYPE
159 /* Open file and return descriptor. Return -1 in case of error. */
160 int open_file_for_writing(const char *file
, _Bool truncate
, _Bool overwrite
) {
163 if (!file
) return -1;
165 fd
= open(file
, O_WRONLY
|O_CREAT
|O_APPEND
|
166 ((truncate
) ? O_TRUNC
: 0) |
167 ((overwrite
) ? 0 : O_EXCL
),
170 fprintf(stderr
, _("%s: Could not open file for writing: %s\n"),
171 file
, strerror(errno
));
179 * @file is static template for file name. See mkstemps(3). It will contain
180 * new file name on return.
181 * @suffix_length is number of bytes of immutable file name suffix.
182 * @return descriptor of opened file, or -1 in case of error. */
183 int create_new_file(char *file
, int suffix_length
) {
186 if (NULL
== file
) return -1;
188 /* TODO: mkstemps() works in CWD. Implement something working in $TMP. */
189 fd
= mkstemps(file
, suffix_length
);
191 fprintf(stderr
, _("Could not create temporary file `%s': %s\n"),
192 file
, strerror(errno
));
200 /* Return 0, -1 in case of error */
201 int mmap_file(const char *file
, int *fd
, void **buffer
, size_t *length
) {
202 struct stat file_info
;
204 if (!file
|| !fd
|| !buffer
|| !length
) return -1;
207 *fd
= open(file
, O_RDONLY
);
209 fprintf(stderr
, _("%s: Could not open file: %s\n"),
210 file
, strerror(errno
));
214 if (-1 == fstat(*fd
, &file_info
)) {
215 fprintf(stderr
, _("%s: Could not get file size: %s\n"), file
,
220 if (file_info
.st_size
< 0) {
221 fprintf(stderr
, _("File `%s' has negative size: %jd\n"), file
,
222 (intmax_t) file_info
.st_size
);
226 *length
= file_info
.st_size
;
229 /* Empty region cannot be mmapped */
232 *buffer
= mmap(NULL
, *length
, PROT_READ
, MAP_PRIVATE
, *fd
, 0);
233 if (*buffer
== MAP_FAILED
) {
234 fprintf(stderr
, _("%s: Could not map file to memory: %s\n"), file
,
245 /* Return 0, -1 in case of error */
246 int munmap_file(int fd
, void *buffer
, size_t length
) {
248 long int page_size
= sysconf(_SC_PAGE_SIZE
);
249 size_t pages
= (length
% page_size
) ?
250 ((length
/ page_size
) + 1) * page_size
:
254 err
= munmap(buffer
, pages
);
257 _("Could not unmap memory at %p and length %zu: %s\n"),
258 buffer
, pages
, strerror(errno
));
264 fprintf(stderr
, _("Could close file descriptor %d: %s\n"), fd
,
272 /* Return 0, -1 in case of error.
273 * @length and @mime_type are optional. */
274 int load_data_from_file(const char *file
, void **data
, size_t *length
,
280 if (!file
|| !data
) return -1;
282 if (mmap_file(file
, &fd
, &buffer
, &map_length
)) return -1;
284 printf(ngettext("Reading %zu byte from file `%s'...\n",
285 "Reading %zu bytes from file `%s'...\n", map_length
),
287 *data
= malloc(map_length
);
289 fprintf(stderr
, _("Error: Not enough memory\n"));
290 munmap_file(fd
, buffer
, map_length
);
293 memcpy(*data
, buffer
, map_length
);
294 if (length
) *length
= map_length
;
297 if (get_mime_type(fd
, file
, mime_type
))
298 fprintf(stderr
, _("Warning: %s: Could not determine MIME type\n"),
302 munmap_file(fd
, buffer
, map_length
);
303 printf(_("Done.\n"));
308 /* Save @data to file specified by descriptor @fd. If @fd is negative, @file
309 * file will be opened first. Descriptor is closed at the end of this
310 * function. Supply @file name even if @fd is positive, the name could be used
312 int save_data_to_file(const char *file
, int fd
, const void *data
,
313 const size_t length
, const char *mime_type
, _Bool overwrite
) {
314 ssize_t written
, left
= length
;
316 if (fd
< 0 && !file
) return -1;
317 if (length
> 0 && !data
) return -1;
320 fd
= open_file_for_writing(file
, 1, overwrite
);
321 if (fd
== -1) return -1;
325 printf(ngettext("Writing %zu byte to file `%s'...\n",
326 "Writing %zu bytes to file `%s'...\n", length
),
329 printf(ngettext("Writing %zu byte to file desciptor %d...\n",
330 "Writing %zu bytes to file descriptor %d...\n", length
),
333 written
= write(fd
, data
+ length
- left
, left
);
336 fprintf(stderr
, _("%s: Could not save file: %s\n"),
337 file
, strerror(errno
));
339 fprintf(stderr
, _("Descriptor %d: Could not write into: %s\n"),
340 fd
, strerror(errno
));
347 save_mime_type(fd
, mime_type
);
349 if (-1 == close(fd
)) {
351 fprintf(stderr
, _("%s: Closing file failed: %s\n"),
352 file
, strerror(errno
));
354 fprintf(stderr
, _("Descript %d: Closing failed: %s\n"),
355 fd
, strerror(errno
));
359 printf(_("Done.\n"));
364 /* @node_list is pointer to by-function allocated weak copy of libxml node
365 * pointers list. *NULL means empty list. It will be freed and NULLed in case
367 * @xpat_expr is UTF-8 encoded XPath expression. */
368 static int xpath2nodelist(xmlNodePtr
*node_list
, xmlXPathContextPtr xpath_ctx
,
369 const xmlChar
*xpath_expr
) {
370 xmlXPathObjectPtr result
= NULL
;
371 xmlNodePtr node
= NULL
, prev_node
= NULL
;
373 if (!node_list
|| !xpath_ctx
|| !xpath_expr
) return -1;
375 result
= xmlXPathEvalExpression(xpath_expr
, xpath_ctx
);
377 char *xpath_expr_locale
= utf82locale((const char*) xpath_expr
);
378 fprintf(stderr
, _("Error while evaluating XPath expression `%s'\n"),
380 free(xpath_expr_locale
);
384 if (xmlXPathNodeSetIsEmpty(result
->nodesetval
)) {
385 /* Empty match, returning empty node list */
388 /* Convert node set to list of siblings */
389 for (int i
= 0; i
< result
->nodesetval
->nodeNr
; i
++) {
390 /* Make weak copy of the node */
391 node
= malloc(sizeof(*node
));
393 fprintf(stderr
, _("Not enough memory\n"));
394 xmlXPathFreeObject(result
);
395 for (node
= *node_list
; node
; node
= node
->next
)
400 memcpy(node
, result
->nodesetval
->nodeTab
[i
], sizeof(*node
));
402 /* Add node to node_list */
403 node
->prev
= prev_node
;
406 prev_node
->next
= node
;
412 /*printf("* Embeding node #%d:\n", i);
413 xmlDebugDumpNode(stdout, node, 2);*/
417 xmlXPathFreeObject(result
);
422 /* Parse @buffer as XML document and return @node_list specified by
424 * @node_list is weak copy that must be non-recursively freed by caller. Caller
425 * must free XML document (accessible through @node_list member) on its own.
426 * In case of error @node_list value will be invalid.
427 * If @node_list is returned empty, function freed parsed document already.
428 * @xpat_expr is UTF-8 encoded XPath expression */
429 int load_xml_subtree_from_memory(const void *buffer
, size_t length
,
430 xmlNodePtr
*node_list
, const char *xpath_expr
) {
432 xmlDocPtr xml
= NULL
;
433 xmlXPathContextPtr xpath_ctx
= NULL
;
435 if (!buffer
|| !length
) {
437 _("Empty XML document, XPath expression cannot be evaluated\n"));
440 if (!node_list
|| !xpath_expr
)
444 /* Create XML documents */
445 xml
= xmlParseMemory(buffer
, length
);
447 fprintf(stderr
, _("Error while parsing document\n"));
451 xpath_ctx
= xmlXPathNewContext(xml
);
453 fprintf(stderr
, _("Error while creating XPath context\n"));
458 if (xpath2nodelist(node_list
, xpath_ctx
, (const xmlChar
*)xpath_expr
)) {
459 char *xpath_expr_locale
= utf82locale(xpath_expr
);
460 fprintf(stderr
, _("Could not convert XPath result to node list: %s\n"),
462 free(xpath_expr_locale
);
468 xmlXPathFreeContext(xpath_ctx
);
469 if (retval
|| !*node_list
) xmlFreeDoc(xml
);
474 /* Deallocate struct isds_document with embedded XML recursively and NULL it */
475 void free_document_with_xml_node_list(struct isds_document
**document
) {
476 /* Free document xml_node_lists and associated XML document because
477 * they are weak copies and isds_document_free() does not free it. */
478 if (!document
|| !*document
) return;
480 if ((*document
)->is_xml
&& (*document
)->xml_node_list
) {
481 xmlFreeDoc((*document
)->xml_node_list
->doc
);
482 for (xmlNodePtr node
= (*document
)->xml_node_list
; node
;
487 isds_document_free(document
);
491 /* Serialize XML @node_list to automatically rellaocated libxml @buffer */
492 int serialize_xml_to_buffer(xmlBufferPtr
*buffer
, const xmlNodePtr node_list
) {
494 xmlSaveCtxtPtr save_ctx
= NULL
;
495 xmlNodePtr element
, subtree_copy
= NULL
;
496 xmlDocPtr subtree_doc
= NULL
;
502 if (*buffer
) xmlBufferFree(*buffer
);
504 /* Prepare buffer to serialize into */
505 *buffer
= xmlBufferCreate();
508 _("Could not create buffer to serialize XML tree into\n"));
512 save_ctx
= xmlSaveToBuffer(*buffer
, "UTF-8", 0);
514 fprintf(stderr
, _("Could not create XML serializer\n"));
520 /* Select node and serialize it */
521 if (!node_list
->next
&& node_list
->type
== XML_TEXT_NODE
) {
522 /* One text node becomes plain text file */
523 /* TODO: Is CDATA expanded as text? Are entities expanded? */
524 /* XXX: According LibXML documentation, this function does not return
525 * meaningful value yet */
526 xmlSaveTree(save_ctx
, node_list
);
528 /* Serialize element */
529 if (!node_list
->next
&& node_list
->type
== XML_ELEMENT_NODE
) {
530 /* One element becomes root */
533 /* Parent becomes root */
534 if (!node_list
->parent
) {
536 _("XML node list to serialize is missing parent node\n"));
540 element
= node_list
->parent
;
543 /* Use temporary XML document to resolve name space definitions */
544 /* XXX: We can not use xmlNodeDump() nor xmlSaveTree because it dumps
545 * the subtree as is. It can result in not well-formed on invalid XML
546 * tree (e.g. name space prefix definition can miss.) */
547 subtree_doc
= xmlNewDoc(BAD_CAST
"1.0");
549 fprintf(stderr
, _("Could not build temporary XML document\n"));
553 /* XXX: Copy subtree and attach the copy to document.
554 * One node can not bee attached into more document at the same time. */
555 subtree_copy
= xmlCopyNodeList(element
);
557 fprintf(stderr
, _("Could not copy XML subtree\n"));
561 xmlDocSetRootElement(subtree_doc
, subtree_copy
);
562 /* Only this way we get name space definition as @xmlns:isds,
563 * otherwise we get name space prefix without definition */
564 /* FIXME: Don't overwrite original default name space */
565 /*isds_ns = xmlNewNs(subtree_copy, BAD_CAST ISDS_NS, NULL);
567 isds_log_message(context, _("Could not create ISDS name space"));
571 xmlSetNs(subtree_copy, isds_ns);*/
573 /* XXX: According LibXML documentation, this function does not return
574 * meaningful value yet */
575 xmlSaveDoc(save_ctx
, subtree_doc
);
578 /* Flush XML to buffer. After this call we are sure all data have been
579 * processed successfully. */
580 if (-1 == xmlSaveFlush(save_ctx
)) {
581 fprintf(stderr
, _("Could not serialize XML tree\n"));
587 xmlFreeDoc(subtree_doc
); /* Frees subtree_copy */
588 xmlSaveClose(save_ctx
);
591 xmlBufferFree(*buffer
);
599 /* Save @node_list to file specified by descriptor @fd. If @fd is negative, @file
600 * file will be opened first. Descriptor is closed at the end of this
601 * function. Supply @file name even if @fd is positive, the name could be used
603 int save_xml_to_file(const char *file
, int fd
, const xmlNodePtr node_list
,
604 const char *mime_type
, _Bool overwrite
) {
606 xmlBufferPtr buffer
= NULL
;
608 retval
= serialize_xml_to_buffer(&buffer
, node_list
);
611 /* And save buffer into file */
612 retval
= save_data_to_file(file
, fd
, buffer
->content
, buffer
->use
,
613 mime_type
, overwrite
);
614 xmlBufferFree(buffer
);
621 /* Return 0 if @path is directory, 1 if not, -1 if error occurred */
622 int is_directory(const char *path
) {
623 struct stat dir_stat
;
625 if (!path
) return -1;
626 if (stat(path
, &dir_stat
)) return -1;
627 if S_ISDIR(dir_stat
.st_mode
) return 0;
633 * @return 0 on success, non-zero otherwise. */
634 int unlink_file(const char *file
) {
635 if (NULL
== file
|| !*file
) return -1;
637 if (-1 == remove(file
)) {
638 fprintf(stderr
, _("Could not remove file `%s': %s\n"),
639 file
, strerror(errno
));