static bool persistent_spc2_release_lun(pr_context_t *context, int session_id,
                                        lun_t lun, scsi_command_status_t *scsi_status)
{
    pr_info_t *pr_info = &context->pr_info[lun];

    EXA_ASSERT(session_id < MAX_GLOBAL_SESSION);

    if (!can_use_spc2_reserve(context, lun, session_id))
    {
        exalog_warning("iSCSI PR conflict: session %i cannot release SPC-2 "
                       "reservation on LUN %" PRIlun ", SPC-2 reserve not "
                       "possible on this LUN", session_id, lun);
        return false;
    }

    if (pr_info->spc2_reserve != SPC2_RESERVE_NONE
        && pr_info->spc2_reserve != session_id)
    {
        exalog_warning("iSCSI PR conflict: session %i cannot release SPC-2 "
                       "reservation on LUN %" PRIlun ", LUN already reserved "
                       "by %i", session_id, lun, pr_info->spc2_reserve);
        return false;
    }

    pr_info->spc2_reserve = SPC2_RESERVE_NONE;

    exalog_debug("iSCSI PR: session %i released SPC-2 reservation on LUN %"
                 PRIlun, session_id, lun);
    return true;
}
Exemple #2
0
/**
 * Thread responsible for accepting connections
 *
 * It's a separate thread because we accept do some memory allocation and we
 * must avoid that in recv thread.
 *
 * @param unused  Unused parameter
 */
static void accept_thread(void *unused)
{
    exalog_as(EXAMSG_ISCSI_ID);

    while (algopr_run)
    {
        exa_nodeid_t node_id;
        const char *ip_addr;
        struct sockaddr_in client_address;
        int size = sizeof(client_address);

        int sock =
            os_accept(eth.accept_sock, (struct sockaddr *)&client_address, &size);

        if (sock < 0)
            continue; /* it's a false accept */

        ip_addr = os_inet_ntoa(client_address.sin_addr);

        if (!suspended)
        {
            exalog_warning("Closing incoming connection from %s while not"
                           " suspended.", ip_addr);
            __close_socket(sock);
            continue;
        }

        internal_setsock_opt(sock, SOCK_FLAGS);

        node_id = get_peer_id_from_ip_addr(ip_addr);
        if (!EXA_NODEID_VALID(node_id))
        {
            exalog_warning("Closing incoming connection from unknown node %s.",
                           ip_addr);
            __close_socket(sock);
            continue;
        }

        set_peer_socket(node_id, ip_addr, sock);
    }
}
static bool persistent_reserve_lun(pr_context_t *context,
                                   int session_id, lun_t lun,
                                   pr_key_t reservation_key,
                                   pr_type_t access_type)
{
    pr_info_t *pr_info = &context->pr_info[lun];

    EXA_ASSERT(session_id < MAX_GLOBAL_SESSION);

    /* lun have already have a persistent reserve key and we are not a
     * reservation lun holder */
    if (is_lun_reserved(context, lun)
        && !persistent_is_holder(context, session_id, lun))
    {
        exalog_warning("iSCSI PR conflict: session %i cannot change LUN %"
                       PRIlun " reservation, already reserved by another "
                       "session", session_id, lun);
        return false;
    }

     /* lun have already have a persistent reserve key and we are a
      * reservation lun holder and we change access type */
    if (is_lun_reserved(context, lun)
        && persistent_is_holder(context, session_id, lun)
        && pr_info->reservation_type != access_type)
    {
        exalog_warning("iSCSI PR conflict: session %i cannot change LUN %"
                       PRIlun " reservation, reservation type mismatch ("
                       "received %02x / current %02x)", session_id, lun,
                       access_type, pr_info->reservation_type);
        return false;
    }

    pr_info->reservation_type = access_type;
    pr_info_set_holder(pr_info, session_id);

    exalog_debug("iSCSI PR: session %i got a reservation on LUN %" PRIlun,
                 session_id, lun);

    return true;
}
static int check_registration(const pr_context_t *context, int session_id, lun_t lun,
                              pr_key_t reservation_key, int service_action)
{
    const pr_info_t *pr_info = &context->pr_info[lun];
    bool is_registered = pr_info_is_registered(pr_info, session_id);
    pr_key_t current_key = pr_info_get_registration_key(pr_info, session_id);

    EXA_ASSERT(session_id < MAX_GLOBAL_SESSION);

    /* are we registered with reservation_key ?
     *
     * FIXME: "is_registered" test added for intel iSCSI test
     */
    if (service_action != PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY
        && is_registered && current_key != reservation_key)
    {
        exalog_warning("iSCSI PR conflict: "
                       "session %i registration check on LUN %" PRIlun " failed"
                       ", registration key mismatch"
                       " (received %" PRIu64 " / current %" PRIu64 ")",
                       session_id, lun, reservation_key,
                       current_key);
        return SCSI_STATUS_RESERVATION_CONFLICT;
    }

    if (service_action != PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY
        && service_action != PR_OUT_REGISTER
        && !is_registered)
    {
        exalog_warning("iSCSI PR conflict: "
                       "session %i registration check on LUN %" PRIlun " failed"
                       ", session not registered",
                       session_id, lun);
        return SCSI_STATUS_RESERVATION_CONFLICT;
    }

    return SCSI_STATUS_GOOD;
}
int exa_perf_instance_static_init(void)
{
    const char *perf_config;
    exaperf_err_t err;

    eh = exaperf_alloc();
    if (eh == NULL)
    {
        exalog_error("Failed initializing exaperf");
        return -ENOMEM;
    }

    perf_config = getenv("EXANODES_PERF_CONFIG");
    if (perf_config == NULL)
    {
        exalog_debug("No perf config set");
        return 0;
    }

    /* initialize the component */
    err = exaperf_init(eh, perf_config, exa_perf_instance_print);
    switch (err)
    {
    case EXAPERF_SUCCESS:
        exalog_info("Loaded perf config '%s'", perf_config);
        return 0;

    case EXAPERF_CONF_FILE_OPEN_FAILED:
        exalog_warning("Perf config '%s' not found, ignored", perf_config);
        exaperf_free(eh);
        eh = NULL;
        return 0;

    default:
        /* FIXME Use error string instead of error code */
        exalog_error("Failed loading perf config '%s' (%d)", perf_config, err);
        return -EINVAL;
    }
}
Exemple #6
0
/** \brief Initialization of examsgd daemon.
 *
 * Command line must contain the node name and the interface name to use
 * for control messages.
 *
 * Accepts hidden option -d (debugging mode).
 *
 * \param[in] argc	Argument count.
 * \param[in] argv	Array of argument values.
 *
 * \return exit code.
 */
