blob: 82efd58f7eafa7bba61d710ec5fc0c58474a90fb [file] [log] [blame]
/*
shared_main.c
This file is part of DRBD by Philipp Reisner and Lars Ellenberg.
Copyright (C) 2014, LINBIT HA Solutions GmbH.
drbd 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, or (at your option)
any later version.
drbd 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 drbd; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <search.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/sockios.h>
#include <linux/netdevice.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <time.h>
#include "drbd_endian.h"
#include "shared_main.h"
#include "shared_tool.h"
extern struct ifreq *ifreq_list;
struct d_globals global_options = {
.cmd_timeout_short = CMD_TIMEOUT_SHORT_DEF,
.cmd_timeout_medium = CMD_TIMEOUT_MEDIUM_DEF,
.cmd_timeout_medium = CMD_TIMEOUT_LONG_DEF,
.dialog_refresh = 1,
.usage_count = UC_ASK,
};
void chld_sig_hand(int __attribute((unused)) unused)
{
// do nothing. But interrupt systemcalls :)
}
unsigned minor_by_id(const char *id)
{
if (strncmp(id, "minor-", 6))
return -1U;
return m_strtoll(id + 6, 1);
}
/*
* I'd really rather parse the output of
* ip -o a s
* once, and be done.
* But anyways....
*/
struct ifreq *get_ifreq(void)
{
int sockfd, num_ifaces;
struct ifreq *ifr;
struct ifconf ifc;
size_t buf_size;
if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) {
perror("Cannot open socket");
exit(EXIT_FAILURE);
}
num_ifaces = 0;
ifc.ifc_req = NULL;
/* realloc buffer size until no overflow occurs */
do {
num_ifaces += 16; /* initial guess and increment */
buf_size = ++num_ifaces * sizeof(struct ifreq);
ifc.ifc_len = buf_size;
if (NULL == (ifc.ifc_req = realloc(ifc.ifc_req, ifc.ifc_len))) {
fprintf(stderr, "Out of memory.\n");
return NULL;
}
if (ioctl(sockfd, SIOCGIFCONF, &ifc)) {
perror("ioctl SIOCFIFCONF");
free(ifc.ifc_req);
return NULL;
}
} while (buf_size <= (size_t) ifc.ifc_len);
num_ifaces = ifc.ifc_len / sizeof(struct ifreq);
/* Since we allocated at least one more than necessary,
* this serves as a stop marker for the iteration in
* have_ip() */
ifc.ifc_req[num_ifaces].ifr_name[0] = 0;
for (ifr = ifc.ifc_req; ifr->ifr_name[0] != 0; ifr++) {
/* we only want to look up the presence or absence of a certain address
* here. but we want to skip "down" interfaces. if an interface is down,
* we store an invalid sa_family, so the lookup will skip it.
*/
struct ifreq ifr_for_flags = *ifr; /* get a copy to work with */
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr_for_flags) < 0) {
perror("ioctl SIOCGIFFLAGS");
ifr->ifr_addr.sa_family = -1; /* what's wrong here? anyways: skip */
continue;
}
if (!(ifr_for_flags.ifr_flags & IFF_UP)) {
ifr->ifr_addr.sa_family = -1; /* is not up: skip */
continue;
}
}
close(sockfd);
return ifc.ifc_req;
}
int have_ip_ipv4(const char *ip)
{
struct ifreq *ifr;
struct in_addr query_addr;
query_addr.s_addr = inet_addr(ip);
if (!ifreq_list)
ifreq_list = get_ifreq();
for (ifr = ifreq_list; ifr && ifr->ifr_name[0] != 0; ifr++) {
/* SIOCGIFCONF only supports AF_INET */
struct sockaddr_in *list_addr =
(struct sockaddr_in *)&ifr->ifr_addr;
if (ifr->ifr_addr.sa_family != AF_INET)
continue;
if (query_addr.s_addr == list_addr->sin_addr.s_addr)
return 1;
}
return 0;
}
int have_ip_ipv6(const char *ip)
{
FILE *if_inet6;
struct in6_addr addr6, query_addr;
unsigned int b[4];
char tmp_ip[INET6_ADDRSTRLEN+1];
char name[20]; /* IFNAMSIZ aka IF_NAMESIZE is 16 */
int i;
/* don't want to do getaddrinfo lookup, but inet_pton get's confused by
* %eth0 link local scope specifiers. So we have a temporary copy
* without that part. */
for (i=0; ip[i] && ip[i] != '%' && i < INET6_ADDRSTRLEN; i++)
tmp_ip[i] = ip[i];
tmp_ip[i] = 0;
if (inet_pton(AF_INET6, tmp_ip, &query_addr) <= 0)
return 0;
#define PROC_IF_INET6 "/proc/net/if_inet6"
if_inet6 = fopen(PROC_IF_INET6, "r");
if (!if_inet6) {
if (errno != ENOENT)
perror("open of " PROC_IF_INET6 " failed:");
#undef PROC_IF_INET6
return 0;
}
while (fscanf
(if_inet6,
X32(08) X32(08) X32(08) X32(08) " %*x %*x %*x %*x %s",
b, b + 1, b + 2, b + 3, name) > 0) {
for (i = 0; i < 4; i++)
addr6.s6_addr32[i] = cpu_to_be32(b[i]);
if (memcmp(&query_addr, &addr6, sizeof(struct in6_addr)) == 0) {
fclose(if_inet6);
return 1;
}
}
fclose(if_inet6);
return 0;
}
int have_ip(const char *af, const char *ip)
{
if (!strcmp(af, "ipv4"))
return have_ip_ipv4(ip);
else if (!strcmp(af, "ipv6"))
return have_ip_ipv6(ip);
return 1; /* SCI */
}
extern char *progname;
void substitute_deprecated_cmd(char **c, char *deprecated,
char *substitution)
{
if (!strcmp(*c, deprecated)) {
fprintf(stderr, "'%s %s' is deprecated, use '%s %s' instead.\n",
progname, deprecated, progname, substitution);
*c = substitution;
}
}
pid_t my_fork(void)
{
pid_t pid = -1;
int try;
for (try = 0; try < 10; try++) {
errno = 0;
pid = fork();
if (pid != -1 || errno != EAGAIN)
return pid;
err("fork: retry: Resource temporarily unavailable\n");
usleep(100 * 1000);
}
return pid;
}
void m__system(char **argv, int flags, const char *res_name, pid_t *kid, int *fd, int *ex)
{
pid_t pid;
int status, rv = -1;
int timeout = 0;
char **cmdline = argv;
int pipe_fds[2];
struct sigaction so;
struct sigaction sa;
if (flags & (RETURN_STDERR_FD | RETURN_STDOUT_FD))
assert(fd);
sa.sa_handler = &alarm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (dry_run || verbose) {
if (sh_varname && *cmdline)
printf("%s=%s\n", sh_varname,
res_name ? shell_escape(res_name) : "");
while (*cmdline) {
printf("%s ", shell_escape(*cmdline++));
}
printf("\n");
if (dry_run) {
if (kid)
*kid = -1;
if (fd)
*fd = -1;
if (ex)
*ex = 0;
return;
}
}
/* flush stdout and stderr, so output of drbdadm
* and helper binaries is reported in order! */
fflush(stdout);
fflush(stderr);
if (adjust_with_progress && !(flags & RETURN_STDERR_FD))
flags |= SUPRESS_STDERR;
/* create the pipe in any case:
* it helps the analyzer and later we have:
* '*fd = pipe_fds[0];' */
if (pipe(pipe_fds) < 0) {
perror("pipe");
fprintf(stderr, "Error in pipe, giving up.\n");
exit(E_EXEC_ERROR);
}
pid = my_fork();
if (pid == -1) {
fprintf(stderr, "Can not fork\n");
exit(E_EXEC_ERROR);
}
if (pid == 0) {
prctl(PR_SET_PDEATHSIG, SIGKILL);
/* Child: close reading end. */
close(pipe_fds[0]);
if (flags & RETURN_STDOUT_FD) {
dup2(pipe_fds[1], STDOUT_FILENO);
}
if (flags & RETURN_STDERR_FD) {
dup2(pipe_fds[1], STDERR_FILENO);
}
close(pipe_fds[1]);
if (flags & SUPRESS_STDERR) {
FILE *f = freopen("/dev/null", "w", stderr);
if (!f)
fprintf(stderr, "freopen(/dev/null) failed\n");
}
if (argv[0])
execvp(argv[0], argv);
fprintf(stderr, "Can not exec\n");
exit(E_EXEC_ERROR);
}
/* Parent process: close writing end. */
close(pipe_fds[1]);
if (flags & SLEEPS_FINITE) {
sigaction(SIGALRM, &sa, &so);
alarm_raised = 0;
switch (flags & SLEEPS_MASK) {
case SLEEPS_SHORT:
timeout = global_options.cmd_timeout_short;
break;
case SLEEPS_LONG:
timeout = global_options.cmd_timeout_medium;
break;
case SLEEPS_VERY_LONG:
timeout = global_options.cmd_timeout_long;
break;
default:
fprintf(stderr, "logic bug in %s:%d\n", __FILE__,
__LINE__);
exit(E_THINKO);
}
alarm(timeout);
}
if (kid)
*kid = pid;
if (flags & (RETURN_STDOUT_FD | RETURN_STDERR_FD)
|| flags == RETURN_PID) {
if (fd)
*fd = pipe_fds[0];
return;
}
while (1) {
if (waitpid(pid, &status, 0) == -1) {
if (errno != EINTR)
break;
if (alarm_raised) {
alarm(0);
sigaction(SIGALRM, &so, NULL);
rv = 0x100;
break;
} else {
fprintf(stderr, "logic bug in %s:%d\n",
__FILE__, __LINE__);
exit(E_EXEC_ERROR);
}
} else {
if (WIFEXITED(status)) {
rv = WEXITSTATUS(status);
break;
}
}
}
/* Do not close earlier, else the child gets EPIPE. */
close(pipe_fds[0]);
if (flags & SLEEPS_FINITE) {
if (rv >= 10
&& !(flags & (DONT_REPORT_FAILED | SUPRESS_STDERR))) {
fprintf(stderr, "Command '");
for (cmdline = argv; *cmdline; cmdline++) {
fprintf(stderr, "%s", *cmdline);
if (cmdline[1])
fputc(' ', stderr);
}
if (alarm_raised) {
fprintf(stderr,
"' did not terminate within %u seconds\n",
timeout);
exit(E_EXEC_ERROR);
} else {
fprintf(stderr,
"' terminated with exit code %d\n", rv);
}
}
}
fflush(stdout);
fflush(stderr);
if (ex)
*ex = rv;
}