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.
@@ -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_
@@ -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, &params);
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
+ }
@@ -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
@@ -0,0 +1,8 @@
1
+ {
2
+ global:
3
+ Init_qfs_ext;
4
+ rb_*;
5
+ ruby_*;
6
+ local:
7
+ *;
8
+ };
@@ -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_
@@ -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