ruby-oci8 2.2.0.2 → 2.2.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -6
  3. data/ChangeLog +600 -0
  4. data/NEWS +426 -35
  5. data/README.md +27 -9
  6. data/dist-files +13 -2
  7. data/docs/bind-array-to-in_cond.md +38 -0
  8. data/docs/conflicts-local-connections-and-processes.md +98 -0
  9. data/docs/hanging-after-inactivity.md +63 -0
  10. data/docs/install-binary-package.md +15 -11
  11. data/docs/install-full-client.md +18 -21
  12. data/docs/install-instant-client.md +45 -27
  13. data/docs/install-on-osx.md +31 -117
  14. data/docs/ldap-auth-and-function-interposition.md +123 -0
  15. data/docs/number-type-mapping.md +79 -0
  16. data/docs/platform-specific-issues.md +17 -50
  17. data/docs/report-installation-issue.md +11 -8
  18. data/docs/timeout-parameters.md +94 -0
  19. data/ext/oci8/apiwrap.c.tmpl +2 -5
  20. data/ext/oci8/apiwrap.rb +6 -1
  21. data/ext/oci8/apiwrap.yml +39 -143
  22. data/ext/oci8/attr.c +4 -2
  23. data/ext/oci8/bind.c +421 -9
  24. data/ext/oci8/connection_pool.c +3 -3
  25. data/ext/oci8/encoding.c +5 -5
  26. data/ext/oci8/env.c +8 -2
  27. data/ext/oci8/error.c +24 -16
  28. data/ext/oci8/extconf.rb +35 -63
  29. data/ext/oci8/hook_funcs.c +274 -61
  30. data/ext/oci8/lob.c +31 -75
  31. data/ext/oci8/metadata.c +8 -6
  32. data/ext/oci8/object.c +119 -29
  33. data/ext/oci8/oci8.c +46 -133
  34. data/ext/oci8/oci8.h +40 -123
  35. data/ext/oci8/oci8lib.c +178 -46
  36. data/ext/oci8/ocihandle.c +37 -37
  37. data/ext/oci8/ocinumber.c +24 -35
  38. data/ext/oci8/oraconf.rb +168 -337
  39. data/ext/oci8/oradate.c +19 -19
  40. data/ext/oci8/plthook.h +10 -0
  41. data/ext/oci8/plthook_elf.c +433 -268
  42. data/ext/oci8/plthook_osx.c +40 -9
  43. data/ext/oci8/plthook_win32.c +16 -1
  44. data/ext/oci8/stmt.c +52 -17
  45. data/ext/oci8/win32.c +4 -22
  46. data/lib/oci8/bindtype.rb +10 -17
  47. data/lib/oci8/check_load_error.rb +57 -10
  48. data/lib/oci8/compat.rb +5 -1
  49. data/lib/oci8/connection_pool.rb +74 -3
  50. data/lib/oci8/cursor.rb +70 -31
  51. data/lib/oci8/metadata.rb +9 -1
  52. data/lib/oci8/object.rb +14 -1
  53. data/lib/oci8/oci8.rb +184 -58
  54. data/lib/oci8/ocihandle.rb +0 -16
  55. data/lib/oci8/oracle_version.rb +11 -1
  56. data/lib/oci8/properties.rb +55 -0
  57. data/lib/oci8/version.rb +1 -1
  58. data/lib/oci8.rb +48 -4
  59. data/lib/ruby-oci8.rb +1 -0
  60. data/pre-distclean.rb +1 -3
  61. data/ruby-oci8.gemspec +4 -9
  62. data/setup.rb +11 -2
  63. data/test/README.md +37 -0
  64. data/test/config.rb +8 -1
  65. data/test/setup_test_object.sql +42 -14
  66. data/test/setup_test_package.sql +59 -0
  67. data/test/test_all.rb +4 -0
  68. data/test/test_bind_array.rb +70 -0
  69. data/test/test_bind_boolean.rb +99 -0
  70. data/test/test_bind_integer.rb +47 -0
  71. data/test/test_break.rb +11 -9
  72. data/test/test_clob.rb +5 -17
  73. data/test/test_connstr.rb +142 -0
  74. data/test/test_datetime.rb +8 -3
  75. data/test/test_metadata.rb +2 -1
  76. data/test/test_object.rb +99 -18
  77. data/test/test_oci8.rb +170 -46
  78. data/test/test_oranumber.rb +12 -6
  79. data/test/test_package_type.rb +17 -3
  80. data/test/test_properties.rb +17 -0
  81. metadata +45 -55
  82. data/docs/osx-install-dev-tools.png +0 -0
  83. data/test/README +0 -42