int daemon_init(int argc, char *argv[])
{
  int s;

  /* getopt variables */
  static struct option long_opts[] =
    {
      { "cluster-id",  required_argument, NULL, 'c' },
      { "help",        no_argument,       NULL, 'h' },
      { "hostname",    required_argument, NULL, 'N' },
      { "incarnation", required_argument, NULL, 'I' },
      { "mcast-addr",  required_argument, NULL, 'm' },
      { "mcast-port",  required_argument, NULL, 'p' },
      { "node-id",     required_argument, NULL, 'i' },
      { "node-name",   required_argument, NULL, 'n' },
      { "stats",       no_argument,       NULL, 's' },
      { NULL,          0,                 NULL, 0   }
    };
  int long_idx, c;
  char *e;
  extern char *optarg;
  extern int optind;

  /* configurable options and default values */
  const char *node_name = NULL;
  const char *hostname = NULL;
  const char *mgroup = EXAMSG_MCASTIP;
  unsigned short mport = EXAMSG_PORT;
  unsigned short inca = 0;
  exa_uuid_t cluster_uuid;
  exa_nodeid_t nodeid;

  bool err = false;

  uuid_zero(&cluster_uuid);
  nodeid = EXA_NODEID_NONE;

  /* options parsing */
  while ((c = os_getopt_long(argc, argv, "c:dhi:I:m:n:N:p:s", long_opts, &long_idx))
	 != -1)
    switch (c)
      {
      case 'c':
	if (uuid_scan(optarg, &cluster_uuid) < 0)
	  {
	    fprintf(stderr, "invalid cluster id: '%s'\n", optarg);
	    return -EINVAL;
	  }
	break;

      case 'i':
	nodeid = (exa_nodeid_t)strtol(optarg, &e, 10);
	if (*e || !EXA_NODEID_VALID(nodeid))
	  {
	    fprintf(stderr, "invalid node id: '%s'\n", optarg);
	    return -EINVAL;
	  }
	break;

      case 'I':
	inca = (unsigned short)strtol(optarg, &e, 10);
	if (*e || inca == 0)
	  {
	    fprintf(stderr, "invalid incarnation: '%s'\n", optarg);
	    return -EINVAL;
	  }
	break;

	/* multicast group */
      case 'm':
	mgroup = optarg;
	break;

      case 'n':
	node_name = optarg;
	break;

	/* hostname */
      case 'N':
	hostname = optarg;
	break;

	/* communication port */
      case 'p':
	mport = strtol(optarg, &e, 0);
	if (*e != '\0')
	  {
	    fprintf(stderr, "invalid port number '%s'\n", optarg);
	    return -EINVAL;
	  }
	break;

      case 's':
	examsg_show_stats();
	return 0;
	break;

	/* usage */
      case 'h':
      case '?':
      default:
	usage(argv[0]);
	return -EINVAL;
      }

  if (uuid_is_zero(&cluster_uuid))
    {
      fprintf(stderr, "missing cluster id\n");
      err = true;
    }

  if (nodeid == EXA_NODEID_NONE)
    {
      fprintf(stderr, "missing node id\n");
      err = true;
    }

  if (node_name == NULL)
    {
      fprintf(stderr, "missing node name\n");
      err = true;
    }

  if (hostname == NULL)
    {
      fprintf(stderr, "missing hostname\n");
      err = true;
    }

  if (inca == 0)
    {
      fprintf(stderr, "missing incarnation\n");
      err = true;
    }

  if (err)
    return -EINVAL;

  /* Get cluster id, number of nodes, node id, node name
     and interface parameters */
  if (argc - optind != 0)
    {
      fprintf(stderr, "stray parameters\n");
      usage(argv[0]);
      return -EINVAL;
    }

  signal(SIGTERM, sig_term);
  signal(SIGINT, sig_term);

  s = examsg_static_init(EXAMSG_STATIC_GET);
  if (s)
  {
      fprintf(stderr, "Can't initialize messaging layer.");
      return s;
  }

  exalog_static_init();

  /* Log as exa_msgd by default */
  exalog_as(EXAMSG_CMSGD_ID);

#ifdef USE_YAOURT
  if (yaourt_init())
    exalog_debug("Yaourt: Examsgd init OK");
  else
    exalog_warning("Yaourt: Examsgd init FAILED (%s)", yaourt_error);
#endif

  /* set up network communication */
  return startup(&cluster_uuid, node_name, hostname, nodeid, mgroup, mport, inca);
}
Exemple #7
0
/*
 * thread responsible for receiving data for a client or a server
 * note when we add client, this client is effectively added in the receive queue
 * only few second later due to the select timeout of 3 seconds
 * and there are the same problem for the deleteion of a client
 */
