1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
  14  */
  15 
  16 #include <sys/debug.h>
  17 #include <string.h>
  18 #include <unistd.h>
  19 #include <errno.h>
  20 #include <umem.h>
  21 #include <inttypes.h>
  22 
  23 #include <sys/krrp.h>
  24 #include "libkrrp.h"
  25 #include "libkrrp_impl.h"
  26 
  27 /*
  28  * The initial size of buffer that will be
  29  * passed to kernel when a KRRP_IOCTL is called
  30  */
  31 #define LIBKRRP_INIT_BUF_SIZE 4096
  32 
  33 static int krrp_ioctl_call(libkrrp_handle_t *, krrp_ioctl_cmd_t,
  34     krrp_ioctl_data_t *);
  35 static int krrp_ioctl_alloc_data(libkrrp_handle_t *,
  36     krrp_ioctl_data_t **, size_t);
  37 static void krrp_ioctl_free_data(krrp_ioctl_data_t *);
  38 static int krrp_ioctl_data_from_nvl(libkrrp_handle_t *,
  39     krrp_ioctl_data_t **, nvlist_t *, size_t);
  40 static int krrp_ioctl_data_to_nvl(libkrrp_handle_t *,
  41     krrp_ioctl_data_t *, nvlist_t **);
  42 
  43 int
  44 krrp_ioctl_perform(libkrrp_handle_t *hdl, krrp_ioctl_cmd_t cmd,
  45     nvlist_t *in_params, nvlist_t **out_params)
  46 {
  47         int rc;
  48         size_t buf_sz, attempt = 0;
  49         krrp_ioctl_data_t *ioctl_data = NULL;
  50         nvlist_t *result_nvl = NULL;
  51 
  52         VERIFY(hdl != NULL);
  53 
  54         hdl->libkrrp_last_cmd = cmd;
  55 
  56         /*
  57          * To get result from kernel we pass to it a buffer.
  58          * If kernel has more data than the size of the buffer
  59          * then it returns ENOSPC. In this case IOCTL will be
  60          * called no more than 10 times and on each iteration
  61          * the size of the buffer will be doubled.
  62          */
  63         buf_sz = LIBKRRP_INIT_BUF_SIZE;
  64         for (;;) {
  65                 rc = krrp_ioctl_data_from_nvl(hdl,
  66                     &ioctl_data, in_params, buf_sz);
  67                 if (rc != 0)
  68                         return (-1);
  69 
  70                 rc = krrp_ioctl_call(hdl, cmd, ioctl_data);
  71                 if (rc != 0) {
  72                         const libkrrp_error_t *err = libkrrp_error(hdl);
  73                         if (err->unix_errno == ENOSPC) {
  74                                 if (++attempt > 10)
  75                                         goto fini;
  76 
  77                                 krrp_ioctl_free_data(ioctl_data);
  78                                 ioctl_data = NULL;
  79                                 buf_sz *= 2;
  80                                 continue;
  81                         }
  82 
  83                         goto fini;
  84                 }
  85 
  86                 break;
  87         }
  88 
  89         VERIFY0(rc);
  90 
  91         rc = krrp_ioctl_data_to_nvl(hdl, ioctl_data, &result_nvl);
  92         if (rc != 0)
  93                 goto fini;
  94 
  95         if (ioctl_data->out_flags & KRRP_IOCTL_FLAG_ERROR) {
  96                 VERIFY0(libkrrp_error_from_nvl(result_nvl,
  97                     &hdl->libkrrp_error));
  98                 rc = -1;
  99                 fnvlist_free(result_nvl);
 100         } else if (ioctl_data->out_flags & KRRP_IOCTL_FLAG_RESULT) {
 101                 VERIFY(out_params != NULL && *out_params == NULL);
 102                 *out_params = result_nvl;
 103         }
 104 
 105 fini:
 106         krrp_ioctl_free_data(ioctl_data);
 107         return (rc);
 108 }
 109 
 110 static int
 111 krrp_ioctl_call(libkrrp_handle_t *hdl, krrp_ioctl_cmd_t cmd,
 112     krrp_ioctl_data_t *ioctl_data)
 113 {
 114         int rc;
 115 
 116         rc = ioctl(hdl->libkrrp_fd, cmd, ioctl_data);
 117 
 118         if (rc != 0) {
 119                 switch (errno) {
 120                 case ENOTACTIVE:
 121                         libkrrp_error_set(&hdl->libkrrp_error,
 122                             LIBKRRP_ERRNO_SVCNOTACTIVE, 0, 0);
 123                         break;
 124                 case EALREADY:
 125                         libkrrp_error_set(&hdl->libkrrp_error,
 126                             LIBKRRP_ERRNO_SVCACTIVE, 0, 0);
 127                         break;
 128                 case ENOTSUP:
 129                         libkrrp_error_set(&hdl->libkrrp_error,
 130                             LIBKRRP_ERRNO_NOTSUP, 0, 0);
 131                         break;
 132                 default:
 133                         libkrrp_error_set(&hdl->libkrrp_error,
 134                             LIBKRRP_ERRNO_IOCTLFAIL, errno, 0);
 135                 }
 136                 return (-1);
 137         }
 138 
 139         return (0);
 140 }
 141 
 142 static int
 143 krrp_ioctl_alloc_data(libkrrp_handle_t *hdl, krrp_ioctl_data_t **res_ioctl_data,
 144     size_t buf_size)
 145 {
 146         krrp_ioctl_data_t *ioctl_data;
 147 
 148         VERIFY(buf_size <= UINT32_MAX);
 149 
 150         ioctl_data = umem_alloc(sizeof (krrp_ioctl_data_t) + buf_size,
 151             UMEM_DEFAULT);
 152 
 153         if (ioctl_data == NULL) {
 154                 libkrrp_error_set(&hdl->libkrrp_error, LIBKRRP_ERRNO_NOMEM,
 155                     errno, 0);
 156                 return (-1);
 157         }
 158 
 159         (void) memset(ioctl_data, 0, sizeof (krrp_ioctl_data_t));
 160 
 161         ioctl_data->buf_size = buf_size;
 162 
 163         *res_ioctl_data = ioctl_data;
 164 
 165         return (0);
 166 }
 167 
 168 static void
 169 krrp_ioctl_free_data(krrp_ioctl_data_t *ioctl_data)
 170 {
 171         umem_free(ioctl_data, sizeof (krrp_ioctl_data_t) +
 172             ioctl_data->buf_size);
 173 }
 174 
 175 static int
 176 krrp_ioctl_data_from_nvl(libkrrp_handle_t *hdl,
 177     krrp_ioctl_data_t **res_ioctl_data, nvlist_t *params,
 178     size_t add_size)
 179 {
 180         int rc;
 181         size_t packed_size = 0;
 182         krrp_ioctl_data_t *ioctl_data = NULL;
 183 
 184         if (params != NULL)
 185                 packed_size = fnvlist_size(params);
 186 
 187         rc = krrp_ioctl_alloc_data(hdl, &ioctl_data,
 188             packed_size + add_size);
 189         if (rc != 0)
 190                 return (-1);
 191 
 192         if (params != NULL) {
 193                 char *buf = ioctl_data->buf;
 194                 VERIFY0(nvlist_pack(params, &buf,
 195                     &packed_size, NV_ENCODE_NATIVE, 0));
 196                 ioctl_data->data_size = packed_size;
 197         }
 198 
 199         *res_ioctl_data = ioctl_data;
 200 
 201         return (0);
 202 }
 203 
 204 static int
 205 krrp_ioctl_data_to_nvl(libkrrp_handle_t *hdl, krrp_ioctl_data_t *ioctl_data,
 206     nvlist_t **result_nvl)
 207 {
 208         nvlist_t *nvl = NULL;
 209         int rc;
 210 
 211         if (!(ioctl_data->out_flags & KRRP_IOCTL_FLAG_ERROR) &&
 212             !(ioctl_data->out_flags & KRRP_IOCTL_FLAG_RESULT))
 213                 return (0);
 214 
 215         VERIFY(*result_nvl == NULL && result_nvl != NULL);
 216         VERIFY(ioctl_data->data_size != 0);
 217 
 218         rc = nvlist_unpack(ioctl_data->buf,
 219             ioctl_data->data_size, &nvl, 0);
 220 
 221         if (rc == ENOMEM) {
 222                 libkrrp_error_set(&hdl->libkrrp_error,
 223                     LIBKRRP_ERRNO_NOMEM, rc, 0);
 224         } else if (rc != 0) {
 225                 libkrrp_error_set(&hdl->libkrrp_error,
 226                     LIBKRRP_ERRNO_IOCTLDATAFAIL, rc, 0);
 227                 return (-1);
 228         }
 229 
 230         *result_nvl = nvl;
 231         return (0);
 232 }