virDomainBlockPeek QEMU and remote support

* qemud/remote.c, qemud/remote_protocol.x, src/remote_internal.c:
	  Remote support.
	* qemud/remote_dispatch_localvars.h,
	  qemud/remote_dispatch_proc_switch.h,
	  qemud/remote_dispatch_prototypes.h,
	  qemud/remote_protocol.c,
	  qemud/remote_protocol.h:
	  Generated files for remote support.
	* src/xen_unified.c, src/driver.h, src/libvirt.c: Small fix -
	  pass flags around internally.
	* src/qemu_driver.c: Support for QEMU.
	* src/xend_internal.c: Remove redundant fstat call from Xen.
This commit is contained in:
Richard W.M. Jones 2008-06-05 21:12:26 +00:00
parent 8354895e68
commit a73a88a19f
14 changed files with 259 additions and 7 deletions

View File

@ -1,3 +1,19 @@
Thu Jun 5 22:08:00 BST 2008 Richard W.M. Jones <rjones@redhat.com>
virDomainBlockPeek QEMU and remote support
* qemud/remote.c, qemud/remote_protocol.x, src/remote_internal.c:
Remote support.
* qemud/remote_dispatch_localvars.h,
qemud/remote_dispatch_proc_switch.h,
qemud/remote_dispatch_prototypes.h,
qemud/remote_protocol.c,
qemud/remote_protocol.h:
Generated files for remote support.
* src/xen_unified.c, src/driver.h, src/libvirt.c: Small fix -
pass flags around internally.
* src/qemu_driver.c: Support for QEMU.
* src/xend_internal.c: Remove redundant fstat call from Xen.
Thu Jun 5 14:10:00 BST 2008 Richard W.M. Jones <rjones@redhat.com> Thu Jun 5 14:10:00 BST 2008 Richard W.M. Jones <rjones@redhat.com>
virDomainBlockPeek call virDomainBlockPeek call

View File