data/ext/oci8/extconf.rb CHANGED
@@ -25,77 +25,44 @@ $CFLAGS += oraconf.cflags
25
25
  saved_libs = $libs
26
26
  $libs += oraconf.libs
27
27
 
28
- oci_actual_client_version = 0x08000000
29
- funcs = {}
30
- YAML.load(open(File.dirname(__FILE__) + '/apiwrap.yml')).each do |key, val|
31
- key = key[0..-4] if key[-3..-1] == '_nb'
32
- ver = val[:version]
33
- ver_major = (ver / 100)
34
- ver_minor = (ver / 10) % 10
35
- ver_update = ver % 10
36
- ver = ((ver_major << 24) | (ver_minor << 20) | (ver_update << 12))
37
- funcs[ver] ||= []
38
- funcs[ver] << key
39
- end
40
-
41
28
  saved_defs = $defs.clone
42
- funcs.keys.sort.each do |version|
43
- next if version == 0x08000000
44
- verstr = format('%d.%d.%d', ((version >> 24) & 0xFF), ((version >> 20) & 0xF), ((version >> 12) & 0xFF))
45
- puts "checking for Oracle #{verstr} API - start"
46
- result = catch :result do
47
- funcs[version].sort.each do |func|
48
- unless have_func(func)
49
- throw :result, "fail"
50
- end
51
- end
52
- oci_actual_client_version = version
53
- "pass"
29
+ fmt = "%s"
30
+ def fmt.%(x)
31
+ x ? super : "failed"
32
+ end
33
+ oci_major_version = checking_for 'OCI_MAJOR_VERSION in oci.h', fmt do
34
+ try_constant('OCI_MAJOR_VERSION', 'oci.h')
35
+ end
36
+ if oci_major_version
37
+ oci_minor_version = checking_for 'OCI_MINOR_VERSION in oci.h', fmt do
38
+ try_constant('OCI_MINOR_VERSION', 'oci.h')
39
+ end
40
+ else
41
+ if have_func('OCILobGetLength2')
42
+ oci_major_version = 10
43
+ oci_minor_version = 1
44
+ elsif have_func('OCIStmtPrepare2')
45
+ raise "Ruby-oci8 #{OCI8::VERSION} doesn't support Oracle 9iR2. Use ruby-oci8 2.1.x instead."
46
+ elsif have_func('OCILogon2')
47
+ raise "Ruby-oci8 #{OCI8::VERSION} doesn't support Oracle 9iR1. Use ruby-oci8 2.1.x instead."
48
+ elsif have_func('OCIEnvCreate')
49
+ raise "Ruby-oci8 #{OCI8::VERSION} doesn't support Oracle 8i. Use ruby-oci8 2.0.x instead."
50
+ else
51
+ raise "Ruby-oci8 #{OCI8::VERSION} doesn't support Oracle 8. Use ruby-oci8 2.0.x instead."
54
52
  end
55
- puts "checking for Oracle #{verstr} API - #{result}"
56
- break if result == 'fail'
57
53
  end
58
54
  $defs = saved_defs
59
55
 
