perci.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. /*
  2. * Copyright 2010 by egnite GmbH
  3. *
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions
  8. * are met:
  9. *
  10. * 1. Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * 2. Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. * 3. Neither the name of the copyright holders nor the names of
  16. * contributors may be used to endorse or promote products derived
  17. * from this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  22. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  23. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  24. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  25. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  26. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  27. * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  28. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  29. * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  30. * SUCH DAMAGE.
  31. *
  32. * For additional information see http://www.ethernut.de/
  33. */
  34. /*
  35. * \file gorp/buffer/perci.c
  36. * \brief Persistent circular buffer.
  37. *
  38. * Applications may use this module for logging any kind of data.
  39. *
  40. * \verbatim
  41. * $Id$
  42. * \endverbatim
  43. */
  44. #include <io.h>
  45. #include <fcntl.h>
  46. #include <sys/stat.h>
  47. #include <string.h>
  48. #include <stdarg.h>
  49. #include <time.h>
  50. #include <errno.h>
  51. #include <sys/nutdebug.h>
  52. #include <sys/event.h>
  53. #include <gorp/perci.h>
  54. /*!
  55. * \addtogroup xgPerci
  56. */
  57. /*@{*/
  58. //#define NUTDEBUG
  59. /*!
  60. * \brief Dump ring buffer file to given stream.
  61. *
  62. * \param path Path name of the file.
  63. * \param stream Stream to dump the contents to.
  64. */
  65. void PerCiDump(FILE *stream, char *path)
  66. {
  67. #ifdef NUTDEBUG
  68. int fd;
  69. int got;
  70. perci_fast_reclen_t i;
  71. perci_fast_reclen_t ll;
  72. perci_recnum_t recnum;
  73. perci_reclen_t reclen;
  74. uint8_t *recbuf;
  75. fd = _open(path, _O_RDWR | _O_BINARY);
  76. if (fd == -1) {
  77. fprintf(stream, "Error %d opening %s\n", errno, path);
  78. return;
  79. } else {
  80. fprintf(stream, "Dump %ld bytes in %s\n", _filelength(fd), path);
  81. recbuf = malloc(PERCI_DATASIZE);
  82. for (recnum = 0;; recnum++) {
  83. got = _read(fd, &reclen, sizeof(perci_reclen_t));
  84. if (got <= 0) {
  85. break;
  86. }
  87. fprintf(stream, "%03u %03u ", recnum, (unsigned int)reclen);
  88. got = _read(fd, recbuf, PERCI_DATASIZE);
  89. for (i = 0, ll = 0; i < (perci_fast_reclen_t) got && i <= (perci_fast_reclen_t) reclen; i++) {
  90. if (recbuf[i] < ' ') {
  91. ll += 2;
  92. if (recbuf[i] == '\n') {
  93. fputs("\\n", stream);
  94. }
  95. else if (recbuf[i] == '\r') {
  96. fputs("\\r", stream);
  97. }
  98. else if (recbuf[i] == '\t') {
  99. fputs("\\t", stream);
  100. } else {
  101. fprintf(stream, "\\x%02x", recbuf[i]);
  102. ll += 2;
  103. }
  104. } else {
  105. fputc(recbuf[i], stream);
  106. ll++;
  107. }
  108. if (ll > 80) {
  109. fprintf(stream, "...");
  110. break;
  111. }
  112. }
  113. fputc('\n', stream);
  114. }
  115. _close(fd);
  116. free(recbuf);
  117. }
  118. #endif
  119. }
  120. /*!
  121. * \brief Initialize a ring buffer file.
  122. *
  123. * If the file doesn't exist, a new file will be created. If the file
  124. * exists, any buffered data will be erased.
  125. *
  126. * The total size of the file is
  127. * \code
  128. * recs * PERCI_RECSIZE + sizeof(perci_reclen_t)
  129. * \endcode
  130. * bytes.
  131. *
  132. * The available data space is
  133. * \code
  134. * recs * PERCI_DATASIZE
  135. * \endcode
  136. * bytes.
  137. *
  138. * \param path Path name of the file.
  139. * \param recs Number of buffer records, at least 2.
  140. *
  141. * \return 0 on success and -1 on failure.
  142. */
  143. int PerCiInit(char *path, int recs)
  144. {
  145. int fd;
  146. int i;
  147. PERCI_RECORD *rec;
  148. /* Check function parameters. */
  149. NUTASSERT(path != NULL);
  150. NUTASSERT(recs >= 2);
  151. fd = _open(path, _O_RDWR | _O_CREAT | _O_TRUNC | _O_BINARY);
  152. if (fd == -1) {
  153. return -1;
  154. }
  155. rec = calloc(PERCI_RECSIZE, 1);
  156. if (rec) {
  157. for (i = 0; i < recs; i++) {
  158. _write(fd, rec, PERCI_RECSIZE);
  159. }
  160. _write(fd, rec, sizeof(perci_reclen_t));
  161. free(rec);
  162. }
  163. _close(fd);
  164. return 0;
  165. }
  166. /*!
  167. * \brief Open a ring buffer file.
  168. *
  169. * The file must have been created by a previous call to PerCiInit().
  170. *
  171. * The function failes, if the file doesn't exist or if it is corrupted.
  172. * Applications may use the following sequence to open a circular buffer.
  173. * \code
  174. * PERCI_WRITER *perci;
  175. * char *path = "UFLASH0:data.log";
  176. *
  177. * while ((perci = PerCiOpen(path)) == NULL) {
  178. * if (PerCiInit(path, 128)) {
  179. * printf("Error %d creating %s\n", errno, path);
  180. * break;
  181. * }
  182. * }
  183. * \endcode
  184. *
  185. * \param path Path name of the file.
  186. *
  187. * \return A pointer to a PERCI_WRITER structure on success. The return
  188. * of a NULL pointer indicates a failure.
  189. */
  190. PERCI_WRITER *PerCiOpen(char *path)
  191. {
  192. uint_fast8_t ok = 0;
  193. PERCI_WRITER *writer;
  194. perci_reclen_t reclen;
  195. /* Check function parameter. */
  196. NUTASSERT(path != NULL);
  197. /* Allocate a writer structure and open the file. */
  198. writer = calloc(1, sizeof(PERCI_WRITER));
  199. if (writer) {
  200. /* Open the file. If this fails, release the writer structure
  201. and return a NULL pointer. */
  202. writer->pcw_fd = _open(path, _O_RDWR | _O_BINARY);
  203. if (writer->pcw_fd != -1) {
  204. /* File exists, determine its size. If it doesn't contain at
  205. least 2 records, we consider it corrupted. */
  206. writer->pcw_size = _filelength(writer->pcw_fd);
  207. if (writer->pcw_size >= 2 * PERCI_RECSIZE + sizeof(perci_reclen_t)) {
  208. writer->pcw_size -= sizeof(perci_reclen_t);
  209. /*
  210. * Scan the file for the next available record.
  211. */
  212. _seek(writer->pcw_fd, 0, SEEK_SET);
  213. for (writer->pcw_recnum = 0; writer->pcw_recnum < PERCI_MAX_RECORDS; writer->pcw_recnum++) {
  214. /* Read the length of the current record. */
  215. if (_read(writer->pcw_fd, &reclen, sizeof(perci_reclen_t)) != sizeof(perci_reclen_t)) {
  216. /* Corrupted. */
  217. break;
  218. }
  219. /* If this record isn't completely filled, then we continue
  220. writing at this point. */
  221. if (reclen < PERCI_DATASIZE) {
  222. /* Fill the record buffer. */
  223. writer->pcw_rec.pcd_len = reclen;
  224. if (reclen && _read(writer->pcw_fd, writer->pcw_rec.pcd_data, reclen) != reclen) {
  225. break;
  226. }
  227. NutEventPost(&writer->pcw_mutex);
  228. ok = 1;
  229. break;
  230. }
  231. /* This record is filled, move to the next one. */
  232. _seek(writer->pcw_fd, PERCI_DATASIZE, SEEK_CUR);
  233. }
  234. }
  235. }
  236. }
  237. /* Release resources on error. */
  238. if (!ok && writer) {
  239. if (writer->pcw_fd != -1) {
  240. _close(writer->pcw_fd);
  241. }
  242. free(writer);
  243. writer = NULL;
  244. }
  245. return writer;
  246. }
  247. /*!
  248. * \brief Flush the current write buffer.
  249. *
  250. * \param writer Pointer to a PERCI_WRITER structure obtained by a
  251. * previous call to PerCiOpen().
  252. */
  253. void PerCiFlush(PERCI_WRITER * writer)
  254. {
  255. /* Check function parameter. */
  256. NUTASSERT(writer != NULL);
  257. NUTASSERT(writer->pcw_fd != -1);
  258. _seek(writer->pcw_fd, writer->pcw_recnum * PERCI_RECSIZE, SEEK_SET);
  259. _write(writer->pcw_fd, &writer->pcw_rec, sizeof(perci_reclen_t) + writer->pcw_rec.pcd_len);
  260. }
  261. /*!
  262. * \brief Close a ring buffer file.
  263. *
  264. * \param writer Pointer to a PERCI_WRITER structure obtained by a
  265. * previous call to PerCiOpen().
  266. */
  267. void PerCiClose(PERCI_WRITER * writer)
  268. {
  269. /* Check function parameter. */
  270. NUTASSERT(writer != NULL);
  271. if (writer->pcw_rec.pcd_len) {
  272. PerCiFlush(writer);
  273. }
  274. _close(writer->pcw_fd);
  275. free(writer);
  276. }
  277. /*!
  278. * \brief Write to a ring buffer file.
  279. *
  280. * \param writer Pointer to a PERCI_WRITER structure obtained by a
  281. * previous call to PerCiOpen().
  282. *
  283. * \return The number of bytes successfully written or -1 on failure.
  284. */
  285. int PerCiWrite(PERCI_WRITER * writer, const char *data, int len)
  286. {
  287. perci_fast_reclen_t cnt = 0;
  288. perci_fast_reclen_t num;
  289. perci_fast_reclen_t reclen;
  290. /* Check parameters. */
  291. NUTASSERT(writer != NULL);
  292. NUTASSERT(writer->pcw_fd != -1);
  293. NUTASSERT(writer->pcw_rec.pcd_len <= PERCI_DATASIZE);
  294. NutEventWait(&writer->pcw_mutex, NUT_WAIT_INFINITE);
  295. while ((perci_fast_reclen_t) len > cnt) {
  296. /* Calculate the number of bytes to write in the next step. */
  297. reclen = (perci_fast_reclen_t) writer->pcw_rec.pcd_len;
  298. num = (perci_fast_reclen_t) len - cnt;
  299. if (num > PERCI_DATASIZE - reclen) {
  300. num = PERCI_DATASIZE - reclen;
  301. }
  302. /* Move the bytes to the record buffer. */
  303. memcpy(&writer->pcw_rec.pcd_data[reclen], data, num);
  304. writer->pcw_rec.pcd_len += num;
  305. cnt += num;
  306. data += num;
  307. /* If the buffered record is completely filled, then write it
  308. back to the file and claim the next one. Note, that we write
  309. a whole record, which is sizeof(perci_reclen_t) larger than
  310. the real record size. This way we automatically override the
  311. length of the following record with zero. */
  312. if (writer->pcw_rec.pcd_len == PERCI_DATASIZE) {
  313. _seek(writer->pcw_fd, writer->pcw_recnum * PERCI_RECSIZE, SEEK_SET);
  314. if (_write(writer->pcw_fd, &writer->pcw_rec, sizeof(PERCI_RECORD)) != sizeof(PERCI_RECORD)) {
  315. NutEventPost(&writer->pcw_mutex);
  316. return -1;
  317. }
  318. writer->pcw_rec.pcd_len = 0;
  319. writer->pcw_recnum++;
  320. /* Check for wrap around. */
  321. if (writer->pcw_recnum * PERCI_RECSIZE >= writer->pcw_size) {
  322. writer->pcw_recnum = 0;
  323. _seek(writer->pcw_fd, 0, SEEK_SET);
  324. if (_write(writer->pcw_fd, &writer->pcw_rec.pcd_len, sizeof(perci_reclen_t)) != sizeof(perci_reclen_t)) {
  325. NutEventPost(&writer->pcw_mutex);
  326. return -1;
  327. }
  328. }
  329. }
  330. }
  331. NutEventPost(&writer->pcw_mutex);
  332. return cnt;
  333. }
  334. /*!
  335. * \brief Write formatted line to a ring buffer file.
  336. *
  337. * Alternate form of PerCiWriteFormat(), in which the arguments have
  338. * already been captured using the variable-length argument facilities.
  339. *
  340. * \param writer Pointer to a PERCI_WRITER structure obtained by a
  341. * previous call to PerCiOpen().
  342. * \param fmt Format string containing conversion specifications
  343. * like printf.
  344. * \param ap Pointer to the list of arguments.
  345. *
  346. * \return The number of bytes successfully written or -1 on failure.
  347. */
  348. int PerCiWriteVarList(PERCI_WRITER * writer, const char *fmt, va_list ap)
  349. {
  350. int cnt;
  351. char *line;
  352. /* Check function parameter. */
  353. NUTASSERT(fmt != NULL);
  354. line = malloc(PERCI_DATASIZE);
  355. if (line) {
  356. cnt = vsnprintf(line, PERCI_DATASIZE, fmt, ap);
  357. cnt = PerCiWrite(writer, line, strlen(line));
  358. free(line);
  359. } else {
  360. cnt = -1;
  361. }
  362. return cnt;
  363. }
  364. /*!
  365. * \brief Write formatted line to a ring buffer file.
  366. *
  367. * \param writer Pointer to a PERCI_WRITER structure obtained by a
  368. * previous call to PerCiOpen().
  369. * \param fmt Format string containing conversion specifications
  370. * like printf.
  371. *
  372. * \return The number of bytes successfully written or -1 on failure.
  373. */
  374. int PerCiWriteFormat(PERCI_WRITER * writer, const char *fmt, ...)
  375. {
  376. int rc;
  377. va_list ap;
  378. /* Check function parameter. */
  379. NUTASSERT(fmt != NULL);
  380. va_start(ap, fmt);
  381. rc = PerCiWriteVarList(writer, fmt, ap);
  382. va_end(ap);
  383. return rc;
  384. }
  385. /*!
  386. * \brief Find next record with data.
  387. *
  388. * \param writer Pointer to a PERCI_WRITER structure obtained by a
  389. * previous call to PerCiOpen().
  390. * \param recnum Pointer to a variable, which contains the number of
  391. * the start record upon entry and which will contain
  392. * the number of the record found upon exit.
  393. *
  394. * \return The number of bytes contained in the record found, or 0 if
  395. * no more records with data are available.
  396. */
  397. static perci_fast_reclen_t FindNextData(PERCI_WRITER * writer, perci_fast_recnum_t * recnum)
  398. {
  399. perci_reclen_t reclen;
  400. long pos;
  401. int got;
  402. /* Check parameters. */
  403. NUTASSERT(writer != NULL);
  404. NutEventWait(&writer->pcw_mutex, NUT_WAIT_INFINITE);
  405. pos = *recnum * PERCI_RECSIZE;
  406. for (;;) {
  407. /* If we reached the end of the file, then continue at its
  408. beginning. */
  409. if (pos >= writer->pcw_size) {
  410. pos = 0;
  411. *recnum = 0;
  412. }
  413. /* If we reached the latest record, then return the number
  414. of bytes in the write buffer. */
  415. if (*recnum == writer->pcw_recnum) {
  416. reclen = writer->pcw_rec.pcd_len;
  417. break;
  418. }
  419. /* Otherwise read the number of bytes available in the record.
  420. This value is stored at the beginning of each record. */
  421. _seek(writer->pcw_fd, pos, SEEK_SET);
  422. got = _read(writer->pcw_fd, &reclen, sizeof(perci_reclen_t));
  423. if (got == sizeof(perci_reclen_t) && reclen) {
  424. break;
  425. }
  426. /* Either this record is empty or the read failed, which we
  427. sliently ignore for now. Move on to the next record. */
  428. pos += PERCI_RECSIZE;
  429. (*recnum)++;
  430. }
  431. NutEventPost(&writer->pcw_mutex);
  432. return reclen;
  433. }
  434. /*!
  435. * \brief Start reading from a ring buffer file.
  436. *
  437. * It is assumed, that the ring buffer is kept open while the application
  438. * is running. From time to time the buffered data will be retrieved.
  439. * Therefore, a reader is attached to a previously open file and reading
  440. * starts at the oldest record.
  441. *
  442. * When all data has been read, the application must call PerCiDetachReader()
  443. * to relase all allocated resources for reading.
  444. *
  445. * Multiple readers may be concurrently attached to the same file.
  446. *
  447. * \param writer Pointer to a PERCI_WRITER structure obtained by a
  448. * previous call to PerCiOpen().
  449. *
  450. * \return A pointer to a PERCI_READER structure on success. The return
  451. * of a NULL pointer indicates a failure.
  452. */
  453. PERCI_READER *PerCiAttachReader(PERCI_WRITER * writer)
  454. {
  455. PERCI_READER *reader;
  456. /* Check parameters. */
  457. NUTASSERT(writer != NULL);
  458. reader = malloc(sizeof(PERCI_READER));
  459. reader->pcr_cil = writer;
  460. reader->pcr_recpos = 0;
  461. /* Search the oldest record that contains data. Start with the one
  462. above the current record of the writer. */
  463. reader->pcr_recnum = writer->pcw_recnum + 1;
  464. reader->pcr_reclen = FindNextData(writer, &reader->pcr_recnum);
  465. return reader;
  466. }
  467. /*!
  468. * \brief Stop reading from a ring buffer file.
  469. *
  470. * \param reader Pointer to a PERCI_READER structure obtained by a
  471. * previous call to PerCiAttachReader().
  472. */
  473. void PerCiDetachReader(PERCI_READER * reader)
  474. {
  475. /* Check parameters. */
  476. NUTASSERT(reader != NULL);
  477. free(reader);
  478. }
  479. /*!
  480. * \brief Read data from a ring buffer file.
  481. *
  482. * \param reader Pointer to a PERCI_READER structure obtained by a
  483. * previous call to PerCiAttachReader().
  484. * \param data Pointer to the buffer that receives the data.
  485. * \param len Number of bytes to read.
  486. *
  487. * \return The number of bytes read. If this is lower than the
  488. * requested length, then we reached the end.
  489. */
  490. int PerCiRead(PERCI_READER * reader, char *data, int len)
  491. {
  492. int cnt = 0;
  493. perci_fast_reclen_t num;
  494. int got;
  495. /* Check parameters. */
  496. NUTASSERT(reader != NULL);
  497. /*
  498. * Loop for for number of requested bytes.
  499. */
  500. while (len > cnt) {
  501. /* Calculate the number of bytes to read in the next step. */
  502. num = len - cnt;
  503. if (num > reader->pcr_reclen - reader->pcr_recpos) {
  504. num = reader->pcr_reclen - reader->pcr_recpos;
  505. }
  506. /* Check if we reached the last record. This is the one currently
  507. used by the writer. */
  508. if (reader->pcr_recnum == reader->pcr_cil->pcw_recnum) {
  509. if (num) {
  510. /* Get the data from the write buffer. */
  511. memcpy(data, &reader->pcr_cil->pcw_rec.pcd_data[reader->pcr_recpos], num);
  512. data += num;
  513. cnt += num;
  514. reader->pcr_recpos += num;
  515. } else {
  516. /* All data read. */
  517. break;
  518. }
  519. }
  520. /* Not the last record. Continue reading from the file. */
  521. else {
  522. /* Check if we reached the end of the current record. */
  523. if (num) {
  524. got = _read(reader->pcr_cil->pcw_fd, data, num);
  525. if (got != num) {
  526. break;
  527. }
  528. data += num;
  529. cnt += num;
  530. reader->pcr_recpos += num;
  531. } else {
  532. /* We consumed all data in this record. Step to the next. */
  533. reader->pcr_recnum++;
  534. reader->pcr_recpos = 0;
  535. /* Find the next record containing data. */
  536. reader->pcr_reclen = FindNextData(reader->pcr_cil, &reader->pcr_recnum);
  537. if (reader->pcr_reclen == 0) {
  538. /* No more data available. */
  539. break;
  540. }
  541. }
  542. }
  543. }
  544. return cnt;
  545. }
  546. /*!
  547. * \brief Read a text line from a ring buffer file.
  548. *
  549. * \param reader Pointer to a PERCI_READER structure obtained by a
  550. * previous call to PerCiAttachReader().
  551. * \param data Pointer to the buffer that receives the data.
  552. * \param len Number of bytes to read.
  553. *
  554. * \return The number of bytes read. If this is lower than the
  555. * requested length, then we reached the end.
  556. */
  557. int PerCiReadLine(PERCI_READER * reader, char *line, int len)
  558. {
  559. int cnt = 0;
  560. char *cp = line;
  561. /* Check function parameters. */
  562. NUTASSERT(reader != NULL);
  563. NUTASSERT(line != NULL);
  564. while (cnt < len) {
  565. if (PerCiRead(reader, cp, 1) != 1) {
  566. break;
  567. }
  568. cnt++;
  569. if (*cp++ == '\n') {
  570. break;
  571. }
  572. }
  573. *cp = 0;
  574. return cnt;
  575. }
  576. /*@}*/