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;
}