60
- have_type('oratext', 'ociap.h')
61
- have_type('OCIDateTime*', 'ociap.h')
62
- have_type('OCIInterval*', 'ociap.h')
63
- have_type('OCICallbackLobRead2', 'ociap.h')
64
- have_type('OCICallbackLobWrite2', 'ociap.h')
65
- have_type('OCIAdmin*', 'ociap.h')
66
- have_type('OCIAuthInfo*', 'ociap.h')
67
- have_type('OCIMsg*', 'ociap.h')
68
- have_type('OCICPool*', 'ociap.h')
69
-
70
56
  if with_config('oracle-version')
71
- oci_client_version = OCI8::OracleVersion.new(with_config('oracle-version')).to_i
57
+ oraver = OCI8::OracleVersion.new(with_config('oracle-version'))
72
58
  else
73
- oci_client_version = oci_actual_client_version
59
+ oraver = OCI8::OracleVersion.new(oci_major_version, oci_minor_version)
74
60
  end
75
- $defs << "-DORACLE_CLIENT_VERSION=#{format('0x%08x', oci_client_version)}"
61
+ $defs << "-DORACLE_CLIENT_VERSION=#{format('0x%08x', oraver.to_i)}"
76
62
 
77
63
  if with_config('runtime-check')
78
64
  $defs << "-DRUNTIME_API_CHECK=1"
79
65
  $libs = saved_libs
80
- else
81
- oraver = OCI8::OracleVersion.new(oci_client_version)
82
- if oraver < OCI8::OracleVersion.new(10)
83
- case "#{oraver.major}.#{oraver.minor}"
84
- when "8.0"
85
- ora_name = "Oracle 8"
86
- oci8_ver = "2.0.x"
87
- when "8.1"
88
- ora_name = "Oracle 8i"
89
- oci8_ver = "2.0.x"
90
- when "9.1"
91
- ora_name = "Oracle 9iR1"
92
- oci8_ver = "2.1.x"
93
- when "9.2"
94
- ora_name = "Oracle 9iR2"
95
- oci8_ver = "2.1.x"
96
- end
97
- raise "Ruby-oci8 #{OCI8::VERSION} doesn't support #{ora_name}. Use ruby-oci8 #{oci8_ver} instead."
98
- end
99
66
  end
100
67
 
