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