| /* |
| drbdadm_usage_cnt.c |
| |
| This file is part of DRBD by Philipp Reisner and Lars Ellenberg. |
| |
| Copyright (C) 2006-2008, LINBIT Information Technologies GmbH |
| Copyright (C) 2006-2008, Philipp Reisner <philipp.reisner@linbit.com> |
| Copyright (C) 2006-2008, Lars Ellenberg <lars.ellenberg@linbit.com> |
| |
| 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. |
| |
| */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <setjmp.h> |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| |
| #include <arpa/inet.h> |
| #include <netinet/in.h> |
| #include <netdb.h> |
| |
| #include "drbdadm.h" |
| #include "drbdtool_common.h" |
| #include "drbd_endian.h" |
| #include "linux/drbd.h" /* only use DRBD_MAGIC from here! */ |
| #include "config_flags.h" |
| |
| #define HTTP_PORT 80 |
| #define HTTP_HOST "usage.drbd.org" |
| #define HTTP_ADDR "212.69.161.111" |
| #define NODE_ID_FILE DRBD_LIB_DIR"/node_id" |
| |
| |
| struct node_info { |
| uint64_t node_uuid; |
| struct version rev; |
| }; |
| |
| struct node_info_od { |
| uint32_t magic; |
| struct node_info ni; |
| } __packed; |
| |
| |
| |
| void maybe_exec_legacy_drbdadm(char **argv) |
| { |
| const struct version *driver_version = drbd_driver_version(FALLBACK_TO_UTILS); |
| |
| if (driver_version->version.major == 8 && |
| driver_version->version.minor == 3) { |
| #ifdef DRBD_LEGACY_83 |
| /* This drbdadm warned already... */ |
| setenv("DRBD_DONT_WARN_ON_VERSION_MISMATCH", "1", 0); |
| add_lib_drbd_to_path(); |
| execvp(drbdadm_83, argv); |
| err("execvp() failed to exec %s: %m\n", drbdadm_83); |
| #else |
| config_help_legacy("drbdadm", driver_version); |
| #endif |
| exit(E_EXEC_ERROR); |
| } |
| if (driver_version->version.major == 8 && |
| driver_version->version.minor == 4) { |
| #ifdef DRBD_LEGACY_84 |
| /* This drbdadm warned already... */ |
| setenv("DRBD_DONT_WARN_ON_VERSION_MISMATCH", "1", 0); |
| add_lib_drbd_to_path(); |
| execvp(drbdadm_84, argv); |
| err("execvp() failed to exec %s: %m\n", drbdadm_84); |
| #else |
| config_help_legacy("drbdadm", driver_version); |
| #endif |
| exit(E_EXEC_ERROR); |
| } |
| } |
| |
| static char *vcs_to_str(struct version *rev) |
| { |
| static char buffer[80]; // Not generic, sufficient for the purpose. |
| |
| if( rev->svn_revision ) { |
| snprintf(buffer,80,"nv="U32,rev->svn_revision); |
| } else { |
| int len=20,p; |
| unsigned char *bytes; |
| |
| p = sprintf(buffer,"git="); |
| bytes = (unsigned char*)rev->git_hash; |
| while(len--) p += sprintf(buffer+p,"%02x",*bytes++); |
| } |
| return buffer; |
| } |
| |
| static void write_node_id(struct node_info *ni) |
| { |
| int fd; |
| struct node_info_od on_disk; |
| int size; |
| |
| fd = open(NODE_ID_FILE,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR); |
| if( fd == -1 && errno == ENOENT) { |
| mkdir(DRBD_LIB_DIR,S_IRWXU); |
| fd = open(NODE_ID_FILE,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR); |
| } |
| |
| if( fd == -1) { |
| err("Creation of "NODE_ID_FILE" failed: %m\n"); |
| exit(20); |
| } |
| |
| if(ni->rev.svn_revision != 0) { // SVN style (old) |
| on_disk.magic = cpu_to_be32(DRBD_MAGIC); |
| on_disk.ni.node_uuid = cpu_to_be64(ni->node_uuid); |
| on_disk.ni.rev.svn_revision = cpu_to_be32(ni->rev.svn_revision); |
| memset(on_disk.ni.rev.git_hash,0,GIT_HASH_BYTE); |
| size = SVN_STYLE_OD; |
| } else { |
| on_disk.magic = cpu_to_be32(DRBD_MAGIC+1); |
| on_disk.ni.node_uuid = cpu_to_be64(ni->node_uuid); |
| on_disk.ni.rev.svn_revision = 0; |
| memcpy(on_disk.ni.rev.git_hash,ni->rev.git_hash,GIT_HASH_BYTE); |
| size = sizeof(on_disk); |
| } |
| |
| if( write(fd,&on_disk, size) != size) { |
| err("Write to "NODE_ID_FILE" failed: %m\n"); |
| exit(20); |
| } |
| |
| close(fd); |
| } |
| |
| |
| static int read_node_id(struct node_info *ni) |
| { |
| int rr; |
| int fd; |
| struct node_info_od on_disk; |
| |
| fd = open(NODE_ID_FILE, O_RDONLY); |
| if (fd == -1) { |
| return 0; |
| } |
| |
| rr = read(fd, &on_disk, sizeof(on_disk)); |
| if (rr != sizeof(on_disk) && rr != SVN_STYLE_OD) { |
| close(fd); |
| return 0; |
| } |
| |
| switch (be32_to_cpu(on_disk.magic)) { |
| case DRBD_MAGIC: |
| ni->node_uuid = be64_to_cpu(on_disk.ni.node_uuid); |
| ni->rev.svn_revision = be32_to_cpu(on_disk.ni.rev.svn_revision); |
| memset(ni->rev.git_hash, 0, GIT_HASH_BYTE); |
| break; |
| case DRBD_MAGIC+1: |
| ni->node_uuid = be64_to_cpu(on_disk.ni.node_uuid); |
| ni->rev.svn_revision = 0; |
| memcpy(ni->rev.git_hash, on_disk.ni.rev.git_hash, GIT_HASH_BYTE); |
| break; |
| default: |
| return 0; |
| } |
| |
| close(fd); |
| return 1; |
| } |
| |
| /* What we probably should do is use getaddrinfo_a(), |
| * instead of alarm() and siglongjump limited gethostbyname(), |
| * but I don't like implicit threads. */ |
| /* to interrupt gethostbyname, |
| * we not only need a signal, |
| * but also the long jump: |
| * gethostbyname would otherwise just restart the syscall |
| * and timeout again. */ |
| static sigjmp_buf timed_out; |
| static void gethostbyname_timeout(int __attribute((unused)) signo) |
| { |
| siglongjmp(timed_out, 1); |
| } |
| |
| #define DNS_TIMEOUT 3 /* seconds */ |
| #define SOCKET_TIMEOUT 3 /* seconds */ |
| struct hostent *my_gethostbyname(const char *name) |
| { |
| struct sigaction sa; |
| struct sigaction so; |
| struct hostent *h; |
| static int failed_once_already = 0; |
| |
| if (failed_once_already) |
| return NULL; |
| |
| alarm(0); |
| sa.sa_handler = &gethostbyname_timeout; |
| sigemptyset(&sa.sa_mask); |
| sa.sa_flags = SA_NODEFER; |
| |
| sigaction(SIGALRM, &sa, &so); |
| |
| if (!sigsetjmp(timed_out, 1)) { |
| struct hostent ret; |
| char buf[2048]; |
| int my_h_errno; |
| |
| alarm(DNS_TIMEOUT); |
| /* h = gethostbyname(name); |
| * If the resolver is unresponsive, |
| * we may siglongjmp out of a "critical section" of gethostbyname, |
| * still holding some glibc internal lock. |
| * Any later attempt to call gethostbyname() would then deadlock |
| * (last syscall would be futex(...)) |
| * |
| * gethostbyname_r() apparently does not use any internal locks. |
| * Even if unnecessary in our case, it feels less dirty. |
| */ |
| gethostbyname_r(name, &ret, buf, sizeof(buf), &h, &my_h_errno); |
| } else { |
| /* timed out, longjmp of SIGALRM jumped here */ |
| h = NULL; |
| } |
| |
| alarm(0); |
| sigaction(SIGALRM, &so, NULL); |
| |
| if (h == NULL) |
| failed_once_already = 1; |
| return h; |
| } |
| |
| /** |
| * insert_usage_with_socket: |
| * |
| * Return codes: |
| * |
| * 0 - success |
| * 1 - failed to create socket |
| * 2 - unknown server |
| * 3 - cannot connect to server |
| * 5 - other error |
| */ |
| static int make_get_request(char *uri) { |
| struct sockaddr_in server; |
| struct hostent *host_info; |
| unsigned long addr; |
| int sock; |
| char *req_buf; |
| char *http_host = HTTP_HOST; |
| int buf_len = 1024; |
| char buffer[buf_len]; |
| FILE *sockfd; |
| int writeit; |
| struct timeval timeout = { .tv_sec = SOCKET_TIMEOUT }; |
| struct utsname nodeinfo; |
| |
| sock = socket( PF_INET, SOCK_STREAM, 0); |
| if (sock < 0) |
| return 1; |
| |
| setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); |
| setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); |
| |
| memset (&server, 0, sizeof(server)); |
| |
| /* convert host name to ip */ |
| host_info = my_gethostbyname(http_host); |
| if (host_info == NULL) { |
| /* unknown host, try with ip */ |
| if ((addr = inet_addr( HTTP_ADDR )) != INADDR_NONE) |
| memcpy((char *)&server.sin_addr, &addr, sizeof(addr)); |
| else { |
| close(sock); |
| return 2; |
| } |
| } else { |
| memcpy((char *)&server.sin_addr, host_info->h_addr, |
| host_info->h_length); |
| } |
| |
| uname(&nodeinfo); |
| req_buf = ssprintf("GET %s HTTP/1.0\r\n" |
| "Host: "HTTP_HOST"\r\n" |
| "User-Agent: drbdadm/"PACKAGE_VERSION" (%s; %s; %s; %s)\r\n" |
| "\r\n", |
| uri, |
| nodeinfo.sysname, nodeinfo.release, |
| nodeinfo.version, nodeinfo.machine); |
| |
| server.sin_family = AF_INET; |
| server.sin_port = htons(HTTP_PORT); |
| |
| if (connect(sock, (struct sockaddr*)&server, sizeof(server))<0) { |
| /* cannot connect to server */ |
| close(sock); |
| return 3; |
| } |
| |
| if ((sockfd = fdopen(sock, "r+")) == NULL) { |
| close(sock); |
| return 5; |
| } |
| |
| if (fputs(req_buf, sockfd) == EOF) { |
| fclose(sockfd); |
| close(sock); |
| return 5; |
| } |
| |
| writeit = 0; |
| while (fgets(buffer, buf_len, sockfd) != NULL) { |
| /* ignore http headers */ |
| if (writeit == 0) { |
| if (buffer[0] == '\r' || buffer[0] == '\n') |
| writeit = 1; |
| } else { |
| err("%s", buffer); |
| } |
| } |
| fclose(sockfd); |
| close(sock); |
| return 0; |
| } |
| |
| static void url_encode(char *in, char *out) |
| { |
| char *h = "0123456789abcdef"; |
| unsigned char c; |
| |
| while ((c = *in++) != 0) { |
| if (c == '\n') |
| break; |
| if (('a' <= c && c <= 'z') || |
| ('A' <= c && c <= 'Z') || |
| ('0' <= c && c <= '9') || |
| c == '-' || c == '_' || c == '.') |
| *out++ = c; |
| else if (c == ' ') |
| *out++ = '+'; |
| else { |
| *out++ = '%'; |
| *out++ = h[c >> 4]; |
| *out++ = h[c & 0x0f]; |
| } |
| } |
| *out = 0; |
| } |
| |
| /* returns 1, if either svn_revision or git_hash are known, |
| * returns 0 otherwise. */ |
| int have_vcs_hash(const struct version *v) |
| { |
| int i; |
| if (v->svn_revision) |
| return 1; |
| for (i = 0; i < GIT_HASH_BYTE; i++) |
| if (v->git_hash[i]) |
| return 1; |
| return 0; |
| } |
| |
| /* Ensure that the node is counted on http://usage.drbd.org |
| */ |
| #define ANSWER_SIZE 80 |
| |
| void uc_node(enum usage_count_type type) |
| { |
| struct node_info ni; |
| char *uri; |
| int send = 0; |
| int update = 0; |
| char answer[ANSWER_SIZE]; |
| char n_comment[ANSWER_SIZE*3]; |
| char *r; |
| const struct version *driver_version = drbd_driver_version(FALLBACK_TO_UTILS); |
| |
| if( type == UC_NO ) return; |
| if( getuid() != 0 ) return; |
| |
| /* not when running directly from init, |
| * or if stdout is no tty. |
| * you do not want to have the "user information message" |
| * as output from `drbdadm sh-resources all` |
| */ |
| if (getenv("INIT_VERSION")) return; |
| if (no_tty) return; |
| |
| if( ! read_node_id(&ni) ) { |
| get_random_bytes(&ni.node_uuid,sizeof(ni.node_uuid)); |
| ni.rev = *driver_version; |
| send = 1; |
| } else if (!have_vcs_hash(driver_version)) { |
| /* If we don't know the current version control system hash, |
| * we found the version via "modprobe -F version drbd", |
| * and did not find a /proc/drbd to read it from. |
| * Avoid flipping between "hash-some-value" and "hash-all-zero", |
| * Re-registering every time... |
| */ |
| send = 0; |
| } else { |
| // read_node_id() was successful |
| if (!version_equal(&ni.rev, driver_version)) { |
| ni.rev = *driver_version; |
| update = 1; |
| send = 1; |
| } |
| } |
| |
| if(!send) return; |
| |
| n_comment[0]=0; |
| if (type == UC_ASK ) { |
| err("\n" |
| "\t\t--== This is %s of DRBD ==--\n" |
| "Please take part in the global DRBD usage count at http://"HTTP_HOST".\n\n" |
| "The counter works anonymously. It creates a random number to identify\n" |
| "your machine and sends that random number, along with the kernel and\n" |
| "DRBD version, to "HTTP_HOST".\n\n" |
| "The benefits for you are:\n" |
| " * In response to your submission, the server ("HTTP_HOST") will tell you\n" |
| " how many users before you have installed this version (%s).\n" |
| " * With a high counter LINBIT has a strong motivation to\n" |
| " continue funding DRBD's development.\n\n" |
| "http://"HTTP_HOST"/cgi-bin/insert_usage.pl?nu="U64"&%s\n\n" |
| "In case you want to participate but know that this machine is firewalled,\n" |
| "simply issue the query string with your favorite web browser or wget.\n" |
| "You can control all of this by setting 'usage-count' in your drbd.conf.\n\n" |
| "* You may enter a free form comment about your machine, that gets\n" |
| " used on "HTTP_HOST" instead of the big random number.\n" |
| "* If you wish to opt out entirely, simply enter 'no'.\n" |
| "* To count this node without comment, just press [RETURN]\n", |
| update ? "an update" : "a new installation", |
| PACKAGE_VERSION, ni.node_uuid, vcs_to_str(&ni.rev)); |
| r = fgets(answer, ANSWER_SIZE, stdin); |
| if(r && !strcmp(answer,"no\n")) send = 0; |
| url_encode(answer,n_comment); |
| } |
| |
| uri = ssprintf("http://"HTTP_HOST"/cgi-bin/insert_usage.pl?nu="U64"&%s%s%s", |
| ni.node_uuid, vcs_to_str(&ni.rev), |
| n_comment[0] ? "&nc=" : "", n_comment); |
| |
| if (send) { |
| write_node_id(&ni); |
| |
| err("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" |
| " --== Thank you for participating in the global usage survey ==--\n" |
| "The server's response is:\n\n"); |
| make_get_request(uri); |
| if (type == UC_ASK) { |
| err("\n" |
| "From now on, drbdadm will contact "HTTP_HOST" only when you update\n" |
| "DRBD or when you use 'drbdadm create-md'. Of course it will continue\n" |
| "to ask you for confirmation as long as 'usage-count' is at its default\n" |
| "value of 'ask'.\n\n" |
| "Just press [RETURN] to continue: "); |
| if (fgets(answer, 9, stdin) == NULL) |
| err("Could not read answer\n"); |
| } |
| } |
| } |
| |
| /* For our purpose (finding the revision) SLURP_SIZE is always enough. |
| */ |
| static char* run_adm_drbdmeta(const struct cfg_ctx *ctx, const char *arg_override) |
| { |
| const int SLURP_SIZE = 4096; |
| int rr,pipes[2]; |
| char* buffer; |
| pid_t pid; |
| |
| buffer = malloc(SLURP_SIZE); |
| if(!buffer) return 0; |
| |
| if(pipe(pipes)) return 0; |
| |
| pid = fork(); |
| if(pid == -1) { |
| err("Can not fork\n"); |
| exit(E_EXEC_ERROR); |
| } |
| if(pid == 0) { |
| struct adm_cmd local_cmd = *ctx->cmd; |
| struct cfg_ctx local_ctx = *ctx; |
| // child |
| close(pipes[0]); // close reading end |
| dup2(pipes[1],1); // 1 = stdout |
| close(pipes[1]); |
| local_cmd.name = arg_override; |
| local_ctx.cmd = &local_cmd; |
| rr = _adm_drbdmeta(&local_ctx, |
| SLEEPS_VERY_LONG| |
| DONT_REPORT_FAILED, |
| NULL); |
| exit(rr); |
| } |
| close(pipes[1]); // close writing end |
| |
| rr = read(pipes[0], buffer, SLURP_SIZE-1); |
| if( rr == -1) { |
| free(buffer); |
| // FIXME cleanup |
| return 0; |
| } |
| buffer[rr]=0; |
| close(pipes[0]); |
| |
| waitpid(pid,0,0); |
| |
| return buffer; |
| } |
| |
| struct d_name *find_backend_option(const char *opt_name) |
| { |
| struct d_name *b_opt; |
| const int str_len = strlen(opt_name); |
| |
| STAILQ_FOREACH(b_opt, &backend_options, link) { |
| if (!strncmp(b_opt->name, opt_name, str_len)) |
| return b_opt; |
| } |
| return NULL; |
| } |
| |
| static bool peer_completely_diskless(struct d_host_info *peer) |
| { |
| struct d_volume *vol; |
| |
| for_each_volume(vol, &peer->volumes) { |
| if (vol->disk) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int adm_create_md(const struct cfg_ctx *ctx) |
| { |
| char answer[ANSWER_SIZE]; |
| struct node_info ni; |
| uint64_t device_uuid=0; |
| uint64_t device_size=0; |
| char *uri; |
| int send=0; |
| char *tb; |
| int rv, fd, verbose_tmp; |
| char *r, *max_peers_str = NULL; |
| struct d_name *b_opt_max_peers; |
| const char *opt_max_peers = "--max-peers="; |
| |
| b_opt_max_peers = find_backend_option(opt_max_peers); |
| if (b_opt_max_peers) { |
| max_peers_str = ssprintf("%s", b_opt_max_peers->name + strlen(opt_max_peers)); |
| } else { |
| struct connection *conn; |
| int max_peers = 0; |
| |
| set_peer_in_resource(ctx->res, true); |
| |
| for_each_connection(conn, &ctx->res->connections) { |
| if (conn->ignore) |
| continue; |
| |
| if (!peer_completely_diskless(conn->peer)) |
| max_peers++; |
| } |
| |
| if (max_peers == 0) |
| max_peers = 1; |
| |
| max_peers_str = ssprintf("%d", max_peers); |
| } |
| |
| /* drbdmeta does not understand "--max-peers=", |
| * so we drop it from the option list here... */ |
| if (b_opt_max_peers) |
| STAILQ_REMOVE(&backend_options, b_opt_max_peers, d_name, link); |
| |
| verbose_tmp = verbose; |
| verbose = 0; |
| tb = run_adm_drbdmeta(ctx, "read-dev-uuid"); |
| verbose = verbose_tmp; |
| device_uuid = strto_u64(tb,NULL,16); |
| free(tb); |
| |
| /* This is "drbdmeta ... create-md". |
| * It implicitly adds all backend_options to the command line. */ |
| rv = _adm_drbdmeta(ctx, SLEEPS_VERY_LONG, max_peers_str); |
| /* ... now add back "--max-peers=", if any, |
| * in case the caller loops over several volumes. */ |
| if (b_opt_max_peers) |
| insert_head(&backend_options, b_opt_max_peers); |
| |
| if(rv || dry_run) return rv; |
| |
| fd = open(ctx->vol->disk,O_RDONLY); |
| if( fd != -1) { |
| device_size = bdev_size(fd); |
| close(fd); |
| } |
| |
| if( read_node_id(&ni) && device_size && !device_uuid) { |
| get_random_bytes(&device_uuid, sizeof(uint64_t)); |
| |
| if( global_options.usage_count == UC_YES ) send = 1; |
| if( global_options.usage_count == UC_ASK ) { |
| err("\n" |
| "\t\t--== Creating metadata ==--\n" |
| "As with nodes, we count the total number of devices mirrored by DRBD\n" |
| "at http://"HTTP_HOST".\n\n" |
| "The counter works anonymously. It creates a random number to identify\n" |
| "the device and sends that random number, along with the kernel and\n" |
| "DRBD version, to "HTTP_HOST".\n\n" |
| "http://"HTTP_HOST"/cgi-bin/insert_usage.pl?nu="U64"&ru="U64"&rs="U64"\n\n" |
| "* If you wish to opt out entirely, simply enter 'no'.\n" |
| "* To continue, just press [RETURN]\n", |
| ni.node_uuid, device_uuid, device_size); |
| r = fgets(answer, ANSWER_SIZE, stdin); |
| if(r && strcmp(answer,"no\n")) send = 1; |
| } |
| } |
| |
| if(!device_uuid) { |
| get_random_bytes(&device_uuid, sizeof(uint64_t)); |
| } |
| |
| if (send) { |
| uri = ssprintf("http://"HTTP_HOST"/cgi-bin/insert_usage.pl?" |
| "nu="U64"&ru="U64"&rs="U64, |
| ni.node_uuid, device_uuid, device_size); |
| make_get_request(uri); |
| } |
| |
| /* HACK */ |
| { |
| struct adm_cmd local_cmd = *ctx->cmd; |
| struct cfg_ctx local_ctx = *ctx; |
| struct names old_backend_options; |
| char *opt; |
| |
| opt = ssprintf(X64(016), device_uuid); |
| old_backend_options = backend_options; |
| STAILQ_INIT(&backend_options); |
| insert_tail(&backend_options, names_from_str(opt)); |
| |
| local_cmd.name = "write-dev-uuid"; |
| local_ctx.cmd = &local_cmd; |
| local_cmd.drbdsetup_ctx = &wildcard_ctx; |
| _adm_drbdmeta(&local_ctx, SLEEPS_VERY_LONG, NULL); |
| |
| free_names(&backend_options); |
| backend_options = old_backend_options; |
| } |
| return rv; |
| } |
| |