qfs 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -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