Skip to content

Instantly share code, notes, and snippets.

@ghfields
Created December 19, 2018 18:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ghfields/53e3ecf076e1237a779a6eaee357bf0f to your computer and use it in GitHub Desktop.
Save ghfields/53e3ecf076e1237a779a6eaee357bf0f to your computer and use it in GitHub Desktop.
ZFS Features Manual Comparison
#!/usr/bin/env python3
# A messy script that figures out features.
# It's... uh... messy. Sorry.
# Feel free to steal it, modify it to suit your own needs, et cetera.
# I am not responsible if this script eats your laundry.
# License: CC0 https://creativecommons.org/share-your-work/public-domain/cc0/
# Yes, this uses manpages.
#
# Why? Because I'm not going to manually make such a table, I'm lazy. I'm not
# going to read through all of the ZFS implementation code to figure out if
# you've implemented a feature or not. I'm not going to ask people every so
# often about which features they've implemented. All of these options have me
# remembering to do work that I don't want to do.
#
# "But manpages can be wrong!" you cry out! Well, in that case you have a bug.
# Fix your manpages, you not-good-at-documenting-code person.
#
# "The script is wrong!" you say. Well, in that case, contact me on freenode,
# and tell me why it's wrong. My nick is zgrep.
from sys import argv
if len(argv) == 1:
path = '.'
elif len(argv) != 2:
print('Usage:', argv[0], 'path')
exit(1)
else:
path = argv[1]
from collections import defaultdict
from urllib.request import urlopen
from datetime import datetime
from re import sub as regex, findall
from json import loads as dejson
def zfsonlinux():
sources = {'master':'https://raw.githubusercontent.com/zfsonlinux/zfs/master/man/man5/zpool-features.5'}
with urlopen('https://zfsonlinux.org') as web:
versions = findall(r'download/zfs-([0-9.]+)', web.read().decode('utf-8', 'ignore'))
for ver in set(versions):
sources[ver] = 'https://raw.githubusercontent.com/zfsonlinux/zfs/zfs-{}/man/man5/zpool-features.5'.format(ver)
return sources
def openzfsonosx():
sources = {'master': 'https://raw.githubusercontent.com/openzfsonosx/zfs/master/man/man5/zpool-features.5'}
with urlopen('https://api.github.com/repos/openzfsonosx/zfs/tags') as web:
try:
tags = dejson(web.read().decode('utf-8', 'ignore'))
tags = [ x['name'].lstrip('zfs-') for x in tags ]
tags.sort()
tags = tags[-2:]
except:
tags = []
for ver in tags:
sources[ver] = 'https://raw.githubusercontent.com/openzfsonosx/zfs/zfs-{}/man/man5/zpool-features.5'.format(ver)
return sources
def freebsd():
sources = {'head': 'https://svnweb.freebsd.org/base/head/cddl/contrib/opensolaris/cmd/zpool/zpool-features.7?view=co'}
with urlopen('https://www.freebsd.org/releases/') as web:
versions = findall(r'/releases/([0-9.]+?)R', web.read().decode('utf-8', 'ignore'))
with urlopen('https://svnweb.freebsd.org/base/release/') as web:
data = web.read().decode('utf-8', 'ignore')
actualversions = []
for ver in set(versions):
found = list(sorted(findall(
r'/base/release/(' + ver.replace('.', '\\.') + r'[0-9.]*)',
data
)))
if found:
actualversions.append(found[-1])
for ver in actualversions:
sources[ver] = 'https://svnweb.freebsd.org/base/release/{}/cddl/contrib/opensolaris/cmd/zpool/zpool-features.7?view=co'.format(ver)
return sources
def omniosce():
sources = {'master': 'https://raw.githubusercontent.com/omniosorg/illumos-omnios/master/usr/src/man/man5/zpool-features.5'}
with urlopen('https://omniosce.org/releasenotes.html') as web:
versions = findall(r'omnios-build/blob/(r[0-9]+)', web.read().decode('utf-8', 'ignore'))
for ver in versions:
sources[ver] = 'https://raw.githubusercontent.com/omniosorg/illumos-omnios/{}/usr/src/man/man5/zpool-features.5'.format(ver)
return sources
def joyent():
sources = {'master': 'https://raw.githubusercontent.com/joyent/illumos-joyent/master/usr/src/man/man5/zpool-features.5'}
with urlopen('https://github.com/joyent/illumos-joyent') as web:
versions = findall(r'data-name="release-([0-9]+)"', web.read().decode('utf-8', 'ignore'))
versions.sort()
versions = versions[-2:]
for ver in versions:
sources[ver] = 'https://raw.githubusercontent.com/joyent/illumos-joyent/release-{}/usr/src/man/man5/zpool-features.5'.format(ver)
return sources
def nexenta():
sources = {'master': 'https://raw.githubusercontent.com/Nexenta/illumos-nexenta/master/usr/src/man/man5/zpool-features.5'}
with urlopen('https://github.com/Nexenta/illumos-nexenta') as web:
versions = findall(r'data-name="release-([0-9.FP-]+)"', web.read().decode('utf-8', 'ignore'))
versions.sort()
for ver in versions:
sources[ver] = 'https://raw.githubusercontent.com/Nexenta/illumos-nexenta/release-{}/usr/src/man/man5/zpool-features.5'.format(ver)
return sources
sources = {
'ZFS on Linux': zfsonlinux(),
'FreeBSD': freebsd(),
'OpenZFS on OSX': openzfsonosx(),
'OmniOSCE': omniosce(),
'Joyent': joyent(),
'Nexenta': nexenta(),
'OpenZFS': {
'master': 'https://raw.githubusercontent.com/openzfs/openzfs/master/usr/src/man/man5/zpool-features.5',
},
'DragonFlyBSD': {
'zfsport': 'https://raw.githubusercontent.com/victoredwardocallaghan/DragonFlyBSD/zfs-port/cddl/contrib/opensolaris/cmd/zpool/zpool-features.7',
},
'NetBSD': {
'main': 'http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/external/cddl/osnet/sbin/zpool/zpool-features.7?only_with_tag=MAIN',
},
# 'OpenZFS on Windows': {
# 'master': 'https://raw.githubusercontent.com/openzfsonwindows/ZFSin/master/ZFSin/zfs/man/man5/zpool-features.5',
# },
}
features = defaultdict(list)
readonly = dict()
for name, sub in sources.items():
for ver, url in sub.items():
with urlopen(url) as c:
man = c.read().decode('utf-8')
for line in man.split('\n'):
if line.startswith('.It '):
line = line[4:]
if line.startswith('GUID'):
guid = line.split()[-1]
features[guid].append((name, ver))
elif line.startswith('READ\\-ONLY COMPATIBLE'):
readonly[guid] = line.split()[-1]
header = list(sorted(sources.keys()))
header = list(zip(header, (sorted(sources[name],
key=lambda x: regex(r'[^0-9]', '', x) or x) for name in header)))
header.append(('Sortix', ('current',)))
html = open(path + '/zfs.html', 'w')
mw = open(path + '/zfs.txt', 'w')
html.write('''<!DOCTYPE html>
<title>ZFS Feature Matrix</title>
<meta charset="utf-8" /><meta name="referrer" content="never" />
<style>body{font-family: "Helvetica", "Arial", sans-serif}
.yes{background-color:lightgreen}
.warn{background-color:yellow}
.no{background-color:lightsalmon}
table{border-collapse: collapse}
th,td{padding:0.2em 0.4em;border:1px solid #aaa;background-color:#f9f9f9}
th{background-color:#eaecf0}
th[scope=row]{text-align:left}</style>
''')
html.write('<table>\n')
html.write('<tr><th scope="col" rowspan="2">Feature Flag</th>')
html.write('<th scole="col" rowspan="2">Read-Only<br />Compatible</th>')
mw.write('{| class="wikitable"\n')
mw.write('!rowspan=2|Feature Flag\n')
mw.write('!rowspan=2|Read-Only<br />Compatible\n')
for name, vers in header:
html.write('<th scope="col" colspan="' + str(len(vers)) + '">' + name + '</th>')
mw.write('!colspan=' + str(len(vers)) + '|' + name + '\n')
html.write('</tr>\n<tr>')
mw.write('|-\n')
for _, vers in header:
for ver in vers:
html.write('<td>' + ver + '</td>')
mw.write('| ' + ver + '\n')
html.write('</tr>\n')
mw.write('|-\n')
for feature, names in sorted(features.items()):
html.write('<tr><th scope="row">' + feature + '</th>')
mw.write('!style="text-align:left"|' + feature + '\n')
if readonly[feature] == 'yes':
html.write('<td class="yes">yes</td>')
mw.write('|style="background-color:lightgreen"|yes\n')
else:
html.write('<td class="warn">no</td>')
mw.write('|style="background-color:yellow"|no\n')
for name, vers in header:
for ver in vers:
if (name, ver) in names:
html.write('<td class="yes">yes</td>')
mw.write('|style="background-color:lightgreen"|yes\n')
else:
html.write('<td class="no">no</td>')
mw.write('|style="background-color:lightsalmon"|no\n')
html.write('</tr>\n')
mw.write('|-\n')
html.write('</table>\n')
mw.write('|}\n')
now = datetime.now().isoformat() + 'Z'
html.write('<p>This works by parsing manpages for feature flags, and is entirely dependent on good, accurate documentation.<br />Last updated on ' + now + ' using <a href="zfs.py">zfs.py</a>.</p>\n')
mw.write("This works by parsing manpages for feature flags, and is entirely dependent on good, accurate documentation.<br />Last updated on " + now + " using [https://soluble.zgrep.org/zfs.py ''zfs.py''].\n")
html.close()
mw.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment