runshare 0.1.0

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,498 @@
1
+ /*
2
+ * unshare(1) - command-line interface for unshare(2)
3
+ *
4
+ * Copyright (C) 2009 Mikhail Gusarov <dottedmag@dottedmag.net>
5
+ *
6
+ * This program is free software; you can redistribute it and/or modify it
7
+ * under the terms of the GNU General Public License as published by the
8
+ * Free Software Foundation; either version 2, or (at your option) any
9
+ * later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful, but
12
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ * General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License along
17
+ * with this program; if not, write to the Free Software Foundation, Inc.,
18
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
+ */
20
+
21
+ #include <errno.h>
22
+ #include <grp.h>
23
+ #include <ruby.h>
24
+ #include <sched.h>
25
+ #include <stdlib.h>
26
+ #include <sys/mount.h>
27
+ #include <sys/prctl.h>
28
+ #include <sys/stat.h>
29
+ #include <sys/types.h>
30
+ #include <sys/wait.h>
31
+ #include <unistd.h>
32
+
33
+ #include "include/c.h"
34
+ #include "include/caputils.h"
35
+ #include "include/namespace.h"
36
+ #include "include/pathnames.h"
37
+ #include "include/all-io.h"
38
+
39
+ #include "unshare.h"
40
+
41
+ /* /proc namespace files and mountpoints for binds */
42
+ static struct namespace_file {
43
+ int type; /* CLONE_NEW* */
44
+ const char *name; /* ns/<type> */
45
+ const char *target; /* user specified target for bind mount */
46
+ } namespace_files[] = {
47
+ { .type = CLONE_NEWUSER, .name = "ns/user" },
48
+ { .type = CLONE_NEWCGROUP,.name = "ns/cgroup" },
49
+ { .type = CLONE_NEWIPC, .name = "ns/ipc" },
50
+ { .type = CLONE_NEWUTS, .name = "ns/uts" },
51
+ { .type = CLONE_NEWNET, .name = "ns/net" },
52
+ { .type = CLONE_NEWPID, .name = "ns/pid_for_children" },
53
+ { .type = CLONE_NEWNS, .name = "ns/mnt" },
54
+ { .type = CLONE_NEWTIME, .name = "ns/time_for_children" },
55
+ { .name = NULL }
56
+ };
57
+
58
+ static int npersists = 0; /* number of persistent namespaces */
59
+
60
+ static void setgroups_control(int action)
61
+ {
62
+ const char *file = _PATH_PROC_SETGROUPS;
63
+ const char *cmd;
64
+ int fd;
65
+
66
+ if (action < 0 || (size_t) action >= ARRAY_SIZE(setgroups_strings))
67
+ return;
68
+ cmd = setgroups_strings[action];
69
+
70
+ fd = open(file, O_WRONLY);
71
+ if (fd < 0) {
72
+ if (errno == ENOENT)
73
+ return;
74
+ err(EXIT_FAILURE, _("cannot open %s"), file);
75
+ }
76
+
77
+ if (write_all(fd, cmd, strlen(cmd)))
78
+ err(EXIT_FAILURE, _("write failed %s"), file);
79
+ close(fd);
80
+ }
81
+
82
+ static void map_id(const char *file, uint32_t from, uint32_t to)
83
+ {
84
+ char *buf;
85
+ int fd;
86
+
87
+ fd = open(file, O_WRONLY);
88
+ if (fd < 0)
89
+ err(EXIT_FAILURE, _("cannot open %s"), file);
90
+
91
+ xasprintf(&buf, "%u %u 1", from, to);
92
+ if (write_all(fd, buf, strlen(buf)))
93
+ err(EXIT_FAILURE, _("write failed %s"), file);
94
+ free(buf);
95
+ close(fd);
96
+ }
97
+
98
+ static void set_propagation(unsigned long flags)
99
+ {
100
+ if (flags == 0)
101
+ return;
102
+
103
+ if (mount("none", "/", NULL, flags, NULL) != 0)
104
+ err(EXIT_FAILURE, _("cannot change root filesystem propagation"));
105
+ }
106
+
107
+ static int set_ns_target(int type, const char *path)
108
+ {
109
+ struct namespace_file *ns;
110
+
111
+ for (ns = namespace_files; ns->name; ns++) {
112
+ if (ns->type != type)
113
+ continue;
114
+ ns->target = path;
115
+ npersists++;
116
+ return 0;
117
+ }
118
+
119
+ return -EINVAL;
120
+ }
121
+
122
+ static int bind_ns_files(pid_t pid)
123
+ {
124
+ struct namespace_file *ns;
125
+ char src[PATH_MAX];
126
+
127
+ for (ns = namespace_files; ns->name; ns++) {
128
+ if (!ns->target)
129
+ continue;
130
+
131
+ snprintf(src, sizeof(src), "/proc/%u/%s", (unsigned) pid, ns->name);
132
+
133
+ if (mount(src, ns->target, NULL, MS_BIND, NULL) != 0)
134
+ err(EXIT_FAILURE, _("mount %s on %s failed"), src, ns->target);
135
+ }
136
+
137
+ return 0;
138
+ }
139
+
140
+ static ino_t get_mnt_ino(pid_t pid)
141
+ {
142
+ struct stat st;
143
+ char path[PATH_MAX];
144
+
145
+ snprintf(path, sizeof(path), "/proc/%u/ns/mnt", (unsigned) pid);
146
+
147
+ if (stat(path, &st) != 0)
148
+ err(EXIT_FAILURE, _("stat of %s failed"), path);
149
+ return st.st_ino;
150
+ }
151
+
152
+ static void settime(time_t offset, clockid_t clk_id)
153
+ {
154
+ char buf[sizeof(stringify_value(ULONG_MAX)) * 3];
155
+ int fd, len;
156
+
157
+ len = snprintf(buf, sizeof(buf), "%d %ld 0", clk_id, offset);
158
+
159
+ fd = open("/proc/self/timens_offsets", O_WRONLY);
160
+ if (fd < 0)
161
+ err(EXIT_FAILURE, _("failed to open /proc/self/timens_offsets"));
162
+
163
+ if (write(fd, buf, len) != len)
164
+ err(EXIT_FAILURE, _("failed to write to /proc/self/timens_offsets"));
165
+
166
+ close(fd);
167
+ }
168
+
169
+ static void bind_ns_files_from_child(pid_t *child, int fds[2])
170
+ {
171
+ char ch;
172
+ pid_t ppid = getpid();
173
+ ino_t ino = get_mnt_ino(ppid);
174
+
175
+ if (pipe(fds) < 0)
176
+ err(EXIT_FAILURE, _("pipe failed"));
177
+
178
+ *child = fork();
179
+
180
+ switch (*child) {
181
+ case -1:
182
+ err(EXIT_FAILURE, _("fork failed"));
183
+
184
+ case 0: /* child */
185
+ close(fds[1]);
186
+ fds[1] = -1;
187
+
188
+ /* wait for parent */
189
+ if (read_all(fds[0], &ch, 1) != 1 && ch != PIPE_SYNC_BYTE)
190
+ err(EXIT_FAILURE, _("failed to read pipe"));
191
+ if (get_mnt_ino(ppid) == ino)
192
+ exit(EXIT_FAILURE);
193
+ bind_ns_files(ppid);
194
+ exit(EXIT_SUCCESS);
195
+ break;
196
+
197
+ default: /* parent */
198
+ close(fds[0]);
199
+ fds[0] = -1;
200
+ break;
201
+ }
202
+ }
203
+
204
+ int rb_unshare_internal(struct rb_unshare_args args)
205
+ {
206
+ int unshare_flags = 0;
207
+
208
+ // int kill_child_signo = 0; /* 0 means --kill-child was not used */
209
+ char *procmnt = NULL;
210
+ char *newroot = NULL;
211
+ char *newdir = NULL;
212
+
213
+ int fds[2];
214
+ int status;
215
+
216
+ int pid_bind = 0;
217
+ int pid = 0;
218
+
219
+ time_t monotonic = 0;
220
+ time_t boottime = 0;
221
+
222
+ uid_t real_euid = geteuid();
223
+ gid_t real_egid = getegid();
224
+
225
+ if (args.clone_newns) {
226
+ unshare_flags |= CLONE_NEWNS;
227
+ // if (optarg)
228
+ // set_ns_target(CLONE_NEWNS, optarg);
229
+ }
230
+ if (args.clone_newuts) {
231
+ unshare_flags |= CLONE_NEWUTS;
232
+ // if (optarg)
233
+ // set_ns_target(CLONE_NEWUTS, optarg);
234
+ }
235
+ if (args.clone_newipc) {
236
+ unshare_flags |= CLONE_NEWIPC;
237
+ // if (optarg)
238
+ // set_ns_target(CLONE_NEWIPC, optarg);
239
+ }
240
+ if (args.clone_newnet) {
241
+ unshare_flags |= CLONE_NEWNET;
242
+ // if (optarg)
243
+ // set_ns_target(CLONE_NEWNET, optarg);
244
+ }
245
+ if (args.clone_newpid) {
246
+ unshare_flags |= CLONE_NEWPID;
247
+ // if (optarg)
248
+ // set_ns_target(CLONE_NEWPID, optarg);
249
+ }
250
+ if (args.clone_newuser) {
251
+ unshare_flags |= CLONE_NEWUSER;
252
+ // if (optarg)
253
+ // set_ns_target(CLONE_NEWUSER, optarg);
254
+ }
255
+ if (args.clone_newcgroup) {
256
+ unshare_flags |= CLONE_NEWCGROUP;
257
+ // if (optarg)
258
+ // set_ns_target(CLONE_NEWCGROUP, optarg);
259
+ }
260
+ if (args.clone_newtime) {
261
+ unshare_flags |= CLONE_NEWTIME;
262
+ // if (optarg)
263
+ // set_ns_target(CLONE_NEWTIME, optarg);
264
+ }
265
+ if (args.mount_proc != Qundef) {
266
+ unshare_flags |= CLONE_NEWNS;
267
+ procmnt = StringValueCStr(args.mount_proc);
268
+ }
269
+ if (args.map_user != (uid_t) -1) {
270
+ unshare_flags |= CLONE_NEWUSER;
271
+ }
272
+ if (args.map_group != (gid_t) -1) {
273
+ unshare_flags |= CLONE_NEWUSER;
274
+ }
275
+ if (args.map_root_user) {
276
+ unshare_flags |= CLONE_NEWUSER;
277
+ args.map_user = 0;
278
+ args.map_group = 0;
279
+ }
280
+ if (args.map_current_user) {
281
+ unshare_flags |= CLONE_NEWUSER;
282
+ args.map_user = real_euid;
283
+ args.map_group = real_egid;
284
+ }
285
+ if (args.kill_child) {
286
+ args.fork = true;
287
+ // if (optarg) {
288
+ // if ((kill_child_signo = signame_to_signum(optarg)) < 0)
289
+ // errx(EXIT_FAILURE, _("unknown signal: %s"),
290
+ // optarg);
291
+ // } else {
292
+ // kill_child_signo = SIGKILL;
293
+ // }
294
+ }
295
+ if (args.keep_caps) {
296
+ cap_last_cap(); /* Force last cap to be cached before we fork. */
297
+ }
298
+ if (args.root != Qundef) {
299
+ newroot = StringValueCStr(args.root);
300
+ }
301
+ // case OPT_MONOTONIC:
302
+ // monotonic = strtoul_or_err(optarg, _("failed to parse monotonic offset"));
303
+ // force_monotonic = 1;
304
+ // break;
305
+ // case OPT_BOOTTIME:
306
+ // boottime = strtoul_or_err(optarg, _("failed to parse boottime offset"));
307
+ // force_boottime = 1;
308
+ // break;
309
+
310
+ if ((args.force_monotonic || args.force_boottime) && !(unshare_flags & CLONE_NEWTIME))
311
+ errx(EXIT_FAILURE, _("options --monotonic and --boottime require "
312
+ "unsharing of a time namespace (-t)"));
313
+
314
+ if (npersists && (unshare_flags & CLONE_NEWNS))
315
+ bind_ns_files_from_child(&pid_bind, fds);
316
+
317
+ if (-1 == unshare(unshare_flags))
318
+ err(EXIT_FAILURE, _("unshare failed"));
319
+
320
+ if (args.force_boottime)
321
+ settime(boottime, CLOCK_BOOTTIME);
322
+
323
+ if (args.force_monotonic)
324
+ settime(monotonic, CLOCK_MONOTONIC);
325
+
326
+ if (args.fork) {
327
+ /* force child forking before mountspace binding
328
+ * so pid_for_children is populated.
329
+ * Silence:
330
+ * warning: pthread_create failed for timer: Invalid argument, scheduling broken
331
+ * by setting $VERBOSE = nil.
332
+ * */
333
+ VALUE res = rb_eval_string("Process.fork");
334
+ pid = NIL_P(res) ? 0 : NUM2INT(res);
335
+
336
+ switch(pid) {
337
+ case -1:
338
+ err(EXIT_FAILURE, _("fork failed"));
339
+ case 0: /* child */
340
+ if (pid_bind && (unshare_flags & CLONE_NEWNS))
341
+ close(fds[1]);
342
+ break;
343
+ default: /* parent */
344
+ break;
345
+ }
346
+ }
347
+
348
+ if (npersists && (pid || !args.fork)) {
349
+ /* run in parent */
350
+ if (pid_bind && (unshare_flags & CLONE_NEWNS)) {
351
+ int rc;
352
+ char ch = PIPE_SYNC_BYTE;
353
+
354
+ /* signal child we are ready */
355
+ write_all(fds[1], &ch, 1);
356
+ close(fds[1]);
357
+ fds[1] = -1;
358
+
359
+ /* wait for bind_ns_files_from_child() */
360
+ do {
361
+ rc = NUM2INT(PIDT2NUM(rb_waitpid(pid_bind, &status, 0)));
362
+ if (rc < 0) {
363
+ if (errno == EINTR)
364
+ continue;
365
+ rb_sys_fail("rb_waitpid");
366
+ }
367
+ if (WIFEXITED(status) &&
368
+ WEXITSTATUS(status) != EXIT_SUCCESS) {
369
+ return NUM2PIDT(INT2NUM(pid));
370
+ }
371
+ } while (rc < 0);
372
+ } else {
373
+ /* simple way, just bind */
374
+ bind_ns_files(getpid());
375
+ }
376
+ }
377
+
378
+ if (pid) {
379
+ if (!args.wait) {
380
+ return NUM2PIDT(INT2NUM(pid));
381
+ }
382
+
383
+ if (NUM2INT(PIDT2NUM(rb_waitpid(pid, &status, 0))) == -1) {
384
+ rb_sys_fail("rb_waitpid");
385
+ }
386
+
387
+ if (WIFEXITED(status)) {
388
+ return NUM2PIDT(INT2NUM(pid));
389
+ }
390
+
391
+ if (WIFSIGNALED(status)) {
392
+ kill(getpid(), WTERMSIG(status));
393
+ }
394
+
395
+ err(EXIT_FAILURE, _("child exit failed"));
396
+ }
397
+
398
+ if (args.kill_child) {
399
+ // if (kill_child_signo != 0 && prctl(PR_SET_PDEATHSIG, kill_child_signo) < 0)
400
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0)
401
+ err(EXIT_FAILURE, "prctl failed");
402
+ }
403
+
404
+ if (args.map_user != (uid_t) -1)
405
+ map_id(_PATH_PROC_UIDMAP, args.map_user, real_euid);
406
+
407
+ /* Since Linux 3.19 unprivileged writing of /proc/self/gid_map
408
+ * has been disabled unless /proc/self/setgroups is written
409
+ * first to permanently disable the ability to call setgroups
410
+ * in that user namespace. */
411
+ if (args.map_group != (gid_t) -1) {
412
+ if (args.set_groups == SETGROUPS_ALLOW)
413
+ errx(EXIT_FAILURE, _("options --setgroups=allow and "
414
+ "--map-group are mutually exclusive"));
415
+ setgroups_control(SETGROUPS_DENY);
416
+ map_id(_PATH_PROC_GIDMAP, args.map_group, real_egid);
417
+ }
418
+
419
+ if (args.set_groups != SETGROUPS_NONE)
420
+ setgroups_control(args.set_groups);
421
+
422
+ if ((unshare_flags & CLONE_NEWNS) && args.propagation)
423
+ set_propagation(args.propagation);
424
+
425
+ if (newroot) {
426
+ if (chroot(newroot) != 0)
427
+ err(EXIT_FAILURE,
428
+ _("cannot change root directory to '%s'"), newroot);
429
+ newdir = newdir ?: "/";
430
+ }
431
+ if (newdir && chdir(newdir))
432
+ err(EXIT_FAILURE, _("cannot chdir to '%s'"), newdir);
433
+
434
+ if (procmnt) {
435
+ /* When not changing root and using the default propagation flags
436
+ then the recursive propagation change of root will
437
+ automatically change that of an existing proc mount. */
438
+ if (!newroot && args.propagation != (MS_PRIVATE|MS_REC)) {
439
+ int rc = mount("none", procmnt, NULL, MS_PRIVATE|MS_REC, NULL);
440
+
441
+ /* Custom procmnt means that proc is very likely not mounted, causing EINVAL.
442
+ Ignoring the error in this specific instance is considered safe. */
443
+ if(rc != 0 && errno != EINVAL)
444
+ err(EXIT_FAILURE, _("cannot change %s filesystem propagation"), procmnt);
445
+ }
446
+
447
+ if (mount("proc", procmnt, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0)
448
+ err(EXIT_FAILURE, _("mount %s failed"), procmnt);
449
+ }
450
+
451
+ if (args.set_gid) {
452
+ if (setgroups(0, NULL) != 0) /* drop supplementary groups */
453
+ err(EXIT_FAILURE, _("setgroups failed"));
454
+ if (setgid(args.set_gid) < 0) /* change GID */
455
+ err(EXIT_FAILURE, _("setgid failed"));
456
+ }
457
+ if (args.set_uid && setuid(args.set_uid) < 0) /* change UID */
458
+ err(EXIT_FAILURE, _("setuid failed"));
459
+
460
+ /* We use capabilities system calls to propagate the permitted
461
+ * capabilities into the ambient set because we have already
462
+ * forked so are in async-signal-safe context. */
463
+ if (args.keep_caps && (unshare_flags & CLONE_NEWUSER)) {
464
+ struct __user_cap_header_struct header = {
465
+ .version = _LINUX_CAPABILITY_VERSION_3,
466
+ .pid = 0,
467
+ };
468
+
469
+ struct __user_cap_data_struct payload[_LINUX_CAPABILITY_U32S_3] = {{ 0 }};
470
+ uint64_t effective, cap;
471
+
472
+ if (capget(&header, payload) < 0)
473
+ err(EXIT_FAILURE, _("capget failed"));
474
+
475
+ /* In order the make capabilities ambient, we first need to ensure
476
+ * that they are all inheritable. */
477
+ payload[0].inheritable = payload[0].permitted;
478
+ payload[1].inheritable = payload[1].permitted;
479
+
480
+ if (capset(&header, payload) < 0)
481
+ err(EXIT_FAILURE, _("capset failed"));
482
+
483
+ effective = ((uint64_t)payload[1].effective << 32) | (uint64_t)payload[0].effective;
484
+
485
+ for (cap = 0; cap < (sizeof(effective) * 8); cap++) {
486
+ /* This is the same check as cap_valid(), but using
487
+ * the runtime value for the last valid cap. */
488
+ if (cap > (uint64_t) cap_last_cap())
489
+ continue;
490
+
491
+ if ((effective & (1 << cap))
492
+ && prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) < 0)
493
+ err(EXIT_FAILURE, _("prctl(PR_CAP_AMBIENT) failed"));
494
+ }
495
+ }
496
+
497
+ return NUM2PIDT(INT2NUM(pid));
498
+ }
@@ -0,0 +1,82 @@
1
+ #ifndef UNSHARE_H
2
+ #define UNSHARE_H 1
3
+
4
+ #include <stdbool.h>
5
+ #include <stdio.h>
6
+
7
+ #include "include/c.h"
8
+
9
+ #undef _
10
+ # define _(Text) (Text)
11
+
12
+ struct rb_unshare_args {
13
+ bool clone_newuser;
14
+ bool clone_newcgroup;
15
+ bool clone_newipc;
16
+ bool clone_newuts;
17
+ bool clone_newnet;
18
+ bool clone_newpid;
19
+ bool clone_newns;
20
+ bool clone_newtime;
21
+
22
+ bool fork;
23
+ bool wait;
24
+
25
+ // default: /proc
26
+ VALUE mount_proc;
27
+ VALUE root;
28
+
29
+ VALUE new_dir;
30
+ bool map_root_user;
31
+ bool map_current_user;
32
+ uid_t map_user;
33
+ gid_t map_group;
34
+ bool keep_caps;
35
+ uid_t set_uid;
36
+ gid_t set_gid;
37
+ int set_groups;
38
+ unsigned long propagation;
39
+ bool force_boottime;
40
+ bool force_monotonic;
41
+ bool kill_child;
42
+ };
43
+
44
+ int rb_unshare_internal(struct rb_unshare_args args);
45
+
46
+ /* synchronize parent and child by pipe */
47
+ #define PIPE_SYNC_BYTE 0x06
48
+
49
+ /* 'private' is kernel default */
50
+ #define UNSHARE_PROPAGATION_DEFAULT (MS_REC | MS_PRIVATE)
51
+
52
+ enum {
53
+ SETGROUPS_NONE = -1,
54
+ SETGROUPS_DENY = 0,
55
+ SETGROUPS_ALLOW = 1,
56
+ };
57
+
58
+ static const char *setgroups_strings[] =
59
+ {
60
+ [SETGROUPS_DENY] = "deny",
61
+ [SETGROUPS_ALLOW] = "allow"
62
+ };
63
+
64
+ #ifndef XALLOC_EXIT_CODE
65
+ # define XALLOC_EXIT_CODE EXIT_FAILURE
66
+ #endif
67
+
68
+ static inline
69
+ __attribute__((__format__(printf, 2, 3)))
70
+ int xasprintf(char **strp, const char *fmt, ...) {
71
+ int ret;
72
+ va_list args;
73
+
74
+ va_start(args, fmt);
75
+ ret = vasprintf(&(*strp), fmt, args);
76
+ va_end(args);
77
+ if (ret < 0)
78
+ err(XALLOC_EXIT_CODE, "cannot allocate string");
79
+ return ret;
80
+ }
81
+
82
+ #endif
@@ -0,0 +1,3 @@
1
+ module RUnshare
2
+ VERSION = "0.1.0"
3
+ end
data/lib/runshare.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "runshare/version"
2
+ require "runshare/runshare"
3
+
4
+ module RUnshare
5
+ class Error < StandardError; end
6
+ # Your code goes here...
7
+ end
data/runshare.gemspec ADDED
@@ -0,0 +1,44 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "runshare/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "runshare"
8
+ spec.version = RUnshare::VERSION
9
+ spec.authors = ["Ivan Prisyazhnyy"]
10
+ spec.email = ["john.koepi@gmail.com"]
11
+
12
+ spec.summary = "This tool allows to unshare Linux namespaces."
13
+ spec.description = File.read("README.md")
14
+ spec.homepage = "https://github.com/sitano/runshare"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ # spec.metadata["allowed_push_host"] = "Set to 'http://mygemserver.com'"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = "https://github.com/sitano/runshare"
24
+ # spec.metadata["changelog_uri"] = "Put your gem's CHANGELOG.md URL here."
25
+ else
26
+ raise "RubyGems 2.0 or newer is required to protect against " \
27
+ "public gem pushes."
28
+ end
29
+
30
+ # Specify which files should be added to the gem when it is released.
31
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
33
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
+ end
35
+ spec.bindir = "exe"
36
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
+ spec.require_paths = ["lib"]
38
+ spec.extensions = ["ext/runshare/extconf.rb"]
39
+
40
+ spec.add_development_dependency "bundler", "~> 1.17"
41
+ spec.add_development_dependency "rake", "~> 10.0"
42
+ spec.add_development_dependency "rake-compiler"
43
+ spec.add_development_dependency "rspec", "~> 3.0"
44
+ end