static void algopr_receive_thread(void *unused)
{
    struct pending_request pending_requests[EXA_MAX_NODES_NUMBER];
    exa_select_handle_t *sh = exa_select_new_handle();
    int i;
    int ret;
    payload_t *payload = NULL;
    struct nbd_root_list root_list_recv;

    /* FIXME: handle the case when we have more than 1024 open file (limit of fd_set) */
    fd_set fds;

    exalog_as(EXAMSG_ISCSI_ID);

    nbd_init_root(EXA_MAX_NODES_NUMBER, sizeof(payload_t), &root_list_recv);

    for (i = 0; i < EXA_MAX_NODES_NUMBER; i++)
        request_reset(&pending_requests[i]);

    while (algopr_run)
    {
	int nfds = 0;
        FD_ZERO(&fds);
        /* if one node is added or deleted, this deletion or addition are
           effective after this */
        os_thread_mutex_lock(&peers_lock);
        for (i = 0; i < EXA_MAX_NODES_NUMBER; i++)
        {
            int fd_act = __get_peer_socket(i);
            if (fd_act < 0)
            {
                payload_t *temp_payload;
                temp_payload = request_reset(&pending_requests[i]);
                if (temp_payload != NULL)
                {
                    if (pending_requests[i].big_buffer)
                        nbd_list_post(&eth.root_list_big_recv.free,
                                      temp_payload->buffer, -1);

                    nbd_list_post(&root_list_recv.free, temp_payload, -1);
                }
                temp_payload = NULL;
                continue;
            }

            FD_SET(fd_act, &fds);
	    nfds = fd_act > nfds ? fd_act : nfds;
        }
        os_thread_mutex_unlock(&peers_lock);

        ret = exa_select_in(sh, nfds + 1, &fds);
        if (ret != 0 && ret != -EFAULT)
            exalog_error("Select upon receive failed: %s (%d)",
                         os_strerror(-ret), ret);

        os_thread_mutex_lock(&peers_lock);
        for (i = 0; i < EXA_MAX_NODES_NUMBER; i++)
        {
            struct pending_request *req;
            int fd_act;

            fd_act = __get_peer_socket(i);
            if (fd_act < 0 || !FD_ISSET(fd_act, &fds))
                continue;

            req = &pending_requests[i];
            /* WARNING payload is kept from an iteration of while loop to
             * another, so the variable MUST be global. */
            /* FIXME Remove the nbdlist which is useless as we already know
             * that we NEED EXA_MAX_NODES_NUMBER payload_t elements to be able
             * to receive simultaneously from EXA_MAX_NODES_NUMBER nodes
             * FIXME the LISTWAIT flag below is WRONG because waiting here
             * would mean deadlock... hopefully there are enough elements, and
             * we never wait.... */
            if (payload == NULL)
            {
                payload = nbd_list_remove(&root_list_recv.free, NULL, LISTWAIT);
                EXA_ASSERT(payload != NULL);
            }
            if (request_init_transfer(payload, req) == 1)
                payload = NULL;

            ret = request_receive(fd_act, req);
            if (ret == DATA_TRANSFER_NEED_BIG_BUFFER)
            {
                req->payload->buffer = nbd_list_remove(&eth.root_list_big_recv.free,
                                                       NULL, LISTWAIT);
                EXA_ASSERT(req->payload->buffer != NULL);

                req->big_buffer = true;

                /* here we just continue because it is forbidden to call
                 * request_receive without passing into select (as sockets are
                 * blocking, we may remain blocked on the recv of nothing) */
                continue;
            }

            if (ret == DATA_TRANSFER_PENDING)
                continue;

            if (ret == DATA_TRANSFER_ERROR)
            {
                payload_t *temp_payload = request_reset(req);

                if (req->big_buffer)
                    nbd_list_post(&eth.root_list_big_recv.free,
                                  temp_payload->buffer, -1);

                nbd_list_post(&root_list_recv.free, temp_payload, -1);

                __disconnect_from_peer(i);

                if (!suspended)
                    exalog_warning("Failed receiving from peer %" PRInodeid
                                  " (socket %d): transfer error.", i, fd_act);
                continue;
            }

            if (ret == DATA_TRANSFER_COMPLETE)
            {
                payload_t *_payload = request_reset(req);

                /* update data network checking data */
                algopr_new_msg(_payload->payload, _payload->size1,
                               _payload->buffer,  _payload->size2);
                nbd_list_post(&root_list_recv.free, _payload, -1);
            }
        }
        os_thread_mutex_unlock(&peers_lock);
    }

    nbd_close_root(&root_list_recv);
    exa_select_delete_handle(sh);
}
Exemple #8
0
/**
 * Process a connection that has incoming data.
 *
 * \param[in] conn_id  Connection id
 * \param[in] sock_fd  Connection socket
 */
