ruby-oci8 2.2.3 → 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,14 +9,15 @@
9
9
  #ifndef WIN32
10
10
  #include <unistd.h>
11
11
  #include <sys/socket.h>
12
+ #include <netinet/in.h>
13
+ #include <netinet/tcp.h>
12
14
  #endif
13
15
 
14
- #define DEBUG_HOOK_FUNCS 1
15
-
16
16
  #ifdef WIN32
17
17
  static CRITICAL_SECTION lock;
18
18
  #define LOCK(lock) EnterCriticalSection(lock)
19
19
  #define UNLOCK(lock) LeaveCriticalSection(lock)
20
+ typedef int socklen_t;
20
21
  #else
21
22
  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
22
23
  #define LOCK(lock) pthread_mutex_lock(lock)
@@ -25,6 +26,29 @@ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
25
26
  #define INVALID_SOCKET (-1)
26
27
  #endif
27
28
 
29
+ #if defined(__APPLE__) && defined(TCP_KEEPALIVE) /* macOS */
30
+ #define USE_TCP_KEEPALIVE
31
+ #define SUPPORT_TCP_KEEPALIVE_TIME
32
+ #elif defined(__sun) && defined(TCP_KEEPALIVE_THRESHOLD) /* Solaris */
33
+ #define USE_TCP_KEEPALIVE_THRESHOLD
34
+ #define SUPPORT_TCP_KEEPALIVE_TIME
35
+ #elif defined(TCP_KEEPIDLE) /* Linux, etc */
36
+ #define USE_TCP_KEEPIDLE
37
+ #define SUPPORT_TCP_KEEPALIVE_TIME
38
+ #elif defined(WIN32) /* Windows */
39
+ #define SUPPORT_TCP_KEEPALIVE_TIME
40
+ #endif
41
+
42
+ int oci8_cancel_read_at_exit = 0;
43
+
44
+ #ifdef SUPPORT_TCP_KEEPALIVE_TIME
45
+ int oci8_tcp_keepalive_time = 0;
46
+ static int hook_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
47
+ #else
48
+ int oci8_tcp_keepalive_time = -1;
49
+ #endif
50
+
51
+
28
52
  typedef struct {
29
53
  const char *func_name;
30
54
  void *func_addr;
@@ -93,6 +117,16 @@ static int replace_functions(const char * const *files, hook_func_entry_t *funct
93
117
 
94
118
  #ifdef WIN32
95
119
 
120
+ #ifndef _MSC_VER
121
+ /* setsockopt() in ws2_32.dll */
122
+ #define setsockopt rboci_setsockopt
123
+ typedef int (WSAAPI *setsockopt_t)(SOCKET, int, int, const void *, int);
124
+ static setsockopt_t setsockopt;
125
+ #endif
126
+
127
+ /* system-wide keepalive interval */
128
+ static DWORD keepalive_interval;
129
+
96
130
  static int locK_is_initialized;
97
131
 
98
132
  static int WSAAPI hook_WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
@@ -116,6 +150,7 @@ static const char * const tcp_func_files[] = {
116
150
 
117
151
  static hook_func_entry_t tcp_functions[] = {
118
152
  {"WSARecv", (void*)hook_WSARecv, NULL},
153
+ {"setsockopt", (void*)hook_setsockopt, NULL},
119
154
  {NULL, NULL, NULL},
120
155
  };
121
156
 
@@ -123,22 +158,63 @@ static hook_func_entry_t tcp_functions[] = {
123
158
  static int WSAAPI hook_WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
124
159
  {
125
160
  socket_entry_t entry;
161
+ int enable_cancel = oci8_cancel_read_at_exit;
126
162
  int rv;
127
163
 
128
- socket_entry_set(&entry, s);
164
+ if (enable_cancel > 0) {
165
+ socket_entry_set(&entry, s);
166
+ }
129
167
  rv = WSARecv(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine);
130
- socket_entry_clear(&entry);
168
+ if (enable_cancel > 0) {
169
+ socket_entry_clear(&entry);
170
+ }
131
171
  return rv;
132
172
  }
133
173
 
134
174
  void oci8_install_hook_functions()
135
175
  {
176
+ static int hook_functions_installed = 0;
177
+ HKEY hKey;
178
+ DWORD type;
179
+ DWORD data;
180
+ DWORD cbData = sizeof(data);
181
+ const char *reg_key = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
182
+
183
+ if (hook_functions_installed) {
184
+ return;
185
+ }
186
+
136
187
  InitializeCriticalSectionAndSpinCount(&lock, 5000);
137
188
  locK_is_initialized = 1;
138
189
 
190
+ #ifndef _MSC_VER
191
+ /* Get setsockopt in ws2_32.dll.
192
+ * setsockopt used by mingw compiler isn't same with that in ws2_32.dll.
193
+ */
194
+ setsockopt = (setsockopt_t)GetProcAddress(GetModuleHandleA("WS2_32.DLL"), "setsockopt");
195
+ if (setsockopt == NULL){
196
+ rb_raise(rb_eRuntimeError, "setsockopt isn't found in WS2_32.DLL");
197
+ }
198
+ #endif
199
+
200
+ /* Get system-wide keepalive interval parameter.
201
+ * https://technet.microsoft.com/en-us/library/cc957548.aspx
202
+ */
203
+ if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, reg_key, 0, KEY_QUERY_VALUE, &hKey) != 0) {
204
+ rb_raise(rb_eRuntimeError, "failed to open the registry key HKLM\\%s", reg_key);
205
+ }
206
+ keepalive_interval = 1000; /* default value when the following entry isn't found. */
207
+ if (RegQueryValueEx(hKey, "KeepAliveInterval", NULL, &type, (LPBYTE)&data, &cbData) == 0) {
208
+ if (type == REG_DWORD) {
209
+ keepalive_interval = data;
210
+ }
211
+ }
212
+ RegCloseKey(hKey);
213
+
139
214
  if (replace_functions(tcp_func_files, tcp_functions) != 0) {
140
215
  rb_raise(rb_eRuntimeError, "No DLL is found to hook.");
141
216
  }
217
+ hook_functions_installed = 1;
142
218
  }
143
219
 
144
220
  static void shutdown_socket(socket_entry_t *entry)
@@ -170,25 +246,39 @@ static const char * const files[] = {
170
246
 
171
247
  static hook_func_entry_t functions[] = {
172
248
  {"read", (void*)hook_read, NULL},
249
+ #ifdef SUPPORT_TCP_KEEPALIVE_TIME
250
+ {"setsockopt", (void*)hook_setsockopt, NULL},
251
+ #endif
173
252
  {NULL, NULL, NULL},
174
253
  };
175
254
 
176
255
  static ssize_t hook_read(int fd, void *buf, size_t count)
177
256
  {
178
257
  socket_entry_t entry;
258
+ int enable_cancel = oci8_cancel_read_at_exit;
179
259
  ssize_t rv;
180
260
 
181
- socket_entry_set(&entry, fd);
261
+ if (enable_cancel > 0) {
262
+ socket_entry_set(&entry, fd);
263
+ }
182
264
  rv = read(fd, buf, count);
183
- socket_entry_clear(&entry);
265
+ if (enable_cancel > 0) {
266
+ socket_entry_clear(&entry);
267
+ }
184
268
  return rv;
185
269
  }
186
270
 
187
271
  void oci8_install_hook_functions(void)
188
272
  {
273
+ static int hook_functions_installed = 0;
274
+
275
+ if (hook_functions_installed) {
276
+ return;
277
+ }
189
278
  if (replace_functions(files, functions) != 0) {
190
279
  rb_raise(rb_eRuntimeError, "No shared library is found to hook.");
191
280
  }
281
+ hook_functions_installed = 1;
192
282
  }
193
283
 
194
284
  static void shutdown_socket(socket_entry_t *entry)
@@ -197,6 +287,41 @@ static void shutdown_socket(socket_entry_t *entry)
197
287
  }
198
288
  #endif
199
289
 
290
+ #ifdef SUPPORT_TCP_KEEPALIVE_TIME
291
+ static int hook_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen)
292
+ {
293
+ int rv = setsockopt(sockfd, level, optname, optval, optlen);
294
+
295
+ if (rv == 0 && level == SOL_SOCKET && optname == SO_KEEPALIVE
296
+ && optlen == sizeof(int) && *(const int*)optval != 0) {
297
+ /* If Oracle client libraries enables keepalive by (ENABLE=BROKEN),
298
+ * set per-connection keepalive socket options to overwrite
299
+ * system-wide setting.
300
+ */
301
+ if (oci8_tcp_keepalive_time > 0) {
302
+ #if defined(USE_TCP_KEEPALIVE) /* macOS */
303
+ setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, &oci8_tcp_keepalive_time, sizeof(int));
304
+ #elif defined(USE_TCP_KEEPALIVE_THRESHOLD) /* Solaris */
305
+ unsigned int millisec = oci8_tcp_keepalive_time * 1000;
306
+ setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, &millisec, sizeof(millisec));
307
+ #elif defined(USE_TCP_KEEPIDLE) /* Linux, etc */
308
+ setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &oci8_tcp_keepalive_time, sizeof(int));
309
+ #elif defined(WIN32) /* Windows */
310
+ struct tcp_keepalive vals;
311
+ DWORD dummy;
312
+
313
+ vals.onoff = 1;
314
+ vals.keepalivetime = oci8_tcp_keepalive_time * 1000;
315
+ vals.keepaliveinterval = keepalive_interval;
316
+ WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &vals, sizeof(vals), NULL, 0,
317
+ &dummy, NULL, NULL);
318
+ #endif
319
+ }
320
+ }
321
+ return rv;
322
+ }
323
+ #endif
324
+
200
325
  void oci8_shutdown_sockets(void)
