Line data Source code
1 : /* FreeTDS - Library of routines accessing Sybase and Microsoft databases
2 : * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 Brian Bruns
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Library General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Library General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Library General Public
15 : * License along with this library; if not, write to the
16 : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 : * Boston, MA 02111-1307, USA.
18 : */
19 :
20 : #include <config.h>
21 :
22 : #include <stdarg.h>
23 :
24 : #include <freetds/time.h>
25 :
26 : #include <assert.h>
27 : #include <ctype.h>
28 : #include <limits.h>
29 : #include <stdio.h>
30 :
31 : #if HAVE_STDLIB_H
32 : #include <stdlib.h>
33 : #endif /* HAVE_STDLIB_H */
34 :
35 : #if HAVE_STRING_H
36 : #include <string.h>
37 : #endif /* HAVE_STRING_H */
38 :
39 : #if HAVE_UNISTD_H
40 : #include <unistd.h>
41 : #endif /* HAVE_UNISTD_H */
42 :
43 : #ifdef _WIN32
44 : #include <process.h>
45 : #endif
46 :
47 : #include <freetds/tds.h>
48 : #include <freetds/tds/checks.h>
49 : #include <freetds/thread.h>
50 :
51 : /**
52 : * Set state of TDS connection, with logging and checking.
53 : * \param tds state information for the socket and the TDS protocol
54 : * \param state the new state of the connection, cf. TDS_STATE.
55 : * \return the new state, which might not be \a state.
56 : */
57 : TDS_STATE
58 1514258 : tds_set_state(TDSSOCKET * tds, TDS_STATE state)
59 : {
60 : TDS_STATE prior_state;
61 : static const char state_names[][8] = {
62 : "IDLE",
63 : "WRITING",
64 : "SENDING",
65 : "PENDING",
66 : "READING",
67 : "DEAD"
68 : };
69 1514258 : assert(state < TDS_VECTOR_SIZE(state_names));
70 1514258 : assert(tds->state < TDS_VECTOR_SIZE(state_names));
71 :
72 1514258 : prior_state = tds->state;
73 1514258 : if (state == prior_state)
74 : return state;
75 :
76 1503220 : switch (state) {
77 645473 : case TDS_PENDING:
78 645473 : if (prior_state == TDS_READING || prior_state == TDS_WRITING) {
79 566449 : tds->state = TDS_PENDING;
80 566449 : tds_mutex_unlock(&tds->wire_mtx);
81 566449 : break;
82 : }
83 79024 : tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n",
84 : state_names[prior_state], state_names[state]);
85 : break;
86 566388 : case TDS_READING:
87 : /* transition to READING are valid only from PENDING */
88 566388 : if (tds_mutex_trylock(&tds->wire_mtx))
89 10 : return tds->state;
90 566378 : if (tds->state != TDS_PENDING) {
91 9 : tds_mutex_unlock(&tds->wire_mtx);
92 9 : tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n",
93 : state_names[prior_state], state_names[state]);
94 : break;
95 : }
96 566369 : tds->state = state;
97 566369 : break;
98 64442 : case TDS_SENDING:
99 64442 : if (prior_state != TDS_READING && prior_state != TDS_WRITING) {
100 0 : tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n",
101 : state_names[prior_state], state_names[state]);
102 : break;
103 : }
104 64442 : if (tds->state == TDS_READING) {
105 : /* TODO check this code, copied from tds_submit_prepare */
106 756 : tds_free_all_results(tds);
107 756 : tds->rows_affected = TDS_NO_COUNT;
108 756 : tds_release_cursor(&tds->cur_cursor);
109 756 : tds_release_cur_dyn(tds);
110 756 : tds->current_op = TDS_OP_NONE;
111 : }
112 :
113 64442 : tds_mutex_unlock(&tds->wire_mtx);
114 64442 : tds->state = state;
115 64442 : break;
116 78920 : case TDS_IDLE:
117 78920 : if (prior_state == TDS_DEAD && TDS_IS_SOCKET_INVALID(tds_get_s(tds))) {
118 20 : tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n",
119 : state_names[prior_state], state_names[state]);
120 : break;
121 : }
122 : case TDS_DEAD:
123 83343 : if (prior_state == TDS_READING || prior_state == TDS_WRITING)
124 79014 : tds_mutex_unlock(&tds->wire_mtx);
125 83343 : tds->state = state;
126 :
127 : /* invalid, code should have either close or aborted all freezes */
128 83343 : if (TDS_UNLIKELY(tds->frozen)) {
129 : TDSFREEZE freeze;
130 :
131 0 : tds->frozen = 1;
132 0 : freeze.tds = tds;
133 0 : freeze.pkt = tds->frozen_packets;
134 0 : freeze.pkt_pos = 8;
135 0 : freeze.size_len = 0;
136 0 : tds_freeze_abort(&freeze);
137 :
138 0 : tds_connection_close(tds->conn);
139 : }
140 : break;
141 143554 : case TDS_WRITING:
142 143554 : CHECK_TDS_EXTRA(tds);
143 :
144 143554 : if (tds_mutex_trylock(&tds->wire_mtx))
145 0 : return tds->state;
146 :
147 143554 : if (tds->state == TDS_DEAD) {
148 2 : tds_mutex_unlock(&tds->wire_mtx);
149 2 : tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n",
150 : state_names[prior_state], state_names[state]);
151 2 : tdserror(tds_get_ctx(tds), tds, TDSEWRIT, 0);
152 2 : break;
153 143552 : } else if (tds->state != TDS_IDLE && tds->state != TDS_SENDING) {
154 16 : tds_mutex_unlock(&tds->wire_mtx);
155 16 : tdsdump_log(TDS_DBG_ERROR, "logic error: cannot change query state from %s to %s\n",
156 : state_names[prior_state], state_names[state]);
157 16 : tdserror(tds_get_ctx(tds), tds, TDSERPND, 0);
158 16 : break;
159 : }
160 :
161 143536 : if (tds->state == TDS_IDLE) {
162 : /* TODO check this code, copied from tds_submit_prepare */
163 79094 : tds_free_all_results(tds);
164 79094 : tds->rows_affected = TDS_NO_COUNT;
165 79094 : tds_release_cursor(&tds->cur_cursor);
166 79094 : tds_release_cur_dyn(tds);
167 79094 : tds->current_op = TDS_OP_NONE;
168 : }
169 :
170 143536 : tds->state = state;
171 143536 : break;
172 : default:
173 0 : assert(0);
174 : break;
175 : }
176 :
177 1503210 : state = tds->state;
178 :
179 1503210 : tdsdump_log(TDS_DBG_INFO1, "Changed query state from %s to %s\n", state_names[prior_state], state_names[state]);
180 1503210 : CHECK_TDS_EXTRA(tds);
181 :
182 1503210 : return state;
183 : }
184 :
185 :
186 : void
187 3676 : tds_swap_bytes(void *buf, size_t bytes)
188 : {
189 : unsigned char tmp, *begin, *last;
190 :
191 3676 : begin = (unsigned char *) buf;
192 3676 : last = begin + bytes;
193 :
194 22944 : while (begin < --last) {
195 15592 : tmp = *last;
196 15592 : *last = *begin;
197 15592 : *begin++ = tmp;
198 : }
199 3676 : }
200 :
201 : unsigned int
202 7938 : tds_gettime_ms(void)
203 : {
204 : #ifdef _WIN32
205 : /* note: static analysis warns that this will loop every 49 days,
206 : * however elsewhere we take care to use this return value in a way
207 : * that allows for unsigned wraparound */
208 : return GetTickCount();
209 : #elif defined(HAVE_GETHRTIME)
210 : return (unsigned int) (gethrtime() / 1000000u);
211 : #elif defined(HAVE_CLOCK_GETTIME) && defined(TDS_GETTIMEMILLI_CONST)
212 : struct timespec ts;
213 7938 : clock_gettime(TDS_GETTIMEMILLI_CONST, &ts);
214 7938 : return (unsigned int) (ts.tv_sec * 1000u + ts.tv_nsec / 1000000u);
215 : #elif defined(HAVE_GETTIMEOFDAY)
216 : struct timeval tv;
217 : gettimeofday(&tv, NULL);
218 : return (unsigned int) (tv.tv_sec * 1000u + tv.tv_usec / 1000u);
219 : #else
220 : #error How to implement tds_gettime_ms ??
221 : #endif
222 : }
223 :
224 : /*
225 : * Call the client library's error handler
226 : */
227 : #define EXINFO 1
228 : #define EXUSER 2
229 : #define EXNONFATAL 3
230 : #define EXCONVERSION 4
231 : #define EXSERVER 5
232 : #define EXTIME 6
233 : #define EXPROGRAM 7
234 : #define EXRESOURCE 8
235 : #define EXCOMM 9
236 : #define EXFATAL 10
237 : #define EXCONSISTENCY 11
238 :
239 : typedef struct tds_error_message
240 : {
241 : TDSERRNO msgno;
242 : TDS_TINYINT severity;
243 : const char *msgtext;
244 : } TDS_ERROR_MESSAGE;
245 :
246 : static const TDS_ERROR_MESSAGE tds_error_messages[] =
247 : { { TDSEICONVIU, EXCONVERSION, "Buffer exhausted converting characters from client into server's character set" }
248 : , { TDSEICONVAVAIL, EXCONVERSION, "Character set conversion is not available between client character set '%.*s' and "
249 : "server character set '%.*s'" }
250 : , { TDSEICONVO, EXCONVERSION, "Error converting characters into server's character set. Some character(s) could "
251 : "not be converted" }
252 : , { TDSEICONVI, EXCONVERSION, "Some character(s) could not be converted into client's character set. "
253 : "Unconverted bytes were changed to question marks ('?')" }
254 : , { TDSEICONV2BIG, EXCONVERSION, "Some character(s) could not be converted into client's character set" }
255 : , { TDSEICONVPARTIAL, EXCONVERSION, "Illegal byte sequence encountered, data ends with an incomplete "
256 : "multibyte sequence" }
257 : , { TDSEPORTINSTANCE, EXUSER, "Both port and instance specified" }
258 : , { TDSEVERDOWN, EXINFO, "TDS version downgraded to 7.1!" }
259 : , { TDSEMEM, EXRESOURCE, "Unable to allocate sufficient memory" }
260 : , { TDSEPWD, EXUSER, "Login incorrect" }
261 : , { TDSERPND, EXPROGRAM, "Attempt to initiate a new TDS server operation with results pending" }
262 : , { TDSEBTOK, EXCOMM, "Bad token from the server: Datastream processing out of sync" }
263 : , { TDSECAP, EXCOMM, "DB-Library capabilities not accepted by the Server" }
264 : , { TDSECAPTYP, EXCOMM, "Unexpected capability type in CAPABILITY datastream" }
265 : , { TDSECLOS, EXCOMM, "Error in closing network connection" }
266 : , { TDSECONN, EXCOMM, "Unable to connect: TDS server is unavailable or does not exist" }
267 : , { TDSEEUNR, EXCOMM, "Unsolicited event notification received" }
268 : , { TDSEFCON, EXCOMM, "TDS server connection failed" }
269 : , { TDSENEG, EXCOMM, "Negotiated login attempt failed" }
270 : , { TDSEOOB, EXCOMM, "Error in sending out-of-band data to the server" }
271 : , { TDSEREAD, EXCOMM, "Read from the server failed" }
272 : , { TDSETIME, EXTIME, "TDS server connection timed out" }
273 : , { TDSESEOF, EXCOMM, "Unexpected EOF from the server" }
274 : , { TDSEINTF, EXUSER, "Server name not found in configuration files." }
275 : , { TDSESOCK, EXCOMM, "Unable to open socket" }
276 : , { TDSESYNC, EXCOMM, "Read attempted while out of synchronization with TDS server" }
277 : , { TDSEUHST, EXUSER, "Unknown host machine name." }
278 : , { TDSEUMSG, EXCOMM, "Unknown message-id in MSG datastream" }
279 : , { TDSEUSCT, EXCOMM, "Unable to set communications timer" }
280 : , { TDSEUTDS, EXCOMM, "Unrecognized TDS version received from the server" }
281 : , { TDSEWRIT, EXCOMM, "Write to the server failed" }
282 : , { TDSECONF, EXUSER, "Local configuration error. "
283 : "Check TDSDUMPCONFIG log for details." }
284 : /* last, with msgno == TDSEOK */
285 : , { TDSEOK, EXCONSISTENCY, "unrecognized msgno" }
286 : };
287 :
288 : static const TDS_ERROR_MESSAGE *tds_find_error(int msgno);
289 : static int tds_report_error(const TDSCONTEXT * tds_ctx, TDSSOCKET * tds, const TDS_ERROR_MESSAGE * err,
290 : int msgno, int errnum, char *message);
291 :
292 : static const char *
293 0 : retname(int retcode)
294 : {
295 0 : switch(retcode) {
296 : case TDS_INT_CONTINUE:
297 : return "TDS_INT_CONTINUE";
298 0 : case TDS_INT_CANCEL:
299 0 : return "TDS_INT_CANCEL";
300 0 : case TDS_INT_TIMEOUT:
301 0 : return "TDS_INT_TIMEOUT";
302 : }
303 0 : assert(0);
304 : return "nonesuch";
305 : }
306 :
307 : /**
308 : * \brief Call the client library's error handler (for library-generated errors only)
309 : *
310 : * The client library error handler may return:
311 : * TDS_INT_CANCEL -- Return TDS_FAIL to the calling function. For TDSETIME, closes the connection first.
312 : * TDS_INT_CONTINUE -- For TDSETIME only, retry the network read/write operation. Else invalid.
313 : * TDS_INT_TIMEOUT -- For TDSETIME only, send a TDSCANCEL packet. Else invalid.
314 : *
315 : * These are Sybase semantics, but they serve all purposes.
316 : * The application tells the library to quit, fail, retry, or attempt to cancel. In the event of a network timeout,
317 : * a failed operation necessarily means the connection becomes unusable, because no cancellation dialog was
318 : * concluded with the server.
319 : *
320 : * It is the client library's duty to call the error handler installed by the application, if any, and to interpret the
321 : * installed handler's return code. It may return to this function one of the above codes only. This function will not
322 : * check the return code because there's nothing that can be done here except abort. It is merely passed to the
323 : * calling function, which will (we hope) DTRT.
324 : *
325 : * \param tds_ctx points to a TDSCONTEXT structure
326 : * \param tds the connection structure, may be NULL if not connected
327 : * \param msgno an enumerated libtds msgno, cf. tds.h
328 : * \param errnum the OS errno, if it matters, else zero
329 : *
330 : * \returns client library function's return code
331 : */
332 : int
333 41378 : tdserror(const TDSCONTEXT *tds_ctx, TDSSOCKET *tds, int msgno, int errnum)
334 : {
335 41378 : return tds_report_error(tds_ctx, tds, tds_find_error(msgno), msgno, errnum, NULL);
336 : }
337 :
338 : /**
339 : * \brief Call the client library's error handler for a message with placeholders.
340 : *
341 : * Same as tdserror() but the error message is used as a format for the additional arguments.
342 : * If the message cannot be formatted, the handler receives the message with the placeholders.
343 : *
344 : * \param tds_ctx points to a TDSCONTEXT structure
345 : * \param tds the connection structure, may be NULL if not connected
346 : * \param msgno an enumerated libtds msgno, cf. tds.h
347 : * \param errnum the OS errno, if it matters, else zero
348 : * \returns same as tdserror()
349 : */
350 : int
351 10 : tdserror_args(const TDSCONTEXT *tds_ctx, TDSSOCKET *tds, int msgno, int errnum, ...)
352 : {
353 10 : const TDS_ERROR_MESSAGE *err = tds_find_error(msgno);
354 10 : char *message = NULL;
355 : va_list ap;
356 : int rc;
357 :
358 10 : va_start(ap, errnum);
359 10 : if (vasprintf(&message, err->msgtext, ap) < 0)
360 0 : message = NULL;
361 10 : va_end(ap);
362 :
363 10 : rc = tds_report_error(tds_ctx, tds, err, msgno, errnum, message);
364 10 : free(message);
365 10 : return rc;
366 : }
367 :
368 : /** Look up an error message, returns a generic one if not found. */
369 : static const TDS_ERROR_MESSAGE *
370 : tds_find_error(int msgno)
371 : {
372 : const TDS_ERROR_MESSAGE *err;
373 :
374 65512 : for (err = tds_error_messages; err->msgno != TDSEOK; ++err) {
375 65512 : if (err->msgno == msgno)
376 : break;
377 : }
378 : return err;
379 : }
380 :
381 : /**
382 : * Call the client library's error handler.
383 : * \param message allocated text of the message, still owned by the caller, NULL to use the text in \a err
384 : */
385 : static int
386 41388 : tds_report_error(const TDSCONTEXT *tds_ctx, TDSSOCKET *tds, const TDS_ERROR_MESSAGE *err, int msgno, int errnum, char *message)
387 : {
388 : #if 0
389 : static const char int_exit_text[] = "FreeTDS: libtds: exiting because client error handler returned %d for msgno %d\n";
390 : static const char int_invalid_text[] = "%s (%d) received from client library error handler for nontimeout for error %d."
391 : " Treating as INT_EXIT\n";
392 : #endif
393 : TDSMESSAGE msg;
394 41388 : int rc = TDS_INT_CANCEL;
395 :
396 41388 : tdsdump_log(TDS_DBG_FUNC, "tdserror(%p, %p, %d, %d)\n", tds_ctx, tds, msgno, errnum);
397 :
398 41388 : CHECK_CONTEXT_EXTRA(tds_ctx);
399 :
400 41388 : if (tds)
401 41388 : CHECK_TDS_EXTRA(tds);
402 :
403 41388 : if (tds_ctx && tds_ctx->err_handler) {
404 41334 : memset(&msg, 0, sizeof(TDSMESSAGE));
405 41334 : msg.msgno = msgno;
406 41334 : msg.severity = err->severity;
407 41334 : msg.state = -1;
408 41334 : msg.server = "OpenClient";
409 41334 : msg.line_number = -1;
410 41334 : msg.message = message ? message : (TDS_CHAR *) err->msgtext;
411 41334 : msg.message_allocated = (message != NULL);
412 41334 : msg.sql_state = tds_alloc_client_sqlstate(msgno);
413 :
414 41334 : msg.oserr = errnum;
415 :
416 : /*
417 : * Call client library handler.
418 : * The client library must return a valid code. It is not checked again here.
419 : */
420 41334 : rc = tds_ctx->err_handler(tds_ctx, tds, &msg);
421 41334 : tdsdump_log(TDS_DBG_FUNC, "tdserror: client library returned %s(%d)\n", retname(rc), rc);
422 :
423 41334 : TDS_ZERO_FREE(msg.sql_state);
424 : } else {
425 : static const char msg[] = "tdserror: client library not called because either "
426 : "tds_ctx (%p) or tds_ctx->err_handler is NULL\n";
427 54 : tdsdump_log(TDS_DBG_ERROR, msg, tds_ctx);
428 : }
429 :
430 :
431 41388 : assert(msgno == TDSETIME || rc != TDS_INT_TIMEOUT); /* client library should prevent */
432 41388 : assert(msgno == TDSETIME || rc != TDS_INT_CONTINUE); /* client library should prevent */
433 :
434 41388 : if (msgno != TDSETIME && rc != TDS_INT_CANCEL) {
435 0 : tdsdump_log(TDS_DBG_SEVERE, "exit: %s(%d) valid only for TDSETIME\n", retname(rc), rc);
436 : rc = TDS_INT_CANCEL;
437 : }
438 :
439 41388 : if (rc == TDS_INT_TIMEOUT) {
440 196 : tds_send_cancel(tds);
441 196 : rc = TDS_INT_CONTINUE;
442 : }
443 :
444 41388 : tdsdump_log(TDS_DBG_FUNC, "tdserror: returning %s(%d)\n", retname(rc), rc);
445 :
446 41388 : return rc;
447 : }
448 :
|