1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "config.h"
3 #include <fcntl.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <snappy.h>
8 
9 #include "internal.h"
10 #include "iobuffer.h"
11 #include "bitfield.h"
12 #include "crc32.h"
13 #include "util.h"
14 
15 
tree_file_open(tree_file* file, const char *filename, int openflags, const couch_file_ops *ops)16 couchstore_error_t tree_file_open(tree_file* file,
17                                   const char *filename,
18                                   int openflags,
19                                   const couch_file_ops *ops)
20 {
21     couchstore_error_t errcode = COUCHSTORE_SUCCESS;
22 
23     /* Sanity check input parameters */
24     if (filename == NULL || file == NULL || ops == NULL ||
25             ops->version != 5 ||
26             ops->constructor == NULL || ops->open == NULL ||
27             ops->close == NULL || ops->pread == NULL ||
28             ops->pwrite == NULL || ops->goto_eof == NULL ||
29             ops->sync == NULL || ops->destructor == NULL) {
30         return COUCHSTORE_ERROR_INVALID_ARGUMENTS;
31     }
32 
33     memset(file, 0, sizeof(*file));
34 
35     file->path = (const char *) strdup(filename);
36     error_unless(file->path, COUCHSTORE_ERROR_ALLOC_FAIL);
37 
38     file->ops = couch_get_buffered_file_ops(&file->lastError, ops, &file->handle);
39     error_unless(file->ops, COUCHSTORE_ERROR_ALLOC_FAIL);
40 
41     error_pass(file->ops->open(&file->lastError, &file->handle,
42                                filename, openflags));
43 
44 cleanup:
45     if (errcode != COUCHSTORE_SUCCESS) {
46         free((char *) file->path);
47         file->path = NULL;
48         if (file->ops) {
49             file->ops->destructor(&file->lastError, file->handle);
50             file->ops = NULL;
51             file->handle = NULL;
52         }
53     }
54     return errcode;
55 }
56 
tree_file_close(tree_file* file)57 void tree_file_close(tree_file* file)
58 {
59     if (file->ops) {
60         file->ops->close(&file->lastError, file->handle);
61         file->ops->destructor(&file->lastError, file->handle);
62     }
63     free((char*)file->path);
64 }
65 
66 /** Read bytes from the database file, skipping over the header-detection bytes at every block
67     boundary. */
read_skipping_prefixes(tree_file *file, cs_off_t *pos, ssize_t len, void *dst)68 static couchstore_error_t read_skipping_prefixes(tree_file *file,
69                                                  cs_off_t *pos,
70                                                  ssize_t len,
71                                                  void *dst) {
72     if (*pos % COUCH_BLOCK_SIZE == 0) {
73         ++*pos;
74     }
75     while (len > 0) {
76         ssize_t read_size = COUCH_BLOCK_SIZE - (*pos % COUCH_BLOCK_SIZE);
77         if (read_size > len) {
78             read_size = len;
79         }
80         ssize_t got_bytes = file->ops->pread(&file->lastError, file->handle,
81                                              dst, read_size, *pos);
82         if (got_bytes < 0) {
83             return (couchstore_error_t) got_bytes;
84         } else if (got_bytes == 0) {
85             return COUCHSTORE_ERROR_READ;
86         }
87         *pos += got_bytes;
88         len -= got_bytes;
89         dst = (char*)dst + got_bytes;
90         if (*pos % COUCH_BLOCK_SIZE == 0) {
91             ++*pos;
92         }
93     }
94     return COUCHSTORE_SUCCESS;
95 }
96 
97 /*
98  * Common subroutine of pread_bin, pread_compressed and pread_header.
99  * Parameters and return value are the same as for pread_bin,
100  * except the 'max_header_size' parameter which is greater than 0 if
101  * reading a header, 0 otherwise.
102  */
pread_bin_internal(tree_file *file, cs_off_t pos, char **ret_ptr, uint32_t max_header_size)103 static int pread_bin_internal(tree_file *file,
104                               cs_off_t pos,
105                               char **ret_ptr,
106                               uint32_t max_header_size)
107 {
108     struct {
109         uint32_t chunk_len;
110         uint32_t crc32;
111     } info;
112 
113     couchstore_error_t err = read_skipping_prefixes(file, &pos, sizeof(info), &info);
114     if (err < 0) {
115         return err;
116     }
117 
118     info.chunk_len = ntohl(info.chunk_len) & ~0x80000000;
119     if (max_header_size) {
120         if (info.chunk_len < 4 || info.chunk_len > max_header_size)
121             return COUCHSTORE_ERROR_CORRUPT;
122         info.chunk_len -= 4;    //Header len includes CRC len.
123     }
124     info.crc32 = ntohl(info.crc32);
125 
126     char* buf = static_cast<char*>(malloc(info.chunk_len));
127     if (!buf) {
128         return COUCHSTORE_ERROR_ALLOC_FAIL;
129     }
130     err = read_skipping_prefixes(file, &pos, info.chunk_len, buf);
131     if (!err && info.crc32 && info.crc32 != hash_crc32(buf, info.chunk_len)) {
132         err = COUCHSTORE_ERROR_CHECKSUM_FAIL;
133     }
134     if (err < 0) {
135         free(buf);
136         return err;
137     }
138 
139     *ret_ptr = buf;
140     return info.chunk_len;
141 }
142 
pread_header(tree_file *file, cs_off_t pos, char **ret_ptr, uint32_t max_header_size)143 int pread_header(tree_file *file,
144                  cs_off_t pos,
145                  char **ret_ptr,
146                  uint32_t max_header_size)
147 {
148     if (max_header_size == 0) {
149         return COUCHSTORE_ERROR_INVALID_ARGUMENTS;
150     }
151 
152     return pread_bin_internal(file, pos + 1, ret_ptr, max_header_size);
153 }
154 
pread_compressed(tree_file *file, cs_off_t pos, char **ret_ptr)155 int pread_compressed(tree_file *file, cs_off_t pos, char **ret_ptr)
156 {
157     char *compressed_buf;
158     char *new_buf;
159     int len = pread_bin_internal(file, pos, &compressed_buf, 0);
160     if (len < 0) {
161         return len;
162     }
163     size_t uncompressed_len;
164 
165     if (!snappy::GetUncompressedLength(compressed_buf, len, &uncompressed_len)) {
166         //should be compressed but snappy doesn't see it as valid.
167         free(compressed_buf);
168         return COUCHSTORE_ERROR_CORRUPT;
169     }
170 
171     new_buf = static_cast<char *>(malloc(uncompressed_len));
172     if (!new_buf) {
173         free(compressed_buf);
174         return COUCHSTORE_ERROR_ALLOC_FAIL;
175     }
176 
177     if (!snappy::RawUncompress(compressed_buf, len, new_buf)) {
178         free(compressed_buf);
179         free(new_buf);
180         return COUCHSTORE_ERROR_CORRUPT;
181     }
182 
183     free(compressed_buf);
184     *ret_ptr = new_buf;
185     return static_cast<int>(uncompressed_len);
186 }
187 
pread_bin(tree_file *file, cs_off_t pos, char **ret_ptr)188 int pread_bin(tree_file *file, cs_off_t pos, char **ret_ptr)
189 {
190     return pread_bin_internal(file, pos, ret_ptr, 0);
191 }
192