ruby-staci 2.2.9

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 (115) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +14 -0
  3. data/COPYING +30 -0
  4. data/COPYING_old +64 -0
  5. data/ChangeLog +3826 -0
  6. data/Makefile +92 -0
  7. data/NEWS +1194 -0
  8. data/README.md +66 -0
  9. data/dist-files +113 -0
  10. data/docs/bind-array-to-in_cond.md +38 -0
  11. data/docs/conflicts-local-connections-and-processes.md +98 -0
  12. data/docs/hanging-after-inactivity.md +63 -0
  13. data/docs/install-binary-package.md +44 -0
  14. data/docs/install-full-client.md +111 -0
  15. data/docs/install-instant-client.md +194 -0
  16. data/docs/install-on-osx.md +133 -0
  17. data/docs/ldap-auth-and-function-interposition.md +123 -0
  18. data/docs/number-type-mapping.md +79 -0
  19. data/docs/osx-install-dev-tools.png +0 -0
  20. data/docs/platform-specific-issues.md +164 -0
  21. data/docs/report-installation-issue.md +50 -0
  22. data/docs/timeout-parameters.md +94 -0
  23. data/ext/oci8/.document +18 -0
  24. data/ext/oci8/MANIFEST +18 -0
  25. data/ext/oci8/apiwrap.c.tmpl +178 -0
  26. data/ext/oci8/apiwrap.h.tmpl +61 -0
  27. data/ext/oci8/apiwrap.rb +96 -0
  28. data/ext/oci8/apiwrap.yml +1322 -0
  29. data/ext/oci8/attr.c +57 -0
  30. data/ext/oci8/bind.c +838 -0
  31. data/ext/oci8/connection_pool.c +216 -0
  32. data/ext/oci8/encoding.c +196 -0
  33. data/ext/oci8/env.c +139 -0
  34. data/ext/oci8/error.c +385 -0
  35. data/ext/oci8/extconf.rb +219 -0
  36. data/ext/oci8/hook_funcs.c +407 -0
  37. data/ext/oci8/lob.c +1278 -0
  38. data/ext/oci8/metadata.c +279 -0
  39. data/ext/oci8/object.c +919 -0
  40. data/ext/oci8/oci8.c +1058 -0
  41. data/ext/oci8/oci8.h +556 -0
  42. data/ext/oci8/oci8lib.c +704 -0
  43. data/ext/oci8/ocidatetime.c +506 -0
  44. data/ext/oci8/ocihandle.c +852 -0
  45. data/ext/oci8/ocinumber.c +1922 -0
  46. data/ext/oci8/oraconf.rb +1145 -0
  47. data/ext/oci8/oradate.c +670 -0
  48. data/ext/oci8/oranumber_util.c +352 -0
  49. data/ext/oci8/oranumber_util.h +24 -0
  50. data/ext/oci8/plthook.h +66 -0
  51. data/ext/oci8/plthook_elf.c +702 -0
  52. data/ext/oci8/plthook_osx.c +505 -0
  53. data/ext/oci8/plthook_win32.c +391 -0
  54. data/ext/oci8/post-config.rb +5 -0
  55. data/ext/oci8/stmt.c +448 -0
  56. data/ext/oci8/thread_util.c +81 -0
  57. data/ext/oci8/thread_util.h +18 -0
  58. data/ext/oci8/util.c +71 -0
  59. data/ext/oci8/win32.c +117 -0
  60. data/lib/.document +1 -0
  61. data/lib/dbd/STACI.rb +591 -0
  62. data/lib/oci8/.document +8 -0
  63. data/lib/oci8/bindtype.rb +333 -0
  64. data/lib/oci8/check_load_error.rb +146 -0
  65. data/lib/oci8/compat.rb +117 -0
  66. data/lib/oci8/connection_pool.rb +179 -0
  67. data/lib/oci8/cursor.rb +605 -0
  68. data/lib/oci8/datetime.rb +605 -0
  69. data/lib/oci8/encoding-init.rb +45 -0
  70. data/lib/oci8/encoding.yml +537 -0
  71. data/lib/oci8/metadata.rb +2148 -0
  72. data/lib/oci8/object.rb +641 -0
  73. data/lib/oci8/oci8.rb +756 -0
  74. data/lib/oci8/ocihandle.rb +591 -0
  75. data/lib/oci8/oracle_version.rb +153 -0
  76. data/lib/oci8/properties.rb +196 -0
  77. data/lib/oci8/version.rb +3 -0
  78. data/lib/ruby-staci.rb +1 -0
  79. data/lib/staci.rb +190 -0
  80. data/metaconfig +142 -0
  81. data/pre-distclean.rb +7 -0
  82. data/ruby-aci.gemspec +83 -0
  83. data/setup.rb +1342 -0
  84. data/test/README.md +37 -0
  85. data/test/config.rb +201 -0
  86. data/test/setup_test_object.sql +199 -0
  87. data/test/setup_test_package.sql +59 -0
  88. data/test/test_all.rb +56 -0
  89. data/test/test_appinfo.rb +62 -0
  90. data/test/test_array_dml.rb +333 -0
  91. data/test/test_bind_array.rb +70 -0
  92. data/test/test_bind_boolean.rb +99 -0
  93. data/test/test_bind_integer.rb +47 -0
  94. data/test/test_bind_raw.rb +45 -0
  95. data/test/test_bind_string.rb +105 -0
  96. data/test/test_bind_time.rb +177 -0
  97. data/test/test_break.rb +124 -0
  98. data/test/test_clob.rb +86 -0
  99. data/test/test_connection_pool.rb +124 -0
  100. data/test/test_connstr.rb +220 -0
  101. data/test/test_datetime.rb +585 -0
  102. data/test/test_dbi.rb +365 -0
  103. data/test/test_dbi_clob.rb +53 -0
  104. data/test/test_encoding.rb +103 -0
  105. data/test/test_error.rb +87 -0
  106. data/test/test_metadata.rb +2674 -0
  107. data/test/test_object.rb +546 -0
  108. data/test/test_oci8.rb +624 -0
  109. data/test/test_oracle_version.rb +68 -0
  110. data/test/test_oradate.rb +255 -0
  111. data/test/test_oranumber.rb +786 -0
  112. data/test/test_package_type.rb +981 -0
  113. data/test/test_properties.rb +17 -0
  114. data/test/test_rowid.rb +32 -0
  115. metadata +158 -0