101
68
  $objs = ["oci8lib.o", "env.o", "error.o", "oci8.o", "ocihandle.o",
@@ -105,7 +72,7 @@ $objs = ["oci8lib.o", "env.o", "error.o", "oci8.o", "ocihandle.o",
105
72
  "ocinumber.o", "ocidatetime.o", "object.o", "apiwrap.o",
106
73
  "encoding.o", "oranumber_util.o", "thread_util.o", "util.o"]
107
74
 
108
- if RUBY_PLATFORM =~ /mswin32|cygwin|mingw32|bccwin32/
75
+ if RUBY_PLATFORM =~ /mswin32|cygwin|mingw/
109
76
  $defs << "-DUSE_WIN32_C"
110
77
  $objs << "win32.o"
111
78
  end
@@ -172,6 +139,8 @@ when 'rbx'
172
139
  so_basename += 'rbx'
173
140
  when 'jruby'
174
141
  raise "Ruby-oci8 doesn't support jruby because its C extension support is in development in jruby 1.6 and deprecated in jruby 1.7."
142
+ when 'truffleruby'
143
+ so_basename += 'truffleruby'
175
144
  else
176
145
  raise 'unsupported ruby engine: ' + RUBY_ENGINE
177
146
  end
@@ -179,19 +148,22 @@ end
179
148
  print "checking for plthook... "
180
149
  STDOUT.flush
181
150
  case RUBY_PLATFORM
182
- when /mswin32|cygwin|mingw32|bccwin32/
151
+ when /mswin32|cygwin|mingw/
183
152
  plthook_src = "plthook_win32.c"
184
153
  when /darwin/
185
154
  plthook_src = "plthook_osx.c"
186
155
  else
187
156
  plthook_src = "plthook_elf.c"
188
157
  end
189
- if xsystem(cc_command("").gsub(CONFTEST_C, File.dirname(__FILE__) + "/" + plthook_src))
190
- File.delete(plthook_src.gsub(/\.c$/, '.' + RbConfig::CONFIG["OBJEXT"]))
158
+ FileUtils.copy(File.dirname(__FILE__) + "/" + plthook_src, CONFTEST_C)
159
+ if xsystem(cc_command(""))
160
+ FileUtils.rm_f("#{CONFTEST}.#{$OBJEXT}")
191
161
  puts plthook_src
192
162
  $objs << plthook_src.gsub(/\.c$/, '.o')
193
163
  $objs << "hook_funcs.o"
194
164
  $defs << "-DHAVE_PLTHOOK"
165
+ have_library('dbghelp', 'ImageDirectoryEntryToData', ['windows.h', 'dbghelp.h']) if RUBY_PLATFORM =~ /cygwin/
166
+ $libs += ' -lws2_32' if RUBY_PLATFORM =~ /cygwin/
195
167
  else
196
168
  puts "no"
197
169
  end
@@ -2,29 +2,69 @@
2
2
  /*
3
3
  * hook.c
4
4
  *
5
- * Copyright (C) 2015 Kubo Takehiro <kubo@jiubao.org>
5
+ * Copyright (C) 2015-2018 Kubo Takehiro <kubo@jiubao.org>
6
6
  */
7
+ #if defined(_WIN32) || defined(__CYGWIN__)
8
+ #define WINDOWS
9
+ #include <winsock2.h>
10
+ #endif
7
11
  #include "oci8.h"
8
12
  #include "plthook.h"
9
- #ifndef WIN32
13
+ #ifdef __CYGWIN__
14
+ #undef boolean /* boolean defined in oratypes.h coflicts with that in windows.h */
15
+ #define stricmp strcasecmp
16
+ #define strnicmp strncasecmp
17
+ #endif
18
+ #ifdef WINDOWS
19
+ #include <windows.h>
20
+ #include <mstcpip.h>
21
+ #include <tlhelp32.h>
22
+ #else
10
23
  #include <unistd.h>
11
24
  #include <sys/socket.h>
25
+ #include <netinet/in.h>
26
+ #include <netinet/tcp.h>
27
+ #include <dlfcn.h>
12
28
  #endif
13
29
 
14
- #define DEBUG_HOOK_FUNCS 1
15
-
16
- #ifdef WIN32
30
+ #ifdef WINDOWS
17
31
  static CRITICAL_SECTION lock;
18
32
  #define LOCK(lock) EnterCriticalSection(lock)
19
33
  #define UNLOCK(lock) LeaveCriticalSection(lock)
34
+ typedef int socklen_t;
20
35
  #else
21
36
  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
22
37
  #define LOCK(lock) pthread_mutex_lock(lock)
23
38
  #define UNLOCK(lock) pthread_mutex_unlock(lock)
24
39
  #define SOCKET int
25
40
  #define INVALID_SOCKET (-1)
41
+ #define WSAAPI
42
+ #endif
43
+
44
+ #if defined(__APPLE__) && defined(TCP_KEEPALIVE) /* macOS */
45
+ #define USE_TCP_KEEPALIVE
46
+ #define SUPPORT_TCP_KEEPALIVE_TIME
47
+ #elif defined(__sun) && defined(TCP_KEEPALIVE_THRESHOLD) /* Solaris */
48
+ #define USE_TCP_KEEPALIVE_THRESHOLD
49
+ #define SUPPORT_TCP_KEEPALIVE_TIME
50
+ #elif defined(TCP_KEEPIDLE) /* Linux, etc */
51
+ #define USE_TCP_KEEPIDLE
52
+ #define SUPPORT_TCP_KEEPALIVE_TIME
53
+ #elif defined(WINDOWS)
54
+ #define SUPPORT_TCP_KEEPALIVE_TIME
26
55
  #endif
27
56
 
57
+ int oci8_cancel_read_at_exit = 0;
58
+
59
+ #ifdef SUPPORT_TCP_KEEPALIVE_TIME
60
+ int oci8_tcp_keepalive_time = 0;
61
+ static int WSAAPI hook_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
62
+ #else
63
+ int oci8_tcp_keepalive_time = -1;
64
+ #endif
65
+
66
+ static char hook_errmsg[512];
67
+
28
68
  typedef struct {
29
69
  const char *func_name;
30
70
  void *func_addr;
@@ -60,62 +100,56 @@ static void socket_entry_clear(socket_entry_t *entry)
60
100
  UNLOCK(&lock);
61
101
  }
62
102
 
63
- static int replace_functions(const char * const *files, hook_func_entry_t *functions)
103
+ static int replace_functions(void *addr, const char *file, hook_func_entry_t *functions)
64
104
  {
105
+ plthook_t *ph;
65
106
  int i;
107
+ int rv = 0;
66
108
 
67
- for (i = 0; files[i] != NULL; i++) {
68
- const char *file = files[i];
69
- plthook_t *ph;
70
- if (plthook_open(&ph, file) == 0) {
71
- int j;
72
- int rv = 0;
73
-
74
- /* install hooks */
75
- for (j = 0; functions[j].func_name != NULL ; j++) {
76
- hook_func_entry_t *function = &functions[j];
77
- rv = plthook_replace(ph, function->func_name, function->func_addr, &function->old_func_addr);
78
- if (rv != 0) {
79
- while (--j >= 0) {
80
- /*restore hooked fuction address */
81
- plthook_replace(ph, functions[j].func_name, functions[j].old_func_addr, NULL);
82
- }
83
- plthook_close(ph);
84
- rb_raise(rb_eRuntimeError, "Could not replace function %s in %s", function->func_name, file);
85
- }
109
+ if (plthook_open_by_address(&ph, addr) != 0) {
110
+ strncpy(hook_errmsg, plthook_error(), sizeof(hook_errmsg) - 1);
111
+ hook_errmsg[sizeof(hook_errmsg) - 1] = '\0';
112
+ return -1;
113
+ }
114
+
115
+ /* install hooks */
116
+ for (i = 0; functions[i].func_name != NULL ; i++) {
117
+ hook_func_entry_t *function = &functions[i];
118
+ rv = plthook_replace(ph, function->func_name, function->func_addr, &function->old_func_addr);
119
+ if (rv != 0) {
120
+ strncpy(hook_errmsg, plthook_error(), sizeof(hook_errmsg) - 1);
121
+ hook_errmsg[sizeof(hook_errmsg) - 1] = '\0';
122
+ while (--i >= 0) {
123
+ /*restore hooked fuction address */
124
+ plthook_replace(ph, functions[i].func_name, functions[i].old_func_addr, NULL);
86
125
  }
87
- plthook_close(ph);
88
- return 0;
126
+ snprintf(hook_errmsg, sizeof(hook_errmsg), "Could not replace function %s in %s", function->func_name, file);
127
+ break;
89
128
  }
90
129
  }
91
- return -1;
130
+ plthook_close(ph);
131
+ return rv;
92
132
  }
93
133
 
94
- #ifdef WIN32
134
+ #ifdef WINDOWS
135
+
136
+ #ifndef _MSC_VER
137
+ /* setsockopt() in ws2_32.dll */
138
+ #define setsockopt rboci_setsockopt
139
+ typedef int (WSAAPI *setsockopt_t)(SOCKET, int, int, const void *, int);
140
+ static setsockopt_t setsockopt;
141
+ #endif
142
+
143
+ /* system-wide keepalive interval */
144
+ static DWORD keepalive_interval;
95
145
 
96
146
  static int locK_is_initialized;
97
147
 
98
148
  static int WSAAPI hook_WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
99
149
 
100
- static const char * const tcp_func_files[] = {
101
- /* full client */
102
- "orantcp12.dll",
103
- "orantcp11.dll",
104
- "orantcp10.dll",
105
- "orantcp9.dll",
106
- /* instant client basic */
107
- "oraociei12.dll",
108
- "oraociei11.dll",
109
- "oraociei10.dll",
110
- /* instant client basic lite */
111
- "oraociicus12.dll",
112
- "oraociicus11.dll",
113
- "oraociicus10.dll",
114
- NULL,
115
- };
116
-
117
150
  static hook_func_entry_t tcp_functions[] = {
118
151
  {"WSARecv", (void*)hook_WSARecv, NULL},
152
+ {"setsockopt", (void*)hook_setsockopt, NULL},
119
153
  {NULL, NULL, NULL},
120
154
  };
121
155
 
@@ -123,22 +157,112 @@ static hook_func_entry_t tcp_functions[] = {
123
157
  static int WSAAPI hook_WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
124
158
  {
125
159
  socket_entry_t entry;
160
+ int enable_cancel = oci8_cancel_read_at_exit;
126
161
  int rv;
127
162
 
128
- socket_entry_set(&entry, s);
163
+ if (enable_cancel > 0) {
164
+ socket_entry_set(&entry, s);
165
+ }
129
166
  rv = WSARecv(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine);
130
- socket_entry_clear(&entry);
167
+ if (enable_cancel > 0) {
168
+ socket_entry_clear(&entry);
169
+ }
131
170
  return rv;
132
171
  }
133
172
 
173
+ static int is_target_dll(MODULEENTRY32 *me)
174
+ {
175
+ static const char *basenames[] = {
176
+ "orantcp", // ORACLE_HOME-based client
177
+ "oraociei", // instant client basic
178
+ "oraociicus", // instant client basic lite
179
+ NULL,
180
+ };
181
+ const char **basename = basenames;
182
+ while (*basename != NULL) {
183
+ if (strnicmp(me->szModule, *basename, strlen(*basename)) == 0) {
184
+ break;
185
+ }
186
+ basename++;
187
+ }
188
+ if (*basename == NULL) {
189
+ return 0;
190
+ }
191
+ // basename{zero_or_more_digits}.dll
192
+ const char *p = me->szModule + strlen(*basename);
193
+ while ('0' <= *p && *p <= '9') {
194
+ p++;
195
+ }
196
+ if (stricmp(p, ".dll") != 0) {
197
+ return 0;
198
+ }
199
+ if (GetProcAddress((HMODULE)me->modBaseAddr, "nttini") == NULL) {
200
+ return 0;
201
+ }
202
+ return 1;
203
+ }
204
+
134
205
  void oci8_install_hook_functions()
135
206
  {
207
+ static int hook_functions_installed = 0;
208
+ HKEY hKey;
209
+ DWORD type;
210
+ DWORD data;
211
+ DWORD cbData = sizeof(data);
212
+ const char *reg_key = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
213
+ HANDLE hSnapshot;
214
+ MODULEENTRY32 me;
215
+ BOOL module_found = FALSE;
216
+
217
+ if (hook_functions_installed) {
218
+ return;
219
+ }
220
+
136
221
  InitializeCriticalSectionAndSpinCount(&lock, 5000);
137
222
  locK_is_initialized = 1;
138
223
 
139
- if (replace_functions(tcp_func_files, tcp_functions) != 0) {
224
+ #ifndef _MSC_VER
225
+ /* Get setsockopt in ws2_32.dll.
226
+ * setsockopt used by mingw compiler isn't same with that in ws2_32.dll.
227
+ */
228
+ setsockopt = (setsockopt_t)GetProcAddress(GetModuleHandleA("WS2_32.DLL"), "setsockopt");
229
+ if (setsockopt == NULL){
230
+ rb_raise(rb_eRuntimeError, "setsockopt isn't found in WS2_32.DLL");
231
+ }
232
+ #endif
233
+
234
+ /* Get system-wide keepalive interval parameter.
235
+ * https://technet.microsoft.com/en-us/library/cc957548.aspx
236
+ */
237
+ if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, reg_key, 0, KEY_QUERY_VALUE, &hKey) != 0) {
238
+ rb_raise(rb_eRuntimeError, "failed to open the registry key HKLM\\%s", reg_key);
239
+ }
240
+ keepalive_interval = 1000; /* default value when the following entry isn't found. */
241
+ if (RegQueryValueEx(hKey, "KeepAliveInterval", NULL, &type, (LPBYTE)&data, &cbData) == 0) {
242
+ if (type == REG_DWORD) {
243
+ keepalive_interval = data;
244
+ }
245
+ }
246
+ RegCloseKey(hKey);
247
+
248
+ hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
249
+ me.dwSize = sizeof(me);
250
+ if (Module32First(hSnapshot, &me)) {
251
+ do {
252
+ if (is_target_dll(&me)) {
253
+ module_found = TRUE;
254
+ if (replace_functions(me.modBaseAddr, me.szExePath, tcp_functions) != 0) {
255
+ CloseHandle(hSnapshot);
256
+ rb_raise(rb_eRuntimeError, "Hook error: %s", hook_errmsg);
257
+ }
258
+ }
259
+ } while (Module32Next(hSnapshot, &me));
260
+ }
261
+ CloseHandle(hSnapshot);
262
+ if (!module_found) {
140
263
  rb_raise(rb_eRuntimeError, "No DLL is found to hook.");
141
264
  }
265
+ hook_functions_installed = 1;
142
266
  }
143
267
 
144
268
  static void shutdown_socket(socket_entry_t *entry)
@@ -160,35 +284,89 @@ static ssize_t hook_read(int fd, void *buf, size_t count);
160
284
  #define SO_EXT "so"
161
285
  #endif
162
286
 
163
- static const char * const files[] = {
164
- "libclntsh." SO_EXT ".12.1",
165
- "libclntsh." SO_EXT ".11.1",
166
- "libclntsh." SO_EXT ".10.1",
167
- "libclntsh." SO_EXT ".9.0",
168
- NULL,
169
- };
170
-
171
287
  static hook_func_entry_t functions[] = {
172
288
  {"read", (void*)hook_read, NULL},
289
+ #ifdef SUPPORT_TCP_KEEPALIVE_TIME
290
+ {"setsockopt", (void*)hook_setsockopt, NULL},
291
+ #endif
173
292
  {NULL, NULL, NULL},
174
293
  };
175
294
 
176
295
  static ssize_t hook_read(int fd, void *buf, size_t count)
177
296
  {
178
297
  socket_entry_t entry;
298
+ int enable_cancel = oci8_cancel_read_at_exit;
179
299
  ssize_t rv;
180
300
 
181
- socket_entry_set(&entry, fd);
301
+ if (enable_cancel > 0) {
302
+ socket_entry_set(&entry, fd);
303
+ }
182
304
  rv = read(fd, buf, count);
183
- socket_entry_clear(&entry);
305
+ if (enable_cancel > 0) {
306
+ socket_entry_clear(&entry);
307
+ }
184
308
  return rv;
185
309
  }
186
310
 
311
+ static void *ocifunc_addr(void *dlsym_handle, const char **file)
312
+ {
313
+ void *addr = dlsym(dlsym_handle, "OCIEnvCreate");
314
+ Dl_info dli;
315
+
316
+ if (addr == NULL) {
317
+ return NULL;
318
+ }
319
+ if (dladdr(addr, &dli) == 0) {
320
+ return NULL;
321
+ }
322
+ if (strstr(dli.dli_fname, "/libclntsh." SO_EXT) == NULL) {
323
+ return NULL;
324
+ }
325
+ *file = dli.dli_fname;
326
+ return addr;
327
+ }
328
+
329
+ #ifdef __linux__
330
+ #include <link.h>
331
+ static void *ocifunc_addr_linux(const char **file)
332
+ {
333
+ struct link_map *lm;
334
+ for (lm = _r_debug.r_map; lm != NULL; lm = lm->l_next) {
335
+ if (strstr(lm->l_name, "/libclntsh." SO_EXT) != NULL) {
336
+ *file = lm->l_name;
337
+ return (void*)lm->l_addr;
338
+ }
339
+ }
340
+ return NULL;
341
+ }
342
+ #endif
343
+
187
344
  void oci8_install_hook_functions(void)
188
345
  {
189
- if (replace_functions(files, functions) != 0) {
346
+ static int hook_functions_installed = 0;
347
+ void *addr;
348
+ const char *file;
349
+
350
+ if (hook_functions_installed) {
351
+ return;
352
+ }
353
+ addr = ocifunc_addr(RTLD_DEFAULT, &file);
354
+ if (addr == NULL) {
355
+ /* OCI symbols may be hooked by LD_PRELOAD. */
356
+ addr = ocifunc_addr(RTLD_NEXT, &file);
357
+ }
358
+ #ifdef __linux__
359
+ if (addr == NULL) {
360
+ addr = ocifunc_addr_linux(&file);
361
+ }
362
+ #endif
363
+ if (addr == NULL) {
190
364
  rb_raise(rb_eRuntimeError, "No shared library is found to hook.");
191
365
  }
366
+ if (replace_functions(addr, file, functions) != 0) {
367
+ rb_raise(rb_eRuntimeError, "Hook error: %s", hook_errmsg);
368
+ }
369
+ hook_functions_installed = 1;
192
370
  }
193
371
 
194
372
  static void shutdown_socket(socket_entry_t *entry)
@@ -197,11 +375,46 @@ static void shutdown_socket(socket_entry_t *entry)
197
375
  }
198
376
  #endif
199
377
 
378
+ #ifdef SUPPORT_TCP_KEEPALIVE_TIME
379
+ static int WSAAPI hook_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen)
380
+ {
381
+ int rv = setsockopt(sockfd, level, optname, optval, optlen);
382
+
383
+ if (rv == 0 && level == SOL_SOCKET && optname == SO_KEEPALIVE
384
+ && optlen == sizeof(int) && *(const int*)optval != 0) {
385
+ /* If Oracle client libraries enables keepalive by (ENABLE=BROKEN),
386
+ * set per-connection keepalive socket options to overwrite
387
+ * system-wide setting.
388
+ */
389
+ if (oci8_tcp_keepalive_time > 0) {
390
+ #if defined(USE_TCP_KEEPALIVE) /* macOS */
391
+ setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, &oci8_tcp_keepalive_time, sizeof(int));
392
+ #elif defined(USE_TCP_KEEPALIVE_THRESHOLD) /* Solaris */
393
+ unsigned int millisec = oci8_tcp_keepalive_time * 1000;
394
+ setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, &millisec, sizeof(millisec));
395
+ #elif defined(USE_TCP_KEEPIDLE) /* Linux, etc */
396
+ setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &oci8_tcp_keepalive_time, sizeof(int));
397
+ #elif defined(WINDOWS)
398
+ struct tcp_keepalive vals;
399
+ DWORD dummy;
400
+
401
+ vals.onoff = 1;
402
+ vals.keepalivetime = oci8_tcp_keepalive_time * 1000;
403
+ vals.keepaliveinterval = keepalive_interval;
404
+ WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &vals, sizeof(vals), NULL, 0,
405
+ &dummy, NULL, NULL);
406
+ #endif
407
+ }
408
+ }
409
+ return rv;
410
+ }
411
+ #endif
412
+
200
413
  void oci8_shutdown_sockets(void)
201
414
  {
202
415
  socket_entry_t *entry;
203
416
 
204
- #ifdef WIN32
417
+ #ifdef WINDOWS
205
418
  if (!locK_is_initialized) {
206
419
  return;
207
420
  }