@ -889,6 +889,54 @@ remoteDispatchDomainInterfaceStats (struct qemud_server *server ATTRIBUTE_UNUSED
return 0; return 0;
} }
static int
remoteDispatchDomainBlockPeek (struct qemud_server *server ATTRIBUTE_UNUSED,
struct qemud_client *client,
remote_message_header *req,
remote_domain_block_peek_args *args,
remote_domain_block_peek_ret *ret)
{
virDomainPtr dom;
char *path;
unsigned long long offset;
size_t size;
unsigned int flags;
CHECK_CONN (client);
dom = get_nonnull_domain (client->conn, args->dom);
if (dom == NULL) {
remoteDispatchError (client, req, "%s", _("domain not found"));
return -2;
}
path = args->path;
offset = args->offset;
size = args->size;
flags = args->flags;
if (size > REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX) {
remoteDispatchError (client, req,
"%s", _("size > maximum buffer size"));
return -2;
}
ret->buffer.buffer_len = size;
ret->buffer.buffer_val = malloc (size);
if (!ret->buffer.buffer_val) {
remoteDispatchError (client, req, "%s", strerror (errno));
return -2;
}
if (virDomainBlockPeek (dom, path, offset, size,
ret->buffer.buffer_val, flags) == -1) {
/* free (ret->buffer.buffer_val); - caller frees */
virDomainFree (dom);
return -1;
}
virDomainFree (dom);
return 0;
}
static int static int
remoteDispatchDomainAttachDevice (struct qemud_server *server ATTRIBUTE_UNUSED, remoteDispatchDomainAttachDevice (struct qemud_server *server ATTRIBUTE_UNUSED,
struct qemud_client *client, struct qemud_client *client,

View File

@ -41,6 +41,8 @@ remote_list_domains_ret lv_remote_list_domains_ret;
remote_network_define_xml_args lv_remote_network_define_xml_args; remote_network_define_xml_args lv_remote_network_define_xml_args;
remote_network_define_xml_ret lv_remote_network_define_xml_ret; remote_network_define_xml_ret lv_remote_network_define_xml_ret;
remote_get_type_ret lv_remote_get_type_ret; remote_get_type_ret lv_remote_get_type_ret;
remote_domain_block_peek_args lv_remote_domain_block_peek_args;
remote_domain_block_peek_ret lv_remote_domain_block_peek_ret;
remote_storage_vol_delete_args lv_remote_storage_vol_delete_args; remote_storage_vol_delete_args lv_remote_storage_vol_delete_args;
remote_network_dump_xml_args lv_remote_network_dump_xml_args; remote_network_dump_xml_args lv_remote_network_dump_xml_args;
remote_network_dump_xml_ret lv_remote_network_dump_xml_ret; remote_network_dump_xml_ret lv_remote_network_dump_xml_ret;

View File

@ -47,6 +47,15 @@ case REMOTE_PROC_DOMAIN_ATTACH_DEVICE:
args = (char *) &lv_remote_domain_attach_device_args; args = (char *) &lv_remote_domain_attach_device_args;
memset (&lv_remote_domain_attach_device_args, 0, sizeof lv_remote_domain_attach_device_args); memset (&lv_remote_domain_attach_device_args, 0, sizeof lv_remote_domain_attach_device_args);
break; break;
case REMOTE_PROC_DOMAIN_BLOCK_PEEK:
fn = (dispatch_fn) remoteDispatchDomainBlockPeek;
args_filter = (xdrproc_t) xdr_remote_domain_block_peek_args;
args = (char *) &lv_remote_domain_block_peek_args;
memset (&lv_remote_domain_block_peek_args, 0, sizeof lv_remote_domain_block_peek_args);
ret_filter = (xdrproc_t) xdr_remote_domain_block_peek_ret;
ret = (char *) &lv_remote_domain_block_peek_ret;
memset (&lv_remote_domain_block_peek_ret, 0, sizeof lv_remote_domain_block_peek_ret);
break;
case REMOTE_PROC_DOMAIN_BLOCK_STATS: case REMOTE_PROC_DOMAIN_BLOCK_STATS:
fn = (dispatch_fn) remoteDispatchDomainBlockStats; fn = (dispatch_fn) remoteDispatchDomainBlockStats;
args_filter = (xdrproc_t) xdr_remote_domain_block_stats_args; args_filter = (xdrproc_t) xdr_remote_domain_block_stats_args;

View File

@ -9,6 +9,7 @@ static int remoteDispatchAuthSaslStart (struct qemud_server *server, struct qemu
static int remoteDispatchAuthSaslStep (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_auth_sasl_step_args *args, remote_auth_sasl_step_ret *ret); static int remoteDispatchAuthSaslStep (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_auth_sasl_step_args *args, remote_auth_sasl_step_ret *ret);
static int remoteDispatchClose (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, void *args, void *ret); static int remoteDispatchClose (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, void *args, void *ret);
static int remoteDispatchDomainAttachDevice (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_attach_device_args *args, void *ret); static int remoteDispatchDomainAttachDevice (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_attach_device_args *args, void *ret);
static int remoteDispatchDomainBlockPeek (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_block_peek_args *args, remote_domain_block_peek_ret *ret);
static int remoteDispatchDomainBlockStats (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_block_stats_args *args, remote_domain_block_stats_ret *ret); static int remoteDispatchDomainBlockStats (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_block_stats_args *args, remote_domain_block_stats_ret *ret);
static int remoteDispatchDomainCoreDump (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_core_dump_args *args, void *ret); static int remoteDispatchDomainCoreDump (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_core_dump_args *args, void *ret);
static int remoteDispatchDomainCreate (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_create_args *args, void *ret); static int remoteDispatchDomainCreate (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_create_args *args, void *ret);

View File

@ -534,6 +534,33 @@ xdr_remote_domain_interface_stats_ret (XDR *xdrs, remote_domain_interface_stats_
return TRUE; return TRUE;
} }
bool_t
xdr_remote_domain_block_peek_args (XDR *xdrs, remote_domain_block_peek_args *objp)
{
if (!xdr_remote_nonnull_domain (xdrs, &objp->dom))
return FALSE;
if (!xdr_remote_nonnull_string (xdrs, &objp->path))
return FALSE;
if (!xdr_u_quad_t (xdrs, &objp->offset))
return FALSE;
if (!xdr_u_int (xdrs, &objp->size))
return FALSE;
if (!xdr_u_int (xdrs, &objp->flags))
return FALSE;
return TRUE;
}
bool_t
xdr_remote_domain_block_peek_ret (XDR *xdrs, remote_domain_block_peek_ret *objp)
{
char **objp_cpp0 = (char **) (void *) &objp->buffer.buffer_val;
if (!xdr_bytes (xdrs, objp_cpp0, (u_int *) &objp->buffer.buffer_len, REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX))
return FALSE;
return TRUE;
}
bool_t bool_t
xdr_remote_list_domains_args (XDR *xdrs, remote_list_domains_args *objp) xdr_remote_list_domains_args (XDR *xdrs, remote_list_domains_args *objp)
{ {

View File

@ -33,6 +33,7 @@ typedef remote_nonnull_string *remote_string;
#define REMOTE_NODE_MAX_CELLS 1024 #define REMOTE_NODE_MAX_CELLS 1024
#define REMOTE_AUTH_SASL_DATA_MAX 65536 #define REMOTE_AUTH_SASL_DATA_MAX 65536
#define REMOTE_AUTH_TYPE_LIST_MAX 20 #define REMOTE_AUTH_TYPE_LIST_MAX 20
#define REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX 65536
typedef char remote_uuid[VIR_UUID_BUFLEN]; typedef char remote_uuid[VIR_UUID_BUFLEN];
@ -263,6 +264,23 @@ struct remote_domain_interface_stats_ret {
}; };
typedef struct remote_domain_interface_stats_ret remote_domain_interface_stats_ret; typedef struct remote_domain_interface_stats_ret remote_domain_interface_stats_ret;
struct remote_domain_block_peek_args {
remote_nonnull_domain dom;
remote_nonnull_string path;
u_quad_t offset;
u_int size;
u_int flags;
};
typedef struct remote_domain_block_peek_args remote_domain_block_peek_args;
struct remote_domain_block_peek_ret {
struct {
u_int buffer_len;
char *buffer_val;
} buffer;
};
typedef struct remote_domain_block_peek_ret remote_domain_block_peek_ret;
struct remote_list_domains_args { struct remote_list_domains_args {
int maxids; int maxids;
}; };
@ -1138,6 +1156,7 @@ enum remote_procedure {
REMOTE_PROC_STORAGE_VOL_GET_PATH = 100, REMOTE_PROC_STORAGE_VOL_GET_PATH = 100,
REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY = 101, REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY = 101,
REMOTE_PROC_NODE_GET_FREE_MEMORY = 102, REMOTE_PROC_NODE_GET_FREE_MEMORY = 102,
REMOTE_PROC_DOMAIN_BLOCK_PEEK = 103,
}; };
typedef enum remote_procedure remote_procedure; typedef enum remote_procedure remote_procedure;
@ -1206,6 +1225,8 @@ extern bool_t xdr_remote_domain_block_stats_args (XDR *, remote_domain_block_st
extern bool_t xdr_remote_domain_block_stats_ret (XDR *, remote_domain_block_stats_ret*); extern bool_t xdr_remote_domain_block_stats_ret (XDR *, remote_domain_block_stats_ret*);
extern bool_t xdr_remote_domain_interface_stats_args (XDR *, remote_domain_interface_stats_args*); extern bool_t xdr_remote_domain_interface_stats_args (XDR *, remote_domain_interface_stats_args*);
extern bool_t xdr_remote_domain_interface_stats_ret (XDR *, remote_domain_interface_stats_ret*); extern bool_t xdr_remote_domain_interface_stats_ret (XDR *, remote_domain_interface_stats_ret*);
extern bool_t xdr_remote_domain_block_peek_args (XDR *, remote_domain_block_peek_args*);
extern bool_t xdr_remote_domain_block_peek_ret (XDR *, remote_domain_block_peek_ret*);
extern bool_t xdr_remote_list_domains_args (XDR *, remote_list_domains_args*); extern bool_t xdr_remote_list_domains_args (XDR *, remote_list_domains_args*);
extern bool_t xdr_remote_list_domains_ret (XDR *, remote_list_domains_ret*); extern bool_t xdr_remote_list_domains_ret (XDR *, remote_list_domains_ret*);
extern bool_t xdr_remote_num_of_domains_ret (XDR *, remote_num_of_domains_ret*); extern bool_t xdr_remote_num_of_domains_ret (XDR *, remote_num_of_domains_ret*);
@ -1381,6 +1402,8 @@ extern bool_t xdr_remote_domain_block_stats_args ();
extern bool_t xdr_remote_domain_block_stats_ret (); extern bool_t xdr_remote_domain_block_stats_ret ();
extern bool_t xdr_remote_domain_interface_stats_args (); extern bool_t xdr_remote_domain_interface_stats_args ();
extern bool_t xdr_remote_domain_interface_stats_ret (); extern bool_t xdr_remote_domain_interface_stats_ret ();
extern bool_t xdr_remote_domain_block_peek_args ();
extern bool_t xdr_remote_domain_block_peek_ret ();
extern bool_t xdr_remote_list_domains_args (); extern bool_t xdr_remote_list_domains_args ();
extern bool_t xdr_remote_list_domains_ret (); extern bool_t xdr_remote_list_domains_ret ();
extern bool_t xdr_remote_num_of_domains_ret (); extern bool_t xdr_remote_num_of_domains_ret ();

View File

@ -96,6 +96,12 @@ const REMOTE_AUTH_SASL_DATA_MAX = 65536;
/* Maximum number of auth types */ /* Maximum number of auth types */
const REMOTE_AUTH_TYPE_LIST_MAX = 20; const REMOTE_AUTH_TYPE_LIST_MAX = 20;
/* Maximum length of a block or memory peek buffer message.
* Note applications need to be aware of this limit and issue multiple
* requests for large amounts of data.
*/
const REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX = 65536;
/* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */
typedef opaque remote_uuid[VIR_UUID_BUFLEN]; typedef opaque remote_uuid[VIR_UUID_BUFLEN];
@ -322,6 +328,18 @@ struct remote_domain_interface_stats_ret {
hyper tx_drop; hyper tx_drop;
}; };
struct remote_domain_block_peek_args {
remote_nonnull_domain dom;
remote_nonnull_string path;
unsigned hyper offset;
unsigned size;
unsigned flags;
};
struct remote_domain_block_peek_ret {
opaque buffer<REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX>;
};
struct remote_list_domains_args { struct remote_list_domains_args {
int maxids; int maxids;
}; };
@ -1036,7 +1054,9 @@ enum remote_procedure {
REMOTE_PROC_STORAGE_VOL_GET_PATH = 100, REMOTE_PROC_STORAGE_VOL_GET_PATH = 100,
REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY = 101, REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY = 101,
REMOTE_PROC_NODE_GET_FREE_MEMORY = 102 REMOTE_PROC_NODE_GET_FREE_MEMORY = 102,
REMOTE_PROC_DOMAIN_BLOCK_PEEK = 103
}; };
/* Custom RPC structure. */ /* Custom RPC structure. */

View File

@ -230,7 +230,8 @@ typedef int
(virDomainPtr domain, (virDomainPtr domain,
const char *path, const char *path,
unsigned long long offset, size_t size, unsigned long long offset, size_t size,
void *buffer); void *buffer,
unsigned int flags);
typedef int typedef int
(*virDrvDomainMigratePrepare) (*virDrvDomainMigratePrepare)

View File

@ -2659,7 +2659,8 @@ virDomainBlockPeek (virDomainPtr dom,
} }
if (conn->driver->domainBlockPeek) if (conn->driver->domainBlockPeek)
return conn->driver->domainBlockPeek (dom, path, offset, size, buffer); return conn->driver->domainBlockPeek (dom, path, offset, size,
buffer, flags);
virLibDomainError (dom, VIR_ERR_NO_SUPPORT, __FUNCTION__); virLibDomainError (dom, VIR_ERR_NO_SUPPORT, __FUNCTION__);
return -1; return -1;

