IF YOU WOULD LIKE TO GET AN ACCOUNT, please write an email to s dot adaszewski at gmail dot com. User accounts are meant only to report issues and/or generate pull requests. This is a purpose-specific Git hosting for ADARED projects. Thank you for your understanding!
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

jail.py 9.4KB

4 년 전
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import subprocess
  2. from .zfs import *
  3. import random
  4. import shutil
  5. import json
  6. from tabulate import tabulate
  7. import os
  8. import jailconf
  9. from .mount import getmntinfo
  10. import shlex
  11. def jail_fs_create(image):
  12. image, _ = zfs_find(image, focker_type='image', zfs_type='snapshot')
  13. sha256 = bytes([ random.randint(0, 255) for _ in range(32) ]).hex()
  14. lst = zfs_list(fields=['focker:sha256'], focker_type='image')
  15. lst = list(filter(lambda a: a[0] == sha256, lst))
  16. if lst:
  17. raise ValueError('Whew, a collision...')
  18. poolname = zfs_poolname()
  19. for pre in range(7, 32):
  20. name = poolname + '/focker/jails/' + sha256[:pre]
  21. if not zfs_exists(name):
  22. break
  23. zfs_run(['zfs', 'clone', '-o', 'focker:sha256=' + sha256, image, name])
  24. return name
  25. def gen_env_command(command, env):
  26. if any(map(lambda a: ' ' in a, env.keys())):
  27. raise ValueError('Environment variable names cannot contain spaces')
  28. env = [ 'export ' + k + '=' + shlex.quote(v) \
  29. for (k, v) in env.items() ]
  30. command = ' && '.join(env + [ command ])
  31. return command
  32. def quote(s):
  33. s = s.replace('\\', '\\\\')
  34. s = s.replace('\'', '\\\'')
  35. s = '\'' + s + '\''
  36. return s
  37. def jail_create(path, command, env, mounts, hostname=None):
  38. name = os.path.split(path)[-1]
  39. if os.path.exists('/etc/jail.conf'):
  40. conf = jailconf.load('/etc/jail.conf')
  41. else:
  42. conf = jailconf.JailConf()
  43. conf[name] = blk = jailconf.JailBlock()
  44. blk['path'] = path
  45. if command:
  46. command = gen_env_command(command, env)
  47. command = quote(command)
  48. print('command:', command)
  49. blk['exec.start'] = command
  50. prestart = [ 'cp /etc/resolv.conf ' +
  51. shlex.quote(os.path.join(path, 'etc/resolv.conf')) ]
  52. poststop = []
  53. if mounts:
  54. for (from_, on) in mounts:
  55. if not from_.startswith('/'):
  56. from_, _ = zfs_find(from_, focker_type='volume')
  57. from_ = zfs_mountpoint(from_)
  58. prestart.append('mount -t nullfs ' + shlex.quote(from_) +
  59. ' ' + shlex.quote(os.path.join(path, on.strip('/'))))
  60. poststop += [ 'umount -f ' +
  61. os.path.join(path, on.strip('/')) \
  62. for (_, on) in reversed(mounts) ]
  63. if prestart:
  64. blk['exec.prestart'] = quote(' && '.join(prestart))
  65. if poststop:
  66. blk['exec.poststop'] = quote(' && '.join(poststop))
  67. blk['persist'] = True
  68. blk['interface'] = 'lo1'
  69. blk['ip4.addr'] = '127.0.1.0'
  70. blk['mount.devfs'] = True
  71. blk['exec.clean'] = True
  72. blk['host.hostname'] = hostname or name
  73. conf.write('/etc/jail.conf')
  74. return name
  75. def jail_run_v2(path, command, env, mounts):
  76. name = os.path.split(path)[-1]
  77. command = gen_env_command(command, env)
  78. jail_create(path, None, None, mounts)
  79. subprocess.check_output([ 'jail', '-c', name ])
  80. subprocess.run([ 'jexec', name, '/bin/sh', '-c', command ])
  81. subprocess.check_output([ 'jail', '-r', name ])
  82. def get_jid(path):
  83. data = json.loads(subprocess.check_output(['jls', '--libxo=json']))
  84. lst = data['jail-information']['jail']
  85. lst = list(filter(lambda a: a['path'] == path, lst))
  86. if len(lst) == 0:
  87. raise ValueError('JID not found for path: ' + path)
  88. if len(lst) > 1:
  89. raise ValueError('Ambiguous JID for path: ' + path)
  90. return str(lst[0]['jid'])
  91. def do_mounts(path, mounts):
  92. print('mounts:', mounts)
  93. for (source, target) in mounts:
  94. if source.startswith('/'):
  95. name = source
  96. else:
  97. name, _ = zfs_find(source, focker_type='volume')
  98. name = zfs_mountpoint(name)
  99. while target.startswith('/'):
  100. target = target[1:]
  101. subprocess.check_output(['mount', '-t', 'nullfs', name, os.path.join(path, target)])
  102. def undo_mounts(path, mounts):
  103. for (_, target) in reversed(mounts):
  104. while target.startswith('/'):
  105. target = target[1:]
  106. subprocess.check_output(['umount', '-f', os.path.join(path, target)])
  107. def jail_run(path, command, mounts=[]):
  108. command = ['jail', '-c', 'host.hostname=' + os.path.split(path)[1], 'persist=1', 'mount.devfs=1', 'interface=lo1', 'ip4.addr=127.0.1.0', 'path=' + path, 'command', '/bin/sh', '-c', command]
  109. print('Running:', ' '.join(command))
  110. try:
  111. do_mounts(path, mounts)
  112. shutil.copyfile('/etc/resolv.conf', os.path.join(path, 'etc/resolv.conf'))
  113. res = subprocess.run(command)
  114. finally:
  115. try:
  116. subprocess.run(['jail', '-r', get_jid(path)])
  117. except ValueError:
  118. pass
  119. subprocess.run(['umount', '-f', os.path.join(path, 'dev')])
  120. undo_mounts(path, mounts)
  121. if res.returncode != 0:
  122. # subprocess.run(['umount', os.path.join(path, 'dev')])
  123. raise RuntimeError('Command failed')
  124. def jail_stop(path):
  125. try:
  126. jid = get_jid(path)
  127. jailname = os.path.split(path)[-1]
  128. subprocess.run(['jail', '-r', jailname])
  129. except ValueError:
  130. print('JID could not be determined')
  131. # import time
  132. # time.sleep(1)
  133. mi = getmntinfo()
  134. for m in mi:
  135. mntonname = m['f_mntonname'].decode('utf-8')
  136. if mntonname.startswith(path + os.path.sep):
  137. print('Unmounting:', mntonname)
  138. subprocess.run(['umount', '-f', mntonname])
  139. def jail_remove(path):
  140. print('Removing jail:', path)
  141. jail_stop(path)
  142. subprocess.run(['zfs', 'destroy', '-r', '-f', zfs_name(path)])
  143. if os.path.exists('/etc/jail.conf'):
  144. conf = jailconf.load('/etc/jail.conf')
  145. name = os.path.split(path)[-1]
  146. if name in conf:
  147. del conf[name]
  148. conf.write('/etc/jail.conf')
  149. def command_jail_create(args):
  150. name = jail_fs_create(args.image)
  151. if args.tags:
  152. zfs_tag(name, args.tags)
  153. path = zfs_mountpoint(name)
  154. jail_create(path, args.command,
  155. { a.split(':')[0]: ':'.join(a.split(':')[1:]) \
  156. for a in args.env },
  157. [ [a.split(':')[0], ':'.join(a.split(':')[1:])] \
  158. for a in args.mounts ],
  159. args.hostname )
  160. print(sha256)
  161. print(path)
  162. def command_jail_start(args):
  163. name, _ = zfs_find(args.reference, focker_type='jail')
  164. path = zfs_mountpoint(name)
  165. jailname = os.path.split(path)[-1]
  166. subprocess.run(['jail', '-c', jailname])
  167. def command_jail_stop(args):
  168. name, _ = zfs_find(args.reference, focker_type='jail')
  169. path = zfs_mountpoint(name)
  170. jail_stop(path)
  171. def command_jail_remove(args):
  172. name, _ = zfs_find(args.reference, focker_type='jail')
  173. path = zfs_mountpoint(name)
  174. jail_remove(path)
  175. def command_jail_exec(args):
  176. name, _ = zfs_find(args.reference, focker_type='jail')
  177. path = zfs_mountpoint(name)
  178. jid = get_jid(path)
  179. subprocess.run(['jexec', str(jid)] + args.command)
  180. def command_jail_oneshot(args):
  181. name = jail_fs_create(args.image)
  182. path = zfs_mountpoint(name)
  183. env = { a.split(':')[0]: ':'.join(a.split(':')[1:]) \
  184. for a in args.env }
  185. mounts = [ [ a.split(':')[0], a.split(':')[1] ] \
  186. for a in args.mounts]
  187. jailname = jail_create(path,
  188. ' '.join(map(shlex.quote, args.command or ['/bin/sh'])),
  189. env, mounts)
  190. subprocess.run(['jail', '-c', jailname])
  191. jail_remove(path)
  192. # Deprecated
  193. def command_jail_oneshot_old():
  194. base, _ = zfs_snapshot_by_tag_or_sha256(args.image)
  195. # root = '/'.join(base.split('/')[:-1])
  196. for _ in range(10**6):
  197. sha256 = bytes([ random.randint(0, 255) for _ in range(32) ]).hex()
  198. name = sha256[:7]
  199. name = base.split('/')[0] + '/focker/jails/' + name
  200. if not zfs_exists(name):
  201. break
  202. zfs_run(['zfs', 'clone', '-o', 'focker:sha256=' + sha256, base, name])
  203. try:
  204. mounts = list(map(lambda a: a.split(':'), args.mounts))
  205. jail_run(zfs_mountpoint(name), args.command, mounts)
  206. # subprocess.check_output(['jail', '-c', 'interface=lo1', 'ip4.addr=127.0.1.0', 'path=' + zfs_mountpoint(name), 'command', command])
  207. finally:
  208. # subprocess.run(['umount', zfs_mountpoint(name) + '/dev'])
  209. zfs_run(['zfs', 'destroy', '-f', name])
  210. # raise
  211. def command_jail_list(args):
  212. lst = zfs_list(fields=['focker:sha256,focker:tags,mountpoint'], focker_type='jail')
  213. jails = subprocess.check_output(['jls', '--libxo=json'])
  214. jails = json.loads(jails)['jail-information']['jail']
  215. jails = { j['path']: j for j in jails }
  216. lst = list(map(lambda a: [ a[1],
  217. a[0] if args.full_sha256 else a[0][:7],
  218. a[2],
  219. jails[a[2]]['jid'] if a[2] in jails else '-' ], lst))
  220. print(tabulate(lst, headers=['Tags', 'SHA256', 'mountpoint', 'JID']))
  221. def command_jail_tag(args):
  222. name, _ = zfs_find(args.reference, focker_type='jail')
  223. zfs_untag(args.tags, focker_type='jail')
  224. zfs_tag(name, args.tags)
  225. def command_jail_untag(args):
  226. zfs_untag(args.tags, focker_type='jail')
  227. def command_jail_prune(args):
  228. jails = subprocess.check_output(['jls', '--libxo=json'])
  229. jails = json.loads(jails)['jail-information']['jail']
  230. used = set()
  231. for j in jails:
  232. used.add(j['path'])
  233. lst = zfs_list(fields=['focker:sha256,focker:tags,mountpoint,name'], focker_type='jail')
  234. for j in lst:
  235. if j[1] == '-' and (j[2] not in used or args.force):
  236. jail_remove(j[2])