| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2004-2007 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 <fcntl.h> |
| #include <unistd.h> |
| #include <malloc.h> |
| #include <string.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| #include <sys/param.h> |
| |
| #include "textfile.h" |
| |
| 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, const char *key, size_t len, int icase) |
| { |
| char *off = (icase) ? strcasestr(map, key) : strstr(map, key); |
| |
| while (off && ((off > map && *(off - 1) != '\r' && |
| *(off - 1) != '\n') || *(off + len) != ' ')) { |
| if (icase) |
| off = strcasestr(off + len, key); |
| else |
| off = strstr(off + len, key); |
| } |
| |
| return off; |
| } |
| |
| 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 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, len; |
| int fd, 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, 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 = strpbrk(off, "\r\n"); |
| if (!end) { |
| err = EILSEQ; |
| goto unmap; |
| } |
| |
| if (value && (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: |
| 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, key, len, icase); |
| if (!off) { |
| err = EILSEQ; |
| goto unmap; |
| } |
| |
| end = strpbrk(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 (1) { |
| end = strpbrk(off, " "); |
| 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; |
| |
| end = strpbrk(off, "\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; |
| } |