/*
* probe disks for filesystems and partitions
*
* Copyright (C) 2020 Sylvain BERTRAND <sylvain.bertrand@legeek.net>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include "udev.h"
#define loop for(;;)
#define u8 char
struct busybox_blkid_t {
char *type;
char *uuid;
char *label;
};
static void busybox_blkid_exec(struct udev_device *dev, int s)
{
struct udev *udev = udev_device_get_udev(dev);
const char *devnode;
int r;
devnode = udev_device_get_devnode(dev);
close(0);
r = dup2(s, 1);
if (r == -1) {
err(udev, "blkid:unable to duplicate socket filedescriptor on standart output\n");
exit(1);
}
loop {
errno = 0;
info(udev, "blkid:exec for %s\n", devnode);
execlp("blkid", "blkid", devnode, 0);
if (errno != EAGAIN) {
err(udev, "blkid:unable to exec\n");
exit(1);
}
/* EAGAIN */
}
/* unreachable */
}
static void busybox_blkid_output_field(char *buf, char *key_name,
char **value_dup)
{
char *key;
char *value;
char *value_end;
key = strstr(buf, key_name);
if (key == 0)
return;
value = strchr(key, '=');
if (value == 0)
return;
if (value[1] == 0 || value[1] != '"')
return;
value += 2;
value_end = strchr(value, '"');
if (value_end == 0)
return;
*value_end = 0;
*value_dup = strdup(value);
*value_end = '"';
}
static int busybox_blkid_output_parse(struct udev *udev, int s,
struct busybox_blkid_t *props)
{
u8 buf[BUFSIZ + 1]; /* zero terminating char in worst case scenario */
u8 *buf_end;
u8 *p_end;
buf_end = buf + BUFSIZ;
p_end = buf;
loop {
struct pollfd pfd;
int r;
pfd.fd = s;
pfd.events = POLLIN;
pfd.revents = 0;
errno = 0;
r = poll(&pfd, 1, 2000); /* timeout of 2 secs */
if (r == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
err(udev, "blkid:something went wrong while polling the unix socket\n");
return EXIT_FAILURE;
} else if (r == 0) {
err(udev, "blkid:timeout occured while polling the unix socket\n");
return EXIT_FAILURE;
}
/* r >= 0 */
if ((pfd.revents & POLLIN) != 0) {
loop {
errno = 0;
r = recv(s, p_end, buf_end - p_end, 0);
/*
* when dealing with sockets, you can get
* 0-length messages, just use the generalized
* case
*/
if (r >= 0) {
p_end += r;
break;
} else if (r == -1 && errno == EINTR)
continue;
/* r < -1 || (r == -1 && errno != EINTR) */
err(udev, "blkid:something went wrong while reading from the unix socket\n");
return EXIT_FAILURE;
}
if (p_end == buf_end)
break; /* try to process the buf data */
}
if ((pfd.revents & (POLLERR | POLLHUP)) != 0)
break; /* try to process the buf data */
}
*p_end = 0;
/*====================================================================*/
busybox_blkid_output_field(buf, "TYPE", &props->type);
info(udev, "blkid:extracted filesystem \"%s\"\n", props->type ? props->type : "unknown");
busybox_blkid_output_field(buf, "UUID", &props->uuid);
info(udev, "blkid:extracted uuid \"%s\"\n", props->uuid ? props->uuid : "unknown");
busybox_blkid_output_field(buf, "LABEL", &props->label);
info(udev, "blkid:extracted label \"%s\"\n", props->label ? props->label : "unknown");
return EXIT_SUCCESS;
}
static int busybox_blkid_fork(struct udev_device *dev,
struct busybox_blkid_t *props)
{
struct udev *udev = udev_device_get_udev(dev);
int r;
int parse_r;
int sv[2];
pid_t blkid_pid;
int blkid_status;
r = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
if (r == -1) {
err(udev, "blkid:unable to create a socket pair for invokation\n");
return EXIT_FAILURE;
}
blkid_pid = fork();
if (blkid_pid == -1) {
close(sv[0]);
close(sv[1]);
err(udev, "blkid:unable to fork in order to exec\n");
return EXIT_FAILURE;
} else if (blkid_pid == 0) {
close(sv[0]);
busybox_blkid_exec(dev, sv[1]);
/* unreachable */
}
close(sv[1]);
parse_r = busybox_blkid_output_parse(udev, sv[0], props);
close(sv[0]);
/*--------------------------------------------------------------------*/
r = waitpid(blkid_pid, &blkid_status, 0);
if (r == -1) {
err(udev, "blkid:unable to wait for on pid %d\n", blkid_pid);
return parse_r;
}
if (WIFEXITED(blkid_status)) {
if (WEXITSTATUS(blkid_status) != 0) {
err(udev, "blkid:exit status %d\n", WEXITSTATUS(blkid_status));
return EXIT_FAILURE;
}
/* WEXITSTATUS(blkid_status) == 0 */
return parse_r;
}
err(udev, "blkid:exited abnormally\n");
return EXIT_FAILURE;
}
static int builtin_blkid(struct udev_device *dev, int argc, char *argv[],
bool test)
{
int r;
struct busybox_blkid_t props;
memset(&props, 0, sizeof(props));
r = busybox_blkid_fork(dev, &props);
if (r == EXIT_FAILURE)
return EXIT_FAILURE;
if (props.type != 0) {
udev_builtin_add_property(dev, test, "ID_FS_TYPE", props.type);
free(props.type);
}
if (props.uuid != 0) {
udev_builtin_add_property(dev, test, "ID_FS_UUID", props.uuid);
free(props.uuid);
}
if (props.label != 0) {
udev_builtin_add_property(dev, test, "ID_FS_LABEL",
props.label);
free(props.label);
}
return EXIT_SUCCESS;
}
const struct udev_builtin udev_builtin_blkid = {
.name = "blkid",
.cmd = builtin_blkid,
.help = "filesystem and partition probing",
.run_once = true,
};