Whamcloud - gitweb
LU-13238 ofd: add OFD access logs
[fs/lustre-release.git] / lustre / utils / ofd_access_log_reader.c
diff --git a/lustre/utils/ofd_access_log_reader.c b/lustre/utils/ofd_access_log_reader.c
new file mode 100644 (file)
index 0000000..6625241
--- /dev/null
@@ -0,0 +1,723 @@
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ *
+ * Copyright 2020, DataDirect Networks Storage.
+ *
+ * This file is part of Lustre, http://www.lustre.org/
+ *
+ * Author: John L. Hammond <jhammond@whamcloud.com>
+ *
+ * lustre/utils/ofd_access_log_reader.c
+ *
+ * Sample utility to discover and read Lustre (ofd) access logs.
+ *
+ * This demonstrates the discovery and reading of Lustre access logs
+ * (see linux/lustre/lustre_access_log.h and
+ * lustre/ofd/ofd_access_log.c.). By default it opens the control
+ * device, discovers and opens all access log devices, and consumes
+ * all access log entries. If invoked with the --list option then it
+ * prints information about all available devices to stdout and exits.
+ *
+ * Structured trace points (when --trace is used) are added to permit
+ * testing of the access log functionality (see test_165* in
+ * lustre/tests/sanity.sh).
+ */
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <malloc.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/signalfd.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <linux/types.h>
+#include <linux/lustre/lustre_user.h>
+#include <linux/lustre/lustre_access_log.h>
+#include "lstddef.h"
+
+/* TODO fsname filter */
+
+static FILE *debug_file;
+static FILE *trace_file;
+
+#define DEBUG(fmt, args...)                                            \
+       do {                                                            \
+               if (debug_file != NULL)                                 \
+                       fprintf(debug_file, "DEBUG %s:%d: "fmt, __func__, __LINE__, ##args); \
+       } while (0)
+
+#define TRACE(fmt, args...)                                            \
+       do {                                                            \
+               if (trace_file != NULL)                                 \
+                       fprintf(trace_file, "TRACE "fmt, ##args);       \
+       } while (0)
+
+#define DEBUG_D(x) DEBUG("%s = %d\n", #x, x)
+#define DEBUG_P(x) DEBUG("%s = %p\n", #x, x)
+#define DEBUG_S(x) DEBUG("%s = '%s'\n", #x, x)
+#define DEBUG_U(x) DEBUG("%s = %u\n", #x, x)
+#define DEBUG_U32(x) DEBUG("%s = %"PRIu32"\n", #x, x)
+#define DEBUG_U64(x) DEBUG("%s = %"PRIu64"\n", #x, x)
+
+#define ERROR(fmt, args...) \
+       fprintf(stderr, "%s: "fmt, program_invocation_short_name, ##args)
+
+#define FATAL(fmt, args...)                    \
+       do {                                    \
+               ERROR("FATAL: "fmt, ##args);    \
+               exit(EXIT_FAILURE);             \
+       } while (0)
+
+enum {
+       ALR_EXIT_SUCCESS = INT_MIN + EXIT_SUCCESS,
+       ALR_EXIT_FAILURE = INT_MIN + EXIT_FAILURE,
+       ALR_ERROR = -1,
+       ALR_EOF = 0,
+       ALR_OK = 1,
+};
+
+struct alr_dev {
+       char *alr_name;
+       int (*alr_io)(int /* epoll_fd */, struct alr_dev * /* this */, unsigned int /* mask */);
+       void (*alr_destroy)(struct alr_dev *);
+       int alr_fd;
+};
+
+struct alr_log {
+       struct alr_dev alr_dev;
+       char *alr_buf;
+       size_t alr_buf_size;
+       size_t alr_entry_size;
+       dev_t alr_rdev;
+};
+
+static struct alr_log *alr_log[1 << 20]; /* 20 == MINORBITS */
+static int oal_version; /* FIXME ... major version, minor version */
+static unsigned int oal_log_major;
+static unsigned int oal_log_minor_max;
+
+#define D_ALR_DEV "%s %d"
+#define P_ALR_DEV(ad) \
+       (ad)->alr_name, (ad)->alr_fd
+
+#define D_ALR_LOG D_ALR_DEV" %u:%u"
+#define P_ALR_LOG(al) \
+       P_ALR_DEV(&(al)->alr_dev), major((al)->alr_rdev), minor((al)->alr_rdev)
+
+static void alr_dev_free(int epoll_fd, struct alr_dev *ad)
+{
+       TRACE("alr_dev_free %s\n", ad->alr_name);
+
+       if (!(ad->alr_fd < 0))
+               epoll_ctl(epoll_fd, EPOLL_CTL_DEL, ad->alr_fd, NULL);
+
+       if (ad->alr_destroy != NULL)
+               (*ad->alr_destroy)(ad);
+
+       if (!(ad->alr_fd < 0))
+               close(ad->alr_fd);
+
+       free(ad->alr_name);
+       free(ad);
+}
+
+static struct alr_log **alr_log_lookup(dev_t rdev)
+{
+       assert(major(rdev) == oal_log_major);
+
+       if (!(minor(rdev) < ARRAY_SIZE(alr_log)))
+               return NULL;
+
+       return &alr_log[minor(rdev)];
+}
+
+static const char *alr_flags_to_str(unsigned int flags)
+{
+       switch (flags & (OFD_ACCESS_READ | OFD_ACCESS_WRITE)) {
+       default:
+               return "0";
+       case OFD_ACCESS_READ:
+               return "r";
+       case OFD_ACCESS_WRITE:
+               return "w";
+       case OFD_ACCESS_READ | OFD_ACCESS_WRITE:
+               return "rw";
+       }
+}
+
+/* /dev/lustre-access-log/scratch-OST0000 device poll callback: read entries
+ * from log and print. */
+static int alr_log_io(int epoll_fd, struct alr_dev *ad, unsigned int mask)
+{
+       struct alr_log *al = container_of(ad, struct alr_log, alr_dev);
+       ssize_t i, count;
+
+       TRACE("alr_log_io %s\n", ad->alr_name);
+       DEBUG_U(mask);
+
+       assert(al->alr_entry_size != 0);
+       assert(al->alr_buf_size != 0);
+       assert(al->alr_buf != NULL);
+
+       count = read(ad->alr_fd, al->alr_buf, al->alr_buf_size);
+       if (count < 0) {
+               ERROR("cannot read events from '%s': %s\n", ad->alr_name, strerror(errno));
+               return ALR_ERROR;
+       }
+
+       if (count == 0) {
+               TRACE("alr_log_eof %s\n", ad->alr_name);
+               return ALR_EOF;
+       }
+
+       if (count % al->alr_entry_size != 0) {
+               ERROR("invalid read from "D_ALR_LOG": entry_size = %zu, count = %zd\n",
+                       P_ALR_LOG(al), al->alr_entry_size, count);
+               return ALR_ERROR;
+       }
+
+       DEBUG("read "D_ALR_LOG", count = %zd\n", P_ALR_LOG(al), count);
+
+       for (i = 0; i < count; i += al->alr_entry_size) {
+               struct ofd_access_entry_v1 *oae =
+                       (struct ofd_access_entry_v1 *)&al->alr_buf[i];
+
+               TRACE("alr_log_entry %s "DFID" %lu %lu %lu %u %u %s\n",
+                       ad->alr_name,
+                       PFID(&oae->oae_parent_fid),
+                       (unsigned long)oae->oae_begin,
+                       (unsigned long)oae->oae_end,
+                       (unsigned long)oae->oae_time,
+                       (unsigned int)oae->oae_size,
+                       (unsigned int)oae->oae_segment_count,
+                       alr_flags_to_str(oae->oae_flags));
+       }
+
+       return ALR_OK;
+}
+
+static void alr_log_destroy(struct alr_dev *ad)
+{
+       struct alr_log *al = container_of(ad, struct alr_log, alr_dev);
+       struct alr_log **pal;
+
+       TRACE("alr_log_free %s\n", ad->alr_name);
+       assert(major(al->alr_rdev) == oal_log_major);
+
+       pal = alr_log_lookup(al->alr_rdev);
+       if (pal != NULL && *pal == al)
+               *pal = NULL;
+
+       free(al->alr_buf);
+       al->alr_buf = NULL;
+       al->alr_buf_size = 0;
+}
+
+/* Add an access log (identified by path) to the epoll set. */
+static int alr_log_add(int epoll_fd, const char *path)
+{
+       struct alr_log **pal, *al = NULL;
+       struct stat st;
+       int fd = -1;
+       int rc;
+
+       fd = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC);
+       if (fd < 0) {
+               ERROR("cannot open device '%s': %s\n", path, strerror(errno));
+               rc = (errno == ENOENT ? 0 : -1); /* Possible race. */
+               goto out;
+       }
+
+       /* Revalidate rdev in case of race. */
+       rc = fstat(fd, &st);
+       if (rc < 0) {
+               ERROR("cannot stat '%s': %s\n", path, strerror(errno));
+               goto out;
+       }
+
+       if (major(st.st_rdev) != oal_log_major)
+               goto out;
+
+       pal = alr_log_lookup(st.st_rdev);
+       if (pal == NULL) {
+               ERROR("no device slot available for '%s' with minor %u\n",
+                       path, minor(st.st_rdev));
+               goto out;
+       }
+
+       if (*pal != NULL)
+               goto out; /* We already have this device. */
+
+       struct lustre_access_log_info_v1 lali;
+
+       memset(&lali, 0, sizeof(lali));
+
+       rc = ioctl(fd, LUSTRE_ACCESS_LOG_IOCTL_INFO, &lali);
+       if (rc < 0) {
+               ERROR("cannot get info for device '%s': %s\n",
+                       path, strerror(errno));
+               goto out;
+       }
+
+       if (lali.lali_type != LUSTRE_ACCESS_LOG_TYPE_OFD) {
+               rc = 0;
+               goto out;
+       }
+
+       al = calloc(1, sizeof(*al));
+       if (al == NULL)
+               FATAL("cannot allocate struct alr_dev of size %zu: %s\n",
+                       sizeof(*al), strerror(errno));
+
+       al->alr_dev.alr_io = &alr_log_io;
+       al->alr_dev.alr_destroy = &alr_log_destroy;
+       al->alr_dev.alr_fd = fd;
+       fd = -1;
+
+       al->alr_rdev = st.st_rdev;
+
+       al->alr_dev.alr_name = strdup(lali.lali_name);
+       if (al->alr_dev.alr_name == NULL)
+               FATAL("cannot copy name of size %zu: %s\n",
+                       strlen(lali.lali_name), strerror(errno));
+
+       al->alr_buf_size = lali.lali_log_size;
+       al->alr_entry_size = lali.lali_entry_size;
+
+       if (al->alr_entry_size == 0) {
+               ERROR("device '%s' has zero entry size\n", path);
+               rc = -1;
+               goto out;
+       }
+
+       if (al->alr_buf_size == 0)
+               al->alr_buf_size = 1048576;
+
+       al->alr_buf_size = roundup(al->alr_buf_size, al->alr_entry_size);
+
+       al->alr_buf = malloc(al->alr_buf_size);
+       if (al->alr_buf == NULL)
+               FATAL("cannot allocate log buffer for '%s' of size %zu: %s\n",
+                       path, al->alr_buf_size, strerror(errno));
+
+       struct epoll_event ev = {
+               .events = EPOLLIN | EPOLLHUP,
+               .data.ptr = &al->alr_dev,
+       };
+
+       rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, al->alr_dev.alr_fd, &ev);
+       if (rc < 0) {
+               ERROR("cannot add device '%s' to epoll set: %s\n",
+                       path, strerror(errno));
+               goto out;
+       }
+
+       TRACE("alr_log_add %s\n", al->alr_dev.alr_name);
+
+       if (oal_log_minor_max < minor(al->alr_rdev))
+               oal_log_minor_max = minor(al->alr_rdev);
+
+       assert(*pal == NULL);
+       *pal = al;
+       al = NULL;
+       rc = 0;
+out:
+       if (al != NULL)
+               alr_dev_free(epoll_fd, &al->alr_dev);
+
+       if (!(fd < 0))
+               close(fd);
+
+       return rc;
+}
+
+/* Scan /dev/lustre-access-log/ for new access log devices and add to
+ * epoll set. */
+static int alr_scan(int epoll_fd)
+{
+       const char dir_path[] = "/dev/"LUSTRE_ACCESS_LOG_DIR_NAME;
+       DIR *dir;
+       int dir_fd;
+       struct dirent *d;
+       int rc;
+
+       dir = opendir(dir_path);
+       if (dir == NULL) {
+               ERROR("cannot open '%s' for scanning: %s\n", dir_path, strerror(errno));
+               return ALR_EXIT_FAILURE;
+       }
+
+       dir_fd = dirfd(dir);
+
+       /* Scan /dev for devices with major equal to oal_log_major and add
+        * any new devices. */
+       while ((d = readdir(dir)) != NULL) {
+               char path[6 + PATH_MAX];
+               struct alr_log **pal;
+               struct stat st;
+
+               if (d->d_type != DT_CHR)
+                       continue;
+
+               rc = fstatat(dir_fd, d->d_name, &st, 0);
+               if (rc < 0) {
+                       ERROR("cannot stat '%s/%s' while scanning: %s\n",
+                               dir_path, d->d_name, strerror(errno));
+                       continue;
+               }
+
+               if (!S_ISCHR(st.st_mode))
+                       continue;
+
+               if (major(st.st_rdev) != oal_log_major)
+                       continue;
+
+               pal = alr_log_lookup(st.st_rdev);
+               if (pal == NULL) {
+                       ERROR("no device slot available for '%s/%s' with minor %u\n",
+                               dir_path, d->d_name, minor(st.st_rdev));
+                       continue;
+               }
+
+               if (*pal != NULL)
+                       continue; /* We already have this device. */
+
+               snprintf(path, sizeof(path), "%s/%s", dir_path, d->d_name);
+
+               alr_log_add(epoll_fd, path);
+       }
+
+       closedir(dir);
+
+       return ALR_OK;
+}
+
+/* /dev/lustre-access-log/control device poll callback: call prescan
+ * ioctl and scan /dev/lustre-access-log/ for new access log
+ * devices. */
+static int alr_ctl_io(int epoll_fd, struct alr_dev *cd, unsigned int mask)
+{
+       int rc;
+
+       TRACE("%s\n", __func__);
+       DEBUG_U(mask);
+
+       if (mask & EPOLLERR)
+               return ALR_EXIT_FAILURE;
+
+       if (mask & EPOLLHUP)
+               return ALR_EXIT_SUCCESS;
+
+       rc = ioctl(cd->alr_fd, LUSTRE_ACCESS_LOG_IOCTL_PRESCAN);
+       if (rc < 0) {
+               ERROR("cannot start scanning: %s\n", strerror(errno));
+               return ALR_EXIT_FAILURE;
+       }
+
+       return alr_scan(epoll_fd);
+}
+
+/* signalfd epoll callback. Handle SIGINT and SIGTERM by breaking from
+ * the epoll loop and exiting normally.*/
+static int alr_signal_io(int epoll_fd, struct alr_dev *sd, unsigned int mask)
+{
+       struct signalfd_siginfo ssi;
+       ssize_t rc;
+
+       TRACE("%s\n", __func__);
+       DEBUG_U(mask);
+
+       rc = read(sd->alr_fd, &ssi, sizeof(ssi));
+       if (rc <= 0)
+               return ALR_OK;
+
+       DEBUG_U32(ssi.ssi_signo);
+       switch (ssi.ssi_signo) {
+       case SIGINT:
+       case SIGTERM:
+               return ALR_EXIT_SUCCESS;
+       default:
+               return ALR_OK;
+       }
+}
+
+/* Call LUSTRE_ACCESS_LOG_IOCTL_INFO to get access log info and print
+ * YAML formatted info to stdout. */
+static int alr_log_info(struct alr_log *al)
+{
+       struct lustre_access_log_info_v1 lali;
+       int rc;
+
+       rc = ioctl(al->alr_dev.alr_fd, LUSTRE_ACCESS_LOG_IOCTL_INFO, &lali);
+       if (rc < 0) {
+               ERROR("cannot get info for device '%s': %s\n",
+                       al->alr_dev.alr_name, strerror(errno));
+               return -1;
+       }
+
+       printf("- name: %s\n"
+              "  version: %#x\n"
+              "  type: %#x\n"
+              "  log_size: %u\n"
+              "  entry_size: %u\n"
+              "  _head: %u\n"
+              "  _tail: %u\n"
+              "  _entry_space: %u\n"
+              "  _entry_count: %u\n"
+              "  _drop_count: %u\n"
+              "  _is_closed: %u\n",
+              lali.lali_name,
+              lali.lali_version,
+              lali.lali_type,
+              lali.lali_log_size,
+              lali.lali_entry_size,
+              lali._lali_head,
+              lali._lali_tail,
+              lali._lali_entry_space,
+              lali._lali_entry_count,
+              lali._lali_drop_count,
+              lali._lali_is_closed);
+
+       return 0;
+}
+
+static struct alr_dev *alr_dev_create(int epoll_fd, int fd, const char *name,
+                       int (*io)(int, struct alr_dev *, unsigned int),
+                       void (*destroy)(struct alr_dev *))
+{
+       struct alr_dev *alr;
+       int rc;
+
+       alr = calloc(1, sizeof(*alr));
+       if (alr == NULL)
+               return NULL;
+
+       alr->alr_name = strdup(name);
+       if (alr->alr_name == NULL) {
+               free(alr);
+               return NULL;
+       }
+       alr->alr_io = io;
+       alr->alr_destroy = destroy;
+       alr->alr_fd = fd;
+
+       struct epoll_event event = {
+               .events = EPOLLIN | EPOLLHUP,
+               .data.ptr = alr,
+       };
+
+       rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, alr->alr_fd, &event);
+       if (rc < 0) {
+               free(alr);
+               return NULL;
+       }
+
+       return alr;
+}
+
+int main(int argc, char *argv[])
+{
+       const char ctl_path[] = "/dev/"LUSTRE_ACCESS_LOG_DIR_NAME"/control";
+       struct alr_dev *alr_signal = NULL;
+       struct alr_dev *alr_ctl = NULL;
+       unsigned int m;
+       int list_info = 0;
+       int epoll_fd = -1;
+       int signal_fd = -1;
+       int ctl_fd = -1;
+       int exit_status;
+       int rc;
+       int c;
+
+       static struct option options[] = {
+               { .name = "debug", .has_arg = optional_argument, .val = 'd', },
+               { .name = "help", .has_arg = no_argument, .val = 'h', },
+               { .name = "list", .has_arg = no_argument, .val = 'l', },
+               { .name = "trace", .has_arg = optional_argument, .val = 't', },
+               { .name = NULL, },
+       };
+
+       while ((c = getopt_long(argc, argv, "d::hlt::", options, NULL)) != -1) {
+               switch (c) {
+               case 'd':
+                       if (optarg == NULL) {
+                               debug_file = stderr;
+                       } else if (strcmp(optarg, "-") == 0) {
+                               debug_file = stdout;
+                       } else {
+                               debug_file = fopen(optarg, "a");
+                               if (debug_file == NULL)
+                                       FATAL("cannot open debug file '%s': %s\n",
+                                               optarg, strerror(errno));
+                       }
+
+                       break;
+               case 'h':
+                       /* ... */
+                       exit(EXIT_SUCCESS);
+               case 'l':
+                       list_info = 1;
+                       break;
+               case 't':
+                       if (optarg == NULL) {
+                               trace_file = stderr;
+                       } else if (strcmp(optarg, "-") == 0) {
+                               trace_file = stdout;
+                       } else {
+                               trace_file = fopen(optarg, "a");
+                               if (debug_file == NULL)
+                                       FATAL("cannot open debug file '%s': %s\n",
+                                               optarg, strerror(errno));
+                       }
+
+                       break;
+               case '?':
+                       /* Try ... for more ... */
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+       if (epoll_fd < 0)
+               FATAL("cannot create epoll set: %s\n", strerror(errno));
+
+       /* Setup signal FD and add to epoll set. */
+       sigset_t signal_mask;
+       sigemptyset(&signal_mask);
+       sigaddset(&signal_mask, SIGINT);
+       sigaddset(&signal_mask, SIGTERM);
+       rc = sigprocmask(SIG_BLOCK, &signal_mask, NULL);
+       if (rc < 0)
+               FATAL("cannot set process signal mask: %s\n", strerror(errno));
+
+       signal_fd = signalfd(-1, &signal_mask, SFD_NONBLOCK|SFD_CLOEXEC);
+       if (signal_fd < 0)
+               FATAL("cannot create signalfd: %s\n", strerror(errno));
+
+       alr_signal = alr_dev_create(epoll_fd, signal_fd, "signal", &alr_signal_io, NULL);
+       if (alr_signal == NULL)
+               FATAL("cannot register signalfd: %s\n", strerror(errno));
+
+       signal_fd = -1;
+
+       /* Open control device. */
+       ctl_fd = open(ctl_path, O_RDONLY|O_NONBLOCK|O_CLOEXEC);
+       if (ctl_fd < 0)
+               FATAL("cannot open '%s': %s\n", ctl_path, strerror(errno));
+
+       /* Get and print interface version. */
+       oal_version = ioctl(ctl_fd, LUSTRE_ACCESS_LOG_IOCTL_VERSION);
+       if (oal_version < 0)
+               FATAL("cannot get ofd access log interface version: %s\n", strerror(errno));
+
+       DEBUG_D(oal_version);
+
+       /* Get and print device major used for access log devices. */
+       oal_log_major = ioctl(ctl_fd, LUSTRE_ACCESS_LOG_IOCTL_MAJOR);
+       if (oal_log_major < 0)
+               FATAL("cannot get ofd access log major: %s\n", strerror(errno));
+
+       DEBUG_D(oal_log_major);
+
+       /* Add control device to epoll set. */
+       alr_ctl = alr_dev_create(epoll_fd, ctl_fd, "control", &alr_ctl_io, NULL);
+       if (alr_ctl == NULL)
+               FATAL("cannot register control device: %s\n", strerror(errno));
+
+       ctl_fd = -1;
+
+       do {
+               struct epoll_event ev[32];
+               int timeout = (list_info ? 0 : -1);
+               int i, ev_count;
+
+               ev_count = epoll_wait(epoll_fd, ev, ARRAY_SIZE(ev), timeout);
+               if (ev_count < 0) {
+                       if (errno == EINTR) /* Signal or timeout. */
+                               continue;
+
+                       ERROR("cannot wait on epoll set: %s\n", strerror(errno));
+                       exit_status = EXIT_FAILURE;
+                       goto out;
+               }
+
+               DEBUG_D(ev_count);
+
+               for (i = 0; i < ev_count; i++) {
+                       struct alr_dev *ad = ev[i].data.ptr;
+                       unsigned int mask = ev[i].events;
+
+                       rc = (*ad->alr_io)(epoll_fd, ad, mask);
+                       switch (rc) {
+                       case ALR_EXIT_FAILURE:
+                               exit_status = EXIT_FAILURE;
+                               goto out;
+                       case ALR_EXIT_SUCCESS:
+                               exit_status = EXIT_SUCCESS;
+                               goto out;
+                       case ALR_ERROR:
+                       case ALR_EOF:
+                               alr_dev_free(epoll_fd, ad);
+                               break;
+                       case ALR_OK:
+                       default:
+                               break;
+                       }
+               }
+       } while (!list_info);
+
+       exit_status = EXIT_SUCCESS;
+out:
+       assert(oal_log_minor_max < ARRAY_SIZE(alr_log));
+
+       for (m = 0; m <= oal_log_minor_max; m++) {
+               if (alr_log[m] == NULL)
+                       continue;
+
+               if (list_info) {
+                       rc = alr_log_info(alr_log[m]);
+                       if (rc < 0)
+                               exit_status = EXIT_FAILURE;
+               }
+
+               alr_dev_free(epoll_fd, &alr_log[m]->alr_dev);
+       }
+
+       alr_dev_free(epoll_fd, alr_ctl);
+       alr_dev_free(epoll_fd, alr_signal);
+       close(epoll_fd);
+
+       DEBUG_D(exit_status);
+
+       return exit_status;
+}