ruby-oci8 2.2.0.2 → 2.2.12

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.
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
  }