runshare 0.1.0

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