qfs 0.0.13
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.
- data/.gitignore +15 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +201 -0
- data/README.md +56 -0
- data/Rakefile +19 -0
- data/ext/qfs/attr.c +54 -0
- data/ext/qfs/attr.h +10 -0
- data/ext/qfs/extconf.rb +33 -0
- data/ext/qfs/file.c +133 -0
- data/ext/qfs/file.h +19 -0
- data/ext/qfs/qfs.c +367 -0
- data/ext/qfs/qfs.h +20 -0
- data/ext/qfs/qfs_ext.version +8 -0
- data/ext/qfs/util.h +29 -0
- data/lib/qfs.rb +390 -0
- data/lib/qfs/version.rb +3 -0
- data/qfs.gemspec +29 -0
- data/test/qfs_test.rb +393 -0
- data/test/test_helper.rb +2 -0
- metadata +149 -0
data/ext/qfs/file.h
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#ifndef QFS_EXT_FILE_H_
|
2
|
+
#define QFS_EXT_FILE_H_
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
|
6
|
+
extern VALUE cQfsFile;
|
7
|
+
|
8
|
+
struct qfs_file {
|
9
|
+
VALUE client;
|
10
|
+
int fd;
|
11
|
+
};
|
12
|
+
|
13
|
+
void qfs_file_deallocate(void*);
|
14
|
+
void qfs_file_mark(void*);
|
15
|
+
VALUE qfs_file_allocate(VALUE);
|
16
|
+
|
17
|
+
void init_qfs_ext_file(void);
|
18
|
+
|
19
|
+
#endif // QFS_FILE_H_
|
data/ext/qfs/qfs.c
ADDED
@@ -0,0 +1,367 @@
|
|
1
|
+
#include "qfs.h"
|
2
|
+
|
3
|
+
#include <ruby.h>
|
4
|
+
#include "attr.h"
|
5
|
+
#include "file.h"
|
6
|
+
#include "util.h"
|
7
|
+
|
8
|
+
// This is the base size of a buffer to contain the working directory
|
9
|
+
#ifndef BASE_WD_BUFFER_SIZE
|
10
|
+
#define BASE_WD_BUFFER_SIZE 512
|
11
|
+
#endif
|
12
|
+
|
13
|
+
// The maximum size this buffer should ever grow to (10kb)
|
14
|
+
#ifndef MAX_WD_BUFFER_SIZE
|
15
|
+
#define MAX_WD_BUFFER_SIZE 4096
|
16
|
+
#endif
|
17
|
+
|
18
|
+
VALUE mQfs;
|
19
|
+
VALUE eQfsError;
|
20
|
+
|
21
|
+
static VALUE cQfsBaseClient;
|
22
|
+
|
23
|
+
// Ruby Errno::* exceptions
|
24
|
+
VALUE rb_eErrnoENOENT;
|
25
|
+
VALUE rb_eErrnoENAMETOOLONG;
|
26
|
+
|
27
|
+
/* qfs_client */
|
28
|
+
|
29
|
+
static void qfs_client_deallocate(void *qfsvp) {
|
30
|
+
TRACE;
|
31
|
+
struct qfs_client *qfs = qfsvp;
|
32
|
+
if (qfs->qfs) {
|
33
|
+
qfs_release(qfs->qfs);
|
34
|
+
qfs->qfs = NULL;
|
35
|
+
}
|
36
|
+
free(qfs);
|
37
|
+
TRACE_R;
|
38
|
+
}
|
39
|
+
|
40
|
+
static VALUE qfs_client_allocate(VALUE klass) {
|
41
|
+
struct qfs_client *qfs = malloc(sizeof(struct qfs_client));
|
42
|
+
qfs->qfs = NULL;
|
43
|
+
return Data_Wrap_Struct(klass, NULL, qfs_client_deallocate, qfs);
|
44
|
+
}
|
45
|
+
|
46
|
+
/* qfs_connect wrapper. Raises Qfs::Error on error */
|
47
|
+
static VALUE qfs_client_connect(VALUE self, VALUE host, VALUE port) {
|
48
|
+
struct qfs_client *qfs;
|
49
|
+
Check_Type(host, T_STRING);
|
50
|
+
Check_Type(port, T_FIXNUM);
|
51
|
+
Data_Get_Struct(self, struct qfs_client, qfs);
|
52
|
+
qfs->qfs = qfs_connect(StringValueCStr(host), FIX2INT(port));
|
53
|
+
if (!qfs->qfs) {
|
54
|
+
rb_raise(eQfsError, "Connection failed");
|
55
|
+
}
|
56
|
+
return Qnil;
|
57
|
+
}
|
58
|
+
|
59
|
+
/* qfs_release wrapper */
|
60
|
+
static VALUE qfs_client_release(VALUE self) {
|
61
|
+
TRACE;
|
62
|
+
struct qfs_client *qfs;
|
63
|
+
Data_Get_Struct(self, struct qfs_client, qfs);
|
64
|
+
if (qfs->qfs) {
|
65
|
+
qfs_release(qfs->qfs);
|
66
|
+
}
|
67
|
+
qfs->qfs = NULL;
|
68
|
+
TRACE_R;
|
69
|
+
return Qnil;
|
70
|
+
}
|
71
|
+
|
72
|
+
static VALUE qfs_client_open(int argc, VALUE *argv, VALUE self) {
|
73
|
+
struct qfs_client *client;
|
74
|
+
VALUE path;
|
75
|
+
VALUE oflag;
|
76
|
+
VALUE mode;
|
77
|
+
VALUE params;
|
78
|
+
rb_scan_args(argc, argv, "13", &path, &oflag, &mode, ¶ms);
|
79
|
+
Check_Type(path, T_STRING);
|
80
|
+
int ioflag;
|
81
|
+
uint16_t imode;
|
82
|
+
char *sparams;
|
83
|
+
if (oflag == Qnil) {
|
84
|
+
ioflag = O_RDONLY;
|
85
|
+
} else {
|
86
|
+
Check_Type(oflag, T_FIXNUM);
|
87
|
+
ioflag = FIX2INT(oflag);
|
88
|
+
}
|
89
|
+
if (mode == Qnil) {
|
90
|
+
imode = 0666;
|
91
|
+
} else {
|
92
|
+
Check_Type(mode, T_FIXNUM);
|
93
|
+
imode = (uint16_t)FIX2INT(mode);
|
94
|
+
}
|
95
|
+
if (params == Qnil) {
|
96
|
+
sparams = NULL;
|
97
|
+
} else {
|
98
|
+
Check_Type(params, T_STRING);
|
99
|
+
sparams = StringValueCStr(params);
|
100
|
+
}
|
101
|
+
|
102
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
103
|
+
int fd = qfs_open_file(client->qfs, StringValueCStr(path), ioflag, imode, sparams);
|
104
|
+
QFS_CHECK_ERR(fd);
|
105
|
+
struct qfs_file *file = ALLOC(struct qfs_file);
|
106
|
+
file->client = self;
|
107
|
+
file->fd = fd;
|
108
|
+
return Data_Wrap_Struct(cQfsFile, qfs_file_mark, qfs_file_deallocate, file);
|
109
|
+
}
|
110
|
+
|
111
|
+
static VALUE qfs_client_readdir(VALUE self, VALUE path) {
|
112
|
+
int left;
|
113
|
+
int count = 0;
|
114
|
+
Check_Type(path, T_STRING);
|
115
|
+
char *p = StringValueCStr(path);
|
116
|
+
struct qfs_iter *iter = NULL;
|
117
|
+
struct qfs_attr attr;
|
118
|
+
struct qfs_client *client;
|
119
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
120
|
+
while ((left = qfs_readdir(client->qfs, p, &iter, &attr)) > 0) {
|
121
|
+
struct qfs_attr *tmp_attr = ALLOC(struct qfs_attr);
|
122
|
+
memcpy(tmp_attr, &attr, sizeof(attr));
|
123
|
+
count += 1;
|
124
|
+
rb_yield(Data_Wrap_Struct(cQfsAttr, NULL, free, tmp_attr));
|
125
|
+
}
|
126
|
+
qfs_iter_free(&iter);
|
127
|
+
QFS_CHECK_ERR(left);
|
128
|
+
return INT2FIX(count);
|
129
|
+
}
|
130
|
+
|
131
|
+
static VALUE qfs_client_path_checking(VALUE self, VALUE path,
|
132
|
+
bool (*check_func)(struct QFS*, const char*)) {
|
133
|
+
Check_Type(path, T_STRING);
|
134
|
+
char *p = StringValueCStr(path);
|
135
|
+
struct qfs_client *client;
|
136
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
137
|
+
bool exists = (*check_func)(client->qfs, p);
|
138
|
+
return INT2BOOL(exists);
|
139
|
+
}
|
140
|
+
|
141
|
+
static VALUE qfs_client_exists(VALUE self, VALUE path) {
|
142
|
+
return qfs_client_path_checking(self, path, qfs_exists);
|
143
|
+
}
|
144
|
+
|
145
|
+
static VALUE qfs_client_isfile(VALUE self, VALUE path) {
|
146
|
+
return qfs_client_path_checking(self, path, qfs_isfile);
|
147
|
+
}
|
148
|
+
|
149
|
+
static VALUE qfs_client_isdirectory(VALUE self, VALUE path) {
|
150
|
+
return qfs_client_path_checking(self, path, qfs_isdirectory);
|
151
|
+
}
|
152
|
+
|
153
|
+
static VALUE qfs_client_remove(VALUE self, VALUE path) {
|
154
|
+
Check_Type(path, T_STRING);
|
155
|
+
char *p = StringValueCStr(path);
|
156
|
+
|
157
|
+
// Check that the file is regular
|
158
|
+
VALUE isfile = qfs_client_isfile(self, path);
|
159
|
+
if (!RTEST(isfile)) {
|
160
|
+
rb_raise(eQfsError, "Not a regular file - %s", p);
|
161
|
+
}
|
162
|
+
|
163
|
+
struct qfs_client *client;
|
164
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
165
|
+
int res = qfs_remove(client->qfs, p);
|
166
|
+
|
167
|
+
// Raise an exception if the file didn't exist
|
168
|
+
if (res == -2) {
|
169
|
+
rb_raise(rb_eErrnoENOENT, "No such file or directory - %s", p);
|
170
|
+
}
|
171
|
+
|
172
|
+
QFS_CHECK_ERR(res);
|
173
|
+
return INT2NUM(1);
|
174
|
+
}
|
175
|
+
|
176
|
+
static VALUE qfs_client_mkdir_base(VALUE self, VALUE path, VALUE mode,
|
177
|
+
int (*mkdir_func)(struct QFS*, const char*, mode_t)) {
|
178
|
+
Check_Type(path, T_STRING);
|
179
|
+
char *p = StringValueCStr(path);
|
180
|
+
|
181
|
+
// check if the directory already exists
|
182
|
+
if (RTEST(qfs_client_exists(self, path))) {
|
183
|
+
rb_raise(eQfsError, "File exists - %s", p);
|
184
|
+
}
|
185
|
+
|
186
|
+
struct qfs_client *client;
|
187
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
188
|
+
Check_Type(mode, T_FIXNUM);
|
189
|
+
uint16_t imode = (uint16_t)FIX2INT(mode);
|
190
|
+
int res = (*mkdir_func)(client->qfs, p, imode);
|
191
|
+
QFS_CHECK_ERR(res);
|
192
|
+
return RES2BOOL(res);
|
193
|
+
}
|
194
|
+
|
195
|
+
static VALUE qfs_client_mkdir(VALUE self, VALUE path, VALUE mode) {
|
196
|
+
return qfs_client_mkdir_base(self, path, mode, qfs_mkdir);
|
197
|
+
}
|
198
|
+
|
199
|
+
static VALUE qfs_client_mkdir_p(VALUE self, VALUE path, VALUE mode) {
|
200
|
+
return qfs_client_mkdir_base(self, path, mode, qfs_mkdirs);
|
201
|
+
}
|
202
|
+
|
203
|
+
static VALUE qfs_client_rmdir_base(VALUE self, VALUE path,
|
204
|
+
int (*rmdir_func)(struct QFS*, const char*)) {
|
205
|
+
Check_Type(path, T_STRING);
|
206
|
+
char *p = StringValueCStr(path);
|
207
|
+
|
208
|
+
struct qfs_client *client;
|
209
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
210
|
+
int res = (*rmdir_func)(client->qfs, p);
|
211
|
+
|
212
|
+
// Raise an exception if the file didn't exist
|
213
|
+
if (res == -2) {
|
214
|
+
rb_raise(rb_eErrnoENOENT, "No such file or directory - %s", p);
|
215
|
+
}
|
216
|
+
|
217
|
+
QFS_CHECK_ERR(res);
|
218
|
+
return INT2NUM(res);
|
219
|
+
}
|
220
|
+
|
221
|
+
static VALUE qfs_client_rmdir(VALUE self, VALUE path) {
|
222
|
+
return qfs_client_rmdir_base(self, path, qfs_rmdir);
|
223
|
+
}
|
224
|
+
|
225
|
+
static VALUE qfs_client_rmdirs(VALUE self, VALUE path) {
|
226
|
+
return qfs_client_rmdir_base(self, path, qfs_rmdirs);
|
227
|
+
}
|
228
|
+
|
229
|
+
// Making multiple stat calls against the same file appears to return the
|
230
|
+
// same result, event if the properties change. This can be reproduced by
|
231
|
+
// the permissions on a file, stat'ing it, setting the permissions to something
|
232
|
+
// else, and stat'ing it again. This appears to be a property of the
|
233
|
+
// QFS C api.
|
234
|
+
static VALUE qfs_client_stat(VALUE self, VALUE path) {
|
235
|
+
Check_Type(path, T_STRING);
|
236
|
+
char *p = StringValueCStr(path);
|
237
|
+
struct qfs_client *client;
|
238
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
239
|
+
struct qfs_attr *attr = ALLOC(struct qfs_attr);
|
240
|
+
int res = qfs_stat(client->qfs, p, attr);
|
241
|
+
QFS_CHECK_ERR(res);
|
242
|
+
return Data_Wrap_Struct(cQfsAttr, NULL, free, attr);
|
243
|
+
}
|
244
|
+
|
245
|
+
static VALUE qfs_client_chmod_base(VALUE self, VALUE path, VALUE mode,
|
246
|
+
int (*chmod_func)(struct QFS*, const char*, mode_t)) {
|
247
|
+
Check_Type(path, T_STRING);
|
248
|
+
Check_Type(mode, T_FIXNUM);
|
249
|
+
struct qfs_client *client;
|
250
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
251
|
+
char *p = StringValueCStr(path);
|
252
|
+
uint16_t imode = (uint16_t)FIX2INT(mode);
|
253
|
+
int res = (*chmod_func)(client->qfs, p, imode);
|
254
|
+
QFS_CHECK_ERR(res);
|
255
|
+
return RES2BOOL(res);
|
256
|
+
}
|
257
|
+
|
258
|
+
static VALUE qfs_client_chmod(VALUE self, VALUE path, VALUE mode) {
|
259
|
+
return qfs_client_chmod_base(self, path, mode, qfs_chmod);
|
260
|
+
}
|
261
|
+
|
262
|
+
static VALUE qfs_client_chmod_r(VALUE self, VALUE path, VALUE mode) {
|
263
|
+
return qfs_client_chmod_base(self, path, mode, qfs_chmod_r);
|
264
|
+
}
|
265
|
+
|
266
|
+
static VALUE qfs_client_rename(VALUE self, VALUE old, VALUE new) {
|
267
|
+
Check_Type(old, T_STRING);
|
268
|
+
Check_Type(new, T_STRING);
|
269
|
+
struct qfs_client *client;
|
270
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
271
|
+
char *old_s = StringValueCStr(old);
|
272
|
+
char *new_s = StringValueCStr(new);
|
273
|
+
int res = qfs_rename(client->qfs, old_s, new_s);
|
274
|
+
QFS_CHECK_ERR(res);
|
275
|
+
return RES2BOOL(res);
|
276
|
+
}
|
277
|
+
|
278
|
+
static VALUE qfs_set_attribute_revalidate_time(VALUE self, VALUE seconds) {
|
279
|
+
Check_Type(seconds, T_FIXNUM);
|
280
|
+
int secs = FIX2INT(seconds);
|
281
|
+
struct qfs_client *client;
|
282
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
283
|
+
qfs_set_fileattributerevalidatetime(client->qfs, secs);
|
284
|
+
return Qnil;
|
285
|
+
}
|
286
|
+
|
287
|
+
static VALUE qfs_client_cd_base(VALUE self, VALUE path,
|
288
|
+
int (*cd_func)(struct QFS*, const char*)) {
|
289
|
+
Check_Type(path, T_STRING);
|
290
|
+
struct qfs_client *client;
|
291
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
292
|
+
char *p = StringValueCStr(path);
|
293
|
+
int res = cd_func(client->qfs, p);
|
294
|
+
QFS_CHECK_ERR(res);
|
295
|
+
return Qnil;
|
296
|
+
}
|
297
|
+
|
298
|
+
static VALUE qfs_client_cd(VALUE self, VALUE path) {
|
299
|
+
return qfs_client_cd_base(self, path, qfs_cd);
|
300
|
+
}
|
301
|
+
|
302
|
+
static VALUE qfs_client_setwd(VALUE self, VALUE path) {
|
303
|
+
return qfs_client_cd_base(self, path, qfs_setwd);
|
304
|
+
}
|
305
|
+
|
306
|
+
static VALUE qfs_client_getwd(VALUE self) {
|
307
|
+
struct qfs_client *client;
|
308
|
+
Data_Get_Struct(self, struct qfs_client, client);
|
309
|
+
|
310
|
+
size_t n = (size_t)BASE_WD_BUFFER_SIZE;
|
311
|
+
while (n < (size_t)MAX_WD_BUFFER_SIZE) {
|
312
|
+
VALUE str = rb_str_buf_new((long)n);
|
313
|
+
int len_read = qfs_getwd(client->qfs, RSTRING_PTR(str), n);
|
314
|
+
QFS_CHECK_ERR(len_read);
|
315
|
+
// Return if the entire WD was read into the buffer
|
316
|
+
if (len_read < (int)n) {
|
317
|
+
rb_str_set_len(str, len_read);
|
318
|
+
return str;
|
319
|
+
}
|
320
|
+
|
321
|
+
n = n * 2; // Grow the buffer
|
322
|
+
}
|
323
|
+
|
324
|
+
// The path was too long, raise an exception. This is just a sanity
|
325
|
+
// check, in practice there is no reason that a path should be greater
|
326
|
+
// than 10kb in size.
|
327
|
+
rb_raise(rb_eErrnoENAMETOOLONG, "Working directory path too long");
|
328
|
+
}
|
329
|
+
|
330
|
+
void Init_qfs_ext() {
|
331
|
+
mQfs = rb_define_module("Qfs");
|
332
|
+
|
333
|
+
check_trace_enabled();
|
334
|
+
|
335
|
+
cQfsBaseClient = rb_define_class_under(mQfs, "BaseClient", rb_cObject);
|
336
|
+
rb_define_alloc_func(cQfsBaseClient, qfs_client_allocate);
|
337
|
+
rb_define_method(cQfsBaseClient, "initialize", qfs_client_connect, 2);
|
338
|
+
rb_define_method(cQfsBaseClient, "release", qfs_client_release, 0);
|
339
|
+
rb_define_method(cQfsBaseClient, "open", qfs_client_open, -1);
|
340
|
+
rb_define_method(cQfsBaseClient, "readdir", qfs_client_readdir, 1);
|
341
|
+
rb_define_method(cQfsBaseClient, "exists", qfs_client_exists, 1);
|
342
|
+
rb_define_method(cQfsBaseClient, "remove", qfs_client_remove, 1);
|
343
|
+
rb_define_method(cQfsBaseClient, "isfile", qfs_client_isfile, 1);
|
344
|
+
rb_define_method(cQfsBaseClient, "isdirectory", qfs_client_isdirectory, 1);
|
345
|
+
rb_define_method(cQfsBaseClient, "mkdir", qfs_client_mkdir, 2);
|
346
|
+
rb_define_method(cQfsBaseClient, "mkdir_p", qfs_client_mkdir_p, 2);
|
347
|
+
rb_define_method(cQfsBaseClient, "rmdir", qfs_client_rmdir, 1);
|
348
|
+
rb_define_method(cQfsBaseClient, "rmdirs", qfs_client_rmdirs, 1);
|
349
|
+
rb_define_method(cQfsBaseClient, "stat", qfs_client_stat, 1);
|
350
|
+
rb_define_method(cQfsBaseClient, "chmod", qfs_client_chmod, 2);
|
351
|
+
rb_define_method(cQfsBaseClient, "rename", qfs_client_rename, 2);
|
352
|
+
rb_define_method(cQfsBaseClient, "cd", qfs_client_cd, 1);
|
353
|
+
rb_define_method(cQfsBaseClient, "setwd", qfs_client_setwd, 1);
|
354
|
+
rb_define_protected_method(cQfsBaseClient, "cwd", qfs_client_getwd, 0);
|
355
|
+
rb_define_private_method(cQfsBaseClient, "chmod_r", qfs_client_chmod_r, 2);
|
356
|
+
rb_define_private_method(cQfsBaseClient, "set_attribute_revalidate_time",
|
357
|
+
qfs_set_attribute_revalidate_time, 1);
|
358
|
+
|
359
|
+
init_qfs_ext_file();
|
360
|
+
init_qfs_ext_attr();
|
361
|
+
|
362
|
+
eQfsError = rb_define_class_under(mQfs, "Error", rb_eStandardError);
|
363
|
+
|
364
|
+
// Get the errno exceptions
|
365
|
+
rb_eErrnoENOENT = rb_const_get(rb_mErrno, rb_intern("ENOENT"));
|
366
|
+
rb_eErrnoENAMETOOLONG = rb_const_get(rb_mErrno, rb_intern("ENAMETOOLONG"));
|
367
|
+
}
|
data/ext/qfs/qfs.h
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#ifndef RUBY_QFS_H
|
2
|
+
#define RUBY_QFS_H
|
3
|
+
|
4
|
+
#include <sys/types.h>
|
5
|
+
#include <sys/time.h>
|
6
|
+
#include <kfs/c/qfs.h>
|
7
|
+
#include <ruby.h>
|
8
|
+
|
9
|
+
extern VALUE mQfs;
|
10
|
+
extern VALUE eQfsError;
|
11
|
+
|
12
|
+
// QFS structs
|
13
|
+
|
14
|
+
struct qfs_client {
|
15
|
+
struct QFS *qfs;
|
16
|
+
};
|
17
|
+
|
18
|
+
void Init_qfs_ext(void);
|
19
|
+
|
20
|
+
#endif
|
data/ext/qfs/util.h
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#ifndef QFS_EXT_UTIL_H_
|
2
|
+
#define QFS_EXT_UTIL_H_
|
3
|
+
|
4
|
+
#define QFS_NIL_FD -1
|
5
|
+
|
6
|
+
#include <ruby.h>
|
7
|
+
|
8
|
+
static int QFS_TRACE_ENABLED = 0;
|
9
|
+
static inline void check_trace_enabled() {
|
10
|
+
if (getenv("RUBY_QFS_TRACE")) {
|
11
|
+
QFS_TRACE_ENABLED = 1;
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
#define TRACE do { if (QFS_TRACE_ENABLED) fprintf(stderr, "TRACE: %s start\n", __func__); } while(0)
|
16
|
+
#define TRACE_R do { if (QFS_TRACE_ENABLED) fprintf(stderr, "TRACE: %s end\n", __func__); } while(0)
|
17
|
+
#define WARN(s) do { fprintf(stderr, "WARN: %s\n", s); } while(0)
|
18
|
+
|
19
|
+
#define QFS_CHECK_ERR(i) do { if (i < 0) { \
|
20
|
+
char buf[512]; \
|
21
|
+
rb_raise(eQfsError, "%s", qfs_strerror((int)i, buf, 1024)); \
|
22
|
+
TRACE_R; return Qnil; \
|
23
|
+
} } while (0)
|
24
|
+
|
25
|
+
#define NTIME(x) rb_time_new(x.tv_sec, x.tv_usec)
|
26
|
+
#define INT2BOOL(x) (x?Qtrue:Qfalse)
|
27
|
+
#define RES2BOOL(x) (x>=0 ? Qtrue : Qfalse)
|
28
|
+
|
29
|
+
#endif // QFS_EXT_UTIL_H_
|
data/lib/qfs.rb
ADDED
@@ -0,0 +1,390 @@
|
|
1
|
+
require 'qfs/version'
|
2
|
+
require 'qfs_ext'
|
3
|
+
require 'fcntl'
|
4
|
+
|
5
|
+
##
|
6
|
+
# Container module for QFS classes
|
7
|
+
module Qfs
|
8
|
+
# supported oflags
|
9
|
+
O_CREAT = Fcntl::O_CREAT
|
10
|
+
O_EXCL = Fcntl::O_EXCL
|
11
|
+
O_RDWR = Fcntl::O_RDWR
|
12
|
+
O_RDONLY = Fcntl::O_RDONLY
|
13
|
+
O_WRONLY = Fcntl::O_WRONLY
|
14
|
+
O_TRUNC = Fcntl::O_TRUNC
|
15
|
+
|
16
|
+
# A higher-level Client to interact with QFS. This attempts to use
|
17
|
+
# a similar interface to ruby's native IO functionality.
|
18
|
+
class Client < BaseClient
|
19
|
+
# Open a file on QFS. This method uses a very similar interface to the
|
20
|
+
# 'open' method standard in ruby.
|
21
|
+
#
|
22
|
+
# Modes
|
23
|
+
# * 'r': Read only
|
24
|
+
# * 'w': Write only, overwrite or create new file
|
25
|
+
# * 'a': Append to the file
|
26
|
+
#
|
27
|
+
# @param [String] path the path to the file
|
28
|
+
# @param [Int] mode_str One of the mode types above
|
29
|
+
# @option options [Int] :mode permissions to set on the file
|
30
|
+
# @option options [String] :params
|
31
|
+
#
|
32
|
+
# @return [File] a Qfs::File
|
33
|
+
# @yield [File] a Qfs::File
|
34
|
+
def open(path, mode_str, options = {})
|
35
|
+
flags = mode_to_flags(mode_str)
|
36
|
+
raise Qfs::Error, "#{mode_str} is not a valid mode string" if flags.nil?
|
37
|
+
|
38
|
+
mode ||= options[:mode]
|
39
|
+
params ||= options[:params]
|
40
|
+
f = super(path, flags, mode, params)
|
41
|
+
|
42
|
+
# If the file was opened in append mode, seek to the end to simulate
|
43
|
+
# O_APPEND behavior
|
44
|
+
f.seek(0, IO::SEEK_END) if mode_str == 'a'
|
45
|
+
|
46
|
+
return f unless block_given?
|
47
|
+
|
48
|
+
begin
|
49
|
+
yield f
|
50
|
+
ensure
|
51
|
+
f.close
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Open a connection on the specified host and post, and yield it
|
56
|
+
# to a block
|
57
|
+
#
|
58
|
+
# @example Open a connection and yield to a block
|
59
|
+
# Qfs::Client.with_client('localhost', 10000) do |client|
|
60
|
+
# client.write('/file', '')
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# @param [String] host the hostname to connect to
|
64
|
+
# @param [Int] port the port to connect to
|
65
|
+
#
|
66
|
+
# @yield [Client] a new client
|
67
|
+
def self.with_client(host, port)
|
68
|
+
c = new(host, port)
|
69
|
+
begin
|
70
|
+
yield c
|
71
|
+
ensure
|
72
|
+
c.release
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
alias exists? exists
|
77
|
+
alias exist? exists?
|
78
|
+
|
79
|
+
alias file? isfile
|
80
|
+
|
81
|
+
alias directory? isdirectory
|
82
|
+
|
83
|
+
# Remove a regular file. Pass 'true' to stop exceptions from
|
84
|
+
# being thrown if the file doesn't exist.
|
85
|
+
#
|
86
|
+
# @param [String] path the path to the file
|
87
|
+
# @param [Bool] force Wheather or not to throw an exception if file doesn't
|
88
|
+
# exist.
|
89
|
+
#
|
90
|
+
# @raise [Error] if force=false
|
91
|
+
#
|
92
|
+
# @return [Bool] if or if not the method succeeded
|
93
|
+
def remove(path, force = false)
|
94
|
+
force_remove(force) { super(path) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create a directory
|
98
|
+
#
|
99
|
+
# @param [String] path the path to the directory to make
|
100
|
+
# @param [Int] mode the permissions to set on the new directory
|
101
|
+
#
|
102
|
+
# @return [Bool] if the directory was created
|
103
|
+
def mkdir(path, mode = 0o600)
|
104
|
+
super(path, mode)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create a directory and create parent directories if needed
|
108
|
+
#
|
109
|
+
# @param [String] path the path to the directory to make
|
110
|
+
# @param [Int] mode the permissions to set on the new directory
|
111
|
+
#
|
112
|
+
# @return [Bool] if the directory was created
|
113
|
+
def mkdir_p(path, mode = 0o600)
|
114
|
+
super(path, mode)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Remove a directory
|
118
|
+
#
|
119
|
+
# @param [String] path the path to the file
|
120
|
+
# @param [Bool] force Whether or not to throw an exception if the operation
|
121
|
+
# fails
|
122
|
+
#
|
123
|
+
# @raise [Error] if force=false
|
124
|
+
#
|
125
|
+
# @return [Bool] if or if not the method succeeded
|
126
|
+
def rmdir(path, force = false)
|
127
|
+
force_remove(force) { super(path) }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Remove a directory recursively
|
131
|
+
#
|
132
|
+
# @param [String] path the path to the file
|
133
|
+
# @param [Bool] force Whether or not to throw an exception if the directory
|
134
|
+
# doesn't exist.
|
135
|
+
#
|
136
|
+
# @raise [Error] if force=false
|
137
|
+
#
|
138
|
+
# @return [Bool] if or if not the method succeeded
|
139
|
+
def rmdirs(path, force = false)
|
140
|
+
force_remove(force) { super(path) }
|
141
|
+
end
|
142
|
+
|
143
|
+
# Recursively remove directories and files.
|
144
|
+
#
|
145
|
+
# @param [String] path the path to the file
|
146
|
+
# @param [Bool] force Whether or not to throw an exception if the directory
|
147
|
+
# doesn't exist.
|
148
|
+
#
|
149
|
+
# @raise [Error] if force=false
|
150
|
+
#
|
151
|
+
# @return [Bool] if or if not the method succeeded
|
152
|
+
def rm_rf(path, force = false)
|
153
|
+
force_remove(force) do
|
154
|
+
return remove(path) if file?(path)
|
155
|
+
readdir(path) do |f|
|
156
|
+
fpath = ::File.join(path, f.filename)
|
157
|
+
rm_rf(fpath) if directory?(fpath)
|
158
|
+
remove(fpath) if file?(fpath)
|
159
|
+
end
|
160
|
+
rmdir(path)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Read from a file and return the data
|
165
|
+
#
|
166
|
+
# @param [String] path the path to the file
|
167
|
+
# @param [Int] len the number of bytes to read
|
168
|
+
#
|
169
|
+
# @return [String] the data from the file
|
170
|
+
def read(path, len = nil)
|
171
|
+
open(path, 'r') { |f| f.read(len) }
|
172
|
+
end
|
173
|
+
|
174
|
+
# Write to a file
|
175
|
+
#
|
176
|
+
# @param [String] path the path to the file
|
177
|
+
# @param [String] data the data to be written
|
178
|
+
#
|
179
|
+
# @return [String] the number of bytes written
|
180
|
+
def write(path, data)
|
181
|
+
open(path, 'w') { |f| f.write(data) }
|
182
|
+
end
|
183
|
+
|
184
|
+
# Read from a directory, optionally outputting a list of Attr
|
185
|
+
# objects or yielding to a block
|
186
|
+
#
|
187
|
+
# @example Usage with a block:
|
188
|
+
# client.readdir('/') do |paths|
|
189
|
+
# puts paths.filename
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# @example Usage without a block:
|
193
|
+
# client.readdir('/').each do |paths|
|
194
|
+
# puts paths.filename
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# @param [String] path the path to read from
|
198
|
+
#
|
199
|
+
# @return [Array<Attr>] a list of Attr objects
|
200
|
+
#
|
201
|
+
# @yield [Attr] Attr objects
|
202
|
+
def readdir(path)
|
203
|
+
attrs = []
|
204
|
+
super(path) do |attr|
|
205
|
+
unless current_or_previous_dir?(attr.filename)
|
206
|
+
block_given? ? yield(attr) : attrs.push(attr)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
return attrs unless block_given?
|
211
|
+
end
|
212
|
+
|
213
|
+
# Change the permissions of a file or directory
|
214
|
+
#
|
215
|
+
# @param [String] path Path to the file to chmod
|
216
|
+
# @param [Int] mode_int The permissions to set
|
217
|
+
# @option options [Bool] :recursive Set to recursively chmod the directory
|
218
|
+
def chmod(path, mode_int, options = {})
|
219
|
+
return chmod_r(path, mode_int) if options[:recursive]
|
220
|
+
super(path, mode_int)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Get an Attr object for the file at the specified path
|
224
|
+
#
|
225
|
+
# Note that this method will cache it's result for a specific file for the
|
226
|
+
# entire lifetime of a Client object. If you need to get an updated Attr
|
227
|
+
# for a file/directory, you need to create a new Client.
|
228
|
+
#
|
229
|
+
# @param [String] path The path to the file or directory to stat
|
230
|
+
# @param options [Bool] :refresh If this is set, the file will be opened
|
231
|
+
# and reopened to guarantee stat returns updated values
|
232
|
+
#
|
233
|
+
# @return [Attr] An attr object
|
234
|
+
def stat(path, options = {})
|
235
|
+
# close the file and repoen it guarantee that the newest data is present
|
236
|
+
# opening in a block will guarantee it is closed
|
237
|
+
open(path, 'w') { } if options[:refresh]
|
238
|
+
|
239
|
+
super(path)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Move a file to a new path.
|
243
|
+
#
|
244
|
+
# @param [String] old The path to the file to move
|
245
|
+
# @param [String] new The new destination
|
246
|
+
def move(old, new)
|
247
|
+
rename(old, new)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Change the current working directory
|
251
|
+
#
|
252
|
+
# @param [String] path The directory to change to
|
253
|
+
def cd(path)
|
254
|
+
super(path)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Set the current working directory
|
258
|
+
#
|
259
|
+
# @param [String] path The directory to change to
|
260
|
+
def setwd(path)
|
261
|
+
super(path)
|
262
|
+
end
|
263
|
+
|
264
|
+
# Return the current working directory
|
265
|
+
#
|
266
|
+
# @param [Int] len The length of the buffer that should be allocated
|
267
|
+
# to store the cwd. An exception will be thrown if it
|
268
|
+
# is too small.
|
269
|
+
#
|
270
|
+
# @return [String] The current working directory
|
271
|
+
def cwd
|
272
|
+
super
|
273
|
+
end
|
274
|
+
|
275
|
+
private
|
276
|
+
|
277
|
+
# Return if the specified string is the path to the current
|
278
|
+
# directory '.' or to the previous directory '..'
|
279
|
+
def current_or_previous_dir?(name)
|
280
|
+
case name
|
281
|
+
when '.', '..'
|
282
|
+
true
|
283
|
+
else
|
284
|
+
false
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# If force is true, call the block and just return zero if it
|
289
|
+
# throws an exception
|
290
|
+
def force_remove(force)
|
291
|
+
return yield unless force
|
292
|
+
begin
|
293
|
+
return yield
|
294
|
+
rescue Errno::ENOENT
|
295
|
+
return 0
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# Maps mode strings to oflags
|
300
|
+
MODE_STR_TO_FLAGS = {
|
301
|
+
'r' => Qfs::O_RDONLY,
|
302
|
+
'w' => Qfs::O_WRONLY | Qfs::O_TRUNC | Qfs::O_CREAT,
|
303
|
+
'a' => Qfs::O_WRONLY,
|
304
|
+
}
|
305
|
+
|
306
|
+
##
|
307
|
+
# Convert the mode strings to oflags
|
308
|
+
def mode_to_flags(mode)
|
309
|
+
MODE_STR_TO_FLAGS[mode]
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
##
|
314
|
+
# A File on QFS.
|
315
|
+
class File
|
316
|
+
# Read from a file. Don't specify a length to read the entire file.
|
317
|
+
#
|
318
|
+
# @param [Int] len the number of bytes to read. Omit or set to nil to
|
319
|
+
# read the entire file.
|
320
|
+
#
|
321
|
+
# @return [String] the data read from the file.
|
322
|
+
def read(len = nil)
|
323
|
+
len ||= stat.size
|
324
|
+
read_len(len)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Seek to the specified position in the file. The default 'whence' value
|
328
|
+
# is SEEK_CUR, so the offset will be applied to the current file position.
|
329
|
+
#
|
330
|
+
# @param [Int] offset The offset to seek to
|
331
|
+
# @param [Int] whence One of the following constants: IO::SEEK_CUR,
|
332
|
+
# IO::SEEK_END, IO::SEEK_SET. Defaults to IO::SEEK_CUR
|
333
|
+
#
|
334
|
+
# @return [Int] The current file position
|
335
|
+
def seek(offset, whence=IO::SEEK_CUR)
|
336
|
+
seek_internal(offset, whence)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# A container class for the properties of a file or directory.
|
341
|
+
# These can be retrieved with either Client::stat or File#stat.
|
342
|
+
#
|
343
|
+
# @attr_reader [String] filename The base name of the file/directory
|
344
|
+
# @attr_reader [Int] id
|
345
|
+
# @attr_reader [Int] mode The permissions set on the file/directory
|
346
|
+
# @attr_reader [Int] uid User ID
|
347
|
+
# @attr_reader [Int] gid Group ID
|
348
|
+
# @attr_reader [Time] mtime The time last modified
|
349
|
+
# @attr_reader [Time] ctime The time the file/directory's attributes were
|
350
|
+
# changed
|
351
|
+
# @attr_reader [Bool] directory If the file is a directory
|
352
|
+
# @attr_reader [Int] size The size of the file
|
353
|
+
# @attr_reader [Int] chunks The number of chunks in the file or files in a
|
354
|
+
# directory
|
355
|
+
# @attr_reader [Int] directories The number of subdirectories
|
356
|
+
# @attr_reader [Int] replicas
|
357
|
+
# @attr_reader [Int] stripes
|
358
|
+
# @attr_reader [Int] recovery_stripes
|
359
|
+
# @attr_reader [Int] striper_type
|
360
|
+
# @attr_reader [Int] stripe_size
|
361
|
+
# @attr_reader [Int] min_stier
|
362
|
+
# @attr_reader [Int] max_stier
|
363
|
+
class Attr
|
364
|
+
# Attempt to mimic the format of the QFS "ls" command
|
365
|
+
def to_s
|
366
|
+
[
|
367
|
+
"#{directory? ? 'd' : '-'}#{mode_to_s}",
|
368
|
+
'-',
|
369
|
+
uid,
|
370
|
+
gid,
|
371
|
+
size,
|
372
|
+
mtime.strftime('%Y-%m-%d %H:%M'),
|
373
|
+
filename,
|
374
|
+
].join(' ')
|
375
|
+
end
|
376
|
+
|
377
|
+
private
|
378
|
+
|
379
|
+
##
|
380
|
+
# Get the mode as a typically formatted string
|
381
|
+
# returns a string in the form 'rwxrwxrwx'
|
382
|
+
def mode_to_s
|
383
|
+
m = mode
|
384
|
+
perms = %w(x w r)
|
385
|
+
8.downto(0).reduce('') do |sum, i|
|
386
|
+
sum + ((m & (1 << i)) != 0 ? perms[i % perms.length] : '-')
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|