Contiki-NG
storage-cfs.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010, Swedish Institute of Computer Science
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the Institute nor the names of its contributors
14  * may be used to endorse or promote products derived from this software
15  * without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 /**
31  * \file
32  * Contiki File System (CFS) backend for the storage abstraction
33  * used by the database.
34  * \author
35  * Nicolas Tsiftes <nvt@sics.se>
36  */
37 
38 #include <stdio.h>
39 #include <string.h>
40 
41 #include "cfs/cfs.h"
42 #include "cfs/cfs-coffee.h"
43 #include "lib/random.h"
44 
45 #define DEBUG DEBUG_NONE
46 #include "net/ipv6/uip-debug.h"
47 
48 #include "db-options.h"
49 #include "storage.h"
50 
51 struct attribute_record {
52  char name[ATTRIBUTE_NAME_LENGTH];
53  uint8_t domain;
54  uint8_t element_size;
55 };
56 
57 struct index_record {
58  char attribute_name[ATTRIBUTE_NAME_LENGTH];
59  char file_name[DB_MAX_FILENAME_LENGTH];
60  uint8_t type;
61 };
62 
63 #if DB_FEATURE_COFFEE
64 #define DB_COFFEE_CATALOG_SIZE RELATION_NAME_LENGTH + \
65  (DB_MAX_ATTRIBUTES_PER_RELATION * \
66  sizeof(struct attribute_record))
67 #endif
68 
69 #define ROW_XOR 0xf6U
70 
71 static void
72 merge_strings(char *dest, char *prefix, char *suffix)
73 {
74  strcpy(dest, prefix);
75  strcat(dest, suffix);
76 }
77 
78 char *
79 storage_generate_file(char *prefix, unsigned long size)
80 {
81  static char filename[ATTRIBUTE_NAME_LENGTH + sizeof(".ffff")];
82 #if !DB_FEATURE_COFFEE
83  int fd;
84 #endif
85 
86  snprintf(filename, sizeof(filename), "%s.%x", prefix,
87  (unsigned)(random_rand() & 0xffff));
88 
89 #if DB_FEATURE_COFFEE
90  PRINTF("DB: Reserving %lu bytes in %s\n", size, filename);
91  if(cfs_coffee_reserve(filename, size) < 0) {
92  PRINTF("DB: Failed to reserve\n");
93  return NULL;
94  }
95  return filename;
96 #else
97  fd = cfs_open(filename, CFS_WRITE);
98  cfs_close(fd);
99  return fd < 0 ? NULL : filename;
100 #endif /* DB_FEATURE_COFFEE */
101 }
102 
103 db_result_t
104 storage_load(relation_t *rel)
105 {
106  PRINTF("DB: Opening the tuple file %s\n", rel->tuple_filename);
107  rel->tuple_storage = cfs_open(rel->tuple_filename,
109  if(rel->tuple_storage < 0) {
110  PRINTF("DB: Failed to open the tuple file\n");
111  return DB_STORAGE_ERROR;
112  }
113 
114  return DB_OK;
115 }
116 
117 void
118 storage_unload(relation_t *rel)
119 {
120  if(RELATION_HAS_TUPLES(rel)) {
121  PRINTF("DB: Unload tuple file %s\n", rel->tuple_filename);
122 
123  cfs_close(rel->tuple_storage);
124  rel->tuple_storage = -1;
125  }
126 }
127 
128 db_result_t
129 storage_get_relation(relation_t *rel, char *name)
130 {
131  int fd;
132  int r;
133  int i;
134  struct attribute_record record;
135  db_result_t result;
136 
137  fd = cfs_open(name, CFS_READ);
138  if(fd < 0) {
139  return DB_STORAGE_ERROR;
140  }
141 
142  r = cfs_read(fd, rel->name, sizeof(rel->name));
143  if(r != sizeof(rel->name)) {
144  cfs_close(fd);
145  PRINTF("DB: Failed to read name, got %d of %d bytes\n",
146  r, sizeof(rel->name));
147  return DB_STORAGE_ERROR;
148  }
149 
150  r = cfs_read(fd, rel->tuple_filename, sizeof(rel->tuple_filename));
151  if(r != sizeof(rel->name)) {
152  cfs_close(fd);
153  PRINTF("DB: Failed to read tuple filename\n");
154  return DB_STORAGE_ERROR;
155  }
156 
157  rel->tuple_filename[sizeof(rel->tuple_filename) - 1] ^= ROW_XOR;
158 
159  /* Read attribute records. */
160  result = DB_OK;
161  for(i = 0;; i++) {
162  r = cfs_read(fd, &record, sizeof(record));
163  if(r == 0) {
164  break;
165  }
166  if(r != sizeof(record)) {
167  PRINTF("DB: Failed to read attribute record %d (r = %d)\n", i, r);
168  result = DB_STORAGE_ERROR;
169  break;
170  }
171 
172  if(relation_attribute_add(rel, DB_MEMORY, record.name,
173  record.domain, record.element_size) == NULL) {
174  PRINTF("DB: Failed to add the attribute %s\n", record.name);
175  result = DB_STORAGE_ERROR;
176  break;
177  }
178  }
179 
180  PRINTF("DB: Read %d attributes\n", i);
181 
182  cfs_close(fd);
183  return result;
184 }
185 
186 db_result_t
187 storage_put_relation(relation_t *rel)
188 {
189  int fd;
190  int r;
191  char *str;
192  unsigned char *last_byte;
193 
194  PRINTF("DB: put_relation(%s)\n", rel->name);
195 
196  cfs_remove(rel->name);
197 
198 #if DB_FEATURE_COFFEE
199  cfs_coffee_reserve(rel->name, DB_COFFEE_CATALOG_SIZE);
200 #endif
201 
202  fd = cfs_open(rel->name, CFS_WRITE | CFS_READ);
203  if(fd < 0) {
204  return DB_STORAGE_ERROR;
205  }
206 
207  r = cfs_write(fd, rel->name, sizeof(rel->name));
208  if(r != sizeof(rel->name)) {
209  cfs_close(fd);
210  cfs_remove(rel->name);
211  return DB_STORAGE_ERROR;
212  }
213 
214  if(rel->tuple_filename[0] == '\0') {
215  str = storage_generate_file("tuple", DB_COFFEE_RESERVE_SIZE);
216  if(str == NULL) {
217  cfs_close(fd);
218  cfs_remove(rel->name);
219  return DB_STORAGE_ERROR;
220  }
221 
222  strncpy(rel->tuple_filename, str, sizeof(rel->tuple_filename) - 1);
223  rel->tuple_filename[sizeof(rel->tuple_filename) - 1] = '\0';
224  }
225 
226  /*
227  * Encode the last byte to ensure that the filename is not
228  * null-terminated. This will make the Coffee FS determine
229  * the correct length when re-opening the file.
230  */
231  last_byte = (unsigned char *)&rel->tuple_filename[sizeof(rel->tuple_filename) - 1];
232  *last_byte ^= ROW_XOR;
233 
234  r = cfs_write(fd, rel->tuple_filename, sizeof(rel->tuple_filename));
235 
236  *last_byte ^= ROW_XOR;
237 
238  if(r != sizeof(rel->tuple_filename)) {
239  cfs_close(fd);
240  cfs_remove(rel->tuple_filename);
241  return DB_STORAGE_ERROR;
242  }
243 
244  PRINTF("DB: Saved relation %s\n", rel->name);
245 
246  cfs_close(fd);
247  return DB_OK;
248 }
249 
250 db_result_t
251 storage_put_attribute(relation_t *rel, attribute_t *attr)
252 {
253  int fd;
254  struct attribute_record record;
255  int r;
256 
257  PRINTF("DB: put_attribute(%s, %s)\n", rel->name, attr->name);
258 
259  fd = cfs_open(rel->name, CFS_WRITE | CFS_APPEND);
260  if(fd < 0) {
261  return DB_STORAGE_ERROR;
262  }
263 
264  memset(&record.name, 0, sizeof(record.name));
265  memcpy(record.name, attr->name, sizeof(record.name));
266  record.domain = attr->domain;
267  record.element_size = attr->element_size;
268  r = cfs_write(fd, &record, sizeof(record));
269  if(r != sizeof(record)) {
270  cfs_close(fd);
271  cfs_remove(rel->name);
272  return DB_STORAGE_ERROR;
273  }
274 
275  cfs_close(fd);
276  return DB_OK;
277 }
278 
279 db_result_t
280 storage_drop_relation(relation_t *rel, int remove_tuples)
281 {
282  if(remove_tuples && RELATION_HAS_TUPLES(rel)) {
283  cfs_remove(rel->tuple_filename);
284  }
285  return cfs_remove(rel->name) < 0 ? DB_STORAGE_ERROR : DB_OK;
286 }
287 
288 #if DB_FEATURE_REMOVE
289 db_result_t
290 storage_rename_relation(char *old_name, char *new_name)
291 {
292  db_result_t result;
293  int old_fd;
294  int new_fd;
295  int r;
296  char buf[64];
297 
298  result = DB_STORAGE_ERROR;
299  old_fd = new_fd = -1;
300 
301  old_fd = cfs_open(old_name, CFS_READ);
302  new_fd = cfs_open(new_name, CFS_WRITE);
303  if(old_fd < 0 || new_fd < 0) {
304  goto error;
305  }
306 
307  for(;;) {
308  r = cfs_read(old_fd, buf, sizeof(buf));
309  if(r < 0) {
310  goto error;
311  } else if(r == 0) {
312  break;
313  }
314  if(cfs_write(new_fd, buf, r) != r) {
315  goto error;
316  }
317  };
318 
319  cfs_remove(old_name);
320  result = DB_OK;
321 
322 error:
323  cfs_close(old_fd);
324  cfs_close(new_fd);
325 
326  if(result != DB_OK) {
327  cfs_remove(new_name);
328  }
329  return result;
330 }
331 #endif /* DB_FEATURE_REMOVE */
332 
333 db_result_t
334 storage_get_index(index_t *index, relation_t *rel, attribute_t *attr)
335 {
336  char filename[INDEX_NAME_LENGTH];
337  int fd;
338  int r;
339  struct index_record record;
340  db_result_t result;
341 
342  merge_strings(filename, rel->name, INDEX_NAME_SUFFIX);
343 
344  fd = cfs_open(filename, CFS_READ);
345  if(fd < 0) {
346  return DB_STORAGE_ERROR;
347  }
348 
349  for(result = DB_STORAGE_ERROR;;) {
350  r = cfs_read(fd, &record, sizeof(record));
351  if(r < sizeof(record)) {
352  break;
353  }
354  if(strcmp(attr->name, record.attribute_name) == 0) {
355  PRINTF("DB: Found the index record for %s.%s: type %d, filename %s\n",
356  rel->name, attr->name, record.type, record.file_name);
357  index->type = record.type;
358  memcpy(index->descriptor_file, record.file_name,
359  sizeof(index->descriptor_file));
360  result = DB_OK;
361  }
362  }
363 
364  cfs_close(fd);
365 
366  return result;
367 }
368 
369 db_result_t
370 storage_put_index(index_t *index)
371 {
372  char filename[INDEX_NAME_LENGTH];
373  int fd;
374  int r;
375  struct index_record record;
376  db_result_t result;
377 
378  merge_strings(filename, index->rel->name, INDEX_NAME_SUFFIX);
379 
380  fd = cfs_open(filename, CFS_WRITE | CFS_APPEND);
381  if(fd < 0) {
382  return DB_STORAGE_ERROR;
383  }
384 
385  strcpy(record.attribute_name, index->attr->name);
386  memcpy(record.file_name, index->descriptor_file, sizeof(record.file_name));
387  record.type = index->type;
388 
389  result = DB_OK;
390  r = cfs_write(fd, &record, sizeof(record));
391  if(r < sizeof(record)) {
392  result = DB_STORAGE_ERROR;
393  } else {
394  PRINTF("DB: Wrote an index record for %s.%s, type %d\n",
395  index->rel->name, index->attr->name, record.type);
396  }
397 
398  cfs_close(fd);
399 
400  return result;
401 }
402 
403 db_result_t
404 storage_get_row(relation_t *rel, tuple_id_t *tuple_id, storage_row_t row)
405 {
406  int r;
407  tuple_id_t nrows;
408 
409  if(DB_ERROR(storage_get_row_amount(rel, &nrows))) {
410  return DB_STORAGE_ERROR;
411  }
412 
413  if(*tuple_id >= nrows) {
414  return DB_FINISHED;
415  }
416 
417  if(cfs_seek(rel->tuple_storage, *tuple_id * rel->row_length, CFS_SEEK_SET) ==
418  (cfs_offset_t)-1) {
419  return DB_STORAGE_ERROR;
420  }
421 
422  r = cfs_read(rel->tuple_storage, row, rel->row_length);
423  if(r < 0) {
424  PRINTF("DB: Reading failed on fd %d\n", rel->tuple_storage);
425  return DB_STORAGE_ERROR;
426  } else if(r == 0) {
427  return DB_FINISHED;
428  } else if(r < rel->row_length) {
429  PRINTF("DB: Incomplete record: %d < %d\n", r, rel->row_length);
430  return DB_STORAGE_ERROR;
431  }
432 
433  row[rel->row_length - 1] ^= ROW_XOR;
434 
435  PRINTF("DB: Read %d bytes from relation %s\n", rel->row_length, rel->name);
436 
437  return DB_OK;
438 }
439 
440 db_result_t
441 storage_put_row(relation_t *rel, storage_row_t row)
442 {
443  cfs_offset_t end;
444  unsigned remaining;
445  int r;
446  unsigned char *last_byte;
447 #if DB_FEATURE_INTEGRITY
448  int missing_bytes;
449  char buf[rel->row_length];
450 #endif
451 
452  end = cfs_seek(rel->tuple_storage, 0, CFS_SEEK_END);
453  if(end == (cfs_offset_t)-1) {
454  return DB_STORAGE_ERROR;
455  }
456 
457 #if DB_FEATURE_INTEGRITY
458  missing_bytes = end % rel->row_length;
459  if(missing_bytes > 0) {
460  memset(buf, 0xff, sizeof(buf));
461  r = cfs_write(rel->tuple_storage, buf, sizeof(buf));
462  if(r != missing_bytes) {
463  return DB_STORAGE_ERROR;
464  }
465  }
466 #endif
467 
468  /* Ensure that last written byte is separated from 0, to make file
469  lengths correct in Coffee. */
470  last_byte = row + rel->row_length - 1;
471  *last_byte ^= ROW_XOR;
472 
473  remaining = rel->row_length;
474  do {
475  r = cfs_write(rel->tuple_storage, row, remaining);
476  if(r < 0) {
477  PRINTF("DB: Failed to store %u bytes\n", remaining);
478  *last_byte ^= ROW_XOR;
479  return DB_STORAGE_ERROR;
480  }
481  row += r;
482  remaining -= r;
483  } while(remaining > 0);
484 
485  PRINTF("DB: Stored a of %d bytes\n", rel->row_length);
486 
487  *last_byte ^= ROW_XOR;
488 
489  return DB_OK;
490 }
491 
492 db_result_t
493 storage_get_row_amount(relation_t *rel, tuple_id_t *amount)
494 {
495  cfs_offset_t offset;
496 
497  if(rel->row_length == 0) {
498  *amount = 0;
499  } else {
500  offset = cfs_seek(rel->tuple_storage, 0, CFS_SEEK_END);
501  if(offset == (cfs_offset_t)-1) {
502  return DB_STORAGE_ERROR;
503  }
504 
505  *amount = (tuple_id_t)(offset / rel->row_length);
506  }
507 
508  return DB_OK;
509 }
510 
511 db_storage_id_t
512 storage_open(const char *filename)
513 {
514  int fd;
515 
516  fd = cfs_open(filename, CFS_WRITE | CFS_READ);
517 #if DB_FEATURE_COFFEE
518  if(fd >= 0) {
520  }
521 #endif
522  return fd;
523 }
524 
525 void
526 storage_close(db_storage_id_t fd)
527 {
528  cfs_close(fd);
529 }
530 
531 db_result_t
532 storage_read(db_storage_id_t fd,
533  void *buffer, unsigned long offset, unsigned length)
534 {
535  char *ptr;
536  int r;
537 
538  /* Extend the file if necessary, so that previously unwritten bytes
539  will be read in as zeroes. */
540  if(cfs_seek(fd, offset + length, CFS_SEEK_SET) == (cfs_offset_t)-1) {
541  return DB_STORAGE_ERROR;
542  }
543 
544  if(cfs_seek(fd, offset, CFS_SEEK_SET) == (cfs_offset_t)-1) {
545  return DB_STORAGE_ERROR;
546  }
547 
548  ptr = buffer;
549  while(length > 0) {
550  r = cfs_read(fd, ptr, length);
551  if(r <= 0) {
552  return DB_STORAGE_ERROR;
553  }
554  ptr += r;
555  length -= r;
556  }
557 
558  return DB_OK;
559 }
560 
561 db_result_t
562 storage_write(db_storage_id_t fd,
563  void *buffer, unsigned long offset, unsigned length)
564 {
565  char *ptr;
566  int r;
567 
568  if(cfs_seek(fd, offset, CFS_SEEK_SET) == (cfs_offset_t)-1) {
569  return DB_STORAGE_ERROR;
570  }
571 
572  ptr = buffer;
573  while(length > 0) {
574  r = cfs_write(fd, ptr, length);
575  if(r <= 0) {
576  return DB_STORAGE_ERROR;
577  }
578  ptr += r;
579  length -= r;
580  }
581 
582  return DB_OK;
583 }
Header for the Coffee file system.
int cfs_open(const char *name, int flags)
Open a file.
Definition: cfs-sdcard.c:41
A set of debugging macros for the IP stack
int cfs_coffee_set_io_semantics(int fd, unsigned flags)
Set the I/O semantics for accessing a file.
Definition: cfs-coffee.c:1313
int cfs_remove(const char *name)
Remove a file.
Definition: cfs-cooja.c:150
cfs_offset_t cfs_seek(int fd, cfs_offset_t offset, int whence)
Seek to a specified position in an open file.
Definition: cfs-sdcard.c:94
#define CFS_APPEND
Specify that cfs_open() should append written data to the file rather than overwriting it...
Definition: cfs.h:120
int cfs_write(int fd, const void *buf, unsigned int len)
Write data to an open file.
Definition: cfs-sdcard.c:86
The storage interface used by the database.
CFS header file.
Database configuration options.
#define CFS_COFFEE_IO_FLASH_AWARE
Instruct Coffee that the access pattern to this file is adapted to flash I/O semantics by design...
Definition: cfs-coffee.h:53
void cfs_close(int fd)
Close an open file.
Definition: cfs-sdcard.c:69
#define CFS_SEEK_END
Specify that cfs_seek() should compute the offset from the end of the file.
Definition: cfs.h:147
int cfs_read(int fd, void *buf, unsigned int len)
Read data from an open file.
Definition: cfs-sdcard.c:78
#define CFS_READ
Specify that cfs_open() should open a file for reading.
Definition: cfs.h:92
int cfs_coffee_reserve(const char *name, cfs_offset_t size)
Reserve space for a file.
Definition: cfs-coffee.c:1277
#define CFS_SEEK_SET
Specify that cfs_seek() should compute the offset from the beginning of the file. ...
Definition: cfs.h:129
#define CFS_WRITE
Specify that cfs_open() should open a file for writing.
Definition: cfs.h:106
unsigned short random_rand(void)
Generates a new random number using the cc2538 RNG.
Definition: random.c:58