/* * Copyright (C) 2009 Citrix Ltd. * Author Vincent Hanquez * * This program 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; version 2.1 only. with the special * exception on linking described in file LICENSE. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. */ #include "libxl_osdeps.h" /* must come before any other headers */ #include "libxl_internal.h" void libxl__alloc_failed(libxl_ctx *ctx, const char *func, size_t nmemb, size_t size) { #define M "libxl: FATAL ERROR: memory allocation failure" #define M_SIZE M " (%s, %lu x %lu)\n" #define M_NSIZE M " (%s)\n" if (size) { libxl__log(ctx, XTL_CRITICAL, ENOMEM, 0, 0, func, INVALID_DOMID, M_SIZE, func, (unsigned long)nmemb, (unsigned long)size); fprintf(stderr, M_SIZE, func, (unsigned long)nmemb, (unsigned long)size); } else { libxl__log(ctx, XTL_CRITICAL, ENOMEM, 0, 0, func, INVALID_DOMID, M_NSIZE, func); fprintf(stderr, M_NSIZE, func); } fflush(stderr); _exit(-1); #undef M_NSIZE #undef M_SIZE #undef M } void libxl__ptr_add(libxl__gc *gc, void *ptr) { int i; if (!libxl__gc_is_real(gc)) return; if (!ptr) return; /* fast case: we have space in the array for storing the pointer */ for (i = 0; i < gc->alloc_maxsize; i++) { if (!gc->alloc_ptrs[i]) { gc->alloc_ptrs[i] = ptr; return; } } int new_maxsize = gc->alloc_maxsize * 2 + 25; assert(new_maxsize < INT_MAX / sizeof(void*) / 2); gc->alloc_ptrs = realloc(gc->alloc_ptrs, new_maxsize * sizeof(void *)); if (!gc->alloc_ptrs) libxl__alloc_failed(CTX, __func__, new_maxsize, sizeof(void*)); gc->alloc_ptrs[gc->alloc_maxsize++] = ptr; while (gc->alloc_maxsize < new_maxsize) gc->alloc_ptrs[gc->alloc_maxsize++] = 0; return; } void libxl__free_all(libxl__gc *gc) { void *ptr; int i; assert(libxl__gc_is_real(gc)); for (i = 0; i < gc->alloc_maxsize; i++) { ptr = gc->alloc_ptrs[i]; gc->alloc_ptrs[i] = NULL; free(ptr); } free(gc->alloc_ptrs); gc->alloc_ptrs = 0; gc->alloc_maxsize = 0; } void *libxl__malloc(libxl__gc *gc, size_t size) { void *ptr = malloc(size); if (!ptr) libxl__alloc_failed(CTX, __func__, size, 1); libxl__ptr_add(gc, ptr); return ptr; } void *libxl__zalloc(libxl__gc *gc, size_t size) { void *ptr = calloc(size, 1); if (!ptr) libxl__alloc_failed(CTX, __func__, size, 1); libxl__ptr_add(gc, ptr); return ptr; } void *libxl__calloc(libxl__gc *gc, size_t nmemb, size_t size) { void *ptr = calloc(nmemb, size); if (!ptr) libxl__alloc_failed(CTX, __func__, nmemb, size); libxl__ptr_add(gc, ptr); return ptr; } void *libxl__realloc(libxl__gc *gc, void *ptr, size_t new_size) { void *new_ptr = realloc(ptr, new_size); int i = 0; if (new_ptr == NULL && new_size != 0) libxl__alloc_failed(CTX, __func__, new_size, 1); if (ptr == NULL) { libxl__ptr_add(gc, new_ptr); } else if (new_ptr != ptr && libxl__gc_is_real(gc)) { for (i = 0; ; i++) { assert(i < gc->alloc_maxsize); if (gc->alloc_ptrs[i] == ptr) { gc->alloc_ptrs[i] = new_ptr; break; } } } return new_ptr; } char *libxl__vsprintf(libxl__gc *gc, const char *fmt, va_list ap) { char *s; va_list aq; int ret; va_copy(aq, ap); ret = vsnprintf(NULL, 0, fmt, aq); va_end(aq); assert(ret >= 0); s = libxl__zalloc(gc, ret + 1); va_copy(aq, ap); ret = vsnprintf(s, ret + 1, fmt, aq); va_end(aq); return s; } char *libxl__sprintf(libxl__gc *gc, const char *fmt, ...) { char *s; va_list ap; va_start(ap, fmt); s = libxl__vsprintf(gc, fmt, ap); va_end(ap); return s; } char *libxl__strdup(libxl__gc *gc, const char *c) { char *s; if (!c) return NULL; s = strdup(c); if (!s) libxl__alloc_failed(CTX, __func__, strlen(c), 1); libxl__ptr_add(gc, s); return s; } char *libxl__strndup(libxl__gc *gc, const char *c, size_t n) { char *s; if (!c) return NULL; s = strndup(c, n); if (!s) libxl__alloc_failed(CTX, __func__, n, 1); libxl__ptr_add(gc, s); return s; } char *libxl__dirname(libxl__gc *gc, const char *s) { char *c = strrchr(s, '/'); if (!c) return NULL; return libxl__strndup(gc, s, c - s); } void libxl__logv(libxl_ctx *ctx, xentoollog_level msglevel, int errnoval, const char *file, int line, const char *func, uint32_t domid, const char *fmt, va_list ap) { /* WARNING this function may not call any libxl-provided * memory allocation function, as those may * call libxl__alloc_failed which calls libxl__logv. */ char *enomem = "[out of memory formatting log message]"; char *base = NULL; int rc, esave; char fileline[256]; char domain[256]; esave = errno; rc = vasprintf(&base, fmt, ap); if (rc<0) { base = enomem; goto x; } fileline[0] = 0; if (file) snprintf(fileline, sizeof(fileline), "%s:%d",file,line); fileline[sizeof(fileline)-1] = 0; domain[0] = 0; if (libxl_domid_valid_guest(domid)) snprintf(domain, sizeof(domain), "Domain %"PRIu32":", domid); x: xtl_log(ctx->lg, msglevel, errnoval, "libxl", "%s%s%s%s%s" "%s", fileline, func&&file?":":"", func?func:"", func||file?": ":"", domain, base); if (base != enomem) free(base); errno = esave; } void libxl__log(libxl_ctx *ctx, xentoollog_level msglevel, int errnoval, const char *file, int line, const char *func, uint32_t domid, const char *fmt, ...) { va_list ap; va_start(ap, fmt); libxl__logv(ctx, msglevel, errnoval, file, line, func, domid, fmt, ap); va_end(ap); } char *libxl__abs_path(libxl__gc *gc, const char *s, const char *path) { if (s[0] == '/') return libxl__strdup(gc, s); return GCSPRINTF("%s/%s", path, s); } int libxl__file_reference_map(libxl__file_reference *f) { struct stat st_buf; int ret, fd; void *data; if (f->mapped) return 0; fd = open(f->path, O_RDONLY); if (fd < 0) return ERROR_FAIL; ret = fstat(fd, &st_buf); if (ret < 0) goto out; ret = -1; data = mmap(NULL, st_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (data == MAP_FAILED) goto out; f->mapped = 1; f->data = data; f->size = st_buf.st_size; ret = 0; out: close(fd); return ret == 0 ? 0 : ERROR_FAIL; } int libxl__file_reference_unmap(libxl__file_reference *f) { int ret; if (!f->mapped) return 0; ret = munmap(f->data, f->size); if (ret == 0) { f->mapped = 0; f->data = NULL; f->size = 0; return 0; } return ERROR_FAIL; } _hidden int libxl__parse_mac(const char *s, libxl_mac mac) { const char *tok; char *endptr; int i; for (i = 0, tok = s; *tok && (i < 6); ++i, tok = endptr) { mac[i] = strtol(tok, &endptr, 16); if (endptr != (tok + 2) || (*endptr != '\0' && *endptr != ':') ) return ERROR_INVAL; if (*endptr == ':') endptr++; } if ( i != 6 ) return ERROR_INVAL; return 0; } _hidden int libxl__compare_macs(libxl_mac *a, libxl_mac *b) { int i; for (i = 0; i<6; i++) { if ((*a)[i] != (*b)[i]) return (*a)[i] - (*b)[i]; } return 0; } _hidden int libxl__mac_is_default(libxl_mac *mac) { return (!(*mac)[0] && !(*mac)[1] && !(*mac)[2] && !(*mac)[3] && !(*mac)[4] && !(*mac)[5]); } _hidden int libxl__init_recursive_mutex(libxl_ctx *ctx, pthread_mutex_t *lock) { GC_INIT(ctx); pthread_mutexattr_t attr; int rc = 0; if (pthread_mutexattr_init(&attr) != 0) { LOGE(ERROR, "Failed to init mutex attributes"); rc = ERROR_FAIL; goto out; } if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) { LOGE(ERROR, "Failed to set mutex attributes"); rc = ERROR_FAIL; goto out; } if (pthread_mutex_init(lock, &attr) != 0) { LOGE(ERROR, "Failed to init mutex"); rc = ERROR_FAIL; goto out; } out: pthread_mutexattr_destroy(&attr); GC_FREE; return rc; } int libxl__device_model_version_running(libxl__gc *gc, uint32_t domid) { char *path = NULL; char *dm_version = NULL; libxl_device_model_version value; path = libxl__xs_libxl_path(gc, domid); path = GCSPRINTF("%s/dm-version", path); dm_version = libxl__xs_read(gc, XBT_NULL, path); if (!dm_version) { return LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL; } if (libxl_device_model_version_from_string(dm_version, &value) < 0) { LOGD(ERROR, domid, "fatal: %s contain a wrong value (%s)", path, dm_version); return -1; } return value; } /* Portability note: this lock utilises flock(2) so a proper implementation of * flock(2) is required. */ libxl__flock *libxl__lock_file(libxl__gc *gc, const char *lockfile) { libxl__flock *lock; int fd; struct stat stab, fstab; lock = libxl__zalloc(NOGC, sizeof(libxl__flock)); lock->path = libxl__strdup(NOGC, lockfile); while (true) { libxl__carefd_begin(); fd = open(lockfile, O_RDWR|O_CREAT, 0666); if (fd < 0) LOGE(ERROR, "cannot open lockfile %s, errno=%d", lockfile, errno); lock->carefd = libxl__carefd_opened(CTX, fd); if (fd < 0) goto out; /* Lock the file in exclusive mode, wait indefinitely to * acquire the lock */ while (flock(fd, LOCK_EX)) { switch (errno) { case EINTR: /* Signal received, retry */ continue; default: /* All other errno: EBADF, EINVAL, ENOLCK, EWOULDBLOCK */ LOGE(ERROR, "unexpected error while trying to lock %s, fd=%d, errno=%d", lockfile, fd, errno); goto out; } } if (fstat(fd, &fstab)) { LOGE(ERROR, "cannot fstat %s, fd=%d, errno=%d", lockfile, fd, errno); goto out; } if (stat(lockfile, &stab)) { if (errno != ENOENT) { LOGE(ERROR, "cannot stat %s, errno=%d", lockfile, errno); goto out; } } else { if (stab.st_dev == fstab.st_dev && stab.st_ino == fstab.st_ino) break; } libxl__carefd_close(lock->carefd); } return lock; out: if (lock) libxl__unlock_file(lock); return NULL; } void libxl__unlock_file(libxl__flock *lock) { /* It's important to unlink the file before closing fd to avoid * the following race (if close before unlink): * * P1 LOCK P2 UNLOCK * fd1 = open(lockfile) * close(fd2) * flock(fd1) * fstat and stat check success * unlink(lockfile) * return lock * * In above case P1 thinks it has got hold of the lock but * actually lock is released by P2 (lockfile unlinked). */ if (lock->path) unlink(lock->path); if (lock->carefd) libxl__carefd_close(lock->carefd); free(lock->path); free(lock); } libxl__flock *libxl__lock_domain_userdata(libxl__gc *gc, uint32_t domid) { const char *lockfile; libxl__flock *lock; lockfile = libxl__userdata_path(gc, domid, "domain-userdata-lock", "l"); if (!lockfile) return NULL; lock = libxl__lock_file(gc, lockfile); /* Check the domain is still there, if not we should release the * lock and clean up. */ if (libxl_domain_info(CTX, NULL, domid)) { libxl__unlock_file(lock); return NULL; } return lock; } libxl__flock *libxl__lock_domid_history(libxl__gc *gc) { const char *lockfile; lockfile = libxl__domid_history_path(gc, ".lock"); if (!lockfile) return NULL; return libxl__lock_file(gc, lockfile); } int libxl__get_domain_configuration(libxl__gc *gc, uint32_t domid, libxl_domain_config *d_config) { uint8_t *data = NULL; int rc, len; rc = libxl__userdata_retrieve(gc, domid, "libxl-json", &data, &len); if (rc) { LOGEVD(ERROR, rc, domid, "failed to retrieve domain configuration"); rc = ERROR_FAIL; goto out; } if (len == 0) { /* No logging, not necessary an error from caller's PoV. */ rc = ERROR_JSON_CONFIG_EMPTY; goto out; } rc = libxl_domain_config_from_json(CTX, d_config, (const char *)data); out: free(data); return rc; } int libxl__set_domain_configuration(libxl__gc *gc, uint32_t domid, libxl_domain_config *d_config) { char *d_config_json; int rc; d_config_json = libxl_domain_config_to_json(CTX, d_config); if (!d_config_json) { LOGED(ERROR, domid, "failed to convert domain configuration to JSON"); rc = ERROR_FAIL; goto out; } rc = libxl__userdata_store(gc, domid, "libxl-json", (const uint8_t *)d_config_json, strlen(d_config_json) + 1 /* include '\0' */); if (rc) { LOGEVD(ERROR, rc, domid, "failed to store domain configuration"); rc = ERROR_FAIL; goto out; } out: free(d_config_json); return rc; } void libxl__update_domain_configuration(libxl__gc *gc, libxl_domain_config *dst, const libxl_domain_config *src) { int i, idx, num; const libxl__device_type *dt; for (idx = 0;; idx++) { dt = device_type_tbl[idx]; if (!dt) break; num = *libxl__device_type_get_num(dt, src); if (!dt->update_config || !num) continue; for (i = 0; i < num; i++) dt->update_config(gc, libxl__device_type_get_elem(dt, dst, i), libxl__device_type_get_elem(dt, src, i)); } /* update guest UUID */ libxl_uuid_copy(CTX, &dst->c_info.uuid, &src->c_info.uuid); /* video ram */ dst->b_info.video_memkb = src->b_info.video_memkb; } static void ev_slowlock_init_internal(libxl__ev_slowlock *lock, const char *userdata_userid) { libxl__ev_child_init(&lock->child); lock->userdata_userid = userdata_userid; lock->path = NULL; lock->fd = -1; lock->held = false; } void libxl__ev_devlock_init(libxl__ev_slowlock *lock) { ev_slowlock_init_internal(lock, "libxl-device-changes-lock"); } void libxl__ev_qmplock_init(libxl__ev_slowlock *lock) { ev_slowlock_init_internal(lock, "qmp-socket-lock"); } static void ev_lock_prepare_fork(libxl__egc *egc, libxl__ev_slowlock *lock); static void ev_lock_child_callback(libxl__egc *egc, libxl__ev_child *child, pid_t pid, int status); void libxl__ev_slowlock_lock(libxl__egc *egc, libxl__ev_slowlock *lock) { STATE_AO_GC(lock->ao); const char *lockfile; lockfile = libxl__userdata_path(gc, lock->domid, lock->userdata_userid, "l"); if (!lockfile) goto out; lock->path = libxl__strdup(NOGC, lockfile); ev_lock_prepare_fork(egc, lock); return; out: lock->callback(egc, lock, ERROR_LOCK_FAIL); } static void ev_lock_prepare_fork(libxl__egc *egc, libxl__ev_slowlock *lock) { STATE_AO_GC(lock->ao); pid_t pid; int fd; /* Convenience aliases */ libxl_domid domid = lock->domid; const char *lockfile = lock->path; lock->fd = open(lockfile, O_RDWR|O_CREAT, 0666); if (lock->fd < 0) { LOGED(ERROR, domid, "cannot open lockfile %s", lockfile); goto out; } fd = lock->fd; /* Enable this optimisation only in releases, so the fork code is * exercised while libxl is built with debug=y. */ #ifndef CONFIG_DEBUG /* * We try to grab the lock before forking as it is likely to be free. * Even though we are supposed to CTX_UNLOCK before attempting to grab * the ev_lock, it is fine to do a non-blocking request now with the * CTX_LOCK held as if that fails we'll try again in a fork (CTX_UNLOCK * will be called in libxl), that will avoid deadlocks. */ int r = flock(fd, LOCK_EX | LOCK_NB); if (!r) { libxl_fd_set_cloexec(CTX, fd, 1); /* We held a lock, no need to fork but we need to check it. */ ev_lock_child_callback(egc, &lock->child, 0, 0); return; } #endif pid = libxl__ev_child_fork(gc, &lock->child, ev_lock_child_callback); if (pid < 0) goto out; if (!pid) { /* child */ int exit_val = 0; /* Lock the file in exclusive mode, wait indefinitely to * acquire the lock */ while (flock(fd, LOCK_EX)) { switch (errno) { case EINTR: /* Signal received, retry */ continue; default: /* All other errno: EBADF, EINVAL, ENOLCK, EWOULDBLOCK */ LOGED(ERROR, domid, "unexpected error while trying to lock %s, fd=%d", lockfile, fd); exit_val = 1; break; } } _exit(exit_val); } /* Now that the child has the fd, set cloexec in the parent to prevent * more leakage than necessary */ libxl_fd_set_cloexec(CTX, fd, 1); return; out: libxl__ev_slowlock_unlock(gc, lock); lock->callback(egc, lock, ERROR_LOCK_FAIL); } static void ev_lock_child_callback(libxl__egc *egc, libxl__ev_child *child, pid_t pid, int status) { EGC_GC; libxl__ev_slowlock *lock = CONTAINER_OF(child, *lock, child); struct stat stab, fstab; int rc = ERROR_LOCK_FAIL; /* Convenience aliases */ int fd = lock->fd; const char *lockfile = lock->path; libxl_domid domid = lock->domid; if (status) { libxl_report_child_exitstatus(CTX, XTL_ERROR, "flock child", pid, status); goto out; } if (fstat(fd, &fstab)) { LOGED(ERROR, domid, "cannot fstat %s, fd=%d", lockfile, fd); goto out; } if (stat(lockfile, &stab)) { if (errno != ENOENT) { LOGED(ERROR, domid, "cannot stat %s", lockfile); goto out; } } else { if (stab.st_dev == fstab.st_dev && stab.st_ino == fstab.st_ino) { /* We held the lock */ lock->held = true; rc = 0; goto out; } } /* We didn't grab the lock, let's try again */ flock(lock->fd, LOCK_UN); close(lock->fd); lock->fd = -1; ev_lock_prepare_fork(egc, lock); return; out: if (lock->held) { /* Check the domain is still there, if not we should release the * lock and clean up. */ if (libxl_domain_info(CTX, NULL, domid)) rc = ERROR_LOCK_FAIL; } if (rc) { LOGD(ERROR, domid, "Failed to grab lock for %s", lock->userdata_userid); libxl__ev_slowlock_unlock(gc, lock); } lock->callback(egc, lock, rc); } void libxl__ev_slowlock_unlock(libxl__gc *gc, libxl__ev_slowlock *lock) { int r; assert(!libxl__ev_child_inuse(&lock->child)); /* See the rationale in libxl__unlock_domain_userdata() * about why we do unlink() before unlock(). */ if (lock->path && lock->held) unlink(lock->path); if (lock->fd >= 0) { /* We need to call unlock as the fd may have leaked into other * processes */ r = flock(lock->fd, LOCK_UN); if (r) LOGED(ERROR, lock->domid, "failed to unlock fd=%d, path=%s", lock->fd, lock->path); close(lock->fd); } free(lock->path); ev_slowlock_init_internal(lock, lock->userdata_userid); } void libxl__ev_slowlock_dispose(libxl__gc *gc, libxl__ev_slowlock *lock) { libxl__ev_child_kill_deregister(lock->ao, &lock->child, SIGKILL); libxl__ev_slowlock_unlock(gc, lock); } /* * Local variables: * mode: C * c-basic-offset: 4 * indent-tabs-mode: nil * End: */