/* * openvz_conf.c: config functions for managing OpenVZ VEs * * Copyright (C) 2006, 2007 Binary Karma * Copyright (C) 2006 Shuveb Hussain * Copyright (C) 2007 Anoop Joe Cyriac * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 * * Authors: * Shuveb Hussain * Anoop Joe Cyriac * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "virterror_internal.h" #include "openvz_conf.h" #include "uuid.h" #include "buf.h" #include "memory.h" #include "util.h" #include "nodeinfo.h" static char *openvzLocateConfDir(void); static int openvzGetVPSUUID(int vpsid, char *uuidstr); static int openvzLocateConfFile(int vpsid, char *conffile, int maxlen, const char *ext); static int openvzAssignUUIDs(void); int strtoI(const char *str) { int val; if (virStrToLong_i(str, NULL, 10, &val) < 0) return 0 ; return val; } static int openvzExtractVersionInfo(const char *cmd, int *retversion) { const char *const vzarg[] = { cmd, "--help", NULL }; const char *const vzenv[] = { "LC_ALL=C", NULL }; pid_t child; int newstdout = -1; int ret = -1, status; unsigned int major, minor, micro; unsigned int version; if (retversion) *retversion = 0; if (virExec(NULL, vzarg, vzenv, NULL, &child, -1, &newstdout, NULL, VIR_EXEC_NONE) < 0) return -1; char *help = NULL; int len = virFileReadLimFD(newstdout, 4096, &help); if (len < 0) goto cleanup2; if (sscanf(help, "vzctl version %u.%u.%u", &major, &minor, µ) != 3) { goto cleanup2; } version = (major * 1000 * 1000) + (minor * 1000) + micro; if (retversion) *retversion = version; ret = 0; cleanup2: VIR_FREE(help); if (close(newstdout) < 0) ret = -1; rewait: if (waitpid(child, &status, 0) != child) { if (errno == EINTR) goto rewait; ret = -1; } return ret; } int openvzExtractVersion(virConnectPtr conn, struct openvz_driver *driver) { if (driver->version > 0) return 0; if (openvzExtractVersionInfo(VZCTL, &driver->version) < 0) { openvzError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("Cound not extract vzctl version")); return -1; } return 0; } virCapsPtr openvzCapsInit(void) { struct utsname utsname; virCapsPtr caps; virCapsGuestPtr guest; uname(&utsname); if ((caps = virCapabilitiesNew(utsname.machine, 0, 0)) == NULL) goto no_memory; virCapabilitiesSetMacPrefix(caps, (unsigned char[]){ 0x52, 0x54, 0x00 }); if ((guest = virCapabilitiesAddGuest(caps, "exe", utsname.machine, sizeof(int) == 4 ? 32 : 8, NULL, NULL, 0, NULL)) == NULL) goto no_memory; if (virCapabilitiesAddGuestDomain(guest, "openvz", NULL, NULL, 0, NULL) == NULL) goto no_memory; return caps; no_memory: virCapabilitiesFree(caps); return NULL; } static int openvzReadNetworkConf(virConnectPtr conn, virDomainDefPtr def, int veid) { int ret; virDomainNetDefPtr net; char temp[4096]; char *token, *saveptr = NULL; /*parse routing network configuration* * Sample from config: * IP_ADDRESS="1.1.1.1 1.1.1.2" * splited IPs by space */ ret = openvzReadConfigParam(veid, "IP_ADDRESS", temp, sizeof(temp)); if (ret < 0) { openvzError(conn, VIR_ERR_INTERNAL_ERROR, _("Cound not read 'IP_ADDRESS' from config for container %d"), veid); goto error; } else if (ret > 0) { token = strtok_r(temp, " ", &saveptr); while (token != NULL) { if (VIR_ALLOC(net) < 0) goto no_memory; net->type = VIR_DOMAIN_NET_TYPE_ETHERNET; net->data.ethernet.ipaddr = strdup(token); if (net->data.ethernet.ipaddr == NULL) goto no_memory; if (VIR_REALLOC_N(def->nets, def->nnets + 1) < 0) goto no_memory; def->nets[def->nnets++] = net; net = NULL; token = strtok_r(NULL, " ", &saveptr); } } /*parse bridge devices*/ /*Sample from config: *NETIF="ifname=eth10,mac=00:18:51:C1:05:EE,host_ifname=veth105.10,host_mac=00:18:51:8F:D9:F3" *devices splited by ';' */ ret = openvzReadConfigParam(veid, "NETIF", temp, sizeof(temp)); if (ret < 0) { openvzError(conn, VIR_ERR_INTERNAL_ERROR, _("Cound not read 'NETIF' from config for container %d"), veid); goto error; } else if (ret > 0) { token = strtok_r(temp, ";", &saveptr); while (token != NULL) { /*add new device to list*/ if (VIR_ALLOC(net) < 0) goto no_memory; net->type = VIR_DOMAIN_NET_TYPE_BRIDGE; char *p = token, *next = token; char cpy_temp[32]; int len; /*parse string*/ do { while (*next != '\0' && *next != ',') next++; if (STRPREFIX(p, "ifname=")) { p += 7; /* skip in libvirt */ } else if (STRPREFIX(p, "host_ifname=")) { p += 12; len = next - p; if (len > 16) { openvzError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("Too long network device name")); goto error; } if (VIR_ALLOC_N(net->ifname, len+1) < 0) goto no_memory; strncpy(net->ifname, p, len); net->ifname[len] = '\0'; } else if (STRPREFIX(p, "bridge=")) { p += 7; len = next - p; if (len > 16) { openvzError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("Too long bridge device name")); goto error; } if (VIR_ALLOC_N(net->data.bridge.brname, len+1) < 0) goto no_memory; strncpy(net->data.bridge.brname, p, len); net->data.bridge.brname[len] = '\0'; } else if (STRPREFIX(p, "mac=")) { p += 4; len = next - p; if (len != 17) { //should be 17 openvzError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("Wrong length MAC address")); goto error; } strncpy(cpy_temp, p, len); cpy_temp[len] = '\0'; if (virParseMacAddr(cpy_temp, net->mac) < 0) { openvzError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("Wrong MAC address")); goto error; } } p = ++next; } while (p < token + strlen(token)); if (VIR_REALLOC_N(def->nets, def->nnets + 1) < 0) goto no_memory; def->nets[def->nnets++] = net; net = NULL; token = strtok_r(NULL, ";", &saveptr); } } return 0; no_memory: openvzError(conn, VIR_ERR_NO_MEMORY, NULL); error: virDomainNetDefFree(net); return -1; } static int openvzReadFSConf(virConnectPtr conn, virDomainDefPtr def, int veid) { int ret; virDomainFSDefPtr fs = NULL; char temp[4096]; ret = openvzReadConfigParam(veid, "OSTEMPLATE", temp, sizeof(temp)); if (ret < 0) { openvzError(conn, VIR_ERR_INTERNAL_ERROR, _("Cound not read 'OSTEMPLATE' from config for container %d"), veid); goto error; } else if (ret > 0) { if (VIR_ALLOC(fs) < 0) goto no_memory; fs->type = VIR_DOMAIN_FS_TYPE_TEMPLATE; fs->src = strdup(temp); fs->dst = strdup("/"); if (fs->src == NULL || fs->dst == NULL) goto no_memory; if (VIR_REALLOC_N(def->fss, def->nfss + 1) < 0) goto no_memory; def->fss[def->nfss++] = fs; fs = NULL; } return 0; no_memory: openvzError(conn, VIR_ERR_NO_MEMORY, NULL); error: virDomainFSDefFree(fs); return -1; } /* Free all memory associated with a openvz_driver structure */ void openvzFreeDriver(struct openvz_driver *driver) { if (!driver) return; virDomainObjListFree(&driver->domains); virCapabilitiesFree(driver->caps); } int openvzLoadDomains(struct openvz_driver *driver) { FILE *fp; int veid, ret; char status[16]; char uuidstr[VIR_UUID_STRING_BUFLEN]; virDomainObjPtr dom = NULL; char temp[50]; if (openvzAssignUUIDs() < 0) return -1; if ((fp = popen(VZLIST " -a -ovpsid,status -H 2>/dev/null", "r")) == NULL) { openvzError(NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("popen failed")); return -1; } while(!feof(fp)) { if (fscanf(fp, "%d %s\n", &veid, status) != 2) { if (feof(fp)) break; openvzError(NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to parse vzlist output")); goto cleanup; } if (VIR_ALLOC(dom) < 0 || VIR_ALLOC(dom->def) < 0) goto no_memory; if (STREQ(status, "stopped")) dom->state = VIR_DOMAIN_SHUTOFF; else dom->state = VIR_DOMAIN_RUNNING; dom->pid = veid; dom->def->id = dom->state == VIR_DOMAIN_SHUTOFF ? -1 : veid; if (asprintf(&dom->def->name, "%i", veid) < 0) { dom->def->name = NULL; goto no_memory; } openvzGetVPSUUID(veid, uuidstr); ret = virUUIDParse(uuidstr, dom->def->uuid); if (ret == -1) { openvzError(NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("UUID in config file malformed")); goto cleanup; } if (!(dom->def->os.type = strdup("exe"))) goto no_memory; if (!(dom->def->os.init = strdup("/sbin/init"))) goto no_memory; ret = openvzReadConfigParam(veid, "CPUS", temp, sizeof(temp)); if (ret < 0) { openvzError(NULL, VIR_ERR_INTERNAL_ERROR, _("Cound not read config for container %d"), veid); goto cleanup; } else if (ret > 0) { dom->def->vcpus = strtoI(temp); } if (ret == 0 || dom->def->vcpus == 0) dom->def->vcpus = openvzGetNodeCPUs(); /* XXX load rest of VM config data .... */ openvzReadNetworkConf(NULL, dom->def, veid); openvzReadFSConf(NULL, dom->def, veid); if (VIR_REALLOC_N(driver->domains.objs, driver->domains.count + 1) < 0) goto no_memory; driver->domains.objs[driver->domains.count++] = dom; dom = NULL; } fclose(fp); return 0; no_memory: openvzError(NULL, VIR_ERR_NO_MEMORY, NULL); cleanup: fclose(fp); virDomainObjFree(dom); return -1; } unsigned int openvzGetNodeCPUs(void) { virNodeInfo nodeinfo; if (virNodeInfoPopulate(NULL, &nodeinfo) < 0) { openvzError(NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("Cound not read nodeinfo")); return 0; } return nodeinfo.cpus; } int openvzWriteConfigParam(int vpsid, const char *param, const char *value) { char conf_file[PATH_MAX]; char temp_file[PATH_MAX]; char line[PATH_MAX] ; int fd, temp_fd; if (openvzLocateConfFile(vpsid, conf_file, PATH_MAX, "conf")<0) return -1; if (openvzLocateConfFile(vpsid, temp_file, PATH_MAX, "tmp")<0) return -1; fd = open(conf_file, O_RDONLY); if (fd == -1) return -1; temp_fd = open(temp_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (temp_fd == -1) { close(fd); return -1; } while(1) { if (openvz_readline(fd, line, sizeof(line)) <= 0) break; if (!(STRPREFIX(line, param) && line[strlen(param)] == '=')) { if (safewrite(temp_fd, line, strlen(line)) != strlen(line)) goto error; } } if (safewrite(temp_fd, param, strlen(param)) < 0 || safewrite(temp_fd, "=\"", 2) < 0 || safewrite(temp_fd, value, strlen(value)) < 0 || safewrite(temp_fd, "\"\n", 2) < 0) goto error; if (close(fd) < 0) goto error; fd = -1; if (close(temp_fd) < 0) goto error; temp_fd = -1; if (rename(temp_file, conf_file) < 0) goto error; return 0; error: if (fd != -1) close(fd); if (temp_fd != -1) close(temp_fd); unlink(temp_file); return -1; } /* * Read parameter from container config * sample: 133, "OSTEMPLATE", value, 1024 * return: -1 - error * 0 - don't found * 1 - OK */ int openvzReadConfigParam(int vpsid ,const char * param, char *value, int maxlen) { char conf_file[PATH_MAX] ; char line[PATH_MAX] ; int ret, found = 0; int fd ; char * sf, * token; char *saveptr = NULL; if (openvzLocateConfFile(vpsid, conf_file, PATH_MAX, "conf")<0) return -1; value[0] = 0; fd = open(conf_file, O_RDONLY); if (fd == -1) return -1; while(1) { ret = openvz_readline(fd, line, sizeof(line)); if(ret <= 0) break; saveptr = NULL; if (STREQLEN(line, param, strlen(param))) { sf = line; sf += strlen(param); if (sf[0] == '=' && sf[1] != '\0' ) { sf ++; if ((token = strtok_r(sf,"\"\t\n", &saveptr)) != NULL) { strncpy(value, token, maxlen) ; value[maxlen-1] = '\0'; found = 1; } } } } close(fd); if (ret == 0 && found) ret = 1; return ret ; } /* Locate config file of container * return -1 - error * 0 - OK */ static int openvzLocateConfFile(int vpsid, char *conffile, int maxlen, const char *ext) { char * confdir; int ret = 0; confdir = openvzLocateConfDir(); if (confdir == NULL) return -1; if (snprintf(conffile, maxlen, "%s/%d.%s", confdir, vpsid, ext ? ext : "conf") >= maxlen) ret = -1; VIR_FREE(confdir); return ret; } static char *openvzLocateConfDir(void) { const char *conf_dir_list[] = {"/etc/vz/conf", "/usr/local/etc/conf", NULL}; int i=0; while(conf_dir_list[i]) { if(!access(conf_dir_list[i], F_OK)) return strdup(conf_dir_list[i]); i ++; } return NULL; } /* Richard Steven's classic readline() function */ int openvz_readline(int fd, char *ptr, int maxlen) { int n, rc; char c; for(n = 1; n < maxlen; n ++) { if( (rc = read(fd, &c, 1)) == 1) { *ptr++ = c; if(c == '\n') break; } else if(rc == 0) { if(n == 1) return 0; /* EOF condition */ else break; } else return -1; /* error */ } *ptr = 0; return n; } static int openvzGetVPSUUID(int vpsid, char *uuidstr) { char conf_file[PATH_MAX]; char line[1024]; char uuidbuf[1024]; char iden[1024]; int fd, ret; if (openvzLocateConfFile(vpsid, conf_file, PATH_MAX, "conf")<0) return -1; fd = open(conf_file, O_RDONLY); if(fd == -1) return -1; while(1) { ret = openvz_readline(fd, line, sizeof(line)); if(ret == -1) return -1; if(ret == 0) { /* EoF, UUID was not found */ uuidstr[0] = 0; break; } sscanf(line, "%s %s\n", iden, uuidbuf); if(STREQ(iden, "#UUID:")) { strncpy(uuidstr, uuidbuf, VIR_UUID_STRING_BUFLEN); break; } } close(fd); return 0; } /* Do actual checking for UUID presence in conf file, * assign if not present. */ int openvzSetDefinedUUID(int vpsid, unsigned char *uuid) { char conf_file[PATH_MAX]; char uuidstr[VIR_UUID_STRING_BUFLEN]; if (uuid == NULL) return -1; if (openvzLocateConfFile(vpsid, conf_file, PATH_MAX, "conf")<0) return -1; if (openvzGetVPSUUID(vpsid, uuidstr)) return -1; if (uuidstr[0] == 0) { FILE *fp = fopen(conf_file, "a"); /* append */ if (fp == NULL) return -1; virUUIDFormat(uuid, uuidstr); /* Record failure if fprintf or fclose fails, and be careful always to close the stream. */ if ((fprintf(fp, "\n#UUID: %s\n", uuidstr) < 0) + (fclose(fp) == EOF)) return -1; } return 0; } static int openvzSetUUID(int vpsid){ unsigned char uuid[VIR_UUID_BUFLEN]; virUUIDGenerate(uuid); return openvzSetDefinedUUID(vpsid, uuid); } /* * Scan VPS config files and see if they have a UUID. * If not, assign one. Just append one to the config * file as comment so that the OpenVZ tools ignore it. * */ static int openvzAssignUUIDs(void) { DIR *dp; struct dirent *dent; char *conf_dir; int vpsid, res; char ext[8]; conf_dir = openvzLocateConfDir(); if (conf_dir == NULL) return -1; dp = opendir(conf_dir); if(dp == NULL) { VIR_FREE(conf_dir); return 0; } while((dent = readdir(dp))) { res = sscanf(dent->d_name, "%d.%5s", &vpsid, ext); if(!(res == 2 && STREQ(ext, "conf"))) continue; if(vpsid > 0) /* '0.conf' belongs to the host, ignore it */ openvzSetUUID(vpsid); } closedir(dp); VIR_FREE(conf_dir); return 0; }