201
326
  {
202
327
  socket_entry_t *entry;
@@ -286,7 +286,7 @@ static VALUE oci8_s_oracle_client_vernum(VALUE klass)
286
286
  /*
287
287
  * @overload OCI8.__get_prop(key)
288
288
  *
289
- * @param [Fixnum] key 1, 2 or 3
289
+ * @param [Integer] key 1, 2 or 3
290
290
  * @return [Object] depends on +key+.
291
291
  * @private
292
292
  */
@@ -306,16 +306,13 @@ static VALUE oci8_s_get_prop(VALUE klass, VALUE key)
306
306
  /*
307
307
  * @overload OCI8.__set_prop(key, value)
308
308
  *
309
- * @param [Fixnum] key 1, 2 or 3
309
+ * @param [Integer] key 1, 2 or 3
310
310
  * @param [Object] value depends on +key+.
311
311
  *
312
312
  * @private
313
313
  */
314
314
  static VALUE oci8_s_set_prop(VALUE klass, VALUE key, VALUE val)
315
315
  {
316
- #ifdef HAVE_PLTHOOK
317
- static int hook_functions_installed = 0;
318
- #endif
319
316
  switch (NUM2INT(key)) {
320
317
  case 1:
321
318
  oci8_float_conversion_type_is_ruby = RTEST(val) ? 1 : 0;
@@ -331,13 +328,21 @@ static VALUE oci8_s_set_prop(VALUE klass, VALUE key, VALUE val)
331
328
  oci8_env_mode = NUM2UINT(val);
332
329
  break;
333
330
  case 3:
331
+ if (oci8_cancel_read_at_exit == -1) {
332
+ rb_raise(rb_eNotImpError, "OCI8.properties[:cancel_read_at_exit] isn't available.");
333
+ }
334
334
  #ifdef HAVE_PLTHOOK
335
- if (!hook_functions_installed) {
336
- oci8_install_hook_functions();
337
- hook_functions_installed = 1;
335
+ oci8_install_hook_functions();
336
+ oci8_cancel_read_at_exit = RTEST(val) ? 1 : 0;
337
+ #endif
338
+ break;
339
+ case 4:
340
+ if (oci8_tcp_keepalive_time == -1) {
341
+ rb_raise(rb_eNotImpError, "OCI8.properties[:tcp_keepalive_time] isn't available.");
338
342
  }
339
- #else
340
- rb_raise(rb_eNotImpError, ":cancel_read_at_exit isn't implemented on this machine.");
343
+ #ifdef HAVE_PLTHOOK
344
+ oci8_install_hook_functions();
345
+ oci8_tcp_keepalive_time = NIL_P(val) ? 0 : NUM2INT(val);
341
346
  #endif
342
347
  break;
343
348
  default:
@@ -359,7 +364,7 @@ static VALUE oci8_s_set_prop(VALUE klass, VALUE key, VALUE val)
359
364
  * # When NLS_LANG is FRENCH_FRANCE.AL32UTF8
360
365
  * OCI8.error_message(1) # => "ORA-00001: violation de contrainte unique (%s.%s)"
361
366
  *
362
- * @param [Fixnum] message_no Oracle error message number
367
+ * @param [Integer] message_no Oracle error message number
363
368
  * @return [String] Oracle error message
364
369
  */
365
370
  static VALUE oci8_s_error_message(VALUE klass, VALUE msgid)
@@ -668,8 +673,8 @@ static VALUE oci8_server_attach(VALUE self, VALUE dbname, VALUE attach_mode)
668
673
  *
669
674
  * Begins the session by the OCI function OCISessionBegin().
670
675
  *
671
- * @param [Fixnum] cred
672
- * @param [Fixnum] mode
676
+ * @param [Integer] cred
677
+ * @param [Integer] mode
673
678
  * @private
674
679
  */
675
680
  static VALUE oci8_session_begin(VALUE self, VALUE cred, VALUE mode)
@@ -340,7 +340,8 @@ typedef struct {
340
340
  } oci8_exec_sql_var_t;
341
341
 
342
342
  #define oci8_raise(err, status, stmt) oci8_do_raise(err, status, stmt, __FILE__, __LINE__)
343
- #define oci8_env_raise(err, status) oci8_do_env_raise(err, status, __FILE__, __LINE__)
343
+ #define oci8_env_raise(env, status) oci8_do_env_raise(env, status, 0, __FILE__, __LINE__)
344
+ #define oci8_env_free_and_raise(env, status) oci8_do_env_raise(env, status, 1, __FILE__, __LINE__)
344
345
  #define oci8_raise_init_error() oci8_do_raise_init_error(__FILE__, __LINE__)
345
346
  #define oci8_raise_by_msgno(msgno, default_msg) oci8_do_raise_by_msgno(msgno, default_msg, __FILE__, __LINE__)
346
347
 
@@ -406,7 +407,7 @@ void *oci8_check_typeddata(VALUE obj, const oci8_handle_data_type_t *data_type,
406
407
  extern VALUE eOCIException;
407
408
  extern VALUE eOCIBreak;
408
409
  void Init_oci8_error(void);
409
- NORETURN(void oci8_do_env_raise(OCIEnv *, sword status, const char *file, int line));
410
+ NORETURN(void oci8_do_env_raise(OCIEnv *envhp, sword status, int free_envhp, const char *file, int line));
410
411
  NORETURN(void oci8_do_raise_init_error(const char *file, int line));
411
412
  sb4 oci8_get_error_code(OCIError *errhp);
412
413
  VALUE oci8_get_error_message(ub4 msgno, const char *default_msg);
@@ -514,6 +515,13 @@ void Init_oci8_win32(VALUE cOCI8);
514
515
  /* hook_funcs.c */
515
516
  void oci8_install_hook_functions(void);
516
517
  void oci8_shutdown_sockets(void);
518
+ #ifdef HAVE_PLTHOOK
519
+ extern int oci8_cancel_read_at_exit;
520
+ extern int oci8_tcp_keepalive_time;
521
+ #else
522
+ #define oci8_cancel_read_at_exit (-1)
523
+ #define oci8_tcp_keepalive_time (-1)
524
+ #endif
517
525
 
518
526
  #define OCI8StringValue(v) do { \
519
527
  StringValue(v); \
@@ -7,6 +7,10 @@
7
7
  #ifdef HAVE_RUBY_THREAD_H
8
8
  #include <ruby/thread.h>
9
9
  #endif
10
+ #if defined(HAVE_PLTHOOK) && !defined(WIN32)
11
+ #include <dlfcn.h>
12
+ #include "plthook.h"
13
+ #endif
10
14
 
11
15
  ID oci8_id_at_last_error;
12
16
  ID oci8_id_get;
@@ -67,6 +71,122 @@ static VALUE bind_base_alloc(VALUE klass)
67
71
  rb_raise(rb_eNameError, "private method `new' called for %s:Class", rb_class2name(klass));
68
72
  }
69
73
 
74
+ #if defined(HAVE_PLTHOOK) && !defined(WIN32)
75
+ static const char *find_libclntsh(void *handle)
76
+ {
77
+ void *symaddr = dlsym(handle, "OCIEnvCreate");
78
+ Dl_info info;
79
+ #ifdef __APPLE__
80
+ const char *basename = "libclntsh.dylib";
81
+ #else
82
+ const char *basename = "libclntsh.so";
83
+ #endif
84
+ const char *p;
85
+
86
+ if (symaddr == NULL) {
87
+ return NULL;
88
+ }
89
+ if (dladdr(symaddr, &info) == 0) {
90
+ return NULL;
91
+ }
92
+ if ((p = strrchr(info.dli_fname, '/')) == NULL) {
93
+ return NULL;
94
+ }
95
+ if (strncmp(p + 1, basename, strlen(basename)) != 0) {
96
+ return NULL;
97
+ }
98
+ return info.dli_fname;
99
+ }
100
+
101
+ /*
102
+ * Symbol prefix depends on the platform.
103
+ * Linux x86_64 - no prefix
104
+ * Linux x86_32 - "_"
105
+ * macOS - "@_"
106
+ */
107
+ static const char *find_symbol_prefix(plthook_t *ph, size_t *len)
108
+ {
109
+ unsigned int pos = 0;
110
+ const char *name;
111
+ void **addr;
112
+
113
+ while (plthook_enum(ph, &pos, &name, &addr) == 0) {
114
+ const char *p = strstr(name, "OCIEnvCreate");
115
+ if (p != NULL) {
116
+ *len = p - name;
117
+ return name;
118
+ }
119
+ }
120
+ return NULL;
121
+ }
122
+
123
+ /*
124
+ * Fix PLT entries against function interposition.
125
+ * See: http://www.rubydoc.info/github/kubo/ruby-oci8/file/docs/ldap-auth-and-function-interposition.md
126
+ */
127
+ static void rebind_internal_symbols(void)
128
+ {
129
+ const char *libfile;
130
+ void *handle;
131
+ int flags = RTLD_LAZY | RTLD_NOLOAD;
132
+ plthook_t *ph;
133
+ unsigned int pos = 0;
134
+ const char *name;
135
+ void **addr;
136
+ const char *prefix;
137
+ size_t prefix_len;
138
+
139
+ #ifdef RTLD_FIRST
140
+ flags |= RTLD_FIRST; /* for macOS */
141
+ #endif
142
+
143
+ libfile = find_libclntsh(RTLD_DEFAULT); /* normal case */
144
+ if (libfile == NULL) {
145
+ libfile = find_libclntsh(RTLD_NEXT); /* special case when OCIEnvCreate is hooked by LD_PRELOAD */
146
+ }
147
+ if (libfile == NULL) {
148
+ return;
149
+ }
150
+ handle = dlopen(libfile, flags);
151
+ if (handle == NULL) {
152
+ return;
153
+ }
154
+ if (plthook_open(&ph, libfile) != 0) {
155
+ dlclose(handle);
156
+ return;
157
+ }
158
+ prefix = find_symbol_prefix(ph, &prefix_len);
159
+ if (prefix == NULL) {
160
+ dlclose(handle);
161
+ plthook_close(ph);
162
+ return;
163
+ }
164
+ while (plthook_enum(ph, &pos, &name, &addr) == 0) {
165
+ void *funcaddr;
166
+ if (prefix_len != 0) {
167
+ if (strncmp(name, prefix, prefix_len) != 0) {
168
+ continue;
169
+ }
170
+ name += prefix_len;
171
+ }
172
+ if (strncmp(name, "OCI", 3) == 0) {
173
+ /* exclude functions starting with OCI not to prevent LD_PRELOAD hooking */
174
+ continue;
175
+ }
176
+ funcaddr = dlsym(handle, name);
177
+ if (funcaddr != NULL && *addr != funcaddr) {
178
+ /* If libclntsh.so exports and imports same functions, their
179
+ * PLT entries are forcedly modified to point to itself not
180
+ * to use functions in other libraries.
181
+ */
182
+ *addr = funcaddr;
183
+ }
184
+ }
185
+ plthook_close(ph);
186
+ dlclose(handle);
187
+ }
188
+ #endif
189
+
70
190
  #ifdef _WIN32
71
191
  __declspec(dllexport)
72
192
  #endif
@@ -74,7 +194,7 @@ void
74
194
  Init_oci8lib()
75
195
  {
76
196
  VALUE cOCI8;
77
- OCIEnv *envhp;
197
+ OCIEnv *envhp = NULL;
78
198
  OCIError *errhp;
79
199
  sword rv;
80
200
 
@@ -106,6 +226,9 @@ Init_oci8lib()
106
226
  oracle_client_version = ORAVERNUM(major, minor, update, patch, port_update);
107
227
  }
108
228
  #endif
229
+ #if defined(HAVE_PLTHOOK) && !defined(WIN32)
230
+ rebind_internal_symbols();
231
+ #endif
109
232
 
110
233
  oci8_id_at_last_error = rb_intern("@last_error");
111
234
  oci8_id_get = rb_intern("get");
@@ -153,7 +276,11 @@ Init_oci8lib()
153
276
  /* allocate a temporary errhp to pass Init_oci_number() */
154
277
  rv = OCIEnvCreate(&envhp, oci8_env_mode, NULL, NULL, NULL, NULL, 0, NULL);
155
278
  if (rv != OCI_SUCCESS) {
156
- oci8_raise_init_error();
279
+ if (envhp != NULL) {
280
+ oci8_env_free_and_raise(envhp, rv);
281
+ } else {
282
+ oci8_raise_init_error();
283
+ }
157
284
  }
158
285
  rv = OCIHandleAlloc(envhp, (dvoid *)&errhp, OCI_HTYPE_ERROR, 0, NULL);
159
286
  if (rv != OCI_SUCCESS)