| /* |
| * mdadm - manage Linux "md" devices aka RAID arrays. |
| * |
| * Copyright (C) 2022 Mateusz Grzonka <mateusz.grzonka@intel.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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 for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include "mdadm.h" |
| #include "udev.h" |
| #include "md_p.h" |
| #include "md_u.h" |
| #include <sys/wait.h> |
| #include <signal.h> |
| #include <limits.h> |
| #include <syslog.h> |
| |
| #ifndef NO_LIBUDEV |
| #include <libudev.h> |
| #endif |
| |
| static char *unblock_path; |
| |
| /* |
| * udev_is_available() - Checks for udev in the system. |
| * |
| * Function looks whether udev directories are available and MDADM_NO_UDEV env defined. |
| * |
| * Return: |
| * true if udev is available, |
| * false if not |
| */ |
| bool udev_is_available(void) |
| { |
| struct stat stb; |
| |
| if (stat("/dev/.udev", &stb) != 0 && |
| stat("/run/udev", &stb) != 0) |
| return false; |
| if (check_env("MDADM_NO_UDEV") == 1) |
| return false; |
| return true; |
| } |
| |
| #ifndef NO_LIBUDEV |
| |
| static struct udev *udev; |
| static struct udev_monitor *udev_monitor; |
| |
| /* |
| * udev_release() - Drops references of udev and udev_monitor. |
| */ |
| static void udev_release(void) |
| { |
| udev_monitor_unref(udev_monitor); |
| udev_unref(udev); |
| } |
| |
| /* |
| * udev_initialize() - Initializes udev and udev_monitor structures. |
| * |
| * Function initializes udev, udev_monitor, and sets udev_monitor filter for block devices. |
| * |
| * Return: |
| * UDEV_STATUS_SUCCESS on success |
| * UDEV_STATUS_ERROR on error |
| * UDEV_STATUS_ERROR_NO_UDEV when udev not available |
| */ |
| static enum udev_status udev_initialize(void) |
| { |
| if (!udev_is_available()) { |
| pr_err("No udev.\n"); |
| return UDEV_STATUS_ERROR_NO_UDEV; |
| } |
| |
| udev = udev_new(); |
| if (!udev) { |
| pr_err("Cannot initialize udev.\n"); |
| return UDEV_STATUS_ERROR; |
| } |
| |
| udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); |
| if (!udev_monitor) { |
| pr_err("Cannot initialize udev monitor.\n"); |
| udev = udev_unref(udev); |
| return UDEV_STATUS_ERROR; |
| } |
| |
| if (udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "block", 0) < 0) { |
| pr_err("Cannot add udev monitor event filter for md devices.\n"); |
| udev_release(); |
| return UDEV_STATUS_ERROR; |
| } |
| if (udev_monitor_enable_receiving(udev_monitor) < 0) { |
| pr_err("Cannot enable receiving udev events through udev monitor.\n"); |
| udev_release(); |
| return UDEV_STATUS_ERROR; |
| } |
| atexit(udev_release); |
| return UDEV_STATUS_SUCCESS; |
| } |
| |
| /* |
| * udev_wait_for_events() - Waits for events from udev. |
| * @seconds: Timeout in seconds. |
| * |
| * Function waits udev events, wakes up on event or timeout. |
| * |
| * Return: |
| * UDEV_STATUS_SUCCESS on detected event |
| * UDEV_STATUS_TIMEOUT on timeout |
| * UDEV_STATUS_ERROR on error |
| */ |
| enum udev_status udev_wait_for_events(int seconds) |
| { |
| int fd; |
| fd_set readfds; |
| struct timeval tv; |
| int ret; |
| |
| if (!udev || !udev_monitor) { |
| ret = udev_initialize(); |
| if (ret != UDEV_STATUS_SUCCESS) |
| return ret; |
| } |
| |
| fd = udev_monitor_get_fd(udev_monitor); |
| if (fd < 0) { |
| pr_err("Cannot access file descriptor associated with udev monitor.\n"); |
| return UDEV_STATUS_ERROR; |
| } |
| |
| FD_ZERO(&readfds); |
| FD_SET(fd, &readfds); |
| tv.tv_sec = seconds; |
| tv.tv_usec = 0; |
| |
| if (select(fd + 1, &readfds, NULL, NULL, &tv) > 0 && FD_ISSET(fd, &readfds)) |
| if (udev_monitor_receive_device(udev_monitor)) |
| return UDEV_STATUS_SUCCESS; /* event detected */ |
| return UDEV_STATUS_TIMEOUT; |
| } |
| #endif |
| |
| /* |
| * udev_block() - Block udev from examining newly created arrays. |
| * |
| * When array is created, we don't want udev to examine it immediately. |
| * Function creates /run/mdadm/creating-mdXXX and expects that udev rule |
| * will notice it and act accordingly. |
| * |
| * Return: |
| * UDEV_STATUS_SUCCESS when successfully blocked udev |
| * UDEV_STATUS_ERROR on error |
| */ |
| enum udev_status udev_block(char *devnm) |
| { |
| int fd; |
| char *path = xcalloc(1, BUFSIZ); |
| |
| snprintf(path, BUFSIZ, "/run/mdadm/creating-%s", devnm); |
| |
| fd = open(path, O_CREAT | O_RDWR, 0600); |
| if (!is_fd_valid(fd)) { |
| pr_err("Cannot block udev, error creating blocking file.\n"); |
| pr_err("%s: %s\n", strerror(errno), path); |
| free(path); |
| return UDEV_STATUS_ERROR; |
| } |
| |
| close(fd); |
| unblock_path = path; |
| return UDEV_STATUS_SUCCESS; |
| } |
| |
| /* |
| * udev_unblock() - Unblock udev. |
| */ |
| void udev_unblock(void) |
| { |
| if (unblock_path) |
| unlink(unblock_path); |
| free(unblock_path); |
| unblock_path = NULL; |
| } |