/** * \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; } } }
/** * 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; }
int main(int argc, char **argv) { TDSLOGIN *login; int ret; int verbose = 0; int i; typedef int (*perr)(const TDSCONTEXT *, TDSSOCKET *, TDSMESSAGE *); const perr * my_err; /* use ISO8859-1 as our coding */ strcpy(CHARSET, "ISO8859-1"); ret = try_tds_login(&login, &tds, __FILE__, verbose); if (ret != TDS_SUCCESS) { fprintf(stderr, "try_tds_login() failed\n"); return 1; } /* override a const in a safe way */ my_err = &tds_get_ctx(tds)->err_handler; *((perr*)my_err) = err_handler; /* prepend some characters to check part of sequence error */ einval_error = 1; eilseq_error = 1; /* do not stop on first error so we check conversion correct */ for (i = 0; i <= 192; ++i) test(i, 0); /* try creating a double conversion warning */ eilseq_error = 0; einval_error = 0; /* we already tested this error above */ for (i = 0; i <= 192; ++i) { eilseq_count = 0; test(i, 1); if (eilseq_count > 1) { fprintf(stderr, "Two warnings returned instead of one in %s\n", test_name); g_result = 1; break; } if (eilseq_count < 1 && invalid_char == '?') { fprintf(stderr, "No warning returned in %s\n", test_name); g_result = 1; } } try_tds_logout(login, tds, verbose); return g_result; }
/** * 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; }
void tds_check_tds_extra(const TDSSOCKET * tds) { const int invalid_state = 0; int i; TDSDYNAMIC *cur_dyn = NULL; TDSCURSOR *cur_cursor = NULL; assert(tds); /* test state and connection */ switch (tds->state) { case TDS_DEAD: case TDS_WRITING: case TDS_SENDING: case TDS_PENDING: case TDS_IDLE: case TDS_READING: break; default: assert(invalid_state); } assert(tds->conn); #if ENABLE_ODBC_MARS if (tds->state != TDS_DEAD) assert(!TDS_IS_SOCKET_INVALID(tds_get_s(tds))); #else assert(tds->state == TDS_DEAD || !TDS_IS_SOCKET_INVALID(tds_get_s(tds))); assert(tds->state != TDS_DEAD || TDS_IS_SOCKET_INVALID(tds_get_s(tds))); #endif /* test env */ tds_check_env_extra(&tds->conn->env); /* test buffers and positions */ tds_check_packet_extra(tds->send_packet); tds_check_packet_extra(tds->recv_packet); #if ENABLE_ODBC_MARS if (tds->conn->send_packets) assert(tds->conn->send_pos <= tds->conn->send_packets->len); if (tds->conn->recv_packet) assert(tds->conn->recv_pos <= tds->conn->recv_packet->len); #endif assert(tds->in_pos <= tds->in_len); assert(tds->in_len <= tds->recv_packet->capacity); /* TODO remove blocksize from env and use out_len ?? */ /* assert(tds->out_pos <= tds->out_len); */ /* assert(tds->out_len == 0 || tds->out_buf != NULL); */ assert(tds->send_packet->capacity >= tds->out_buf_max + TDS_ADDITIONAL_SPACE); assert(tds->out_buf >= tds->send_packet->buf); assert(tds->out_buf + tds->out_buf_max + TDS_ADDITIONAL_SPACE <= tds->send_packet->buf + tds->send_packet->capacity); assert(tds->out_pos <= tds->out_buf_max); assert(tds->in_buf == tds->recv_packet->buf || tds->in_buf == tds->recv_packet->buf + 16); assert(tds->recv_packet->capacity > 0); /* test res_info */ if (tds->res_info) tds_check_resultinfo_extra(tds->res_info); /* test num_comp_info, comp_info */ assert(tds->num_comp_info >= 0); for (i = 0; i < tds->num_comp_info; ++i) { assert(tds->comp_info); tds_check_resultinfo_extra(tds->comp_info[i]); } /* param_info */ if (tds->param_info) tds_check_resultinfo_extra(tds->param_info); /* test cursors */ for (cur_cursor = tds->conn->cursors; cur_cursor != NULL; cur_cursor = cur_cursor->next) tds_check_cursor_extra(cur_cursor); /* test dynamics */ for (cur_dyn = tds->conn->dyns; cur_dyn != NULL; cur_dyn = cur_dyn->next) tds_check_dynamic_extra(cur_dyn); /* test tds_ctx */ tds_check_context_extra(tds_get_ctx(tds)); /* TODO test char_conv_count, char_convs */ /* we can't have compute and no results */ assert(tds->num_comp_info == 0 || tds->res_info != NULL); /* we can't have normal and parameters results */ /* TODO too strict ?? */ /* assert(tds->param_info == NULL || tds->res_info == NULL); */ }
/** * 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; }
static int do_query(TDSSOCKET * tds, char *buf, int opt_flags) { int rows = 0; TDSRET rc; int i; TDSCOLUMN *col; int ctype; CONV_RESULT dres; unsigned char *src; TDS_INT srclen; TDS_INT resulttype; struct timeval start, stop; int print_rows = 1; char message[128]; rc = tds_submit_query(tds, buf); if (TDS_FAILED(rc)) { fprintf(stderr, "tds_submit_query() failed\n"); return 1; } while ((rc = tds_process_tokens(tds, &resulttype, NULL, TDS_TOKEN_RESULTS)) == TDS_SUCCESS) { const int stop_mask = TDS_STOPAT_ROWFMT|TDS_RETURN_DONE|TDS_RETURN_ROW|TDS_RETURN_COMPUTE; if (opt_flags & OPT_TIMER) { gettimeofday(&start, NULL); print_rows = 0; } switch (resulttype) { case TDS_ROWFMT_RESULT: if ((!(opt_flags & OPT_NOHEADER)) && tds->current_results) { for (i = 0; i < tds->current_results->num_cols; i++) { if (i) fputs(opt_col_term, stdout); fputs(tds_dstr_cstr(&tds->current_results->columns[i]->column_name), stdout); } fputs(opt_row_term, stdout); } break; case TDS_COMPUTE_RESULT: case TDS_ROW_RESULT: rows = 0; while ((rc = tds_process_tokens(tds, &resulttype, NULL, stop_mask)) == TDS_SUCCESS) { if (resulttype != TDS_ROW_RESULT && resulttype != TDS_COMPUTE_RESULT) break; rows++; if (!tds->current_results) continue; for (i = 0; i < tds->current_results->num_cols; i++) { col = tds->current_results->columns[i]; if (col->column_cur_size < 0) { if (print_rows) { if (i) fputs(opt_col_term, stdout); fputs("NULL", stdout); } continue; } ctype = tds_get_conversion_type(col->column_type, col->column_size); src = col->column_data; if (is_blob_col(col) && col->column_type != SYBVARIANT) src = (unsigned char *) ((TDSBLOB *) src)->textvalue; srclen = col->column_cur_size; if (tds_convert(tds_get_ctx(tds), ctype, (TDS_CHAR *) src, srclen, SYBVARCHAR, &dres) < 0) continue; if (print_rows) { if (i) fputs(opt_col_term, stdout); fputs(dres.c, stdout); } free(dres.c); } if (print_rows) fputs(opt_row_term, stdout); } if (!QUIET) fprintf(stdout, "(%d row%s affected)\n", rows, rows == 1 ? "" : "s"); break; case TDS_STATUS_RESULT: if (!QUIET) printf("(return status = %d)\n", tds->ret_status); break; default: break; } if (opt_flags & OPT_VERSION) { char version[64]; int line = 0; line = tds_version(tds->conn, version); if (line) { TDSMESSAGE msg; memset(&msg, 0, sizeof(TDSMESSAGE)); msg.server = "tsql"; sprintf(message, "using TDS version %s", version); msg.message = message; tsql_handle_message(tds_get_ctx(tds), tds, &msg); } } if (opt_flags & OPT_TIMER) { TDSMESSAGE msg; gettimeofday(&stop, NULL); sprintf(message, "Total time for processing %d rows: %ld msecs\n", rows, (long) ((stop.tv_sec - start.tv_sec) * 1000) + ((stop.tv_usec - start.tv_usec) / 1000)); memset(&msg, 0, sizeof(TDSMESSAGE)); msg.server = "tsql"; msg.message = message; tsql_handle_message(tds_get_ctx(tds), tds, &msg); } } return 0; }
/** * Select on a socket until it's available or the timeout expires. * Meanwhile, call the interrupt function. * \return >0 ready descriptors * 0 timeout * <0 error (cf. errno). Caller should close socket and return failure. * This function does not call tdserror or close the socket because it can't know the context in which it's being called. */ int tds_select(TDSSOCKET * tds, unsigned tds_sel, int timeout_seconds) { int rc, seconds; unsigned int poll_seconds; assert(tds != NULL); assert(timeout_seconds >= 0); /* * The select loop. * If an interrupt handler is installed, we iterate once per second, * else we try once, timing out after timeout_seconds (0 == never). * If select(2) is interrupted by a signal (e.g. press ^C in sqsh), we timeout. * (The application can retry if desired by installing a signal handler.) * * We do not measure current time against end time, to avoid being tricked by ntpd(8) or similar. * Instead, we just count down. * * We exit on the first of these events: * 1. a descriptor is ready. (return to caller) * 2. select(2) returns an important error. (return to caller) * A timeout of zero says "wait forever". We do that by passing a NULL timeval pointer to select(2). */ poll_seconds = (tds_get_ctx(tds) && tds_get_ctx(tds)->int_handler)? 1 : timeout_seconds; for (seconds = timeout_seconds; timeout_seconds == 0 || seconds > 0; seconds -= poll_seconds) { struct pollfd fds[2]; int timeout = poll_seconds ? poll_seconds * 1000 : -1; if (TDS_IS_SOCKET_INVALID(tds_get_s(tds))) return -1; if ((tds_sel & TDSSELREAD) != 0 && tds->conn->tls_session && tds_ssl_pending(tds->conn)) return POLLIN; fds[0].fd = tds_get_s(tds); fds[0].events = tds_sel; fds[0].revents = 0; fds[1].fd = tds->conn->s_signaled; fds[1].events = POLLIN; fds[1].revents = 0; rc = poll(fds, 2, timeout); if (rc > 0 ) { if (fds[0].revents & POLLERR) return -1; rc = fds[0].revents; if (fds[1].revents) { #if ENABLE_ODBC_MARS tds_check_cancel(tds->conn); #endif rc |= TDSPOLLURG; } return rc; } if (rc < 0) { char *errstr; switch (sock_errno) { case TDSSOCK_EINTR: /* FIXME this should be global maximun, not loop one */ seconds += poll_seconds; break; /* let interrupt handler be called */ default: /* documented: EFAULT, EBADF, EINVAL */ errstr = sock_strerror(sock_errno); tdsdump_log(TDS_DBG_ERROR, "error: poll(2) returned %d, \"%s\"\n", sock_errno, errstr); sock_strerror_free(errstr); return rc; } } assert(rc == 0 || (rc < 0 && sock_errno == TDSSOCK_EINTR)); if (tds_get_ctx(tds) && tds_get_ctx(tds)->int_handler) { /* interrupt handler installed */ /* * "If hndlintr() returns INT_CANCEL, DB-Library sends an attention token [TDS_BUFSTAT_ATTN] * to the server. This causes the server to discontinue command processing. * The server may send additional results that have already been computed. * When control returns to the mainline code, the mainline code should do * one of the following: * - Flush the results using dbcancel * - Process the results normally" */ int timeout_action = (*tds_get_ctx(tds)->int_handler) (tds_get_parent(tds)); switch (timeout_action) { case TDS_INT_CONTINUE: /* keep waiting */ continue; case TDS_INT_CANCEL: /* abort the current command batch */ /* FIXME tell tds_goodread() not to call tdserror() */ return 0; default: tdsdump_log(TDS_DBG_NETWORK, "tds_select: invalid interupt handler return code: %d\n", timeout_action); return -1; } } /* * We can reach here if no interrupt handler was installed and we either timed out or got EINTR. * We cannot be polling, so we are about to drop out of the loop. */ assert(poll_seconds == timeout_seconds); } return 0; }
void tds_check_tds_extra(const TDSSOCKET * tds) { const int invalid_state = 0; int found, i; int result_found = 0; TDSDYNAMIC *cur_dyn = NULL; TDSCURSOR *cur_cursor = NULL; assert(tds); /* teset state and connection */ switch (tds->state) { case TDS_DEAD: case TDS_QUERYING: case TDS_PENDING: case TDS_IDLE: case TDS_READING: break; default: assert(invalid_state); } assert(tds->state == TDS_DEAD || !TDS_IS_SOCKET_INVALID(tds_get_s(tds))); assert(tds->state != TDS_DEAD || TDS_IS_SOCKET_INVALID(tds_get_s(tds))); /* test env */ tds_check_env_extra(&tds->env); /* test buffers and positions */ assert(tds->in_pos <= tds->in_len && tds->in_len <= tds->in_buf_max); /* TODO remove blocksize from env and use out_len ?? */ /* assert(tds->out_pos <= tds->out_len); */ /* assert(tds->out_len == 0 || tds->out_buf != NULL); */ assert(tds->out_pos <= tds->env.block_size); assert(tds->env.block_size == 0 || tds->out_buf != NULL); assert(tds->in_buf_max == 0 || tds->in_buf != NULL); /* test res_info */ if (tds->res_info) { tds_check_resultinfo_extra(tds->res_info); if (tds->current_results == tds->res_info) result_found = 1; } /* test num_comp_info, comp_info */ assert(tds->num_comp_info >= 0); for (i = 0; i < tds->num_comp_info; ++i) { assert(tds->comp_info); tds_check_resultinfo_extra(tds->comp_info[i]); if (tds->current_results == tds->comp_info[i]) result_found = 1; } /* param_info */ if (tds->param_info) { tds_check_resultinfo_extra(tds->param_info); if (tds->current_results == tds->param_info) result_found = 1; } /* test cursors */ found = 0; for (cur_cursor = tds->cursors; cur_cursor != NULL; cur_cursor = cur_cursor->next) { tds_check_cursor_extra(cur_cursor); if (tds->current_results == cur_cursor->res_info) result_found = 1; if (cur_cursor == tds->cur_cursor) found = 1; } assert(found || tds->cur_cursor == NULL); /* test num_dyms, cur_dyn, dyns */ found = 0; for (cur_dyn = tds->dyns; cur_dyn != NULL; cur_dyn = cur_dyn->next) { if (cur_dyn == tds->cur_dyn) found = 1; tds_check_dynamic_extra(cur_dyn); if (tds->current_results == cur_dyn->res_info) result_found = 1; } assert(found || tds->cur_dyn == NULL); /* test tds_ctx */ tds_check_context_extra(tds_get_ctx(tds)); /* TODO test char_conv_count, char_convs */ /* current_results should be one of res_info, comp_info, param_info or dynamic */ /* * TODO this test was here to check that current_results was an alias * but with cursor and reference counting current_results can point * to a cursor result available on upper layer * Perhaps we should free results on deallocate and enable * assert again? */ /* assert(result_found || tds->current_results == NULL); */ /* we can't have compute and no results */ assert(tds->num_comp_info == 0 || tds->res_info != NULL); /* we can't have normal and parameters results */ /* TODO too strict ?? */ /* assert(tds->param_info == NULL || tds->res_info == 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; }