View File

@ -3163,6 +3163,64 @@ qemudDomainInterfaceStats (virDomainPtr dom,
#endif #endif
} }
static int
qemudDomainBlockPeek (virDomainPtr dom,
const char *path,
unsigned long long offset, size_t size,
void *buffer,
unsigned int flags ATTRIBUTE_UNUSED)
{
struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
struct qemud_vm *vm = qemudFindVMByUUID (driver, dom->uuid);
int i;
int fd, ret = -1;
if (!vm) {
qemudReportError (dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN,
_("no domain with matching uuid"));
return -1;
}
if (!path || path[0] == '\0') {
qemudReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_ARG,
_("NULL or empty path"));
return -1;
}
/* Check the path belongs to this domain. */
for (i = 0; i < vm->def->ndisks; ++i) {
if (STREQ (vm->def->disks[i].src, path)) goto found;
}
qemudReportError (dom->conn, dom, NULL, VIR_ERR_INVALID_ARG,
_("invalid path"));
return -1;
found:
/* The path is correct, now try to open it and get its size. */
fd = open (path, O_RDONLY);
if (fd == -1) {
qemudReportError (dom->conn, dom, NULL, VIR_ERR_SYSTEM_ERROR,
"%s", strerror (errno));
goto done;
}
/* Seek and read. */
/* NB. Because we configure with AC_SYS_LARGEFILE, off_t should
* be 64 bits on all platforms.
*/
if (lseek (fd, offset, SEEK_SET) == (off_t) -1 ||
saferead (fd, buffer, size) == (ssize_t) -1) {
qemudReportError (dom->conn, dom, NULL, VIR_ERR_SYSTEM_ERROR,
"%s", strerror (errno));
goto done;
}
ret = 0;
done:
if (fd >= 0) close (fd);
return ret;
}
static virNetworkPtr qemudNetworkLookupByUUID(virConnectPtr conn ATTRIBUTE_UNUSED, static virNetworkPtr qemudNetworkLookupByUUID(virConnectPtr conn ATTRIBUTE_UNUSED,
const unsigned char *uuid) { const unsigned char *uuid) {
struct qemud_driver *driver = (struct qemud_driver *)conn->networkPrivateData; struct qemud_driver *driver = (struct qemud_driver *)conn->networkPrivateData;
@ -3521,7 +3579,7 @@ static virDriver qemuDriver = {
NULL, /* domainMigrateFinish */ NULL, /* domainMigrateFinish */
qemudDomainBlockStats, /* domainBlockStats */ qemudDomainBlockStats, /* domainBlockStats */
qemudDomainInterfaceStats, /* domainInterfaceStats */ qemudDomainInterfaceStats, /* domainInterfaceStats */
NULL, /* domainBlockPeek */ qemudDomainBlockPeek, /* domainBlockPeek */
#if HAVE_NUMACTL #if HAVE_NUMACTL
qemudNodeGetCellsFreeMemory, /* nodeGetCellsFreeMemory */ qemudNodeGetCellsFreeMemory, /* nodeGetCellsFreeMemory */
qemudNodeGetFreeMemory, /* getFreeMemory */ qemudNodeGetFreeMemory, /* getFreeMemory */

View File

@ -2374,6 +2374,52 @@ remoteDomainInterfaceStats (virDomainPtr domain, const char *path,
return 0; return 0;
} }
static int
remoteDomainBlockPeek (virDomainPtr domain,
const char *path,
unsigned long long offset,
size_t size,
void *buffer,
unsigned int flags)
{
remote_domain_block_peek_args args;
remote_domain_block_peek_ret ret;
GET_PRIVATE (domain->conn, -1);
if (size > REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX) {
errorf (domain->conn, VIR_ERR_RPC,
_("block peek request too large for remote protocol, %zi > %d"),
size, REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX);
return -1;
}
make_nonnull_domain (&args.dom, domain);
args.path = (char *) path;
args.offset = offset;
args.size = size;
args.flags = flags;
memset (&ret, 0, sizeof ret);
if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_BLOCK_PEEK,
(xdrproc_t) xdr_remote_domain_block_peek_args,
(char *) &args,
(xdrproc_t) xdr_remote_domain_block_peek_ret,
(char *) &ret) == -1)
return -1;
if (ret.buffer.buffer_len != size) {
errorf (domain->conn, VIR_ERR_RPC,
_("returned buffer is not same size as requested"));
free (ret.buffer.buffer_val);
return -1;
}
memcpy (buffer, ret.buffer.buffer_val, size);
free (ret.buffer.buffer_val);
return 0;
}
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
static int static int
@ -4784,6 +4830,7 @@ static virDriver driver = {
.domainMigrateFinish = remoteDomainMigrateFinish, .domainMigrateFinish = remoteDomainMigrateFinish,
.domainBlockStats = remoteDomainBlockStats, .domainBlockStats = remoteDomainBlockStats,
.domainInterfaceStats = remoteDomainInterfaceStats, .domainInterfaceStats = remoteDomainInterfaceStats,
.domainBlockPeek = remoteDomainBlockPeek,
.nodeGetCellsFreeMemory = remoteNodeGetCellsFreeMemory, .nodeGetCellsFreeMemory = remoteNodeGetCellsFreeMemory,
.getFreeMemory = remoteNodeGetFreeMemory, .getFreeMemory = remoteNodeGetFreeMemory,
}; };

