pacct 0.8.0-universal-linux
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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +15 -0
- data/ext/pacct/extconf.rb +9 -0
- data/ext/pacct/pacct_c.c +856 -0
- data/lib/pacct.rb +10 -0
- data/lib/pacct/version.rb +4 -0
- data/pacct.gemspec +23 -0
- data/snapshot/pacct +0 -0
- data/snapshot/pacct_invalid_ids +0 -0
- data/snapshot/pacct_invalid_length +1 -0
- data/spec/entry_spec.rb +46 -0
- data/spec/log_spec.rb +130 -0
- data/spec/pacct_spec.rb +20 -0
- data/spec/spec_helper.rb +6 -0
- metadata +83 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Pacct
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'pacct'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install pacct
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
desc "Run specs"
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
8
|
+
task.rspec_opts =%w{--color --format progress}
|
9
|
+
task.pattern = 'spec/*_spec.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
task :docs do
|
13
|
+
system("rdoc --exclude '(./)?spec/'")
|
14
|
+
end
|
15
|
+
|
data/ext/pacct/pacct_c.c
ADDED
@@ -0,0 +1,856 @@
|
|
1
|
+
#include <errno.h>
|
2
|
+
#include <stdio.h>
|
3
|
+
#include <stdlib.h>
|
4
|
+
|
5
|
+
#include <grp.h>
|
6
|
+
#include <pwd.h>
|
7
|
+
#include <unistd.h>
|
8
|
+
#include <sys/acct.h>
|
9
|
+
#include <sys/types.h>
|
10
|
+
|
11
|
+
#include "ruby.h"
|
12
|
+
|
13
|
+
static char const* validFileModes[] = {
|
14
|
+
"rb",
|
15
|
+
"wb",
|
16
|
+
"r+b",
|
17
|
+
"w+b",
|
18
|
+
};
|
19
|
+
|
20
|
+
static VALUE mPacct;
|
21
|
+
static VALUE cLog;
|
22
|
+
static VALUE cEntry;
|
23
|
+
|
24
|
+
//Classes from Ruby
|
25
|
+
static VALUE cTime;
|
26
|
+
static VALUE cNoMemoryError;
|
27
|
+
static VALUE cSystemCallError;
|
28
|
+
|
29
|
+
//Identifiers
|
30
|
+
static ID id_at;
|
31
|
+
static ID id_new;
|
32
|
+
static ID id_to_i;
|
33
|
+
|
34
|
+
//To do: where is a better place to put these?
|
35
|
+
static VALUE known_users_by_name = Qnil;
|
36
|
+
static VALUE known_groups_by_name = Qnil;
|
37
|
+
static VALUE known_users_by_id = Qnil;
|
38
|
+
static VALUE known_groups_by_id = Qnil;
|
39
|
+
|
40
|
+
//System parameters
|
41
|
+
static int pageSize;
|
42
|
+
static long ticksPerSecond;
|
43
|
+
|
44
|
+
//Converts a comp_t to a long
|
45
|
+
static unsigned long comp_t_to_ulong(comp_t c) {
|
46
|
+
return (unsigned long)(c & 0x1fff) << (((c >> 13) & 0x7) * 3);
|
47
|
+
}
|
48
|
+
|
49
|
+
//Prints a number in binary (for debugging)
|
50
|
+
static void print_bin(unsigned long val) {
|
51
|
+
//Cast prevents warning
|
52
|
+
unsigned long bits = (unsigned long)1 << (sizeof(unsigned long) * 8 - 1);
|
53
|
+
putchar('0' + ((val & bits) > 0));
|
54
|
+
while(bits >>= 1) {
|
55
|
+
putchar('0' + ((val & bits) > 0));
|
56
|
+
}
|
57
|
+
putchar('\n');
|
58
|
+
}
|
59
|
+
|
60
|
+
//Converts a long to a comp_t
|
61
|
+
//To consider: make sure the value is positive?
|
62
|
+
//To consider: more unit testing?
|
63
|
+
static comp_t ulong_to_comp_t(unsigned long l) {
|
64
|
+
size_t bits = 0;
|
65
|
+
unsigned long l2 = l;
|
66
|
+
if(l2) {
|
67
|
+
bits = 1;
|
68
|
+
while(l2 >>= 1) {
|
69
|
+
++bits;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
if(bits <= 13) {
|
73
|
+
return (l & 0x1fff);
|
74
|
+
} else {
|
75
|
+
size_t div_bits, rem_bits;
|
76
|
+
bits -= 13;
|
77
|
+
div_bits = bits / 3;
|
78
|
+
if(div_bits >= 8) {
|
79
|
+
rb_raise(rb_eRangeError, "Exponent overflow in ulong_to_comp_t: Value %lu is too large.", l);
|
80
|
+
}
|
81
|
+
rem_bits = bits - div_bits * 3;
|
82
|
+
if(rem_bits) {
|
83
|
+
div_bits += 1;
|
84
|
+
}
|
85
|
+
//To consider: remove '&'?
|
86
|
+
return ((l >> bits) & 0x1fff) | ((div_bits & 0x7) << 13);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
//Checks the result of a call, raising an error if it fails
|
91
|
+
#define CHECK_CALL(expr, expected_result) \
|
92
|
+
{ \
|
93
|
+
typeof(expr) expected = (expected_result); \
|
94
|
+
typeof(expr) result; \
|
95
|
+
errno = 0; \
|
96
|
+
result = (expr); \
|
97
|
+
if(result != expected) { \
|
98
|
+
if(errno) { \
|
99
|
+
char buf[512]; \
|
100
|
+
VALUE err; \
|
101
|
+
snprintf(buf, sizeof(buf), "%s(%u)", __FILE__, __LINE__); \
|
102
|
+
err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(errno)); \
|
103
|
+
rb_exc_raise(err); \
|
104
|
+
} else { \
|
105
|
+
char buf[512]; \
|
106
|
+
snprintf(buf, sizeof(buf), #expr ": result %i expected, not %i - %s(%u)", expected, result, __FILE__, __LINE__); \
|
107
|
+
rb_raise(rb_eRuntimeError, buf); \
|
108
|
+
} \
|
109
|
+
} \
|
110
|
+
} \
|
111
|
+
|
112
|
+
#define ENSURE_ALLOCATED(ptr) if(!ptr) rb_raise(cNoMemoryError, "Out of memory");
|
113
|
+
|
114
|
+
typedef struct {
|
115
|
+
FILE* file;
|
116
|
+
char* filename;
|
117
|
+
long num_entries;
|
118
|
+
} PacctLog;
|
119
|
+
|
120
|
+
static void pacct_log_free(void* p) {
|
121
|
+
PacctLog* log = (PacctLog*) p;
|
122
|
+
if(log->file) {
|
123
|
+
fclose(log->file);
|
124
|
+
log->file = NULL;
|
125
|
+
}
|
126
|
+
free(log->filename);
|
127
|
+
free(p);
|
128
|
+
}
|
129
|
+
|
130
|
+
/*
|
131
|
+
*call-seq:
|
132
|
+
* new(filename)
|
133
|
+
*
|
134
|
+
*Creates a new Pacct::Log using the given accounting file
|
135
|
+
*/
|
136
|
+
static VALUE pacct_log_new(int argc, VALUE* argv, VALUE class) {
|
137
|
+
VALUE log;
|
138
|
+
VALUE init_args[2];
|
139
|
+
PacctLog* ptr;
|
140
|
+
|
141
|
+
init_args[1] = Qnil;
|
142
|
+
rb_scan_args(argc, argv, "11", init_args, init_args + 1);
|
143
|
+
|
144
|
+
log = Data_Make_Struct(class, PacctLog, 0, pacct_log_free, ptr);
|
145
|
+
|
146
|
+
ptr->file = NULL;
|
147
|
+
ptr->num_entries = 0;
|
148
|
+
|
149
|
+
rb_obj_call_init(log, 2, init_args);
|
150
|
+
return log;
|
151
|
+
}
|
152
|
+
|
153
|
+
static VALUE pacct_log_init(VALUE self, VALUE filename, VALUE mode) {
|
154
|
+
PacctLog* log;
|
155
|
+
FILE* acct;
|
156
|
+
long length;
|
157
|
+
char* c_filename = StringValueCStr(filename);
|
158
|
+
size_t c_filename_len;
|
159
|
+
const char* c_mode = "rb";
|
160
|
+
|
161
|
+
if(mode != Qnil) {
|
162
|
+
int isValidMode = 0;
|
163
|
+
size_t i;
|
164
|
+
c_mode = StringValueCStr(mode);
|
165
|
+
for(i = 0; i < sizeof(validFileModes) / sizeof(char*); ++i) {
|
166
|
+
if(strcmp(c_mode, validFileModes[i]) == 0) {
|
167
|
+
isValidMode = 1;
|
168
|
+
break;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
if(!isValidMode) {
|
172
|
+
rb_raise(rb_eArgError, "Invalid mode for Pacct::File: '%s'", c_mode);
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
acct = fopen(c_filename, c_mode);
|
177
|
+
if(!acct) {
|
178
|
+
rb_raise(rb_eIOError, "Unable to open file '%s'", c_filename);
|
179
|
+
}
|
180
|
+
|
181
|
+
Data_Get_Struct(self, PacctLog, log);
|
182
|
+
|
183
|
+
log->file = acct;
|
184
|
+
c_filename_len = strlen(c_filename);
|
185
|
+
log->filename = malloc(c_filename_len + 1);
|
186
|
+
ENSURE_ALLOCATED(log->filename);
|
187
|
+
strncpy(log->filename, c_filename, c_filename_len);
|
188
|
+
log->filename[c_filename_len] = '\0';
|
189
|
+
|
190
|
+
CHECK_CALL(fseek(acct, 0, SEEK_END), 0);
|
191
|
+
length = ftell(acct);
|
192
|
+
rewind(acct);
|
193
|
+
|
194
|
+
if(length % sizeof(struct acct_v3) != 0) {
|
195
|
+
rb_raise(rb_eIOError, "Accounting file '%s' appears to be the wrong size.", c_filename);
|
196
|
+
}
|
197
|
+
|
198
|
+
log->num_entries = length / sizeof(struct acct_v3);
|
199
|
+
|
200
|
+
return self;
|
201
|
+
}
|
202
|
+
|
203
|
+
static void pacct_log_check_closed(PacctLog* log) {
|
204
|
+
if(!log->file) {
|
205
|
+
rb_raise(rb_eRuntimeError, "The file '%s' has already been closed.", log->filename);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
/*
|
210
|
+
*Closes the log file
|
211
|
+
*/
|
212
|
+
static VALUE pacct_log_close(VALUE self) {
|
213
|
+
PacctLog* log;
|
214
|
+
|
215
|
+
Data_Get_Struct(self, PacctLog, log);
|
216
|
+
|
217
|
+
if(log->file) {
|
218
|
+
fclose(log->file);
|
219
|
+
log->file = NULL;
|
220
|
+
}
|
221
|
+
|
222
|
+
return Qnil;
|
223
|
+
}
|
224
|
+
|
225
|
+
static VALUE pacct_entry_new(PacctLog* log) {
|
226
|
+
struct acct_v3* ptr;
|
227
|
+
VALUE entry = Data_Make_Struct(cEntry, struct acct_v3, 0, free, ptr);
|
228
|
+
if(log) {
|
229
|
+
size_t entries_read;
|
230
|
+
pacct_log_check_closed(log);
|
231
|
+
entries_read = fread(ptr, sizeof(struct acct_v3), 1, log->file);
|
232
|
+
if(entries_read != 1) {
|
233
|
+
rb_raise(rb_eIOError, "Unable to read record from accounting file '%s'", log->filename);
|
234
|
+
}
|
235
|
+
} else {
|
236
|
+
memset(ptr, 0, sizeof(struct acct_v3));
|
237
|
+
}
|
238
|
+
|
239
|
+
return entry;
|
240
|
+
}
|
241
|
+
|
242
|
+
//This is the version of pacct_entry_new that is actually exposed to Ruby.
|
243
|
+
static VALUE ruby_pacct_entry_new(VALUE self) {
|
244
|
+
return pacct_entry_new(NULL);
|
245
|
+
}
|
246
|
+
|
247
|
+
/*
|
248
|
+
*call-seq:
|
249
|
+
* each_entry([start]) {|entry, index| ...}
|
250
|
+
*
|
251
|
+
*Yields each entry in the file to the given block
|
252
|
+
*
|
253
|
+
*If start is given, iteration starts at the entry with that index.
|
254
|
+
*/
|
255
|
+
static VALUE each_entry(int argc, VALUE* argv, VALUE self) {
|
256
|
+
PacctLog* log;
|
257
|
+
VALUE start_value;
|
258
|
+
long start = 0;
|
259
|
+
int i = 0;
|
260
|
+
|
261
|
+
rb_scan_args(argc, argv, "01", &start_value);
|
262
|
+
if(argc && start_value != Qnil) {
|
263
|
+
start = NUM2UINT(start_value);
|
264
|
+
}
|
265
|
+
|
266
|
+
Data_Get_Struct(self, PacctLog, log);
|
267
|
+
|
268
|
+
pacct_log_check_closed(log);
|
269
|
+
|
270
|
+
if(start > log->num_entries) {
|
271
|
+
rb_raise(rb_eRangeError, "Index %li is out of range", start);
|
272
|
+
}
|
273
|
+
|
274
|
+
CHECK_CALL(fseek(log->file, start * sizeof(struct acct_v3), SEEK_SET), 0);
|
275
|
+
|
276
|
+
for(i = start; i < log->num_entries; ++i) {
|
277
|
+
VALUE entry = pacct_entry_new(log);
|
278
|
+
rb_yield(entry);
|
279
|
+
}
|
280
|
+
|
281
|
+
return Qnil;
|
282
|
+
}
|
283
|
+
|
284
|
+
/*
|
285
|
+
*Returns the last entry in the file
|
286
|
+
*/
|
287
|
+
static VALUE last_entry(VALUE self) {
|
288
|
+
PacctLog* log;
|
289
|
+
long pos;
|
290
|
+
VALUE entry;
|
291
|
+
|
292
|
+
Data_Get_Struct(self, PacctLog, log);
|
293
|
+
|
294
|
+
pacct_log_check_closed(log);
|
295
|
+
|
296
|
+
if(log->num_entries == 0) {
|
297
|
+
return Qnil;
|
298
|
+
}
|
299
|
+
|
300
|
+
pos = ftell(log->file);
|
301
|
+
CHECK_CALL(fseek(log->file, -sizeof(struct acct_v3), SEEK_END), 0);
|
302
|
+
|
303
|
+
entry = pacct_entry_new(log);
|
304
|
+
|
305
|
+
CHECK_CALL(fseek(log->file, pos, SEEK_SET), 0);
|
306
|
+
|
307
|
+
return entry;
|
308
|
+
}
|
309
|
+
|
310
|
+
/*
|
311
|
+
*Returns the number of entries in the file
|
312
|
+
*/
|
313
|
+
static VALUE get_num_entries(VALUE self) {
|
314
|
+
PacctLog* log;
|
315
|
+
|
316
|
+
Data_Get_Struct(self, PacctLog, log);
|
317
|
+
|
318
|
+
return INT2NUM(log->num_entries);
|
319
|
+
}
|
320
|
+
|
321
|
+
/*
|
322
|
+
*call-seq:
|
323
|
+
* write_entry(entry)
|
324
|
+
*
|
325
|
+
* Appends the given entry to the file
|
326
|
+
*/
|
327
|
+
static VALUE write_entry(VALUE self, VALUE entry) {
|
328
|
+
//To do consider: verification?
|
329
|
+
PacctLog* log;
|
330
|
+
long pos;
|
331
|
+
struct acct_v3* acct;
|
332
|
+
|
333
|
+
Data_Get_Struct(self, PacctLog, log);
|
334
|
+
pacct_log_check_closed(log);
|
335
|
+
Data_Get_Struct(entry, struct acct_v3, acct);
|
336
|
+
|
337
|
+
pos = ftell(log->file);
|
338
|
+
CHECK_CALL(fseek(log->file, 0, SEEK_END), 0);
|
339
|
+
|
340
|
+
if(fwrite(acct, sizeof(struct acct_v3), 1, log->file) != 1) {
|
341
|
+
rb_raise(rb_eIOError, "Unable to write to accounting file '%s'", log->filename);
|
342
|
+
}
|
343
|
+
|
344
|
+
++(log->num_entries);
|
345
|
+
|
346
|
+
CHECK_CALL(fseek(log->file, pos, SEEK_SET), 0);
|
347
|
+
|
348
|
+
return Qnil;
|
349
|
+
}
|
350
|
+
|
351
|
+
//Methods of Pacct::Entry
|
352
|
+
/*
|
353
|
+
*Returns the process ID
|
354
|
+
*/
|
355
|
+
static VALUE get_process_id(VALUE self) {
|
356
|
+
struct acct_v3* data;
|
357
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
358
|
+
|
359
|
+
return INT2NUM(data->ac_pid);
|
360
|
+
}
|
361
|
+
|
362
|
+
/*
|
363
|
+
*Sets the process ID
|
364
|
+
*/
|
365
|
+
static VALUE set_process_id(VALUE self, VALUE pid) {
|
366
|
+
struct acct_v3* data;
|
367
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
368
|
+
|
369
|
+
data->ac_pid = NUM2UINT(pid);
|
370
|
+
|
371
|
+
return Qnil;
|
372
|
+
}
|
373
|
+
|
374
|
+
/*
|
375
|
+
*Returns the ID of the user who executed the command
|
376
|
+
*/
|
377
|
+
static VALUE get_user_id(VALUE self) {
|
378
|
+
struct acct_v3* data;
|
379
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
380
|
+
|
381
|
+
return INT2NUM(data->ac_uid);
|
382
|
+
}
|
383
|
+
|
384
|
+
/*
|
385
|
+
*Returns the name of the user who executed the command
|
386
|
+
*/
|
387
|
+
static VALUE get_user_name(VALUE self) {
|
388
|
+
struct acct_v3* data;
|
389
|
+
struct passwd* pw_data;
|
390
|
+
VALUE id, name;
|
391
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
392
|
+
|
393
|
+
//If there's a cached user name, return it.
|
394
|
+
id = UINT2NUM(data->ac_uid);
|
395
|
+
name = rb_hash_aref(known_users_by_id, id);
|
396
|
+
if(name != Qnil) {
|
397
|
+
return name;
|
398
|
+
}
|
399
|
+
|
400
|
+
//Otherwise, get the user name from the OS.
|
401
|
+
errno = 0;
|
402
|
+
pw_data = getpwuid(data->ac_uid);
|
403
|
+
if(!pw_data) {
|
404
|
+
char buf[512];
|
405
|
+
VALUE err;
|
406
|
+
int e = errno;
|
407
|
+
snprintf(buf, 512, "Unable to obtain user name for ID %u", data->ac_uid);
|
408
|
+
if(e == 0) {
|
409
|
+
e = ENODATA;
|
410
|
+
}
|
411
|
+
err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(e));
|
412
|
+
rb_exc_raise(err);
|
413
|
+
}
|
414
|
+
|
415
|
+
//Cache the user name.
|
416
|
+
name = rb_str_new2(pw_data->pw_name);
|
417
|
+
rb_hash_aset(known_users_by_id, id, name);
|
418
|
+
|
419
|
+
return name;
|
420
|
+
}
|
421
|
+
|
422
|
+
/*
|
423
|
+
*Sets the name of the user who executed the command
|
424
|
+
*/
|
425
|
+
static VALUE set_user_name(VALUE self, VALUE name) {
|
426
|
+
struct acct_v3* data;
|
427
|
+
struct passwd* pw_data;
|
428
|
+
char* c_name = StringValueCStr(name);
|
429
|
+
VALUE id;
|
430
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
431
|
+
|
432
|
+
id = rb_hash_aref(known_users_by_name, name);
|
433
|
+
if(id != Qnil) {
|
434
|
+
data->ac_uid = NUM2UINT(id);
|
435
|
+
return Qnil;
|
436
|
+
}
|
437
|
+
|
438
|
+
errno = 0;
|
439
|
+
pw_data = getpwnam(c_name);
|
440
|
+
if(!pw_data) {
|
441
|
+
char buf[512];
|
442
|
+
VALUE err;
|
443
|
+
int e = errno;
|
444
|
+
snprintf(buf, 512, "Unable to obtain user ID for name '%s'", c_name);
|
445
|
+
if(e == 0) {
|
446
|
+
e = ENODATA;
|
447
|
+
}
|
448
|
+
err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(e));
|
449
|
+
rb_exc_raise(err);
|
450
|
+
}
|
451
|
+
|
452
|
+
id = UINT2NUM(pw_data->pw_uid);
|
453
|
+
rb_hash_aset(known_users_by_name, name, id);
|
454
|
+
|
455
|
+
data->ac_uid = pw_data->pw_uid;
|
456
|
+
|
457
|
+
return Qnil;
|
458
|
+
}
|
459
|
+
|
460
|
+
/*
|
461
|
+
*Returns the group ID of the user who executed the command
|
462
|
+
*/
|
463
|
+
static VALUE get_group_id(VALUE self) {
|
464
|
+
struct acct_v3* data;
|
465
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
466
|
+
|
467
|
+
return INT2NUM(data->ac_gid);
|
468
|
+
}
|
469
|
+
|
470
|
+
/*
|
471
|
+
*Returns the group name of the user who executed the command
|
472
|
+
*/
|
473
|
+
static VALUE get_group_name(VALUE self) {
|
474
|
+
struct acct_v3* data;
|
475
|
+
struct group* group_data;
|
476
|
+
VALUE name, id;
|
477
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
478
|
+
|
479
|
+
id = UINT2NUM(data->ac_gid);
|
480
|
+
name = rb_hash_aref(known_groups_by_id, id);
|
481
|
+
if(name != Qnil) {
|
482
|
+
return name;
|
483
|
+
}
|
484
|
+
|
485
|
+
errno = 0;
|
486
|
+
group_data = getgrgid(data->ac_gid);
|
487
|
+
if(!group_data) {
|
488
|
+
char buf[512];
|
489
|
+
VALUE err;
|
490
|
+
int e = errno;
|
491
|
+
snprintf(buf, 512, "Unable to obtain group name for ID %u", data->ac_gid);
|
492
|
+
if(e == 0) {
|
493
|
+
e = ENODATA;
|
494
|
+
}
|
495
|
+
err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(e));
|
496
|
+
rb_exc_raise(err);
|
497
|
+
}
|
498
|
+
|
499
|
+
name = rb_str_new2(group_data->gr_name);
|
500
|
+
rb_hash_aset(known_groups_by_id, id, name);
|
501
|
+
|
502
|
+
return name;
|
503
|
+
}
|
504
|
+
|
505
|
+
/*
|
506
|
+
*Sets the group name of the user who executed the command
|
507
|
+
*/
|
508
|
+
static VALUE set_group_name(VALUE self, VALUE name) {
|
509
|
+
struct acct_v3* data;
|
510
|
+
struct group* group_data;
|
511
|
+
VALUE id;
|
512
|
+
char* c_name = StringValueCStr(name);
|
513
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
514
|
+
|
515
|
+
id = rb_hash_aref(known_groups_by_name, name);
|
516
|
+
if(id != Qnil) {
|
517
|
+
data->ac_gid = NUM2UINT(id);
|
518
|
+
return Qnil;
|
519
|
+
}
|
520
|
+
|
521
|
+
errno = 0;
|
522
|
+
group_data = getgrnam(c_name);
|
523
|
+
if(!group_data) {
|
524
|
+
char buf[512];
|
525
|
+
VALUE err;
|
526
|
+
int e = errno;
|
527
|
+
snprintf(buf, 512, "Unable to obtain group ID for name '%s'", c_name);
|
528
|
+
if(e == 0) {
|
529
|
+
e = ENODATA;
|
530
|
+
}
|
531
|
+
err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(e));
|
532
|
+
rb_exc_raise(err);
|
533
|
+
}
|
534
|
+
|
535
|
+
id = UINT2NUM(group_data->gr_gid);
|
536
|
+
rb_hash_aset(known_groups_by_name, name, id);
|
537
|
+
|
538
|
+
data->ac_gid = group_data->gr_gid;
|
539
|
+
|
540
|
+
return Qnil;
|
541
|
+
}
|
542
|
+
|
543
|
+
/*
|
544
|
+
*Returns the task's total user CPU time in seconds
|
545
|
+
*/
|
546
|
+
static VALUE get_user_time(VALUE self) {
|
547
|
+
struct acct_v3* data;
|
548
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
549
|
+
|
550
|
+
return INT2NUM(comp_t_to_ulong(data->ac_utime) / ticksPerSecond);
|
551
|
+
}
|
552
|
+
|
553
|
+
/*
|
554
|
+
*Sets the task's total user CPU time
|
555
|
+
*/
|
556
|
+
static VALUE set_user_time(VALUE self, VALUE value) {
|
557
|
+
struct acct_v3* data;
|
558
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
559
|
+
|
560
|
+
data->ac_utime = ulong_to_comp_t(NUM2ULONG(value) * ticksPerSecond);
|
561
|
+
|
562
|
+
return Qnil;
|
563
|
+
}
|
564
|
+
|
565
|
+
/*
|
566
|
+
*Returns the task's total system CPU time in seconds
|
567
|
+
*/
|
568
|
+
static VALUE get_system_time(VALUE self) {
|
569
|
+
struct acct_v3* data;
|
570
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
571
|
+
|
572
|
+
return INT2NUM(comp_t_to_ulong(data->ac_stime) / ticksPerSecond);
|
573
|
+
}
|
574
|
+
|
575
|
+
/*
|
576
|
+
*Sets the task's total system CPU time
|
577
|
+
*/
|
578
|
+
static VALUE set_system_time(VALUE self, VALUE value) {
|
579
|
+
struct acct_v3* data;
|
580
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
581
|
+
|
582
|
+
data->ac_stime = ulong_to_comp_t(NUM2ULONG(value) * ticksPerSecond);
|
583
|
+
|
584
|
+
return Qnil;
|
585
|
+
}
|
586
|
+
|
587
|
+
/*
|
588
|
+
*Returns the task's total CPU time in seconds
|
589
|
+
*/
|
590
|
+
static VALUE get_cpu_time(VALUE self) {
|
591
|
+
struct acct_v3* data;
|
592
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
593
|
+
|
594
|
+
return INT2NUM((comp_t_to_ulong(data->ac_utime) + comp_t_to_ulong(data->ac_stime)) / ticksPerSecond);
|
595
|
+
}
|
596
|
+
|
597
|
+
/*
|
598
|
+
*Returns the task's total wall time in seconds
|
599
|
+
*/
|
600
|
+
static VALUE get_wall_time(VALUE self) {
|
601
|
+
struct acct_v3* data;
|
602
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
603
|
+
|
604
|
+
return rb_float_new(data->ac_etime);
|
605
|
+
}
|
606
|
+
|
607
|
+
/*
|
608
|
+
*Sets the task's total wall time
|
609
|
+
*/
|
610
|
+
static VALUE set_wall_time(VALUE self, VALUE value) {
|
611
|
+
struct acct_v3* data;
|
612
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
613
|
+
|
614
|
+
data->ac_etime = NUM2DBL(value);
|
615
|
+
|
616
|
+
return Qnil;
|
617
|
+
}
|
618
|
+
|
619
|
+
/*
|
620
|
+
*Returns the task's start time
|
621
|
+
*/
|
622
|
+
static VALUE get_start_time(VALUE self) {
|
623
|
+
struct acct_v3* data;
|
624
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
625
|
+
|
626
|
+
return rb_funcall(cTime, id_at, 1, INT2NUM(data->ac_btime));
|
627
|
+
}
|
628
|
+
|
629
|
+
/*
|
630
|
+
*Sets the task's start time
|
631
|
+
*/
|
632
|
+
static VALUE set_start_time(VALUE self, VALUE value) {
|
633
|
+
struct acct_v3* data;
|
634
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
635
|
+
|
636
|
+
data->ac_btime = NUM2UINT(rb_funcall(value, id_to_i, 0));
|
637
|
+
|
638
|
+
return Qnil;
|
639
|
+
}
|
640
|
+
|
641
|
+
/*
|
642
|
+
*Returns the task's average memory usage in kilobytes
|
643
|
+
*/
|
644
|
+
static VALUE get_average_mem_usage(VALUE self) {
|
645
|
+
struct acct_v3* data;
|
646
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
647
|
+
|
648
|
+
//Why divided by page size?
|
649
|
+
return INT2NUM(comp_t_to_ulong(data->ac_mem) * 1024 / pageSize);
|
650
|
+
}
|
651
|
+
|
652
|
+
/*
|
653
|
+
*Sets the task's average memory usage in kilobytes
|
654
|
+
*/
|
655
|
+
static VALUE set_average_mem_usage(VALUE self, VALUE value) {
|
656
|
+
struct acct_v3* data;
|
657
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
658
|
+
|
659
|
+
data->ac_mem = ulong_to_comp_t(NUM2ULONG(value) * pageSize / 1024);
|
660
|
+
|
661
|
+
return Qnil;
|
662
|
+
}
|
663
|
+
|
664
|
+
/*
|
665
|
+
*Returns the first 15 characters of the command name
|
666
|
+
*/
|
667
|
+
static VALUE get_command_name(VALUE self) {
|
668
|
+
struct acct_v3* data;
|
669
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
670
|
+
|
671
|
+
return rb_str_new2(data->ac_comm);
|
672
|
+
}
|
673
|
+
|
674
|
+
/*
|
675
|
+
*Sets the first 15 characters of the command name
|
676
|
+
*/
|
677
|
+
static VALUE set_command_name(VALUE self, VALUE name) {
|
678
|
+
struct acct_v3* data;
|
679
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
680
|
+
|
681
|
+
strncpy(data->ac_comm, StringValueCStr(name), ACCT_COMM - 1);
|
682
|
+
data->ac_comm[ACCT_COMM - 1] = '\0';
|
683
|
+
|
684
|
+
return Qnil;
|
685
|
+
}
|
686
|
+
|
687
|
+
/*
|
688
|
+
*Returns the command's exit code
|
689
|
+
*/
|
690
|
+
static VALUE get_exit_code(VALUE self) {
|
691
|
+
struct acct_v3* data;
|
692
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
693
|
+
|
694
|
+
return INT2NUM(data->ac_exitcode);
|
695
|
+
}
|
696
|
+
|
697
|
+
/*
|
698
|
+
*Sets the command's exit code
|
699
|
+
*/
|
700
|
+
static VALUE set_exit_code(VALUE self, VALUE value) {
|
701
|
+
struct acct_v3* data;
|
702
|
+
Data_Get_Struct(self, struct acct_v3, data);
|
703
|
+
|
704
|
+
data->ac_exitcode = NUM2UINT(value);
|
705
|
+
|
706
|
+
return Qnil;
|
707
|
+
}
|
708
|
+
|
709
|
+
//Unit testing code
|
710
|
+
|
711
|
+
static VALUE test_check_call_macro(VALUE self, VALUE test) {
|
712
|
+
int i = NUM2INT(test);
|
713
|
+
switch(i) {
|
714
|
+
case 0:
|
715
|
+
CHECK_CALL(0, 0);
|
716
|
+
break;
|
717
|
+
case 1:
|
718
|
+
CHECK_CALL(1, 0);
|
719
|
+
break;
|
720
|
+
case 2:
|
721
|
+
CHECK_CALL(errno = 0, 1);
|
722
|
+
case 3:
|
723
|
+
CHECK_CALL(errno = ERANGE, 0);
|
724
|
+
default:
|
725
|
+
rb_raise(rb_eRangeError, "Unknown test code %i", i);
|
726
|
+
}
|
727
|
+
return Qnil;
|
728
|
+
}
|
729
|
+
|
730
|
+
static VALUE test_read_failure(VALUE self) {
|
731
|
+
PacctLog log;
|
732
|
+
//VALUE entry = pacct_entry_new(NULL);
|
733
|
+
const char* filename = "/dev/null";
|
734
|
+
log.num_entries = 0;
|
735
|
+
log.filename = malloc(strlen(filename) + 1);
|
736
|
+
ENSURE_ALLOCATED(log.filename);
|
737
|
+
strcpy(log.filename, filename);
|
738
|
+
log.file = fopen(log.filename, "r");
|
739
|
+
|
740
|
+
pacct_entry_new(&log);
|
741
|
+
return Qnil;
|
742
|
+
}
|
743
|
+
|
744
|
+
static VALUE test_write_failure(VALUE self) {
|
745
|
+
PacctLog* ptr;
|
746
|
+
VALUE log = Data_Make_Struct(cLog, PacctLog, 0, pacct_log_free, ptr);
|
747
|
+
VALUE entry = pacct_entry_new(NULL);
|
748
|
+
const char* filename = "spec/pacct_spec.rb";
|
749
|
+
ptr->num_entries = 0;
|
750
|
+
ptr->filename = malloc(strlen(filename) + 1);
|
751
|
+
ENSURE_ALLOCATED(ptr->filename);
|
752
|
+
strcpy(ptr->filename, filename);
|
753
|
+
ptr->file = fopen(ptr->filename, "r");
|
754
|
+
|
755
|
+
write_entry(log, entry);
|
756
|
+
return Qnil;
|
757
|
+
}
|
758
|
+
|
759
|
+
static VALUE test_ulong_to_comp_t(VALUE self, VALUE val) {
|
760
|
+
unsigned long l = (unsigned long)NUM2ULONG(val);
|
761
|
+
comp_t result = ulong_to_comp_t(l);
|
762
|
+
return INT2NUM(result);
|
763
|
+
}
|
764
|
+
|
765
|
+
static VALUE test_comp_t_to_ulong(VALUE self, VALUE val) {
|
766
|
+
comp_t c = (comp_t)NUM2UINT(val);
|
767
|
+
unsigned long result = comp_t_to_ulong(c);
|
768
|
+
return ULONG2NUM(result);
|
769
|
+
}
|
770
|
+
|
771
|
+
void Init_pacct_c() {
|
772
|
+
VALUE mRSpec;
|
773
|
+
|
774
|
+
//Get system parameters
|
775
|
+
pageSize = getpagesize();
|
776
|
+
ticksPerSecond = sysconf(_SC_CLK_TCK);
|
777
|
+
|
778
|
+
//Get Ruby objects.
|
779
|
+
cTime = rb_const_get(rb_cObject, rb_intern("Time"));
|
780
|
+
cSystemCallError = rb_const_get(rb_cObject, rb_intern("SystemCallError"));
|
781
|
+
cNoMemoryError = rb_const_get(rb_cObject, rb_intern("NoMemoryError"));
|
782
|
+
|
783
|
+
id_at = rb_intern("at");
|
784
|
+
id_new = rb_intern("new");
|
785
|
+
id_to_i = rb_intern("to_i");
|
786
|
+
|
787
|
+
//To consider: is there any place that these can be unregistered?
|
788
|
+
rb_gc_register_address(&known_users_by_name);
|
789
|
+
rb_gc_register_address(&known_groups_by_name);
|
790
|
+
rb_gc_register_address(&known_users_by_id);
|
791
|
+
rb_gc_register_address(&known_groups_by_id);
|
792
|
+
|
793
|
+
known_users_by_name = rb_hash_new();
|
794
|
+
known_groups_by_name = rb_hash_new();
|
795
|
+
known_users_by_id = rb_hash_new();
|
796
|
+
known_groups_by_id = rb_hash_new();
|
797
|
+
|
798
|
+
//Define Ruby modules/objects/methods.
|
799
|
+
mPacct = rb_define_module("Pacct");
|
800
|
+
/*
|
801
|
+
*Represents an accounting file in acct(5) format
|
802
|
+
*/
|
803
|
+
cLog = rb_define_class_under(mPacct, "Log", rb_cObject);
|
804
|
+
/*
|
805
|
+
*Document-class: Pacct::Entry
|
806
|
+
*
|
807
|
+
*Represents an entry in a Pacct::File
|
808
|
+
*/
|
809
|
+
cEntry = rb_define_class_under(mPacct, "Entry", rb_cObject);
|
810
|
+
rb_define_singleton_method(cLog, "new", pacct_log_new, -1);
|
811
|
+
rb_define_method(cLog, "initialize", pacct_log_init, 2);
|
812
|
+
rb_define_method(cLog, "each_entry", each_entry, -1);
|
813
|
+
rb_define_method(cLog, "last_entry", last_entry, 0);
|
814
|
+
rb_define_method(cLog, "num_entries", get_num_entries, 0);
|
815
|
+
rb_define_method(cLog, "write_entry", write_entry, 1);
|
816
|
+
rb_define_method(cLog, "close", pacct_log_close, 0);
|
817
|
+
|
818
|
+
rb_define_singleton_method(cEntry, "new", ruby_pacct_entry_new, 0);
|
819
|
+
rb_define_method(cEntry, "process_id", get_process_id, 0);
|
820
|
+
rb_define_method(cEntry, "process_id=", set_process_id, 1);
|
821
|
+
rb_define_method(cEntry, "user_id", get_user_id, 0);
|
822
|
+
rb_define_method(cEntry, "user_name", get_user_name, 0);
|
823
|
+
rb_define_method(cEntry, "user_name=", set_user_name, 1);
|
824
|
+
rb_define_method(cEntry, "group_id", get_group_id, 0);
|
825
|
+
rb_define_method(cEntry, "group_name", get_group_name, 0);
|
826
|
+
rb_define_method(cEntry, "group_name=", set_group_name, 1);
|
827
|
+
rb_define_method(cEntry, "user_time", get_user_time, 0);
|
828
|
+
rb_define_method(cEntry, "user_time=", set_user_time, 1);
|
829
|
+
rb_define_method(cEntry, "system_time", get_system_time, 0);
|
830
|
+
rb_define_method(cEntry, "system_time=", set_system_time, 1);
|
831
|
+
rb_define_method(cEntry, "cpu_time", get_cpu_time, 0);
|
832
|
+
rb_define_method(cEntry, "wall_time", get_wall_time, 0);
|
833
|
+
rb_define_method(cEntry, "wall_time=", set_wall_time, 1);
|
834
|
+
rb_define_method(cEntry, "start_time", get_start_time, 0);
|
835
|
+
rb_define_method(cEntry, "start_time=", set_start_time, 1);
|
836
|
+
rb_define_method(cEntry, "memory", get_average_mem_usage, 0);
|
837
|
+
rb_define_method(cEntry, "memory=", set_average_mem_usage, 1);
|
838
|
+
rb_define_method(cEntry, "exit_code", get_exit_code, 0);
|
839
|
+
rb_define_method(cEntry, "exit_code=", set_exit_code, 1);
|
840
|
+
rb_define_method(cEntry, "command_name", get_command_name, 0);
|
841
|
+
rb_define_method(cEntry, "command_name=", set_command_name, 1);
|
842
|
+
|
843
|
+
//To consider: support other testing frameworks?
|
844
|
+
|
845
|
+
mRSpec = rb_const_defined(rb_cObject, rb_intern("RSpec"));
|
846
|
+
if(mRSpec == Qtrue) {
|
847
|
+
/*
|
848
|
+
*Document-module: Pacct::Test
|
849
|
+
*/
|
850
|
+
VALUE mTest = rb_define_module_under(mPacct, "Test");
|
851
|
+
rb_define_module_function(mTest, "check_call", test_check_call_macro, 1);
|
852
|
+
rb_define_module_function(mTest, "write_failure", test_write_failure, 0);
|
853
|
+
rb_define_module_function(mTest, "read_failure", test_read_failure, 0);
|
854
|
+
rb_define_module_function(mTest, "comp_t_to_ulong", test_comp_t_to_ulong, 1);
|
855
|
+
}
|
856
|
+
}
|
data/lib/pacct.rb
ADDED
data/pacct.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pacct/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "pacct"
|
8
|
+
gem.version = Pacct::VERSION
|
9
|
+
gem.authors = ["Ben Merritt"]
|
10
|
+
gem.email = ["blm768@gmail.com"]
|
11
|
+
gem.description = %q{A C extension library for parsing accounting files in acct(5) format}
|
12
|
+
gem.summary = %q{A C extension library for parsing accounting files in acct(5) format}
|
13
|
+
gem.homepage = "https://github.com/blm768/bookie"
|
14
|
+
gem.platform = Gem::Platform.new('universal-linux')
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.extensions = ["ext/pacct/extconf.rb"]
|
19
|
+
gem.test_files = gem.files.grep(%r{^(spec)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
|
22
|
+
gem.add_development_dependency('rspec')
|
23
|
+
end
|
data/snapshot/pacct
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
Wrong length!
|
data/spec/entry_spec.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pacct::Entry do
|
4
|
+
it "correctly converts \"comp_t\"s to integers" do
|
5
|
+
comp_t_to_ulong = Pacct::Test.method(:comp_t_to_ulong)
|
6
|
+
comp_t_to_ulong.call(1).should eql 1
|
7
|
+
comp_t_to_ulong.call((1 << 13) | 1).should eql(1 << 3)
|
8
|
+
comp_t_to_ulong.call((3 << 13) | 20).should eql(20 << 9)
|
9
|
+
end
|
10
|
+
|
11
|
+
#To do: unit test this (eventually).
|
12
|
+
=begin
|
13
|
+
it "correctly converts integers to \"comp_t\"s" do
|
14
|
+
|
15
|
+
end
|
16
|
+
=end
|
17
|
+
|
18
|
+
it "raises an error when a comp_t overflows" do
|
19
|
+
e = Pacct::Entry.new
|
20
|
+
expect {
|
21
|
+
e.memory = 1000000000000
|
22
|
+
}.to raise_error(RangeError, /Exponent overflow in ulong_to_comp_t: Value [\d]+ is too large./)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "truncates command names if they become too long" do
|
26
|
+
e = Pacct::Entry.new
|
27
|
+
e.command_name = 'some_very_long_command_name'
|
28
|
+
e.command_name.should eql 'some_very_long_'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "raises an error when encountering unknown user/group IDs" do
|
32
|
+
log = Pacct::Log.new('snapshot/pacct_invalid_ids')
|
33
|
+
log.each_entry do |entry|
|
34
|
+
#This assumes that these users and groups don't actually exist.
|
35
|
+
#If, for some odd reason, they _do_ exist, this test will fail.
|
36
|
+
expect { entry.user_name }.to raise_error(
|
37
|
+
Errno::ENODATA.new('Unable to obtain user name for ID 4294967295').to_s)
|
38
|
+
expect { entry.user_name = '_____ _' }.to raise_error(
|
39
|
+
Errno::ENODATA.new("Unable to obtain user ID for name '_____ _'").to_s)
|
40
|
+
expect { entry.group_name }.to raise_error(
|
41
|
+
Errno::ENODATA.new('Unable to obtain group name for ID 4294967295').to_s)
|
42
|
+
expect { entry.group_name = '_____ _' }.to raise_error(
|
43
|
+
Errno::ENODATA.new("Unable to obtain group ID for name '_____ _'").to_s)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/log_spec.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pacct::Log do
|
4
|
+
before(:each) do
|
5
|
+
@log = Pacct::Log.new('snapshot/pacct')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "correctly loads data" do
|
9
|
+
n = 0
|
10
|
+
@log.each_entry do |entry|
|
11
|
+
entry.process_id.should eql 1742
|
12
|
+
entry.user_id.should eql 0
|
13
|
+
entry.user_name.should eql "root"
|
14
|
+
entry.group_id.should eql 0
|
15
|
+
entry.group_name.should eql "root"
|
16
|
+
entry.command_name.should eql "accton"
|
17
|
+
entry.start_time.should eql Time.at(1349741116)
|
18
|
+
entry.wall_time.should eql 2.0
|
19
|
+
entry.user_time.should eql 0
|
20
|
+
entry.system_time.should eql 0
|
21
|
+
entry.cpu_time.should eql 0
|
22
|
+
entry.memory.should eql 979
|
23
|
+
entry.exit_code.should eql 0
|
24
|
+
n += 1
|
25
|
+
end
|
26
|
+
n.should eql 1
|
27
|
+
end
|
28
|
+
|
29
|
+
it "correctly handles the seek parameter" do
|
30
|
+
n = 0
|
31
|
+
@log.each_entry(1) do |e|
|
32
|
+
n += 1
|
33
|
+
end
|
34
|
+
n.should eql 0
|
35
|
+
expect { @log.each_entry(2) { |e| } }.to raise_error(RangeError)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "can read data more than once" do
|
39
|
+
2.times do
|
40
|
+
@log.each_entry do |e|
|
41
|
+
e.user_name.should eql 'root'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "raises an error if reading fails" do
|
47
|
+
expect { Pacct::Test::read_failure }.to raise_error(IOError, "Unable to read record from accounting file '/dev/null'")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "correctly finds the last entry" do
|
51
|
+
Helpers::double_log('snapshot/pacct_write') do |log|
|
52
|
+
entry = log.last_entry
|
53
|
+
entry.should_not eql nil
|
54
|
+
entry.exit_code.should eql 1
|
55
|
+
end
|
56
|
+
Pacct::Log.new('/dev/null').last_entry.should eql nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it "raises an error if the file is not found" do
|
60
|
+
expect { Pacct::Log.new('snapshot/abc') }.to raise_error
|
61
|
+
end
|
62
|
+
|
63
|
+
it "raises an error when the file is the wrong size" do
|
64
|
+
expect { Pacct::Log.new('snapshot/pacct_invalid_length') }.to raise_error
|
65
|
+
end
|
66
|
+
|
67
|
+
ENTRY_DATA = {
|
68
|
+
:process_id => 3,
|
69
|
+
:user_name => 'root',
|
70
|
+
:group_name => 'root',
|
71
|
+
:command_name => 'ls',
|
72
|
+
:start_time => Time.local(2012, 1, 1),
|
73
|
+
:wall_time => 10.0,
|
74
|
+
:user_time => 1,
|
75
|
+
:system_time => 1,
|
76
|
+
:memory => 100000,
|
77
|
+
:exit_code => 2}
|
78
|
+
|
79
|
+
it "correctly writes entries at the end of the file" do
|
80
|
+
e = Pacct::Entry.new
|
81
|
+
ENTRY_DATA.each_pair do |key, value|
|
82
|
+
e.method((key.to_s + '=').intern).call(value)
|
83
|
+
end
|
84
|
+
FileUtils.cp('snapshot/pacct', 'snapshot/pacct_write')
|
85
|
+
log = Pacct::Log.new('snapshot/pacct_write', 'r+b')
|
86
|
+
log.write_entry(e)
|
87
|
+
e = log.last_entry
|
88
|
+
e.should_not eql nil
|
89
|
+
ENTRY_DATA.each_pair do |key, value|
|
90
|
+
e.send(key).should eql value
|
91
|
+
end
|
92
|
+
FileUtils.rm('snapshot/pacct_write')
|
93
|
+
end
|
94
|
+
|
95
|
+
it "raises an error if writing fails" do
|
96
|
+
expect { Pacct::Test::write_failure }.to raise_error(IOError, "Unable to write to accounting file 'spec/pacct_spec.rb'")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "creates files when opened in write mode" do
|
100
|
+
FileUtils.rm('snapshot/abc') if File.exists?('snapshot/abc')
|
101
|
+
log = Pacct::Log.new('snapshot/abc', 'wb')
|
102
|
+
File.exists?('snapshot/abc').should eql true
|
103
|
+
FileUtils.rm('snapshot/abc')
|
104
|
+
end
|
105
|
+
|
106
|
+
it "raises an error if an attempt is made to access the file after it has been closed" do
|
107
|
+
log = Pacct::Log.new('/dev/null')
|
108
|
+
log.close
|
109
|
+
str = "The file '/dev/null' has already been closed."
|
110
|
+
expect { log.each_entry }.to raise_error(str)
|
111
|
+
expect { log.write_entry }.to raise_error(str)
|
112
|
+
expect { log.last_entry }.to raise_error(str)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module Helpers
|
117
|
+
def self.double_log(filename)
|
118
|
+
FileUtils.cp('snapshot/pacct', filename)
|
119
|
+
log = Pacct::Log.new(filename, 'r+b')
|
120
|
+
entry = nil
|
121
|
+
log.each_entry do |e|
|
122
|
+
entry = e
|
123
|
+
break
|
124
|
+
end
|
125
|
+
entry.exit_code = 1
|
126
|
+
log.write_entry(entry)
|
127
|
+
yield log
|
128
|
+
FileUtils.rm(filename)
|
129
|
+
end
|
130
|
+
end
|
data/spec/pacct_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pacct do
|
4
|
+
describe :CHECK_CALL do
|
5
|
+
it "only raises an error if the expected result is not given" do
|
6
|
+
Pacct::Test::check_call(0)
|
7
|
+
expect { Pacct::Test::check_call(1) }.to raise_error(/1: result 0 expected, not 1 - pacct_c\.c\([\d]+\)/)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "raises an error if errno is zero" do
|
11
|
+
expect { Pacct::Test::check_call(2) }.to raise_error(/errno = 0: result 1 expected, not 0 - pacct_c\.c\([\d]+\)/)
|
12
|
+
end
|
13
|
+
|
14
|
+
#To consider: test for negative values in setters?
|
15
|
+
|
16
|
+
it "raises an error if errno is nonzero" do
|
17
|
+
expect { Pacct::Test::check_call(3) }.to raise_error(Errno::ERANGE, /Numerical result out of range - pacct_c\.c\([\d]+\)/)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pacct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
prerelease:
|
6
|
+
platform: universal-linux
|
7
|
+
authors:
|
8
|
+
- Ben Merritt
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: A C extension library for parsing accounting files in acct(5) format
|
31
|
+
email:
|
32
|
+
- blm768@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions:
|
35
|
+
- ext/pacct/extconf.rb
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- .gitignore
|
39
|
+
- Gemfile
|
40
|
+
- LICENSE.txt
|
41
|
+
- README.md
|
42
|
+
- Rakefile
|
43
|
+
- ext/pacct/extconf.rb
|
44
|
+
- ext/pacct/pacct_c.c
|
45
|
+
- lib/pacct.rb
|
46
|
+
- lib/pacct/version.rb
|
47
|
+
- pacct.gemspec
|
48
|
+
- snapshot/pacct
|
49
|
+
- snapshot/pacct_invalid_ids
|
50
|
+
- snapshot/pacct_invalid_length
|
51
|
+
- spec/entry_spec.rb
|
52
|
+
- spec/log_spec.rb
|
53
|
+
- spec/pacct_spec.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
homepage: https://github.com/blm768/bookie
|
56
|
+
licenses: []
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.24
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A C extension library for parsing accounting files in acct(5) format
|
79
|
+
test_files:
|
80
|
+
- spec/entry_spec.rb
|
81
|
+
- spec/log_spec.rb
|
82
|
+
- spec/pacct_spec.rb
|
83
|
+
- spec/spec_helper.rb
|