You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

832 lines
21 KiB

  1. #include <Python.h>
  2. #include <structmember.h>
  3. #include <endian.h>
  4. #include <ctype.h>
  5. #include <stdint.h>
  6. /* Values missing from stdint.h */
  7. #define UINT8_MIN 0
  8. #define UINT16_MIN 0
  9. #define UINT32_MIN 0
  10. #define UINT64_MIN 0
  11. /* Marker values (if min == max, skip range check) */
  12. #define FLOAT32_MIN 0
  13. #define FLOAT32_MAX 0
  14. #define FLOAT64_MIN 0
  15. #define FLOAT64_MAX 0
  16. typedef int64_t timestamp_t;
  17. /* This code probably needs to be double-checked for the case
  18. where sizeof(long) != 8, so enforce that here with something
  19. that will fail at build time. */
  20. const static char __long_ok[1 - 2*!(sizeof(int64_t) == sizeof(long))] = { 0 };
  21. /* Somewhat arbitrary, just so we can use fixed sizes for strings
  22. etc. */
  23. static const int MAX_LAYOUT_COUNT = 128;
  24. /* Error object and constants */
  25. static PyObject *ParseError;
  26. typedef enum {
  27. ERR_OTHER,
  28. ERR_NON_MONOTONIC,
  29. ERR_OUT_OF_INTERVAL,
  30. } parseerror_code_t;
  31. static void add_parseerror_codes(PyObject *module)
  32. {
  33. PyModule_AddIntMacro(module, ERR_OTHER);
  34. PyModule_AddIntMacro(module, ERR_NON_MONOTONIC);
  35. PyModule_AddIntMacro(module, ERR_OUT_OF_INTERVAL);
  36. }
  37. /* Helpers to raise ParseErrors. Use "return raise_str(...)" etc. */
  38. static PyObject *raise_str(int line, int col, int code, const char *string)
  39. {
  40. PyObject *o;
  41. o = Py_BuildValue("(iiis)", line, col, code, string);
  42. if (o != NULL) {
  43. PyErr_SetObject(ParseError, o);
  44. Py_DECREF(o);
  45. }
  46. return NULL;
  47. }
  48. static PyObject *raise_int(int line, int col, int code, int64_t num)
  49. {
  50. PyObject *o;
  51. o = Py_BuildValue("(iiiL)", line, col, code, num);
  52. if (o != NULL) {
  53. PyErr_SetObject(ParseError, o);
  54. Py_DECREF(o);
  55. }
  56. return NULL;
  57. }
  58. /****
  59. * Layout and type helpers
  60. */
  61. typedef union {
  62. int8_t i;
  63. uint8_t u;
  64. } union8_t;
  65. typedef union {
  66. int16_t i;
  67. uint16_t u;
  68. } union16_t;
  69. typedef union {
  70. int32_t i;
  71. uint32_t u;
  72. float f;
  73. } union32_t;
  74. typedef union {
  75. int64_t i;
  76. uint64_t u;
  77. double d;
  78. } union64_t;
  79. typedef enum {
  80. LAYOUT_TYPE_NONE,
  81. LAYOUT_TYPE_INT8,
  82. LAYOUT_TYPE_UINT8,
  83. LAYOUT_TYPE_INT16,
  84. LAYOUT_TYPE_UINT16,
  85. LAYOUT_TYPE_INT32,
  86. LAYOUT_TYPE_UINT32,
  87. LAYOUT_TYPE_INT64,
  88. LAYOUT_TYPE_UINT64,
  89. LAYOUT_TYPE_FLOAT32,
  90. LAYOUT_TYPE_FLOAT64,
  91. } layout_type_t;
  92. struct {
  93. char *string;
  94. layout_type_t layout;
  95. int size;
  96. } type_lookup[] = {
  97. { "int8", LAYOUT_TYPE_INT8, 1 },
  98. { "uint8", LAYOUT_TYPE_UINT8, 1 },
  99. { "int16", LAYOUT_TYPE_INT16, 2 },
  100. { "uint16", LAYOUT_TYPE_UINT16, 2 },
  101. { "int32", LAYOUT_TYPE_INT32, 4 },
  102. { "uint32", LAYOUT_TYPE_UINT32, 4 },
  103. { "int64", LAYOUT_TYPE_INT64, 8 },
  104. { "uint64", LAYOUT_TYPE_UINT64, 8 },
  105. { "float32", LAYOUT_TYPE_FLOAT32, 4 },
  106. { "float64", LAYOUT_TYPE_FLOAT64, 8 },
  107. { NULL }
  108. };
  109. /****
  110. * Object definition, init, etc
  111. */
  112. /* Rocket object */
  113. typedef struct {
  114. PyObject_HEAD
  115. layout_type_t layout_type;
  116. int layout_count;
  117. int binary_size;
  118. FILE *file;
  119. int file_size;
  120. } Rocket;
  121. /* Dealloc / new */
  122. static void Rocket_dealloc(Rocket *self)
  123. {
  124. if (self->file) {
  125. fprintf(stderr, "rocket: file wasn't closed\n");
  126. fclose(self->file);
  127. self->file = NULL;
  128. }
  129. self->ob_type->tp_free((PyObject *)self);
  130. }
  131. static PyObject *Rocket_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
  132. {
  133. Rocket *self;
  134. self = (Rocket *)type->tp_alloc(type, 0);
  135. if (!self)
  136. return NULL;
  137. self->layout_type = LAYOUT_TYPE_NONE;
  138. self->layout_count = 0;
  139. self->binary_size = 0;
  140. self->file = NULL;
  141. self->file_size = -1;
  142. return (PyObject *)self;
  143. }
  144. /* .__init__(layout, file) */
  145. static int Rocket_init(Rocket *self, PyObject *args, PyObject *kwds)
  146. {
  147. const char *layout, *path;
  148. static char *kwlist[] = { "layout", "file", NULL };
  149. if (!PyArg_ParseTupleAndKeywords(args, kwds, "sz", kwlist,
  150. &layout, &path))
  151. return -1;
  152. if (!layout)
  153. return -1;
  154. if (path) {
  155. if ((self->file = fopen(path, "a+b")) == NULL) {
  156. PyErr_SetFromErrno(PyExc_OSError);
  157. return -1;
  158. }
  159. self->file_size = -1;
  160. } else {
  161. self->file = NULL;
  162. }
  163. const char *under;
  164. char *tmp;
  165. under = strchr(layout, '_');
  166. if (!under) {
  167. PyErr_SetString(PyExc_ValueError, "no such layout: "
  168. "badly formatted string");
  169. return -1;
  170. }
  171. self->layout_count = strtoul(under+1, &tmp, 10);
  172. if (self->layout_count < 1 || *tmp != '\0') {
  173. PyErr_SetString(PyExc_ValueError, "no such layout: "
  174. "bad count");
  175. return -1;
  176. }
  177. if (self->layout_count >= MAX_LAYOUT_COUNT) {
  178. PyErr_SetString(PyExc_ValueError, "no such layout: "
  179. "count too high");
  180. return -1;
  181. }
  182. int i;
  183. for (i = 0; type_lookup[i].string; i++)
  184. if (strncmp(layout, type_lookup[i].string, under-layout) == 0)
  185. break;
  186. if (!type_lookup[i].string) {
  187. PyErr_SetString(PyExc_ValueError, "no such layout: "
  188. "bad data type");
  189. return -1;
  190. }
  191. self->layout_type = type_lookup[i].layout;
  192. self->binary_size = 8 + (type_lookup[i].size * self->layout_count);
  193. return 0;
  194. }
  195. /* .close() */
  196. static PyObject *Rocket_close(Rocket *self)
  197. {
  198. if (self->file) {
  199. fclose(self->file);
  200. self->file = NULL;
  201. }
  202. Py_INCREF(Py_None);
  203. return Py_None;
  204. }
  205. /* .file_size property */
  206. static PyObject *Rocket_get_file_size(Rocket *self)
  207. {
  208. if (!self->file) {
  209. PyErr_SetString(PyExc_AttributeError, "no file");
  210. return NULL;
  211. }
  212. if (self->file_size < 0) {
  213. int oldpos;
  214. if (((oldpos = ftell(self->file)) < 0) ||
  215. (fseek(self->file, 0, SEEK_END) < 0) ||
  216. ((self->file_size = ftell(self->file)) < 0) ||
  217. (fseek(self->file, oldpos, SEEK_SET) < 0)) {
  218. PyErr_SetFromErrno(PyExc_OSError);
  219. return NULL;
  220. }
  221. }
  222. return PyInt_FromLong(self->file_size);
  223. }
  224. /****
  225. * Append from iterator
  226. */
  227. /* Helper for writing Python objects to the file */
  228. static inline void append_pyobject(FILE *out, PyObject *val, layout_type_t type)
  229. {
  230. union8_t t8;
  231. union16_t t16;
  232. union32_t t32;
  233. union64_t t64;
  234. int ret = 0;
  235. switch (type) {
  236. #define CASE(type, pyconvert, pytype, disktype, htole, bytes) \
  237. case LAYOUT_TYPE_##type: \
  238. pytype = pyconvert(val); \
  239. if (PyErr_Occurred()) \
  240. return; \
  241. disktype = htole(disktype); \
  242. ret = fwrite(&disktype, bytes, 1, out); \
  243. break
  244. CASE(INT8, PyInt_AsLong, t8.i, t8.u, , 1);
  245. CASE(UINT8, PyInt_AsLong, t8.u, t8.u, , 1);
  246. CASE(INT16, PyInt_AsLong, t16.i, t16.u, htole16, 2);
  247. CASE(UINT16, PyInt_AsLong, t16.u, t16.u, htole16, 2);
  248. CASE(INT32, PyInt_AsLong, t32.i, t32.u, htole32, 4);
  249. CASE(UINT32, PyInt_AsLong, t32.u, t32.u, htole32, 4);
  250. CASE(INT64, PyInt_AsLong, t64.i, t64.u, htole64, 8);
  251. CASE(UINT64, PyInt_AsLong, t64.u, t64.u, htole64, 8);
  252. CASE(FLOAT32, PyFloat_AsDouble, t32.f, t32.u, htole32, 4);
  253. CASE(FLOAT64, PyFloat_AsDouble, t64.d, t64.u, htole64, 8);
  254. #undef CASE
  255. default:
  256. PyErr_SetString(PyExc_TypeError, "unknown type");
  257. return;
  258. }
  259. if (ret <= 0) {
  260. PyErr_SetFromErrno(PyExc_OSError);
  261. }
  262. }
  263. /* .append_iter(maxrows, dataiter) */
  264. static PyObject *Rocket_append_iter(Rocket *self, PyObject *args)
  265. {
  266. int maxrows;
  267. PyObject *iter;
  268. PyObject *rowlist;
  269. if (!PyArg_ParseTuple(args, "iO:append_iter", &maxrows, &iter))
  270. return NULL;
  271. if (!PyIter_Check(iter)) {
  272. PyErr_SetString(PyExc_TypeError, "need an iterable");
  273. return NULL;
  274. }
  275. if (!self->file) {
  276. PyErr_SetString(PyExc_Exception, "no file");
  277. return NULL;
  278. }
  279. /* Mark file size so that it will get updated next time it's read */
  280. self->file_size = -1;
  281. int row;
  282. for (row = 0; row < maxrows; row++) {
  283. rowlist = PyIter_Next(iter);
  284. if (!rowlist)
  285. break;
  286. if (!PyList_Check(rowlist)) {
  287. PyErr_SetString(PyExc_TypeError, "rows must be lists");
  288. goto row_err;
  289. }
  290. if (PyList_Size(rowlist) != self->layout_count + 1) {
  291. PyErr_SetString(PyExc_TypeError, "short row");
  292. goto row_err;
  293. }
  294. /* Extract and write timestamp */
  295. append_pyobject(self->file, PyList_GetItem(rowlist, 0),
  296. LAYOUT_TYPE_INT64);
  297. if (PyErr_Occurred())
  298. goto row_err;
  299. /* Extract and write values */
  300. int i;
  301. for (i = 0; i < self->layout_count; i++) {
  302. append_pyobject(self->file,
  303. PyList_GetItem(rowlist, i+1),
  304. self->layout_type);
  305. if (PyErr_Occurred())
  306. goto row_err;
  307. }
  308. }
  309. fflush(self->file);
  310. /* All done */
  311. return PyLong_FromLong(row);
  312. row_err:
  313. fflush(self->file);
  314. Py_DECREF(rowlist);
  315. return NULL;
  316. }
  317. /****
  318. * Append from string
  319. */
  320. static inline long int strtol10(const char *nptr, char **endptr) {
  321. return strtol(nptr, endptr, 10);
  322. }
  323. static inline long int strtoul10(const char *nptr, char **endptr) {
  324. return strtoul(nptr, endptr, 10);
  325. }
  326. /* .append_string(count, data, offset, linenum, start, end, last_timestamp) */
  327. static PyObject *Rocket_append_string(Rocket *self, PyObject *args)
  328. {
  329. int count;
  330. const char *data;
  331. int offset;
  332. const char *linestart;
  333. int linenum;
  334. timestamp_t start;
  335. timestamp_t end;
  336. timestamp_t last_timestamp;
  337. int written = 0;
  338. char *endptr;
  339. union8_t t8;
  340. union16_t t16;
  341. union32_t t32;
  342. union64_t t64;
  343. int i;
  344. /* It would be nice to use 't#' instead of 's' for data,
  345. but we need the null termination for strto*. If we had
  346. strnto* that took a length, we could use t# and not require
  347. a copy. */
  348. if (!PyArg_ParseTuple(args, "isiiLLL:append_string", &count,
  349. &data, &offset, &linenum,
  350. &start, &end, &last_timestamp))
  351. return NULL;
  352. /* Skip spaces, but don't skip over a newline. */
  353. #define SKIP_BLANK(buf) do { \
  354. while (isspace(*buf)) { \
  355. if (*buf == '\n') \
  356. break; \
  357. buf++; \
  358. } } while(0)
  359. const char *buf = &data[offset];
  360. while (written < count && *buf)
  361. {
  362. linestart = buf;
  363. linenum++;
  364. /* Skip leading whitespace and commented lines */
  365. SKIP_BLANK(buf);
  366. if (*buf == '#') {
  367. while (*buf && *buf != '\n')
  368. buf++;
  369. if (*buf)
  370. buf++;
  371. continue;
  372. }
  373. /* Extract timestamp */
  374. t64.i = strtoll(buf, &endptr, 10);
  375. if (endptr == buf || !isspace(*endptr)) {
  376. /* Try parsing as a double instead */
  377. t64.d = strtod(buf, &endptr);
  378. if (endptr == buf)
  379. goto bad_timestamp;
  380. if (!isspace(*endptr))
  381. goto cant_parse_value;
  382. t64.i = round(t64.d);
  383. }
  384. if (t64.i <= last_timestamp)
  385. return raise_int(linenum, buf - linestart + 1,
  386. ERR_NON_MONOTONIC, t64.i);
  387. last_timestamp = t64.i;
  388. if (t64.i < start || t64.i >= end)
  389. return raise_int(linenum, buf - linestart + 1,
  390. ERR_OUT_OF_INTERVAL, t64.i);
  391. t64.u = le64toh(t64.u);
  392. if (fwrite(&t64.u, 8, 1, self->file) != 1)
  393. goto err;
  394. buf = endptr;
  395. /* Parse all values in the line */
  396. switch (self->layout_type) {
  397. #define CS(type, parsefunc, parsetype, realtype, disktype, letoh, bytes) \
  398. case LAYOUT_TYPE_##type: \
  399. /* parse and write in a loop */ \
  400. for (i = 0; i < self->layout_count; i++) { \
  401. /* skip non-newlines */ \
  402. SKIP_BLANK(buf); \
  403. if (*buf == '\n') \
  404. goto wrong_number_of_values; \
  405. /* parse number */ \
  406. parsetype = parsefunc(buf, &endptr); \
  407. if (*endptr && !isspace(*endptr)) \
  408. goto cant_parse_value; \
  409. /* check limits */ \
  410. if (type##_MIN != type##_MAX && \
  411. (parsetype < type##_MIN || \
  412. parsetype > type##_MAX)) \
  413. goto value_out_of_range; \
  414. /* convert to disk representation */ \
  415. realtype = parsetype; \
  416. disktype = letoh(disktype); \
  417. /* write it */ \
  418. if (fwrite(&disktype, bytes, \
  419. 1, self->file) != 1) \
  420. goto err; \
  421. /* advance buf */ \
  422. buf = endptr; \
  423. } \
  424. /* Skip trailing whitespace and comments */ \
  425. SKIP_BLANK(buf); \
  426. if (*buf == '#') \
  427. while (*buf && *buf != '\n') \
  428. buf++; \
  429. if (*buf == '\n') \
  430. buf++; \
  431. else if (*buf != '\0') \
  432. goto extra_data_on_line; \
  433. break
  434. CS(INT8, strtol10, t64.i, t8.i, t8.u, , 1);
  435. CS(UINT8, strtoul10, t64.u, t8.u, t8.u, , 1);
  436. CS(INT16, strtol10, t64.i, t16.i, t16.u, le16toh, 2);
  437. CS(UINT16, strtoul10, t64.u, t16.u, t16.u, le16toh, 2);
  438. CS(INT32, strtol10, t64.i, t32.i, t32.u, le32toh, 4);
  439. CS(UINT32, strtoul10, t64.u, t32.u, t32.u, le32toh, 4);
  440. CS(INT64, strtol10, t64.i, t64.i, t64.u, le64toh, 8);
  441. CS(UINT64, strtoul10, t64.u, t64.u, t64.u, le64toh, 8);
  442. CS(FLOAT32, strtod, t64.d, t32.f, t32.u, le32toh, 4);
  443. CS(FLOAT64, strtod, t64.d, t64.d, t64.u, le64toh, 8);
  444. #undef CS
  445. default:
  446. PyErr_SetString(PyExc_TypeError, "unknown type");
  447. return NULL;
  448. }
  449. /* Done this line */
  450. written++;
  451. }
  452. fflush(self->file);
  453. /* Build return value and return*/
  454. offset = buf - data;
  455. PyObject *o;
  456. o = Py_BuildValue("(iiLi)", written, offset, last_timestamp, linenum);
  457. return o;
  458. err:
  459. PyErr_SetFromErrno(PyExc_OSError);
  460. return NULL;
  461. bad_timestamp:
  462. return raise_str(linenum, buf - linestart + 1,
  463. ERR_OTHER, "bad timestamp");
  464. cant_parse_value:
  465. return raise_str(linenum, buf - linestart + 1,
  466. ERR_OTHER, "can't parse value");
  467. wrong_number_of_values:
  468. return raise_str(linenum, buf - linestart + 1,
  469. ERR_OTHER, "wrong number of values");
  470. value_out_of_range:
  471. return raise_str(linenum, buf - linestart + 1,
  472. ERR_OTHER, "value out of range");
  473. extra_data_on_line:
  474. return raise_str(linenum, buf - linestart + 1,
  475. ERR_OTHER, "extra data on line");
  476. }
  477. /****
  478. * Extract to Python list
  479. */
  480. static int _extract_handle_params(Rocket *self, PyObject *args, long *count)
  481. {
  482. long offset;
  483. if (!PyArg_ParseTuple(args, "ll", &offset, count))
  484. return -1;
  485. if (!self->file) {
  486. PyErr_SetString(PyExc_Exception, "no file");
  487. return -1;
  488. }
  489. /* Seek to target location */
  490. if (fseek(self->file, offset, SEEK_SET) < 0) {
  491. PyErr_SetFromErrno(PyExc_OSError);
  492. return -1;
  493. }
  494. return 0;
  495. }
  496. /* Helper for extracting data from a file as a Python object */
  497. static inline void *extract_pyobject(FILE *in, layout_type_t type)
  498. {
  499. union8_t t8;
  500. union16_t t16;
  501. union32_t t32;
  502. union64_t t64;
  503. switch (type) {
  504. #define CASE(type, pyconvert, pytype, disktype, letoh, bytes) \
  505. case LAYOUT_TYPE_##type: \
  506. if (fread(&disktype, bytes, 1, in) <= 0) \
  507. break; \
  508. disktype = letoh(disktype); \
  509. return pyconvert(pytype); \
  510. break
  511. CASE(INT8, PyInt_FromLong, t8.i, t8.u, , 1);
  512. CASE(UINT8, PyInt_FromLong, t8.u, t8.u, , 1);
  513. CASE(INT16, PyInt_FromLong, t16.i, t16.u, le16toh, 2);
  514. CASE(UINT16, PyInt_FromLong, t16.u, t16.u, le16toh, 2);
  515. CASE(INT32, PyInt_FromLong, t32.i, t32.u, le32toh, 4);
  516. CASE(UINT32, PyInt_FromLong, t32.u, t32.u, le32toh, 4);
  517. CASE(INT64, PyInt_FromLong, t64.i, t64.u, le64toh, 8);
  518. CASE(UINT64, PyInt_FromLong, t64.u, t64.u, le64toh, 8);
  519. CASE(FLOAT32, PyFloat_FromDouble, t32.f, t32.u, le32toh, 4);
  520. CASE(FLOAT64, PyFloat_FromDouble, t64.d, t64.u, le64toh, 8);
  521. #undef CASE
  522. default:
  523. PyErr_SetString(PyExc_TypeError, "unknown type");
  524. return NULL;
  525. }
  526. PyErr_SetString(PyExc_OSError, "failed to read from file");
  527. return NULL;
  528. }
  529. static PyObject *Rocket_extract_list(Rocket *self, PyObject *args)
  530. {
  531. long count;
  532. if (_extract_handle_params(self, args, &count) < 0)
  533. return NULL;
  534. /* Make a list to return */
  535. PyObject *retlist = PyList_New(0);
  536. if (!retlist)
  537. return NULL;
  538. /* Read data into new Python lists */
  539. int row;
  540. for (row = 0; row < count; row++)
  541. {
  542. PyObject *rowlist = PyList_New(self->layout_count + 1);
  543. if (!rowlist) {
  544. Py_DECREF(retlist);
  545. return NULL;
  546. }
  547. /* Timestamp */
  548. PyObject *entry = extract_pyobject(self->file,
  549. LAYOUT_TYPE_INT64);
  550. if (!entry || (PyList_SetItem(rowlist, 0, entry) < 0)) {
  551. Py_DECREF(rowlist);
  552. Py_DECREF(retlist);
  553. return NULL;
  554. }
  555. /* Data */
  556. int i;
  557. for (i = 0; i < self->layout_count; i++) {
  558. PyObject *ent = extract_pyobject(self->file,
  559. self->layout_type);
  560. if (!ent || (PyList_SetItem(rowlist, i+1, ent) < 0)) {
  561. Py_DECREF(rowlist);
  562. Py_DECREF(retlist);
  563. return NULL;
  564. }
  565. }
  566. /* Add row to return value */
  567. if (PyList_Append(retlist, rowlist) < 0) {
  568. Py_DECREF(rowlist);
  569. Py_DECREF(retlist);
  570. return NULL;
  571. }
  572. Py_DECREF(rowlist);
  573. }
  574. return retlist;
  575. }
  576. /****
  577. * Extract to string
  578. */
  579. static PyObject *Rocket_extract_string(Rocket *self, PyObject *args)
  580. {
  581. long count;
  582. if (_extract_handle_params(self, args, &count) < 0)
  583. return NULL;
  584. char *str = NULL, *new;
  585. long len_alloc = 0;
  586. long len = 0;
  587. int ret;
  588. /* min space free in string (and the maximum length of one
  589. line); this is generous */
  590. const int min_free = 32 * MAX_LAYOUT_COUNT;
  591. /* how much to allocate at once */
  592. const int alloc_size = 1048576;
  593. int row, i;
  594. union8_t t8;
  595. union16_t t16;
  596. union32_t t32;
  597. union64_t t64;
  598. for (row = 0; row < count; row++) {
  599. /* Make sure there's space for a line */
  600. if ((len_alloc - len) < min_free) {
  601. /* grow by 1 meg at a time */
  602. len_alloc += alloc_size;
  603. new = realloc(str, len_alloc);
  604. if (new == NULL)
  605. goto err;
  606. str = new;
  607. }
  608. /* Read and print timestamp */
  609. if (fread(&t64.u, 8, 1, self->file) != 1)
  610. goto err;
  611. t64.u = le64toh(t64.u);
  612. ret = sprintf(&str[len], "%ld", t64.i);
  613. if (ret <= 0)
  614. goto err;
  615. len += ret;
  616. /* Read and print values */
  617. switch (self->layout_type) {
  618. #define CASE(type, fmt, fmttype, disktype, letoh, bytes) \
  619. case LAYOUT_TYPE_##type: \
  620. /* read and format in a loop */ \
  621. for (i = 0; i < self->layout_count; i++) { \
  622. if (fread(&disktype, bytes, \
  623. 1, self->file) < 0) \
  624. goto err; \
  625. disktype = letoh(disktype); \
  626. ret = sprintf(&str[len], " " fmt, \
  627. fmttype); \
  628. if (ret <= 0) \
  629. goto err; \
  630. len += ret; \
  631. } \
  632. break
  633. CASE(INT8, "%hhd", t8.i, t8.u, , 1);
  634. CASE(UINT8, "%hhu", t8.u, t8.u, , 1);
  635. CASE(INT16, "%hd", t16.i, t16.u, le16toh, 2);
  636. CASE(UINT16, "%hu", t16.u, t16.u, le16toh, 2);
  637. CASE(INT32, "%d", t32.i, t32.u, le32toh, 4);
  638. CASE(UINT32, "%u", t32.u, t32.u, le32toh, 4);
  639. CASE(INT64, "%ld", t64.i, t64.u, le64toh, 8);
  640. CASE(UINT64, "%lu", t64.u, t64.u, le64toh, 8);
  641. /* These next two are a bit debatable. floats
  642. are 6-9 significant figures, so we print 7.
  643. Doubles are 15-19, so we print 17. This is
  644. similar to the old prep format for float32.
  645. */
  646. CASE(FLOAT32, "%.6e", t32.f, t32.u, le32toh, 4);
  647. CASE(FLOAT64, "%.16e", t64.d, t64.u, le64toh, 8);
  648. #undef CASE
  649. default:
  650. PyErr_SetString(PyExc_TypeError, "unknown type");
  651. if (str) free(str);
  652. return NULL;
  653. }
  654. str[len++] = '\n';
  655. }
  656. PyObject *pystr = PyString_FromStringAndSize(str, len);
  657. free(str);
  658. return pystr;
  659. err:
  660. if (str) free(str);
  661. PyErr_SetFromErrno(PyExc_OSError);
  662. return NULL;
  663. }
  664. /****
  665. * Module and type setup
  666. */
  667. static PyGetSetDef Rocket_getsetters[] = {
  668. { "file_size", (getter)Rocket_get_file_size, NULL,
  669. "file size in bytes", NULL },
  670. { NULL },
  671. };
  672. static PyMemberDef Rocket_members[] = {
  673. { "binary_size", T_INT, offsetof(Rocket, binary_size), 0,
  674. "binary size per row" },
  675. { NULL },
  676. };
  677. static PyMethodDef Rocket_methods[] = {
  678. { "close", (PyCFunction)Rocket_close, METH_NOARGS,
  679. "close(self)\n\n"
  680. "Close file handle" },
  681. { "append_iter", (PyCFunction)Rocket_append_iter, METH_VARARGS,
  682. "append_iter(self, maxrows, iterable)\n\n"
  683. "Append up to maxrows of data from iter to the file" },
  684. { "append_string", (PyCFunction)Rocket_append_string, METH_VARARGS,
  685. "append_string(self, count, data, offset, line, start, end, ts)\n\n"
  686. "Parse string and append data.\n"
  687. "\n"
  688. " count: maximum number of rows to add\n"
  689. " data: string data\n"
  690. " offset: byte offset into data to start parsing\n"
  691. " line: current line number of data\n"
  692. " start: starting timestamp for interval\n"
  693. " end: end timestamp for interval\n"
  694. " ts: last timestamp that was previously parsed\n"
  695. "\n"
  696. "Raises ParseError if timestamps are non-monotonic, outside\n"
  697. "the start/end interval etc.\n"
  698. "\n"
  699. "On success, return a tuple with three values:\n"
  700. " added_rows: how many rows were added from the file\n"
  701. " data_offset: current offset into the data string\n"
  702. " last_timestamp: last timestamp we parsed" },
  703. { "extract_list", (PyCFunction)Rocket_extract_list, METH_VARARGS,
  704. "extract_list(self, offset, count)\n\n"
  705. "Extract count rows of data from the file at offset offset.\n"
  706. "Return a list of lists [[row],[row],...]" },
  707. { "extract_string", (PyCFunction)Rocket_extract_string, METH_VARARGS,
  708. "extract_string(self, offset, count)\n\n"
  709. "Extract count rows of data from the file at offset offset.\n"
  710. "Return an ascii formatted string according to the layout" },
  711. { NULL },
  712. };
  713. static PyTypeObject RocketType = {
  714. PyObject_HEAD_INIT(NULL)
  715. .tp_name = "rocket.Rocket",
  716. .tp_basicsize = sizeof(Rocket),
  717. .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
  718. .tp_new = Rocket_new,
  719. .tp_dealloc = (destructor)Rocket_dealloc,
  720. .tp_init = (initproc)Rocket_init,
  721. .tp_methods = Rocket_methods,
  722. .tp_members = Rocket_members,
  723. .tp_getset = Rocket_getsetters,
  724. .tp_doc = ("rocket.Rocket(layout, file)\n\n"
  725. "C implementation of the \"rocket\" data parsing\n"
  726. "interface, which translates between the binary\n"
  727. "format on disk and the ASCII or Python list\n"
  728. "format used when communicating with the rest of\n"
  729. "the system.")
  730. };
  731. static PyMethodDef module_methods[] = {
  732. { NULL },
  733. };
  734. PyMODINIT_FUNC
  735. initrocket(void)
  736. {
  737. PyObject *module;
  738. RocketType.tp_new = PyType_GenericNew;
  739. if (PyType_Ready(&RocketType) < 0)
  740. return;
  741. module = Py_InitModule3("rocket", module_methods,
  742. "Rocket data parsing and formatting module");
  743. Py_INCREF(&RocketType);
  744. PyModule_AddObject(module, "Rocket", (PyObject *)&RocketType);
  745. ParseError = PyErr_NewException("rocket.ParseError", NULL, NULL);
  746. Py_INCREF(ParseError);
  747. PyModule_AddObject(module, "ParseError", ParseError);
  748. add_parseerror_codes(module);
  749. return;
  750. }