1 /* EINA - EFL data type library
2  * Copyright (C) 2013 Cedric Bail
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library;
16  * if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22 
23 #include <string.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 
30 #ifdef _WIN32
31 # include <evil_private.h> /* windows.h fcntl mkstemps mkdtemp */
32 #endif
33 
34 #define COPY_BLOCKSIZE (4 * 1024 * 1024)
35 
36 #include "eina_config.h"
37 #include "eina_private.h"
38 
39 #include "eina_hash.h"
40 #include "eina_safety_checks.h"
41 #include "eina_file_common.h"
42 #include "eina_xattr.h"
43 
44 #ifndef O_BINARY
45 # define O_BINARY 0
46 #endif
47 
48 #ifdef MAP_FAILED
49 # undef MAP_FAILED
50 #endif
51 #define MAP_FAILED ((void *)-1)
52 
53 Eina_Hash *_eina_file_cache = NULL;
54 Eina_Lock _eina_file_lock_cache;
55 
56 #if defined(EINA_SAFETY_CHECKS) && defined(EINA_MAGIC_DEBUG)
57 # define EINA_FILE_MAGIC_CHECK(f, ...) do { \
58    if (EINA_UNLIKELY((f) == NULL)) \
59      { \
60        EINA_SAFETY_ERROR("safety check failed: " # f " == NULL"); \
61        return __VA_ARGS__; \
62      } \
63    if (EINA_UNLIKELY((f)->__magic != EINA_FILE_MAGIC)) \
64      { \
65         EINA_MAGIC_FAIL(f, EINA_FILE_MAGIC); \
66         return __VA_ARGS__; \
67      } \
68    } while (0)
69 #else
70 # define EINA_FILE_MAGIC_CHECK(f, ...) do {} while(0)
71 #endif
72 
73 static Eina_Spinlock _eina_statgen_lock;
74 static Eina_Statgen _eina_statgen = 0;
75 
76 EAPI void
eina_file_statgen_next(void)77 eina_file_statgen_next(void)
78 {
79    eina_spinlock_take(&_eina_statgen_lock);
80    if (_eina_statgen != 0)
81      {
82         _eina_statgen++;
83         if (_eina_statgen == 0) _eina_statgen = 1;
84      }
85    eina_spinlock_release(&_eina_statgen_lock);
86 }
87 
88 EAPI Eina_Statgen
eina_file_statgen_get(void)89 eina_file_statgen_get(void)
90 {
91    Eina_Statgen s;
92    eina_spinlock_take(&_eina_statgen_lock);
93    s = _eina_statgen;
94    eina_spinlock_release(&_eina_statgen_lock);
95    return s;
96 }
97 
98 EAPI void
eina_file_statgen_enable(void)99 eina_file_statgen_enable(void)
100 {
101    eina_spinlock_take(&_eina_statgen_lock);
102    if (_eina_statgen == 0) _eina_statgen = 1;
103    eina_spinlock_release(&_eina_statgen_lock);
104 }
105 
106 EAPI void
eina_file_statgen_disable(void)107 eina_file_statgen_disable(void)
108 {
109    eina_spinlock_take(&_eina_statgen_lock);
110    _eina_statgen = 0;
111    eina_spinlock_release(&_eina_statgen_lock);
112 }
113 
114 static char *
_eina_file_escape(char * path,size_t len)115 _eina_file_escape(char *path, size_t len)
116 {
117    char *result;
118    char *p;
119    char *q;
120 
121    result = path;
122    p = result;
123    q = result;
124 
125    if (!result)
126      return NULL;
127 
128 #ifdef _WIN32
129    EVIL_PATH_SEP_WIN32_TO_UNIX(path);
130 #endif
131 
132    while ((p = strchr(p, '/')))
133      {
134 	// remove double `/'
135 	if (p[1] == '/')
136 	  {
137 	     memmove(p, p + 1, --len - (p - result));
138 	     result[len] = '\0';
139 	  }
140 	else
141 	  if (p[1] == '.'
142 	      && p[2] == '.')
143 	    {
144 	       // remove `/../'
145 	       if (p[3] == '/')
146 		 {
147 		    char tmp;
148 
149 		    len -= p + 3 - q;
150 		    memmove(q, p + 3, len - (q - result));
151 		    result[len] = '\0';
152 		    p = q;
153 
154 		    /* Update q correctly. */
155 		    tmp = *p;
156 		    *p = '\0';
157 		    q = strrchr(result, '/');
158 		    if (!q) q = result;
159 		    *p = tmp;
160 		 }
161 	       else
162 		 // remove '/..$'
163 		 if (p[3] == '\0')
164 		   {
165 		      len -= p + 2 - q;
166 		      result[len] = '\0';
167                       break;
168 		   }
169 		 else
170 		   {
171 		      q = p;
172 		      ++p;
173 		   }
174 	    }
175         else
176           if (p[1] == '.'
177               && p[2] == '/')
178             {
179                // remove '/./'
180                len -= 2;
181                memmove(p, p + 2, len - (p - result));
182                result[len] = '\0';
183                q = p;
184                ++p;
185             }
186 	  else
187 	    {
188 	       q = p;
189 	       ++p;
190 	    }
191      }
192 
193    return result;
194 }
195 
196 
197 unsigned int
eina_file_map_key_length(const void * key EINA_UNUSED)198 eina_file_map_key_length(const void *key EINA_UNUSED)
199 {
200    return sizeof (unsigned long int) * 2;
201 }
202 
203 int
eina_file_map_key_cmp(const unsigned long long int * key1,int key1_length EINA_UNUSED,const unsigned long long int * key2,int key2_length EINA_UNUSED)204 eina_file_map_key_cmp(const unsigned long long int *key1, int key1_length EINA_UNUSED,
205                        const unsigned long long int *key2, int key2_length EINA_UNUSED)
206 {
207    if (key1[0] == key2[0])
208      {
209         if (key1[1] == key2[1]) return 0;
210         if (key1[1] > key2[1]) return 1;
211         return -1;
212      }
213    if (key1[0] > key2[0]) return 1;
214    return -1;
215 }
216 
217 int
eina_file_map_key_hash(const unsigned long long int * key,int key_length EINA_UNUSED)218 eina_file_map_key_hash(const unsigned long long int *key, int key_length EINA_UNUSED)
219 {
220    return eina_hash_int64(&key[0], sizeof (unsigned long long int))
221      ^ eina_hash_int64(&key[1], sizeof (unsigned long long int));
222 }
223 
224 void *
eina_file_virtual_map_all(Eina_File * file)225 eina_file_virtual_map_all(Eina_File *file)
226 {
227    eina_lock_take(&file->lock);
228    file->global_refcount++;
229    eina_lock_release(&file->lock);
230 
231    return file->global_map;
232 }
233 
234 void *
eina_file_virtual_map_new(Eina_File * file,unsigned long int offset,unsigned long int length)235 eina_file_virtual_map_new(Eina_File *file,
236                           unsigned long int offset, unsigned long int length)
237 {
238    Eina_File_Map *map;
239    unsigned long int key[2];
240 
241    // offset and length has already been checked by the caller function
242 
243    key[0] = offset;
244    key[1] = length;
245 
246    eina_lock_take(&file->lock);
247 
248    map = eina_hash_find(file->map, &key);
249    if (!map)
250      {
251         map = malloc(sizeof (Eina_File_Map));
252         if (!map) goto on_error;
253 
254         map->map = ((char*) file->global_map) + offset;
255         map->offset = offset;
256         map->length = length;
257         map->refcount = 0;
258 
259         eina_hash_add(file->map, &key, map);
260         eina_hash_direct_add(file->rmap, &map->map, map);
261      }
262 
263    map->refcount++;
264 
265  on_error:
266    eina_lock_release(&file->lock);
267    return map ? map->map : NULL;
268 }
269 
270 void
eina_file_virtual_map_free(Eina_File * file,void * map)271 eina_file_virtual_map_free(Eina_File *file, void *map)
272 {
273    Eina_File_Map *em;
274 
275    eina_lock_take(&file->lock);
276 
277    // map could equal global_map even if length != file->length
278    em = eina_hash_find(file->rmap, &map);
279    if (em)
280      {
281         unsigned long int key[2];
282 
283         em->refcount--;
284 
285         if (em->refcount > 0) goto on_exit;
286 
287         key[0] = em->offset;
288         key[1] = em->length;
289 
290         eina_hash_del(file->rmap, &map, em);
291         eina_hash_del(file->map, &key, em);
292      }
293    else
294      {
295         if (file->global_map == map)
296           {
297              file->global_refcount--;
298           }
299      }
300 
301  on_exit:
302    eina_lock_release(&file->lock);
303 }
304 
305 void
eina_file_common_map_free(Eina_File * file,void * map,void (* free_func)(Eina_File_Map * map))306 eina_file_common_map_free(Eina_File *file, void *map,
307                           void (*free_func)(Eina_File_Map *map))
308 {
309    Eina_File_Map *em;
310    unsigned long int key[2];
311    Eina_List *l = NULL;
312    Eina_Bool hashed = EINA_TRUE;
313 
314    em = eina_hash_find(file->rmap, &map);
315    if (!em)
316      {
317         EINA_LIST_FOREACH(file->dead_map, l, em)
318           if (em->map == map)
319             {
320                hashed = EINA_FALSE;
321                break ;
322             }
323         if (hashed) return ;
324      }
325 
326    em->refcount--;
327 
328    if (em->refcount > 0) return ;
329 
330    key[0] = em->offset;
331    key[1] = em->length;
332 
333    if (hashed)
334      {
335         eina_hash_del(file->rmap, &map, em);
336         eina_hash_del(file->map, &key, em);
337      }
338    else
339      {
340         file->dead_map = eina_list_remove_list(file->dead_map, l);
341         free_func(em);
342      }
343 }
344 
345 void
eina_file_flush(Eina_File * file,unsigned long int length)346 eina_file_flush(Eina_File *file, unsigned long int length)
347 {
348    Eina_File_Map *tmp;
349    Eina_Iterator *it;
350    Eina_List *dead_map = NULL;
351    Eina_List *l;
352 
353    eina_lock_take(&file->lock);
354 
355    // File size changed
356    if (file->global_map != MAP_FAILED)
357      {
358         // Forget global map
359         tmp = malloc(sizeof (Eina_File_Map));
360         if (tmp)
361           {
362              tmp->map = file->global_map;
363              tmp->offset = 0;
364              tmp->length = file->length;
365              tmp->refcount = file->refcount;
366 
367              file->dead_map = eina_list_append(file->dead_map, tmp);
368           }
369 
370         file->global_map = MAP_FAILED;
371         file->global_refcount = 0;
372      }
373 
374    it = eina_hash_iterator_data_new(file->map);
375    EINA_ITERATOR_FOREACH(it, tmp)
376      {
377         // Add out of limit map to dead_map
378         if (tmp->offset + tmp->length > length)
379           dead_map = eina_list_append(dead_map, tmp);
380      }
381    eina_iterator_free(it);
382 
383    EINA_LIST_FOREACH(dead_map, l, tmp)
384      {
385         unsigned long int key[2];
386 
387         key[0] = tmp->offset;
388         key[1] = tmp->length;
389 
390         eina_hash_del(file->rmap, &tmp->map, tmp);
391         eina_hash_del(file->map, &key, tmp);
392      }
393 
394    file->dead_map = eina_list_merge(file->dead_map, dead_map);
395 
396    eina_lock_release(&file->lock);
397 }
398 
399 // Private to this file API
400 static void
_eina_file_map_close(Eina_File_Map * map)401 _eina_file_map_close(Eina_File_Map *map)
402 {
403    free(map);
404 }
405 
406 // Global API
407 
408 EAPI char *
eina_file_path_sanitize(const char * path)409 eina_file_path_sanitize(const char *path)
410 {
411    Eina_Tmpstr *result = NULL;
412    char *r;
413    size_t len;
414 
415    if (!path) return NULL;
416 
417    len = strlen(path);
418 
419    if (eina_file_path_relative(path))
420      {
421        result = eina_file_current_directory_get(path, len);
422        len = eina_tmpstr_len(result);
423      }
424    else
425      result = path;
426 
427    r = _eina_file_escape(strdup(result ? result : ""), len);
428    if (result != path) eina_tmpstr_del(result);
429 
430    return r;
431 }
432 
433 EAPI Eina_File *
eina_file_virtualize(const char * virtual_name,const void * data,unsigned long long length,Eina_Bool copy)434 eina_file_virtualize(const char *virtual_name, const void *data, unsigned long long length, Eina_Bool copy)
435 {
436    Eina_File *file;
437    Eina_Nano_Time tp;
438    long int ti;
439    const char *tmpname = "/dev/mem/virtual\\/%16x";
440    size_t slen, head_padded;
441 
442    EINA_SAFETY_ON_NULL_RETURN_VAL(data, NULL);
443 
444    // Generate an almost uniq filename based on current nsec time.
445    if (_eina_time_get(&tp)) return NULL;
446    ti = _eina_time_convert(&tp);
447 
448    slen = virtual_name ? strlen(virtual_name) + 1 : strlen(tmpname) + 17;
449    // align copied data at end of file struct to 16 bytes...
450    head_padded = 16 * ((sizeof(Eina_File) + slen + 15) / 16);
451 
452    file = malloc(head_padded + (copy ? length : 0));
453    if (!file) return NULL;
454 
455    memset(file, 0, sizeof(Eina_File));
456    EINA_MAGIC_SET(file, EINA_FILE_MAGIC);
457    file->filename = (char *)(file + 1);
458    if (virtual_name)
459      file->filename = eina_stringshare_add(virtual_name);
460    else
461      file->filename = eina_stringshare_printf(tmpname, ti);
462 
463    eina_lock_recursive_new(&file->lock);
464    file->mtime = ti / 1000;
465    file->length = length;
466 #ifdef _STAT_VER_LINUX
467    file->mtime_nsec = ti;
468 #endif
469    file->refcount = 1;
470 #ifndef _WIN32
471    file->fd = -1;
472 #else
473    file->handle = INVALID_HANDLE_VALUE;
474 #endif
475    file->virtual = EINA_TRUE;
476    file->map = eina_hash_new(EINA_KEY_LENGTH(eina_file_map_key_length),
477                              EINA_KEY_CMP(eina_file_map_key_cmp),
478                              EINA_KEY_HASH(eina_file_map_key_hash),
479                              EINA_FREE_CB(_eina_file_map_close),
480                              3);
481    file->rmap = eina_hash_pointer_new(NULL);
482 
483    if (copy)
484      {
485         file->copied = EINA_TRUE;
486         file->global_map = ((char *)file) + head_padded;
487         memcpy((char *)file->global_map, data, length);
488      }
489    else
490      {
491         file->global_map = (void *)data;
492      }
493 
494    return file;
495 }
496 
497 EAPI Eina_Bool
eina_file_virtual(Eina_File * file)498 eina_file_virtual(Eina_File *file)
499 {
500    if (!file) return EINA_FALSE;
501    EINA_FILE_MAGIC_CHECK(file, EINA_FALSE);
502    return file->virtual;
503 }
504 
505 EAPI Eina_File *
eina_file_dup(const Eina_File * f)506 eina_file_dup(const Eina_File *f)
507 {
508    Eina_File *file = (Eina_File*) f;
509 
510    if (file)
511      {
512         EINA_FILE_MAGIC_CHECK(f, NULL);
513         eina_lock_take(&file->lock);
514 
515         // For ease of use and safety of the API, if you dup a virtualized file, we prefer to make a copy
516         if (file->virtual && !file->copied)
517           {
518              Eina_File *r;
519 
520              r = eina_file_virtualize(file->filename, file->global_map, file->length, EINA_TRUE);
521              eina_lock_release(&file->lock);
522 
523              return r;
524           }
525         file->refcount++;
526         eina_lock_release(&file->lock);
527      }
528    return file;
529 }
530 
531 void
eina_file_clean_close(Eina_File * file)532 eina_file_clean_close(Eina_File *file)
533 {
534    // Generic destruction of the file
535    eina_hash_free(file->rmap); file->rmap = NULL;
536    eina_hash_free(file->map); file->map = NULL;
537    eina_stringshare_del(file->filename);
538 
539    // Backend specific file resource close
540    eina_file_real_close(file);
541 
542    // Final death
543    EINA_MAGIC_SET(file, 0);
544    free(file);
545 }
546 
547 EAPI void
eina_file_close(Eina_File * file)548 eina_file_close(Eina_File *file)
549 {
550    Eina_Bool leave = EINA_TRUE;
551    unsigned int key;
552 
553    if (!file) return ;
554    EINA_FILE_MAGIC_CHECK(file);
555 
556    eina_lock_take(&_eina_file_lock_cache);
557 
558    eina_lock_take(&file->lock);
559    file->refcount--;
560    if (file->refcount == 0) leave = EINA_FALSE;
561    eina_lock_release(&file->lock);
562    if (leave) goto end;
563 
564    key = eina_hash_superfast((void*) &file->filename, sizeof (void*));
565    if (eina_hash_find_by_hash(_eina_file_cache,
566                               file->filename, 0, key) == file)
567      {
568         eina_hash_del_by_key_hash(_eina_file_cache,
569                                   file->filename, 0, key);
570      }
571 
572    eina_file_clean_close(file);
573  end:
574    eina_lock_release(&_eina_file_lock_cache);
575 }
576 
577 EAPI size_t
eina_file_size_get(const Eina_File * file)578 eina_file_size_get(const Eina_File *file)
579 {
580    EINA_FILE_MAGIC_CHECK(file, 0);
581    return file->length;
582 }
583 
584 EAPI time_t
eina_file_mtime_get(const Eina_File * file)585 eina_file_mtime_get(const Eina_File *file)
586 {
587    EINA_FILE_MAGIC_CHECK(file, 0);
588    return file->mtime;
589 }
590 
591 EAPI const char *
eina_file_filename_get(const Eina_File * file)592 eina_file_filename_get(const Eina_File *file)
593 {
594    EINA_FILE_MAGIC_CHECK(file, NULL);
595    return file->filename;
596 }
597 
598 Eina_Stringshare *
eina_file_sanitize(const char * path)599 eina_file_sanitize(const char *path)
600 {
601    char *filename;
602    Eina_Stringshare *ss;
603 
604    filename = eina_file_path_sanitize(path);
605    if (!filename) return NULL;
606 
607    ss = eina_stringshare_add(filename);
608    free(filename);
609    return ss;
610 }
611 
612 /* search '\r' and '\n' by preserving cache locality and page locality
613    in doing a search inside 4K boundary.
614  */
615 static inline const char *
_eina_find_eol(const char * start,int boundary,const char * end)616 _eina_find_eol(const char *start, int boundary, const char *end)
617 {
618    const char *cr;
619    const char *lf;
620    unsigned long long chunk;
621 
622    while (start < end)
623      {
624         chunk = start + boundary < end ? boundary : end - start;
625         cr = memchr(start, '\r', chunk);
626         lf = memchr(start, '\n', chunk);
627         if (cr)
628           {
629              if (lf && lf < cr)
630                return lf;
631              return cr;
632           }
633         else if (lf)
634            return lf;
635 
636         start += chunk;
637         boundary = 4096;
638      }
639 
640    return end;
641 }
642 
643 static Eina_Bool
_eina_file_map_lines_iterator_next(Eina_Lines_Iterator * it,void ** data)644 _eina_file_map_lines_iterator_next(Eina_Lines_Iterator *it, void **data)
645 {
646    const char *eol;
647    unsigned char match;
648 
649    if (it->current.end >= it->end)
650      return EINA_FALSE;
651 
652    match = *it->current.end;
653    if (it->current.index > 0)
654      it->current.end++;
655    while (it->current.end < it->end &&
656           (*it->current.end == '\n' || *it->current.end == '\r'))
657      {
658         if (match == *it->current.end)
659           break;
660         it->current.end++;
661      }
662    it->current.index++;
663 
664    if (it->current.end == it->end)
665      return EINA_FALSE;
666 
667    eol = _eina_find_eol(it->current.end,
668                         it->boundary,
669                         it->end);
670    it->boundary = (uintptr_t) eol & 0x3FF;
671    if (it->boundary == 0) it->boundary = 4096;
672 
673    it->current.start = it->current.end;
674 
675    it->current.end = eol;
676    it->current.length = eol - it->current.start;
677 
678    *data = &it->current;
679    return EINA_TRUE;
680 }
681 
682 static Eina_File *
_eina_file_map_lines_iterator_container(Eina_Lines_Iterator * it)683 _eina_file_map_lines_iterator_container(Eina_Lines_Iterator *it)
684 {
685    return it->fp;
686 }
687 
688 static void
_eina_file_map_lines_iterator_free(Eina_Lines_Iterator * it)689 _eina_file_map_lines_iterator_free(Eina_Lines_Iterator *it)
690 {
691    eina_file_map_free(it->fp, (void*) it->map);
692    eina_file_close(it->fp);
693 
694    EINA_MAGIC_SET(&it->iterator, 0);
695    free(it);
696 }
697 
698 EAPI Eina_Iterator *
eina_file_map_lines(Eina_File * file)699 eina_file_map_lines(Eina_File *file)
700 {
701    Eina_Lines_Iterator *it;
702 
703    EINA_FILE_MAGIC_CHECK(file, NULL);
704 
705    if (file->length == 0) return NULL;
706 
707    it = calloc(1, sizeof (Eina_Lines_Iterator));
708    if (!it) return NULL;
709 
710    EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);
711 
712    it->map = eina_file_map_all(file, EINA_FILE_SEQUENTIAL);
713    if (!it->map)
714      {
715         free(it);
716         return NULL;
717      }
718 
719    eina_lock_take(&file->lock);
720    file->refcount++;
721    eina_lock_release(&file->lock);
722 
723    it->fp = file;
724    it->boundary = 4096;
725    it->current.start = it->map;
726    it->current.end = it->current.start;
727    it->current.index = 0;
728    it->current.length = 0;
729    it->end = it->map + it->fp->length;
730 
731    it->iterator.version = EINA_ITERATOR_VERSION;
732    it->iterator.next = FUNC_ITERATOR_NEXT(_eina_file_map_lines_iterator_next);
733    it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(_eina_file_map_lines_iterator_container);
734    it->iterator.free = FUNC_ITERATOR_FREE(_eina_file_map_lines_iterator_free);
735 
736    return &it->iterator;
737 }
738 
739 static Eina_Bool
_eina_file_copy_write_internal(int fd,char * buf,size_t size)740 _eina_file_copy_write_internal(int fd, char *buf, size_t size)
741 {
742    size_t done = 0;
743    while (done < size)
744      {
745         ssize_t w = write(fd, buf + done, size - done);
746         if (w >= 0)
747           done += w;
748         else if ((errno != EAGAIN) && (errno != EINTR))
749           {
750              ERR("Error writing destination file during copy: %s",
751                  strerror(errno));
752              return EINA_FALSE;
753           }
754      }
755    return EINA_TRUE;
756 }
757 
758 static Eina_Bool
_eina_file_copy_read_internal(int fd,char * buf,off_t bufsize,ssize_t * readsize)759 _eina_file_copy_read_internal(int fd, char *buf, off_t bufsize, ssize_t *readsize)
760 {
761    while (1)
762      {
763         ssize_t r = read(fd, buf, bufsize);
764         if (r == 0)
765           {
766              ERR("Premature end of source file during copy.");
767              return EINA_FALSE;
768           }
769         else if (r < 0)
770           {
771              if ((errno != EAGAIN) && (errno != EINTR))
772                {
773                   ERR("Error reading source file during copy: %s",
774                       strerror(errno));
775                   return EINA_FALSE;
776                }
777           }
778         else
779           {
780              *readsize = r;
781              return EINA_TRUE;
782           }
783      }
784 }
785 
786 #ifdef HAVE_SPLICE
787 static Eina_Bool
_eina_file_copy_write_splice_internal(int fd,int pipefd,size_t size)788 _eina_file_copy_write_splice_internal(int fd, int pipefd, size_t size)
789 {
790    size_t done = 0;
791    while (done < size)
792      {
793         ssize_t w = splice(pipefd, NULL, fd, NULL, size - done, SPLICE_F_MORE);
794         if (w >= 0)
795           done += w;
796         else if (errno == EINVAL)
797           {
798              INF("Splicing is not supported for destination file");
799              return EINA_FALSE;
800           }
801         else if ((errno != EAGAIN) && (errno != EINTR))
802           {
803              ERR("Error splicing to destination file during copy: %s",
804                  strerror(errno));
805              return EINA_FALSE;
806           }
807      }
808    return EINA_TRUE;
809 }
810 
811 static Eina_Bool
_eina_file_copy_read_splice_internal(int fd,int pipefd,off_t bufsize,ssize_t * readsize)812 _eina_file_copy_read_splice_internal(int fd, int pipefd, off_t bufsize, ssize_t *readsize)
813 {
814    while (1)
815      {
816         ssize_t r = splice(fd, NULL, pipefd, NULL, bufsize, SPLICE_F_MORE);
817         if (r == 0)
818           {
819              ERR("Premature end of source file during splice.");
820              return EINA_FALSE;
821           }
822         else if (r < 0)
823           {
824              if (errno == EINVAL)
825                {
826                   INF("Splicing is not supported for source file");
827                   return EINA_FALSE;
828                }
829              else if ((errno != EAGAIN) && (errno != EINTR))
830                {
831                   ERR("Error splicing from source file during copy: %s",
832                       strerror(errno));
833                   return EINA_FALSE;
834                }
835           }
836         else
837           {
838              *readsize = r;
839              return EINA_TRUE;
840           }
841      }
842 }
843 #endif
844 
845 static Eina_Bool
_eina_file_copy_splice_internal(int s,int d,off_t total,Eina_File_Copy_Progress cb,const void * cb_data,Eina_Bool * splice_unsupported)846 _eina_file_copy_splice_internal(int s, int d, off_t total, Eina_File_Copy_Progress cb, const void *cb_data, Eina_Bool *splice_unsupported)
847 {
848 #ifdef HAVE_SPLICE
849    off_t bufsize = COPY_BLOCKSIZE;
850    off_t done;
851    Eina_Bool ret;
852    int pipefd[2];
853 
854    *splice_unsupported = EINA_TRUE;
855 
856    if (pipe(pipefd) < 0) return EINA_FALSE;
857 
858    done = 0;
859    ret = EINA_TRUE;
860    while (done < total)
861      {
862         size_t todo;
863         ssize_t r;
864 
865         if (done + bufsize < total)
866           todo = bufsize;
867         else
868           todo = total - done;
869 
870         ret = _eina_file_copy_read_splice_internal(s, pipefd[1], todo, &r);
871         if (!ret) break;
872 
873         ret = _eina_file_copy_write_splice_internal(d, pipefd[0], r);
874         if (!ret) break;
875 
876         *splice_unsupported = EINA_FALSE;
877         done += r;
878 
879         if (cb)
880           {
881              ret = cb((void *)cb_data, done, total);
882              if (!ret) break;
883           }
884      }
885 
886    close(pipefd[0]);
887    close(pipefd[1]);
888 
889    return ret;
890 #endif
891    *splice_unsupported = EINA_TRUE;
892    return EINA_FALSE;
893    (void)s;
894    (void)d;
895    (void)total;
896    (void)cb;
897    (void)cb_data;
898 }
899 
900 static Eina_Bool
_eina_file_copy_internal(int s,int d,off_t total,Eina_File_Copy_Progress cb,const void * cb_data)901 _eina_file_copy_internal(int s, int d, off_t total, Eina_File_Copy_Progress cb, const void *cb_data)
902 {
903    void *buf = NULL;
904    off_t bufsize = COPY_BLOCKSIZE;
905    off_t done;
906    Eina_Bool ret, splice_unsupported;
907 
908    ret = _eina_file_copy_splice_internal(s, d, total, cb, cb_data,
909                                          &splice_unsupported);
910    if (ret)
911      return EINA_TRUE;
912    else if (!splice_unsupported) /* splice works, but copy failed anyway */
913      return EINA_FALSE;
914 
915    /* make sure splice didn't change the position */
916    lseek(s, 0, SEEK_SET);
917    lseek(d, 0, SEEK_SET);
918 
919    while ((bufsize > 0) && ((buf = malloc(bufsize)) == NULL))
920      bufsize /= 128;
921 
922    EINA_SAFETY_ON_NULL_RETURN_VAL(buf, EINA_FALSE);
923 
924    done = 0;
925    ret = EINA_TRUE;
926    while (done < total)
927      {
928         size_t todo;
929         ssize_t r;
930 
931         if (done + bufsize < total)
932           todo = bufsize;
933         else
934           todo = total - done;
935 
936         ret = _eina_file_copy_read_internal(s, buf, todo, &r);
937         if (!ret) break;
938 
939         ret = _eina_file_copy_write_internal(d, buf, r);
940         if (!ret) break;
941 
942         done += r;
943 
944         if (cb)
945           {
946              ret = cb((void *)cb_data, done, total);
947              if (!ret) break;
948           }
949      }
950 
951    free(buf);
952    return ret;
953 }
954 
955 EAPI Eina_Bool
eina_file_copy(const char * src,const char * dst,Eina_File_Copy_Flags flags,Eina_File_Copy_Progress cb,const void * cb_data)956 eina_file_copy(const char *src, const char *dst, Eina_File_Copy_Flags flags, Eina_File_Copy_Progress cb, const void *cb_data)
957 {
958    struct stat st;
959    int s, d = -1;
960    Eina_Bool success;
961 
962    EINA_SAFETY_ON_NULL_RETURN_VAL(src, EINA_FALSE);
963    EINA_SAFETY_ON_NULL_RETURN_VAL(dst, EINA_FALSE);
964 
965    s = open(src, O_RDONLY | O_BINARY);
966    EINA_SAFETY_ON_TRUE_RETURN_VAL (s < 0, EINA_FALSE);
967 
968    success = (fstat(s, &st) == 0);
969    EINA_SAFETY_ON_FALSE_GOTO(success, end);
970 
971    d = open(dst, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
972    EINA_SAFETY_ON_TRUE_GOTO(d < 0, end);
973 
974    success = _eina_file_copy_internal(s, d, st.st_size, cb, cb_data);
975    if (success)
976      {
977 #ifdef HAVE_FCHMOD
978         if (flags & EINA_FILE_COPY_PERMISSION)
979           fchmod(d, st.st_mode);
980 #endif
981         if (flags & EINA_FILE_COPY_XATTR)
982           eina_xattr_fd_copy(s, d);
983      }
984 
985  end:
986    if (d >= 0) close(d);
987    else success = EINA_FALSE;
988    close(s);
989 
990    if (!success)
991      unlink(dst);
992 
993    return success;
994 }
995 
996 EAPI int
eina_file_mkstemp(const char * templatename,Eina_Tmpstr ** path)997 eina_file_mkstemp(const char *templatename, Eina_Tmpstr **path)
998 {
999    char buffer[PATH_MAX];
1000    const char *XXXXXX = NULL, *sep;
1001    int fd, len;
1002 #ifndef _WIN32
1003    mode_t old_umask;
1004 #endif
1005 
1006    EINA_SAFETY_ON_NULL_RETURN_VAL(templatename, -1);
1007 
1008    sep = strchr(templatename, '/');
1009 #ifdef _WIN32
1010    if (!sep) sep = strchr(templatename, '\\');
1011 #endif
1012    if (sep)
1013      {
1014         len = eina_strlcpy(buffer, templatename, sizeof(buffer));
1015      }
1016    else
1017      {
1018         len = eina_file_path_join(buffer, sizeof(buffer),
1019                                   eina_environment_tmp_get(), templatename);
1020      }
1021 
1022    /*
1023     * Unix:
1024     * Make sure temp file is created with secure permissions,
1025     * http://man7.org/linux/man-pages/man3/mkstemp.3.html#NOTES
1026     *
1027     * Windows:
1028     * no secure permissions anyway and the umask use below makes
1029     * the file read-only.
1030     */
1031 #ifndef _WIN32
1032    old_umask = umask(S_IRWXG|S_IRWXO);
1033 #endif
1034    if ((XXXXXX = strstr(buffer, "XXXXXX.")) != NULL)
1035      {
1036         int suffixlen = buffer + len - XXXXXX - 6;
1037         fd = mkstemps(buffer, suffixlen);
1038      }
1039    else
1040      fd = mkstemp(buffer);
1041 #ifndef _WIN32
1042    umask(old_umask);
1043 #endif
1044 
1045    if (fd < 0)
1046      {
1047         if (path) *path = NULL;
1048         return -1;
1049      }
1050 
1051    if (path) *path = eina_tmpstr_add(buffer);
1052    return fd;
1053 }
1054 
1055 EAPI Eina_Bool
eina_file_mkdtemp(const char * templatename,Eina_Tmpstr ** path)1056 eina_file_mkdtemp(const char *templatename, Eina_Tmpstr **path)
1057 {
1058    char buffer[PATH_MAX];
1059    char *tmpdirname, *sep;
1060 
1061    EINA_SAFETY_ON_NULL_RETURN_VAL(templatename, EINA_FALSE);
1062 
1063    sep = strchr(templatename, '/');
1064 #ifdef _WIN32
1065    if (!sep) sep = strchr(templatename, '\\');
1066 #endif
1067    if (sep)
1068      {
1069         eina_strlcpy(buffer, templatename, sizeof(buffer));
1070      }
1071    else
1072      {
1073         eina_file_path_join(buffer, sizeof(buffer),
1074                             eina_environment_tmp_get(), templatename);
1075      }
1076 
1077    tmpdirname = mkdtemp(buffer);
1078    if (tmpdirname == NULL)
1079      {
1080         if (path) *path = NULL;
1081         return EINA_FALSE;
1082      }
1083 
1084    if (path) *path = eina_tmpstr_add(tmpdirname);
1085    return EINA_TRUE;
1086 }
1087 
1088 /*============================================================================*
1089  *                                 Global                                     *
1090  *============================================================================*/
1091 
1092 Eina_Bool
eina_file_init(void)1093 eina_file_init(void)
1094 {
1095    _eina_file_log_dom = eina_log_domain_register("eina_file",
1096                                                  EINA_LOG_COLOR_DEFAULT);
1097    if (_eina_file_log_dom < 0)
1098      {
1099         EINA_LOG_ERR("Could not register log domain: eina_file");
1100         return EINA_FALSE;
1101      }
1102 
1103    _eina_file_cache = eina_hash_stringshared_new(NULL);
1104    if (!_eina_file_cache)
1105      {
1106         ERR("Could not create cache.");
1107         eina_log_domain_unregister(_eina_file_log_dom);
1108         _eina_file_log_dom = -1;
1109         return EINA_FALSE;
1110      }
1111 
1112    eina_spinlock_new(&_eina_statgen_lock);
1113    eina_spinlock_take(&_eina_statgen_lock);
1114    if (getenv("EINA_STATGEN")) _eina_statgen = 1;
1115    eina_spinlock_release(&_eina_statgen_lock);
1116    eina_lock_recursive_new(&_eina_file_lock_cache);
1117    eina_magic_string_set(EINA_FILE_MAGIC, "Eina_File");
1118 
1119    return EINA_TRUE;
1120 }
1121 
1122 Eina_Bool
eina_file_shutdown(void)1123 eina_file_shutdown(void)
1124 {
1125    if (eina_hash_population(_eina_file_cache) > 0)
1126      {
1127         Eina_Iterator *it;
1128         const char *key;
1129 
1130         it = eina_hash_iterator_key_new(_eina_file_cache);
1131         EINA_ITERATOR_FOREACH(it, key)
1132           {
1133              Eina_File *f = eina_hash_find(_eina_file_cache, key);
1134              ERR("File [%s] still open %i times !", key, f->refcount);
1135           }
1136         eina_iterator_free(it);
1137      }
1138 
1139    eina_hash_free(_eina_file_cache);
1140    _eina_file_cache = NULL;
1141 
1142    eina_lock_free(&_eina_file_lock_cache);
1143 
1144    eina_log_domain_unregister(_eina_file_log_dom);
1145    _eina_file_log_dom = -1;
1146    eina_spinlock_free(&_eina_statgen_lock);
1147    return EINA_TRUE;
1148 }
1149 
1150 EAPI Eina_Bool
eina_file_close_on_exec(int fd,Eina_Bool on)1151 eina_file_close_on_exec(int fd, Eina_Bool on)
1152 {
1153 #ifdef _WIN32
1154    return EINA_TRUE;
1155    (void)fd;
1156    (void)on;
1157 #elif HAVE_FCNTL
1158    int flags;
1159 
1160    flags = fcntl(fd, F_GETFD);
1161    if (flags < 0)
1162      {
1163         int errno_backup = errno;
1164         ERR("%#x = fcntl(%d, F_GETFD): %s", flags, fd, strerror(errno));
1165         errno = errno_backup;
1166         return EINA_FALSE;
1167      }
1168 
1169    if (on)
1170      flags |= FD_CLOEXEC;
1171    else
1172      flags &= (~flags);
1173 
1174    if (fcntl(fd, F_SETFD, flags) == -1)
1175      {
1176         int errno_backup = errno;
1177         ERR("fcntl(%d, F_SETFD, %#x): %s", fd, flags, strerror(errno));
1178         errno = errno_backup;
1179         return EINA_FALSE;
1180      }
1181    return EINA_TRUE;
1182 #else
1183    static Eina_Bool statement = EINA_FALSE;
1184 
1185    if (!statement)
1186      ERR("fcntl is not available on your platform. fd may leak when using exec.");
1187    statement = EINA_TRUE;
1188    return EINA_TRUE;
1189 #endif
1190 }
1191