command-t 1.7 → 1.8
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/README.txt +74 -18
- data/doc/command-t.txt +74 -18
- data/doc/tags +1 -0
- data/ruby/command-t/Makefile +28 -29
- data/ruby/command-t/depend +2 -2
- data/ruby/command-t/ext.bundle +0 -0
- data/ruby/command-t/ext.c +15 -6
- data/ruby/command-t/ext.h +5 -3
- data/ruby/command-t/extconf.rb +10 -0
- data/ruby/command-t/match.c +13 -8
- data/ruby/command-t/matcher.c +43 -26
- data/ruby/command-t/scanner/file_scanner/watchman_file_scanner.rb +14 -11
- data/ruby/command-t/watchman.c +660 -0
- data/ruby/command-t/watchman.h +52 -0
- metadata +17 -15
data/ruby/command-t/depend
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2010 Wincent Colaiuta. All rights reserved.
|
1
|
+
# Copyright 2010-2014 Wincent Colaiuta. All rights reserved.
|
2
2
|
#
|
3
3
|
# Redistribution and use in source and binary forms, with or without
|
4
4
|
# modification, are permitted provided that the following conditions are met:
|
@@ -21,4 +21,4 @@
|
|
21
21
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
22
22
|
# POSSIBILITY OF SUCH DAMAGE.
|
23
23
|
|
24
|
-
CFLAGS += -
|
24
|
+
CFLAGS += -Wall -Wextra -Wno-unused-parameter
|
data/ruby/command-t/ext.bundle
CHANGED
Binary file
|
data/ruby/command-t/ext.c
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
// Copyright 2010-
|
1
|
+
// Copyright 2010-2014 Wincent Colaiuta. All rights reserved.
|
2
2
|
//
|
3
3
|
// Redistribution and use in source and binary forms, with or without
|
4
4
|
// modification, are permitted provided that the following conditions are met:
|
@@ -22,15 +22,19 @@
|
|
22
22
|
// POSSIBILITY OF SUCH DAMAGE.
|
23
23
|
|
24
24
|
#include "matcher.h"
|
25
|
+
#include "watchman.h"
|
25
26
|
|
26
|
-
VALUE mCommandT
|
27
|
-
VALUE cCommandTMatcher
|
27
|
+
VALUE mCommandT = 0; // module CommandT
|
28
|
+
VALUE cCommandTMatcher = 0; // class CommandT::Matcher
|
29
|
+
VALUE mCommandTWatchman = 0; // module CommandT::Watchman
|
30
|
+
VALUE mCommandTWatchmanUtils = 0; // module CommandT::Watchman::Utils
|
28
31
|
|
29
32
|
VALUE CommandT_option_from_hash(const char *option, VALUE hash)
|
30
33
|
{
|
34
|
+
VALUE key;
|
31
35
|
if (NIL_P(hash))
|
32
36
|
return Qnil;
|
33
|
-
|
37
|
+
key = ID2SYM(rb_intern(option));
|
34
38
|
if (rb_funcall(hash, rb_intern("has_key?"), 1, key) == Qtrue)
|
35
39
|
return rb_hash_aref(hash, key);
|
36
40
|
else
|
@@ -44,8 +48,13 @@ void Init_ext()
|
|
44
48
|
|
45
49
|
// class CommandT::Matcher
|
46
50
|
cCommandTMatcher = rb_define_class_under(mCommandT, "Matcher", rb_cObject);
|
47
|
-
|
48
|
-
// methods
|
49
51
|
rb_define_method(cCommandTMatcher, "initialize", CommandTMatcher_initialize, -1);
|
50
52
|
rb_define_method(cCommandTMatcher, "sorted_matches_for", CommandTMatcher_sorted_matches_for, -1);
|
53
|
+
|
54
|
+
// module CommandT::Watchman::Utils
|
55
|
+
mCommandTWatchman = rb_define_module_under(mCommandT, "Watchman");
|
56
|
+
mCommandTWatchmanUtils = rb_define_module_under(mCommandTWatchman, "Utils");
|
57
|
+
rb_define_singleton_method(mCommandTWatchmanUtils, "load", CommandTWatchmanUtils_load, 1);
|
58
|
+
rb_define_singleton_method(mCommandTWatchmanUtils, "dump", CommandTWatchmanUtils_dump, 1);
|
59
|
+
rb_define_singleton_method(mCommandTWatchmanUtils, "query", CommandTWatchmanUtils_query, 2);
|
51
60
|
}
|
data/ruby/command-t/ext.h
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
// Copyright 2010-
|
1
|
+
// Copyright 2010-2014 Wincent Colaiuta. All rights reserved.
|
2
2
|
//
|
3
3
|
// Redistribution and use in source and binary forms, with or without
|
4
4
|
// modification, are permitted provided that the following conditions are met:
|
@@ -23,8 +23,10 @@
|
|
23
23
|
|
24
24
|
#include <ruby.h>
|
25
25
|
|
26
|
-
extern VALUE mCommandT;
|
27
|
-
extern VALUE cCommandTMatcher;
|
26
|
+
extern VALUE mCommandT; // module CommandT
|
27
|
+
extern VALUE cCommandTMatcher; // class CommandT::Matcher
|
28
|
+
extern VALUE mCommandTWatchman; // module CommandT::Watchman
|
29
|
+
extern VALUE mCommandTWatchmanUtils; // module CommandT::Watchman::Utils
|
28
30
|
|
29
31
|
// Encapsulates common pattern of checking for an option in an optional
|
30
32
|
// options hash. The hash itself may be nil, but an exception will be
|
data/ruby/command-t/extconf.rb
CHANGED
@@ -36,6 +36,16 @@ header('ruby.h')
|
|
36
36
|
header('stdlib.h')
|
37
37
|
header('string.h')
|
38
38
|
|
39
|
+
# optional headers (for CommandT::Watchman::Utils)
|
40
|
+
if have_header('fcntl.h') &&
|
41
|
+
have_header('sys/errno.h') &&
|
42
|
+
have_header('sys/socket.h')
|
43
|
+
RbConfig::MAKEFILE_CONFIG['DEFS'] += ' -DWATCHMAN_BUILD'
|
44
|
+
|
45
|
+
have_header('ruby/st.h') # >= 1.9; sets HAVE_RUBY_ST_H
|
46
|
+
have_header('st.h') # 1.8; sets HAVE_ST_H
|
47
|
+
end
|
48
|
+
|
39
49
|
# optional
|
40
50
|
if RbConfig::CONFIG['THREAD_MODEL'] == 'pthread'
|
41
51
|
have_library('pthread', 'pthread_create') # sets HAVE_PTHREAD_H if found
|
data/ruby/command-t/match.c
CHANGED
@@ -48,6 +48,9 @@ double recursive_match(matchinfo_t *m, // sharable meta-data
|
|
48
48
|
double seen_score = 0; // remember best score seen via recursion
|
49
49
|
int dot_file_match = 0; // true if needle matches a dot-file
|
50
50
|
int dot_search = 0; // true if searching for a dot
|
51
|
+
long i, j, distance;
|
52
|
+
int found;
|
53
|
+
double score_for_char;
|
51
54
|
|
52
55
|
// do we have a memoized result we can return?
|
53
56
|
double memoized = m->memo[needle_idx * m->needle_len + haystack_idx];
|
@@ -60,15 +63,15 @@ double recursive_match(matchinfo_t *m, // sharable meta-data
|
|
60
63
|
goto memoize;
|
61
64
|
}
|
62
65
|
|
63
|
-
for (
|
66
|
+
for (i = needle_idx; i < m->needle_len; i++) {
|
64
67
|
char c = m->needle_p[i];
|
65
68
|
if (c == '.')
|
66
69
|
dot_search = 1;
|
67
|
-
|
70
|
+
found = 0;
|
68
71
|
|
69
72
|
// similar to above, we'll stop iterating when we know we're too close
|
70
73
|
// to the end of the string to possibly match
|
71
|
-
for (
|
74
|
+
for (j = haystack_idx;
|
72
75
|
j <= m->haystack_len - (m->needle_len - i);
|
73
76
|
j++, haystack_idx++) {
|
74
77
|
char d = m->haystack_p[j];
|
@@ -87,8 +90,8 @@ double recursive_match(matchinfo_t *m, // sharable meta-data
|
|
87
90
|
dot_search = 0;
|
88
91
|
|
89
92
|
// calculate score
|
90
|
-
|
91
|
-
|
93
|
+
score_for_char = m->max_score_per_char;
|
94
|
+
distance = j - last_idx;
|
92
95
|
|
93
96
|
if (distance > 1) {
|
94
97
|
double factor = 1.0;
|
@@ -152,6 +155,8 @@ void calculate_match(VALUE str,
|
|
152
155
|
VALUE never_show_dot_files,
|
153
156
|
match_t *out)
|
154
157
|
{
|
158
|
+
long i, max;
|
159
|
+
double score;
|
155
160
|
matchinfo_t m;
|
156
161
|
m.haystack_p = RSTRING_PTR(str);
|
157
162
|
m.haystack_len = RSTRING_LEN(str);
|
@@ -163,14 +168,14 @@ void calculate_match(VALUE str,
|
|
163
168
|
m.never_show_dot_files = never_show_dot_files == Qtrue;
|
164
169
|
|
165
170
|
// calculate score
|
166
|
-
|
171
|
+
score = 1.0;
|
167
172
|
|
168
173
|
// special case for zero-length search string
|
169
174
|
if (m.needle_len == 0) {
|
170
175
|
|
171
176
|
// filter out dot files
|
172
177
|
if (!m.always_show_dot_files) {
|
173
|
-
for (
|
178
|
+
for (i = 0; i < m.haystack_len; i++) {
|
174
179
|
char c = m.haystack_p[i];
|
175
180
|
|
176
181
|
if (c == '.' && (i == 0 || m.haystack_p[i - 1] == '/')) {
|
@@ -183,7 +188,7 @@ void calculate_match(VALUE str,
|
|
183
188
|
|
184
189
|
// prepare for memoization
|
185
190
|
double memo[m.haystack_len * m.needle_len];
|
186
|
-
for (
|
191
|
+
for (i = 0, max = m.haystack_len * m.needle_len; i < max; i++)
|
187
192
|
memo[i] = DBL_MAX;
|
188
193
|
m.memo = memo;
|
189
194
|
|
data/ruby/command-t/matcher.c
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
// Copyright 2010-
|
1
|
+
// Copyright 2010-2014 Wincent Colaiuta. All rights reserved.
|
2
2
|
//
|
3
3
|
// Redistribution and use in source and binary forms, with or without
|
4
4
|
// modification, are permitted provided that the following conditions are met:
|
@@ -77,9 +77,12 @@ int cmp_score(const void *a, const void *b)
|
|
77
77
|
|
78
78
|
VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
|
79
79
|
{
|
80
|
-
|
81
|
-
VALUE
|
80
|
+
VALUE always_show_dot_files;
|
81
|
+
VALUE never_show_dot_files;
|
82
|
+
VALUE options;
|
83
|
+
VALUE scanner;
|
82
84
|
|
85
|
+
// process arguments: 1 mandatory, 1 optional
|
83
86
|
if (rb_scan_args(argc, argv, "11", &scanner, &options) == 1)
|
84
87
|
options = Qnil;
|
85
88
|
if (NIL_P(scanner))
|
@@ -88,8 +91,8 @@ VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
|
|
88
91
|
rb_iv_set(self, "@scanner", scanner);
|
89
92
|
|
90
93
|
// check optional options hash for overrides
|
91
|
-
|
92
|
-
|
94
|
+
always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
|
95
|
+
never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
|
93
96
|
|
94
97
|
rb_iv_set(self, "@always_show_dot_files", always_show_dot_files);
|
95
98
|
rb_iv_set(self, "@never_show_dot_files", never_show_dot_files);
|
@@ -110,8 +113,9 @@ typedef struct {
|
|
110
113
|
|
111
114
|
void *match_thread(void *thread_args)
|
112
115
|
{
|
116
|
+
long i;
|
113
117
|
thread_args_t *args = (thread_args_t *)thread_args;
|
114
|
-
for (
|
118
|
+
for (i = args->thread_index; i < args->path_count; i += args->thread_count) {
|
115
119
|
VALUE path = RARRAY_PTR(args->paths)[i];
|
116
120
|
calculate_match(path,
|
117
121
|
args->abbrev,
|
@@ -126,9 +130,23 @@ void *match_thread(void *thread_args)
|
|
126
130
|
|
127
131
|
VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
|
128
132
|
{
|
129
|
-
|
130
|
-
|
133
|
+
long i, limit, path_count, thread_count;
|
134
|
+
#ifdef HAVE_PTHREAD_H
|
135
|
+
long err;
|
136
|
+
#endif
|
137
|
+
match_t *matches;
|
138
|
+
thread_args_t *thread_args;
|
139
|
+
VALUE abbrev;
|
140
|
+
VALUE always_show_dot_files;
|
141
|
+
VALUE limit_option;
|
142
|
+
VALUE never_show_dot_files;
|
143
|
+
VALUE options;
|
144
|
+
VALUE paths;
|
145
|
+
VALUE results;
|
146
|
+
VALUE scanner;
|
147
|
+
VALUE threads_option;
|
131
148
|
|
149
|
+
// process arguments: 1 mandatory, 1 optional
|
132
150
|
if (rb_scan_args(argc, argv, "11", &abbrev, &options) == 1)
|
133
151
|
options = Qnil;
|
134
152
|
if (NIL_P(abbrev))
|
@@ -138,22 +156,21 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
|
|
138
156
|
abbrev = rb_funcall(abbrev, rb_intern("downcase"), 0);
|
139
157
|
|
140
158
|
// check optional options has for overrides
|
141
|
-
|
142
|
-
|
159
|
+
limit_option = CommandT_option_from_hash("limit", options);
|
160
|
+
threads_option = CommandT_option_from_hash("threads", options);
|
143
161
|
|
144
162
|
// get unsorted matches
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
163
|
+
scanner = rb_iv_get(self, "@scanner");
|
164
|
+
paths = rb_funcall(scanner, rb_intern("paths"), 0);
|
165
|
+
always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
|
166
|
+
never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
|
149
167
|
|
150
|
-
|
151
|
-
|
168
|
+
path_count = RARRAY_LEN(paths);
|
169
|
+
matches = malloc(path_count * sizeof(match_t));
|
152
170
|
if (!matches)
|
153
171
|
rb_raise(rb_eNoMemError, "memory allocation failed");
|
154
172
|
|
155
|
-
|
156
|
-
long thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
|
173
|
+
thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
|
157
174
|
|
158
175
|
#ifdef HAVE_PTHREAD_H
|
159
176
|
#define THREAD_THRESHOLD 1000 /* avoid the overhead of threading when search space is small */
|
@@ -164,10 +181,10 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
|
|
164
181
|
rb_raise(rb_eNoMemError, "memory allocation failed");
|
165
182
|
#endif
|
166
183
|
|
167
|
-
|
184
|
+
thread_args = malloc(sizeof(thread_args_t) * thread_count);
|
168
185
|
if (!thread_args)
|
169
186
|
rb_raise(rb_eNoMemError, "memory allocation failed");
|
170
|
-
for (
|
187
|
+
for (i = 0; i < thread_count; i++) {
|
171
188
|
thread_args[i].thread_count = thread_count;
|
172
189
|
thread_args[i].thread_index = i;
|
173
190
|
thread_args[i].matches = matches;
|
@@ -186,16 +203,16 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
|
|
186
203
|
} else {
|
187
204
|
err = pthread_create(&threads[i], NULL, match_thread, (void *)&thread_args[i]);
|
188
205
|
if (err != 0)
|
189
|
-
rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", err);
|
206
|
+
rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", (int)err);
|
190
207
|
}
|
191
208
|
#endif
|
192
209
|
}
|
193
210
|
|
194
211
|
#ifdef HAVE_PTHREAD_H
|
195
|
-
for (
|
212
|
+
for (i = 0; i < thread_count - 1; i++) {
|
196
213
|
err = pthread_join(threads[i], NULL);
|
197
214
|
if (err != 0)
|
198
|
-
rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", err);
|
215
|
+
rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", (int)err);
|
199
216
|
}
|
200
217
|
free(threads);
|
201
218
|
#endif
|
@@ -208,12 +225,12 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
|
|
208
225
|
// for all other non-empty search strings, sort by score
|
209
226
|
qsort(matches, path_count, sizeof(match_t), cmp_score);
|
210
227
|
|
211
|
-
|
228
|
+
results = rb_ary_new();
|
212
229
|
|
213
|
-
|
230
|
+
limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
|
214
231
|
if (limit == 0)
|
215
232
|
limit = path_count;
|
216
|
-
for (
|
233
|
+
for (i = 0; i < path_count && limit > 0; i++) {
|
217
234
|
if (matches[i].score > 0.0) {
|
218
235
|
rb_funcall(results, rb_intern("push"), 1, matches[i].path);
|
219
236
|
limit--;
|
@@ -21,7 +21,7 @@
|
|
21
21
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
22
22
|
# POSSIBILITY OF SUCH DAMAGE.
|
23
23
|
|
24
|
-
require '
|
24
|
+
require 'pathname'
|
25
25
|
require 'socket'
|
26
26
|
require 'command-t/vim'
|
27
27
|
require 'command-t/vim/path_utilities'
|
@@ -43,24 +43,27 @@ module CommandT
|
|
43
43
|
def paths
|
44
44
|
@paths[@path] ||= begin
|
45
45
|
ensure_cache_under_limit
|
46
|
-
sockname =
|
46
|
+
sockname = Watchman::Utils.load(
|
47
|
+
%x{watchman --output-encoding=bser get-sockname}
|
48
|
+
)['sockname']
|
47
49
|
raise WatchmanUnavailable unless $?.exitstatus.zero?
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
|
51
|
+
UNIXSocket.open(sockname) do |socket|
|
52
|
+
root = Pathname.new(@path).realpath.to_s
|
53
|
+
roots = Watchman::Utils.query(['watch-list'], socket)['roots']
|
54
|
+
if !roots.include?(root)
|
52
55
|
# this path isn't being watched yet; try to set up watch
|
53
|
-
|
56
|
+
result = Watchman::Utils.query(['watch', root], socket)
|
54
57
|
|
55
58
|
# root_restrict_files setting may prevent Watchman from working
|
56
|
-
raise WatchmanUnavailable if
|
59
|
+
raise WatchmanUnavailable if result.has_key?('error')
|
57
60
|
end
|
58
61
|
|
59
|
-
|
62
|
+
query = ['query', root, {
|
60
63
|
'expression' => ['type', 'f'],
|
61
64
|
'fields' => ['name'],
|
62
|
-
}]
|
63
|
-
paths =
|
65
|
+
}]
|
66
|
+
paths = Watchman::Utils.query(query, socket)
|
64
67
|
|
65
68
|
# could return error if watch is removed
|
66
69
|
raise WatchmanUnavailable if paths.has_key?('error')
|
@@ -0,0 +1,660 @@
|
|
1
|
+
// Copyright 2014 Wincent Colaiuta. All rights reserved.
|
2
|
+
//
|
3
|
+
// Redistribution and use in source and binary forms, with or without
|
4
|
+
// modification, are permitted provided that the following conditions are met:
|
5
|
+
//
|
6
|
+
// 1. Redistributions of source code must retain the above copyright notice,
|
7
|
+
// this list of conditions and the following disclaimer.
|
8
|
+
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
9
|
+
// this list of conditions and the following disclaimer in the documentation
|
10
|
+
// and/or other materials provided with the distribution.
|
11
|
+
//
|
12
|
+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
13
|
+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
14
|
+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
15
|
+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
|
16
|
+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
17
|
+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
18
|
+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
19
|
+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
20
|
+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
21
|
+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
22
|
+
// POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
#include "watchman.h"
|
25
|
+
|
26
|
+
#ifdef WATCHMAN_BUILD
|
27
|
+
|
28
|
+
#if defined(HAVE_RUBY_ST_H)
|
29
|
+
#include <ruby/st.h>
|
30
|
+
#elif defined(HAVE_ST_H)
|
31
|
+
#include <st.h>
|
32
|
+
#else
|
33
|
+
#error no st.h header found
|
34
|
+
#endif
|
35
|
+
|
36
|
+
#include <fcntl.h> /* for fcntl() */
|
37
|
+
#include <sys/errno.h> /* for errno */
|
38
|
+
#include <sys/socket.h> /* for recv(), MSG_PEEK */
|
39
|
+
|
40
|
+
typedef struct {
|
41
|
+
uint8_t *data; // payload
|
42
|
+
size_t cap; // total capacity
|
43
|
+
size_t len; // current length
|
44
|
+
} watchman_t;
|
45
|
+
|
46
|
+
// Forward declarations:
|
47
|
+
VALUE watchman_load(char **ptr, char *end);
|
48
|
+
void watchman_dump(watchman_t *w, VALUE serializable);
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Inspect a ruby object (debugging aid)
|
52
|
+
*/
|
53
|
+
#define ruby_inspect(obj) rb_funcall(rb_mKernel, rb_intern("p"), 1, obj)
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Print `count` bytes of memory at `address` (debugging aid)
|
57
|
+
*/
|
58
|
+
#define dump(address, count) \
|
59
|
+
do { \
|
60
|
+
for (int i = 0; i < count; i++) { \
|
61
|
+
printf("%02x ", ((unsigned char *)address)[i]); printf("\n"); \
|
62
|
+
} \
|
63
|
+
} while(0)
|
64
|
+
|
65
|
+
#define WATCHMAN_DEFAULT_STORAGE 4096
|
66
|
+
|
67
|
+
#define WATCHMAN_BINARY_MARKER "\x00\x01"
|
68
|
+
#define WATCHMAN_ARRAY_MARKER 0x00
|
69
|
+
#define WATCHMAN_HASH_MARKER 0x01
|
70
|
+
#define WATCHMAN_STRING_MARKER 0x02
|
71
|
+
#define WATCHMAN_INT8_MARKER 0x03
|
72
|
+
#define WATCHMAN_INT16_MARKER 0x04
|
73
|
+
#define WATCHMAN_INT32_MARKER 0x05
|
74
|
+
#define WATCHMAN_INT64_MARKER 0x06
|
75
|
+
#define WATCHMAN_FLOAT_MARKER 0x07
|
76
|
+
#define WATCHMAN_TRUE 0x08
|
77
|
+
#define WATCHMAN_FALSE 0x09
|
78
|
+
#define WATCHMAN_NIL 0x0a
|
79
|
+
#define WATCHMAN_TEMPLATE_MARKER 0x0b
|
80
|
+
#define WATCHMAN_SKIP_MARKER 0x0c
|
81
|
+
|
82
|
+
#define WATCHMAN_HEADER \
|
83
|
+
WATCHMAN_BINARY_MARKER \
|
84
|
+
"\x06" \
|
85
|
+
"\x00\x00\x00\x00\x00\x00\x00\x00"
|
86
|
+
|
87
|
+
static const char watchman_array_marker = WATCHMAN_ARRAY_MARKER;
|
88
|
+
static const char watchman_hash_marker = WATCHMAN_HASH_MARKER;
|
89
|
+
static const char watchman_string_marker = WATCHMAN_STRING_MARKER;
|
90
|
+
static const char watchman_true = WATCHMAN_TRUE;
|
91
|
+
static const char watchman_false = WATCHMAN_FALSE;
|
92
|
+
static const char watchman_nil = WATCHMAN_NIL;
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Appends `len` bytes, starting at `data`, to the watchman_t struct `w`
|
96
|
+
*
|
97
|
+
* Will attempt to reallocate the underlying storage if it is not sufficient.
|
98
|
+
*/
|
99
|
+
void watchman_append(watchman_t *w, const char *data, size_t len) {
|
100
|
+
if (w->len + len > w->cap) {
|
101
|
+
w->cap += w->len + WATCHMAN_DEFAULT_STORAGE;
|
102
|
+
REALLOC_N(w->data, uint8_t, w->cap);
|
103
|
+
}
|
104
|
+
memcpy(w->data + w->len, data, len);
|
105
|
+
w->len += len;
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Allocate a new watchman_t struct
|
110
|
+
*
|
111
|
+
* The struct has a small amount of extra capacity preallocated, and a blank
|
112
|
+
* header that can be filled in later to describe the PDU.
|
113
|
+
*/
|
114
|
+
watchman_t *watchman_init() {
|
115
|
+
watchman_t *w = ALLOC(watchman_t);
|
116
|
+
w->cap = WATCHMAN_DEFAULT_STORAGE;
|
117
|
+
w->len = 0;
|
118
|
+
w->data = ALLOC_N(uint8_t, WATCHMAN_DEFAULT_STORAGE);
|
119
|
+
|
120
|
+
watchman_append(w, WATCHMAN_HEADER, sizeof(WATCHMAN_HEADER) - 1);
|
121
|
+
return w;
|
122
|
+
}
|
123
|
+
|
124
|
+
/**
|
125
|
+
* Free a watchman_t struct `w` that was previously allocated with
|
126
|
+
* `watchman_init`
|
127
|
+
*/
|
128
|
+
void watchman_free(watchman_t *w) {
|
129
|
+
xfree(w->data);
|
130
|
+
xfree(w);
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* Encodes and appends the integer `num` to `w`
|
135
|
+
*/
|
136
|
+
void watchman_dump_int(watchman_t *w, int64_t num) {
|
137
|
+
char encoded[1 + sizeof(int64_t)];
|
138
|
+
|
139
|
+
if (num == (int8_t)num) {
|
140
|
+
encoded[0] = WATCHMAN_INT8_MARKER;
|
141
|
+
encoded[1] = (int8_t)num;
|
142
|
+
watchman_append(w, encoded, 1 + sizeof(int8_t));
|
143
|
+
} else if (num == (int16_t)num) {
|
144
|
+
encoded[0] = WATCHMAN_INT16_MARKER;
|
145
|
+
*(int16_t *)(encoded + 1) = (int16_t)num;
|
146
|
+
watchman_append(w, encoded, 1 + sizeof(int16_t));
|
147
|
+
} else if (num == (int32_t)num) {
|
148
|
+
encoded[0] = WATCHMAN_INT32_MARKER;
|
149
|
+
*(int32_t *)(encoded + 1) = (int32_t)num;
|
150
|
+
watchman_append(w, encoded, 1 + sizeof(int32_t));
|
151
|
+
} else {
|
152
|
+
encoded[0] = WATCHMAN_INT64_MARKER;
|
153
|
+
*(int64_t *)(encoded + 1) = (int64_t)num;
|
154
|
+
watchman_append(w, encoded, 1 + sizeof(int64_t));
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* Encodes and appends the string `string` to `w`
|
160
|
+
*/
|
161
|
+
void watchman_dump_string(watchman_t *w, VALUE string) {
|
162
|
+
watchman_append(w, &watchman_string_marker, sizeof(watchman_string_marker));
|
163
|
+
watchman_dump_int(w, RSTRING_LEN(string));
|
164
|
+
watchman_append(w, RSTRING_PTR(string), RSTRING_LEN(string));
|
165
|
+
}
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Encodes and appends the double `num` to `w`
|
169
|
+
*/
|
170
|
+
void watchman_dump_double(watchman_t *w, double num) {
|
171
|
+
char encoded[1 + sizeof(double)];
|
172
|
+
encoded[0] = WATCHMAN_FLOAT_MARKER;
|
173
|
+
*(double *)(encoded + 1) = num;
|
174
|
+
watchman_append(w, encoded, sizeof(encoded));
|
175
|
+
}
|
176
|
+
|
177
|
+
/**
|
178
|
+
* Encodes and appends the array `array` to `w`
|
179
|
+
*/
|
180
|
+
void watchman_dump_array(watchman_t *w, VALUE array) {
|
181
|
+
long i;
|
182
|
+
watchman_append(w, &watchman_array_marker, sizeof(watchman_array_marker));
|
183
|
+
watchman_dump_int(w, RARRAY_LEN(array));
|
184
|
+
for (i = 0; i < RARRAY_LEN(array); i++) {
|
185
|
+
watchman_dump(w, rb_ary_entry(array, i));
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
/**
|
190
|
+
* Helper method that encodes and appends a key/value pair (`key`, `value`) from
|
191
|
+
* a hash to the watchman_t struct passed in via `data`
|
192
|
+
*/
|
193
|
+
int watchman_dump_hash_iterator(VALUE key, VALUE value, VALUE data) {
|
194
|
+
watchman_t *w = (watchman_t *)data;
|
195
|
+
watchman_dump_string(w, StringValue(key));
|
196
|
+
watchman_dump(w, value);
|
197
|
+
return ST_CONTINUE;
|
198
|
+
}
|
199
|
+
|
200
|
+
/**
|
201
|
+
* Encodes and appends the hash `hash` to `w`
|
202
|
+
*/
|
203
|
+
void watchman_dump_hash(watchman_t *w, VALUE hash) {
|
204
|
+
watchman_append(w, &watchman_hash_marker, sizeof(watchman_hash_marker));
|
205
|
+
watchman_dump_int(w, RHASH_SIZE(hash));
|
206
|
+
rb_hash_foreach(hash, watchman_dump_hash_iterator, (VALUE)w);
|
207
|
+
}
|
208
|
+
|
209
|
+
/**
|
210
|
+
* Encodes and appends the serialized Ruby object `serializable` to `w`
|
211
|
+
*
|
212
|
+
* Examples of serializable objects include arrays, hashes, strings, numbers
|
213
|
+
* (integers, floats), booleans, and nil.
|
214
|
+
*/
|
215
|
+
void watchman_dump(watchman_t *w, VALUE serializable) {
|
216
|
+
switch (TYPE(serializable)) {
|
217
|
+
case T_ARRAY:
|
218
|
+
return watchman_dump_array(w, serializable);
|
219
|
+
case T_HASH:
|
220
|
+
return watchman_dump_hash(w, serializable);
|
221
|
+
case T_STRING:
|
222
|
+
return watchman_dump_string(w, serializable);
|
223
|
+
case T_FIXNUM: // up to 63 bits
|
224
|
+
return watchman_dump_int(w, FIX2LONG(serializable));
|
225
|
+
case T_BIGNUM:
|
226
|
+
return watchman_dump_int(w, NUM2LL(serializable));
|
227
|
+
case T_FLOAT:
|
228
|
+
return watchman_dump_double(w, NUM2DBL(serializable));
|
229
|
+
case T_TRUE:
|
230
|
+
return watchman_append(w, &watchman_true, sizeof(watchman_true));
|
231
|
+
case T_FALSE:
|
232
|
+
return watchman_append(w, &watchman_false, sizeof(watchman_false));
|
233
|
+
case T_NIL:
|
234
|
+
return watchman_append(w, &watchman_nil, sizeof(watchman_nil));
|
235
|
+
default:
|
236
|
+
rb_raise(rb_eTypeError, "unsupported type");
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
/**
|
241
|
+
* Extract and return the int encoded at `ptr`
|
242
|
+
*
|
243
|
+
* Moves `ptr` past the extracted int.
|
244
|
+
*
|
245
|
+
* Will raise an ArgumentError if extracting the int would take us beyond the
|
246
|
+
* end of the buffer indicated by `end`, or if there is no int encoded at `ptr`.
|
247
|
+
*
|
248
|
+
* @returns The extracted int
|
249
|
+
*/
|
250
|
+
int64_t watchman_load_int(char **ptr, char *end) {
|
251
|
+
char *val_ptr = *ptr + sizeof(int8_t);
|
252
|
+
int64_t val = 0;
|
253
|
+
|
254
|
+
if (val_ptr >= end) {
|
255
|
+
rb_raise(rb_eArgError, "insufficient int storage");
|
256
|
+
}
|
257
|
+
|
258
|
+
switch (*ptr[0]) {
|
259
|
+
case WATCHMAN_INT8_MARKER:
|
260
|
+
if (val_ptr + sizeof(int8_t) > end) {
|
261
|
+
rb_raise(rb_eArgError, "overrun extracting int8_t");
|
262
|
+
}
|
263
|
+
val = *(int8_t *)val_ptr;
|
264
|
+
*ptr = val_ptr + sizeof(int8_t);
|
265
|
+
break;
|
266
|
+
case WATCHMAN_INT16_MARKER:
|
267
|
+
if (val_ptr + sizeof(int16_t) > end) {
|
268
|
+
rb_raise(rb_eArgError, "overrun extracting int16_t");
|
269
|
+
}
|
270
|
+
val = *(int16_t *)val_ptr;
|
271
|
+
*ptr = val_ptr + sizeof(int16_t);
|
272
|
+
break;
|
273
|
+
case WATCHMAN_INT32_MARKER:
|
274
|
+
if (val_ptr + sizeof(int32_t) > end) {
|
275
|
+
rb_raise(rb_eArgError, "overrun extracting int32_t");
|
276
|
+
}
|
277
|
+
val = *(int32_t *)val_ptr;
|
278
|
+
*ptr = val_ptr + sizeof(int32_t);
|
279
|
+
break;
|
280
|
+
case WATCHMAN_INT64_MARKER:
|
281
|
+
if (val_ptr + sizeof(int64_t) > end) {
|
282
|
+
rb_raise(rb_eArgError, "overrun extracting int64_t");
|
283
|
+
}
|
284
|
+
val = *(int64_t *)val_ptr;
|
285
|
+
*ptr = val_ptr + sizeof(int64_t);
|
286
|
+
break;
|
287
|
+
default:
|
288
|
+
rb_raise(rb_eArgError, "bad integer marker 0x%02x", (unsigned int)*ptr[0]);
|
289
|
+
break;
|
290
|
+
}
|
291
|
+
|
292
|
+
return val;
|
293
|
+
}
|
294
|
+
|
295
|
+
/**
|
296
|
+
* Reads and returns a string encoded in the Watchman binary protocol format,
|
297
|
+
* starting at `ptr` and finishing at or before `end`
|
298
|
+
*/
|
299
|
+
VALUE watchman_load_string(char **ptr, char *end) {
|
300
|
+
if (*ptr >= end) {
|
301
|
+
rb_raise(rb_eArgError, "unexpected end of input");
|
302
|
+
}
|
303
|
+
|
304
|
+
if (*ptr[0] != WATCHMAN_STRING_MARKER) {
|
305
|
+
rb_raise(rb_eArgError, "not a number");
|
306
|
+
}
|
307
|
+
|
308
|
+
*ptr += sizeof(int8_t);
|
309
|
+
if (*ptr >= end) {
|
310
|
+
rb_raise(rb_eArgError, "invalid string header");
|
311
|
+
}
|
312
|
+
|
313
|
+
int64_t len = watchman_load_int(ptr, end);
|
314
|
+
if (len == 0) { // special case for zero-length strings
|
315
|
+
return rb_str_new2("");
|
316
|
+
} else if (*ptr + len > end) {
|
317
|
+
rb_raise(rb_eArgError, "insufficient string storage");
|
318
|
+
}
|
319
|
+
|
320
|
+
VALUE string = rb_str_new(*ptr, len);
|
321
|
+
*ptr += len;
|
322
|
+
return string;
|
323
|
+
}
|
324
|
+
|
325
|
+
/**
|
326
|
+
* Reads and returns a double encoded in the Watchman binary protocol format,
|
327
|
+
* starting at `ptr` and finishing at or before `end`
|
328
|
+
*/
|
329
|
+
double watchman_load_double(char **ptr, char *end) {
|
330
|
+
*ptr += sizeof(int8_t); // caller has already verified the marker
|
331
|
+
if (*ptr + sizeof(double) > end) {
|
332
|
+
rb_raise(rb_eArgError, "insufficient double storage");
|
333
|
+
}
|
334
|
+
double val = *(double *)*ptr;
|
335
|
+
*ptr += sizeof(double);
|
336
|
+
return val;
|
337
|
+
}
|
338
|
+
|
339
|
+
/**
|
340
|
+
* Helper method which returns length of the array encoded in the Watchman
|
341
|
+
* binary protocol format, starting at `ptr` and finishing at or before `end`
|
342
|
+
*/
|
343
|
+
int64_t watchman_load_array_header(char **ptr, char *end) {
|
344
|
+
if (*ptr >= end) {
|
345
|
+
rb_raise(rb_eArgError, "unexpected end of input");
|
346
|
+
}
|
347
|
+
|
348
|
+
// verify and consume marker
|
349
|
+
if (*ptr[0] != WATCHMAN_ARRAY_MARKER) {
|
350
|
+
rb_raise(rb_eArgError, "not an array");
|
351
|
+
}
|
352
|
+
*ptr += sizeof(int8_t);
|
353
|
+
|
354
|
+
// expect a count
|
355
|
+
if (*ptr + sizeof(int8_t) * 2 > end) {
|
356
|
+
rb_raise(rb_eArgError, "incomplete array header");
|
357
|
+
}
|
358
|
+
return watchman_load_int(ptr, end);
|
359
|
+
}
|
360
|
+
|
361
|
+
/**
|
362
|
+
* Reads and returns an array encoded in the Watchman binary protocol format,
|
363
|
+
* starting at `ptr` and finishing at or before `end`
|
364
|
+
*/
|
365
|
+
VALUE watchman_load_array(char **ptr, char *end) {
|
366
|
+
int64_t count, i;
|
367
|
+
VALUE array;
|
368
|
+
|
369
|
+
count = watchman_load_array_header(ptr, end);
|
370
|
+
array = rb_ary_new2(count);
|
371
|
+
|
372
|
+
for (i = 0; i < count; i++) {
|
373
|
+
rb_ary_push(array, watchman_load(ptr, end));
|
374
|
+
}
|
375
|
+
|
376
|
+
return array;
|
377
|
+
}
|
378
|
+
|
379
|
+
/**
|
380
|
+
* Reads and returns a hash encoded in the Watchman binary protocol format,
|
381
|
+
* starting at `ptr` and finishing at or before `end`
|
382
|
+
*/
|
383
|
+
VALUE watchman_load_hash(char **ptr, char *end) {
|
384
|
+
int64_t count, i;
|
385
|
+
VALUE hash, key, value;
|
386
|
+
|
387
|
+
*ptr += sizeof(int8_t); // caller has already verified the marker
|
388
|
+
|
389
|
+
// expect a count
|
390
|
+
if (*ptr + sizeof(int8_t) * 2 > end) {
|
391
|
+
rb_raise(rb_eArgError, "incomplete hash header");
|
392
|
+
}
|
393
|
+
count = watchman_load_int(ptr, end);
|
394
|
+
|
395
|
+
hash = rb_hash_new();
|
396
|
+
|
397
|
+
for (i = 0; i < count; i++) {
|
398
|
+
key = watchman_load_string(ptr, end);
|
399
|
+
value = watchman_load(ptr, end);
|
400
|
+
rb_hash_aset(hash, key, value);
|
401
|
+
}
|
402
|
+
|
403
|
+
return hash;
|
404
|
+
}
|
405
|
+
|
406
|
+
/**
|
407
|
+
* Reads and returns a templated array encoded in the Watchman binary protocol
|
408
|
+
* format, starting at `ptr` and finishing at or before `end`
|
409
|
+
*
|
410
|
+
* Templated arrays are arrays of hashes which have repetitive key information
|
411
|
+
* pulled out into a separate "headers" prefix.
|
412
|
+
*
|
413
|
+
* @see https://github.com/facebook/watchman/blob/master/BSER.markdown
|
414
|
+
*/
|
415
|
+
VALUE watchman_load_template(char **ptr, char *end) {
|
416
|
+
int64_t header_items_count, i, row_count;
|
417
|
+
VALUE array, hash, header, key, value;
|
418
|
+
|
419
|
+
*ptr += sizeof(int8_t); // caller has already verified the marker
|
420
|
+
|
421
|
+
// process template header array
|
422
|
+
header_items_count = watchman_load_array_header(ptr, end);
|
423
|
+
header = rb_ary_new2(header_items_count);
|
424
|
+
for (i = 0; i < header_items_count; i++) {
|
425
|
+
rb_ary_push(header, watchman_load_string(ptr, end));
|
426
|
+
}
|
427
|
+
|
428
|
+
// process row items
|
429
|
+
row_count = watchman_load_int(ptr, end);
|
430
|
+
array = rb_ary_new2(header_items_count);
|
431
|
+
while (row_count--) {
|
432
|
+
hash = rb_hash_new();
|
433
|
+
for (i = 0; i < header_items_count; i++) {
|
434
|
+
if (*ptr >= end) {
|
435
|
+
rb_raise(rb_eArgError, "unexpected end of input");
|
436
|
+
}
|
437
|
+
|
438
|
+
if (*ptr[0] == WATCHMAN_SKIP_MARKER) {
|
439
|
+
*ptr += sizeof(uint8_t);
|
440
|
+
} else {
|
441
|
+
value = watchman_load(ptr, end);
|
442
|
+
key = rb_ary_entry(header, i);
|
443
|
+
rb_hash_aset(hash, key, value);
|
444
|
+
}
|
445
|
+
}
|
446
|
+
rb_ary_push(array, hash);
|
447
|
+
}
|
448
|
+
return array;
|
449
|
+
}
|
450
|
+
|
451
|
+
/**
|
452
|
+
* Reads and returns an object encoded in the Watchman binary protocol format,
|
453
|
+
* starting at `ptr` and finishing at or before `end`
|
454
|
+
*/
|
455
|
+
VALUE watchman_load(char **ptr, char *end) {
|
456
|
+
if (*ptr >= end) {
|
457
|
+
rb_raise(rb_eArgError, "unexpected end of input");
|
458
|
+
}
|
459
|
+
|
460
|
+
switch (*ptr[0]) {
|
461
|
+
case WATCHMAN_ARRAY_MARKER:
|
462
|
+
return watchman_load_array(ptr, end);
|
463
|
+
case WATCHMAN_HASH_MARKER:
|
464
|
+
return watchman_load_hash(ptr, end);
|
465
|
+
case WATCHMAN_STRING_MARKER:
|
466
|
+
return watchman_load_string(ptr, end);
|
467
|
+
case WATCHMAN_INT8_MARKER:
|
468
|
+
case WATCHMAN_INT16_MARKER:
|
469
|
+
case WATCHMAN_INT32_MARKER:
|
470
|
+
case WATCHMAN_INT64_MARKER:
|
471
|
+
return LL2NUM(watchman_load_int(ptr, end));
|
472
|
+
case WATCHMAN_FLOAT_MARKER:
|
473
|
+
return rb_float_new(watchman_load_double(ptr, end));
|
474
|
+
case WATCHMAN_TRUE:
|
475
|
+
*ptr += 1;
|
476
|
+
return Qtrue;
|
477
|
+
case WATCHMAN_FALSE:
|
478
|
+
*ptr += 1;
|
479
|
+
return Qfalse;
|
480
|
+
case WATCHMAN_NIL:
|
481
|
+
*ptr += 1;
|
482
|
+
return Qnil;
|
483
|
+
case WATCHMAN_TEMPLATE_MARKER:
|
484
|
+
return watchman_load_template(ptr, end);
|
485
|
+
default:
|
486
|
+
rb_raise(rb_eTypeError, "unsupported type");
|
487
|
+
}
|
488
|
+
|
489
|
+
return Qnil; // keep the compiler happy
|
490
|
+
}
|
491
|
+
|
492
|
+
/**
|
493
|
+
* CommandT::Watchman::Utils.load(serialized)
|
494
|
+
*
|
495
|
+
* Converts the binary object, `serialized`, from the Watchman binary protocol
|
496
|
+
* format into a normal Ruby object.
|
497
|
+
*/
|
498
|
+
VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized) {
|
499
|
+
serialized = StringValue(serialized);
|
500
|
+
long len = RSTRING_LEN(serialized);
|
501
|
+
char *ptr = RSTRING_PTR(serialized);
|
502
|
+
char *end = ptr + len;
|
503
|
+
|
504
|
+
// expect at least the binary marker and a int8_t length counter
|
505
|
+
if ((size_t)len < sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) * 2) {
|
506
|
+
rb_raise(rb_eArgError, "undersized header");
|
507
|
+
}
|
508
|
+
|
509
|
+
if (memcmp(ptr, WATCHMAN_BINARY_MARKER, sizeof(WATCHMAN_BINARY_MARKER) - 1)) {
|
510
|
+
rb_raise(rb_eArgError, "missing binary marker");
|
511
|
+
}
|
512
|
+
|
513
|
+
// get size marker
|
514
|
+
ptr += sizeof(WATCHMAN_BINARY_MARKER) - 1;
|
515
|
+
uint64_t payload_size = watchman_load_int(&ptr, end);
|
516
|
+
if (!payload_size) {
|
517
|
+
rb_raise(rb_eArgError, "empty payload");
|
518
|
+
}
|
519
|
+
|
520
|
+
// sanity check length
|
521
|
+
if (ptr + payload_size != end) {
|
522
|
+
rb_raise(rb_eArgError, "payload size mismatch (%lu)", end - (ptr + payload_size));
|
523
|
+
}
|
524
|
+
|
525
|
+
VALUE loaded = watchman_load(&ptr, end);
|
526
|
+
|
527
|
+
// one more sanity check
|
528
|
+
if (ptr != end) {
|
529
|
+
rb_raise(rb_eArgError, "payload termination mismatch (%lu)", end - ptr);
|
530
|
+
}
|
531
|
+
|
532
|
+
return loaded;
|
533
|
+
}
|
534
|
+
|
535
|
+
/**
|
536
|
+
* CommandT::Watchman::Utils.dump(serializable)
|
537
|
+
*
|
538
|
+
* Converts the Ruby object, `serializable`, into a binary string in the
|
539
|
+
* Watchman binary protocol format.
|
540
|
+
*
|
541
|
+
* Examples of serializable objects include arrays, hashes, strings, numbers
|
542
|
+
* (integers, floats), booleans, and nil.
|
543
|
+
*/
|
544
|
+
VALUE CommandTWatchmanUtils_dump(VALUE self, VALUE serializable) {
|
545
|
+
watchman_t *w = watchman_init();
|
546
|
+
watchman_dump(w, serializable);
|
547
|
+
|
548
|
+
// update header with final length information
|
549
|
+
uint64_t *len = (uint64_t *)(w->data + sizeof(WATCHMAN_HEADER) - sizeof(uint64_t) - 1);
|
550
|
+
*len = w->len - sizeof(WATCHMAN_HEADER) + 1;
|
551
|
+
|
552
|
+
// prepare final return value
|
553
|
+
VALUE serialized = rb_str_buf_new(w->len);
|
554
|
+
rb_str_buf_cat(serialized, (const char*)w->data, w->len);
|
555
|
+
watchman_free(w);
|
556
|
+
return serialized;
|
557
|
+
}
|
558
|
+
|
559
|
+
/**
|
560
|
+
* Helper method for raising a SystemCallError wrapping a lower-level error code
|
561
|
+
* coming from the `errno` global variable.
|
562
|
+
*/
|
563
|
+
void watchman_raise_system_call_error(int number) {
|
564
|
+
VALUE error = INT2FIX(number);
|
565
|
+
rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError));
|
566
|
+
}
|
567
|
+
|
568
|
+
// How far we have to look to figure out the size of the PDU header
|
569
|
+
#define WATCHMAN_SNIFF_BUFFER_SIZE sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t)
|
570
|
+
|
571
|
+
// How far we have to peek, at most, to figure out the size of the PDU itself
|
572
|
+
#define WATCHMAN_PEEK_BUFFER_SIZE \
|
573
|
+
sizeof(WATCHMAN_BINARY_MARKER) - 1 + \
|
574
|
+
sizeof(WATCHMAN_INT64_MARKER) + \
|
575
|
+
sizeof(int64_t)
|
576
|
+
|
577
|
+
/**
|
578
|
+
* CommandT::Watchman::Utils.query(query, socket)
|
579
|
+
*
|
580
|
+
* Converts `query`, a Watchman query comprising Ruby objects, into the Watchman
|
581
|
+
* binary protocol format, transmits it over socket, and unserializes and
|
582
|
+
* returns the result.
|
583
|
+
*/
|
584
|
+
VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) {
|
585
|
+
int fileno = NUM2INT(rb_funcall(socket, rb_intern("fileno"), 0));
|
586
|
+
|
587
|
+
// do blocking I/O to simplify the following logic
|
588
|
+
int flags = fcntl(fileno, F_GETFL);
|
589
|
+
if (fcntl(fileno, F_SETFL, flags & ~O_NONBLOCK) == -1) {
|
590
|
+
rb_raise(rb_eRuntimeError, "unable to clear O_NONBLOCK flag");
|
591
|
+
}
|
592
|
+
|
593
|
+
// send the message
|
594
|
+
VALUE serialized = CommandTWatchmanUtils_dump(self, query);
|
595
|
+
long query_len = RSTRING_LEN(serialized);
|
596
|
+
ssize_t sent = send(fileno, RSTRING_PTR(serialized), query_len, 0);
|
597
|
+
if (sent == -1) {
|
598
|
+
watchman_raise_system_call_error(errno);
|
599
|
+
} else if (sent != query_len) {
|
600
|
+
rb_raise(rb_eRuntimeError, "expected to send %ld bytes but sent %ld",
|
601
|
+
query_len, sent);
|
602
|
+
}
|
603
|
+
|
604
|
+
// sniff to see how large the header is
|
605
|
+
int8_t peek[WATCHMAN_PEEK_BUFFER_SIZE];
|
606
|
+
ssize_t received = recv(fileno, peek, WATCHMAN_SNIFF_BUFFER_SIZE, MSG_PEEK | MSG_WAITALL);
|
607
|
+
if (received == -1) {
|
608
|
+
watchman_raise_system_call_error(errno);
|
609
|
+
} else if (received != WATCHMAN_SNIFF_BUFFER_SIZE) {
|
610
|
+
rb_raise(rb_eRuntimeError, "failed to sniff PDU header");
|
611
|
+
}
|
612
|
+
|
613
|
+
// peek at size of PDU
|
614
|
+
int8_t sizes[] = { 0, 0, 0, 1, 2, 4, 8 };
|
615
|
+
ssize_t peek_size = sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) +
|
616
|
+
sizes[peek[sizeof(WATCHMAN_BINARY_MARKER) - 1]];
|
617
|
+
|
618
|
+
received = recv(fileno, peek, peek_size, MSG_PEEK);
|
619
|
+
if (received == -1) {
|
620
|
+
watchman_raise_system_call_error(errno);
|
621
|
+
} else if (received != peek_size) {
|
622
|
+
rb_raise(rb_eRuntimeError, "failed to peek at PDU header");
|
623
|
+
}
|
624
|
+
int8_t *pdu_size_ptr = peek + sizeof(WATCHMAN_BINARY_MARKER) - sizeof(int8_t);
|
625
|
+
int64_t payload_size =
|
626
|
+
peek_size +
|
627
|
+
watchman_load_int((char **)&pdu_size_ptr, (char *)peek + peek_size);
|
628
|
+
|
629
|
+
// actually read the PDU
|
630
|
+
void *buffer = xmalloc(payload_size);
|
631
|
+
if (!buffer) {
|
632
|
+
rb_raise(rb_eNoMemError, "failed to allocate %lld bytes", payload_size);
|
633
|
+
}
|
634
|
+
received = recv(fileno, buffer, payload_size, MSG_WAITALL);
|
635
|
+
if (received == -1) {
|
636
|
+
watchman_raise_system_call_error(errno);
|
637
|
+
} else if (received != payload_size) {
|
638
|
+
rb_raise(rb_eRuntimeError, "failed to load PDU");
|
639
|
+
}
|
640
|
+
char *payload = buffer + peek_size;
|
641
|
+
VALUE loaded = watchman_load(&payload, payload + payload_size);
|
642
|
+
free(buffer);
|
643
|
+
return loaded;
|
644
|
+
}
|
645
|
+
|
646
|
+
#else /* don't build Watchman utils; supply stubs only*/
|
647
|
+
|
648
|
+
VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized) {
|
649
|
+
rb_raise(rb_eRuntimeError, "unsupported operation");
|
650
|
+
}
|
651
|
+
|
652
|
+
VALUE CommandTWatchmanUtils_dump(VALUE self, VALUE serializable) {
|
653
|
+
rb_raise(rb_eRuntimeError, "unsupported operation");
|
654
|
+
}
|
655
|
+
|
656
|
+
VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) {
|
657
|
+
rb_raise(rb_eRuntimeError, "unsupported operation");
|
658
|
+
}
|
659
|
+
|
660
|
+
#endif
|