glusterfs/extras/prot_filter.py
Jeff Darcy fcc230c99d features: add a directory-protection translator
This is useful to find all calls that remove a file from the protected
directory, including renames and internal calls.  Such calls will cause
a stack trace to be logged.  There's a filter script to add the needed
translators, and then the new functionality can be invoked with one of
the following commands.

	setfattr -n trusted.glusterfs.protect -v log $dir
	setfattr -n trusted.glusterfs.protect -v reject $dir
	setfattr -n trusted.glusterfs.protect -v anything_else $dir

The first logs calls, but still allows them.  The second rejects them
with EPERM.  The third turns off protection for that directory.

Change-Id: Iee4baaf8e837106be2b4099542cb7dcaae40428c
BUG: 888072
Signed-off-by: Jeff Darcy <jdarcy@redhat.com>
Reviewed-on: http://review.gluster.org/4496
Tested-by: Gluster Build System <jenkins@build.gluster.com>
Reviewed-by: Anand Avati <avati@redhat.com>
2013-02-17 12:04:48 -08:00

145 lines
4.8 KiB
Python
Executable File

#!/usr/bin/python
"""
Copyright (c) 2013 Red Hat, Inc. <http://www.redhat.com>
This file is part of GlusterFS.
This file is licensed to you under your choice of the GNU Lesser
General Public License, version 3 or any later version (LGPLv3 or
later), or the GNU General Public License, version 2 (GPLv2), in all
cases as published by the Free Software Foundation.
"""
"""
INSTRUCTIONS
Put this in /usr/lib64/glusterfs/$version/filter to have it run automatically,
or else you'll have to run it by hand every time you change the volume
configuration. Give it a list of volume names on which to enable the
protection functionality; it will deliberately ignore client volfiles for
other volumes, and all server volfiles. It *will* include internal client
volfiles such as those used for NFS or rebalance/self-heal; this is a
deliberate choice so that it will catch deletions from those sources as well.
"""
volume_list = [ "jdtest" ]
import copy
import string
import sys
import types
class Translator:
def __init__ (self, name):
self.name = name
self.xl_type = ""
self.opts = {}
self.subvols = []
self.dumped = False
def __repr__ (self):
return "<Translator %s>" % self.name
def load (path):
# If it's a string, open it; otherwise, assume it's already a
# file-like object (most notably from urllib*).
if type(path) in types.StringTypes:
fp = file(path,"r")
else:
fp = path
all_xlators = {}
xlator = None
last_xlator = None
while True:
text = fp.readline()
if text == "":
break
text = text.split()
if not len(text):
continue
if text[0] == "volume":
if xlator:
raise RuntimeError, "nested volume definition"
xlator = Translator(text[1])
continue
if not xlator:
raise RuntimeError, "text outside volume definition"
if text[0] == "type":
xlator.xl_type = text[1]
continue
if text[0] == "option":
xlator.opts[text[1]] = string.join(text[2:])
continue
if text[0] == "subvolumes":
for sv in text[1:]:
xlator.subvols.append(all_xlators[sv])
continue
if text[0] == "end-volume":
all_xlators[xlator.name] = xlator
last_xlator = xlator
xlator = None
continue
raise RuntimeError, "unrecognized keyword %s" % text[0]
if xlator:
raise RuntimeError, "unclosed volume definition"
return all_xlators, last_xlator
def generate (graph, last, stream=sys.stdout):
for sv in last.subvols:
if not sv.dumped:
generate(graph,sv,stream)
print >> stream, ""
sv.dumped = True
print >> stream, "volume %s" % last.name
print >> stream, " type %s" % last.xl_type
for k, v in last.opts.iteritems():
print >> stream, " option %s %s" % (k, v)
if last.subvols:
print >> stream, " subvolumes %s" % string.join(
[ sv.name for sv in last.subvols ])
print >> stream, "end-volume"
def push_filter (graph, old_xl, filt_type, opts={}):
new_type = "-" + filt_type.split("/")[1]
old_type = "-" + old_xl.xl_type.split("/")[1]
pos = old_xl.name.find(old_type)
if pos >= 0:
new_name = old_xl.name
old_name = new_name[:pos] + new_type + new_name[len(old_type)+pos:]
else:
new_name = old_xl.name + old_type
old_name = old_xl.name + new_type
new_xl = Translator(new_name)
new_xl.xl_type = old_xl.xl_type
new_xl.opts = old_xl.opts
new_xl.subvols = old_xl.subvols
graph[new_xl.name] = new_xl
old_xl.name = old_name
old_xl.xl_type = filt_type
old_xl.opts = opts
old_xl.subvols = [new_xl]
graph[old_xl.name] = old_xl
if __name__ == "__main__":
path = sys.argv[1]
# Alow an override for debugging.
for extra in sys.argv[2:]:
volume_list.append(extra)
graph, last = load(path)
for v in volume_list:
if graph.has_key(v):
break
else:
print "No configured volumes found - aborting."
sys.exit(0)
for v in graph.values():
if v.xl_type == "cluster/distribute":
push_filter(graph,v,"features/prot_dht")
elif v.xl_type == "protocol/client":
push_filter(graph,v,"features/prot_client")
# We push debug/trace so that every fop gets a real frame, because DHT
# gets confused if STACK_WIND_TAIL causes certain fops to be invoked
# from anything other than a direct child.
for v in graph.values():
if v.xl_type == "features/prot_client":
push_filter(graph,v,"debug/trace")
generate(graph,last,stream=open(path,"w"))