.gitignore | 1
Makefile.am | 13
backend.c | 291 ++++++++
bootstrap.conf | 3
configure.ac | 5
dc-rhev-image.c | 1566 +++++++++++++++++++++++++++++++++++++++++++++
doc/warehouse_api_final.md | 10
gnulib-tests/.gitignore | 22
iwhd.spec.in | 1
lib/.gitignore | 22
rest.c | 4
setup.c | 10
12 files changed, 1926 insertions(+), 22 deletions(-)
New commits:
commit de21d4c71346135af16a7f9d23593f93773cb396
Author: Pete Zaitcev <zaitcev(a)yahoo.com>
Date: Thu Mar 3 23:08:48 2011 -0700
Cleanups:
exit(EXIT_FAILURE)
fprintf -> error
unused valiables
stub with ignore_value()
diff --git a/backend.c b/backend.c
index 89340d1..5b2fd40 100644
--- a/backend.c
+++ b/backend.c
@@ -42,6 +42,7 @@
#include "mpipe.h"
#include "backend.h"
#include "state_defs.h"
+#include "ignore-value.h"
#define NFSGID 36
@@ -1332,7 +1333,7 @@ fs_bcreate (const provider_t *prov, const char *bucket)
* dc-rhev-image cannot do setregid either, so no harm done:
* probably this is not a RHEV back-end to begin with.
*/
- chown(bucket,-1,NFSGID);
+ ignore_value(chown(bucket,-1,NFSGID));
return MHD_HTTP_OK;
}
diff --git a/bootstrap.conf b/bootstrap.conf
index 2e7cb31..c64c7ac 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -36,6 +36,7 @@ gnu-web-doc-update
gnupload
hash
hash-pjw
+ignore-value
malloc-gnu
maintainer-makefile
manywarnings
diff --git a/dc-rhev-image.c b/dc-rhev-image.c
index ca991eb..aff22fa 100644
--- a/dc-rhev-image.c
+++ b/dc-rhev-image.c
@@ -22,8 +22,9 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
-/* need _GNU_SOURCE for asprintf */
-#define _GNU_SOURCE
+
+#include <config.h>
+
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
@@ -105,7 +106,7 @@ struct api_conn {
static void Usage(void)
{
fprintf(stderr, "ERROR Usage: " TAG " [" TAG ".conf]\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
static char **env_p;
@@ -229,7 +230,8 @@ static int path_exists(const char *path)
}
static void
-cfg_veripick(char **cfgval, const char *cfgname, json_t *jcfg, char *cfgtag)
+cfg_veripick(char **cfgval, const char *cfgname, json_t *jcfg,
+ const char *cfgtag)
{
json_t *elem;
const char *name;
@@ -240,18 +242,18 @@ cfg_veripick(char **cfgval, const char *cfgname, json_t *jcfg, char
*cfgtag)
fprintf(stderr,
"ERROR configuration %s: tag %s is not a string\n",
cfgname, cfgtag);
- exit(2);
+ exit(EXIT_FAILURE);
}
name = json_string_value(elem);
if (!name) {
fprintf(stderr, "ERROR configuration %s: tag %s has no value\n",
cfgname, cfgtag);
- exit(2);
+ exit(EXIT_FAILURE);
}
tmp = strdup(name);
if (!tmp) {
fprintf(stderr, "ERROR no core\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
*cfgval = tmp;
}
@@ -311,19 +313,19 @@ static void apipaths(struct api_conn *connection, struct curl_slist
*headers,
rcc = curl_easy_perform(curl);
if (rcc != CURLE_OK) {
fprintf(stderr, "ERROR curl failed GET url `%s'\n", url);
- exit(1);
+ exit(EXIT_FAILURE);
}
doc = xmlReadMemory(apib.buf, apib.used, "rhev-api.xml", NULL, 0);
if (doc == NULL) {
fprintf(stderr, "ERROR API parse error in `%s'\n", url);
- exit(1);
+ exit(EXIT_FAILURE);
}
etroot = xmlDocGetRootElement(doc);
if (strcmp((const char *)etroot->name, "api") != 0) {
fprintf(stderr, "ERROR API root is `api'\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
for (et = etroot->children; et; et = et->next) {
@@ -358,11 +360,11 @@ static void apipaths(struct api_conn *connection, struct curl_slist
*headers,
if (!pathsd) {
fprintf(stderr, "ERROR API has no `rel=storagedomains'\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
if (!pathdc) {
fprintf(stderr, "ERROR API has no `rel=datacenters'\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
*psd = pathsd;
@@ -376,11 +378,11 @@ static void apipaths(struct api_conn *connection, struct curl_slist
*headers,
err_alloc:
fprintf(stderr, "ERROR No core\n");
- exit(1);
+ exit(EXIT_FAILURE);
err_href:
fprintf(stderr, "ERROR <link /> with no `href'\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
static xmlNode *xmlGetChild(xmlNode *node, const char *name)
@@ -450,13 +452,13 @@ static struct stor_dom *apistordom_1(struct config *cfg, xmlChar
*uuidsd,
if (!(sd = malloc(sizeof(struct stor_dom)))) {
fprintf(stderr, "ERROR No core\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
memset(sd, 0, sizeof(struct stor_dom));
sd->uuid = strdup((char *)uuidsd);
if (!sd->uuid) {
fprintf(stderr, "ERROR No core\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
sd->address = cfg->nfshost;
sd->path = cfg->nfspath;
@@ -503,19 +505,19 @@ static struct stor_dom *apistordom(struct config *cfg,
rcc = curl_easy_perform(curl);
if (rcc != CURLE_OK) {
fprintf(stderr, "ERROR curl failed GET url `%s'\n", url);
- exit(1);
+ exit(EXIT_FAILURE);
}
doc = xmlReadMemory(apib.buf, apib.used, "rhev-api.xml", NULL, 0);
if (doc == NULL) {
fprintf(stderr, "ERROR API parse error in `%s'\n", url);
- exit(1);
+ exit(EXIT_FAILURE);
}
etroot = xmlDocGetRootElement(doc);
if (strcmp((const char *)etroot->name, "storage_domains") != 0) {
fprintf(stderr, "ERROR API root is `storage_domains'\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
sd = NULL;
@@ -562,7 +564,7 @@ static struct stor_dom *apistordom(struct config *cfg,
if (!sd) {
fprintf(stderr, "ERROR NFS storage domain for `%s' not found\n",
url);
- exit(1);
+ exit(EXIT_FAILURE);
}
xmlFreeDoc(doc);
@@ -573,8 +575,7 @@ static struct stor_dom *apistordom(struct config *cfg,
err_alloc:
fprintf(stderr, "ERROR No core\n");
- exit(1);
- return NULL;
+ exit(EXIT_FAILURE);
}
static int apipoolid(struct config *cfg, struct api_conn *connection,
@@ -611,19 +612,19 @@ static int apipoolid(struct config *cfg, struct api_conn
*connection,
rcc = curl_easy_perform(curl);
if (rcc != CURLE_OK) {
fprintf(stderr, "ERROR curl failed GET url `%s'\n", url);
- exit(1);
+ exit(EXIT_FAILURE);
}
doc = xmlReadMemory(apib.buf, apib.used, "rhev-api.xml", NULL, 0);
if (doc == NULL) {
fprintf(stderr, "ERROR API parse error in `%s'\n", url);
- exit(1);
+ exit(EXIT_FAILURE);
}
etroot = xmlDocGetRootElement(doc);
if (strcmp((const char *)etroot->name, "storage_domains") != 0) {
fprintf(stderr, "ERROR API root is `storage_domains'\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
for (etsd = etroot->children; etsd; etsd = etsd->next) {
@@ -656,8 +657,7 @@ ret:
err_alloc:
fprintf(stderr, "ERROR No core\n");
- exit(1);
- return 0;
+ exit(EXIT_FAILURE);
}
static char *apipool(struct config *cfg, struct api_conn *connection,
@@ -697,7 +697,7 @@ static char *apipool(struct config *cfg, struct api_conn *connection,
rcc = curl_easy_perform(curl);
if (rcc != CURLE_OK) {
fprintf(stderr, "ERROR curl failed GET url `%s'\n", url);
- exit(1);
+ exit(EXIT_FAILURE);
}
/* <--- cleanup curl connection here if needed */
@@ -705,13 +705,13 @@ static char *apipool(struct config *cfg, struct api_conn
*connection,
doc = xmlReadMemory(apib.buf, apib.used, "rhev-api-dc.xml", NULL, 0);
if (doc == NULL) {
fprintf(stderr, "ERROR API parse error in `%s'\n", url);
- exit(1);
+ exit(EXIT_FAILURE);
}
etroot = xmlDocGetRootElement(doc);
if (strcmp((const char *)etroot->name, "data_centers") != 0) {
fprintf(stderr, "ERROR API root is `data_centers'\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
for (etdc = etroot->children; etdc; etdc = etdc->next) {
@@ -768,13 +768,11 @@ static char *apipool(struct config *cfg, struct api_conn
*connection,
}
fprintf(stderr, "ERROR pool not found for storage domain %s\n", sd_uuid);
- exit(1);
- return NULL;
+ exit(EXIT_FAILURE);
err_alloc:
fprintf(stderr, "ERROR No core\n");
- exit(1);
- return NULL;
+ exit(EXIT_FAILURE);
}
static struct stor_dom *apistart(struct config *cfg)
@@ -797,7 +795,7 @@ static struct stor_dom *apistart(struct config *cfg)
if (!huri_parse(&huri, cfg->apiurl)) {
fprintf(stderr, "ERROR unable to parse `apiurl': `%s'\n",
cfg->apiurl);
- exit(1);
+ exit(EXIT_FAILURE);
}
path = strndup(huri.path, huri.path_len);
@@ -874,7 +872,7 @@ static struct stor_dom *apistart(struct config *cfg)
err_alloc:
fprintf(stderr, "ERROR No core\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
static void spitovf(struct config *cfg, struct stor_dom *sd,
@@ -906,7 +904,7 @@ static void spitovf(struct config *cfg, struct stor_dom *sd,
if (mkdir(tmpovfdir, 0770) < 0) {
fprintf(stderr, "ERROR Failed to make directory %s: %s\n",
tmpovfdir, strerror(errno));
- exit(1);
+ exit(EXIT_FAILURE);
}
uuid_unparse_lower(img_uuid, uuidbuf);
@@ -931,13 +929,13 @@ static void spitovf(struct config *cfg, struct stor_dom *sd,
if (!writer) {
fprintf(stderr, "ERROR Error creating the xml driver for %s\n",
tmpovf);
- exit(1);
+ exit(EXIT_FAILURE);
}
rc = xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL);
if (rc < 0) {
fprintf(stderr, "ERROR Error in xmlTextWriterStartDocument\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
rc = xmlTextWriterStartElement(writer, BAD_CAST "ovf:Envelope");
@@ -1325,7 +1323,7 @@ static void spitovf(struct config *cfg, struct stor_dom *sd,
rc = xmlTextWriterEndDocument(writer);
if (rc < 0) {
fprintf(stderr, "ERROR Error in xmlTextWriterEndDocument\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
xmlFreeTextWriter(writer);
@@ -1333,12 +1331,12 @@ static void spitovf(struct config *cfg, struct stor_dom *sd,
if (rename(tmpimgdir, imgdir) < 0) {
fprintf(stderr, "ERROR Failed to rename from %s to %s: %s\n",
tmpimgdir, imgdir, strerror(errno));
- exit(1);
+ exit(EXIT_FAILURE);
}
if (rename(tmpovfdir, ovfdir) < 0) {
fprintf(stderr, "ERROR Failed to rename from %s to %s: %s\n",
tmpovfdir, ovfdir, strerror(errno));
- exit(1);
+ exit(EXIT_FAILURE);
}
free(domdir);
@@ -1351,11 +1349,11 @@ static void spitovf(struct config *cfg, struct stor_dom *sd,
err_alloc:
fprintf(stderr, "ERROR No core\n");
- exit(1);
+ exit(EXIT_FAILURE);
err_xml:
fprintf(stderr, "ERROR Failed to form XML\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
static void copyimage(struct config *cfg, struct stor_dom *sd, uuid_t tpl_uuid)
@@ -1378,7 +1376,7 @@ static void copyimage(struct config *cfg, struct stor_dom *sd,
uuid_t tpl_uuid)
if (geteuid() != NFSUID || getegid() != NFSGID) {
fprintf(stderr,
"ERROR Have to run with user and group 36\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
}
@@ -1388,11 +1386,11 @@ static void copyimage(struct config *cfg, struct stor_dom *sd,
uuid_t tpl_uuid)
if (stat(domdir, &statb) < 0) {
fprintf(stderr, "ERROR failed to stat %s: %s\n",
domdir, strerror(errno));
- exit(1);
+ exit(EXIT_FAILURE);
}
if (!S_ISDIR(statb.st_mode)) {
fprintf(stderr, "ERROR path %s is not a directory\n", domdir);
- exit(1);
+ exit(EXIT_FAILURE);
}
uuid_generate_random(vol_uuid);
@@ -1407,7 +1405,7 @@ static void copyimage(struct config *cfg, struct stor_dom *sd,
uuid_t tpl_uuid)
if (mkdir(tmpimgdir, 0770) < 0) {
fprintf(stderr, "ERROR Failed to make directory %s: %s\n",
tmpimgdir, strerror(errno));
- exit(1);
+ exit(EXIT_FAILURE);
}
now = time(NULL);
@@ -1424,7 +1422,7 @@ static void copyimage(struct config *cfg, struct stor_dom *sd,
uuid_t tpl_uuid)
if (stat(imgdst, &statb) < 0) {
fprintf(stderr, "ERROR failed to stat %s: %s\n",
imgdst, strerror(errno));
- exit(1);
+ exit(EXIT_FAILURE);
}
vol_size = statb.st_size;
@@ -1436,7 +1434,7 @@ static void copyimage(struct config *cfg, struct stor_dom *sd,
uuid_t tpl_uuid)
if (!fp) {
fprintf(stderr, "ERROR Failed to create %s: %s\n",
imgmeta, strerror(errno));
- exit(1);
+ exit(EXIT_FAILURE);
}
fprintf(fp, "DOMAIN=%s\n", sd->uuid);
/* saved template has VOLTYPE=SHARED */
@@ -1458,7 +1456,7 @@ static void copyimage(struct config *cfg, struct stor_dom *sd,
uuid_t tpl_uuid)
fprintf(fp, "EOF\n");
if (close_stream(fp)) {
fprintf(stderr, "ERROR Failed to write %s\n", imgmeta);
- exit(1);
+ exit(EXIT_FAILURE);
}
spitovf(cfg, sd, img_uuid, vol_uuid, vol_size, tpl_uuid);
@@ -1471,14 +1469,14 @@ static void copyimage(struct config *cfg, struct stor_dom *sd,
uuid_t tpl_uuid)
err_alloc:
fprintf(stderr, "ERROR No core\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
int main(int argc, char **argv, char **envp)
{
uuid_t tpl_uuid;
json_error_t err;
- char *cfgname;
+ const char *cfgname;
struct config cfg;
json_t *jcfg;
struct stor_dom *sd;
@@ -1503,21 +1501,21 @@ int main(int argc, char **argv, char **envp)
if (!jcfg) {
fprintf(stderr, "ERROR configuration JSON error %s:%d: %s\n",
cfgname, err.line, err.text);
- exit(2);
+ exit(EXIT_FAILURE);
}
if (json_typeof(jcfg) != JSON_OBJECT) {
fprintf(stderr,
"ERROR configuration JSON %s: root is not a struct\n",
cfgname);
- exit(2);
+ exit(EXIT_FAILURE);
}
/* image: local filename with disk image */
cfg_veripick(&cfg.image, cfgname, jcfg, "image");
if (!path_exists(cfg.image)) {
fprintf(stderr, "ERROR image %s does not exist\n", cfg.image);
- exit(1);
+ exit(EXIT_FAILURE);
}
/* apiurl: the so-called "base", usually "rhev-api" */
cfg_veripick(&cfg.apiurl, cfgname, jcfg, "apiurl");
@@ -1548,11 +1546,11 @@ int main(int argc, char **argv, char **envp)
cfg_veripick(&cfg.nfsdir, cfgname, jcfg, "nfsdir");
if (cfg.nfsdir[0] == 0) {
fprintf(stderr, "ERROR configuration: `nfsdir' is empty\n");
- exit(2);
+ exit(EXIT_FAILURE);
}
if (cfg.nfsdir[0] != '/') {
fprintf(stderr, "ERROR configuration: `nfsdir' is relative\n");
- exit(2);
+ exit(EXIT_FAILURE);
}
json_decref(jcfg);
diff --git a/lib/.gitignore b/lib/.gitignore
index f7907b8..61eef3f 100644
--- a/lib/.gitignore
+++ b/lib/.gitignore
@@ -55,6 +55,7 @@
/hash-pjw.h
/hash.c
/hash.h
+/ignore-value.h
/intprops.h
/inttypes.h
/inttypes.in.h
diff --git a/rest.c b/rest.c
index e89e140..4681871 100644
--- a/rest.c
+++ b/rest.c
@@ -1746,8 +1746,6 @@ proxy_primary_prov (void *cctx, struct MHD_Connection *conn, const
char *url,
DPRINTF("PROXY GET PRIMARY PROVIDER (%s)\n", url);
- my_state *ms = *rctx;
-
// "/_providers/_primary" is the only one we accept for now.
bool valid = strcmp (url, "/_providers/_primary") == 0;
unsigned int rc = (valid ? MHD_HTTP_OK : MHD_HTTP_BAD_REQUEST);
@@ -1780,7 +1778,6 @@ proxy_set_primary (void *cctx, struct MHD_Connection *conn, const
char *url,
DPRINTF("PROXY SET PRIMARY PROVIDER (%s)\n", url);
- my_state *ms = *rctx;
char *name = NULL;
unsigned int rc = MHD_HTTP_BAD_REQUEST;
@@ -1835,7 +1832,6 @@ proxy_delete_prov (void *cctx, struct MHD_Connection *conn, const
char *url,
(void)data;
(void)data_size;
- my_state *ms = *rctx;
struct MHD_Response *resp
= MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
if (!resp) {
diff --git a/setup.c b/setup.c
index 13b9943..c1791f7 100644
--- a/setup.c
+++ b/setup.c
@@ -589,7 +589,7 @@ auto_config(void)
config = json_loads(auto_json,&err);
if (!config) {
- fprintf(stderr,"JSON error on line %d: %s\n",err.line,err.text);
+ error(0, 0, "JSON error on line %d: %s", err.line, err.text);
return NULL;
}
commit 55dbf28e2eb8eecfe90a715d3694c4457917762e
Author: Pete Zaitcev <zaitcev(a)yahoo.com>
Date: Thu Mar 3 23:03:11 2011 -0700
[patch iwhd 3/3] Touch up the warehouse_final.md
This patch fixes the syntax of curl examples and corrects the wording
about the configuration default to match reality.
Signed-off-by: Pete Zaitcev <zaitcev(a)redhat.com>
diff --git a/doc/warehouse_api_final.md b/doc/warehouse_api_final.md
index b5d7294..dd7ea57 100644
--- a/doc/warehouse_api_final.md
+++ b/doc/warehouse_api_final.md
@@ -157,7 +157,8 @@ can be used to fetch or set multiple attributes - including values
To set both of these attributes at once:
- $ curl -d color="blue" -d flavor="lemon"
http://fserver-1:9090/my_bucket/file1/attrs
+ $ curl -d color="blue" -d flavor="lemon" \
+
http://fserver-1:9090/my_bucket/file1/attrs
Single-attribute operations are also supported. To fetch a
single attribute:
@@ -282,7 +283,8 @@ object body except that there's no data transfer from the
client).
The second control operation is used to determine whether replication
to a specific remote warehouse has finished.
- $ curl -d op=check loc=backup
http://fserver-1:9090/my_bucket/file1
+ $ curl -d op=check -d loc=backup \
+
http://fserver-1:9090/my_bucket/file1
This will return a 404 (Not Found) if the object has not been replicated
to that location, or a 200 (OK) if it has.
@@ -291,8 +293,8 @@ Appendix 2: JSON Configuration Format
-------------------------------------
The initial configuration for the image warehouse is pulled
-from a JSON configuration file, repo.json in the current directory
-by default. This defines a set of required attributes plus any
+from a JSON configuration file (set with -c option).
+This defines a set of required attributes plus any
others that the user might want to use in replication policies.
Here's an example:
commit 9fbcee7abb4ba29ab8a77e3b3022663eb7962c29
Author: Pete Zaitcev <zaitcev(a)yahoo.com>
Date: Thu Mar 3 23:02:18 2011 -0700
[patch iwhd 2/3] Fix error handling and waitpid
During the debugging of RHEV-M registration it became evident that if you
fork a child, you have to process the child's output first, and then call
waitpid(). Doing otherwise risks deadlocking if child overflows pipe.
Also, any error messages are invisible, because in case of nonzero exit
code we bail out before they are read from the pipe.
This patch adjusts s3_register to be uniform with fs_rhevm_register,
which is tested to do it right. Jeff Darcy is apprised and agrees.
Conversely, this fixes up fs_rhevm_register to use the cleanup scheme
of organ[] that s3_register has, because it is correct.
One final thing: fgets() differs from strncpy(): it always stores the
terminating nul. So, don't forcenul at [sizeof(buf)-1]. However, we
should kill the newline, or else debugging output looks ugly.
Signed-off-by: Pete Zaitcev <zaitcev(a)redhat.com>
diff --git a/backend.c b/backend.c
index 5457303..89340d1 100644
--- a/backend.c
+++ b/backend.c
@@ -375,8 +375,10 @@ s3_register (my_state *ms, const provider_t *prov, const char *next,
int argc = 0;
pid_t pid;
int organ[2];
+ int wstat;
FILE *fp;
char buf[LINE_SIZE];
+ char *s;
char *cval = NULL;
char *kval = NULL;
int rc = MHD_HTTP_BAD_REQUEST;
@@ -517,22 +519,16 @@ s3_register (my_state *ms, const provider_t *prov, const char
*next,
error (EXIT_FAILURE, errno, "failed to run command %s", cmd);
}
- DPRINTF("waiting for child...\n");
- if (waitpid(pid,NULL,0) < 0) {
- error (0, errno, "waitpid failed");
- }
- /* TBD: check identity/status from waitpid */
- DPRINTF("...child exited\n");
-
close(organ[1]);
fp = fdopen(organ[0],"r");
if (!fp) {
- DPRINTF("could not open parent pipe\n");
+ error (0, 0, "could not open parent pipe stream");
close(organ[0]);
goto cleanup;
}
while (fgets(buf,sizeof(buf)-1,fp)) {
- buf[sizeof(buf)-1] = '\0';
+ if ((s = strchr(buf, '\n')) != NULL)
+ *s = '\0';
if (regexec(&s3_success_pat,buf,2,match,0) == 0) {
buf[match[1].rm_eo] = '\0';
DPRINTF("found AMI ID: %s\n",buf+match[1].rm_so);
@@ -551,6 +547,22 @@ s3_register (my_state *ms, const provider_t *prov, const char *next,
}
fclose(fp);
+ DPRINTF("waiting for child...\n");
+ if (waitpid(pid,&wstat,0) < 0) {
+ error (0, errno, "waitpid failed");
+ goto cleanup;
+ }
+ if (!WIFEXITED(wstat)) {
+ error (0, 0, "%s is killed (status 0x%x)", cmd, wstat);
+ goto cleanup;
+ }
+ if (WEXITSTATUS(wstat)) {
+ error (0, 0, "%s exited with code %d",
+ cmd, WEXITSTATUS(wstat));
+ goto cleanup;
+ }
+ DPRINTF("...child exited\n");
+
cleanup:
/*
* This is a bit tricky. If we found the cert in the HTTP request and
@@ -1350,9 +1362,6 @@ fs_rhevm_register (my_state *ms, const provider_t *prov, const char
*next,
char ami_id_buf[64];
regmatch_t match[2];
- organ[0] = -1;
- organ[1] = -1;
-
if (!regex_ok) {
return MHD_HTTP_BAD_REQUEST;
}
@@ -1507,10 +1516,10 @@ fs_rhevm_register (my_state *ms, const provider_t *prov, const
char *next,
}
close(organ[1]);
- organ[1] = -1;
fp = fdopen(organ[0],"r");
if (!fp) {
- error (0, 0, "could not open parent pipe stream\n");
+ error (0, 0, "could not open parent pipe stream");
+ close(organ[0]);
goto cleanup;
}
while (fgets(buf,sizeof(buf)-1,fp)) {
@@ -1532,7 +1541,6 @@ fs_rhevm_register (my_state *ms, const provider_t *prov, const char
*next,
}
}
fclose(fp);
- organ[0] = -1;
DPRINTF("waiting for child...\n");
if (waitpid(pid,&wstat,0) < 0) {
@@ -1551,10 +1559,6 @@ fs_rhevm_register (my_state *ms, const provider_t *prov, const char
*next,
DPRINTF("...child exited\n");
cleanup:
- if (organ[0] != -1)
- close(organ[0]);
- if (organ[1] != -1)
- close(organ[1]);
/*
* Unfortunately we cannot collect zombies indiscriminately here
* in case another thread forks. TBD: make main loop collect zombies.
commit 70d390c69a38b8446443472a1b93323c9827885f
Author: Pete Zaitcev <zaitcev(a)yahoo.com>
Date: Thu Mar 3 22:57:04 2011 -0700
Add dc-rhev-image.
diff --git a/.gitignore b/.gitignore
index f95dca3..888efbc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
/config.log
/config.status
/configure
+/dc-rhev-image
/gnulib-tests
/iwhd
/iwhd-*.tar.gz
diff --git a/Makefile.am b/Makefile.am
index d6a63b0..4d10bf1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -21,7 +21,7 @@ SUBDIRS = lib . gnulib-tests t man
ACLOCAL_AMFLAGS = -I m4
# iwhd is short for Image WareHouse Daemon.
-bin_PROGRAMS = iwhd
+bin_PROGRAMS = iwhd dc-rhev-image
EXTRA_DIST = \
iwhd-qparser.h \
@@ -56,6 +56,8 @@ iwhd_SOURCES = \
EXTRA_iwhd_SOURCES = qlexer.l
+dc_rhev_image_SOURCES = dc-rhev-image.c
+
VERSION_no_hyphen = $$(echo $(VERSION)|tr - _)
MOSTLYCLEANFILES += iwhd.spec
@@ -102,6 +104,15 @@ iwhd_LDADD = \
$(PTHREAD_LIB) \
$(HAIL_LIBS)
+dc_rhev_image_CPPFLAGS = $(LIBXML_CFLAGS) -I$(top_srcdir)/lib
+dc_rhev_image_LDADD = \
+ lib/libiwhd.a \
+ $(LIB_ACL) \
+ $(CURL_LIB) \
+ $(JANSSON_LIB) \
+ $(LIBXML_LIBS) \
+ $(UUID_LIB)
+
MOSTLYCLEANFILES += qlexer.c
MAINTAINERCLEANFILES += qlexer.c
EXTRA_DIST += qlexer.c
diff --git a/backend.c b/backend.c
index b44c8c0..5457303 100644
--- a/backend.c
+++ b/backend.c
@@ -43,6 +43,8 @@
#include "backend.h"
#include "state_defs.h"
+#define NFSGID 36
+
struct hstor_client *hstor;
/***** Generic module stuff, not specific to one back end *****/
@@ -54,6 +56,7 @@ struct hstor_client *hstor;
* enough to hold the header name plus a CF token (32 bytes).
*/
#define ADDR_SIZE 256
+#define LINE_SIZE 500
#define HEADER_SIZE 64
#define S3_IMAGE_PATTERN "^IMAGE[[:blank:]]+([^[:space:]]+)"
@@ -373,7 +376,7 @@ s3_register (my_state *ms, const provider_t *prov, const char *next,
pid_t pid;
int organ[2];
FILE *fp;
- char buf[ADDR_SIZE];
+ char buf[LINE_SIZE];
char *cval = NULL;
char *kval = NULL;
int rc = MHD_HTTP_BAD_REQUEST;
@@ -1308,14 +1311,264 @@ fs_bcreate (const provider_t *prov, const char *bucket)
DPRINTF("creating bucket %s\n",bucket);
- if (mkdir(bucket,0700) < 0) {
+ if (mkdir(bucket,0750) < 0) {
error (0, errno, "%s: failed to create directory", bucket);
return MHD_HTTP_INTERNAL_SERVER_ERROR;
}
+ /*
+ * The chown fails if iwhd does not run as root, but in that case
+ * dc-rhev-image cannot do setregid either, so no harm done:
+ * probably this is not a RHEV back-end to begin with.
+ */
+ chown(bucket,-1,NFSGID);
return MHD_HTTP_OK;
}
+static int
+fs_rhevm_register (my_state *ms, const provider_t *prov, const char *next,
+ Hash_table *args)
+{
+ char *api_url;
+ char *api_user;
+ char *api_secret;
+ char *nfs_host;
+ char *nfs_path;
+ char *nfs_dir;
+ const char *conf_name = NULL;
+ char *conf_text = NULL;
+ int ret = MHD_HTTP_BAD_REQUEST;
+ int rc;
+ const char *argv[12];
+ int argc = 0;
+ pid_t pid;
+ int organ[2];
+ int wstat;
+ FILE *fp;
+ char buf[LINE_SIZE];
+ char *s;
+ char ami_id_buf[64];
+ regmatch_t match[2];
+
+ organ[0] = -1;
+ organ[1] = -1;
+
+ if (!regex_ok) {
+ return MHD_HTTP_BAD_REQUEST;
+ }
+
+ DPRINTF("*** RHEV-M register %s/%s via %s (%s:%d)\n",
+ ms->bucket, ms->key, prov->name, prov->host, prov->port);
+ if (next) {
+ DPRINTF("RHEV-M register with next!=NULL\n");
+ return MHD_HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * This may be a good time to check if kernel is set.
+ * Saving the code for later. Use partitioned, bootable images for now.
+ */
+#if 0
+ char *kernel = kv_hash_lookup(args,"kernel");
+ if (kernel) {
+ DPRINTF(" (using kernel %s)\n",kernel);
+ }
+#endif
+
+ /*
+ * This is always "http://xxxxxx.xxxx.xxx/rhevm-api".
+ * SSL does not work yet, sorry.
+ */
+ api_url = kv_hash_lookup(args,"api-url");
+ if (!api_url) {
+ /* TBD: This has to have a defined field in provider, too. */
+ error (0, 0, "missing RHEV-M API URL (api-url)");
+ goto cleanup;
+ }
+
+ api_user = kv_hash_lookup(args,"api-key");
+ if (!api_user) {
+ api_user = (char *)prov->username;
+ if (!api_user) {
+ error (0, 0, "missing RHEV-M API username (api-key)");
+ goto cleanup;
+ }
+ }
+
+ api_secret = kv_hash_lookup(args,"api-secret");
+ if (!api_secret) {
+ api_secret = (char *)prov->password;
+ if (!prov->password) {
+ error (0, 0, "missing RHEV-M API secret (api-secret)");
+ goto cleanup;
+ }
+ }
+
+ /*
+ * We need to document how this all works.
+ * - NFS host and NFS path are matched against the list of storage
+ * domains in RHEV-M (we fetch the list using RESTful API).
+ * This way we find the UUIDs that we have to write out.
+ * - "NFS directory" is the place where write the image and metadata.
+ * It is up to the user to make sure that nfs-dir points to
+ * nfs-host:nfs-path. We do not have a way to verify it is so.
+ */
+ nfs_host = kv_hash_lookup(args,"nfs-host");
+ nfs_path = kv_hash_lookup(args,"nfs-path");
+ nfs_dir = kv_hash_lookup(args,"nfs-dir");
+ if (!nfs_host) {
+ error (0, 0, "missing export domain's host (nfs-host)");
+ goto cleanup;
+ }
+ if (!nfs_path) {
+ error (0, 0, "missing export domain's path (nfs-path)");
+ goto cleanup;
+ }
+ if (!nfs_dir) {
+ error (0, 0, "missing target directory (nfs-dir)");
+ goto cleanup;
+ }
+
+#if 0 /* The GC library crashes on free() in this case. */
+ rc = asprintf(&conf_text,
+ "{\n"
+ " \"image\" : \"%s/%s\",\n"
+ " \"apiurl\" : \"%s\",\n"
+ " \"apiuser\" : \"%s\",\n"
+ " \"apipass\" : \"%s\",\n"
+ " \"nfshost\" : \"%s\",\n"
+ " \"nfspath\" : \"%s\",\n"
+ " \"nfsdir\" : \"%s\"\n"
+ "}\n",
+ ms->bucket, ms->key,
+ api_url, api_user, api_secret,
+ nfs_host, nfs_path, nfs_dir
+ );
+ if (rc < 0) {
+ conf_text = NULL;
+ error (0, 0, "no core");
+ goto cleanup;
+ }
+#else
+ conf_text = malloc(1000);
+ if (!conf_text) {
+ error (0, 0, "no core");
+ goto cleanup;
+ }
+ snprintf(conf_text, 1000,
+ "{\n"
+ " \"image\" : \"%s/%s\",\n"
+ " \"apiurl\" : \"%s\",\n"
+ " \"apiuser\" : \"%s\",\n"
+ " \"apipass\" : \"%s\",\n"
+ " \"nfshost\" : \"%s\",\n"
+ " \"nfspath\" : \"%s\",\n"
+ " \"nfsdir\" : \"%s\"\n"
+ "}\n",
+ ms->bucket, ms->key,
+ api_url, api_user, api_secret,
+ nfs_host, nfs_path, nfs_dir
+ );
+#endif
+ conf_name = s3_init_tmpfile(conf_text);
+ if (!conf_name) {
+ error (0, 0, "cannot create a temporary file");
+ goto cleanup;
+ }
+
+ sprintf(ami_id_buf,"pending %lld",(long long)time(NULL));
+ DPRINTF("temporary ami-id = \"%s\"\n",ami_id_buf);
+ (void)meta_set_value(ms->bucket,ms->key,"ami-id",ami_id_buf);
+ rc = MHD_HTTP_INTERNAL_SERVER_ERROR;
+
+ const char *cmd = "dc-rhev-image";
+ argv[argc++] = cmd;
+ argv[argc++] = conf_name;
+ argv[argc] = NULL;
+
+ if (pipe(organ) < 0) {
+ error (0, errno, "pipe creation failed");
+ goto cleanup;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ error (0, errno, "fork failed");
+ close(organ[0]);
+ close(organ[1]);
+ goto cleanup;
+ }
+
+ if (pid == 0) {
+ (void)dup2(organ[1],STDOUT_FILENO);
+ (void)dup2(organ[1],STDERR_FILENO);
+ execvp(cmd, (char* const*)argv);
+ error (EXIT_FAILURE, errno, "failed to run command %s", cmd);
+ }
+
+ close(organ[1]);
+ organ[1] = -1;
+ fp = fdopen(organ[0],"r");
+ if (!fp) {
+ error (0, 0, "could not open parent pipe stream\n");
+ goto cleanup;
+ }
+ while (fgets(buf,sizeof(buf)-1,fp)) {
+ if ((s = strchr(buf, '\n')) != NULL)
+ *s = '\0';
+ if (regexec(&s3_success_pat,buf,2,match,0) == 0) {
+ buf[match[1].rm_eo] = '\0';
+ DPRINTF("found image UUID: %s\n",buf+match[1].rm_so);
+ sprintf(ami_id_buf,"OK %.60s",buf+match[1].rm_so);
+ rc = MHD_HTTP_OK;
+ }
+ else if (strcmp(buf,"ERROR") == 0) {
+ DPRINTF("found err marker: %s\n",buf+sizeof("ERROR"));
+ sprintf(ami_id_buf,"failed %.56s",buf+sizeof("ERROR"));
+ rc = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else {
+ DPRINTF("ignoring line: <%s>\n",buf);
+ }
+ }
+ fclose(fp);
+ organ[0] = -1;
+
+ DPRINTF("waiting for child...\n");
+ if (waitpid(pid,&wstat,0) < 0) {
+ error (0, errno, "waitpid failed");
+ goto cleanup;
+ }
+ if (!WIFEXITED(wstat)) {
+ error (0, 0, "%s is killed (status 0x%x)", cmd, wstat);
+ goto cleanup;
+ }
+ if (WEXITSTATUS(wstat)) {
+ error (0, 0, "%s exited with code %d",
+ cmd, WEXITSTATUS(wstat));
+ goto cleanup;
+ }
+ DPRINTF("...child exited\n");
+
+cleanup:
+ if (organ[0] != -1)
+ close(organ[0]);
+ if (organ[1] != -1)
+ close(organ[1]);
+ /*
+ * Unfortunately we cannot collect zombies indiscriminately here
+ * in case another thread forks. TBD: make main loop collect zombies.
+ */
+ // while(waitpid(-1,&wstat,WNOHANG) >= 0) {}
+ if (conf_name)
+ unlink(conf_name);
+ free((char *)conf_name);
+ free(conf_text);
+
+ (void)meta_set_value(ms->bucket,ms->key,"ami-id",ami_id_buf);
+ return ret;
+}
+
/***** Function tables. ****/
backend_func_tbl bad_func_tbl = {
@@ -1372,3 +1625,14 @@ backend_func_tbl fs_func_tbl = {
fs_bcreate,
bad_register,
};
+
+backend_func_tbl fs_rhevm_func_tbl = {
+ "FS-RHEV-M",
+ fs_init,
+ fs_get_child,
+ fs_put_child,
+ bad_cache_child,
+ fs_delete,
+ fs_bcreate,
+ fs_rhevm_register,
+};
diff --git a/bootstrap.conf b/bootstrap.conf
index 2e4cf7f..2e7cb31 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -18,10 +18,12 @@
# gnulib modules used by this package.
gnulib_modules='
announce-gen
+base64
c-ctype
calloc-posix
close
closeout
+copy-file
dirname
do-release-commit-and-tag
dup2
diff --git a/configure.ac b/configure.ac
index e124f8d..e00e6b5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,11 @@ AC_CHECK_LIB([pthread], [pthread_create],
[AC_MSG_ERROR([Missing required pthread lib])])
AC_SUBST([PTHREAD_LIB])
+AC_CHECK_LIB([uuid], [uuid_generate_random],
+ [UUID_LIB=-luuid],
+ [AC_MSG_ERROR([Missing required uuid lib])])
+AC_SUBST([UUID_LIB])
+
PKG_CHECK_MODULES([HAIL],[libhail >= 0.8])
AC_SUBST([HAIL_LIBS])
AC_SUBST([HAIL_CFLAGS])
diff --git a/dc-rhev-image.c b/dc-rhev-image.c
new file mode 100644
index 0000000..ca991eb
--- /dev/null
+++ b/dc-rhev-image.c
@@ -0,0 +1,1568 @@
+/*
+ * dc-rhev-image: register an image in RHEV
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * The URL parser is taken from GNet (by way of tabled):
+ *
+ * GNet - Networking library
+ * Copyright (C) 2000-2003 David Helder, David Bolcsfoldi, Eric Williams
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/* need _GNU_SOURCE for asprintf */
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include <curl/curl.h>
+#include <jansson.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/encoding.h>
+#include <libxml/xmlwriter.h>
+#include <uuid/uuid.h>
+
+#include "base64.h"
+#include "close-stream.h"
+#include "copy-file.h"
+
+/*
+ * Note that we almost never prefix with TAG due to compatibility with EC2.
+ */
+#define TAG "dc-rhev-image"
+
+#define NFSUID 36
+#define NFSGID 36
+
+static const char NULID[] = "00000000-0000-0000-0000-000000000000";
+
+struct config {
+ char *image;
+ char *apiurl;
+ char *apiuser;
+ char *apipass;
+ char *nfshost;
+ char *nfspath;
+ char *nfsdir;
+};
+
+struct http_uri {
+ const char *scheme;
+ unsigned int scheme_len;
+ const char *userinfo;
+ unsigned int userinfo_len;
+ const char *hostname;
+ unsigned int hostname_len;
+
+ unsigned int port;
+
+ const char *path;
+ unsigned int path_len;
+ const char *query;
+ unsigned int query_len;
+ const char *fragment;
+ unsigned int fragment_len; /* see FIXME in huri_parse */
+};
+
+struct stor_dom {
+ char *uuid;
+ char *address;
+ char *path;
+ char *poolid;
+};
+
+struct api_buf {
+ char *buf;
+ size_t alloc;
+ size_t used;
+};
+
+struct api_conn {
+ CURL *curl;
+ char *base;
+};
+
+static void Usage(void)
+{
+ fprintf(stderr, "ERROR Usage: " TAG " [" TAG ".conf]\n");
+ exit(1);
+}
+
+static char **env_p;
+
+/* our own ISSPACE. ANSI isspace is locale dependent */
+#define ISSPACE(C) (((C) >= 9 && (C) <= 13) || (C) == ' ')
+/* oh what the heck, use this too */
+#define ISDIGIT(C) (((C) >= '0' && (C) <= '9'))
+
+#define ASSIGN(token, ptr, len) \
+ do { \
+ uri->token = (ptr); \
+ uri->token##_len = (len); \
+ } while (0)
+
+static struct http_uri *huri_parse(struct http_uri *uri, const char *uri_text)
+{
+ const char *p, *temp;
+
+ memset(uri, 0, sizeof(*uri));
+
+ /* Skip initial whitespace */
+ p = uri_text;
+ while (*p && ISSPACE((int)*p))
+ ++p;
+ if (!*p) /* Error if it's just a string of space */
+ return NULL;
+
+ /* Scheme */
+ temp = p;
+ while (*p && *p != ':' && *p != '/' && *p !=
'?' && *p != '#')
+ ++p;
+ if (*p == ':') {
+ ASSIGN(scheme, temp, p - temp);
+ ++p;
+ } else /* This char is NUL, /, ?, or # */
+ p = temp;
+
+ /* Authority */
+ if (*p == '/' && p[1] == '/') {
+ p += 2;
+
+ /* Userinfo */
+ temp = p;
+ while (*p && *p != '@' && *p != '/') /* Look for @ or /
*/
+ ++p;
+ if (*p == '@') { /* Found userinfo */
+ ASSIGN(userinfo, temp, p - temp);
+ ++p;
+ } else
+ p = temp;
+
+ /* Hostname */
+
+ /* Check for no hostname at all (e.g. file:// URIs) */
+ if (*p == '/')
+ goto path;
+
+ /* Check for IPv6 canonical hostname in brackets */
+ if (*p == '[') {
+ p++; /* Skip [ */
+ temp = p;
+ while (*p && *p != ']')
+ ++p;
+ if ((p - temp) == 0)
+ goto error;
+ ASSIGN(hostname, temp, p - temp);
+ if (*p)
+ p++; /* Skip ] (if there) */
+ } else {
+ temp = p;
+ while (*p && *p != '/' && *p != '?' && *p !=
'#'
+ && *p != ':')
+ ++p;
+ if ((p - temp) == 0)
+ goto error;
+ ASSIGN(hostname, temp, p - temp);
+ }
+
+ /* Port */
+ if (*p == ':') {
+ for (++p; ISDIGIT((int)*p); ++p)
+ uri->port = uri->port * 10 + (*p - '0');
+ }
+
+ }
+
+ /* Path (we are liberal and won't check if it starts with /) */
+
+path:
+ temp = p;
+ while (*p && *p != '?' && *p != '#')
+ ++p;
+ if (p != temp)
+ ASSIGN(path, temp, p - temp);
+
+ /* Query */
+ if (*p == '?') {
+ temp = p + 1;
+ while (*p && *p != '#')
+ ++p;
+ ASSIGN(query, temp, p - temp);
+ }
+
+ /* Fragment */
+ if (*p == '#') {
+ ++p;
+ uri->fragment = p;
+ /* FIXME: assign uri->fragment_len! */
+ }
+
+ return uri;
+
+error:
+ return NULL;
+}
+
+static int path_exists(const char *path)
+{
+ return access(path, R_OK) == 0;
+}
+
+static void
+cfg_veripick(char **cfgval, const char *cfgname, json_t *jcfg, char *cfgtag)
+{
+ json_t *elem;
+ const char *name;
+ char *tmp;
+
+ elem = json_object_get(jcfg, cfgtag);
+ if (!json_is_string(elem)) {
+ fprintf(stderr,
+ "ERROR configuration %s: tag %s is not a string\n",
+ cfgname, cfgtag);
+ exit(2);
+ }
+ name = json_string_value(elem);
+ if (!name) {
+ fprintf(stderr, "ERROR configuration %s: tag %s has no value\n",
+ cfgname, cfgtag);
+ exit(2);
+ }
+ tmp = strdup(name);
+ if (!tmp) {
+ fprintf(stderr, "ERROR no core\n");
+ exit(1);
+ }
+ *cfgval = tmp;
+}
+
+static size_t api_wcb(void *ptr, size_t bsz, size_t nmemb, void *arg)
+{
+ struct api_buf *bp = arg;
+ char *mem;
+ size_t len;
+
+ if (bp->alloc - bp->used < nmemb) {
+ len = (((bp->used + nmemb) / 2000) + 1) * 2000;
+ mem = realloc(bp->buf, len);
+ if (!mem)
+ return 0;
+ bp->buf = mem;
+ bp->alloc = len;
+ }
+ memcpy(bp->buf + bp->used, ptr, nmemb);
+ bp->used += nmemb;
+ return nmemb;
+}
+
+static void apipaths(struct api_conn *connection, struct curl_slist *headers,
+ char *path, char **psd, char **pdc)
+{
+ CURL *curl = connection->curl;
+ struct api_buf apib;
+ char *url;
+ xmlDocPtr doc;
+ xmlNode *etroot;
+ xmlNode *et;
+ xmlChar *reltype;
+ xmlChar *s;
+ char *pathsd = NULL, *pathdc = NULL;
+ CURLcode rcc;
+ int rc;
+
+ rc = asprintf(&url, "%s%s", connection->base, path);
+ if (rc < 0)
+ goto err_alloc;
+
+ memset(&apib, 0, sizeof(struct api_buf));
+ apib.buf = malloc(4000);
+ if (!apib.buf)
+ goto err_alloc;
+ apib.alloc = 4000;
+
+ // if (debugging) /* if (verbose) */
+ // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, api_wcb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &apib);
+
+ rcc = curl_easy_perform(curl);
+ if (rcc != CURLE_OK) {
+ fprintf(stderr, "ERROR curl failed GET url `%s'\n", url);
+ exit(1);
+ }
+
+ doc = xmlReadMemory(apib.buf, apib.used, "rhev-api.xml", NULL, 0);
+ if (doc == NULL) {
+ fprintf(stderr, "ERROR API parse error in `%s'\n", url);
+ exit(1);
+ }
+
+ etroot = xmlDocGetRootElement(doc);
+ if (strcmp((const char *)etroot->name, "api") != 0) {
+ fprintf(stderr, "ERROR API root is `api'\n");
+ exit(1);
+ }
+
+ for (et = etroot->children; et; et = et->next) {
+ if (et->type != XML_ELEMENT_NODE)
+ continue;
+ if (strcmp((const char *)et->name, "link") != 0)
+ continue;
+ reltype = xmlGetProp(et, (xmlChar *)"rel");
+ if (!reltype)
+ continue;
+ if (strcmp((char *)reltype, "storagedomains") == 0) {
+ s = xmlGetProp(et, (xmlChar *)"href");
+ if (!s)
+ goto err_href;
+ free(pathsd);
+ pathsd = strdup((char *)s);
+ if (!pathsd)
+ goto err_alloc;
+ xmlFree(s);
+ } else if (strcmp((char *)reltype, "datacenters") == 0) {
+ s = xmlGetProp(et, (xmlChar *)"href");
+ if (!s)
+ goto err_href;
+ free(pathdc);
+ pathdc = strdup((char *)s);
+ if (!pathdc)
+ goto err_alloc;
+ xmlFree(s);
+ }
+ xmlFree(reltype);
+ }
+
+ if (!pathsd) {
+ fprintf(stderr, "ERROR API has no `rel=storagedomains'\n");
+ exit(1);
+ }
+ if (!pathdc) {
+ fprintf(stderr, "ERROR API has no `rel=datacenters'\n");
+ exit(1);
+ }
+
+ *psd = pathsd;
+ *pdc = pathdc;
+
+ xmlFreeDoc(doc);
+ /* xmlCleanupParser(); -- hopefuly not necessary */
+ free(apib.buf);
+ free(url);
+ return;
+
+ err_alloc:
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+
+ err_href:
+ fprintf(stderr, "ERROR <link /> with no `href'\n");
+ exit(1);
+}
+
+static xmlNode *xmlGetChild(xmlNode *node, const char *name)
+{
+ xmlNode *et;
+
+ for (et = node->children; et; et = et->next) {
+ if (et->type != XML_ELEMENT_NODE)
+ continue;
+ if (strcmp((const char *)et->name, name) != 0)
+ continue;
+ return et;
+ }
+ return NULL;
+}
+
+static struct stor_dom *apistordom_1(struct config *cfg, xmlChar *uuidsd,
+ xmlNode *etstor)
+{
+ xmlNode *ettext, *etaddr, *etpath;
+ struct stor_dom *sd;
+
+ etaddr = xmlGetChild(etstor, "address");
+ if (!etaddr) {
+ fprintf(stderr,
+ "WARNIING NFS storage domain without address\n");
+ return NULL;
+ }
+ etpath = xmlGetChild(etstor, "path");
+ if (!etpath) {
+ fprintf(stderr,
+ "WARNIING NFS storage domain without path\n");
+ return NULL;
+ }
+
+ /*
+ * XXX canonicalize host in case of e.g. raw IPv4/IPv6 address.
+ */
+ ettext = etaddr->children; /* mysterious indirection */
+ if (!ettext || ettext->type!=XML_TEXT_NODE || !ettext->content)
+ return NULL;
+ if (strcmp((char *)ettext->content, cfg->nfshost) != 0) {
+ /*
+ * Why even print this message? Because we want to do at least
+ * something if configuration is ham-fisted. However, this
+ * will pop up for every server that has 2 NFS storage domains.
+ * For now we cop-out by tagging it "INFO".
+ */
+ fprintf(stderr, "INFO Host `%s' does not match cfg `%s'\n",
+ (char *)ettext->content, cfg->nfshost);
+ return NULL;
+ }
+ ettext = etpath->children; /* mysterious indirection */
+ if (!ettext || ettext->type!=XML_TEXT_NODE || !ettext->content)
+ return NULL;
+ if (strcmp((char *)ettext->content, cfg->nfspath) != 0) {
+ /*
+ * Why even print this message? Because we want to do at least
+ * something if configuration is ham-fisted. However, this
+ * will pop up for every server that has 2 NFS storage domains.
+ * For now we cop-out by tagging it "INFO".
+ */
+ fprintf(stderr, "INFO Host `%s' does not match cfg `%s'\n",
+ (char *)ettext->content, cfg->nfshost);
+ return NULL;
+ }
+
+ if (!(sd = malloc(sizeof(struct stor_dom)))) {
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+ }
+ memset(sd, 0, sizeof(struct stor_dom));
+ sd->uuid = strdup((char *)uuidsd);
+ if (!sd->uuid) {
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+ }
+ sd->address = cfg->nfshost;
+ sd->path = cfg->nfspath;
+
+ return sd;
+}
+
+static struct stor_dom *apistordom(struct config *cfg,
+ struct api_conn *connection, struct curl_slist *headers, char *pathsd)
+{
+ CURL *curl = connection->curl;
+ struct api_buf apib;
+ char *url;
+ struct stor_dom *sd;
+ xmlDocPtr doc;
+ xmlNode *etroot;
+ xmlNode *et;
+ xmlNode *ettype, *etstor, *ettext;
+ xmlChar *uuidsd;
+ CURLcode rcc;
+ int rc;
+
+ // XXX GET /rhevm-api/storagedomains/ crashes server, see bz#670397
+ // So for now we ignore the pathsd and use a query instead.
+ // rc = asprintf(&url, "%s%s", connection->base, pathsd);
+ rc = asprintf(&url, "%s%s", connection->base,
"/rhevm-api/storagedomains/?search=type%20%21%3D%20fcp");
+ if (rc < 0)
+ goto err_alloc;
+
+ memset(&apib, 0, sizeof(struct api_buf));
+ apib.buf = malloc(4000);
+ if (!apib.buf)
+ goto err_alloc;
+ apib.alloc = 4000;
+
+ // if (debugging) /* if (verbose) */
+ // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, api_wcb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &apib);
+
+ rcc = curl_easy_perform(curl);
+ if (rcc != CURLE_OK) {
+ fprintf(stderr, "ERROR curl failed GET url `%s'\n", url);
+ exit(1);
+ }
+
+ doc = xmlReadMemory(apib.buf, apib.used, "rhev-api.xml", NULL, 0);
+ if (doc == NULL) {
+ fprintf(stderr, "ERROR API parse error in `%s'\n", url);
+ exit(1);
+ }
+
+ etroot = xmlDocGetRootElement(doc);
+ if (strcmp((const char *)etroot->name, "storage_domains") != 0) {
+ fprintf(stderr, "ERROR API root is `storage_domains'\n");
+ exit(1);
+ }
+
+ sd = NULL;
+ for (et = etroot->children; et; et = et->next) {
+ if (et->type != XML_ELEMENT_NODE)
+ continue;
+ if (strcmp((const char *)et->name, "storage_domain") != 0)
+ continue;
+
+ ettype = xmlGetChild(et, "type");
+ if (!ettype)
+ continue;
+ ettext = ettype->children; /* mysterious indirection */
+ if (!ettext || ettext->type!=XML_TEXT_NODE || !ettext->content)
+ continue;
+ if (strcmp((char *)ettext->content, "EXPORT") != 0)
+ continue;
+
+ etstor = xmlGetChild(et, "storage");
+ if (!etstor)
+ continue;
+
+ ettype = xmlGetChild(etstor, "type");
+ if (!ettype)
+ continue;
+ ettext = ettype->children; /* mysterious indirection */
+ if (!ettext || ettext->type!=XML_TEXT_NODE || !ettext->content)
+ continue;
+ if (strcmp((char *)ettext->content, "NFS") != 0)
+ continue;
+
+ uuidsd = xmlGetProp(et, (xmlChar *)"id");
+ if (!uuidsd) {
+ fprintf(stderr,
+ "WARNING NFS storage domain without UUID\n");
+ continue;
+ }
+ sd = apistordom_1(cfg, uuidsd, etstor);
+ xmlFree(uuidsd);
+ if (sd)
+ break;
+ }
+
+ if (!sd) {
+ fprintf(stderr, "ERROR NFS storage domain for `%s' not found\n",
+ url);
+ exit(1);
+ }
+
+ xmlFreeDoc(doc);
+ /* xmlCleanupParser(); -- hopefuly not necessary */
+ free(apib.buf);
+ free(url);
+ return sd;
+
+ err_alloc:
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+ return NULL;
+}
+
+static int apipoolid(struct config *cfg, struct api_conn *connection,
+ struct curl_slist *headers, xmlChar *pathsds, char *sd_uuid)
+{
+ CURL *curl = connection->curl;
+ struct api_buf apib;
+ char *url;
+ xmlDocPtr doc;
+ xmlNode *etsd;
+ xmlNode *etroot;
+ xmlChar *xid;
+ CURLcode rcc;
+ int rc;
+
+ rc = asprintf(&url, "%s%s", connection->base, pathsds);
+ if (rc < 0)
+ goto err_alloc;
+
+ memset(&apib, 0, sizeof(struct api_buf));
+ apib.buf = malloc(4000);
+ if (!apib.buf)
+ goto err_alloc;
+ apib.alloc = 4000;
+
+ // if (debugging) /* if (verbose) */
+ // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, api_wcb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &apib);
+
+ rcc = curl_easy_perform(curl);
+ if (rcc != CURLE_OK) {
+ fprintf(stderr, "ERROR curl failed GET url `%s'\n", url);
+ exit(1);
+ }
+
+ doc = xmlReadMemory(apib.buf, apib.used, "rhev-api.xml", NULL, 0);
+ if (doc == NULL) {
+ fprintf(stderr, "ERROR API parse error in `%s'\n", url);
+ exit(1);
+ }
+
+ etroot = xmlDocGetRootElement(doc);
+ if (strcmp((const char *)etroot->name, "storage_domains") != 0) {
+ fprintf(stderr, "ERROR API root is `storage_domains'\n");
+ exit(1);
+ }
+
+ for (etsd = etroot->children; etsd; etsd = etsd->next) {
+ if (etsd->type != XML_ELEMENT_NODE)
+ continue;
+ if (strcmp((const char *)etsd->name, "storage_domain") != 0)
+ continue;
+
+ xid = xmlGetProp(etsd, (xmlChar *)"id");
+ if (!xid) {
+ fprintf(stderr,
+ "WARNING storage domain without UUID\n");
+ continue;
+ }
+ if (strcmp((char *)xid, sd_uuid) == 0) {
+ xmlFree(xid);
+ rc = 1;
+ goto ret;
+ }
+ xmlFree(xid);
+ }
+
+ rc = 0;
+ret:
+ xmlFreeDoc(doc);
+ /* xmlCleanupParser(); -- hopefuly not necessary */
+ free(apib.buf);
+ free(url);
+ return rc;
+
+ err_alloc:
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+ return 0;
+}
+
+static char *apipool(struct config *cfg, struct api_conn *connection,
+ struct curl_slist *headers, char *pathdc, char *sd_uuid)
+{
+ CURL *curl = connection->curl;
+ struct api_buf apib;
+ char *url;
+ char *ret;
+ xmlDocPtr doc;
+ xmlNode *etroot;
+ xmlNode *etdc, *etlink;
+ // xmlNode *ettype, *etstor, *ettext;
+ xmlChar *uuiddc;
+ xmlChar *rel, *href;
+ CURLcode rcc;
+ int rc;
+
+ rc = asprintf(&url, "%s%s", connection->base, pathdc);
+ if (rc < 0)
+ goto err_alloc;
+
+ memset(&apib, 0, sizeof(struct api_buf));
+ apib.buf = malloc(4000);
+ if (!apib.buf)
+ goto err_alloc;
+ apib.alloc = 4000;
+
+ // if (debugging) /* if (verbose) */
+ // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, api_wcb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &apib);
+
+ rcc = curl_easy_perform(curl);
+ if (rcc != CURLE_OK) {
+ fprintf(stderr, "ERROR curl failed GET url `%s'\n", url);
+ exit(1);
+ }
+
+ /* <--- cleanup curl connection here if needed */
+
+ doc = xmlReadMemory(apib.buf, apib.used, "rhev-api-dc.xml", NULL, 0);
+ if (doc == NULL) {
+ fprintf(stderr, "ERROR API parse error in `%s'\n", url);
+ exit(1);
+ }
+
+ etroot = xmlDocGetRootElement(doc);
+ if (strcmp((const char *)etroot->name, "data_centers") != 0) {
+ fprintf(stderr, "ERROR API root is `data_centers'\n");
+ exit(1);
+ }
+
+ for (etdc = etroot->children; etdc; etdc = etdc->next) {
+ if (etdc->type != XML_ELEMENT_NODE)
+ continue;
+ uuiddc = xmlGetProp(etdc, (xmlChar *)"id");
+ if (!uuiddc) {
+ fprintf(stderr,
+ "WARNING NFS storage domain without UUID\n");
+ continue;
+ }
+ for (etlink = etdc->children; etlink; etlink = etlink->next) {
+ if (etlink->type != XML_ELEMENT_NODE)
+ continue;
+ /* most likely it's not even a link, but "name" or "version"
*/
+ if (strcmp((const char *)etlink->name, "link") != 0)
+ continue;
+ rel = xmlGetProp(etlink, (xmlChar *)"rel");
+ if (!rel)
+ continue;
+ /* there's a bunch of other links like "files", "permissions"
*/
+ if (strcmp((const char *)rel, "storagedomains") != 0) {
+ xmlFree(rel);
+ continue;
+ }
+ href = xmlGetProp(etlink, (xmlChar *)"href");
+ if (!href) {
+ fprintf(stderr,
+ "WARNING data canter link without href\n");
+ xmlFree(rel);
+ continue;
+ }
+
+ if (apipoolid(cfg, connection, headers, href, sd_uuid)) {
+ /* StoragePoolId is Data Center's UUID */
+
+ ret = strdup((char *)uuiddc);
+ if (!ret)
+ goto err_alloc;
+ xmlFree(href);
+ xmlFree(rel);
+
+ xmlFree(uuiddc);
+ xmlFreeDoc(doc);
+ /* xmlCleanupParser(); -- hopefuly not necessary */
+ free(apib.buf);
+ free(url);
+ return ret;
+ }
+ xmlFree(href);
+ xmlFree(rel);
+ }
+ xmlFree(uuiddc);
+ }
+
+ fprintf(stderr, "ERROR pool not found for storage domain %s\n", sd_uuid);
+ exit(1);
+ return NULL;
+
+ err_alloc:
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+ return NULL;
+}
+
+static struct stor_dom *apistart(struct config *cfg)
+{
+ struct http_uri huri;
+ struct stor_dom *sd;
+ char *host, *path, *base;
+ char *pathsd, *pathdc;
+ char *authraw, *authhdr;
+ size_t authrlen, authhlen;
+ struct curl_slist *headers, *h;
+ struct api_conn connection;
+ int rc;
+
+ /*
+ * Note that using a proper URL parser means that we drop query and
+ * fragment from the configured URL. Fortunately, it is never a factor
+ * in RHEV-M API, due to its RESTful nature.
+ */
+ if (!huri_parse(&huri, cfg->apiurl)) {
+ fprintf(stderr, "ERROR unable to parse `apiurl': `%s'\n",
+ cfg->apiurl);
+ exit(1);
+ }
+
+ path = strndup(huri.path, huri.path_len);
+ if (!path)
+ goto err_alloc;
+ host = strndup(huri.hostname, huri.hostname_len);
+ if (!host)
+ goto err_alloc;
+
+ if (huri.port)
+ rc = asprintf(&base, "http://%s:%u", host, huri.port);
+ else
+ rc = asprintf(&base, "http://%s", host);
+ if (rc < 0)
+ goto err_alloc;
+
+ connection.base = base;
+ connection.curl = curl_easy_init();
+
+ headers = NULL;
+
+ rc = asprintf(&authraw, "%s:%s", cfg->apiuser, cfg->apipass);
+ if (rc < 0)
+ goto err_alloc;
+ authrlen = strlen(authraw);
+ authhlen = ((authrlen+2)/3) * 4; /* base64 expands 3 into 4 */
+ authhlen += sizeof("Authorization: Basic ")-1;
+ authhdr = malloc(authhlen + 3); /* \r\n and nul */
+ if (!authhdr)
+ goto err_alloc;
+ strcpy(authhdr, "Authorization: Basic ");
+ base64_encode(authraw, authrlen,
+ authhdr + (sizeof("Authorization: Basic ")-1),
+ authhlen - (sizeof("Authorization: Basic ")-1));
+ strcat(authhdr, "\r\n");
+
+ h = curl_slist_append(headers, authhdr);
+ if (!h)
+ goto err_alloc;
+ headers = h;
+
+ /*
+ * Step 1, fetch the API root
+ *
+ * This is largely a formality, because if we know names of resources,
+ * why not to fetch resources directly? But the docs imply we should
+ * jump through this hoop.
+ */
+ apipaths(&connection, headers, path, &pathsd, &pathdc);
+
+ /*
+ * We should pull the API version and check that it's more than 2.3 XXX
+ *
+ * Step 2, connect again, get domains
+ */
+ sd = apistordom(cfg, &connection, headers, pathsd);
+
+ /*
+ * Step 3, find the "storage pool ID"
+ *
+ * This is crazy to be a separate step. You'd expect the storage domain
+ * descriptor itself contain its pool, to be saved on Step 2. But no.
+ */
+ sd->poolid = apipool(cfg, &connection, headers, pathdc, sd->uuid);
+
+ curl_easy_cleanup(connection.curl);
+
+ free(authhdr);
+ free(authraw);
+ free(host);
+ free(path);
+ free(base);
+ return sd;
+
+ err_alloc:
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+}
+
+static void spitovf(struct config *cfg, struct stor_dom *sd,
+ uuid_t img_uuid, uuid_t vol_uuid, off_t vol_size, uuid_t tpl_uuid)
+{
+ time_t now;
+ struct tm now_tm;
+ char now_str[50];
+ xmlTextWriterPtr writer;
+ char uuidbuf[37];
+ char buf100[100];
+ char *s;
+ char *domdir, *tmpovfdir, *tmpovf, *tmpimgdir, *imgdir, *ovfdir;
+ int rc;
+
+ uuid_generate_random(tpl_uuid);
+
+ rc = asprintf(&domdir, "%s/%s", cfg->nfsdir, sd->uuid);
+ if (rc < 0)
+ goto err_alloc;
+ uuid_unparse_lower(tpl_uuid, uuidbuf);
+ rc = asprintf(&tmpovfdir, "%s/iwhd.%s", domdir, uuidbuf);
+ if (rc < 0)
+ goto err_alloc;
+ uuid_unparse_lower(tpl_uuid, uuidbuf);
+ rc = asprintf(&tmpovf, "%s/%s.ovf", tmpovfdir, uuidbuf);
+ if (rc < 0)
+ goto err_alloc;
+ if (mkdir(tmpovfdir, 0770) < 0) {
+ fprintf(stderr, "ERROR Failed to make directory %s: %s\n",
+ tmpovfdir, strerror(errno));
+ exit(1);
+ }
+
+ uuid_unparse_lower(img_uuid, uuidbuf);
+ rc = asprintf(&tmpimgdir, "%s/iwhd.%s", domdir, uuidbuf);
+ if (rc < 0)
+ goto err_alloc;
+ uuid_unparse_lower(img_uuid, uuidbuf);
+ rc = asprintf(&imgdir, "%s/images/%s", domdir, uuidbuf);
+ if (rc < 0)
+ goto err_alloc;
+
+ uuid_unparse_lower(tpl_uuid, uuidbuf);
+ rc = asprintf(&ovfdir, "%s/master/vms/%s", domdir, uuidbuf);
+ if (rc < 0)
+ goto err_alloc;
+
+ now = time(NULL);
+ gmtime_r(&now, &now_tm);
+ strftime(now_str, 50, "%Y/%m/%d %H:%M:%S", &now_tm);
+
+ writer = xmlNewTextWriterFilename(tmpovf, 0);
+ if (!writer) {
+ fprintf(stderr, "ERROR Error creating the xml driver for %s\n",
+ tmpovf);
+ exit(1);
+ }
+
+ rc = xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL);
+ if (rc < 0) {
+ fprintf(stderr, "ERROR Error in xmlTextWriterStartDocument\n");
+ exit(1);
+ }
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "ovf:Envelope");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:version", BAD_CAST "0.9");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xmlns:ovf",
+ BAD_CAST "http://schemas.dmtf.org/ovf/envelope/1/");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xmlns:rasd",
+ BAD_CAST
"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xmlns:vssd",
+ BAD_CAST
"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xmlns:xsi",
+ BAD_CAST "http://www.w3.org/2001/XMLSchema-instance");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "References");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "File");
+ if (rc < 0) goto err_xml;
+ uuid_unparse_lower(img_uuid, buf100);
+ buf100[36] = '/';
+ uuid_unparse_lower(vol_uuid, buf100+37);
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:href", BAD_CAST buf100);
+ if (rc < 0) goto err_xml;
+ uuid_unparse_lower(vol_uuid, uuidbuf);
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:id", BAD_CAST uuidbuf);
+ if (rc < 0) goto err_xml;
+ snprintf(buf100, 100, "%llu", (long long) vol_size);
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:size", BAD_CAST buf100);
+ if (rc < 0) goto err_xml;
+ /* This is basename(), but we handroll it to make sure, due to BSD. */
+ if (!(s = strrchr(cfg->image, '/')) || s[1]==0)
+ s = cfg->image;
+ else
+ s++;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:description", BAD_CAST s);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterEndElement(writer); /* close <File> */
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterEndElement(writer); /* close <References> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Section");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xsi:type", BAD_CAST "ovf:NetworkSection_Type");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "Info", BAD_CAST "List of Networks");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterEndElement(writer); /* close <Section> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Section");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xsi:type", BAD_CAST "ovf:DiskSection_Type");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Disk");
+ if (rc < 0) goto err_xml;
+ uuid_unparse_lower(vol_uuid, uuidbuf);
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:diskId", BAD_CAST uuidbuf);
+ if (rc < 0) goto err_xml;
+ snprintf(buf100, 100, "%llu",
+ (long long)((vol_size + (1024*1024*1024) - 1) / (1024*1024*1024)));
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:size", BAD_CAST buf100);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:actual_size", BAD_CAST buf100);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:vm_snapshot_id", (const xmlChar *) NULID);
+ if (rc < 0) goto err_xml;
+ uuid_unparse_lower(img_uuid, buf100);
+ buf100[36] = '/';
+ uuid_unparse_lower(vol_uuid, buf100+37);
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:fileRef", BAD_CAST buf100);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:format",
+ BAD_CAST
"http://www.vmware.com/technical-resources/interfaces/vmdk_access.html");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:volume-format", BAD_CAST "RAW");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:volume-type", BAD_CAST "Preallocated");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:disk-interface", BAD_CAST "VirtIO");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:disk-type", BAD_CAST "System");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:boot", BAD_CAST "true");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:wipe-after-delete", BAD_CAST "false");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterEndElement(writer); /* close <Disk> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndElement(writer); /* close <Section> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Content");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xsi:type", BAD_CAST "ovf:VirtualSystem_Type");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:id", BAD_CAST "out");
+ if (rc < 0) goto err_xml;
+
+ if (!(s = strrchr(cfg->image, '/'))) s = cfg->image;
+ rc = xmlTextWriterWriteElement(writer, BAD_CAST "Name", BAD_CAST s);
+ if (rc < 0) goto err_xml;
+
+ uuid_unparse_lower(tpl_uuid, uuidbuf);
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "TemplateId", BAD_CAST uuidbuf);
+ if (rc < 0) goto err_xml;
+ /* spec also has 'TemplateName' */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "Description", BAD_CAST "Template by iwhd");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "Domain", BAD_CAST "");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "CreationDate", BAD_CAST now_str);
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "TimeZone", BAD_CAST "");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "IsAutoSuspend", BAD_CAST "false");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "VmType", BAD_CAST "1");
+ if (rc < 0) goto err_xml;
+
+ /* vnc = 0, gxl = 1 */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "default_display_type", BAD_CAST "0");
+ if (rc < 0) goto err_xml;
+
+ /*
+ * C=0, DC=1, N=2, CDN=3, CND=4, DCN=5, DNC=6, NCD=7,
+ * NDC=8, CD=9, D=10, CN=11, DN=12, NC=13, ND=14
+ * (C - HardDisk, D - CDROM, N - Network)
+ */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "default_boot_sequence", BAD_CAST "1");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Section");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xsi:type", BAD_CAST
"ovf:OperatingSystemSection_Type");
+ if (rc < 0) goto err_xml;
+ uuid_unparse_lower(tpl_uuid, uuidbuf);
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:id", BAD_CAST uuidbuf);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "ovf:required", BAD_CAST "false");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "Info", BAD_CAST "Guest OS");
+ if (rc < 0) goto err_xml;
+ /* This is rigid, must be "Other", "OtherLinux", "RHEL6",
or such. */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "Description", BAD_CAST "OtherLinux");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterEndElement(writer); /* close <Section> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Section");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "xsi:type", BAD_CAST
"ovf:VirtualHardwareSection_Type");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "Info", BAD_CAST "1 CPU, 512 Memory");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "System");
+ if (rc < 0) goto err_xml;
+ /* This is probably wrong, needs actual type. */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "vssd:VirtualSystemType", BAD_CAST "RHEVM 4.6.0.163");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterEndElement(writer); /* close <System> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Item");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Caption", BAD_CAST "1 virtual CPU");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Description", BAD_CAST "Number of virtual CPU");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:InstanceId", BAD_CAST "1");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:ResourceType", BAD_CAST "3");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:num_of_sockets", BAD_CAST "1");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:cpu_per_socket", BAD_CAST "1");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndElement(writer); /* close <Item> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Item");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Caption", BAD_CAST "512 MB of memory");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Description", BAD_CAST "Memory Size");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:InstanceId", BAD_CAST "2");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:ResourceType", BAD_CAST "4");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:AllocationUnits", BAD_CAST "MegaBytes");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:VirtualQuantity", BAD_CAST "512");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndElement(writer); /* close <Item> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Item");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Caption", BAD_CAST "Drive 1");
+ if (rc < 0) goto err_xml;
+ /* Why no rasd:Description for disks? */
+ uuid_unparse_lower(vol_uuid, uuidbuf);
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:InstanceId", BAD_CAST uuidbuf);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:ResourceType", BAD_CAST "17");
+ if (rc < 0) goto err_xml;
+ uuid_unparse_lower(img_uuid, buf100);
+ buf100[36] = '/';
+ uuid_unparse_lower(vol_uuid, buf100+37);
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:HostResource", BAD_CAST buf100);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Parent",
+ (const xmlChar *) NULID);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Template",
+ (const xmlChar *) NULID);
+ if (rc < 0) goto err_xml;
+ /* List of installed applications, separated by comma */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:ApplicationList", BAD_CAST "");
+ if (rc < 0) goto err_xml;
+ /*
+ * "Storage Domain Id"
+ * This corresponds to ID of volgroup in host where snapshot was taken.
+ * Obviously we have nothing like it.
+ */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:StorageId",
+ (const xmlChar *) NULID);
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:StoragePoolId", BAD_CAST sd->poolid);
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:CreationDate", BAD_CAST now_str);
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:LastModified", BAD_CAST now_str);
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndElement(writer); /* close <Item> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Item");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Caption", BAD_CAST "Ethernet 0 rhevm");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:InstanceId", BAD_CAST "3");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:ResourceType", BAD_CAST "10");
+ if (rc < 0) goto err_xml;
+ /* e1000 = 2, pv = 3 */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:ResourceSubType", BAD_CAST "3");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Connection", BAD_CAST "rhevm");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Name", BAD_CAST "eth0");
+ if (rc < 0) goto err_xml;
+
+ /* also allowed is "MACAddress" */
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:speed", BAD_CAST "1000");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndElement(writer); /* close <Item> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterStartElement(writer, BAD_CAST "Item");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:Caption", BAD_CAST "Graphics");
+ if (rc < 0) goto err_xml;
+ /* doc says "6", reality is "5" */
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:InstanceId", BAD_CAST "5");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:ResourceType", BAD_CAST "20");
+ if (rc < 0) goto err_xml;
+ rc = xmlTextWriterWriteElement(writer,
+ BAD_CAST "rasd:VirtualQuantity", BAD_CAST "1");
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndElement(writer); /* close <Item> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndElement(writer); /* close <Section> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndElement(writer); /* close <Content> */
+ if (rc < 0) goto err_xml;
+
+ rc = xmlTextWriterEndDocument(writer);
+ if (rc < 0) {
+ fprintf(stderr, "ERROR Error in xmlTextWriterEndDocument\n");
+ exit(1);
+ }
+
+ xmlFreeTextWriter(writer);
+
+ if (rename(tmpimgdir, imgdir) < 0) {
+ fprintf(stderr, "ERROR Failed to rename from %s to %s: %s\n",
+ tmpimgdir, imgdir, strerror(errno));
+ exit(1);
+ }
+ if (rename(tmpovfdir, ovfdir) < 0) {
+ fprintf(stderr, "ERROR Failed to rename from %s to %s: %s\n",
+ tmpovfdir, ovfdir, strerror(errno));
+ exit(1);
+ }
+
+ free(domdir);
+ free(tmpovfdir);
+ free(tmpovf);
+ free(tmpimgdir);
+ free(imgdir);
+ free(ovfdir);
+ return;
+
+ err_alloc:
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+
+ err_xml:
+ fprintf(stderr, "ERROR Failed to form XML\n");
+ exit(1);
+}
+
+static void copyimage(struct config *cfg, struct stor_dom *sd, uuid_t tpl_uuid)
+{
+ int rc;
+ struct stat statb;
+ uuid_t vol_uuid, img_uuid;
+ char *domdir, *tmpimgdir;
+ char *imgsrc, *imgdst, *imgmeta;
+ char uuidbuf[37];
+ time_t now;
+ off_t vol_size;
+ FILE *fp;
+
+ if (geteuid() == 0) {
+ /* printf("We're root, changing user and group to 36"); */
+ setregid(-1, NFSGID);
+ setreuid(-1, NFSUID);
+ } else {
+ if (geteuid() != NFSUID || getegid() != NFSGID) {
+ fprintf(stderr,
+ "ERROR Have to run with user and group 36\n");
+ exit(1);
+ }
+ }
+
+ rc = asprintf(&domdir, "%s/%s", cfg->nfsdir, sd->uuid);
+ if (rc < 0)
+ goto err_alloc;
+ if (stat(domdir, &statb) < 0) {
+ fprintf(stderr, "ERROR failed to stat %s: %s\n",
+ domdir, strerror(errno));
+ exit(1);
+ }
+ if (!S_ISDIR(statb.st_mode)) {
+ fprintf(stderr, "ERROR path %s is not a directory\n", domdir);
+ exit(1);
+ }
+
+ uuid_generate_random(vol_uuid);
+ uuid_generate_random(img_uuid);
+
+ /* XXX Do something about garbage-collecting iwhd.* directories */
+
+ uuid_unparse_lower(img_uuid, uuidbuf);
+ rc = asprintf(&tmpimgdir, "%s/iwhd.%s", domdir, uuidbuf);
+ if (rc < 0)
+ goto err_alloc;
+ if (mkdir(tmpimgdir, 0770) < 0) {
+ fprintf(stderr, "ERROR Failed to make directory %s: %s\n",
+ tmpimgdir, strerror(errno));
+ exit(1);
+ }
+
+ now = time(NULL);
+
+ imgsrc = cfg->image;
+ uuid_unparse_lower(vol_uuid, uuidbuf);
+ rc = asprintf(&imgdst, "%s/%s", tmpimgdir, uuidbuf);
+ if (rc < 0)
+ goto err_alloc;
+
+ /* No error processing here, it exits on error. */
+ copy_file_preserving(imgsrc, imgdst);
+
+ if (stat(imgdst, &statb) < 0) {
+ fprintf(stderr, "ERROR failed to stat %s: %s\n",
+ imgdst, strerror(errno));
+ exit(1);
+ }
+ vol_size = statb.st_size;
+
+ rc = asprintf(&imgmeta, "%s.meta", imgdst);
+ if (rc < 0)
+ goto err_alloc;
+
+ fp = fopen(imgmeta, "w");
+ if (!fp) {
+ fprintf(stderr, "ERROR Failed to create %s: %s\n",
+ imgmeta, strerror(errno));
+ exit(1);
+ }
+ fprintf(fp, "DOMAIN=%s\n", sd->uuid);
+ /* saved template has VOLTYPE=SHARED */
+ fprintf(fp, "VOLTYPE=LEAF\n");
+ fprintf(fp, "CTIME=%llu\n", (long long)now);
+ /* saved template has FORMAT=COW */
+ fprintf(fp, "FORMAT=RAW\n");
+ uuid_unparse_lower(img_uuid, uuidbuf);
+ fprintf(fp, "IMAGE=%s\n", uuidbuf);
+ fprintf(fp, "DISKTYPE=1\n");
+ fprintf(fp, "PUUID=%s\n", NULID);
+ fprintf(fp, "LEGALITY=LEGAL\n");
+ fprintf(fp, "MTIME=%llu\n", (long long)now);
+ fprintf(fp, "POOL_UUID=%s\n", sd->poolid);
+ /* assuming 1KB alignment, 512 is good for sure */
+ fprintf(fp, "SIZE=%llu\n", (long long) (vol_size/512));
+ fprintf(fp, "TYPE=SPARSE\n");
+ fprintf(fp, "DESCRIPTION=Uploaded by iwhd+rhevreg\n");
+ fprintf(fp, "EOF\n");
+ if (close_stream(fp)) {
+ fprintf(stderr, "ERROR Failed to write %s\n", imgmeta);
+ exit(1);
+ }
+
+ spitovf(cfg, sd, img_uuid, vol_uuid, vol_size, tpl_uuid);
+
+ free(imgmeta);
+ free(imgdst);
+ free(tmpimgdir);
+ free(domdir);
+ return;
+
+ err_alloc:
+ fprintf(stderr, "ERROR No core\n");
+ exit(1);
+}
+
+int main(int argc, char **argv, char **envp)
+{
+ uuid_t tpl_uuid;
+ json_error_t err;
+ char *cfgname;
+ struct config cfg;
+ json_t *jcfg;
+ struct stor_dom *sd;
+ char uuidbuf[37];
+
+ env_p = envp;
+
+ if (argc == 1) {
+ cfgname = TAG ".conf";
+ } else if (argc == 2) {
+ cfgname = argv[1];
+ if (cfgname[0] == 0 || cfgname[0] == '-')
+ Usage();
+ } else {
+ Usage();
+ return 2; /* gcc warning about cfgname initialization */
+ }
+
+ memset(&cfg, 0, sizeof(struct config));
+
+ jcfg = json_load_file(cfgname, &err);
+ if (!jcfg) {
+ fprintf(stderr, "ERROR configuration JSON error %s:%d: %s\n",
+ cfgname, err.line, err.text);
+ exit(2);
+ }
+
+ if (json_typeof(jcfg) != JSON_OBJECT) {
+ fprintf(stderr,
+ "ERROR configuration JSON %s: root is not a struct\n",
+ cfgname);
+ exit(2);
+ }
+
+ /* image: local filename with disk image */
+ cfg_veripick(&cfg.image, cfgname, jcfg, "image");
+ if (!path_exists(cfg.image)) {
+ fprintf(stderr, "ERROR image %s does not exist\n", cfg.image);
+ exit(1);
+ }
+ /* apiurl: the so-called "base", usually "rhev-api" */
+ cfg_veripick(&cfg.apiurl, cfgname, jcfg, "apiurl");
+ /*
+ * apiuser: username(a)AD.domain
+ * We do not enforce with '@' syntax in case someone comes up with
+ * RHEV-M that takes local authentication of some other trick.
+ */
+ cfg_veripick(&cfg.apiuser, cfgname, jcfg, "apiuser");
+ /* apipass: password for apiuser */
+ cfg_veripick(&cfg.apipass, cfgname, jcfg, "apipass");
+ /*
+ * nfshost: NFS server name
+ * nfspath: export path
+ * All RHEV-M servers have several storage domains, so we use this to
+ * select one. Also, we verify if 'nfsdir' points where it should.
+ */
+ cfg_veripick(&cfg.nfshost, cfgname, jcfg, "nfshost");
+ cfg_veripick(&cfg.nfspath, cfgname, jcfg, "nfspath");
+ /*
+ * nfsdir: A directory where sysadmin or boot scripts or autofs mounted
+ * the same area that RHEV-M considers an export domain (/mnt/vdsm/rhevm23).
+ * N.B. If iwhd is running on the NFS server itself, nfsdir can be the
+ * exported directory itself (like /home/vdsm/rhevm23). We verify that
+ * the directory contains an expected structure, so attach it to RHEV-M
+ * before trying to register any images.
+ */
+ cfg_veripick(&cfg.nfsdir, cfgname, jcfg, "nfsdir");
+ if (cfg.nfsdir[0] == 0) {
+ fprintf(stderr, "ERROR configuration: `nfsdir' is empty\n");
+ exit(2);
+ }
+ if (cfg.nfsdir[0] != '/') {
+ fprintf(stderr, "ERROR configuration: `nfsdir' is relative\n");
+ exit(2);
+ }
+
+ json_decref(jcfg);
+ jcfg = NULL;
+
+ sd = apistart(&cfg);
+
+ copyimage(&cfg, sd, tpl_uuid);
+
+ uuid_unparse_lower(tpl_uuid, uuidbuf);
+ printf("IMAGE %s\n", uuidbuf);
+ return 0;
+}
diff --git a/gnulib-tests/.gitignore b/gnulib-tests/.gitignore
index 259f316..43fcc40 100644
--- a/gnulib-tests/.gitignore
+++ b/gnulib-tests/.gitignore
@@ -9,6 +9,7 @@
/float+.h
/float.h
/float.in.h
+/ftello.c
/getpagesize.c
/gnulib.mk
/ignore-value.h
@@ -16,6 +17,7 @@
/init.sh
/inttostr.c
/inttostr.h
+/lseek.c
/lstat.c
/macros.h
/malloca.c
@@ -31,12 +33,15 @@
/priv-set.c
/priv-set.h
/putenv.c
+/read-file.c
+/read-file.h
/same-inode.h
/setenv.c
/signature.h
/size_max.h
/snprintf.c
/stat.c
+/stdio-impl.h
/stdio-write.c
/stdio.h
/stdio.in.h
@@ -45,17 +50,28 @@
/sys_stat.h
/sys_stat.in.h
/test-alloca-opt.c
+/test-base64.c
/test-binary-io.c
/test-binary-io.sh
/test-bitrotate.c
/test-c-ctype.c
+/test-copy-acl.c
+/test-copy-acl.sh
+/test-copy-file.c
+/test-copy-file.sh
/test-dirname.c
/test-dup2.c
/test-environ.c
/test-errno.c
/test-fcntl-h.c
+/test-file-has-acl.c
+/test-file-has-acl.sh
/test-fpending.c
/test-fpending.sh
+/test-ftello.c
+/test-ftello.sh
+/test-ftello2.sh
+/test-ftello3.c
/test-getopt.c
/test-getopt.h
/test-getopt_long.h
@@ -64,6 +80,8 @@
/test-ignore-value.c
/test-inttostr.c
/test-inttypes.c
+/test-lseek.c
+/test-lseek.sh
/test-lstat.c
/test-lstat.h
/test-malloc-gnu.c
@@ -82,7 +100,11 @@
/test-priv-set.c
/test-quotearg-simple.c
/test-quotearg.h
+/test-read-file.c
/test-realloc-gnu.c
+/test-sameacls.c
+/test-set-mode-acl.c
+/test-set-mode-acl.sh
/test-setenv.c
/test-snprintf.c
/test-stat.c
diff --git a/iwhd.spec.in b/iwhd.spec.in
index 629dd50..b6ec514 100644
--- a/iwhd.spec.in
+++ b/iwhd.spec.in
@@ -23,6 +23,7 @@ BuildRequires: jansson-devel
BuildRequires: libcurl-devel
BuildRequires: libmicrohttpd-devel
BuildRequires: libxml2-devel
+BuildRequires: libuuid-devel
BuildRequires: mongodb-devel
BuildRequires: bison
BuildRequires: flex
diff --git a/lib/.gitignore b/lib/.gitignore
index bf5c36c..f7907b8 100644
--- a/lib/.gitignore
+++ b/lib/.gitignore
@@ -1,6 +1,12 @@
+/acl-internal.h
+/acl.h
+/acl_entries.c
/arg-nonnull.h
+/base64.c
+/base64.h
/basename-lgpl.c
/basename.c
+/binary-io.h
/bitrotate.h
/c++defs.h
/c-ctype.c
@@ -16,6 +22,9 @@
/closeout.h
/config.charset
/configmake.h
+/copy-acl.c
+/copy-file.c
+/copy-file.h
/dirname-lgpl.c
/dirname.c
/dirname.h
@@ -27,8 +36,13 @@
/exitfail.c
/exitfail.h
/fclose.c
+/fcntl.h
+/fcntl.in.h
+/file-has-acl.c
/fpending.c
/fpending.h
+/full-write.c
+/full-write.h
/getopt.c
/getopt.h
/getopt.in.h
@@ -55,6 +69,7 @@
/memchr.c
/memchr.valgrind
/mkstemp.c
+/open.c
/pipe.c
/progname.c
/progname.h
@@ -67,6 +82,11 @@
/ref-add.sin
/ref-del.sed
/ref-del.sin
+/safe-read.c
+/safe-read.h
+/safe-write.c
+/safe-write.h
+/set-mode-acl.c
/stat.c
/stdarg.h
/stdarg.in.h
@@ -121,6 +141,7 @@
/wchar.in.h
/wctype.h
/wctype.in.h
+/write.c
/xalloc-die.c
/xalloc.h
/xmalloc.c
diff --git a/setup.c b/setup.c
index 3f35355..13b9943 100644
--- a/setup.c
+++ b/setup.c
@@ -65,6 +65,7 @@ extern backend_func_tbl s3_func_tbl;
extern backend_func_tbl curl_func_tbl;
extern backend_func_tbl cf_func_tbl;
extern backend_func_tbl fs_func_tbl;
+extern backend_func_tbl fs_rhevm_func_tbl;
static json_t *config = NULL;
static Hash_table *prov_hash = NULL;
@@ -210,7 +211,7 @@ json_validate_server (unsigned int i)
else if (!strcasecmp(type,"http")) {
needs = NEED_SERVER;
}
- else if (!strcasecmp(type,"fs")) {
+ else if (!strcasecmp(type,"fs") || !strcasecmp(type,"fs-rhev-m")) {
needs = NEED_PATH;
}
else {
@@ -328,6 +329,9 @@ convert_provider (int i, provider_t *out)
else if (!strcasecmp(out->type,"fs")) {
out->func_tbl = &fs_func_tbl;
}
+ else if (!strcasecmp(out->type,"fs-rhev-m")) {
+ out->func_tbl = &fs_rhevm_func_tbl;
+ }
else {
out->func_tbl = &bad_func_tbl;
}
@@ -417,6 +421,8 @@ add_provider (Hash_table *h)
prov->func_tbl = &cf_func_tbl;
else if (!strcasecmp(prov->type,"fs"))
prov->func_tbl = &fs_func_tbl;
+ else if (!strcasecmp(prov->type,"fs-rhev-m"))
+ prov->func_tbl = &fs_rhevm_func_tbl;
else
prov->func_tbl = &bad_func_tbl;