/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #define _GNU_SOURCE #include <stdio.h> #include <errno.h> #include <ctype.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/file.h> #include <sys/stat.h> #include <sys/mman.h> #include <sys/param.h> #include "textfile.h" #ifndef HAVE_FDATASYNC #define fdatasync fsync #endif int create_dirs(const char *filename, const mode_t mode) { struct stat st; char dir[PATH_MAX + 1], *prev, *next; int err; err = stat(filename, &st); if (!err && S_ISREG(st.st_mode)) return 0; memset(dir, 0, PATH_MAX + 1); strcat(dir, "/"); prev = strchr(filename, '/'); while (prev) { next = strchr(prev + 1, '/'); if (!next) break; if (next - prev == 1) { prev = next; continue; } strncat(dir, prev + 1, next - prev); mkdir(dir, mode); prev = next; } return 0; } int create_file(const char *filename, const mode_t mode) { int fd; umask(S_IWGRP | S_IWOTH); create_dirs(filename, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); fd = open(filename, O_RDWR | O_CREAT, mode); if (fd < 0) return fd; close(fd); return 0; } int create_name(char *buf, size_t size, const char *path, const char *address, const char *name) { return snprintf(buf, size, "%s/%s/%s", path, address, name); } static inline char *find_key(char *map, size_t size, const char *key, size_t len, int icase) { char *ptr = map; size_t ptrlen = size; while (ptrlen > len + 1) { int cmp = (icase) ? strncasecmp(ptr, key, len) : strncmp(ptr, key, len); if (cmp == 0) { if (ptr == map) return ptr; if ((*(ptr - 1) == '\r' || *(ptr - 1) == '\n') && *(ptr + len) == ' ') return ptr; } if (icase) { char *p1 = memchr(ptr + 1, tolower(*key), ptrlen - 1); char *p2 = memchr(ptr + 1, toupper(*key), ptrlen - 1); if (!p1) ptr = p2; else if (!p2) ptr = p1; else ptr = (p1 < p2) ? p1 : p2; } else ptr = memchr(ptr + 1, *key, ptrlen - 1); if (!ptr) return NULL; ptrlen = size - (ptr - map); } return NULL; } static inline int write_key_value(int fd, const char *key, const char *value) { char *str; size_t size; int err = 0; size = strlen(key) + strlen(value) + 2; str = malloc(size + 1); if (!str) return ENOMEM; sprintf(str, "%s %s\n", key, value); if (write(fd, str, size) < 0) err = errno; free(str); return err; } static char *strnpbrk(const char *s, ssize_t len, const char *accept) { const char *p = s; const char *end; end = s + len - 1; while (p <= end && *p) { const char *a = accept; while (*a) { if (*p == *a) return (char *) p; a++; } p++; } return NULL; } static int write_key(const char *pathname, const char *key, const char *value, int icase) { struct stat st; char *map, *off, *end, *str; off_t size, pos; size_t base; int fd, len, err = 0; fd = open(pathname, O_RDWR); if (fd < 0) return -errno; if (flock(fd, LOCK_EX) < 0) { err = errno; goto close; } if (fstat(fd, &st) < 0) { err = errno; goto unlock; } size = st.st_size; if (!size) { if (value) { pos = lseek(fd, size, SEEK_SET); err = write_key_value(fd, key, value); } goto unlock; } map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED, fd, 0); if (!map || map == MAP_FAILED) { err = errno; goto unlock; } len = strlen(key); off = find_key(map, size, key, len, icase); if (!off) { if (value) { munmap(map, size); pos = lseek(fd, size, SEEK_SET); err = write_key_value(fd, key, value); } goto unlock; } base = off - map; end = strnpbrk(off, size, "\r\n"); if (!end) { err = EILSEQ; goto unmap; } if (value && ((ssize_t) strlen(value) == end - off - len - 1) && !strncmp(off + len + 1, value, end - off - len - 1)) goto unmap; len = strspn(end, "\r\n"); end += len; len = size - (end - map); if (!len) { munmap(map, size); if (ftruncate(fd, base) < 0) { err = errno; goto unlock; } pos = lseek(fd, base, SEEK_SET); if (value) err = write_key_value(fd, key, value); goto unlock; } if (len < 0 || len > size) { err = EILSEQ; goto unmap; } str = malloc(len); if (!str) { err = errno; goto unmap; } memcpy(str, end, len); munmap(map, size); if (ftruncate(fd, base) < 0) { err = errno; free(str); goto unlock; } pos = lseek(fd, base, SEEK_SET); if (value) err = write_key_value(fd, key, value); if (write(fd, str, len) < 0) err = errno; free(str); goto unlock; unmap: munmap(map, size); unlock: flock(fd, LOCK_UN); close: fdatasync(fd); close(fd); errno = err; return -err; } static char *read_key(const char *pathname, const char *key, int icase) { struct stat st; char *map, *off, *end, *str = NULL; off_t size; size_t len; int fd, err = 0; fd = open(pathname, O_RDONLY); if (fd < 0) return NULL; if (flock(fd, LOCK_SH) < 0) { err = errno; goto close; } if (fstat(fd, &st) < 0) { err = errno; goto unlock; } size = st.st_size; map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (!map || map == MAP_FAILED) { err = errno; goto unlock; } len = strlen(key); off = find_key(map, size, key, len, icase); if (!off) { err = EILSEQ; goto unmap; } end = strnpbrk(off, size - (map - off), "\r\n"); if (!end) { err = EILSEQ; goto unmap; } str = malloc(end - off - len); if (!str) { err = EILSEQ; goto unmap; } memset(str, 0, end - off - len); strncpy(str, off + len + 1, end - off - len - 1); unmap: munmap(map, size); unlock: flock(fd, LOCK_UN); close: close(fd); errno = err; return str; } int textfile_put(const char *pathname, const char *key, const char *value) { return write_key(pathname, key, value, 0); } int textfile_caseput(const char *pathname, const char *key, const char *value) { return write_key(pathname, key, value, 1); } int textfile_del(const char *pathname, const char *key) { return write_key(pathname, key, NULL, 0); } int textfile_casedel(const char *pathname, const char *key) { return write_key(pathname, key, NULL, 1); } char *textfile_get(const char *pathname, const char *key) { return read_key(pathname, key, 0); } char *textfile_caseget(const char *pathname, const char *key) { return read_key(pathname, key, 1); } int textfile_foreach(const char *pathname, void (*func)(char *key, char *value, void *data), void *data) { struct stat st; char *map, *off, *end, *key, *value; off_t size; size_t len; int fd, err = 0; fd = open(pathname, O_RDONLY); if (fd < 0) return -errno; if (flock(fd, LOCK_SH) < 0) { err = errno; goto close; } if (fstat(fd, &st) < 0) { err = errno; goto unlock; } size = st.st_size; map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (!map || map == MAP_FAILED) { err = errno; goto unlock; } off = map; while (size - (off - map) > 0) { end = strnpbrk(off, size - (off - map), " "); if (!end) { err = EILSEQ; break; } len = end - off; key = malloc(len + 1); if (!key) { err = errno; break; } memset(key, 0, len + 1); memcpy(key, off, len); off = end + 1; if (size - (off - map) < 0) { err = EILSEQ; free(key); break; } end = strnpbrk(off, size - (off - map), "\r\n"); if (!end) { err = EILSEQ; free(key); break; } len = end - off; value = malloc(len + 1); if (!value) { err = errno; free(key); break; } memset(value, 0, len + 1); memcpy(value, off, len); func(key, value, data); free(key); free(value); off = end + 1; } munmap(map, size); unlock: flock(fd, LOCK_UN); close: close(fd); errno = err; return 0; }