1 /*
2  * Copyright (C) 2015-2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <trusty_ipc.h>
21 
22 #define TLOG_TAG "storage_client"
23 #include <trusty_log.h>
24 
25 #include <uapi/err.h>
26 
27 #include <lib/storage/storage.h>
28 
29 #define TLOGE_APP_NAME(fmt, ...) TLOGE("%s: " fmt, __progname, ##__VA_ARGS__)
30 
31 #define MAX_CHUNK_SIZE 4040
32 
33 /* At what delay threshold should wait_infinite_logged() start logging? */
34 #define WAIT_INFINITE_LOG_THRESHOLD_MSEC 1000
35 
36 /* Maximum timeout value to use for wait_infinite_logged */
37 #define WAIT_INFINITE_LOG_MAX_TIMEOUT_MSEC 60000
38 
39 /* Initialized by __init_libc in ./trusty/musl/src/env/__libc_start_main.c */
40 extern char* __progname;
41 
make_file_handle(storage_session_t s,uint32_t fid)42 static inline file_handle_t make_file_handle(storage_session_t s,
43                                              uint32_t fid) {
44     return ((uint64_t)s << 32) | fid;
45 }
46 
_to_session(file_handle_t fh)47 static inline storage_session_t _to_session(file_handle_t fh) {
48     return (storage_session_t)(fh >> 32);
49 }
50 
_to_handle(file_handle_t fh)51 static inline uint32_t _to_handle(file_handle_t fh) {
52     return (uint32_t)fh;
53 }
54 
_to_msg_flags(uint32_t opflags)55 static inline uint32_t _to_msg_flags(uint32_t opflags) {
56     uint32_t msg_flags = 0;
57 
58     if (opflags & STORAGE_OP_COMPLETE)
59         msg_flags |= STORAGE_MSG_FLAG_TRANSACT_COMPLETE;
60 
61     if (opflags & STORAGE_OP_CHECKPOINT) {
62         if ((msg_flags & STORAGE_MSG_FLAG_TRANSACT_COMPLETE) == 0) {
63             TLOGE("STORAGE_OP_CHECKPOINT only valid when committing a checkpoint\n");
64         }
65         msg_flags |= STORAGE_MSG_FLAG_TRANSACT_CHECKPOINT;
66     }
67 
68     if (opflags & STORAGE_OP_FS_REPAIRED_ACK) {
69         msg_flags |= STORAGE_MSG_FLAG_FS_REPAIRED_ACK;
70     }
71 
72     return msg_flags;
73 }
74 
check_response(struct storage_msg * msg,ssize_t res)75 static ssize_t check_response(struct storage_msg* msg, ssize_t res) {
76     if (res < 0)
77         return res;
78 
79     if ((size_t)res < sizeof(*msg)) {
80         TLOGE("invalid msg length (%zd < %zd)\n", (size_t)res, sizeof(*msg));
81         return ERR_IO;
82     }
83 
84     /* TLOGI("cmd 0x%x: server returned %u\n", msg->cmd, msg->result); */
85 
86     switch (msg->result) {
87     case STORAGE_NO_ERROR:
88         return res - sizeof(*msg);
89 
90     case STORAGE_ERR_NOT_FOUND:
91         return ERR_NOT_FOUND;
92 
93     case STORAGE_ERR_EXIST:
94         return ERR_ALREADY_EXISTS;
95 
96     case STORAGE_ERR_NOT_VALID:
97         return ERR_NOT_VALID;
98 
99     case STORAGE_ERR_NOT_ALLOWED:
100         return ERR_NOT_ALLOWED;
101 
102     case STORAGE_ERR_TRANSACT:
103         return ERR_BUSY;
104 
105     case STORAGE_ERR_ACCESS:
106         return ERR_ACCESS_DENIED;
107 
108     case STORAGE_ERR_FS_REPAIRED:
109         return ERR_BAD_STATE;
110 
111     case STORAGE_ERR_UNIMPLEMENTED:
112         TLOGE("cmd 0x%x: is unhandles command\n", msg->cmd);
113         return ERR_NOT_IMPLEMENTED;
114 
115     case STORAGE_ERR_GENERIC:
116         TLOGE("cmd 0x%x: internal server error\n", msg->cmd);
117         return ERR_GENERIC;
118 
119     default:
120         TLOGE("cmd 0x%x: unhandled server response %u\n", msg->cmd,
121               msg->result);
122     }
123 
124     return ERR_IO;
125 }
126 
wait_infinite_logged(storage_session_t session,uevent_t * ev,const char * caller)127 int wait_infinite_logged(storage_session_t session,
128                          uevent_t* ev,
129                          const char* caller) {
130     unsigned long wait_time = WAIT_INFINITE_LOG_THRESHOLD_MSEC;
131 
132     /* wait for reply */
133     int rc;
134     do {
135         rc = wait(session, ev, wait_time);
136         if (rc == ERR_TIMED_OUT) {
137             TLOGE_APP_NAME(
138                     "Timed out after %ldx milliseconds, retrying; "
139                     "called by %s\n",
140                     wait_time, caller);
141             wait_time = wait_time * 2;
142             if (wait_time > WAIT_INFINITE_LOG_MAX_TIMEOUT_MSEC) {
143                 wait_time = WAIT_INFINITE_LOG_MAX_TIMEOUT_MSEC;
144             }
145         }
146     } while (rc == ERR_TIMED_OUT);
147 
148     if (wait_time != WAIT_INFINITE_LOG_THRESHOLD_MSEC) {
149         TLOGE_APP_NAME(
150                 "Finally succeeded (last wait: < %ldx milliseconds); "
151                 "called by %s\n",
152                 wait_time, caller);
153     }
154 
155     return rc;
156 }
157 
get_response(storage_session_t session,struct iovec * rx_iovs,uint32_t rx_iovcnt)158 static ssize_t get_response(storage_session_t session,
159                             struct iovec* rx_iovs,
160                             uint32_t rx_iovcnt)
161 
162 {
163     uevent_t ev;
164     struct ipc_msg_info mi;
165     struct ipc_msg rx_msg = {
166             .iov = rx_iovs,
167             .num_iov = rx_iovcnt,
168     };
169 
170     if (!rx_iovcnt)
171         return 0;
172 
173     /* wait for reply */
174     int rc = wait_infinite_logged(session, &ev, __func__);
175     if (rc != NO_ERROR) {
176         TLOGE("%s: interrupted waiting for response", __func__);
177         return rc;
178     }
179 
180     rc = get_msg(session, &mi);
181     if (rc != NO_ERROR) {
182         TLOGE("%s: failed to get_msg (%d)\n", __func__, rc);
183         return rc;
184     }
185 
186     rc = read_msg(session, mi.id, 0, &rx_msg);
187     put_msg(session, mi.id);
188     if (rc < 0) {
189         TLOGE("%s: failed to read msg (%d)\n", __func__, rc);
190         return rc;
191     }
192 
193     if ((size_t)rc != mi.len) {
194         TLOGE("%s: partial message read (%zd vs. %zd)\n", __func__, (size_t)rc,
195               mi.len);
196         return ERR_IO;
197     }
198 
199     return rc;
200 }
201 
wait_to_send(handle_t session,struct ipc_msg * msg)202 static int wait_to_send(handle_t session, struct ipc_msg* msg) {
203     int rc;
204     struct uevent ev;
205 
206     rc = wait_infinite_logged(session, &ev, __func__);
207     if (rc < 0) {
208         TLOGE("failed to wait for outgoing queue to free up\n");
209         return rc;
210     }
211 
212     if (ev.event & IPC_HANDLE_POLL_SEND_UNBLOCKED) {
213         return send_msg(session, msg);
214     }
215 
216     if (ev.event & IPC_HANDLE_POLL_MSG) {
217         return ERR_BUSY;
218     }
219 
220     if (ev.event & IPC_HANDLE_POLL_HUP) {
221         return ERR_CHANNEL_CLOSED;
222     }
223 
224     return rc;
225 }
226 
send_reqv(storage_session_t session,struct iovec * tx_iovs,uint32_t tx_iovcnt,struct iovec * rx_iovs,uint32_t rx_iovcnt)227 ssize_t send_reqv(storage_session_t session,
228                   struct iovec* tx_iovs,
229                   uint32_t tx_iovcnt,
230                   struct iovec* rx_iovs,
231                   uint32_t rx_iovcnt) {
232     ssize_t rc;
233 
234     struct ipc_msg tx_msg = {
235             .iov = tx_iovs,
236             .num_iov = tx_iovcnt,
237     };
238 
239     rc = send_msg(session, &tx_msg);
240     if (rc == ERR_NOT_ENOUGH_BUFFER) {
241         rc = wait_to_send(session, &tx_msg);
242     }
243 
244     if (rc < 0) {
245         TLOGE("%s: failed (%d) to send_msg\n", __func__, (int)rc);
246         return rc;
247     }
248 
249     rc = get_response(session, rx_iovs, rx_iovcnt);
250     if (rc < 0) {
251         TLOGI("%s: failed (%d) to get response\n", __func__, (int)rc);
252         return rc;
253     }
254 
255     return rc;
256 }
257 
storage_open_session(storage_session_t * session_p,const char * type)258 int storage_open_session(storage_session_t* session_p, const char* type) {
259     long rc = connect(type, IPC_CONNECT_WAIT_FOR_PORT);
260     if (rc < 0) {
261         *session_p = STORAGE_INVALID_SESSION;
262         return rc;
263     }
264 
265     *session_p = (storage_session_t)rc;
266     return NO_ERROR;
267 }
268 
storage_close_session(storage_session_t session)269 void storage_close_session(storage_session_t session) {
270     close(session);
271 }
272 
storage_open_file(storage_session_t session,file_handle_t * handle_p,const char * name,uint32_t flags,uint32_t opflags)273 int storage_open_file(storage_session_t session,
274                       file_handle_t* handle_p,
275                       const char* name,
276                       uint32_t flags,
277                       uint32_t opflags) {
278     struct storage_msg msg = {.cmd = STORAGE_FILE_OPEN,
279                               .flags = _to_msg_flags(opflags)};
280     struct storage_file_open_req req = {.flags = flags};
281     struct iovec tx[3] = {{&msg, sizeof(msg)},
282                           {&req, sizeof(req)},
283                           {(void*)name, strlen(name)}};
284     struct storage_file_open_resp rsp = {0};
285     struct iovec rx[2] = {{&msg, sizeof(msg)}, {&rsp, sizeof(rsp)}};
286 
287     *handle_p = make_file_handle(STORAGE_INVALID_SESSION, 0);
288 
289     ssize_t rc = send_reqv(session, tx, 3, rx, 2);
290     rc = check_response(&msg, rc);
291     if (rc < 0)
292         return rc;
293 
294     if ((size_t)rc != sizeof(rsp)) {
295         TLOGE("%s: invalid response length (%zd != %zd)\n", __func__,
296               (size_t)rc, sizeof(rsp));
297         return ERR_IO;
298     }
299     *handle_p = make_file_handle(session, rsp.handle);
300     return NO_ERROR;
301 }
302 
storage_close_file(file_handle_t fh)303 void storage_close_file(file_handle_t fh) {
304     struct storage_msg msg = {.cmd = STORAGE_FILE_CLOSE};
305     struct storage_file_close_req req = {.handle = _to_handle(fh)};
306     struct iovec tx[2] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}};
307     struct iovec rx[1] = {{&msg, sizeof(msg)}};
308 
309     ssize_t rc = send_reqv(_to_session(fh), tx, 2, rx, 1);
310     rc = check_response(&msg, rc);
311     if (rc < 0) {
312         TLOGE("close file failed (%d)\n", (int)rc);
313     }
314 }
315 
storage_move_file(storage_session_t session,file_handle_t handle,const char * old_name,const char * new_name,uint32_t flags,uint32_t opflags)316 int storage_move_file(storage_session_t session,
317                       file_handle_t handle,
318                       const char* old_name,
319                       const char* new_name,
320                       uint32_t flags,
321                       uint32_t opflags) {
322     size_t old_name_len = strlen(old_name);
323     size_t new_name_len = strlen(new_name);
324     struct storage_msg msg = {
325             .cmd = STORAGE_FILE_MOVE,
326             .flags = _to_msg_flags(opflags),
327     };
328     struct storage_file_move_req req = {
329             .flags = flags,
330             .handle = _to_handle(handle),
331             .old_name_len = old_name_len,
332     };
333     struct iovec tx[4] = {
334             {&msg, sizeof(msg)},
335             {&req, sizeof(req)},
336             {(void*)old_name, old_name_len},
337             {(void*)new_name, new_name_len},
338     };
339     struct iovec rx[1] = {{&msg, sizeof(msg)}};
340 
341     ssize_t rc = send_reqv(session, tx, 4, rx, 1);
342     return (int)check_response(&msg, rc);
343 }
344 
storage_delete_file(storage_session_t session,const char * name,uint32_t opflags)345 int storage_delete_file(storage_session_t session,
346                         const char* name,
347                         uint32_t opflags) {
348     struct storage_msg msg = {.cmd = STORAGE_FILE_DELETE,
349                               .flags = _to_msg_flags(opflags)};
350     struct storage_file_delete_req req = {
351             .flags = 0,
352     };
353     struct iovec tx[3] = {{&msg, sizeof(msg)},
354                           {&req, sizeof(req)},
355                           {(void*)name, strlen(name)}};
356     struct iovec rx[1] = {{&msg, sizeof(msg)}};
357 
358     ssize_t rc = send_reqv(session, tx, 3, rx, 1);
359     return (int)check_response(&msg, rc);
360 }
361 
362 struct storage_open_dir_state {
363     uint8_t buf[MAX_CHUNK_SIZE];
364     size_t buf_size;
365     size_t buf_last_read;
366     size_t buf_read;
367 };
368 
storage_open_dir(storage_session_t session,const char * path,struct storage_open_dir_state ** state)369 int storage_open_dir(storage_session_t session,
370                      const char* path,
371                      struct storage_open_dir_state** state) {
372     struct storage_file_list_resp* resp;
373 
374     if (path && strlen(path)) {
375         *state = NULL;
376         return ERR_NOT_FOUND; /* current server does not support directories */
377     }
378     *state = malloc(sizeof(**state));
379     if (*state == NULL) {
380         return ERR_NO_MEMORY;
381     }
382     resp = (void*)(*state)->buf;
383     resp->flags = STORAGE_FILE_LIST_START;
384     (*state)->buf_size = sizeof(*resp);
385     (*state)->buf_last_read = 0;
386     (*state)->buf_read = (*state)->buf_size;
387 
388     return 0;
389 }
390 
storage_close_dir(storage_session_t session,struct storage_open_dir_state * state)391 void storage_close_dir(storage_session_t session,
392                        struct storage_open_dir_state* state) {
393     free(state);
394 }
395 
storage_read_dir_send_message(storage_session_t session,struct storage_open_dir_state * state)396 static int storage_read_dir_send_message(storage_session_t session,
397                                          struct storage_open_dir_state* state) {
398     struct storage_file_list_resp* last_item =
399             (void*)(state->buf + state->buf_last_read);
400     struct storage_msg msg = {.cmd = STORAGE_FILE_LIST};
401     struct storage_file_list_req req = {.flags = last_item->flags};
402     struct iovec tx[3] = {
403             {&msg, sizeof(msg)},
404             {&req, sizeof(req)},
405     };
406     uint32_t tx_count = 2;
407     struct iovec rx[2] = {{&msg, sizeof(msg)},
408                           {state->buf, sizeof(state->buf)}};
409     ssize_t rc;
410 
411     if (last_item->flags != STORAGE_FILE_LIST_START) {
412         tx[2].iov_base = last_item->name;
413         tx[2].iov_len = strlen(last_item->name);
414         tx_count = 3;
415     }
416 
417     rc = send_reqv(session, tx, tx_count, rx, 2);
418     rc = check_response(&msg, rc);
419 
420     state->buf_size = (rc > 0) ? rc : 0;
421     state->buf_last_read = 0;
422     state->buf_read = 0;
423 
424     if (rc < 0)
425         return rc;
426 
427     return 0;
428 }
429 
storage_read_dir(storage_session_t session,struct storage_open_dir_state * state,uint8_t * flags,char * name,size_t name_out_size)430 int storage_read_dir(storage_session_t session,
431                      struct storage_open_dir_state* state,
432                      uint8_t* flags,
433                      char* name,
434                      size_t name_out_size) {
435     int ret;
436     size_t rem;
437     size_t name_size;
438     struct storage_file_list_resp* item;
439 
440     if (state->buf_size == 0) {
441         return ERR_IO;
442     }
443 
444     if (state->buf_read >= state->buf_size) {
445         ret = storage_read_dir_send_message(session, state);
446         if (ret) {
447             return ret;
448         }
449     }
450     rem = state->buf_size - state->buf_read;
451     if (rem < sizeof(*item)) {
452         TLOGE("got short response\n");
453         return ERR_IO;
454     }
455     item = (void*)(state->buf + state->buf_read);
456     rem -= sizeof(*item);
457 
458     *flags = item->flags;
459     if ((item->flags & STORAGE_FILE_LIST_STATE_MASK) == STORAGE_FILE_LIST_END) {
460         state->buf_size = 0;
461         name_size = 0;
462     } else {
463         name_size = strnlen(item->name, rem) + 1;
464         if (name_size > rem) {
465             TLOGE("got invalid filename size %zd >= %zd\n", name_size, rem);
466             return ERR_IO;
467         }
468         if (name_size >= name_out_size) {
469             return ERR_NOT_ENOUGH_BUFFER;
470         }
471         strcpy(name, item->name);
472     }
473 
474     state->buf_last_read = state->buf_read;
475     state->buf_read += sizeof(*item) + name_size;
476 
477     return name_size;
478 }
479 
_read_chunk(file_handle_t fh,storage_off_t off,void * buf,size_t size)480 static ssize_t _read_chunk(file_handle_t fh,
481                            storage_off_t off,
482                            void* buf,
483                            size_t size) {
484     struct storage_msg msg = {.cmd = STORAGE_FILE_READ};
485     struct storage_file_read_req req = {
486             .handle = _to_handle(fh),
487             .size = size,
488             .offset = off,
489     };
490     struct iovec tx[2] = {
491             {&msg, sizeof(msg)},
492             {&req, sizeof(req)},
493     };
494     struct iovec rx[2] = {
495             {&msg, sizeof(msg)},
496             {buf, size},
497     };
498 
499     ssize_t rc = send_reqv(_to_session(fh), tx, 2, rx, 2);
500     return check_response(&msg, rc);
501 }
502 
storage_read(file_handle_t fh,storage_off_t off,void * buf,size_t size)503 ssize_t storage_read(file_handle_t fh,
504                      storage_off_t off,
505                      void* buf,
506                      size_t size) {
507     ssize_t rc;
508     size_t bytes_read = 0;
509     size_t chunk = MAX_CHUNK_SIZE;
510     uint8_t* ptr = buf;
511 
512     while (size) {
513         if (chunk > size)
514             chunk = size;
515         rc = _read_chunk(fh, off, ptr, chunk);
516         if (rc < 0)
517             return rc;
518         if (rc == 0)
519             break;
520         off += rc;
521         ptr += rc;
522         bytes_read += rc;
523         size -= rc;
524     }
525     return bytes_read;
526 }
527 
_write_req(file_handle_t fh,storage_off_t off,const void * buf,size_t size,uint32_t msg_flags)528 static ssize_t _write_req(file_handle_t fh,
529                           storage_off_t off,
530                           const void* buf,
531                           size_t size,
532                           uint32_t msg_flags) {
533     struct storage_msg msg = {
534             .cmd = STORAGE_FILE_WRITE,
535             .flags = msg_flags,
536     };
537     struct storage_file_write_req req = {
538             .handle = _to_handle(fh),
539             .offset = off,
540     };
541     struct iovec tx[3] = {
542             {&msg, sizeof(msg)}, {&req, sizeof(req)}, {(void*)buf, size}};
543     struct iovec rx[1] = {{&msg, sizeof(msg)}};
544 
545     ssize_t rc = send_reqv(_to_session(fh), tx, 3, rx, 1);
546     rc = check_response(&msg, rc);
547     return rc < 0 ? rc : (ssize_t)size;
548 }
549 
storage_write(file_handle_t fh,storage_off_t off,const void * buf,size_t size,uint32_t opflags)550 ssize_t storage_write(file_handle_t fh,
551                       storage_off_t off,
552                       const void* buf,
553                       size_t size,
554                       uint32_t opflags) {
555     ssize_t rc;
556     size_t bytes_written = 0;
557     size_t chunk = MAX_CHUNK_SIZE;
558     const uint8_t* ptr = buf;
559     uint32_t msg_flags = _to_msg_flags(opflags & ~STORAGE_OP_COMPLETE);
560 
561     while (size) {
562         if (chunk >= size) {
563             /* last chunk in sequence */
564             chunk = size;
565             msg_flags = _to_msg_flags(opflags);
566         }
567         rc = _write_req(fh, off, ptr, chunk, msg_flags);
568         if (rc < 0)
569             return rc;
570         if ((size_t)rc != chunk) {
571             TLOGE("got partial write (%d)\n", (int)rc);
572             return ERR_IO;
573         }
574         off += chunk;
575         ptr += chunk;
576         bytes_written += chunk;
577         size -= chunk;
578     }
579     return bytes_written;
580 }
581 
storage_set_file_size(file_handle_t fh,storage_off_t file_size,uint32_t opflags)582 int storage_set_file_size(file_handle_t fh,
583                           storage_off_t file_size,
584                           uint32_t opflags) {
585     struct storage_msg msg = {.cmd = STORAGE_FILE_SET_SIZE,
586                               .flags = _to_msg_flags(opflags)};
587     struct storage_file_set_size_req req = {
588             .handle = _to_handle(fh),
589             .size = file_size,
590     };
591     struct iovec tx[2] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}};
592     struct iovec rx[1] = {{&msg, sizeof(msg)}};
593 
594     ssize_t rc = send_reqv(_to_session(fh), tx, 2, rx, 1);
595     return (int)check_response(&msg, rc);
596 }
597 
storage_get_file_size(file_handle_t fh,storage_off_t * size_p)598 int storage_get_file_size(file_handle_t fh, storage_off_t* size_p) {
599     struct storage_msg msg = {.cmd = STORAGE_FILE_GET_SIZE};
600     struct storage_file_get_size_req req = {
601             .handle = _to_handle(fh),
602     };
603     struct iovec tx[2] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}};
604     struct storage_file_get_size_resp rsp;
605     struct iovec rx[2] = {{&msg, sizeof(msg)}, {&rsp, sizeof(rsp)}};
606 
607     *size_p = 0;
608 
609     ssize_t rc = send_reqv(_to_session(fh), tx, 2, rx, 2);
610     rc = check_response(&msg, rc);
611     if (rc < 0)
612         return rc;
613 
614     if ((size_t)rc != sizeof(rsp)) {
615         TLOGE("%s: invalid response length (%zd != %zd)\n", __func__,
616               (size_t)rc, sizeof(rsp));
617         return ERR_IO;
618     }
619 
620     *size_p = rsp.size;
621     return NO_ERROR;
622 }
623 
storage_end_transaction(storage_session_t session,bool complete)624 int storage_end_transaction(storage_session_t session, bool complete) {
625     struct storage_msg msg = {
626             .cmd = STORAGE_END_TRANSACTION,
627             .flags = complete ? STORAGE_MSG_FLAG_TRANSACT_COMPLETE : 0,
628     };
629     struct iovec iov = {&msg, sizeof(msg)};
630 
631     ssize_t rc = send_reqv(session, &iov, 1, &iov, 1);
632     return (int)check_response(&msg, rc);
633 }
634 
storage_commit_checkpoint(storage_session_t session)635 int storage_commit_checkpoint(storage_session_t session) {
636     struct storage_msg msg = {
637             .cmd = STORAGE_END_TRANSACTION,
638             .flags = STORAGE_MSG_FLAG_TRANSACT_COMPLETE |
639                      STORAGE_MSG_FLAG_TRANSACT_CHECKPOINT,
640     };
641     struct iovec iov = {&msg, sizeof(msg)};
642 
643     ssize_t rc = send_reqv(session, &iov, 1, &iov, 1);
644     return (int)check_response(&msg, rc);
645 }
646