libvirt中错误码和错误信息的分析

libvirt每一个重要的操作之前大都会调用virResetLastError函数来清除错误信息,这是为了使本次操作的错误结果可以存储在错误信息结构里面,同时使上一次的错误不会影响本次操作的结果判断。
查看源码,virResetLastError函数的实现如下:

void
virResetLastError(void)
{
    virErrorPtr err = virLastErrorObject();
    if (err)
        virResetError(err);
}

从字面意思可以知道virLastErrorObject是获得错误信息对象(其实是一个结构体),如果获取到了,说明有错误信息,则用virResetError函数清除对象中的错误信息。下面进入这两个函数,如下:

static virErrorPtr
virLastErrorObject(void)
{
    virErrorPtr err;
    err = virThreadLocalGet(&virLastErr);
    if (!err) {
        if (VIR_ALLOC_QUIET(err) < 0)
            return NULL;
        if (virThreadLocalSet(&virLastErr, err) < 0)
            VIR_FREE(err);
    }
    return err;
}

可以知道函数最总返回去的错误信息是通过virThreadLocalGet(&virLastErr)获得的,virLastErr是一个全局变量,定义如下:

struct virThreadLocal {
    pthread_key_t key;
};

再查看virThreadLocalGet函数定义:

void *virThreadLocalGet(virThreadLocalPtr l)
{
    return pthread_getspecific(l->key);
}

从这里就可以知道了,错误信息的存储是用了线程特定数据,这样每个线程都会有自己的数据存储,该线程中的每个函数都可以访问本线程的特定数据,而其他线程不能访问,这样就能保证数据在线程间安全了。
这里是使用线程特定数据,但是必须得有初始化的部分,经搜索源码,virsh命令有
virResetError函数是复位错误信息对象的,定义如下:

void
virResetError(virErrorPtr err)
{
    if (err == NULL)
        return;
    VIR_FREE(err->message);
    VIR_FREE(err->str1);
    VIR_FREE(err->str2);
    VIR_FREE(err->str3);
    memset(err, 0, sizeof(virError));
}

结合virErrorPtr 结构体变量的定义:

struct _virError {
    int     code;   /* The error code, a virErrorNumber */
    int     domain; /* What part of the library raised this error */
    char       *message;/* human-readable informative error message */
    virErrorLevel level;/* how consequent is the error */
    virConnectPtr conn VIR_DEPRECATED; /* connection if available, deprecated
                                          see note above */
    virDomainPtr dom VIR_DEPRECATED; /* domain if available, deprecated
                                        see note above */
    char       *str1;   /* extra string information */
    char       *str2;   /* extra string information */
    char       *str3;   /* extra string information */
    int     int1;   /* extra number information */
    int     int2;   /* extra number information */
    virNetworkPtr net VIR_DEPRECATED; /* network if available, deprecated
                                         see note above */
};

virResetError函数就是释放错误信息结构体变量中的指针。从结构体域的定义可以知道,code是错误码,message是可以读懂的错误信息。
经过分析virResetLastError函数的作用就是先获得存储错误信息的线程特定数据,然后将线程特定数据中的指针释放,并将里面的全部变量赋值为0。

virResetLastError是读取和复位错误信息的,virsh 和 libvirtd中虽然有多个函数给线程特定数据赋值的,但最终调用的函数大概只有virSetError和virRaiseErrorFull,进入这两个函数:

void
virRaiseErrorFull(const char *filename,
                  const char *funcname,
                  size_t linenr,
                  int domain,
                  int code,
                  virErrorLevel level,
                  const char *str1,
                  const char *str2,
                  const char *str3,
                  int int1,
                  int int2,
                  const char *fmt, ...)
{
    int save_errno = errno;
    virErrorPtr to;
    char *str;
    virLogMetadata meta[] = {
        { .key = "LIBVIRT_DOMAIN", .s = NULL, .iv = domain },
        { .key = "LIBVIRT_CODE", .s = NULL, .iv = code },
        { .key = NULL },
    };

    /*
     * All errors are recorded in thread local storage
     * For compatibility, public API calls will copy them
     * to the per-connection error object when necessary
     */
    to = virLastErrorObject();
    if (!to) {
        errno = save_errno;
        return; /* Hit OOM allocating thread error object, sod all we can do now */
    }

    virResetError(to);

    if (code == VIR_ERR_OK) {
        errno = save_errno;
        return;
    }

    /*
     * formats the message; drop message on OOM situations
     */
    if (fmt == NULL) {
        ignore_value(VIR_STRDUP_QUIET(str, _("No error message provided")));
    } else {
        va_list ap;
        va_start(ap, fmt);
        ignore_value(virVasprintfQuiet(&str, fmt, ap));
        va_end(ap);
    }

    /*
     * Save the information about the error
     */
    /*
     * Deliberately not setting conn, dom & net fields since
     * they're utterly unsafe
     */
    to->domain = domain;
    to->code = code;
    to->message = str;
    to->level = level;
    ignore_value(VIR_STRDUP_QUIET(to->str1, str1));
    ignore_value(VIR_STRDUP_QUIET(to->str2, str2));
    ignore_value(VIR_STRDUP_QUIET(to->str3, str3));
    to->int1 = int1;
    to->int2 = int2;

    virRaiseErrorLog(filename, funcname, linenr,
                     to, meta);

    errno = save_errno;
}
static int
virCopyError(virErrorPtr from,
             virErrorPtr to)
{
    int ret = 0;
    if (!to)
        return 0;
    virResetError(to);
    if (!from)
        return 0;
    to->code = from->code;
    to->domain = from->domain;
    to->level = from->level;
    if (VIR_STRDUP_QUIET(to->message, from->message) < 0)
        ret = -1;
    if (VIR_STRDUP_QUIET(to->str1, from->str1) < 0)
        ret = -1;
    if (VIR_STRDUP_QUIET(to->str2, from->str2) < 0)
        ret = -1;
    if (VIR_STRDUP_QUIET(to->str3, from->str3) < 0)
        ret = -1;
    to->int1 = from->int1;
    to->int2 = from->int2;
    /*
     * Deliberately not setting 'conn', 'dom', 'net' references
     */
    return ret;
}

可以看到设置错误信息,都是先获取线程特定数据,然后将错误信息赋值给它。
最后virsh 和libvirtd初始化线程特定数据的函数都是virInitialize,定义如下:

int
virInitialize(void)
{
    if (virOnce(&virGlobalOnce, virGlobalInit) < 0)
        return -1;

    if (virGlobalError)
        return -1;
    return 0;
}

virOnce确保一个进程中virGlobalInit函数只执行一次,该函数中调用virErrorInitialize,经过一层层的跟进,可以看到最后pthread_key_create函数初始化了线程特定数据。

int
virErrorInitialize(void)
{
    return virThreadLocalInit(&virLastErr, virLastErrFreeData);
}
int virThreadLocalInit(virThreadLocalPtr l,
                       virThreadLocalCleanup c)
{
    int ret;
    if ((ret = pthread_key_create(&l->key, c)) != 0) {
        errno = ret;
        return -1;
    }
    return 0;
}

你可能感兴趣的:(虚拟化,libvirt)