View File

@ -1237,7 +1237,7 @@ xenUnifiedDomainInterfaceStats (virDomainPtr dom, const char *path,
static int static int
xenUnifiedDomainBlockPeek (virDomainPtr dom, const char *path, xenUnifiedDomainBlockPeek (virDomainPtr dom, const char *path,
unsigned long long offset, size_t size, unsigned long long offset, size_t size,
void *buffer) void *buffer, unsigned int flags ATTRIBUTE_UNUSED)
{ {
int r; int r;
GET_PRIVATE (dom->conn); GET_PRIVATE (dom->conn);

View File

@ -4541,7 +4541,6 @@ xenDaemonDomainBlockPeek (virDomainPtr domain, const char *path,
struct sexpr *root; struct sexpr *root;
struct check_path_data data; struct check_path_data data;
int fd, ret = -1; int fd, ret = -1;
struct stat statbuf;
priv = (xenUnifiedPrivatePtr) domain->conn->privateData; priv = (xenUnifiedPrivatePtr) domain->conn->privateData;
@ -4583,7 +4582,7 @@ xenDaemonDomainBlockPeek (virDomainPtr domain, const char *path,
/* The path is correct, now try to open it and get its size. */ /* The path is correct, now try to open it and get its size. */
fd = open (path, O_RDONLY); fd = open (path, O_RDONLY);
if (fd == -1 || fstat (fd, &statbuf) == -1) { if (fd == -1) {
virXendError (domain->conn, VIR_ERR_SYSTEM_ERROR, strerror (errno)); virXendError (domain->conn, VIR_ERR_SYSTEM_ERROR, strerror (errno));
goto done; goto done;
} }