#!/usr/bin/python # Author: Donald Miner # # Copyright (C) 2003 - 2005 Tresys Technology, LLC # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 2. """ This script generates XML documentation information for layers specified by the user. """ import sys import os import glob # GLOBALS class dec_style: ''' "Declaration Style" Specifies the syntax of a declaration. Intended to be used with getParams(). ''' # Example of a line: foo(bar,one,two); # A style that would fit this: dec_style("foo(",3,",",");") # "foo(" - the opening of it, ends at the begining of the first param. # 3 - the number of parameters. # "," - the delimeter to parse apart parameters. # ");" - the end of the declaration statement. def __init__(self,open_str,params,delim,close_str): self.open_str = open_str self.params = params self.delim = delim self.close_str = close_str INTERFACE = dec_style("interface(`",1,None,"'") TEMPLATE = dec_style("template(`",1,None,"'") TUNABLE = dec_style("gen_tunable(",2,",",")") # boolean FIXME: may have to change in the future. BOOLEAN = dec_style("gen_bool(",2,",",")") # Default values of command line arguments. directory = "./" warn = False meta = "metadata" layers = [] tunable_files = [] # FUNCTIONS def getXMLComment(line): ''' Returns the XML comment, (removes "## " from the front of the line). Returns False if the line is not an XML comment. ''' for i in range(0,len(line)-1): # Check if the first 3 characters are "## " if line[i:i+3] in ("## ", "##\t"): # The chars before '#' in the line must be whitespace. if i > 0 and not line[0:i-1].isspace(): return False else: return line[i+3:] # No XML comment. return False def getParams(line, style): ''' Returns a list of items, containing the values of each parameter. ''' # Clean out whitespace. temp_line = line.strip() # Check to see if the string begins with the specified opening # string specified by style. if temp_line[0:len(style.open_str)] == style.open_str: temp_line = temp_line[len(style.open_str):].strip() else: return False # If there is a delimeter. if style.delim: temp_line = temp_line.split(style.delim) else: temp_line = [temp_line] # Only interested in a sertain number of tokens, specified by style. temp_line = temp_line[:style.params] # Remove the end of the declaration, specified by style. end = temp_line[-1].find(style.close_str) if end == -1: warning("line \"%s\" may be syntactically incorrect"\ % line.strip()) return False temp_line[-1] = temp_line[-1][:end] # Remove whitespace for i in range(0,len(temp_line)-1): temp_line[i] = temp_line[i].strip() return temp_line def getModuleXML(file_name): ''' Returns the XML data for a module in a list, one line per list item. ''' # Try to open the file, if it cant, just ignore it. try: module_file = open(file_name, "r") module_code = module_file.readlines() module_file.close() except: warning("cannot open file %s for read, skipping" % file_name) return [] module_buf = [] # Infer the module name, which is the base of the file name. module_buf.append("\n" % os.path.splitext(os.path.split(file_name)[-1])[0]) temp_buf = [] # Phases: find header - looking for the header of the file. # get header - get the header comments and stop when first # whitespace is encountered. # find interface - looking for interfaces to get info for. phase = "find header" # Go line by line and figure out what to do with it. for line in module_code: # In this phase, whitespace and stray code is ignored at the # top of the file. if phase == "find header": if line.isspace(): continue # Once a comment is encountered, start trying to get the # header documentation. elif getXMLComment(line): phase = "get header" # If an interface is found, there is no header, and no # documentation for the interface. elif getParams(line,INTERFACE)\ or getParams(line,TEMPLATE): phase = "find interface" # In this phase, XML comments are being retrieved for the file. if phase == "get header": if getXMLComment(line): temp_buf.append(getXMLComment(line)) continue # If the line is whitespace, the file header is over, # continue on to find interfaces. elif line.isspace(): module_buf += temp_buf temp_buf = [] phase = "find interface" continue # Oops! The comments we have been getting weren't part # of the header so attribute them to an interface # instead. elif getParams(line,INTERFACE)\ or getParams(line,TEMPLATE): phase = "find interface" # In this phase, XML comments are being attributed if phase == "find interface": if getXMLComment(line): temp_buf.append(getXMLComment(line)) continue # If the line is the declaration of a interface, # infer the interface name and add all the comments # to the main buffer. elif getParams(line,INTERFACE): module_buf.append("\n"\ % getParams(line,INTERFACE)[0]) if len(temp_buf): module_buf += temp_buf else: module_buf.append("\n") module_buf.append("Summary is unspecified.\n") module_buf.append("\n") module_buf.append("\n") module_buf.append("Parameters are unspecified.\n") module_buf.append("\n") temp_buf = [] module_buf.append("\n") continue elif getParams(line,TEMPLATE): module_buf.append("\n") # If there are XML comments at the end of the file, they arn't # attributed to anything. These are ignored. if len(temp_buf): warning("orphan XML comments at bottom of file %s" % file_name) module_buf.append("\n") return module_buf def getLayerXML(directory): ''' Returns the XML documentation for a layer. ''' layer_buf = [] # Infer the layer name from the directory name. layer_buf.append("\n" % os.path.basename(directory)) # Try to open the metadata file for this directory and if it exists, # append the contents to the buffer. try: layer_meta = open(directory+"/"+meta, "r") layer_buf += layer_meta.readlines() layer_meta.close() except: warning("cannot open file %s for read, assuming no data"\ % meta) # For each module file in the layer, add its XML. for module in glob.glob("%s/*.if" % directory): layer_buf += getModuleXML(module) layer_buf.append("\n") return layer_buf def getTunableXML(file_name): ''' Return all the XML for the tunables in the file specified. ''' # Try to open the file, if it cant, just ignore it. try: tunable_file = open(file_name, "r") tunable_code = tunable_file.readlines() tunable_file.close() except: warning("cannot open file %s for read, skipping" % file_name) return [] tunable_buf = [] temp_buf = [] # Find tunables and booleans line by line and use the comments above # them. for line in tunable_code: # If it is an XML comment, add it to the buffer and go on. if getXMLComment(line): temp_buf.append(getXMLComment(line)) continue # Get the parameters of a TUNABLE style line. params = getParams(line,TUNABLE) # If the line is not a TUNABLE style declaration, try BOOLEAN. if not params: params = getParams(line,BOOLEAN) # If the line is one of the two styles above, add a tunable tag # and give it the data from the temprorary buffer. if params: tunable_buf.append\ ("\n" % (params[0], params[1])) tunable_buf += temp_buf temp_buf = [] tunable_buf.append("\n") # If there are XML comments at the end of the file, they arn't # attributed to anything. These are ignored. if len(temp_buf): warning("orphan XML comments at bottom of file %s" % file_name) return tunable_buf def getPolicyXML(directory): ''' Return the compelete reference policy XML documentation through a list, one line per item. ''' # Keep track of original path so that it will change back at the end. old_dir = os.path.abspath(os.path.curdir) # Attempt to change directory into the policy directory. If it doesn't # exist just return an empty documentation. try: os.chdir(directory) except: warning("cannot change directory to %s, ignoring"\ % directory) return [] policy_buf = [] policy_buf.append("\n") # Add to the XML each layer specified by the user. for layer in layers: policy_buf += getLayerXML(layer) # Add to the XML each tunable specified by the user. for tunable_file in tunable_files: policy_buf += getTunableXML(tunable_file) policy_buf.append("\n") # Return to old directory. try: os.chdir(old_dir) except: error("cannot change directory to %s" % old_dir) return policy_buf def usage(): """ Displays a message describing the proper usage of this script. """ sys.stdout.write("usage: %s [-w] [-d directory] [-m file] "\ % sys.argv[0]) sys.stdout.write("layerdirectory [layerdirectory...]\n\n") sys.stdout.write("Options:\n") sys.stdout.write("-w --warn -- "+\ "show warnings\n") sys.stdout.write("-m --meta -- "+\ "the filename of the metadata in each layer\n") sys.stdout.write("-d --directory -- "+\ "directory where the layers are\n") sys.stdout.write("-t --tunable -- "+\ "A file containing tunable declarations\n") def warning(description): ''' Warns the user of a non-critical error. ''' if warn: sys.stderr.write("%s: " % sys.argv[0] ) sys.stderr.write("warning: " + description + "\n") def error(description): ''' Describes an error and exists the program. ''' sys.stderr.write("%s: " % sys.argv[0] ) sys.stderr.write("error: " + description + "\n") sys.stderr.flush() sys.exit(1) # MAIN PROGRAM # Check that there are command line arguments. if len(sys.argv) <= 1: usage() sys.exit(1) # Parse the command line arguments for i in range(1, len(sys.argv)): if sys.argv[i-1] in ("-d", "--directory", "-m", "--meta",\ "-t", "--tunable"): continue elif sys.argv[i] in ("-w", "--warn"): warn = True elif sys.argv[i] in ("-d", "--directory"): if i < len(sys.argv)-1: directory = sys.argv[i+1] else: usage() elif sys.argv[i] in ("-m", "--meta"): if i < len(sys.argv)-1: meta = sys.argv[i+1] else: usage() elif sys.argv[i] in ("-t", "--tunable"): if i < len(sys.argv)-1: tunable_files.append(sys.argv[i+1]) else: usage() else: layers.append(sys.argv[i]) # Generate the XML and output it to a file lines = getPolicyXML(directory) for s in lines: sys.stdout.write(s)