Test Failed
Push — main ( 973aa1...436074 )
by Christian
02:37
created

godror.*dpiLobReader.read   F

Complexity

Conditions 36

Size

Total Lines 88
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 36
eloc 63
nop 1
dl 0
loc 88
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like godror.*dpiLobReader.read often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// Copyright 2017, 2022 The Godror Authors
2
//
3
//
4
// SPDX-License-Identifier: UPL-1.0 OR Apache-2.0
5
6
package godror
7
8
/*
9
#include "dpiImpl.h"
10
*/
11
import "C"
12
import (
13
	"bufio"
14
	"errors"
15
	"fmt"
16
	"io"
17
	"runtime"
18
	"strings"
19
	"sync"
20
	"unicode/utf8"
21
	"unsafe"
22
)
23
24
// Lob is for reading/writing a LOB.
25
type Lob struct {
26
	io.Reader
27
	IsClob bool
28
}
29
30
var _ = (io.Reader)((*Lob)(nil))
31
var _ = (io.ReaderAt)((*Lob)(nil))
32
33
// Hijack the underlying lob reader/writer, and
34
// return a DirectLob for reading/writing the lob directly.
35
//
36
// After this, the Lob is unusable!
37
func (lob *Lob) Hijack() (*DirectLob, error) {
38
	if lob == nil || lob.Reader == nil {
39
		return nil, errors.New("lob is nil")
40
	}
41
	lr, ok := lob.Reader.(*dpiLobReader)
42
	if !ok {
43
		return nil, fmt.Errorf("Lob.Reader is %T, not *dpiLobReader", lob.Reader)
44
	}
45
	lob.Reader = nil
46
	return &DirectLob{drv: lr.drv, dpiLob: lr.dpiLob}, nil
47
}
48
49
// WriteTo writes data to w until there's no more data to write or when an error occurs.
50
// The return value n is the number of bytes written. Any error encountered during the write is also returned.
51
//
52
// Will use Lob.Reader.WriteTo, if Lob.Reader implements io.WriterTo.
53
func (lob *Lob) WriteTo(w io.Writer) (n int64, err error) {
54
	if wt, ok := lob.Reader.(io.WriterTo); ok {
55
		return wt.WriteTo(w)
56
	}
57
	return io.CopyBuffer(w, lob.Reader, make([]byte, 1<<20))
58
}
59
60
// NewBufferedReader returns a new bufio.Reader with the given size (or 1M if 0).
61
func (lob *Lob) NewBufferedReader(size int) *bufio.Reader {
62
	if size <= 0 {
63
		size = 1 << 20
64
	}
65
	return bufio.NewReaderSize(lob.Reader, size)
66
}
67
68
// Size exposes the underlying Reader's Size method, if it is supported.
69
func (lob *Lob) Size() (int64, error) {
70
	if lr, ok := lob.Reader.(interface{ Size() (int64, error) }); ok {
71
		return lr.Size()
72
	}
73
	return 0, ErrNotSupported
74
}
75
76
// ReadAt exposes the underlying Reader's ReadAt method, if it is supported.
77
func (lob *Lob) ReadAt(p []byte, off int64) (int, error) {
78
	if lr, ok := lob.Reader.(io.ReaderAt); ok {
79
		return lr.ReadAt(p, off)
80
	}
81
	return 0, ErrNotSupported
82
}
83
84
// Scan assigns a value from a database driver.
85
//
86
// The src value will be of one of the following types:
87
//
88
//    int64
89
//    float64
90
//    bool
91
//    []byte
92
//    string
93
//    time.Time
94
//    nil - for NULL values
95
//
96
// An error should be returned if the value cannot be stored
97
// without loss of information.
98
func (dlr *dpiLobReader) Scan(src interface{}) error {
99
	b, ok := src.([]byte)
100
	if !ok {
101
		return fmt.Errorf("cannot convert LOB to %T", src)
102
	}
103
	_ = b
104
	return nil
105
}
106
107
var _ = io.ReadCloser((*dpiLobReader)(nil))
108
var _ = io.ReaderAt((*dpiLobReader)(nil))
109
110
type dpiLobReader struct {
111
	*drv
112
	dpiLob              *C.dpiLob
113
	buf                 []byte
114
	offset, sizePlusOne C.uint64_t
115
	mu                  sync.Mutex
116
	chunkSize           C.uint32_t
117
	bufR, bufW          int
118
	finished            bool
119
	IsClob              bool
120
}
121
122
// WriteTo writes data to w until there's no more data to write or when an error occurs.
123
// The return value n is the number of bytes written. Any error encountered during the write is also returned.
124
//
125
// Uses efficient, multiple-of-LOB-chunk-size buffered reads.
126
func (dlr *dpiLobReader) WriteTo(w io.Writer) (n int64, err error) {
127
	size := dlr.ChunkSize()
128
	const minBufferSize = 1 << 20
129
	if size <= 0 {
130
		size = minBufferSize
131
	} else {
132
		for size < minBufferSize/2 { // at most 1M
133
			size *= 2
134
		}
135
	}
136
	return io.CopyBuffer(
137
		w,
138
		// Mask WriteTo method
139
		io.Reader(struct {
140
			io.Reader
141
		}{dlr}),
142
		make([]byte, size))
143
}
144
145
// ChunkSize returns the LOB's native chunk size. Reads/writes with a multiply of this size is the most performant.
146
func (dlr *dpiLobReader) ChunkSize() int {
147
	dlr.mu.Lock()
148
	defer dlr.mu.Unlock()
149
	if dlr.chunkSize != 0 {
150
		return int(dlr.chunkSize)
151
	}
152
	if err := dlr.checkExec(func() C.int {
153
		return C.dpiLob_getChunkSize(dlr.dpiLob, &dlr.chunkSize)
154
	}); err != nil {
155
		dlr.chunkSize = 0
156
		return -1
157
	}
158
	return int(dlr.chunkSize)
159
}
160
161
// Read from LOB. It does buffer the reading internally against short buffers (io.ReadAll).
162
func (dlr *dpiLobReader) Read(p []byte) (int, error) {
163
	dlr.mu.Lock()
164
	defer dlr.mu.Unlock()
165
	logger := getLogger()
166
	if logger != nil {
167
		logger.Log("msg", "Read", "bufR", dlr.bufR, "bufW", dlr.bufW, "buf", cap(dlr.buf))
168
	}
169
	if dlr.buf == nil {
170
		if dlr.chunkSize == 0 {
171
			if err := dlr.checkExec(func() C.int {
172
				return C.dpiLob_getChunkSize(dlr.dpiLob, &dlr.chunkSize)
173
			}); err != nil {
174
				return 0, fmt.Errorf("getChunkSize: %w", err)
175
			}
176
		}
177
		// If the dest buffer is big enough, avoid copying.
178
		if ulen := C.uint64_t(len(p)); ulen >= C.uint64_t(dlr.chunkSize) || dlr.sizePlusOne != 0 && ulen+1 >= dlr.sizePlusOne {
179
			if logger != nil {
180
				logger.Log("msg", "direct read", "p", len(p), "chunkSize", dlr.chunkSize)
181
			}
182
			return dlr.read(p)
183
		}
184
		cs := int(dlr.chunkSize)
185
		dlr.buf = make([]byte, (maxI(len(p), 1<<20-cs)/cs+1)*cs)
186
		dlr.bufR, dlr.bufW = 0, 0
187
	} else if dlr.bufW != 0 && cap(dlr.buf) != 0 {
188
		var n int
189
		if dlr.bufR < dlr.bufW {
190
			n = copy(p, dlr.buf[dlr.bufR:dlr.bufW])
191
			dlr.bufR += n
192
		}
193
		if dlr.bufR == dlr.bufW {
194
			dlr.bufR, dlr.bufW = 0, 0
195
		}
196
		if n != 0 {
197
			return n, nil
198
		}
199
	}
200
	var err error
201
	// We only read into dlr.buf when it's empty, dlr.bufR == dlr.bufW == 0
202
	dlr.bufW, err = dlr.read(dlr.buf)
203
	if logger != nil {
204
		logger.Log("msg", "dlr.read", "bufR", dlr.bufR, "bufW", dlr.bufW, "chunkSize", dlr.chunkSize, "error", err)
205
	}
206
	dlr.bufR = copy(p, dlr.buf[:dlr.bufW])
207
	if err == io.EOF && dlr.bufW != dlr.bufR {
208
		err = nil
209
	}
210
	return dlr.bufR, err
211
}
212
213
var ErrCLOB = errors.New("CLOB is not supported")
214
215
// Size returns the LOB's size. It returns ErrCLOB for CLOB,
216
// (works only for BLOBs), as Oracle reports CLOB size in runes, not in bytes!
217
func (dlr *dpiLobReader) Size() (int64, error) {
218
	dlr.mu.Lock()
219
	err := dlr.getSize()
220
	size := dlr.sizePlusOne - 1
221
	isClob := dlr.IsClob
222
	dlr.mu.Unlock()
223
	if err == nil && isClob {
224
		err = ErrCLOB
225
	}
226
	return int64(size), err
227
}
228
func (dlr *dpiLobReader) getSize() error {
229
	if dlr.sizePlusOne != 0 {
230
		return nil
231
	}
232
	var err error
233
	runtime.LockOSThread()
234
	if err = dlr.checkExecNoLOT(func() C.int {
235
		return C.dpiLob_getSize(dlr.dpiLob, &dlr.sizePlusOne)
236
	}); err != nil {
237
		err = fmt.Errorf("getSize: %w", err)
238
		C.dpiLob_close(dlr.dpiLob)
239
		dlr.dpiLob = nil
240
	}
241
	runtime.UnlockOSThread()
242
	dlr.sizePlusOne++
243
	return err
244
}
245
246
// read does the real LOB reading.
247
func (dlr *dpiLobReader) read(p []byte) (int, error) {
248
	if dlr == nil {
249
		return 0, errors.New("read on nil dpiLobReader")
250
	}
251
	logger := getLogger()
252
	if logger != nil {
253
		logger.Log("msg", "LOB Read", "dlr", fmt.Sprintf("%p", dlr), "offset", dlr.offset, "size", dlr.sizePlusOne, "finished", dlr.finished, "clob", dlr.IsClob)
254
	}
255
	if dlr.finished || dlr.dpiLob == nil {
256
		return 0, io.EOF
257
	}
258
	if len(p) < 1 || dlr.IsClob && len(p) < 4 {
259
		return 0, io.ErrShortBuffer
260
	}
261
	runtime.LockOSThread()
262
	defer runtime.UnlockOSThread()
263
	// For CLOB, sizePlusOne and offset counts the CHARACTERS!
264
	// See https://oracle.github.io/odpi/doc/public_functions/dpiLob.html dpiLob_readBytes
265
	if dlr.sizePlusOne == 0 { // first read
266
		// never read size before
267
		if err := dlr.getSize(); err != nil {
268
			var coder interface{ Code() int }
269
			if errors.As(err, &coder) && coder.Code() == 22922 || strings.Contains(err.Error(), "invalid dpiLob handle") {
270
				return 0, io.EOF
271
			}
272
			return 0, err
273
		}
274
275
		var lobType C.dpiOracleTypeNum
276
		if err := dlr.checkExecNoLOT(func() C.int {
277
			return C.dpiLob_getType(dlr.dpiLob, &lobType)
278
		}); err == nil &&
279
			(2017 <= lobType && lobType <= 2019) {
280
			dlr.IsClob = lobType == 2017 || lobType == 2018 // CLOB and NCLOB
281
		}
282
	}
283
	n := C.uint64_t(len(p))
284
	amount := n
285
	if dlr.IsClob {
286
		amount /= 4 // dpiLob_readBytes' amount is the number of CHARACTERS for CLOBs.
287
	}
288
	// fmt.Printf("%p.Read offset=%d sizePlusOne=%d n=%d\n", dlr.dpiLob, dlr.offset, dlr.sizePlusOne, n)
289
	if logger != nil {
290
		logger.Log("msg", "Read", "offset", dlr.offset, "sizePlusOne", dlr.sizePlusOne, "n", n, "amount", amount)
291
	}
292
	if !dlr.IsClob && dlr.offset+1 >= dlr.sizePlusOne {
293
		if logger != nil {
294
			logger.Log("msg", "LOB reached end", "offset", dlr.offset, "size", dlr.sizePlusOne)
295
		}
296
		return 0, io.EOF
297
	}
298
	if err := dlr.drv.checkExecNoLOT(func() C.int {
299
		return C.dpiLob_readBytes(dlr.dpiLob, dlr.offset+1, amount, (*C.char)(unsafe.Pointer(&p[0])), &n)
300
	}); err != nil {
301
		if logger != nil {
302
			logger.Log("msg", "readBytes", "error", err)
303
		}
304
		C.dpiLob_close(dlr.dpiLob)
305
		dlr.dpiLob = nil
306
		if logger != nil {
307
			logger.Log("msg", "LOB read", "error", err)
308
		}
309
		var codeErr interface{ Code() int }
310
		if dlr.finished = errors.As(err, &codeErr) && codeErr.Code() == 1403; dlr.finished {
311
			dlr.offset += n
312
			return int(n), io.EOF
313
		}
314
		return int(n), fmt.Errorf("dpiLob_readbytes(lob=%p offset=%d n=%d): %w", dlr.dpiLob, dlr.offset, len(p), err)
315
	}
316
	if logger != nil {
317
		logger.Log("msg", "read", "n", n)
318
	}
319
	if dlr.IsClob {
320
		dlr.offset += C.uint64_t(utf8.RuneCount(p[:n]))
321
	} else {
322
		dlr.offset += n
323
	}
324
	var err error
325
	if amount != 0 && n == 0 || !dlr.IsClob && dlr.offset+1 >= dlr.sizePlusOne {
326
		C.dpiLob_close(dlr.dpiLob)
327
		dlr.dpiLob = nil
328
		dlr.finished = true
329
		err = io.EOF
330
	}
331
	if logger != nil {
332
		logger.Log("msg", "LOB", "n", n, "offset", dlr.offset, "size", dlr.sizePlusOne, "finished", dlr.finished, "clob", dlr.IsClob, "error", err)
333
	}
334
	return int(n), err
335
}
336
337
// ReadAt reads at the specified offset (in bytes).
338
// Works only for BLOBs!
339
func (dlr *dpiLobReader) ReadAt(p []byte, off int64) (int, error) {
340
	dlr.mu.Lock()
341
	defer dlr.mu.Unlock()
342
	if dlr.IsClob {
343
		return 0, ErrCLOB
344
	}
345
	n := C.uint64_t(len(p))
346
	err := dlr.checkExec(func() C.int {
347
		return C.dpiLob_readBytes(dlr.dpiLob, C.uint64_t(off+1), n, (*C.char)(unsafe.Pointer(&p[0])), &n)
348
	})
349
	if err != nil {
350
		err = fmt.Errorf("readBytes at %d for %d: %w", off, n, dlr.getError())
351
	}
352
	return int(n), err
353
}
354
func (dlr *dpiLobReader) Close() error {
355
	if dlr == nil || dlr.dpiLob == nil {
356
		return nil
357
	}
358
	lob := dlr.dpiLob
359
	dlr.dpiLob = nil
360
	return closeLob(dlr, lob)
361
}
362
363
type dpiLobWriter struct {
364
	*drv
365
	dpiLob *C.dpiLob
366
	offset C.uint64_t
367
	opened bool
368
	isClob bool
369
}
370
371
func (dlw *dpiLobWriter) Write(p []byte) (int, error) {
372
	runtime.LockOSThread()
373
	defer runtime.UnlockOSThread()
374
375
	lob := dlw.dpiLob
376
	if !dlw.opened {
377
		// fmt.Printf("open %p\n", lob)
378
		if err := dlw.drv.checkExecNoLOT(func() C.int {
379
			return C.dpiLob_openResource(lob)
380
		}); err != nil {
381
			return 0, fmt.Errorf("openResources(%p): %w", lob, err)
382
		}
383
		dlw.opened = true
384
	}
385
386
	n := C.uint64_t(len(p))
387
	if err := dlw.drv.checkExecNoLOT(func() C.int {
388
		return C.dpiLob_writeBytes(lob, dlw.offset+1, (*C.char)(unsafe.Pointer(&p[0])), n)
389
	}); err != nil {
390
		err = fmt.Errorf("writeBytes(%p, offset=%d, data=%d): %w", lob, dlw.offset, n, err)
391
		dlw.dpiLob = nil
392
		closeLob(dlw, lob)
393
		return 0, err
394
	}
395
	// fmt.Printf("written %q into %p@%d\n", p[:n], lob, dlw.offset)
396
	dlw.offset += n
397
398
	return int(n), nil
399
}
400
401
func (dlw *dpiLobWriter) Close() error {
402
	if dlw == nil || dlw.dpiLob == nil {
403
		return nil
404
	}
405
	lob := dlw.dpiLob
406
	dlw.dpiLob = nil
407
	return closeLob(dlw, lob)
408
}
409
410
func closeLob(d interface{ getError() error }, lob *C.dpiLob) error {
411
	if lob == nil {
412
		return nil
413
	}
414
415
	runtime.LockOSThread()
416
	defer runtime.UnlockOSThread()
417
418
	var isOpen C.int
419
	if C.dpiLob_getIsResourceOpen(lob, &isOpen) != C.DPI_FAILURE && isOpen == 1 {
420
		if C.dpiLob_closeResource(lob) == C.DPI_FAILURE {
421
			if err := d.getError(); err != nil {
422
				var codeErr interface{ Code() int }
423
				if errors.As(err, &codeErr) && codeErr.Code() != 22289 { // cannot perform %s operation on an unopened file or LOB
424
					return fmt.Errorf("closeResource(%p): %w", lob, err)
425
				}
426
			}
427
		}
428
	}
429
	C.dpiLob_release(lob)
430
	return nil
431
}
432
433
// DirectLob holds a Lob and allows direct (Read/WriteAt, not streaming Read/Write) operations on it.
434
type DirectLob struct {
435
	drv            *drv
436
	dpiLob         *C.dpiLob
437
	opened, isClob bool
438
}
439
440
var _ = io.ReaderAt((*DirectLob)(nil))
441
var _ = io.WriterAt((*DirectLob)(nil))
442
443
// NewTempLob returns a temporary LOB as DirectLob.
444
func (c *conn) NewTempLob(isClob bool) (*DirectLob, error) {
445
	typ := C.uint(C.DPI_ORACLE_TYPE_BLOB)
446
	if isClob {
447
		typ = C.DPI_ORACLE_TYPE_CLOB
448
	}
449
	lob := DirectLob{drv: c.drv, isClob: isClob}
450
	if err := c.checkExec(func() C.int { return C.dpiConn_newTempLob(c.dpiConn, typ, &lob.dpiLob) }); err != nil {
451
		return nil, fmt.Errorf("newTempLob: %w", err)
452
	}
453
	return &lob, nil
454
}
455
456
// Close the Lob.
457
func (dl *DirectLob) Close() error {
458
	if !dl.opened {
459
		return nil
460
	}
461
	lob := dl.dpiLob
462
	dl.opened, dl.dpiLob = false, nil
463
	return closeLob(dl.drv, lob)
464
}
465
466
// Size returns the size of the LOB.
467
//
468
// WARNING: for historical reasons, Oracle stores CLOBs and NCLOBs using the UTF-16 encoding,
469
// regardless of what encoding is otherwise in use by the database.
470
// The number of characters, however, is defined by the number of UCS-2 codepoints.
471
// For this reason, if a character requires more than one UCS-2 codepoint,
472
// the size returned will be inaccurate and care must be taken to account for the difference!
473
func (dl *DirectLob) Size() (int64, error) {
474
	var n C.uint64_t
475
	if dl.dpiLob == nil {
476
		return 0, nil
477
	}
478
	if err := dl.drv.checkExec(func() C.int {
479
		return C.dpiLob_getSize(dl.dpiLob, &n)
480
	}); err != nil {
481
		var coder interface{ Code() int }
482
		if errors.As(err, &coder) && coder.Code() == 22922 || strings.Contains(err.Error(), "invalid dpiLob handle") {
483
			return 0, nil
484
		}
485
		return int64(n), fmt.Errorf("getSize: %w", err)
486
	}
487
	return int64(n), nil
488
}
489
490
// Trim the LOB to the given size.
491
func (dl *DirectLob) Trim(size int64) error {
492
	if err := dl.drv.checkExec(func() C.int {
493
		return C.dpiLob_trim(dl.dpiLob, C.uint64_t(size))
494
	}); err != nil {
495
		return fmt.Errorf("trim: %w", err)
496
	}
497
	return nil
498
}
499
500
// Set the contents of the LOB to the given byte slice.
501
// The LOB is cleared first.
502
func (dl *DirectLob) Set(p []byte) error {
503
	if err := dl.drv.checkExec(func() C.int {
504
		return C.dpiLob_setFromBytes(dl.dpiLob, (*C.char)(unsafe.Pointer(&p[0])), C.uint64_t(len(p)))
505
	}); err != nil {
506
		return fmt.Errorf("setFromBytes: %w", err)
507
	}
508
	return nil
509
}
510
511
// ReadAt reads at most len(p) bytes into p at offset.
512
//
513
// CLOB's offset must be in amount of characters, and does not work reliably!
514
//
515
// WARNING: for historical reasons, Oracle stores CLOBs and NCLOBs using the UTF-16 encoding,
516
// regardless of what encoding is otherwise in use by the database.
517
// The number of characters, however, is defined by the number of UCS-2 codepoints.
518
// For this reason, if a character requires more than one UCS-2 codepoint,
519
// the size returned will be inaccurate and care must be taken to account for the difference!
520
func (dl *DirectLob) ReadAt(p []byte, offset int64) (int, error) {
521
	n := C.uint64_t(len(p))
522
	if dl.dpiLob == nil {
523
		return 0, io.EOF
524
	}
525
	amount := n
526
	if dl.isClob {
527
		amount /= 4
528
		if amount == 0 {
529
			return 0, io.ErrShortBuffer
530
		}
531
	}
532
	if err := dl.drv.checkExec(func() C.int {
533
		return C.dpiLob_readBytes(dl.dpiLob, C.uint64_t(offset)+1, n, (*C.char)(unsafe.Pointer(&p[0])), &n)
534
	}); err != nil {
535
		return int(n), fmt.Errorf("readBytes: %w", err)
536
	}
537
	return int(n), nil
538
}
539
540
// WriteAt writes p starting at offset.
541
func (dl *DirectLob) WriteAt(p []byte, offset int64) (int, error) {
542
	runtime.LockOSThread()
543
	defer runtime.UnlockOSThread()
544
545
	if !dl.opened {
546
		// fmt.Printf("open %p\n", lob)
547
		if err := dl.drv.checkExecNoLOT(func() C.int {
548
			return C.dpiLob_openResource(dl.dpiLob)
549
		}); err != nil {
550
			return 0, fmt.Errorf("openResources(%p): %w", dl.dpiLob, err)
551
		}
552
		dl.opened = true
553
	}
554
555
	n := C.uint64_t(len(p))
556
	if err := dl.drv.checkExecNoLOT(func() C.int {
557
		return C.dpiLob_writeBytes(dl.dpiLob, C.uint64_t(offset)+1, (*C.char)(unsafe.Pointer(&p[0])), n)
558
	}); err != nil {
559
		return int(n), fmt.Errorf("writeBytes: %w", err)
560
	}
561
	return int(n), nil
562
}
563
564
// GetFileName Return directory alias and file name for a BFILE type LOB.
565
func (dl *DirectLob) GetFileName() (dir, file string, err error) {
566
	var directoryAliasLength, fileNameLength C.uint32_t
567
	var directoryAlias, fileName *C.char
568
	if err := dl.drv.checkExec(func() C.int {
569
		return C.dpiLob_getDirectoryAndFileName(dl.dpiLob,
570
			&directoryAlias,
571
			&directoryAliasLength,
572
			&fileName,
573
			&fileNameLength,
574
		)
575
	}); err != nil {
576
		return dir, file, fmt.Errorf("GetFileName: %w", err)
577
	}
578
	dir = C.GoStringN(directoryAlias, C.int(directoryAliasLength))
579
	file = C.GoStringN(fileName, C.int(fileNameLength))
580
	return dir, file, nil
581
}
582
583
func maxI(a, b int) int {
584
	if a < b {
585
		return b
586
	}
587
	return a
588
}
589