/** * \param tds the famous socket * \param buffer data to send * \param buflen bytes in buffer * \param last 1 if this is the last packet, else 0 * \return length written (>0), <0 on failure */ int tds_goodwrite(TDSSOCKET * tds, const unsigned char *buffer, size_t buflen) { int len; size_t sent = 0; assert(tds && buffer); while (sent < buflen) { /* TODO if send buffer is full we block receive !!! */ len = tds_select(tds, TDSSELWRITE, tds->query_timeout); if (len > 0) { len = tds_socket_write(tds->conn, tds, buffer + sent, buflen - sent); if (len == 0) continue; if (len < 0) return len; sent += len; continue; } /* error */ if (len < 0) { int err = sock_errno; char *errstr; if (TDSSOCK_WOULDBLOCK(err)) /* shouldn't happen, but OK, retry */ continue; errstr = sock_strerror(err); tdsdump_log(TDS_DBG_NETWORK, "select(2) failed: %d (%s)\n", err, errstr); sock_strerror_free(errstr); tds_connection_close(tds->conn); tdserror(tds_get_ctx(tds), tds, TDSEWRIT, err); return -1; } /* timeout */ tdsdump_log(TDS_DBG_NETWORK, "tds_goodwrite(): timed out, asking client\n"); switch (tdserror(tds_get_ctx(tds), tds, TDSETIME, sock_errno)) { case TDS_INT_CONTINUE: break; default: case TDS_INT_CANCEL: tds_close_socket(tds); return -1; } } return (int) sent; }
/** * Loops until we have received some characters * return -1 on failure */ int tds_goodread(TDSSOCKET * tds, unsigned char *buf, int buflen) { if (tds == NULL || buf == NULL || buflen < 1) return -1; for (;;) { int len, err; /* FIXME this block writing from other sessions */ len = tds_select(tds, TDSSELREAD, tds->query_timeout); #if !ENABLE_ODBC_MARS if (len > 0 && (len & TDSPOLLURG)) { char buf[32]; READSOCKET(tds->conn->s_signaled, buf, sizeof(buf)); /* send cancel */ if (!tds->in_cancel) tds_put_cancel(tds); continue; } #endif if (len > 0) { len = tds_socket_read(tds->conn, tds, buf, buflen); if (len == 0) continue; return len; } /* error */ if (len < 0) { if (TDSSOCK_WOULDBLOCK(sock_errno)) /* shouldn't happen, but OK */ continue; err = sock_errno; tds_connection_close(tds->conn); tdserror(tds_get_ctx(tds), tds, TDSEREAD, err); return -1; } /* timeout */ switch (tdserror(tds_get_ctx(tds), tds, TDSETIME, sock_errno)) { case TDS_INT_CONTINUE: break; default: case TDS_INT_CANCEL: tds_close_socket(tds); return -1; } } }
/** * Read from an OS socket * @TODO remove tds, save error somewhere, report error in another way * @returns 0 if blocking, <0 error >0 bytes read */ static int tds_socket_read(TDSCONNECTION * conn, TDSSOCKET *tds, unsigned char *buf, int buflen) { int len, err; #if ENABLE_EXTRA_CHECKS /* this simulate the fact that recv can return less bytes */ if (buflen >= 5) { static int cnt = 0; if (++cnt == 5) { cnt = 0; buflen -= 3; } } #endif /* read directly from socket*/ len = READSOCKET(conn->s, buf, buflen); if (len > 0) return len; err = sock_errno; if (len < 0 && TDSSOCK_WOULDBLOCK(err)) return 0; /* detect connection close */ tds_connection_close(conn); tdserror(conn->tds_ctx, tds, len == 0 ? TDSESEOF : TDSEREAD, len == 0 ? 0 : err); return -1; }
/** * Close current socket * for last socket close entire connection * for MARS send FIN request */ void tds_close_socket(TDSSOCKET * tds) { if (!IS_TDSDEAD(tds)) { #if ENABLE_ODBC_MARS TDSCONNECTION *conn = tds->conn; unsigned n = 0, count = 0; tds_mutex_lock(&conn->list_mtx); for (; n < conn->num_sessions; ++n) if (TDSSOCKET_VALID(conn->sessions[n])) ++count; if (count > 1) tds_append_fin(tds); tds_mutex_unlock(&conn->list_mtx); if (count <= 1) { tds_disconnect(tds); tds_connection_close(conn); } else { tds_set_state(tds, TDS_DEAD); } #else tds_disconnect(tds); if (CLOSESOCKET(tds_get_s(tds)) == -1) tdserror(tds_get_ctx(tds), tds, TDSECLOS, sock_errno); tds_set_s(tds, INVALID_SOCKET); tds_set_state(tds, TDS_DEAD); #endif } }
static int tds_connection_put_packet(TDSSOCKET *tds, TDSPACKET *packet) { TDSCONNECTION *conn = tds->conn; if (TDS_UNLIKELY(!packet)) { tds_close_socket(tds); return TDS_FAIL; } tds->out_pos = 0; tds_mutex_lock(&conn->list_mtx); for (;;) { int wait_res; if (IS_TDSDEAD(tds)) { tdsdump_log(TDS_DBG_NETWORK, "Write attempt when state is TDS_DEAD"); break; } /* limit packet sending looking at sequence/window */ if (tds->send_seq <= tds->send_wnd) { /* append packet */ tds_append_packet(&conn->send_packets, packet); packet = NULL; } /* network ok ? process network */ if (!conn->in_net_tds) { tds_connection_network(conn, tds, packet ? 0 : 1); if (packet) continue; /* FIXME we are not sure we sent the packet !!! */ break; } /* signal thread processing network to handle our packet */ /* TODO check result */ tds_wakeup_send(&conn->wakeup, 0); /* wait local condition */ wait_res = tds_cond_timedwait(&tds->packet_cond, &conn->list_mtx, tds->query_timeout); if (wait_res == ETIMEDOUT && tdserror(tds_get_ctx(tds), tds, TDSETIME, ETIMEDOUT) != TDS_INT_CONTINUE) { tds_mutex_unlock(&conn->list_mtx); tds_close_socket(tds); tds_free_packets(packet); return TDS_FAIL; } } tds_mutex_unlock(&conn->list_mtx); if (TDS_UNLIKELY(packet)) { tds_free_packets(packet); return TDS_FAIL; } if (IS_TDSDEAD(tds)) return TDS_FAIL; return TDS_SUCCESS; }
/** * Help to build query to be sent to server. * Append column declaration to the query. * Only for TDS 7.0+. * \tds * \param[out] clause output string * \param bcpcol column to append * \param first true if column is the first * \return TDS_SUCCESS or TDS_FAIL. */ static TDSRET tds7_build_bulk_insert_stmt(TDSSOCKET * tds, TDSPBCB * clause, TDSCOLUMN * bcpcol, int first) { char column_type[40]; tdsdump_log(TDS_DBG_FUNC, "tds7_build_bulk_insert_stmt(%p, %p, %p, %d)\n", tds, clause, bcpcol, first); if (TDS_FAILED(tds_get_column_declaration(tds, bcpcol, column_type))) { tdserror(tds_get_ctx(tds), tds, TDSEBPROBADTYP, errno); tdsdump_log(TDS_DBG_FUNC, "error: cannot build bulk insert statement. unrecognized server datatype %d\n", bcpcol->on_server.column_type); return TDS_FAIL; } if (clause->cb < strlen(clause->pb) + tds_quote_id(tds, NULL, tds_dstr_cstr(&bcpcol->column_name), tds_dstr_len(&bcpcol->column_name)) + strlen(column_type) + ((first) ? 2u : 4u)) { char *temp = (char*) malloc(2 * clause->cb); if (!temp) { tdserror(tds_get_ctx(tds), tds, TDSEMEM, errno); return TDS_FAIL; } strcpy(temp, clause->pb); if (clause->from_malloc) free(clause->pb); clause->from_malloc = 1; clause->pb = temp; clause->cb *= 2; } if (!first) strcat(clause->pb, ", "); tds_quote_id(tds, strchr(clause->pb, 0), tds_dstr_cstr(&bcpcol->column_name), tds_dstr_len(&bcpcol->column_name)); strcat(clause->pb, " "); strcat(clause->pb, column_type); return TDS_SUCCESS; }
/** * Write to an OS socket * @returns 0 if blocking, <0 error >0 bytes readed */ static int tds_socket_write(TDSCONNECTION *conn, TDSSOCKET *tds, const unsigned char *buf, int buflen) { int err, len; char *errstr; #if ENABLE_EXTRA_CHECKS /* this simulate the fact that send can return less bytes */ if (buflen >= 5) { static int cnt = 0; if (++cnt == 5) { cnt = 0; buflen -= 3; } } #endif #if defined(__APPLE__) && defined(SO_NOSIGPIPE) len = send(conn->s, buf, buflen, 0); #else len = WRITESOCKET(conn->s, buf, buflen); #endif if (len > 0) return len; err = sock_errno; if (0 == len || TDSSOCK_WOULDBLOCK(err)) return 0; assert(len < 0); /* detect connection close */ errstr = sock_strerror(err); tdsdump_log(TDS_DBG_NETWORK, "send(2) failed: %d (%s)\n", err, errstr); sock_strerror_free(errstr); tds_connection_close(conn); tdserror(conn->tds_ctx, tds, TDSEWRIT, err); return -1; }
/** * tds_read_config_info() will fill the tds connection structure based on configuration * information gathered in the following order: * 1) Program specified in TDSLOGIN structure * 2) The environment variables TDSVER, TDSDUMP, TDSPORT, TDSQUERY, TDSHOST * 3) A config file with the following search order: * a) a readable file specified by environment variable FREETDSCONF * b) a readable file in ~/.freetds.conf * c) a readable file in $prefix/etc/freetds.conf * 3) ~/.interfaces if exists * 4) $SYBASE/interfaces if exists * 5) TDS_DEF_* default values * * .tdsrc and freetds.conf have been added to make the package easier to * integration with various Linux and *BSD distributions. */ TDSLOGIN * tds_read_config_info(TDSSOCKET * tds, TDSLOGIN * login, TDSLOCALE * locale) { TDSLOGIN *connection; char *s; char *path; pid_t pid; int opened = 0, found; struct addrinfo *addrs; /* allocate a new structure with hard coded and build-time defaults */ connection = tds_alloc_login(0); if (!connection || !tds_init_login(connection, locale)) { tds_free_login(connection); return NULL; } s = getenv("TDSDUMPCONFIG"); if (s) { if (*s) { opened = tdsdump_open(s); } else { pid = getpid(); if (asprintf(&path, pid_config_logpath, pid) >= 0) { if (*path) { opened = tdsdump_open(path); } free(path); } } } tdsdump_log(TDS_DBG_INFO1, "Getting connection information for [%s].\n", tds_dstr_cstr(&login->server_name)); /* (The server name is set in login.c.) */ /* Read the config files. */ tdsdump_log(TDS_DBG_INFO1, "Attempting to read conf files.\n"); found = tds_read_conf_file(connection, tds_dstr_cstr(&login->server_name)); if (!found) { if (parse_server_name_for_port(connection, login)) { found = tds_read_conf_file(connection, tds_dstr_cstr(&connection->server_name)); /* do it again to really override what found in freetds.conf */ if (found) { parse_server_name_for_port(connection, login); } else if (TDS_SUCCEED(tds_lookup_host_set(tds_dstr_cstr(&connection->server_name), &connection->ip_addrs))) { if (!tds_dstr_dup(&connection->server_host_name, &connection->server_name)) { tds_free_login(connection); return NULL; } found = 1; } } } if (!found) { /* fallback to interfaces file */ tdsdump_log(TDS_DBG_INFO1, "Failed in reading conf file. Trying interface files.\n"); if (!tds_read_interfaces(tds_dstr_cstr(&login->server_name), connection)) { tdsdump_log(TDS_DBG_INFO1, "Failed to find [%s] in configuration files; trying '%s' instead.\n", tds_dstr_cstr(&login->server_name), tds_dstr_cstr(&connection->server_name)); if (connection->ip_addrs == NULL) tdserror(tds_get_ctx(tds), tds, TDSEINTF, 0); } } /* Override config file settings with environment variables. */ tds_fix_login(connection); /* And finally apply anything from the login structure */ if (!tds_config_login(connection, login)) { tds_free_login(connection); return NULL; } if (opened) { char tmp[128]; tdsdump_log(TDS_DBG_INFO1, "Final connection parameters:\n"); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "server_name", tds_dstr_cstr(&connection->server_name)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "server_host_name", tds_dstr_cstr(&connection->server_host_name)); for (addrs = connection->ip_addrs; addrs != NULL; addrs = addrs->ai_next) tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "ip_addr", tds_addrinfo2str(addrs, tmp, sizeof(tmp))); if (connection->ip_addrs == NULL) tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "ip_addr", ""); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "instance_name", tds_dstr_cstr(&connection->instance_name)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "port", connection->port); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "major_version", TDS_MAJOR(connection)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "minor_version", TDS_MINOR(connection)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "block_size", connection->block_size); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "language", tds_dstr_cstr(&connection->language)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "server_charset", tds_dstr_cstr(&connection->server_charset)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "connect_timeout", connection->connect_timeout); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "client_host_name", tds_dstr_cstr(&connection->client_host_name)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "client_charset", tds_dstr_cstr(&connection->client_charset)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "use_utf16", connection->use_utf16); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "app_name", tds_dstr_cstr(&connection->app_name)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "user_name", tds_dstr_cstr(&connection->user_name)); /* tdsdump_log(TDS_DBG_PASSWD, "\t%20s = %s\n", "password", tds_dstr_cstr(&connection->password)); (no such flag yet) */ tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "library", tds_dstr_cstr(&connection->library)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "bulk_copy", (int)connection->bulk_copy); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "suppress_language", (int)connection->suppress_language); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "encrypt level", (int)connection->encryption_level); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "query_timeout", connection->query_timeout); /* tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "capabilities", tds_dstr_cstr(&connection->capabilities)); (not null terminated) */ tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "database", tds_dstr_cstr(&connection->database)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "dump_file", tds_dstr_cstr(&connection->dump_file)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %x\n", "debug_flags", connection->debug_flags); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "text_size", connection->text_size); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "emul_little_endian", connection->emul_little_endian); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "server_realm_name", tds_dstr_cstr(&connection->server_realm_name)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "server_spn", tds_dstr_cstr(&connection->server_spn)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "cafile", tds_dstr_cstr(&connection->cafile)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "crlfile", tds_dstr_cstr(&connection->crlfile)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "check_ssl_hostname", connection->check_ssl_hostname); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %s\n", "db_filename", tds_dstr_cstr(&connection->db_filename)); tdsdump_log(TDS_DBG_INFO1, "\t%20s = %d\n", "readonly_intent", connection->readonly_intent); tdsdump_close(); } /* * If a dump file has been specified, start logging */ if (!tds_dstr_isempty(&connection->dump_file) && !tdsdump_isopen()) { if (connection->debug_flags) tds_debug_flags = connection->debug_flags; tdsdump_open(tds_dstr_cstr(&connection->dump_file)); } return connection; }
/** * Set state of TDS connection, with logging and checking. * \param tds state information for the socket and the TDS protocol * \param state the new state of the connection, cf. TDS_STATE. * \return the new state, which might not be \a state. */ TDS_STATE tds_set_state(TDSSOCKET * tds, TDS_STATE state) { const TDS_STATE prior_state = tds->state; static const char state_names[][10] = { "IDLE", "QUERYING", "PENDING", "READING", "DEAD" }; assert(state < TDS_VECTOR_SIZE(state_names)); assert(tds->state < TDS_VECTOR_SIZE(state_names)); if (state == tds->state) return state; switch(state) { /* transition to READING are valid only from PENDING */ case TDS_PENDING: if (tds->state != TDS_READING && tds->state != TDS_QUERYING) { tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); return tds->state; } tds->state = state; break; case TDS_READING: if (tds->state != TDS_PENDING) { tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); return tds->state; } tds->state = state; break; case TDS_IDLE: if (tds->state == TDS_DEAD && TDS_IS_SOCKET_INVALID(tds_get_s(tds))) { tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); return tds->state; } case TDS_DEAD: tds->state = state; break; case TDS_QUERYING: CHECK_TDS_EXTRA(tds); if (tds->state == TDS_DEAD) { tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); tdserror(tds_get_ctx(tds), tds, TDSEWRIT, 0); break; } else if (tds->state != TDS_IDLE) { tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); tdserror(tds_get_ctx(tds), tds, TDSERPND, 0); break; } /* TODO check this code, copied from tds_submit_prepare */ tds_free_all_results(tds); tds->rows_affected = TDS_NO_COUNT; tds_release_cursor(tds, tds->cur_cursor); tds->cur_cursor = NULL; tds->internal_sp_called = 0; tds->state = state; break; default: assert(0); break; } tdsdump_log(TDS_DBG_ERROR, "Changed query state from %s to %s\n", state_names[prior_state], state_names[state]); CHECK_TDS_EXTRA(tds); return tds->state; }
/** * Read in one 'packet' from the server. This is a wrapped outer packet of * the protocol (they bundle result packets into chunks and wrap them at * what appears to be 512 bytes regardless of how that breaks internal packet * up. (tetherow\@nol.org) * @return bytes read or -1 on failure */ int tds_read_packet(TDSSOCKET * tds) { #if ENABLE_ODBC_MARS TDSCONNECTION *conn = tds->conn; tds_mutex_lock(&conn->list_mtx); for (;;) { int wait_res; TDSPACKET **p_packet; if (IS_TDSDEAD(tds)) { tdsdump_log(TDS_DBG_NETWORK, "Read attempt when state is TDS_DEAD\n"); break; } /* if there is a packet for me return it */ for (p_packet = &conn->packets; *p_packet; p_packet = &(*p_packet)->next) if ((*p_packet)->sid == tds->sid) break; if (*p_packet) { size_t hdr_size; /* remove our packet from list */ TDSPACKET *packet = *p_packet; *p_packet = packet->next; tds_packet_cache_add(conn, tds->recv_packet); tds_mutex_unlock(&conn->list_mtx); packet->next = NULL; tds->recv_packet = packet; hdr_size = packet->buf[0] == TDS72_SMP ? sizeof(TDS72_SMP_HEADER) : 0; tds->in_buf = packet->buf + hdr_size; tds->in_len = packet->len - hdr_size; tds->in_pos = 8; tds->in_flag = tds->in_buf[0]; /* send acknowledge if needed */ if (tds->recv_seq + 2 >= tds->recv_wnd) tds_update_recv_wnd(tds, tds->recv_seq + 4); return tds->in_len; } /* network ok ? process network */ if (!conn->in_net_tds) { tds_connection_network(conn, tds, 0); continue; } /* wait local condition */ wait_res = tds_cond_timedwait(&tds->packet_cond, &conn->list_mtx, tds->query_timeout); if (wait_res == ETIMEDOUT && tdserror(tds_get_ctx(tds), tds, TDSETIME, ETIMEDOUT) != TDS_INT_CONTINUE) { tds_mutex_unlock(&conn->list_mtx); tds_close_socket(tds); return -1; } } tds_mutex_unlock(&conn->list_mtx); return -1; #else /* !ENABLE_ODBC_MARS */ unsigned char *pkt = tds->in_buf, *p, *end; if (IS_TDSDEAD(tds)) { tdsdump_log(TDS_DBG_NETWORK, "Read attempt when state is TDS_DEAD"); return -1; } tds->in_len = 0; tds->in_pos = 0; for (p = pkt, end = p+8; p < end;) { int len = tds_connection_read(tds, p, end - p); if (len <= 0) { tds_close_socket(tds); return -1; } p += len; if (p - pkt >= 4) { unsigned pktlen = TDS_GET_A2BE(pkt+2); /* packet must at least contains header */ if (TDS_UNLIKELY(pktlen < 8)) { tds_close_socket(tds); return -1; } if (TDS_UNLIKELY(pktlen > tds->recv_packet->capacity)) { TDSPACKET *packet = tds_realloc_packet(tds->recv_packet, pktlen); if (TDS_UNLIKELY(!packet)) { tds_close_socket(tds); return -1; } tds->recv_packet = packet; pkt = packet->buf; p = pkt + (p-tds->in_buf); tds->in_buf = pkt; } end = pkt + pktlen; } } /* set the received packet type flag */ tds->in_flag = pkt[0]; /* Set the length and pos (not sure what pos is used for now */ tds->in_len = p - pkt; tds->in_pos = 8; tdsdump_dump_buf(TDS_DBG_NETWORK, "Received packet", tds->in_buf, tds->in_len); return tds->in_len; #endif /* !ENABLE_ODBC_MARS */ }
static void tds_connection_network(TDSCONNECTION *conn, TDSSOCKET *tds, int send) { assert(!conn->in_net_tds); conn->in_net_tds = tds; tds_mutex_unlock(&conn->list_mtx); for (;;) { /* wait packets or update */ int rc = tds_select(tds, conn->send_packets ? TDSSELREAD|TDSSELWRITE : TDSSELREAD, tds->query_timeout); if (rc < 0) { /* FIXME better error report */ tds_connection_close(conn); break; } /* change notify */ /* TODO async */ if (!rc) { /* timeout */ tdsdump_log(TDS_DBG_INFO1, "timeout\n"); switch (rc = tdserror(tds_get_ctx(tds), tds, TDSETIME, sock_errno)) { case TDS_INT_CONTINUE: continue; default: case TDS_INT_CANCEL: tds_close_socket(tds); } break; } /* * we must write first to catch write errors as * write errors, not as read */ /* something to send */ if (conn->send_packets && (rc & POLLOUT) != 0) { TDSSOCKET *s; short sid = tds_packet_write(conn); if (sid == tds->sid) break; /* return to caller */ tds_mutex_lock(&conn->list_mtx); if (sid >= 0 && sid < conn->num_sessions) { s = conn->sessions[sid]; if (TDSSOCKET_VALID(s)) tds_cond_signal(&s->packet_cond); } tds_mutex_unlock(&conn->list_mtx); /* avoid using a possible closed connection */ continue; } /* received */ if (rc & POLLIN) { TDSPACKET *packet; TDSSOCKET *s; /* try to read a packet */ tds_packet_read(conn, tds); packet = conn->recv_packet; if (!packet || conn->recv_pos < packet->len) continue; conn->recv_packet = NULL; conn->recv_pos = 0; tdsdump_dump_buf(TDS_DBG_NETWORK, "Received packet", packet->buf, packet->len); tds_mutex_lock(&conn->list_mtx); if (packet->sid >= 0 && packet->sid < conn->num_sessions) { s = conn->sessions[packet->sid]; if (TDSSOCKET_VALID(s)) { /* append to correct session */ if (packet->buf[0] == TDS72_SMP && packet->buf[1] != TDS_SMP_DATA) tds_packet_cache_add(conn, packet); else tds_append_packet(&conn->packets, packet); packet = NULL; /* notify */ tds_cond_signal(&s->packet_cond); } } tds_mutex_unlock(&conn->list_mtx); tds_free_packets(packet); /* if we are receiving return the packet */ if (!send) break; } } tds_mutex_lock(&conn->list_mtx); conn->in_net_tds = NULL; }
/** * Set state of TDS connection, with logging and checking. * \param tds state information for the socket and the TDS protocol * \param state the new state of the connection, cf. TDS_STATE. * \return the new state, which might not be \a state. */ TDS_STATE tds_set_state(TDSSOCKET * tds, TDS_STATE state) { TDS_STATE prior_state; static const char state_names[][8] = { "IDLE", "WRITING", "SENDING", "PENDING", "READING", "DEAD" }; assert(state < TDS_VECTOR_SIZE(state_names)); assert(tds->state < TDS_VECTOR_SIZE(state_names)); prior_state = tds->state; if (state == prior_state) return state; switch(state) { case TDS_PENDING: if (prior_state == TDS_READING || prior_state == TDS_WRITING) { tds->state = TDS_PENDING; tds_mutex_unlock(&tds->wire_mtx); break; } tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); break; case TDS_READING: /* transition to READING are valid only from PENDING */ if (tds_mutex_trylock(&tds->wire_mtx)) return tds->state; if (tds->state != TDS_PENDING) { tds_mutex_unlock(&tds->wire_mtx); tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); break; } tds->state = state; break; case TDS_SENDING: if (prior_state != TDS_READING && prior_state != TDS_WRITING) { tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); break; } if (tds->state == TDS_READING) { /* TODO check this code, copied from tds_submit_prepare */ tds_free_all_results(tds); tds->rows_affected = TDS_NO_COUNT; tds_release_cursor(&tds->cur_cursor); tds_release_cur_dyn(tds); tds->current_op = TDS_OP_NONE; } tds_mutex_unlock(&tds->wire_mtx); tds->state = state; break; case TDS_IDLE: if (prior_state == TDS_DEAD && TDS_IS_SOCKET_INVALID(tds_get_s(tds))) { tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); break; } case TDS_DEAD: if (prior_state == TDS_READING || prior_state == TDS_WRITING) tds_mutex_unlock(&tds->wire_mtx); tds->state = state; break; case TDS_WRITING: CHECK_TDS_EXTRA(tds); if (tds_mutex_trylock(&tds->wire_mtx)) return tds->state; if (tds->state == TDS_DEAD) { tds_mutex_unlock(&tds->wire_mtx); tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); tdserror(tds_get_ctx(tds), tds, TDSEWRIT, 0); break; } else if (tds->state != TDS_IDLE && tds->state != TDS_SENDING) { tds_mutex_unlock(&tds->wire_mtx); tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n", state_names[prior_state], state_names[state]); tdserror(tds_get_ctx(tds), tds, TDSERPND, 0); break; } if (tds->state == TDS_IDLE) { /* TODO check this code, copied from tds_submit_prepare */ tds_free_all_results(tds); tds->rows_affected = TDS_NO_COUNT; tds_release_cursor(&tds->cur_cursor); tds_release_cur_dyn(tds); tds->current_op = TDS_OP_NONE; } tds->state = state; break; default: assert(0); break; } state = tds->state; tdsdump_log(TDS_DBG_ERROR, "Changed query state from %s to %s\n", state_names[prior_state], state_names[state]); CHECK_TDS_EXTRA(tds); return state; }