@@ -0,0 +1,391 @@
1
+ /* -*- indent-tabs-mode: nil -*-
2
+ *
3
+ * plthook_win32.c -- implemention of plthook for PE format
4
+ *
5
+ * URL: https://github.com/kubo/plthook
6
+ *
7
+ * ------------------------------------------------------
8
+ *
9
+ * Copyright 2013-2014 Kubo Takehiro <kubo@jiubao.org>
10
+ *
11
+ * Redistribution and use in source and binary forms, with or without modification, are
12
+ * permitted provided that the following conditions are met:
13
+ *
14
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
15
+ * conditions and the following disclaimer.
16
+ *
17
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
18
+ * of conditions and the following disclaimer in the documentation and/or other materials
19
+ * provided with the distribution.
20
+ *
21
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED
22
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
23
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
24
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ *
31
+ * The views and conclusions contained in the software and documentation are those of the
32
+ * authors and should not be interpreted as representing official policies, either expressed
33
+ * or implied, of the authors.
34
+ *
35
+ */
36
+ #include <stdio.h>
37
+ #include <stddef.h>
38
+ #include <stdarg.h>
39
+ #include <windows.h>
40
+ #include <dbghelp.h>
41
+ #include "plthook.h"
42
+
43
+ #ifdef _MSC_VER
44
+ #pragma comment(lib, "dbghelp.lib")
45
+ #endif
46
+
47
+ #ifndef _Printf_format_string_
48
+ #define _Printf_format_string_
49
+ #endif
50
+ #ifndef __GNUC__
51
+ #define __attribute__(arg)
52
+ #endif
53
+
54
+ #if defined _LP64 /* data model: I32/LP64 */
55
+ #define SIZE_T_FMT "lu"
56
+ #elif defined _WIN64 /* data model: IL32/P64 */
57
+ #define SIZE_T_FMT "I64u"
58
+ #else
59
+ #define SIZE_T_FMT "u"
60
+ #endif
61
+
62
+ #ifdef __CYGWIN__
63
+ #define stricmp strcasecmp
64
+ #endif
65
+
66
+ typedef struct {
67
+ const char *mod_name;
68
+ const char *name;
69
+ void **addr;
70
+ } import_address_entry_t;
71
+
72
+ struct plthook {
73
+ HMODULE hMod;
74
+ unsigned int num_entries;
75
+ import_address_entry_t entries[1];
76
+ };
77
+
78
+ static char errbuf[512];
79
+ static int plthook_open_real(plthook_t **plthook_out, HMODULE hMod);
80
+ static void set_errmsg(_Printf_format_string_ const char *fmt, ...) __attribute__((__format__ (__printf__, 1, 2)));
81
+ static void set_errmsg2(_Printf_format_string_ const char *fmt, ...) __attribute__((__format__ (__printf__, 1, 2)));
82
+ static const char *winsock2_ordinal2name(int ordinal);
83
+
84
+ int plthook_open(plthook_t **plthook_out, const char *filename)
85
+ {
86
+ HMODULE hMod;
87
+
88
+ *plthook_out = NULL;
89
+ if (!GetModuleHandleExA(0, filename, &hMod)) {
90
+ set_errmsg2("Cannot get module %s: ", filename);
91
+ return PLTHOOK_FILE_NOT_FOUND;
92
+ }
93
+ return plthook_open_real(plthook_out, hMod);
94
+ }
95
+
96
+ int plthook_open_by_handle(plthook_t **plthook_out, void *hndl)
97
+ {
98
+ if (hndl == NULL) {
99
+ set_errmsg("NULL handle");
100
+ return PLTHOOK_FILE_NOT_FOUND;
101
+ }
102
+ return plthook_open_real(plthook_out, (HMODULE)hndl);
103
+ }
104
+
105
+ int plthook_open_by_address(plthook_t **plthook_out, void *address)
106
+ {
107
+ HMODULE hMod;
108
+
109
+ *plthook_out = NULL;
110
+ if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, address, &hMod)) {
111
+ set_errmsg2("Cannot get module at address %p: ", address);
112
+ return PLTHOOK_FILE_NOT_FOUND;
113
+ }
114
+ return plthook_open_real(plthook_out, hMod);
115
+ }
116
+
117
+ static int plthook_open_real(plthook_t **plthook_out, HMODULE hMod)
118
+ {
119
+ plthook_t *plthook;
120
+ ULONG ulSize;
121
+ IMAGE_IMPORT_DESCRIPTOR *desc_head, *desc;
122
+ size_t num_entries = 0;
123
+ size_t ordinal_name_buflen = 0;
124
+ size_t idx;
125
+ char *ordinal_name_buf;
126
+
127
+ desc_head = (IMAGE_IMPORT_DESCRIPTOR*)ImageDirectoryEntryToData(hMod, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
128
+ if (desc_head == NULL) {
129
+ set_errmsg2("ImageDirectoryEntryToData error: ");
130
+ return PLTHOOK_INTERNAL_ERROR;
131
+ }
132
+
133
+ /* Calculate size to allocate memory. */
134
+ for (desc = desc_head; desc->Name != 0; desc++) {
135
+ IMAGE_THUNK_DATA *name_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->OriginalFirstThunk);
136
+ IMAGE_THUNK_DATA *addr_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->FirstThunk);
137
+ int is_winsock2_dll = (stricmp((char *)hMod + desc->Name, "WS2_32.DLL") == 0);
138
+
139
+ while (addr_thunk->u1.Function != 0) {
140
+ if (IMAGE_SNAP_BY_ORDINAL(name_thunk->u1.Ordinal)) {
141
+ int ordinal = IMAGE_ORDINAL(name_thunk->u1.Ordinal);
142
+ const char *name = NULL;
143
+ if (is_winsock2_dll) {
144
+ name = winsock2_ordinal2name(ordinal);
145
+ }
146
+ if (name == NULL) {
147
+ char buf[64];
148
+ ordinal_name_buflen += sprintf(buf, "#%d", ordinal) + 1;
149
+ }
150
+ }
151
+ num_entries++;
152
+ name_thunk++;
153
+ addr_thunk++;
154
+ }
155
+ }
156
+
157
+ plthook = calloc(1, offsetof(plthook_t, entries) + sizeof(import_address_entry_t) * num_entries + ordinal_name_buflen);
158
+ if (plthook == NULL) {
159
+ set_errmsg("failed to allocate memory: %" SIZE_T_FMT " bytes", sizeof(plthook_t));
160
+ return PLTHOOK_OUT_OF_MEMORY;
161
+ }
162
+ plthook->hMod = hMod;
163
+ plthook->num_entries = num_entries;
164
+
165
+ ordinal_name_buf = (char*)plthook + offsetof(plthook_t, entries) + sizeof(import_address_entry_t) * num_entries;
166
+ idx = 0;
167
+ for (desc = desc_head; desc->Name != 0; desc++) {
168
+ IMAGE_THUNK_DATA *name_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->OriginalFirstThunk);
169
+ IMAGE_THUNK_DATA *addr_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->FirstThunk);
170
+ int is_winsock2_dll = (stricmp((char *)hMod + desc->Name, "WS2_32.DLL") == 0);
171
+
172
+ while (addr_thunk->u1.Function != 0) {
173
+ const char *name = NULL;
174
+
175
+ if (IMAGE_SNAP_BY_ORDINAL(name_thunk->u1.Ordinal)) {
176
+ int ordinal = IMAGE_ORDINAL(name_thunk->u1.Ordinal);
177
+ if (is_winsock2_dll) {
178
+ name = winsock2_ordinal2name(ordinal);
179
+ }
180
+ if (name == NULL) {
181
+ ordinal_name_buf += sprintf(ordinal_name_buf, "#%d", ordinal) + 1;
182
+ }
183
+ } else {
184
+ name = (char*)((PIMAGE_IMPORT_BY_NAME)((char*)hMod + name_thunk->u1.AddressOfData))->Name;
185
+ }
186
+ plthook->entries[idx].mod_name = (char *)hMod + desc->Name;
187
+ plthook->entries[idx].name = name;
188
+ plthook->entries[idx].addr = (void**)&addr_thunk->u1.Function;
189
+ idx++;
190
+ name_thunk++;
191
+ addr_thunk++;
192
+ }
193
+ }
194
+
195
+ *plthook_out = plthook;
196
+ return 0;
197
+ }
198
+
199
+ int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out)
200
+ {
201
+ if (*pos >= plthook->num_entries) {
202
+ *name_out = NULL;
203
+ *addr_out = NULL;
204
+ return EOF;
205
+ }
206
+ *name_out = plthook->entries[*pos].name;
207
+ *addr_out = plthook->entries[*pos].addr;
208
+ (*pos)++;
209
+ return 0;
210
+ }
211
+
212
+ static void replace_funcaddr(void **addr, void *newfunc, void **oldfunc)
213
+ {
214
+ DWORD dwOld;
215
+ DWORD dwDummy;
216
+
217
+ if (oldfunc != NULL) {
218
+ *oldfunc = *addr;
219
+ }
220
+ VirtualProtect(addr, sizeof(void *), PAGE_EXECUTE_READWRITE, &dwOld);
221
+ *addr = newfunc;
222
+ VirtualProtect(addr, sizeof(void *), dwOld, &dwDummy);
223
+ }
224
+
225
+ int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc)
226
+ {
227
+ #ifndef _WIN64
228
+ size_t funcnamelen = strlen(funcname);
229
+ #endif
230
+ unsigned int pos = 0;
231
+ const char *name;
232
+ void **addr;
233
+ int rv;
234
+
235
+ if (plthook == NULL) {
236
+ set_errmsg("invalid argument: The first argument is null.");
237
+ return PLTHOOK_INVALID_ARGUMENT;
238
+ }
239
+ while ((rv = plthook_enum(plthook, &pos, &name, &addr)) == 0) {
240
+ #ifdef _WIN64
241
+ if (strcmp(name, funcname) == 0) {
242
+ replace_funcaddr(addr, funcaddr, oldfunc);
243
+ return 0;
244
+ }
245
+ #else
246
+ /* Function names may be decorated in Windows 32-bit applications. */
247
+ if (strncmp(name, funcname, funcnamelen) == 0) {
248
+ if (name[funcnamelen] == '\0' || name[funcnamelen] == '@') {
249
+ replace_funcaddr(addr, funcaddr, oldfunc);
250
+ return 0;
251
+ }
252
+ }
253
+ if (name[0] == '_' || name[0] == '@') {
254
+ name++;
255
+ if (strncmp(name, funcname, funcnamelen) == 0) {
256
+ if (name[funcnamelen] == '\0' || name[funcnamelen] == '@') {
257
+ replace_funcaddr(addr, funcaddr, oldfunc);
258
+ return 0;
259
+ }
260
+ }
261
+ }
262
+ #endif
263
+ }
264
+ if (rv == EOF) {
265
+ set_errmsg("no such function: %s", funcname);
266
+ rv = PLTHOOK_FUNCTION_NOT_FOUND;
267
+ }
268
+ return rv;
269
+ }
270
+
271
+ void plthook_close(plthook_t *plthook)
272
+ {
273
+ if (plthook != NULL) {
274
+ free(plthook);
275
+ }
276
+ }
277
+
278
+ const char *plthook_error(void)
279
+ {
280
+ return errbuf;
281
+ }
282
+
283
+ static void set_errmsg(const char *fmt, ...)
284
+ {
285
+ va_list ap;
286
+ va_start(ap, fmt);
287
+ vsnprintf(errbuf, sizeof(errbuf) - 1, fmt, ap);
288
+ va_end(ap);
289
+ }
290
+
291
+ static void set_errmsg2(const char *fmt, ...)
292
+ {
293
+ va_list ap;
294
+ size_t len;
295
+
296
+ va_start(ap, fmt);
297
+ vsnprintf(errbuf, sizeof(errbuf) - 1, fmt, ap);
298
+ va_end(ap);
299
+ len = strlen(errbuf);
300
+ FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_IGNORE_INSERTS,
301
+ NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
302
+ errbuf + len, sizeof(errbuf) - len - 1, NULL);
303
+ }
304
+
305
+ static const char *winsock2_ordinal2name(int ordinal)
306
+ {
307
+ switch (ordinal) {
308
+ case 1: return "accept";
309
+ case 2: return "bind";
310
+ case 3: return "closesocket";
311
+ case 4: return "connect";
312
+ case 5: return "getpeername";
313
+ case 6: return "getsockname";
314
+ case 7: return "getsockopt";
315
+ case 8: return "htonl";
316
+ case 9: return "htons";
317
+ case 10: return "inet_addr";
318
+ case 11: return "inet_ntoa";
319
+ case 12: return "ioctlsocket";
320
+ case 13: return "listen";
321
+ case 14: return "ntohl";
322
+ case 15: return "ntohs";
323
+ case 16: return "recv";
324
+ case 17: return "recvfrom";
325
+ case 18: return "select";
326
+ case 19: return "send";
327
+ case 20: return "sendto";
328
+ case 21: return "setsockopt";
329
+ case 22: return "shutdown";
330
+ case 23: return "socket";
331
+ case 24: return "MigrateWinsockConfiguration";
332
+ case 51: return "gethostbyaddr";
333
+ case 52: return "gethostbyname";
334
+ case 53: return "getprotobyname";
335
+ case 54: return "getprotobynumber";
336
+ case 55: return "getservbyname";
337
+ case 56: return "getservbyport";
338
+ case 57: return "gethostname";
339
+ case 101: return "WSAAsyncSelect";
340
+ case 102: return "WSAAsyncGetHostByAddr";
341
+ case 103: return "WSAAsyncGetHostByName";
342
+ case 104: return "WSAAsyncGetProtoByNumber";
343
+ case 105: return "WSAAsyncGetProtoByName";
344
+ case 106: return "WSAAsyncGetServByPort";
345
+ case 107: return "WSAAsyncGetServByName";
346
+ case 108: return "WSACancelAsyncRequest";
347
+ case 109: return "WSASetBlockingHook";
348
+ case 110: return "WSAUnhookBlockingHook";
349
+ case 111: return "WSAGetLastError";
350
+ case 112: return "WSASetLastError";
351
+ case 113: return "WSACancelBlockingCall";
352
+ case 114: return "WSAIsBlocking";
353
+ case 115: return "WSAStartup";
354
+ case 116: return "WSACleanup";
355
+ case 151: return "__WSAFDIsSet";
356
+ case 500: return "WEP";
357
+ case 1000: return "WSApSetPostRoutine";
358
+ case 1001: return "WsControl";
359
+ case 1002: return "closesockinfo";
360
+ case 1003: return "Arecv";
361
+ case 1004: return "Asend";
362
+ case 1005: return "WSHEnumProtocols";
363
+ case 1100: return "inet_network";
364
+ case 1101: return "getnetbyname";
365
+ case 1102: return "rcmd";
366
+ case 1103: return "rexec";
367
+ case 1104: return "rresvport";
368
+ case 1105: return "sethostname";
369
+ case 1106: return "dn_expand";
370
+ case 1107: return "WSARecvEx";
371
+ case 1108: return "s_perror";
372
+ case 1109: return "GetAddressByNameA";
373
+ case 1110: return "GetAddressByNameW";
374
+ case 1111: return "EnumProtocolsA";
375
+ case 1112: return "EnumProtocolsW";
376
+ case 1113: return "GetTypeByNameA";
377
+ case 1114: return "GetTypeByNameW";
378
+ case 1115: return "GetNameByTypeA";
379
+ case 1116: return "GetNameByTypeW";
380
+ case 1117: return "SetServiceA";
381
+ case 1118: return "SetServiceW";
382
+ case 1119: return "GetServiceA";
383
+ case 1120: return "GetServiceW";
384
+ case 1130: return "NPLoadNameSpaces";
385
+ case 1131: return "NSPStartup";
386
+ case 1140: return "TransmitFile";
387
+ case 1141: return "AcceptEx";
388
+ case 1142: return "GetAcceptExSockaddrs";
389
+ }
390
+ return NULL;
391
+ }
@@ -0,0 +1,5 @@
1
+ File.foreach("#{curr_objdir}/extconf.h") do |line|
2
+ if /^#define OCI8_CLIENT_VERSION "(...)"/ =~ line
3
+ set_config('oracle_version', $1)
4
+ end
5
+ end