summaryrefslogtreecommitdiff
path: root/ubi-utils/ubihealthd.c
diff options
context:
space:
mode:
Diffstat (limited to 'ubi-utils/ubihealthd.c')
-rw-r--r--ubi-utils/ubihealthd.c272
1 files changed, 272 insertions, 0 deletions
diff --git a/ubi-utils/ubihealthd.c b/ubi-utils/ubihealthd.c
new file mode 100644
index 0000000..3e665be
--- /dev/null
+++ b/ubi-utils/ubihealthd.c
@@ -0,0 +1,272 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <mtd/ubi-user.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <syslog.h>
+#include <sys/random.h>
+#include <sys/signalfd.h>
+#include <sys/stat.h>
+#include <sys/timerfd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define PROGRAM_NAME "ubihealthd"
+
+#include "libubi.h"
+#include "common.h"
+
+#ifndef UBI_IOCRPEB
+#define UBI_IOCRPEB _IOW(UBI_IOC_MAGIC, 4, int32_t)
+#endif
+
+struct peb_state {
+ int alive;
+ int pnum;
+ int last_errno;
+};
+
+static struct peb_state **peb_state_array;
+static int peb_state_array_len;
+static int cur_pos;
+static const char *ubi_device = "/dev/ubi0";
+static int ubi_fd;
+static int interval_secs = 120;
+static int nodaemon;
+
+static const char opt_string[] = "d:i:f";
+static const struct option options[] = {
+ {
+ .name = "device",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'd'
+ },
+ {
+ .name = "interval",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'i'
+ },
+};
+
+static void dolog(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (nodaemon)
+ vfprintf(stderr, fmt, ap);
+ else
+ vsyslog(LOG_DAEMON | LOG_WARNING, fmt, ap);
+ va_end(ap);
+}
+
+static void build_peb_list(void)
+{
+ int i, pos;
+ struct peb_state *ps;
+
+ peb_state_array = xmalloc(sizeof(ps) * peb_state_array_len);
+
+ for (i = 0; i < peb_state_array_len; i++) {
+ ps = xmalloc(sizeof(*ps));
+
+ ps->pnum = i;
+ ps->last_errno = 0;
+ ps->alive = 1;
+
+ peb_state_array[i] = ps;
+ }
+
+ /* Shuffle the list */
+ for (i = 0; i < peb_state_array_len; i++) {
+ pos = rand() % peb_state_array_len;
+
+ ps = peb_state_array[pos];
+ peb_state_array[pos] = peb_state_array[i];
+ peb_state_array[i] = ps;
+ }
+}
+
+static struct peb_state *__next_peb(void)
+{
+ struct peb_state *ps = peb_state_array[cur_pos];
+
+ cur_pos++;
+ if (cur_pos >= peb_state_array_len)
+ cur_pos = 0;
+
+ return ps;
+}
+
+static struct peb_state *next_peb(void)
+{
+ int i;
+ struct peb_state *ps;
+
+ /* Find next PEB in our list, skip bad PEBs */
+ for (i = 0; i < peb_state_array_len; i++) {
+ ps = __next_peb();
+ if (ps->alive)
+ return ps;
+ }
+
+ dolog("Fatal: All PEBs are gone?!\n");
+ exit(1);
+
+ return NULL;
+}
+
+static int process_one_peb(void)
+{
+ int rc;
+ struct peb_state *ps = next_peb();
+
+ rc = ioctl(ubi_fd, UBI_IOCRPEB, &ps->pnum);
+ if (!rc)
+ return 0;
+ else
+ rc = errno;
+
+ switch (rc) {
+ case EINVAL: {
+ dolog("Unable to check PEB %i for unknown reason!\n", ps->pnum);
+ break;
+ }
+ case ENOENT: {
+ /* UBI ignores this PEB */
+ ps->alive = 0;
+ break;
+ }
+ case EBUSY: {
+ if (ps->last_errno == rc)
+ dolog("Warning: Unable to check PEB %i\n", ps->pnum);
+ break;
+ }
+ case EAGAIN: {
+ if (ps->last_errno == rc)
+ dolog("Warning: PEB %i has bitflips, but cannot scrub!\n", ps->pnum);
+ break;
+ }
+ case EUCLEAN: {
+ /* Scrub happened */
+ break;
+ }
+ case ENOTTY: {
+ dolog("Fatal: Kernel does not support this interface. Too old kernel?\n");
+ exit(1);
+ break;
+ }
+ case ENODEV: {
+ dolog("Fatal: UBI device vanished under us.\n");
+ exit(1);
+ }
+ default:
+ dolog("Warning: Unknown return code from kernel: %i\n", rc);
+ }
+
+ ps->last_errno = rc;
+
+ return 0;
+}
+
+static int get_peb_count(void)
+{
+ libubi_t libubi = libubi_open();
+ struct ubi_dev_info dev_info;
+
+ if (!libubi) {
+ fprintf(stderr, "Unable to init libubi, is UBI present?\n");
+ exit(1);
+ }
+
+ if (ubi_get_dev_info(libubi, ubi_device, &dev_info)) {
+ fprintf(stderr, "Fatal: Could not get ubi info for %s\n", ubi_device);
+ exit(1);
+ }
+
+ libubi_close(libubi);
+
+ return dev_info.total_lebs;
+}
+
+static void init_prng(void)
+{
+ int ret, seed;
+
+ ret = getrandom(&seed, sizeof(seed), 0);
+ if (ret != sizeof(seed)) {
+ if (ret == -1)
+ fprintf(stderr, "Unable to get random seed: %m\n");
+ else
+ fprintf(stderr, "Unable to get %zi bytes random seed\n", sizeof(seed));
+
+ exit(1);
+ }
+ srand(seed);
+}
+
+int main (int argc, char *argv[])
+{
+ int c, i;
+
+ while ((c = getopt_long(argc, argv, opt_string, options, &i)) != -1) {
+ switch(c) {
+ case 'd': {
+ ubi_device = optarg;
+ break;
+ }
+ case 'i': {
+ interval_secs = atoi(optarg);
+ if (!interval_secs) {
+ fprintf(stderr, "Bad interval value! %s\n", optarg);
+ exit(1);
+ }
+ break;
+ }
+ case 'f': {
+ nodaemon = 1;
+ break;
+ }
+ case '?':
+ default:
+ fprintf(stderr, "Usage: %s [ -d UBI_DEVICE ] [-i INTERVAL_SEC ] [ -f ]\n", argv[0]);
+ exit(1);
+ break;
+ }
+ }
+
+ ubi_fd = open(ubi_device, O_RDONLY);
+ if (ubi_fd == -1) {
+ fprintf(stderr, "Fatal: Unable to open %s: %m\n", ubi_device);
+ exit(1);
+ }
+
+ init_prng();
+
+ peb_state_array_len = get_peb_count();
+ build_peb_list();
+
+ if (!nodaemon) {
+ if (daemon(0, 0) == -1) {
+ fprintf(stderr, "Unable to become a daemon: %m\n");
+ exit(1);
+ }
+ }
+
+ for (;;) {
+ process_one_peb();
+ sleep(interval_secs);
+ }
+
+ return 0;
+}