upgrade obexd to 0.47
[profile/ivi/obexd.git] / plugins / filesystem.c
1 /*
2  *
3  *  OBEX Server
4  *
5  *  Copyright (C) 2009-2010  Intel Corporation
6  *  Copyright (C) 2007-2010  Marcel Holtmann <marcel@holtmann.org>
7  *
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22  *
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <stdio.h>
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <dirent.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/statvfs.h>
39 #include <sys/sendfile.h>
40 #include <fcntl.h>
41 #include <wait.h>
42 #include <inttypes.h>
43
44 #include <glib.h>
45
46 #include "obexd.h"
47 #include "plugin.h"
48 #include "log.h"
49 #include "mimetype.h"
50 #include "filesystem.h"
51
52 #define EOL_CHARS "\n"
53
54 #define FL_VERSION "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" EOL_CHARS
55
56 #define FL_TYPE "<!DOCTYPE folder-listing SYSTEM \"obex-folder-listing.dtd\">" EOL_CHARS
57
58 #define FL_TYPE_PCSUITE "<!DOCTYPE folder-listing SYSTEM \"obex-folder-listing.dtd\"" EOL_CHARS \
59                         "  [ <!ATTLIST folder mem-type CDATA #IMPLIED> ]>" EOL_CHARS
60
61 #define FL_BODY_BEGIN "<folder-listing version=\"1.0\">" EOL_CHARS
62
63 #define FL_BODY_END "</folder-listing>" EOL_CHARS
64
65 #define FL_PARENT_FOLDER_ELEMENT "<parent-folder/>" EOL_CHARS
66
67 #define FL_FILE_ELEMENT "<file name=\"%s\" size=\"%" PRIu64 "\"" \
68                         " %s accessed=\"%s\" " \
69                         "modified=\"%s\" created=\"%s\"/>" EOL_CHARS
70
71 #define FL_FOLDER_ELEMENT "<folder name=\"%s\" %s accessed=\"%s\" " \
72                         "modified=\"%s\" created=\"%s\"/>" EOL_CHARS
73
74 #define FL_FOLDER_ELEMENT_PCSUITE "<folder name=\"%s\" %s accessed=\"%s\"" \
75                         " modified=\"%s\" mem-type=\"DEV\"" \
76                         " created=\"%s\"/>" EOL_CHARS
77
78 #define FTP_TARGET_SIZE 16
79
80 static const uint8_t FTP_TARGET[FTP_TARGET_SIZE] = {
81                         0xF9, 0xEC, 0x7B, 0xC4,  0x95, 0x3C, 0x11, 0xD2,
82                         0x98, 0x4E, 0x52, 0x54,  0x00, 0xDC, 0x9E, 0x09  };
83
84 #define PCSUITE_WHO_SIZE 8
85
86 static const uint8_t PCSUITE_WHO[PCSUITE_WHO_SIZE] = {
87                         'P', 'C', ' ', 'S', 'u', 'i', 't', 'e' };
88
89 gboolean is_filename(const char *name)
90 {
91         if (strchr(name, '/'))
92                 return FALSE;
93
94         if (strcmp(name, ".") == 0)
95                 return FALSE;
96
97         if (strcmp(name, "..") == 0)
98                 return FALSE;
99
100         return TRUE;
101 }
102
103 int verify_path(const char *path)
104 {
105         char *t;
106         int ret = 0;
107
108         if (obex_option_symlinks())
109                 return 0;
110
111         t = realpath(path, NULL);
112         if (t == NULL)
113                 return -errno;
114
115         if (!g_str_has_prefix(t, obex_option_root_folder()))
116                 ret = -EPERM;
117
118         free(t);
119
120         return ret;
121 }
122
123 static char *file_stat_line(char *filename, struct stat *fstat,
124                                         struct stat *dstat, gboolean root,
125                                         gboolean pcsuite)
126 {
127         char perm[51], atime[18], ctime[18], mtime[18];
128         char *escaped, *ret = NULL;
129
130         snprintf(perm, 50, "user-perm=\"%s%s%s\" group-perm=\"%s%s%s\" "
131                         "other-perm=\"%s%s%s\"",
132                         (fstat->st_mode & S_IRUSR ? "R" : ""),
133                         (fstat->st_mode & S_IWUSR ? "W" : ""),
134                         (dstat->st_mode & S_IWUSR ? "D" : ""),
135                         (fstat->st_mode & S_IRGRP ? "R" : ""),
136                         (fstat->st_mode & S_IWGRP ? "W" : ""),
137                         (dstat->st_mode & S_IWGRP ? "D" : ""),
138                         (fstat->st_mode & S_IROTH ? "R" : ""),
139                         (fstat->st_mode & S_IWOTH ? "W" : ""),
140                         (dstat->st_mode & S_IWOTH ? "D" : ""));
141
142         strftime(atime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_atime));
143         strftime(ctime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_ctime));
144         strftime(mtime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_mtime));
145
146         escaped = g_markup_escape_text(filename, -1);
147
148         if (S_ISDIR(fstat->st_mode)) {
149                 if (pcsuite && root && g_str_equal(filename, "Data"))
150                         ret = g_strdup_printf(FL_FOLDER_ELEMENT_PCSUITE,
151                                                 escaped, perm, atime,
152                                                 mtime, ctime);
153                 else
154                         ret = g_strdup_printf(FL_FOLDER_ELEMENT, escaped, perm,
155                                                         atime, mtime, ctime);
156         } else if (S_ISREG(fstat->st_mode))
157                 ret = g_strdup_printf(FL_FILE_ELEMENT, escaped,
158                                         (uint64_t) fstat->st_size,
159                                         perm, atime, mtime, ctime);
160
161         g_free(escaped);
162
163         return ret;
164 }
165
166 static void *filesystem_open(const char *name, int oflag, mode_t mode,
167                                         void *context, size_t *size, int *err)
168 {
169         struct stat stats;
170         struct statvfs buf;
171         int fd, ret;
172         uint64_t avail;
173
174         fd = open(name, oflag, mode);
175         if (fd < 0) {
176                 if (err)
177                         *err = -errno;
178                 return NULL;
179         }
180
181         if (fstat(fd, &stats) < 0) {
182                 if (err)
183                         *err = -errno;
184                 goto failed;
185         }
186
187         ret = verify_path(name);
188         if (ret < 0) {
189                 if (err)
190                         *err = ret;
191                 goto failed;
192         }
193
194         if (oflag == O_RDONLY) {
195                 if (size)
196                         *size = stats.st_size;
197                 goto done;
198         }
199
200         if (fstatvfs(fd, &buf) < 0) {
201                 if (err)
202                         *err = -errno;
203                 goto failed;
204         }
205
206         if (size == NULL)
207                 goto done;
208
209         avail = (uint64_t) buf.f_bsize * buf.f_bavail;
210         if (avail < *size) {
211                 if (err)
212                         *err = -ENOSPC;
213                 goto failed;
214         }
215
216 done:
217         if (err)
218                 *err = 0;
219
220         return GINT_TO_POINTER(fd);
221
222 failed:
223         close(fd);
224         return NULL;
225 }
226
227 static int filesystem_close(void *object)
228 {
229         if (close(GPOINTER_TO_INT(object)) < 0)
230                 return -errno;
231
232         return 0;
233 }
234
235 static ssize_t filesystem_read(void *object, void *buf, size_t count)
236 {
237         ssize_t ret;
238
239         ret = read(GPOINTER_TO_INT(object), buf, count);
240         if (ret < 0)
241                 return -errno;
242
243         return ret;
244 }
245
246 static ssize_t filesystem_write(void *object, const void *buf, size_t count)
247 {
248         ssize_t ret;
249
250         ret = write(GPOINTER_TO_INT(object), buf, count);
251         if (ret < 0)
252                 return -errno;
253
254         return ret;
255 }
256
257 static int filesystem_rename(const char *name, const char *destname)
258 {
259         int ret;
260
261         ret = rename(name, destname);
262         if (ret < 0) {
263                 error("rename(%s, %s): %s (%d)", name, destname,
264                                                 strerror(errno), errno);
265                 return -errno;
266         }
267
268         return ret;
269 }
270
271 static int sendfile_async(int out_fd, int in_fd, off_t *offset, size_t count)
272 {
273         int pid;
274
275         /* Run sendfile on child process */
276         pid = fork();
277         switch (pid) {
278                 case 0:
279                         break;
280                 case -1:
281                         error("fork() %s (%d)", strerror(errno), errno);
282                         return -errno;
283                 default:
284                         DBG("child %d forked", pid);
285                         return pid;
286         }
287
288         /* At child */
289         if (sendfile(out_fd, in_fd, offset, count) < 0)
290                 error("sendfile(): %s (%d)", strerror(errno), errno);
291
292         close(in_fd);
293         close(out_fd);
294
295         exit(errno);
296 }
297
298 static int filesystem_copy(const char *name, const char *destname)
299 {
300         void *in, *out;
301         ssize_t ret;
302         size_t size;
303         struct stat st;
304         int in_fd, out_fd, err;
305
306         in = filesystem_open(name, O_RDONLY, 0, NULL, &size, &err);
307         if (in == NULL) {
308                 error("open(%s): %s (%d)", name, strerror(-err), -err);
309                 return -err;
310         }
311
312         in_fd = GPOINTER_TO_INT(in);
313         ret = fstat(in_fd, &st);
314         if (ret < 0) {
315                 error("stat(%s): %s (%d)", name, strerror(errno), errno);
316                 return -errno;
317         }
318
319         out = filesystem_open(destname, O_WRONLY | O_CREAT | O_TRUNC,
320                                         st.st_mode, NULL, &size, &err);
321         if (out == NULL) {
322                 error("open(%s): %s (%d)", destname, strerror(-err), -err);
323                 filesystem_close(in);
324                 return -errno;
325         }
326
327         out_fd = GPOINTER_TO_INT(out);
328
329         /* Check if sendfile is supported */
330         ret = sendfile(out_fd, in_fd, NULL, 0);
331         if (ret < 0) {
332                 ret = -errno;
333                 error("sendfile: %s (%zd)", strerror(-ret), -ret);
334                 goto done;
335         }
336
337         ret = sendfile_async(out_fd, in_fd, NULL, st.st_size);
338         if (ret < 0)
339                 goto done;
340
341         return 0;
342
343 done:
344         filesystem_close(in);
345         filesystem_close(out);
346
347         return ret;
348 }
349
350 struct capability_object {
351         int pid;
352         int output;
353         int err;
354         gboolean aborted;
355         GString *buffer;
356 };
357
358 static void script_exited(GPid pid, int status, void *data)
359 {
360         struct capability_object *object = data;
361         char buf[128];
362
363         object->pid = -1;
364
365         DBG("pid: %d status: %d", pid, status);
366
367         g_spawn_close_pid(pid);
368
369         /* free the object if aborted */
370         if (object->aborted) {
371                 if (object->buffer != NULL)
372                         g_string_free(object->buffer, TRUE);
373
374                 g_free(object);
375                 return;
376         }
377
378         if (WEXITSTATUS(status) != EXIT_SUCCESS) {
379                 memset(buf, 0, sizeof(buf));
380                 if (read(object->err, buf, sizeof(buf)) > 0)
381                         error("%s", buf);
382                 obex_object_set_io_flags(data, G_IO_ERR, -EPERM);
383         } else
384                 obex_object_set_io_flags(data, G_IO_IN, 0);
385 }
386
387 static int capability_exec(const char **argv, int *output, int *err)
388 {
389         GError *gerr = NULL;
390         int pid;
391         GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH;
392
393         if (!g_spawn_async_with_pipes(NULL, (char **) argv, NULL, flags, NULL,
394                                 NULL, &pid, NULL, output, err, &gerr)) {
395                 error("%s", gerr->message);
396                 g_error_free(gerr);
397                 return -EPERM;
398         }
399
400         DBG("executing %s pid %d", argv[0], pid);
401
402         return pid;
403 }
404
405 static void *capability_open(const char *name, int oflag, mode_t mode,
406                                         void *context, size_t *size, int *err)
407 {
408         struct capability_object *object = NULL;
409         char *buf;
410         const char *argv[2];
411
412         if (oflag != O_RDONLY)
413                 goto fail;
414
415         object = g_new0(struct capability_object, 1);
416         object->pid = -1;
417         object->output = -1;
418         object->err = -1;
419
420         if (name[0] != '!') {
421                 GError *gerr = NULL;
422                 gboolean ret;
423
424                 ret = g_file_get_contents(name, &buf, NULL, &gerr);
425                 if (ret == FALSE) {
426                         error("%s", gerr->message);
427                         g_error_free(gerr);
428                         goto fail;
429                 }
430
431                 object->buffer = g_string_new(buf);
432
433                 if (size)
434                         *size = object->buffer->len;
435
436                 goto done;
437         }
438
439         argv[0] = &name[1];
440         argv[1] = NULL;
441
442         object->pid = capability_exec(argv, &object->output, &object->err);
443         if (object->pid < 0)
444                 goto fail;
445
446         /* Watch cannot be removed while the process is still running */
447         g_child_watch_add(object->pid, script_exited, object);
448
449 done:
450         if (err)
451                 *err = 0;
452
453         return object;
454
455 fail:
456         if (err)
457                 *err = -EPERM;
458
459         g_free(object);
460         return NULL;
461 }
462
463 static GString *append_pcsuite_preamble(GString *object)
464 {
465         return g_string_append(object, FL_TYPE_PCSUITE);
466 }
467
468 static GString *append_folder_preamble(GString *object)
469 {
470         return g_string_append(object, FL_TYPE);
471 }
472
473 static GString *append_listing(GString *object, const char *name,
474                                 gboolean pcsuite, size_t *size, int *err)
475 {
476         struct stat fstat, dstat;
477         struct dirent *ep;
478         DIR *dp;
479         gboolean root;
480         int ret;
481
482         root = g_str_equal(name, obex_option_root_folder());
483
484         dp = opendir(name);
485         if (dp == NULL) {
486                 if (err)
487                         *err = -ENOENT;
488                 goto failed;
489         }
490
491         if (!root)
492                 object = g_string_append(object, FL_PARENT_FOLDER_ELEMENT);
493
494         ret = verify_path(name);
495         if (ret < 0) {
496                 *err = ret;
497                 goto failed;
498         }
499
500         ret = stat(name, &dstat);
501         if (ret < 0) {
502                 if (err)
503                         *err = -errno;
504                 goto failed;
505         }
506
507         while ((ep = readdir(dp))) {
508                 char *filename;
509                 char *fullname;
510                 char *line;
511
512                 if (ep->d_name[0] == '.')
513                         continue;
514
515                 filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL);
516                 if (filename == NULL) {
517                         error("g_filename_to_utf8: invalid filename");
518                         continue;
519                 }
520
521                 fullname = g_build_filename(name, ep->d_name, NULL);
522
523                 ret = stat(fullname, &fstat);
524                 if (ret < 0) {
525                         DBG("stat: %s(%d)", strerror(errno), errno);
526                         g_free(filename);
527                         g_free(fullname);
528                         continue;
529                 }
530
531                 g_free(fullname);
532
533                 line = file_stat_line(filename, &fstat, &dstat, root, FALSE);
534                 if (line == NULL) {
535                         g_free(filename);
536                         continue;
537                 }
538
539                 g_free(filename);
540
541                 object = g_string_append(object, line);
542                 g_free(line);
543         }
544
545         closedir(dp);
546
547         object = g_string_append(object, FL_BODY_END);
548         if (size)
549                 *size = object->len;
550
551         if (err)
552                 *err = 0;
553
554         return object;
555
556 failed:
557         if (dp)
558                 closedir(dp);
559
560         g_string_free(object, TRUE);
561         return NULL;
562 }
563
564 static void *folder_open(const char *name, int oflag, mode_t mode,
565                                         void *context, size_t *size, int *err)
566 {
567         GString *object;
568
569         object = g_string_new(FL_VERSION);
570         object = append_folder_preamble(object);
571         object = g_string_append(object, FL_BODY_BEGIN);
572
573         return append_listing(object, name, FALSE, size, err);
574 }
575
576 static void *pcsuite_open(const char *name, int oflag, mode_t mode,
577                                         void *context, size_t *size, int *err)
578 {
579         GString *object;
580
581         object = g_string_new(FL_VERSION);
582         object = append_pcsuite_preamble(object);
583         object = g_string_append(object, FL_BODY_BEGIN);
584
585         return append_listing(object, name, TRUE, size, err);
586 }
587
588 static int string_free(void *object)
589 {
590         GString *string = object;
591
592         g_string_free(string, TRUE);
593
594         return 0;
595 }
596
597 ssize_t string_read(void *object, void *buf, size_t count)
598 {
599         GString *string = object;
600         ssize_t len;
601
602         if (string->len == 0)
603                 return 0;
604
605         len = MIN(string->len, count);
606         memcpy(buf, string->str, len);
607         g_string_erase(string, 0, len);
608
609         return len;
610 }
611
612 static ssize_t folder_read(void *object, void *buf, size_t count)
613 {
614         return string_read(object, buf, count);
615 }
616
617 static ssize_t capability_read(void *object, void *buf, size_t count)
618 {
619         struct capability_object *obj = object;
620
621         if (obj->buffer)
622                 return string_read(obj->buffer, buf, count);
623
624         if (obj->pid >= 0)
625                 return -EAGAIN;
626
627         return read(obj->output, buf, count);
628 }
629
630 static int capability_close(void *object)
631 {
632         struct capability_object *obj = object;
633         int err = 0;
634
635         if (obj->pid < 0)
636                 goto done;
637
638         DBG("kill: pid %d", obj->pid);
639         err = kill(obj->pid, SIGTERM);
640         if (err < 0) {
641                 err = -errno;
642                 error("kill: %s (%d)", strerror(-err), -err);
643                 goto done;
644         }
645
646         obj->aborted = TRUE;
647         return 0;
648
649 done:
650         if (obj->buffer != NULL)
651                 g_string_free(obj->buffer, TRUE);
652
653         g_free(obj);
654
655         return err;
656 }
657
658 static struct obex_mime_type_driver file = {
659         .open = filesystem_open,
660         .close = filesystem_close,
661         .read = filesystem_read,
662         .write = filesystem_write,
663         .remove = remove,
664         .move = filesystem_rename,
665         .copy = filesystem_copy,
666 };
667
668 static struct obex_mime_type_driver capability = {
669         .target = FTP_TARGET,
670         .target_size = FTP_TARGET_SIZE,
671         .mimetype = "x-obex/capability",
672         .open = capability_open,
673         .close = capability_close,
674         .read = capability_read,
675 };
676
677 static struct obex_mime_type_driver folder = {
678         .target = FTP_TARGET,
679         .target_size = FTP_TARGET_SIZE,
680         .mimetype = "x-obex/folder-listing",
681         .open = folder_open,
682         .close = string_free,
683         .read = folder_read,
684 };
685
686 static struct obex_mime_type_driver pcsuite = {
687         .target = FTP_TARGET,
688         .target_size = FTP_TARGET_SIZE,
689         .who = PCSUITE_WHO,
690         .who_size = PCSUITE_WHO_SIZE,
691         .mimetype = "x-obex/folder-listing",
692         .open = pcsuite_open,
693         .close = string_free,
694         .read = folder_read,
695 };
696
697 static int filesystem_init(void)
698 {
699         int err;
700
701         err = obex_mime_type_driver_register(&folder);
702         if (err < 0)
703                 return err;
704
705         err = obex_mime_type_driver_register(&capability);
706         if (err < 0)
707                 return err;
708
709         err = obex_mime_type_driver_register(&pcsuite);
710         if (err < 0)
711                 return err;
712
713         return obex_mime_type_driver_register(&file);
714 }
715
716 static void filesystem_exit(void)
717 {
718         obex_mime_type_driver_unregister(&folder);
719         obex_mime_type_driver_unregister(&capability);
720         obex_mime_type_driver_unregister(&file);
721 }
722
723 OBEX_PLUGIN_DEFINE(filesystem, filesystem_init, filesystem_exit)