ruby-oci8 2.2.3 → 2.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)