mmapper 1.0.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/ext/extconf.rb +1 -27
- data/ext/mmapper.c +98 -0
- data/lib/mmapper.rb +47 -31
- metadata +7 -21
- data/ext/mmap.go +0 -127
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bd38094ddc8cb154aea8cded45f5775c2b4bc873dbf037c805230635332ee23
|
4
|
+
data.tar.gz: c19ed16231032e3e11e82723aed9fca20c8d0d51b2a0c5054ec4b8ddf88ceffe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d485802f03e104bbfaccd82c2b7e7efa0ee332dd95f41962b1a41558f4fcbe5484c8c0c50ff583d84e1e92a535ae1f51740c53bd0207b98645153b60ad8f094
|
7
|
+
data.tar.gz: e520f6a7f6ca6f718fa02093563a7752f5afd2e15525f63fbfbd2d4fb065ced6539c133e25ab6887aac5863169118eb8fa6e9a98bc66d174dc728c28cd712286
|
data/ext/extconf.rb
CHANGED
@@ -1,29 +1,3 @@
|
|
1
1
|
require 'mkmf'
|
2
|
-
require 'fileutils'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
# Detect OS and set correct library name
|
7
|
-
LIB_NAME =
|
8
|
-
case RUBY_PLATFORM
|
9
|
-
when /darwin/ then "libmmapper.dylib"
|
10
|
-
when /mingw|mswin/ then "mmapper.dll"
|
11
|
-
else "libmmapper.so"
|
12
|
-
end
|
13
|
-
|
14
|
-
LIB_PATH = File.join(LIB_DIR, LIB_NAME)
|
15
|
-
|
16
|
-
# Ensure Go module is initialized
|
17
|
-
Dir.chdir(LIB_DIR) do
|
18
|
-
unless File.exist?("go.mod")
|
19
|
-
puts "Initializing Go module..."
|
20
|
-
system("go mod init mmapper") || raise("Failed to initialize Go module")
|
21
|
-
end
|
22
|
-
|
23
|
-
puts "Building Go shared library for #{RUBY_PLATFORM}..."
|
24
|
-
unless system("go build -o #{LIB_NAME} -buildmode=c-shared .")
|
25
|
-
raise "Go build failed!"
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
create_makefile('mmapper')
|
3
|
+
create_makefile('mmapper/mmapper')
|
data/ext/mmapper.c
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
#include <fcntl.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <string.h>
|
6
|
+
#include <sys/mman.h>
|
7
|
+
#include <unistd.h>
|
8
|
+
|
9
|
+
typedef struct {
|
10
|
+
int fd;
|
11
|
+
size_t size;
|
12
|
+
void *map;
|
13
|
+
} mmap_file;
|
14
|
+
|
15
|
+
static VALUE cMmapFile;
|
16
|
+
|
17
|
+
static void mmap_file_free(void *ptr) {
|
18
|
+
mmap_file *m = (mmap_file *)ptr;
|
19
|
+
if (m->map)
|
20
|
+
munmap(m->map, m->size);
|
21
|
+
if (m->fd >= 0)
|
22
|
+
close(m->fd);
|
23
|
+
free(m);
|
24
|
+
}
|
25
|
+
|
26
|
+
static VALUE mmap_file_alloc(VALUE klass) {
|
27
|
+
mmap_file *m = ALLOC(mmap_file);
|
28
|
+
m->fd = -1;
|
29
|
+
m->map = NULL;
|
30
|
+
m->size = 0;
|
31
|
+
return Data_Wrap_Struct(klass, NULL, mmap_file_free, m);
|
32
|
+
}
|
33
|
+
|
34
|
+
static VALUE mmap_file_initialize(VALUE self, VALUE path) {
|
35
|
+
mmap_file *m;
|
36
|
+
const char *c_path = StringValueCStr(path);
|
37
|
+
Data_Get_Struct(self, mmap_file, m);
|
38
|
+
|
39
|
+
m->fd = open(c_path, O_RDWR);
|
40
|
+
if (m->fd < 0)
|
41
|
+
rb_sys_fail("open");
|
42
|
+
|
43
|
+
m->size = lseek(m->fd, 0, SEEK_END);
|
44
|
+
lseek(m->fd, 0, SEEK_SET);
|
45
|
+
|
46
|
+
m->map = mmap(NULL, m->size, PROT_READ | PROT_WRITE, MAP_SHARED, m->fd, 0);
|
47
|
+
if (m->map == MAP_FAILED)
|
48
|
+
rb_sys_fail("mmap");
|
49
|
+
|
50
|
+
return self;
|
51
|
+
}
|
52
|
+
|
53
|
+
static VALUE mmap_file_read(VALUE self, VALUE offset_val, VALUE length_val) {
|
54
|
+
mmap_file *m;
|
55
|
+
Data_Get_Struct(self, mmap_file, m);
|
56
|
+
|
57
|
+
long offset = NUM2LONG(offset_val);
|
58
|
+
long length = NUM2LONG(length_val);
|
59
|
+
|
60
|
+
if (offset < 0 || offset + length > (long)m->size)
|
61
|
+
rb_raise(rb_eArgError, "read out of bounds");
|
62
|
+
|
63
|
+
return rb_str_new((char *)m->map + offset, length);
|
64
|
+
}
|
65
|
+
|
66
|
+
static VALUE mmap_file_write(VALUE self, VALUE offset_val, VALUE str_val) {
|
67
|
+
mmap_file *m;
|
68
|
+
Data_Get_Struct(self, mmap_file, m);
|
69
|
+
|
70
|
+
long offset = NUM2LONG(offset_val);
|
71
|
+
StringValue(str_val);
|
72
|
+
long len = RSTRING_LEN(str_val);
|
73
|
+
|
74
|
+
if (offset < 0 || offset + len > (long)m->size)
|
75
|
+
rb_raise(rb_eArgError, "write out of bounds");
|
76
|
+
|
77
|
+
memcpy((char *)m->map + offset, RSTRING_PTR(str_val), len);
|
78
|
+
msync(m->map, m->size, MS_SYNC);
|
79
|
+
|
80
|
+
return Qtrue;
|
81
|
+
}
|
82
|
+
|
83
|
+
static VALUE mmap_file_size(VALUE self) {
|
84
|
+
mmap_file *m;
|
85
|
+
Data_Get_Struct(self, mmap_file, m);
|
86
|
+
return LONG2NUM(m->size);
|
87
|
+
}
|
88
|
+
|
89
|
+
void Init_mmapper(void) {
|
90
|
+
VALUE mMmapper = rb_define_module("Mmapper");
|
91
|
+
cMmapFile = rb_define_class_under(mMmapper, "File", rb_cObject);
|
92
|
+
|
93
|
+
rb_define_alloc_func(cMmapFile, mmap_file_alloc);
|
94
|
+
rb_define_method(cMmapFile, "initialize", mmap_file_initialize, 1);
|
95
|
+
rb_define_method(cMmapFile, "read", mmap_file_read, 2);
|
96
|
+
rb_define_method(cMmapFile, "write", mmap_file_write, 2);
|
97
|
+
rb_define_method(cMmapFile, "size", mmap_file_size, 0);
|
98
|
+
}
|
data/lib/mmapper.rb
CHANGED
@@ -1,45 +1,61 @@
|
|
1
|
-
require '
|
1
|
+
require 'mmapper/mmapper'
|
2
2
|
|
3
3
|
module Mmapper
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
class File
|
5
|
+
def find_matching_line(prefix)
|
6
|
+
low = 0
|
7
|
+
high = size
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
else "libmmapper.so"
|
13
|
-
end
|
9
|
+
while low < high
|
10
|
+
mid = (low + high) / 2
|
11
|
+
line_start = find_line_start(mid)
|
12
|
+
line = read_line_at(line_start)
|
14
13
|
|
15
|
-
|
14
|
+
return nil if line.nil?
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
16
|
+
if line < prefix
|
17
|
+
low = mid + 1
|
18
|
+
else
|
19
|
+
high = mid
|
20
|
+
end
|
23
21
|
|
24
|
-
|
22
|
+
end
|
25
23
|
|
26
|
-
|
27
|
-
|
24
|
+
final_line_start = find_line_start(low)
|
25
|
+
line = read_line_at(final_line_start)
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
if line&.start_with?(prefix)
|
28
|
+
line
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
33
32
|
end
|
34
33
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
private
|
35
|
+
|
36
|
+
def find_line_start(pos)
|
37
|
+
pos = [pos, size - 1].min
|
38
|
+
pos -= 1 while pos > 0 && read(pos, 1) != "\n"
|
39
|
+
pos += 1 if pos != 0
|
40
|
+
pos
|
39
41
|
end
|
40
|
-
end
|
41
42
|
|
42
|
-
|
43
|
-
|
43
|
+
def read_line_at(pos)
|
44
|
+
buf = +''
|
45
|
+
chunk_size = 64
|
46
|
+
while pos < size
|
47
|
+
safe_len = [size - pos, chunk_size].min
|
48
|
+
chunk = read(pos, safe_len)
|
49
|
+
newline_idx = chunk.index("\n")
|
50
|
+
if newline_idx
|
51
|
+
buf << chunk[0..newline_idx]
|
52
|
+
break
|
53
|
+
else
|
54
|
+
buf << chunk
|
55
|
+
pos += chunk_size
|
56
|
+
end
|
57
|
+
end
|
58
|
+
buf.empty? ? nil : buf.chomp
|
59
|
+
end
|
44
60
|
end
|
45
61
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mmapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl Dawson
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
11
|
-
dependencies:
|
12
|
-
-
|
13
|
-
name: ffi
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
15
|
-
requirements:
|
16
|
-
- - "~>"
|
17
|
-
- !ruby/object:Gem::Version
|
18
|
-
version: '1.15'
|
19
|
-
type: :runtime
|
20
|
-
prerelease: false
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
-
requirements:
|
23
|
-
- - "~>"
|
24
|
-
- !ruby/object:Gem::Version
|
25
|
-
version: '1.15'
|
26
|
-
description: Wraps a Go extension for mmap-ing files.
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: Wraps a C extension for mmap-ing files.
|
27
13
|
email:
|
28
14
|
- email@carldaws.com
|
29
15
|
executables: []
|
@@ -33,7 +19,7 @@ extra_rdoc_files: []
|
|
33
19
|
files:
|
34
20
|
- README.md
|
35
21
|
- ext/extconf.rb
|
36
|
-
- ext/
|
22
|
+
- ext/mmapper.c
|
37
23
|
- lib/mmapper.rb
|
38
24
|
homepage: https://github.com/carldaws/mmapper
|
39
25
|
licenses:
|
@@ -53,7 +39,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '0'
|
55
41
|
requirements: []
|
56
|
-
rubygems_version: 3.6.
|
42
|
+
rubygems_version: 3.6.7
|
57
43
|
specification_version: 4
|
58
|
-
summary: Mmap-ed files using
|
44
|
+
summary: Mmap-ed files in Ruby using a C native extension
|
59
45
|
test_files: []
|
data/ext/mmap.go
DELETED
@@ -1,127 +0,0 @@
|
|
1
|
-
package main
|
2
|
-
|
3
|
-
/*
|
4
|
-
#include <stdlib.h>
|
5
|
-
*/
|
6
|
-
import "C"
|
7
|
-
|
8
|
-
import (
|
9
|
-
"bytes"
|
10
|
-
"os"
|
11
|
-
"sync"
|
12
|
-
"syscall"
|
13
|
-
)
|
14
|
-
|
15
|
-
// Mmapper holds the mmap data for a file
|
16
|
-
type Mmapper struct {
|
17
|
-
mmapData []byte
|
18
|
-
fileSize int
|
19
|
-
}
|
20
|
-
|
21
|
-
// Store instances
|
22
|
-
var (
|
23
|
-
mu sync.Mutex
|
24
|
-
mmappers = make(map[int]*Mmapper)
|
25
|
-
nextID = 1
|
26
|
-
)
|
27
|
-
|
28
|
-
// mmapFile maps a file into memory.
|
29
|
-
func mmapFile(filename string) (*Mmapper, error) {
|
30
|
-
file, err := os.Open(filename)
|
31
|
-
if err != nil {
|
32
|
-
return nil, err
|
33
|
-
}
|
34
|
-
defer file.Close()
|
35
|
-
|
36
|
-
fi, err := file.Stat()
|
37
|
-
if err != nil {
|
38
|
-
return nil, err
|
39
|
-
}
|
40
|
-
|
41
|
-
size := fi.Size()
|
42
|
-
if size == 0 {
|
43
|
-
return nil, os.ErrInvalid
|
44
|
-
}
|
45
|
-
|
46
|
-
data, err := syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
|
47
|
-
if err != nil {
|
48
|
-
return nil, err
|
49
|
-
}
|
50
|
-
|
51
|
-
return &Mmapper{mmapData: data, fileSize: int(size)}, nil
|
52
|
-
}
|
53
|
-
|
54
|
-
// findLineStart moves backward to find the start of a line.
|
55
|
-
func findLineStart(data []byte, pos int) int {
|
56
|
-
for pos > 0 && data[pos-1] != '\n' {
|
57
|
-
pos--
|
58
|
-
}
|
59
|
-
return pos
|
60
|
-
}
|
61
|
-
|
62
|
-
// readLine reads a full line starting from a given position.
|
63
|
-
func readLine(data []byte, start int, fileSize int) string {
|
64
|
-
end := start
|
65
|
-
for end < fileSize && data[end] != '\n' {
|
66
|
-
end++
|
67
|
-
}
|
68
|
-
return string(data[start:end])
|
69
|
-
}
|
70
|
-
|
71
|
-
// binarySearchPrefix performs a binary search for a prefix.
|
72
|
-
func binarySearchPrefix(m *Mmapper, prefix string) string {
|
73
|
-
low, high := 0, m.fileSize-1
|
74
|
-
var match string
|
75
|
-
|
76
|
-
for low <= high {
|
77
|
-
mid := (low + high) / 2
|
78
|
-
mid = findLineStart(m.mmapData, mid)
|
79
|
-
|
80
|
-
line := readLine(m.mmapData, mid, m.fileSize)
|
81
|
-
if bytes.HasPrefix([]byte(line), []byte(prefix)) {
|
82
|
-
match = line
|
83
|
-
high = mid - 1
|
84
|
-
} else if line < prefix {
|
85
|
-
low = mid + len(line) + 1
|
86
|
-
} else {
|
87
|
-
high = mid - 1
|
88
|
-
}
|
89
|
-
}
|
90
|
-
return match
|
91
|
-
}
|
92
|
-
|
93
|
-
//export CreateMmapper
|
94
|
-
func CreateMmapper(filename *C.char) C.int {
|
95
|
-
m, err := mmapFile(C.GoString(filename))
|
96
|
-
if err != nil {
|
97
|
-
return -1 // Error case
|
98
|
-
}
|
99
|
-
|
100
|
-
mu.Lock()
|
101
|
-
id := nextID
|
102
|
-
nextID++
|
103
|
-
mmappers[id] = m
|
104
|
-
mu.Unlock()
|
105
|
-
|
106
|
-
return C.int(id)
|
107
|
-
}
|
108
|
-
|
109
|
-
//export FindMatchingLine
|
110
|
-
func FindMatchingLine(mmapperID C.int, prefix *C.char) *C.char {
|
111
|
-
mu.Lock()
|
112
|
-
m, exists := mmappers[int(mmapperID)]
|
113
|
-
mu.Unlock()
|
114
|
-
|
115
|
-
if !exists {
|
116
|
-
return nil
|
117
|
-
}
|
118
|
-
|
119
|
-
match := binarySearchPrefix(m, C.GoString(prefix))
|
120
|
-
if match == "" {
|
121
|
-
return nil
|
122
|
-
}
|
123
|
-
|
124
|
-
return C.CString(match)
|
125
|
-
}
|
126
|
-
|
127
|
-
func main() {}
|