static void
handle_inputdata(int conn_id, int sock_fd)
{
  char *buffer = NULL;
  void *data = NULL;
  size_t data_size;
  adm_command_code_t cmd_code;
  const struct AdmCommand *command;
  exa_uuid_t cluster_uuid;
  cl_error_desc_t err_desc;
  int retval;

  /* Receive the xml tree parsed in message */
  retval = receive(sock_fd, &buffer);
  if (retval < 0)
    {
      if (retval == -ECONNRESET || retval == -ECONNABORTED)
	exalog_debug("CONNECTION %d: An error occurred : %d [%s]",
		     conn_id, retval, exa_error_msg(retval));
      else
	exalog_error("Socket %d peer '%s': An error occurred : %s (%d)",
		     sock_fd, cli_peer_from_fd(sock_fd), exa_error_msg(retval),
		     retval);

      close_connection(conn_id);
      return;
    }

  /* Parse the tree we just received and get a newly allocated payload data */
  xml_command_parse(buffer, &cmd_code, &cluster_uuid,
                    &data, &data_size, &err_desc);

  /* buffer is now parsed, let's free it */
  os_free(buffer);

  if (err_desc.code != EXA_SUCCESS)
    {
      /* No need to free data buffer if parsing returned an error */
      exalog_error("Failed to parse command on socket %d (from peer '%s'): %s (%d)",
	           sock_fd, cli_peer_from_fd(sock_fd), err_desc.msg, err_desc.code);

      cli_command_end_complete(sock_fd, &err_desc);
      return;
    }

  command = adm_command_find(cmd_code);
  EXA_ASSERT_VERBOSE(command, "Missing implementation of command #%d", cmd_code);

  connectlist[conn_id].uid = get_new_cmd_uid();
  retval = send_command_to_evmgr(connectlist[conn_id].uid,
                                 command,
				 &cluster_uuid, data, data_size);
  if (retval < 0)
    {
      if (retval == -EXA_ERR_ADM_BUSY)
        exalog_warning("Running command %s (request from %s) failed: %s",
                       adm_command_find(cmd_code)->msg,
                       cli_get_peername(connectlist[conn_id].uid),
                       exa_error_msg(retval));
      else
        exalog_error("Running command %s (request from %s) failed: %s (%d)",
                     adm_command_find(cmd_code)->msg,
		     cli_get_peername(connectlist[conn_id].uid),
                     exa_error_msg(retval), retval);

      set_error(&err_desc, retval, NULL);
      cli_command_end_complete(sock_fd, &err_desc);

      /* the command was not scheduled, reset the uid */
      connectlist[conn_id].uid = CMD_UID_INVALID;
    }

  os_free(data);
}
static void persistent_preempt_lun(pr_context_t *context,
                                   int session_id, lun_t lun,
                                   pr_key_t reservation_key,
                                   pr_key_t service_action_key,
                                   pr_type_t access_type,
                                   scsi_command_status_t *scsi_status)
{
    pr_info_t *pr_info = &context->pr_info[lun];
    bool all_registrants = false;
    unsigned int i;
    int id;

    EXA_ASSERT(session_id < MAX_GLOBAL_SESSION);

    /* spc 3 figure 3 pg 80 5.6.10.4.1 */
    if (pr_info->reservation_type == PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS
        || pr_info->reservation_type == PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS)
    {
        all_registrants = true;
    }

    if (!all_registrants && service_action_key == 0)
    {
        exalog_warning("iSCSI PR failed: "
                       "session %i cannot preempt reservation on LUN %" PRIlun
                       ", operation not allowed",
                       session_id, lun);
        SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_CHECK_CONDITION,
                          SCSI_SENSE_ILLEGAL_REQUEST,
                          SCSI_SENSE_ASC_INVALID_FIELD_IN_PARAMETER_LIST);
        return;
    }

    for (i = 0; i < MAX_REGISTRATIONS; i++)
    {
        pr_key_t key = pr_info->registrations[i].key;
        id = pr_info->registrations[i].session_id;

        /* spc3r20 5.6.10.4.3  paragraphe 2 b) expept I_T nexus used for this command
         *
         * FIXME: I think that this code unregister all the sessions that are
         * not the preempting one and that don't have the right registration key.
         */
        if ((service_action_key == key || service_action_key == 0)
            && id != session_id && id != SESSION_ID_NONE)
        {
            pr_info->registrations[i].key = 0;
            pr_info->registrations[i].session_id = SESSION_ID_NONE;
            callback_send_sense_data(context, id, lun, SCSI_STATUS_CHECK_CONDITION,
                                     SCSI_SENSE_UNIT_ATTENTION, 0);
        }
    }

    exalog_debug("iSCSI PR: session %i preempt on LUN %" PRIlun,
                 session_id, lun);
    SCSI_STATUS_OK(scsi_status, 0);

    /* if there are no reservation on lun, preempt don't put another
     * reservation
     */
    if (!is_lun_reserved(context, lun))
        return;

    if ((all_registrants && service_action_key == 0)
        || (!all_registrants && service_action_key == get_holder_key(context, lun)))
    {
        pr_info_set_holder(pr_info, session_id);
        pr_info->reservation_type = access_type;
    }
}
static void persistent_release_lun(pr_context_t *context,
                                   int session_id, lun_t lun,
                                   pr_key_t reservation_key,
                                   int reservation_type,
                                   scsi_command_status_t *scsi_status)
{
    int id;
    pr_info_t *pr_info = &context->pr_info[lun];

    EXA_ASSERT(session_id < MAX_GLOBAL_SESSION);

    /* lun already have a persistent reserve key
     * and we are not a reservation lun holder
     */
    if (is_lun_reserved(context, lun)
        && !persistent_is_holder(context, session_id, lun))
    {
        exalog_warning("iSCSI PR conflict: "
                       "session %i cannot release LUN %" PRIlun " reservation"
                       ", it does not hold the reservation",
                       session_id, lun);
        SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_RESERVATION_CONFLICT, 0, 0);
        return;
    }

    /* lun already have a persistent reserve key
     * and we are a reservation lun holder
     */
    if (is_lun_reserved(context, lun)
        && persistent_is_holder(context, session_id, lun)
        && reservation_type != pr_info->reservation_type)
    {
        exalog_warning("iSCSI PR failed: "
                       "session %i cannot release LUN %" PRIlun " reservation"
                       ", reservation type mismatch (received %02x / current %02x)",
                        session_id, lun, reservation_type,
                       pr_info->reservation_type);
        SCSI_STATUS_ERROR(
            scsi_status,
            SCSI_STATUS_CHECK_CONDITION,
            SCSI_SENSE_ILLEGAL_REQUEST,
            SCSI_SENSE_ASC_INVALID_RELEASE_OF_PERSISTENT_RESERVATION);
        return;
    }

    /* now all registrant reservation holder will be unregistered */
    if (pr_info->reservation_type != PR_TYPE_WRITE_EXCLUSIVE
        && pr_info->reservation_type != PR_TYPE_EXCLUSIVE_ACCESS)
    {
        unsigned int i;

        for (i = 0; i < MAX_REGISTRATIONS; i++)
        {
            id = pr_info->registrations[i].session_id;

            /* FIXME: the session that triggers the release dont't get the
             * unregistration callback. I understand that as the session does
             * not need to to be signaled with an events it has triggered.
             */
            if (id == SESSION_ID_NONE || id == session_id)
                continue;

            callback_send_sense_data(context, id, lun, SCSI_STATUS_CHECK_CONDITION,
                                     SCSI_SENSE_UNIT_ATTENTION,
                                     SCSI_SENSE_ASC_RESERVATIONS_RELEASED);
        }
    }

    /* no more reservation */
    pr_info->reservation_type = PR_TYPE_NONE;
    pr_info->holder_index = MAX_REGISTRATIONS;

    exalog_debug("iSCSI PR: session %i released the reservation on LUN %" PRIlun,
                 session_id, lun);
    SCSI_STATUS_OK(scsi_status, 0);
}
void pr_reserve_out(pr_context_t *context, lun_t lun,
                    const unsigned char *cdb, int session_id,
                    scsi_command_status_t *scsi_status)
{
    pr_info_t *pr_info = &context->pr_info[lun];

    EXA_ASSERT(session_id < MAX_GLOBAL_SESSION);

    if (cdb[0] == RESERVE_6)
    {
       if (persistent_spc2_reserve_lun(context, session_id, lun, scsi_status))
           SCSI_STATUS_OK(scsi_status, 0);
       else
           SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_RESERVATION_CONFLICT, 0, 0);
       return;
    }

    if (cdb[0] == RELEASE_6)
    {
        if (persistent_spc2_release_lun(context, session_id, lun, scsi_status))
            SCSI_STATUS_OK(scsi_status, 0);
        else
            SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_RESERVATION_CONFLICT, 0, 0);
        return;
    }

    if (cdb[0] == PERSISTENT_RESERVE_OUT)
    {
        /* int len = get_bigendian32(cdb + 5); // FIXME: TODO: must be checked for all service action */
        int pr_scope = cdb[2] >> 4;
        int pr_type = cdb[2] & 0xf;
        const unsigned char *param = cdb + SCSI_CDB_MAX_FIXED_LENGTH;

        pr_key_t reservation_key = get_bigendian64(param);
        pr_key_t service_action_key = get_bigendian64(param + 8);
        unsigned char flags = param[20];
        unsigned char spec_i_pt = (flags >> 3) & 1;
        /*    unsigned char all_tg_pt = (flags >>2) & 1 ; */
        int service_action = cdb[1] & 0x1f;

        /* spc3r23 table 116 "allowed scope" */
        if (pr_scope != PR_LU_SCOPE
            && service_action != PR_OUT_REGISTER
            && service_action != PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY
            && service_action != PR_OUT_CLEAR)
        {
            exalog_error("iSCSI PR failed: "
                         "reserve operation (%i) from session %i on LUN %" PRIlun
                         " is not allowed in this scope",
                         service_action, session_id, lun);
            SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_CHECK_CONDITION,
                              SCSI_SENSE_ILLEGAL_REQUEST,
                              SCSI_SENSE_ASC_INVALID_FIELD_IN_CDB);
            return;
        }

        if (check_registration(context, session_id, lun, reservation_key,
                               service_action) != SCSI_STATUS_GOOD)
        {
            SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_RESERVATION_CONFLICT, 0, 0);
            return;
        }

        switch (service_action)
        {
        case PR_OUT_REGISTER:
            if (spec_i_pt == 0)
            {
                persistent_register_lun(context, session_id,
                                        lun, service_action_key, scsi_status);
                break;
            }
            /* spec_i_pt == 1 ! */
            if (reservation_key != 0)   /* spec_i_pt == 1 and we are on a registered nexus */
            {
                exalog_warning("iSCSI PR conflict: "
                               "reserve operation (%i) from session %i on LUN %" PRIlun
                               " is not allowed, already registered nexus %" PRIu64,
                               service_action, session_id, lun, reservation_key);
                SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_CHECK_CONDITION,
                                  SCSI_SENSE_ILLEGAL_REQUEST,
                                  SCSI_SENSE_ASC_INVALID_FIELD_IN_CDB);
                break;
                ;
            }

            persistent_register_lun(context, session_id, lun,
                                    service_action_key, scsi_status);
            break;

        case PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY:
            if (service_action_key == 0
                && !pr_info_is_registered(pr_info, session_id))
            {
                /* FIXME: is this a "nothing to do" case ? */
                exalog_debug("iSCSI PR: register and ignore existing keys"
                             " from session %i on LUN %" PRIlun " (ntd)",
                             session_id, lun);

                SCSI_STATUS_OK(scsi_status, 0);
                break;
            }

            if (spec_i_pt == 1)
            {
                exalog_warning("iSCSI PR failed: "
                               "reserve operation (%i) from session %i on LUN %" PRIlun
                               " is not allowed",
                               service_action, session_id, lun);
                SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_CHECK_CONDITION,
                                  SCSI_SENSE_ILLEGAL_REQUEST,
                                  SCSI_SENSE_ASC_INVALID_FIELD_IN_CDB);
                break;
            }
            persistent_register_lun(context, session_id, lun,
                                    service_action_key, scsi_status);
            break;

        case PR_OUT_RESERVE:
            if (persistent_reserve_lun(context, session_id, lun,
                                       reservation_key, pr_type))
                SCSI_STATUS_OK(scsi_status, 0);
            else
                SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_RESERVATION_CONFLICT,
                                  0, 0);
            break;

        case PR_OUT_RELEASE:
            persistent_release_lun(context, session_id, lun,
                                   reservation_key, pr_type, scsi_status);
            break;

        case PR_OUT_CLEAR:
            persistent_clear_lun(context, session_id, lun, scsi_status);
            break;

        case PR_OUT_PREEMPT:
            persistent_preempt_lun(context, session_id, lun,
                                   reservation_key, service_action_key,
                                   pr_type, scsi_status);
            break;

        case PR_OUT_PREEMPT_AND_ABORT:
            persistent_preempt_lun(context, session_id, lun,
                                   reservation_key, service_action_key,
                                   pr_type, scsi_status);
            break;
            /* FIXME : we must add abort task set and it's not done spc3 - 5.6.10.5 */

        case PR_OUT_REGISTER_AND_MOVE:
            /* FIXME : not implemented ,not needed for windows */
            exalog_error("iSCSI PR failed: "
                         "reserve operation 'register_and_move' not supported"
                         " (from session %i on LUN %" PRIlun ")",
                         session_id, lun);
            SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_CHECK_CONDITION,
                              SCSI_SENSE_ILLEGAL_REQUEST, 0);
            break;

        default:
            exalog_error("iSCSI PR failed: "
                         "reserve operation (%i) not expected"
                         " (from session %i on LUN %" PRIlun ")",
                         service_action, session_id, lun);
            SCSI_STATUS_ERROR(scsi_status, SCSI_STATUS_CHECK_CONDITION,
                              SCSI_SENSE_ILLEGAL_REQUEST, 0);
            break;
        }

        /* PR generation sp3r23 6.11.2 */
        if (scsi_status->status == SCSI_STATUS_GOOD
            && service_action != PR_OUT_RESERVE
            && service_action != PR_OUT_RELEASE)
        {
            pr_info->pr_generation++;
        }
    }