Features: * Zero changes to existing codes. * Utilizing existing libmultipath codes. * Library user guide is in 'man 3 libdmmp.h'. * Every public function has its own manpage in section 3 generated by linux 'kernel-doc' tool. Usage: make -j5 sudo make install \ bindir=/usr/sbin/ \ syslibdir=/usr/lib64/ \ libdir=/usr/lib64/multipath \ rcdir=/etc/rc.d/init.d \ unitdir=/usr/lib/systemd/system \ includedir=/usr/include make libdmmp_check man libdmmp.h man dmmp_mpath_array_get man <dmmp function name> User case: Storaged multipath plugin: https://github.com/storaged-project/storaged/pull/40 FAQ: 1. Why not use better approach like wrapping multipathd IPC output? That often means a lot changes to existing code which might be rejected. I would like to create a stable set of API, while its internal implementation could be changed without breaking binary compatibility. 2. Why not build on existing libmultipath internal library? The libmultipath has too many public symbols which seems a bad design for public library. Yes, we still expose some internal symbols via libdmmp currently, that's because we are depending on libmultipath right now, to fix that we need to change libmultipath which I intend to avoid at this initial path set. 3. Any developer notes? Following Linux kernel code style and libabc guideline. Others are recorded in 'libdmmp/DEV_NOTES' 4. Can the library be licensed as LGPL? Nope. LGPL library cannot link to any GPL code, but our dependent libmultipath library is GPL. You could create some D-BUS API use libdmmp if license concerns. We might able to license libdmmp to LGPL when some day we change our implementation. 5. Why not expose all properties out? Let's do this step by step. This commit only contains minimum API set required to create the initial storaged multipath plugin. Signed-off-by: Gris Ge <fge@xxxxxxxxxx> --- .gitignore | 3 + Makefile | 4 + Makefile.inc | 5 +- libdmmp/DEV_NOTES | 44 + libdmmp/Makefile | 75 ++ libdmmp/docs/kernel-doc | 2703 +++++++++++++++++++++++++++++++++++++++++++ libdmmp/docs/libdmmp.h.3 | 110 ++ libdmmp/docs/split-man.pl | 41 + libdmmp/libdmmp.c | 421 +++++++ libdmmp/libdmmp.pc.in | 10 + libdmmp/libdmmp/libdmmp.h | 514 ++++++++ libdmmp/libdmmp_misc.c | 85 ++ libdmmp/libdmmp_mp.c | 165 +++ libdmmp/libdmmp_path.c | 98 ++ libdmmp/libdmmp_pg.c | 177 +++ libdmmp/libdmmp_private.h | 133 +++ libdmmp/test/Makefile | 27 + libdmmp/test/libdmmp_test.c | 123 ++ 18 files changed, 4737 insertions(+), 1 deletion(-) create mode 100644 libdmmp/DEV_NOTES create mode 100644 libdmmp/Makefile create mode 100755 libdmmp/docs/kernel-doc create mode 100644 libdmmp/docs/libdmmp.h.3 create mode 100755 libdmmp/docs/split-man.pl create mode 100644 libdmmp/libdmmp.c create mode 100644 libdmmp/libdmmp.pc.in create mode 100644 libdmmp/libdmmp/libdmmp.h create mode 100644 libdmmp/libdmmp_misc.c create mode 100644 libdmmp/libdmmp_mp.c create mode 100644 libdmmp/libdmmp_path.c create mode 100644 libdmmp/libdmmp_pg.c create mode 100644 libdmmp/libdmmp_private.h create mode 100644 libdmmp/test/Makefile create mode 100644 libdmmp/test/libdmmp_test.c diff --git a/.gitignore b/.gitignore index 7f25d0e..c3cb539 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ multipath/multipath multipathd/multipathd mpathpersist/mpathpersist .nfs* +libdmmp/docs/man/*.3 +libdmmp/*.so.* +libdmmp/test/libdmmp_test diff --git a/Makefile b/Makefile index baf7753..f26acb4 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ BUILDDIRS = \ libmultipath/prioritizers \ libmultipath/checkers \ libmpathpersist \ + libdmmp \ multipath \ multipathd \ mpathpersist \ @@ -79,3 +80,6 @@ release: rpm: release rpmbuild -bb multipath-tools.spec + +libdmmp_check: all + $(MAKE) -C libdmmp check diff --git a/Makefile.inc b/Makefile.inc index c3ed73f..fbc6851 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -31,12 +31,13 @@ ifndef SYSTEMDPATH SYSTEMDPATH=usr/lib endif -prefix = +prefix = exec_prefix = $(prefix) bindir = $(exec_prefix)/sbin libudevdir = $(prefix)/$(SYSTEMDPATH)/udev udevrulesdir = $(libudevdir)/rules.d multipathdir = $(TOPDIR)/libmultipath +libdmmpdir = $(TOPDIR)/libdmmp mandir = $(prefix)/usr/share/man/man8 man5dir = $(prefix)/usr/share/man/man5 man3dir = $(prefix)/usr/share/man/man3 @@ -45,6 +46,8 @@ syslibdir = $(prefix)/$(LIB) libdir = $(prefix)/$(LIB)/multipath unitdir = $(prefix)/$(SYSTEMDPATH)/systemd/system mpathpersistdir = $(TOPDIR)/libmpathpersist +includedir = $(prefix)/usr/include +pkgconfdir = $(prefix)/usr/share/pkgconfig GZIP = gzip -9 -c INSTALL_PROGRAM = install diff --git a/libdmmp/DEV_NOTES b/libdmmp/DEV_NOTES new file mode 100644 index 0000000..a7f0f3f --- /dev/null +++ b/libdmmp/DEV_NOTES @@ -0,0 +1,44 @@ +== Planed features == + * Find a way to hide symbol exposed by libmultipath + * Make /usr/bin/multipath and this library simply query information from + multipathd via IPC. + +== Code style == + * Keep things as simple as possible. + * Linux Kernel code style. + * Don't use typedef. + * Don't use enum unless it's for user input argument. + * We are not smarter than API user, so don't create wrapping function like: + + ``` + dmmp_mpath_search_by_id(struct dmmp_context *ctx, + struct dmmp_mpath **dmmp_mp, + uint32_t dmmp_mp_count, const char *id) + + dmmp_path_group_id_search(struct dmmp_mpath *dmmp_mp, + const char *blk_name) + ``` + * The performance is the same for query single mpath and query all mpaths, + so no `dmmp_mpath_of_wwid(struct dmmp_context *ctx, const char *wwid)` yet. + +== Naming scheme == + * Public constants should be named as `DMMP_XXX_YYY`. + * Public functions should be named as `dmmp_<noun>_<verb>`. + * Private constants should be named as `_DMMP_XXX_YYY`. + * Private functions should be named as `_dmmp_<noun>_<verb>`. + + +== Code Layout == + * libdmmp_private.h + Internal functions or macros. + * libdmmp.c + For `struct dmmp_context` related functions and deal with libmultipath + internal library. + * libdmmp_mp.c + For `struct dmmp_mpath` + * libdmmp_pg.c + For `struct dmmp_path_group` + * libdmmp_path.c + For `struct dmmp_path` + * libdmmp_misc.c + Misc diff --git a/libdmmp/Makefile b/libdmmp/Makefile new file mode 100644 index 0000000..9f70398 --- /dev/null +++ b/libdmmp/Makefile @@ -0,0 +1,75 @@ +# Makefile +# +# Copyright (C) 2015 - 2016 Red Hat, Inc. +# Gris Ge <fge@xxxxxxxxxx> +# +include ../Makefile.inc + +LIBDMMP_VERSION=0.1.0 +SONAME=$(LIBDMMP_VERSION) +DEVLIB = libdmmp.so +LIBS = $(DEVLIB).$(SONAME) +LIBDEPS = -L$(multipathdir) -lmultipath +PKGFILE = libdmmp.pc +EXTRA_MAN_FILES = libdmmp.h.3 +HEADERS = libdmmp/libdmmp.h +OBJS = libdmmp.o libdmmp_mp.o libdmmp_pg.o libdmmp_path.o libdmmp_misc.o + +# Once we cleaned up libmultipath public symbol, we could use hide visibility +#CFLAGS += -fvisibility=hidden -I$(multipathdir) -I$(libdmmpdir) +CFLAGS += -fvisibility=default -I$(multipathdir) -I$(libdmmpdir) + +all: $(LIBS) doc + +$(LIBS): $(OBJS) + $(CC) $(LDFLAGS) $(SHARED_FLAGS) \ + -Wl,-exclude-libs,-soname=$@ $(CFLAGS) -o $@ $(OBJS) $(LIBDEPS) + ln -sf $@ $(DEVLIB) + +install: + $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS) + $(INSTALL_PROGRAM) -m 644 -D \ + $(HEADERS) $(DESTDIR)/$(includedir)/$(HEADERS) + ln -sf $(LIBS) $(DESTDIR)/$(syslibdir)/$(DEVLIB) + $(INSTALL_PROGRAM) -m 644 -D \ + $(PKGFILE).in $(DESTDIR)/$(pkgconfdir)/$(PKGFILE) + perl -i -pe 's|__VERSION__|$(LIBDMMP_VERSION)|g' \ + $(DESTDIR)/$(pkgconfdir)/$(PKGFILE) + perl -i -pe 's|__LIBMPDIR__|$(libdir)|g' \ + $(DESTDIR)/$(pkgconfdir)/$(PKGFILE) + perl -i -pe 's|__LIBDIR__|$(syslibdir)|g' \ + $(DESTDIR)/$(pkgconfdir)/$(PKGFILE) + perl -i -pe 's|__INCLUDEDIR__|$(includedir)|g' \ + $(DESTDIR)/$(pkgconfdir)/$(PKGFILE) + @for file in docs/man/*.3.gz; do \ + $(INSTALL_PROGRAM) -m 644 -D \ + $$file \ + $(DESTDIR)/$(man3dir)/ || exit $?; \ + done + +uninstall: + rm -f $(DESTDIR)$(syslibdir)/$(LIBS) + rm -f $(DESTDIR)$(includedir)/$(HEADERS) + rm -f $(DESTDIR)/$(syslibdir)/$(DEVLIB) + @for file in $(DESTDIR)/$(man3dir)/dmmp_*; do \ + rm $$file; \ + done + rm -f $(DESTDIR)$(man3dir)/libdmmp.h* + +clean: + rm -f core *.a *.o *.gz *.so *.so.* + rm -f docs/man/*.3.gz + $(MAKE) -C test clean + +check: all + $(MAKE) -C test check + +doc: + @for file in $(EXTRA_MAN_FILES); do \ + cp -fv docs/$$file docs/man/$$file; \ + done + perl docs/kernel-doc -man $(HEADERS) | perl docs/split-man.pl docs/man + @for file in docs/man/*.3; do \ + gzip -f $$file; \ + done + find docs/man -type f -name \*[0-9].gz diff --git a/libdmmp/docs/kernel-doc b/libdmmp/docs/kernel-doc new file mode 100755 index 0000000..9a08fb5 --- /dev/null +++ b/libdmmp/docs/kernel-doc @@ -0,0 +1,2703 @@ +#!/usr/bin/perl -w + +use strict; + +## Copyright (c) 1998 Michael Zucchi, All Rights Reserved ## +## Copyright (C) 2000, 1 Tim Waugh <twaugh@xxxxxxxxxx> ## +## Copyright (C) 2001 Simon Huggins ## +## Copyright (C) 2005-2012 Randy Dunlap ## +## Copyright (C) 2012 Dan Luedtke ## +## ## +## #define enhancements by Armin Kuster <akuster@xxxxxxxxxx> ## +## Copyright (c) 2000 MontaVista Software, Inc. ## +## ## +## This software falls under the GNU General Public License. ## +## Please read the COPYING file for more information ## + +# 18/01/2001 - Cleanups +# Functions prototyped as foo(void) same as foo() +# Stop eval'ing where we don't need to. +# -- huggie@xxxxxxxx + +# 27/06/2001 - Allowed whitespace after initial "/**" and +# allowed comments before function declarations. +# -- Christian Kreibich <ck@xxxxxxxxx> + +# Still to do: +# - add perldoc documentation +# - Look more closely at some of the scarier bits :) + +# 26/05/2001 - Support for separate source and object trees. +# Return error code. +# Keith Owens <kaos@xxxxxxxxxx> + +# 23/09/2001 - Added support for typedefs, structs, enums and unions +# Support for Context section; can be terminated using empty line +# Small fixes (like spaces vs. \s in regex) +# -- Tim Jansen <tim@xxxxxxxxxx> + +# 25/07/2012 - Added support for HTML5 +# -- Dan Luedtke <mail@xxxxxxxx> + +# +# This will read a 'c' file and scan for embedded comments in the +# style of gnome comments (+minor extensions - see below). +# + +# Note: This only supports 'c'. + +# usage: +# kernel-doc [ -docbook | -html | -html5 | -text | -man | -list ] +# [ -no-doc-sections ] +# [ -function funcname [ -function funcname ...] ] +# c file(s)s > outputfile +# or +# [ -nofunction funcname [ -function funcname ...] ] +# c file(s)s > outputfile +# +# Set output format using one of -docbook -html -html5 -text or -man. +# Default is man. +# The -list format is for internal use by docproc. +# +# -no-doc-sections +# Do not output DOC: sections +# +# -function funcname +# If set, then only generate documentation for the given function(s) or +# DOC: section titles. All other functions and DOC: sections are ignored. +# +# -nofunction funcname +# If set, then only generate documentation for the other function(s)/DOC: +# sections. Cannot be used together with -function (yes, that's a bug -- +# perl hackers can fix it 8)) +# +# c files - list of 'c' files to process +# +# All output goes to stdout, with errors to stderr. + +# +# format of comments. +# In the following table, (...)? signifies optional structure. +# (...)* signifies 0 or more structure elements +# /** +# * function_name(:)? (- short description)? +# (* @parameterx: (description of parameter x)?)* +# (* a blank line)? +# * (Description:)? (Description of function)? +# * (section header: (section description)? )* +# (*)?*/ +# +# So .. the trivial example would be: +# +# /** +# * my_function +# */ +# +# If the Description: header tag is omitted, then there must be a blank line +# after the last parameter specification. +# e.g. +# /** +# * my_function - does my stuff +# * @my_arg: its mine damnit +# * +# * Does my stuff explained. +# */ +# +# or, could also use: +# /** +# * my_function - does my stuff +# * @my_arg: its mine damnit +# * Description: Does my stuff explained. +# */ +# etc. +# +# Besides functions you can also write documentation for structs, unions, +# enums and typedefs. Instead of the function name you must write the name +# of the declaration; the struct/union/enum/typedef must always precede +# the name. Nesting of declarations is not supported. +# Use the argument mechanism to document members or constants. +# e.g. +# /** +# * struct my_struct - short description +# * @a: first member +# * @b: second member +# * +# * Longer description +# */ +# struct my_struct { +# int a; +# int b; +# /* private: */ +# int c; +# }; +# +# All descriptions can be multiline, except the short function description. +# +# For really longs structs, you can also describe arguments inside the +# body of the struct. +# eg. +# /** +# * struct my_struct - short description +# * @a: first member +# * @b: second member +# * +# * Longer description +# */ +# struct my_struct { +# int a; +# int b; +# /** +# * @c: This is longer description of C +# * +# * You can use paragraphs to describe arguments +# * using this method. +# */ +# int c; +# }; +# +# This should be use only for struct/enum members. +# +# You can also add additional sections. When documenting kernel functions you +# should document the "Context:" of the function, e.g. whether the functions +# can be called form interrupts. Unlike other sections you can end it with an +# empty line. +# A non-void function should have a "Return:" section describing the return +# value(s). +# Example-sections should contain the string EXAMPLE so that they are marked +# appropriately in DocBook. +# +# Example: +# /** +# * user_function - function that can only be called in user context +# * @a: some argument +# * Context: !in_interrupt() +# * +# * Some description +# * Example: +# * user_function(22); +# */ +# ... +# +# +# All descriptive text is further processed, scanning for the following special +# patterns, which are highlighted appropriately. +# +# 'funcname()' - function +# '$ENVVAR' - environmental variable +# '&struct_name' - name of a structure (up to two words including 'struct') +# '@parameter' - name of a parameter +# '%CONST' - name of a constant. + +## init lots of data + +my $errors = 0; +my $warnings = 0; +my $anon_struct_union = 0; + +# match expressions used to find embedded type information +my $type_constant = '\%([-_\w]+)'; +my $type_func = '(\w+)\(\)'; +my $type_param = '\@(\w+)'; +my $type_struct = '\&((struct\s*)*[_\w]+)'; +my $type_struct_xml = '\\&((struct\s*)*[_\w]+)'; +my $type_env = '(\$\w+)'; + +# Output conversion substitutions. +# One for each output format + +# these work fairly well +my %highlights_html = ( $type_constant, "<i>\$1</i>", + $type_func, "<b>\$1</b>", + $type_struct_xml, "<i>\$1</i>", + $type_env, "<b><i>\$1</i></b>", + $type_param, "<tt><b>\$1</b></tt>" ); +my $local_lt = "\\\\\\\\lt:"; +my $local_gt = "\\\\\\\\gt:"; +my $blankline_html = $local_lt . "p" . $local_gt; # was "<p>" + +# html version 5 +my %highlights_html5 = ( $type_constant, "<span class=\"const\">\$1</span>", + $type_func, "<span class=\"func\">\$1</span>", + $type_struct_xml, "<span class=\"struct\">\$1</span>", + $type_env, "<span class=\"env\">\$1</span>", + $type_param, "<span class=\"param\">\$1</span>" ); +my $blankline_html5 = $local_lt . "br /" . $local_gt; + +# XML, docbook format +my %highlights_xml = ( "([^=])\\\"([^\\\"<]+)\\\"", "\$1<quote>\$2</quote>", + $type_constant, "<constant>\$1</constant>", + $type_func, "<function>\$1</function>", + $type_struct_xml, "<structname>\$1</structname>", + $type_env, "<envar>\$1</envar>", + $type_param, "<parameter>\$1</parameter>" ); +my $blankline_xml = $local_lt . "/para" . $local_gt . $local_lt . "para" . $local_gt . "\n"; + +# gnome, docbook format +my %highlights_gnome = ( $type_constant, "<replaceable class=\"option\">\$1</replaceable>", + $type_func, "<function>\$1</function>", + $type_struct, "<structname>\$1</structname>", + $type_env, "<envar>\$1</envar>", + $type_param, "<parameter>\$1</parameter>" ); +my $blankline_gnome = "</para><para>\n"; + +# these are pretty rough +my %highlights_man = ( $type_constant, "\$1", + $type_func, "\\\\fB\$1\\\\fP", + $type_struct, "\\\\fI\$1\\\\fP", + $type_param, "\\\\fI\$1\\\\fP" ); +my $blankline_man = ""; + +# text-mode +my %highlights_text = ( $type_constant, "\$1", + $type_func, "\$1", + $type_struct, "\$1", + $type_param, "\$1" ); +my $blankline_text = ""; + +# list mode +my %highlights_list = ( $type_constant, "\$1", + $type_func, "\$1", + $type_struct, "\$1", + $type_param, "\$1" ); +my $blankline_list = ""; + +# read arguments +if ($#ARGV == -1) { + usage(); +} + +my $kernelversion; +my $dohighlight = ""; + +my $verbose = 0; +my $output_mode = "man"; +my $output_preformatted = 0; +my $no_doc_sections = 0; +my %highlights = %highlights_man; +my $blankline = $blankline_man; +my $modulename = "Kernel API"; +my $function_only = 0; +my $show_not_found = 0; + +my @build_time; +if (defined($ENV{'KBUILD_BUILD_TIMESTAMP'}) && + (my $seconds = `date -d"${ENV{'KBUILD_BUILD_TIMESTAMP'}}" +%s`) ne '') { + @build_time = gmtime($seconds); +} else { + @build_time = localtime; +} + +my $man_date = ('January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', + 'November', 'December')[$build_time[4]] . + " " . ($build_time[5]+1900); + +# Essentially these are globals. +# They probably want to be tidied up, made more localised or something. +# CAVEAT EMPTOR! Some of the others I localised may not want to be, which +# could cause "use of undefined value" or other bugs. +my ($function, %function_table, %parametertypes, $declaration_purpose); +my ($type, $declaration_name, $return_type); +my ($newsection, $newcontents, $prototype, $brcount, %source_map); + +if (defined($ENV{'KBUILD_VERBOSE'})) { + $verbose = "$ENV{'KBUILD_VERBOSE'}"; +} + +# Generated docbook code is inserted in a template at a point where +# docbook v3.1 requires a non-zero sequence of RefEntry's; see: +# http://www.oasis-open.org/docbook/documentation/reference/html/refentry.html +# We keep track of number of generated entries and generate a dummy +# if needs be to ensure the expanded template can be postprocessed +# into html. +my $section_counter = 0; + +my $lineprefix=""; + +# states +# 0 - normal code +# 1 - looking for function name +# 2 - scanning field start. +# 3 - scanning prototype. +# 4 - documentation block +# 5 - gathering documentation outside main block +my $state; +my $in_doc_sect; + +# Split Doc State +# 0 - Invalid (Before start or after finish) +# 1 - Is started (the /** was found inside a struct) +# 2 - The @parameter header was found, start accepting multi paragraph text. +# 3 - Finished (the */ was found) +# 4 - Error - Comment without header was found. Spit a warning as it's not +# proper kernel-doc and ignore the rest. +my $split_doc_state; + +#declaration types: can be +# 'function', 'struct', 'union', 'enum', 'typedef' +my $decl_type; + +my $doc_special = "\@\%\$\&"; + +my $doc_start = '^/\*\*\s*$'; # Allow whitespace at end of comment start. +my $doc_end = '\*/'; +my $doc_com = '\s*\*\s*'; +my $doc_com_body = '\s*\* ?'; +my $doc_decl = $doc_com . '(\w+)'; +my $doc_sect = $doc_com . '([' . $doc_special . ']?[\w\s]+):(.*)'; +my $doc_content = $doc_com_body . '(.*)'; +my $doc_block = $doc_com . 'DOC:\s*(.*)?'; +my $doc_split_start = '^\s*/\*\*\s*$'; +my $doc_split_sect = '\s*\*\s*(@[\w\s]+):(.*)'; +my $doc_split_end = '^\s*\*/\s*$'; + +my %constants; +my %parameterdescs; +my @parameterlist; +my %sections; +my @sectionlist; +my $sectcheck; +my $struct_actual; + +my $contents = ""; +my $section_default = "Description"; # default section +my $section_intro = "Introduction"; +my $section = $section_default; +my $section_context = "Context"; +my $section_return = "Return"; + +my $undescribed = "-- undescribed --"; + +reset_state(); + +while ($ARGV[0] =~ m/^-(.*)/) { + my $cmd = shift @ARGV; + if ($cmd eq "-html") { + $output_mode = "html"; + %highlights = %highlights_html; + $blankline = $blankline_html; + } elsif ($cmd eq "-html5") { + $output_mode = "html5"; + %highlights = %highlights_html5; + $blankline = $blankline_html5; + } elsif ($cmd eq "-man") { + $output_mode = "man"; + %highlights = %highlights_man; + $blankline = $blankline_man; + } elsif ($cmd eq "-text") { + $output_mode = "text"; + %highlights = %highlights_text; + $blankline = $blankline_text; + } elsif ($cmd eq "-docbook") { + $output_mode = "xml"; + %highlights = %highlights_xml; + $blankline = $blankline_xml; + } elsif ($cmd eq "-list") { + $output_mode = "list"; + %highlights = %highlights_list; + $blankline = $blankline_list; + } elsif ($cmd eq "-gnome") { + $output_mode = "gnome"; + %highlights = %highlights_gnome; + $blankline = $blankline_gnome; + } elsif ($cmd eq "-module") { # not needed for XML, inherits from calling document + $modulename = shift @ARGV; + } elsif ($cmd eq "-function") { # to only output specific functions + $function_only = 1; + $function = shift @ARGV; + $function_table{$function} = 1; + } elsif ($cmd eq "-nofunction") { # to only output specific functions + $function_only = 2; + $function = shift @ARGV; + $function_table{$function} = 1; + } elsif ($cmd eq "-v") { + $verbose = 1; + } elsif (($cmd eq "-h") || ($cmd eq "--help")) { + usage(); + } elsif ($cmd eq '-no-doc-sections') { + $no_doc_sections = 1; + } elsif ($cmd eq '-show-not-found') { + $show_not_found = 1; + } +} + +# continue execution near EOF; + +sub usage { + print "Usage: $0 [ -docbook | -html | -html5 | -text | -man | -list ]\n"; + print " [ -no-doc-sections ]\n"; + print " [ -function funcname [ -function funcname ...] ]\n"; + print " [ -nofunction funcname [ -nofunction funcname ...] ]\n"; + print " [ -v ]\n"; + print " c source file(s) > outputfile\n"; + print " -v : verbose output, more warnings & other info listed\n"; + exit 1; +} + +# get kernel version from env +sub get_kernel_version() { + my $version = 'unknown kernel version'; + + if (defined($ENV{'KERNELVERSION'})) { + $version = $ENV{'KERNELVERSION'}; + } + return $version; +} + +## +# dumps section contents to arrays/hashes intended for that purpose. +# +sub dump_section { + my $file = shift; + my $name = shift; + my $contents = join "\n", @_; + + if ($name =~ m/$type_constant/) { + $name = $1; +# print STDERR "constant section '$1' = '$contents'\n"; + $constants{$name} = $contents; + } elsif ($name =~ m/$type_param/) { +# print STDERR "parameter def '$1' = '$contents'\n"; + $name = $1; + $parameterdescs{$name} = $contents; + $sectcheck = $sectcheck . $name . " "; + } elsif ($name eq "@\.\.\.") { +# print STDERR "parameter def '...' = '$contents'\n"; + $name = "..."; + $parameterdescs{$name} = $contents; + $sectcheck = $sectcheck . $name . " "; + } else { +# print STDERR "other section '$name' = '$contents'\n"; + if (defined($sections{$name}) && ($sections{$name} ne "")) { + print STDERR "${file}:$.: error: duplicate section name '$name'\n"; + ++$errors; + } + $sections{$name} = $contents; + push @sectionlist, $name; + } +} + +## +# dump DOC: section after checking that it should go out +# +sub dump_doc_section { + my $file = shift; + my $name = shift; + my $contents = join "\n", @_; + + if ($no_doc_sections) { + return; + } + + if (($function_only == 0) || + ( $function_only == 1 && defined($function_table{$name})) || + ( $function_only == 2 && !defined($function_table{$name}))) + { + dump_section($file, $name, $contents); + output_blockhead({'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'module' => $modulename, + 'content-only' => ($function_only != 0), }); + } +} + +## +# output function +# +# parameterdescs, a hash. +# function => "function name" +# parameterlist => @list of parameters +# parameterdescs => %parameter descriptions +# sectionlist => @list of sections +# sections => %section descriptions +# + +sub output_highlight { + my $contents = join "\n",@_; + my $line; + +# DEBUG +# if (!defined $contents) { +# use Carp; +# confess "output_highlight got called with no args?\n"; +# } + + if ($output_mode eq "html" || $output_mode eq "html5" || + $output_mode eq "xml") { + $contents = local_unescape($contents); + # convert data read & converted thru xml_escape() into &xyz; format: + $contents =~ s/\\\\\\/\&/g; + } +# print STDERR "contents b4:$contents\n"; + eval $dohighlight; + die $@ if $@; +# print STDERR "contents af:$contents\n"; + +# strip whitespaces when generating html5 + if ($output_mode eq "html5") { + $contents =~ s/^\s+//; + $contents =~ s/\s+$//; + } + foreach $line (split "\n", $contents) { + if (! $output_preformatted) { + $line =~ s/^\s*//; + } + if ($line eq ""){ + if (! $output_preformatted) { + print $lineprefix, local_unescape($blankline); + } + } else { + $line =~ s/\\\\\\/\&/g; + if ($output_mode eq "man" && substr($line, 0, 1) eq ".") { + print "\\&$line"; + } else { + print $lineprefix, $line; + } + } + print "\n"; + } +} + +# output sections in html +sub output_section_html(%) { + my %args = %{$_[0]}; + my $section; + + foreach $section (@{$args{'sectionlist'}}) { + print "<h3>$section</h3>\n"; + print "<blockquote>\n"; + output_highlight($args{'sections'}{$section}); + print "</blockquote>\n"; + } +} + +# output enum in html +sub output_enum_html(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + print "<h2>enum " . $args{'enum'} . "</h2>\n"; + + print "<b>enum " . $args{'enum'} . "</b> {<br>\n"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print " <b>" . $parameter . "</b>"; + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ",\n"; + } + print "<br>"; + } + print "};<br>\n"; + + print "<h3>Constants</h3>\n"; + print "<dl>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print "<dt><b>" . $parameter . "</b>\n"; + print "<dd>"; + output_highlight($args{'parameterdescs'}{$parameter}); + } + print "</dl>\n"; + output_section_html(@_); + print "<hr>\n"; +} + +# output typedef in html +sub output_typedef_html(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + print "<h2>typedef " . $args{'typedef'} . "</h2>\n"; + + print "<b>typedef " . $args{'typedef'} . "</b>\n"; + output_section_html(@_); + print "<hr>\n"; +} + +# output struct in html +sub output_struct_html(%) { + my %args = %{$_[0]}; + my ($parameter); + + print "<h2>" . $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "</h2>\n"; + print "<b>" . $args{'type'} . " " . $args{'struct'} . "</b> {<br>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + print "$parameter<br>\n"; + next; + } + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print " <i>$1</i><b>$parameter</b>) <i>($2)</i>;<br>\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print " <i>$1</i> <b>$parameter</b>$2;<br>\n"; + } else { + print " <i>$type</i> <b>$parameter</b>;<br>\n"; + } + } + print "};<br>\n"; + + print "<h3>Members</h3>\n"; + print "<dl>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "<dt><b>" . $parameter . "</b>\n"; + print "<dd>"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + print "</dl>\n"; + output_section_html(@_); + print "<hr>\n"; +} + +# output function in html +sub output_function_html(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print "<h2>" . $args{'function'} . " - " . $args{'purpose'} . "</h2>\n"; + print "<i>" . $args{'functiontype'} . "</i>\n"; + print "<b>" . $args{'function'} . "</b>\n"; + print "("; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "<i>$1</i><b>$parameter</b>) <i>($2)</i>"; + } else { + print "<i>" . $type . "</i> <b>" . $parameter . "</b>"; + } + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ",\n"; + } + } + print ")\n"; + + print "<h3>Arguments</h3>\n"; + print "<dl>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "<dt><b>" . $parameter . "</b>\n"; + print "<dd>"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + print "</dl>\n"; + output_section_html(@_); + print "<hr>\n"; +} + +# output DOC: block header in html +sub output_blockhead_html(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + foreach $section (@{$args{'sectionlist'}}) { + print "<h3>$section</h3>\n"; + print "<ul>\n"; + output_highlight($args{'sections'}{$section}); + print "</ul>\n"; + } + print "<hr>\n"; +} + +# output sections in html5 +sub output_section_html5(%) { + my %args = %{$_[0]}; + my $section; + + foreach $section (@{$args{'sectionlist'}}) { + print "<section>\n"; + print "<h1>$section</h1>\n"; + print "<p>\n"; + output_highlight($args{'sections'}{$section}); + print "</p>\n"; + print "</section>\n"; + } +} + +# output enum in html5 +sub output_enum_html5(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + my $html5id; + + $html5id = $args{'enum'}; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "<article class=\"enum\" id=\"enum:". $html5id . "\">"; + print "<h1>enum " . $args{'enum'} . "</h1>\n"; + print "<ol class=\"code\">\n"; + print "<li>"; + print "<span class=\"keyword\">enum</span> "; + print "<span class=\"identifier\">" . $args{'enum'} . "</span> {"; + print "</li>\n"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print "<li class=\"indent\">"; + print "<span class=\"param\">" . $parameter . "</span>"; + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ","; + } + print "</li>\n"; + } + print "<li>};</li>\n"; + print "</ol>\n"; + + print "<section>\n"; + print "<h1>Constants</h1>\n"; + print "<dl>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print "<dt>" . $parameter . "</dt>\n"; + print "<dd>"; + output_highlight($args{'parameterdescs'}{$parameter}); + print "</dd>\n"; + } + print "</dl>\n"; + print "</section>\n"; + output_section_html5(@_); + print "</article>\n"; +} + +# output typedef in html5 +sub output_typedef_html5(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + my $html5id; + + $html5id = $args{'typedef'}; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "<article class=\"typedef\" id=\"typedef:" . $html5id . "\">\n"; + print "<h1>typedef " . $args{'typedef'} . "</h1>\n"; + + print "<ol class=\"code\">\n"; + print "<li>"; + print "<span class=\"keyword\">typedef</span> "; + print "<span class=\"identifier\">" . $args{'typedef'} . "</span>"; + print "</li>\n"; + print "</ol>\n"; + output_section_html5(@_); + print "</article>\n"; +} + +# output struct in html5 +sub output_struct_html5(%) { + my %args = %{$_[0]}; + my ($parameter); + my $html5id; + + $html5id = $args{'struct'}; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "<article class=\"struct\" id=\"struct:" . $html5id . "\">\n"; + print "<hgroup>\n"; + print "<h1>" . $args{'type'} . " " . $args{'struct'} . "</h1>"; + print "<h2>". $args{'purpose'} . "</h2>\n"; + print "</hgroup>\n"; + print "<ol class=\"code\">\n"; + print "<li>"; + print "<span class=\"type\">" . $args{'type'} . "</span> "; + print "<span class=\"identifier\">" . $args{'struct'} . "</span> {"; + print "</li>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print "<li class=\"indent\">"; + if ($parameter =~ /^#/) { + print "<span class=\"param\">" . $parameter ."</span>\n"; + print "</li>\n"; + next; + } + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "<span class=\"type\">$1</span> "; + print "<span class=\"param\">$parameter</span>"; + print "<span class=\"type\">)</span> "; + print "(<span class=\"args\">$2</span>);"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print "<span class=\"type\">$1</span> "; + print "<span class=\"param\">$parameter</span>"; + print "<span class=\"bits\">$2</span>;"; + } else { + print "<span class=\"type\">$type</span> "; + print "<span class=\"param\">$parameter</span>;"; + } + print "</li>\n"; + } + print "<li>};</li>\n"; + print "</ol>\n"; + + print "<section>\n"; + print "<h1>Members</h1>\n"; + print "<dl>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "<dt>" . $parameter . "</dt>\n"; + print "<dd>"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print "</dd>\n"; + } + print "</dl>\n"; + print "</section>\n"; + output_section_html5(@_); + print "</article>\n"; +} + +# output function in html5 +sub output_function_html5(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $html5id; + + $html5id = $args{'function'}; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "<article class=\"function\" id=\"func:". $html5id . "\">\n"; + print "<hgroup>\n"; + print "<h1>" . $args{'function'} . "</h1>"; + print "<h2>" . $args{'purpose'} . "</h2>\n"; + print "</hgroup>\n"; + print "<ol class=\"code\">\n"; + print "<li>"; + print "<span class=\"type\">" . $args{'functiontype'} . "</span> "; + print "<span class=\"identifier\">" . $args{'function'} . "</span> ("; + print "</li>"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print "<li class=\"indent\">"; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "<span class=\"type\">$1</span> "; + print "<span class=\"param\">$parameter</span>"; + print "<span class=\"type\">)</span> "; + print "(<span class=\"args\">$2</span>)"; + } else { + print "<span class=\"type\">$type</span> "; + print "<span class=\"param\">$parameter</span>"; + } + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ","; + } + print "</li>\n"; + } + print "<li>)</li>\n"; + print "</ol>\n"; + + print "<section>\n"; + print "<h1>Arguments</h1>\n"; + print "<p>\n"; + print "<dl>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "<dt>" . $parameter . "</dt>\n"; + print "<dd>"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print "</dd>\n"; + } + print "</dl>\n"; + print "</section>\n"; + output_section_html5(@_); + print "</article>\n"; +} + +# output DOC: block header in html5 +sub output_blockhead_html5(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $html5id; + + foreach $section (@{$args{'sectionlist'}}) { + $html5id = $section; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "<article class=\"doc\" id=\"doc:". $html5id . "\">\n"; + print "<h1>$section</h1>\n"; + print "<p>\n"; + output_highlight($args{'sections'}{$section}); + print "</p>\n"; + } + print "</article>\n"; +} + +sub output_section_xml(%) { + my %args = %{$_[0]}; + my $section; + # print out each section + $lineprefix=" "; + foreach $section (@{$args{'sectionlist'}}) { + print "<refsect1>\n"; + print "<title>$section</title>\n"; + if ($section =~ m/EXAMPLE/i) { + print "<informalexample><programlisting>\n"; + $output_preformatted = 1; + } else { + print "<para>\n"; + } + output_highlight($args{'sections'}{$section}); + $output_preformatted = 0; + if ($section =~ m/EXAMPLE/i) { + print "</programlisting></informalexample>\n"; + } else { + print "</para>\n"; + } + print "</refsect1>\n"; + } +} + +# output function in XML DocBook +sub output_function_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $id; + + $id = "API-" . $args{'function'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "<refentry id=\"$id\">\n"; + print "<refentryinfo>\n"; + print " <title>LINUX</title>\n"; + print " <productname>Kernel Hackers Manual</productname>\n"; + print " <date>$man_date</date>\n"; + print "</refentryinfo>\n"; + print "<refmeta>\n"; + print " <refentrytitle><phrase>" . $args{'function'} . "</phrase></refentrytitle>\n"; + print " <manvolnum>9</manvolnum>\n"; + print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n"; + print "</refmeta>\n"; + print "<refnamediv>\n"; + print " <refname>" . $args{'function'} . "</refname>\n"; + print " <refpurpose>\n"; + print " "; + output_highlight ($args{'purpose'}); + print " </refpurpose>\n"; + print "</refnamediv>\n"; + + print "<refsynopsisdiv>\n"; + print " <title>Synopsis</title>\n"; + print " <funcsynopsis><funcprototype>\n"; + print " <funcdef>" . $args{'functiontype'} . " "; + print "<function>" . $args{'function'} . " </function></funcdef>\n"; + + $count = 0; + if ($#{$args{'parameterlist'}} >= 0) { + foreach $parameter (@{$args{'parameterlist'}}) { + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print " <paramdef>$1<parameter>$parameter</parameter>)\n"; + print " <funcparams>$2</funcparams></paramdef>\n"; + } else { + print " <paramdef>" . $type; + print " <parameter>$parameter</parameter></paramdef>\n"; + } + } + } else { + print " <void/>\n"; + } + print " </funcprototype></funcsynopsis>\n"; + print "</refsynopsisdiv>\n"; + + # print parameters + print "<refsect1>\n <title>Arguments</title>\n"; + if ($#{$args{'parameterlist'}} >= 0) { + print " <variablelist>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print " <varlistentry>\n <term><parameter>$parameter</parameter></term>\n"; + print " <listitem>\n <para>\n"; + $lineprefix=" "; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print " </para>\n </listitem>\n </varlistentry>\n"; + } + print " </variablelist>\n"; + } else { + print " <para>\n None\n </para>\n"; + } + print "</refsect1>\n"; + + output_section_xml(@_); + print "</refentry>\n\n"; +} + +# output struct in XML DocBook +sub output_struct_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $id; + + $id = "API-struct-" . $args{'struct'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "<refentry id=\"$id\">\n"; + print "<refentryinfo>\n"; + print " <title>LINUX</title>\n"; + print " <productname>Kernel Hackers Manual</productname>\n"; + print " <date>$man_date</date>\n"; + print "</refentryinfo>\n"; + print "<refmeta>\n"; + print " <refentrytitle><phrase>" . $args{'type'} . " " . $args{'struct'} . "</phrase></refentrytitle>\n"; + print " <manvolnum>9</manvolnum>\n"; + print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n"; + print "</refmeta>\n"; + print "<refnamediv>\n"; + print " <refname>" . $args{'type'} . " " . $args{'struct'} . "</refname>\n"; + print " <refpurpose>\n"; + print " "; + output_highlight ($args{'purpose'}); + print " </refpurpose>\n"; + print "</refnamediv>\n"; + + print "<refsynopsisdiv>\n"; + print " <title>Synopsis</title>\n"; + print " <programlisting>\n"; + print $args{'type'} . " " . $args{'struct'} . " {\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + my $prm = $parameter; + # convert data read & converted thru xml_escape() into &xyz; format: + # This allows us to have #define macros interspersed in a struct. + $prm =~ s/\\\\\\/\&/g; + print "$prm\n"; + next; + } + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + defined($args{'parameterdescs'}{$parameter_name}) || next; + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print " $1 $parameter) ($2);\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print " $1 $parameter$2;\n"; + } else { + print " " . $type . " " . $parameter . ";\n"; + } + } + print "};"; + print " </programlisting>\n"; + print "</refsynopsisdiv>\n"; + + print " <refsect1>\n"; + print " <title>Members</title>\n"; + + if ($#{$args{'parameterlist'}} >= 0) { + print " <variablelist>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + defined($args{'parameterdescs'}{$parameter_name}) || next; + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print " <varlistentry>"; + print " <term>$parameter</term>\n"; + print " <listitem><para>\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print " </para></listitem>\n"; + print " </varlistentry>\n"; + } + print " </variablelist>\n"; + } else { + print " <para>\n None\n </para>\n"; + } + print " </refsect1>\n"; + + output_section_xml(@_); + + print "</refentry>\n\n"; +} + +# output enum in XML DocBook +sub output_enum_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $id; + + $id = "API-enum-" . $args{'enum'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "<refentry id=\"$id\">\n"; + print "<refentryinfo>\n"; + print " <title>LINUX</title>\n"; + print " <productname>Kernel Hackers Manual</productname>\n"; + print " <date>$man_date</date>\n"; + print "</refentryinfo>\n"; + print "<refmeta>\n"; + print " <refentrytitle><phrase>enum " . $args{'enum'} . "</phrase></refentrytitle>\n"; + print " <manvolnum>9</manvolnum>\n"; + print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n"; + print "</refmeta>\n"; + print "<refnamediv>\n"; + print " <refname>enum " . $args{'enum'} . "</refname>\n"; + print " <refpurpose>\n"; + print " "; + output_highlight ($args{'purpose'}); + print " </refpurpose>\n"; + print "</refnamediv>\n"; + + print "<refsynopsisdiv>\n"; + print " <title>Synopsis</title>\n"; + print " <programlisting>\n"; + print "enum " . $args{'enum'} . " {\n"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print " $parameter"; + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ","; + } + print "\n"; + } + print "};"; + print " </programlisting>\n"; + print "</refsynopsisdiv>\n"; + + print "<refsect1>\n"; + print " <title>Constants</title>\n"; + print " <variablelist>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print " <varlistentry>"; + print " <term>$parameter</term>\n"; + print " <listitem><para>\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print " </para></listitem>\n"; + print " </varlistentry>\n"; + } + print " </variablelist>\n"; + print "</refsect1>\n"; + + output_section_xml(@_); + + print "</refentry>\n\n"; +} + +# output typedef in XML DocBook +sub output_typedef_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $id; + + $id = "API-typedef-" . $args{'typedef'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "<refentry id=\"$id\">\n"; + print "<refentryinfo>\n"; + print " <title>LINUX</title>\n"; + print " <productname>Kernel Hackers Manual</productname>\n"; + print " <date>$man_date</date>\n"; + print "</refentryinfo>\n"; + print "<refmeta>\n"; + print " <refentrytitle><phrase>typedef " . $args{'typedef'} . "</phrase></refentrytitle>\n"; + print " <manvolnum>9</manvolnum>\n"; + print "</refmeta>\n"; + print "<refnamediv>\n"; + print " <refname>typedef " . $args{'typedef'} . "</refname>\n"; + print " <refpurpose>\n"; + print " "; + output_highlight ($args{'purpose'}); + print " </refpurpose>\n"; + print "</refnamediv>\n"; + + print "<refsynopsisdiv>\n"; + print " <title>Synopsis</title>\n"; + print " <synopsis>typedef " . $args{'typedef'} . ";</synopsis>\n"; + print "</refsynopsisdiv>\n"; + + output_section_xml(@_); + + print "</refentry>\n\n"; +} + +# output in XML DocBook +sub output_blockhead_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + my $id = $args{'module'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + # print out each section + $lineprefix=" "; + foreach $section (@{$args{'sectionlist'}}) { + if (!$args{'content-only'}) { + print "<refsect1>\n <title>$section</title>\n"; + } + if ($section =~ m/EXAMPLE/i) { + print "<example><para>\n"; + $output_preformatted = 1; + } else { + print "<para>\n"; + } + output_highlight($args{'sections'}{$section}); + $output_preformatted = 0; + if ($section =~ m/EXAMPLE/i) { + print "</para></example>\n"; + } else { + print "</para>"; + } + if (!$args{'content-only'}) { + print "\n</refsect1>\n"; + } + } + + print "\n\n"; +} + +# output in XML DocBook +sub output_function_gnome { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $id; + + $id = $args{'module'} . "-" . $args{'function'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "<sect2>\n"; + print " <title id=\"$id\">" . $args{'function'} . "</title>\n"; + + print " <funcsynopsis>\n"; + print " <funcdef>" . $args{'functiontype'} . " "; + print "<function>" . $args{'function'} . " "; + print "</function></funcdef>\n"; + + $count = 0; + if ($#{$args{'parameterlist'}} >= 0) { + foreach $parameter (@{$args{'parameterlist'}}) { + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print " <paramdef>$1 <parameter>$parameter</parameter>)\n"; + print " <funcparams>$2</funcparams></paramdef>\n"; + } else { + print " <paramdef>" . $type; + print " <parameter>$parameter</parameter></paramdef>\n"; + } + } + } else { + print " <void>\n"; + } + print " </funcsynopsis>\n"; + if ($#{$args{'parameterlist'}} >= 0) { + print " <informaltable pgwide=\"1\" frame=\"none\" role=\"params\">\n"; + print "<tgroup cols=\"2\">\n"; + print "<colspec colwidth=\"2*\">\n"; + print "<colspec colwidth=\"8*\">\n"; + print "<tbody>\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print " <row><entry align=\"right\"><parameter>$parameter</parameter></entry>\n"; + print " <entry>\n"; + $lineprefix=" "; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print " </entry></row>\n"; + } + print " </tbody></tgroup></informaltable>\n"; + } else { + print " <para>\n None\n </para>\n"; + } + + # print out each section + $lineprefix=" "; + foreach $section (@{$args{'sectionlist'}}) { + print "<simplesect>\n <title>$section</title>\n"; + if ($section =~ m/EXAMPLE/i) { + print "<example><programlisting>\n"; + $output_preformatted = 1; + } else { + } + print "<para>\n"; + output_highlight($args{'sections'}{$section}); + $output_preformatted = 0; + print "</para>\n"; + if ($section =~ m/EXAMPLE/i) { + print "</programlisting></example>\n"; + } else { + } + print " </simplesect>\n"; + } + + print "</sect2>\n\n"; +} + +## +# output function in man +sub output_function_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print ".TH \"$args{'function'}\" 9 \"$args{'function'}\" \"$man_date\" \"Kernel Hacker's Manual\" LINUX\n"; + + print ".SH NAME\n"; + print $args{'function'} . " \\- " . $args{'purpose'} . "\n"; + + print ".SH SYNOPSIS\n"; + if ($args{'functiontype'} ne "") { + print ".B \"" . $args{'functiontype'} . "\" " . $args{'function'} . "\n"; + } else { + print ".B \"" . $args{'function'} . "\n"; + } + $count = 0; + my $parenth = "("; + my $post = ","; + foreach my $parameter (@{$args{'parameterlist'}}) { + if ($count == $#{$args{'parameterlist'}}) { + $post = ");"; + } + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print ".BI \"" . $parenth . $1 . "\" " . $parameter . " \") (" . $2 . ")" . $post . "\"\n"; + } else { + $type =~ s/([^\*])$/$1 /; + print ".BI \"" . $parenth . $type . "\" " . $parameter . " \"" . $post . "\"\n"; + } + $count++; + $parenth = ""; + } + + print ".SH ARGUMENTS\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"", uc $section, "\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output enum in man +sub output_enum_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print ".TH \"$args{'module'}\" 9 \"enum $args{'enum'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print "enum " . $args{'enum'} . " \\- " . $args{'purpose'} . "\n"; + + print ".SH SYNOPSIS\n"; + print "enum " . $args{'enum'} . " {\n"; + $count = 0; + foreach my $parameter (@{$args{'parameterlist'}}) { + print ".br\n.BI \" $parameter\"\n"; + if ($count == $#{$args{'parameterlist'}}) { + print "\n};\n"; + last; + } + else { + print ", \n.br\n"; + } + $count++; + } + + print ".SH Constants\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output struct in man +sub output_struct_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + print ".TH \"$args{'module'}\" 9 \"" . $args{'type'} . " " . $args{'struct'} . "\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print $args{'type'} . " " . $args{'struct'} . " \\- " . $args{'purpose'} . "\n"; + + print ".SH SYNOPSIS\n"; + print $args{'type'} . " " . $args{'struct'} . " {\n.br\n"; + + foreach my $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + print ".BI \"$parameter\"\n.br\n"; + next; + } + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print ".BI \" " . $1 . "\" " . $parameter . " \") (" . $2 . ")" . "\"\n;\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print ".BI \" " . $1 . "\ \" " . $parameter . $2 . " \"" . "\"\n;\n"; + } else { + $type =~ s/([^\*])$/$1 /; + print ".BI \" " . $type . "\" " . $parameter . " \"" . "\"\n;\n"; + } + print "\n.br\n"; + } + print "};\n.br\n"; + + print ".SH Members\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output typedef in man +sub output_typedef_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + print ".TH \"$args{'module'}\" 9 \"$args{'typedef'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print "typedef " . $args{'typedef'} . " \\- " . $args{'purpose'} . "\n"; + + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +sub output_blockhead_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print ".TH \"$args{'module'}\" 9 \"$args{'module'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output in text +sub output_function_text(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $start; + + print "Name:\n\n"; + print $args{'function'} . " - " . $args{'purpose'} . "\n"; + + print "\nSynopsis:\n\n"; + if ($args{'functiontype'} ne "") { + $start = $args{'functiontype'} . " " . $args{'function'} . " ("; + } else { + $start = $args{'function'} . " ("; + } + print $start; + + my $count = 0; + foreach my $parameter (@{$args{'parameterlist'}}) { + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print $1 . $parameter . ") (" . $2; + } else { + print $type . " " . $parameter; + } + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ",\n"; + print " " x length($start); + } else { + print ");\n\n"; + } + } + + print "Arguments:\n\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print $parameter . "\n\t" . $args{'parameterdescs'}{$parameter_name} . "\n"; + } + output_section_text(@_); +} + +#output sections in text +sub output_section_text(%) { + my %args = %{$_[0]}; + my $section; + + print "\n"; + foreach $section (@{$args{'sectionlist'}}) { + print "$section:\n\n"; + output_highlight($args{'sections'}{$section}); + } + print "\n\n"; +} + +# output enum in text +sub output_enum_text(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + print "Enum:\n\n"; + + print "enum " . $args{'enum'} . " - " . $args{'purpose'} . "\n\n"; + print "enum " . $args{'enum'} . " {\n"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print "\t$parameter"; + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ","; + } + print "\n"; + } + print "};\n\n"; + + print "Constants:\n\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print "$parameter\n\t"; + print $args{'parameterdescs'}{$parameter} . "\n"; + } + + output_section_text(@_); +} + +# output typedef in text +sub output_typedef_text(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + print "Typedef:\n\n"; + + print "typedef " . $args{'typedef'} . " - " . $args{'purpose'} . "\n"; + output_section_text(@_); +} + +# output struct as text +sub output_struct_text(%) { + my %args = %{$_[0]}; + my ($parameter); + + print $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "\n\n"; + print $args{'type'} . " " . $args{'struct'} . " {\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + print "$parameter\n"; + next; + } + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "\t$1 $parameter) ($2);\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print "\t$1 $parameter$2;\n"; + } else { + print "\t" . $type . " " . $parameter . ";\n"; + } + } + print "};\n\n"; + + print "Members:\n\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "$parameter\n\t"; + print $args{'parameterdescs'}{$parameter_name} . "\n"; + } + print "\n"; + output_section_text(@_); +} + +sub output_blockhead_text(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + foreach $section (@{$args{'sectionlist'}}) { + print " $section:\n"; + print " -> "; + output_highlight($args{'sections'}{$section}); + } +} + +## list mode output functions + +sub output_function_list(%) { + my %args = %{$_[0]}; + + print $args{'function'} . "\n"; +} + +# output enum in list +sub output_enum_list(%) { + my %args = %{$_[0]}; + print $args{'enum'} . "\n"; +} + +# output typedef in list +sub output_typedef_list(%) { + my %args = %{$_[0]}; + print $args{'typedef'} . "\n"; +} + +# output struct as list +sub output_struct_list(%) { + my %args = %{$_[0]}; + + print $args{'struct'} . "\n"; +} + +sub output_blockhead_list(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + foreach $section (@{$args{'sectionlist'}}) { + print "DOC: $section\n"; + } +} + +## +# generic output function for all types (function, struct/union, typedef, enum); +# calls the generated, variable output_ function name based on +# functype and output_mode +sub output_declaration { + no strict 'refs'; + my $name = shift; + my $functype = shift; + my $func = "output_${functype}_$output_mode"; + if (($function_only==0) || + ( $function_only == 1 && defined($function_table{$name})) || + ( $function_only == 2 && !defined($function_table{$name}))) + { + &$func(@_); + $section_counter++; + } +} + +## +# generic output function - calls the right one based on current output mode. +sub output_blockhead { + no strict 'refs'; + my $func = "output_blockhead_" . $output_mode; + &$func(@_); + $section_counter++; +} + +## +# takes a declaration (struct, union, enum, typedef) and +# invokes the right handler. NOT called for functions. +sub dump_declaration($$) { + no strict 'refs'; + my ($prototype, $file) = @_; + my $func = "dump_" . $decl_type; + &$func(@_); +} + +sub dump_union($$) { + dump_struct(@_); +} + +sub dump_struct($$) { + my $x = shift; + my $file = shift; + my $nested; + + if ($x =~ /(struct|union)\s+(\w+)\s*{(.*)}/) { + #my $decl_type = $1; + $declaration_name = $2; + my $members = $3; + + # ignore embedded structs or unions + $members =~ s/({.*})//g; + $nested = $1; + + # ignore members marked private: + $members =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gos; + $members =~ s/\/\*\s*private:.*//gos; + # strip comments: + $members =~ s/\/\*.*?\*\///gos; + $nested =~ s/\/\*.*?\*\///gos; + # strip kmemcheck_bitfield_{begin,end}.*; + $members =~ s/kmemcheck_bitfield_.*?;//gos; + # strip attributes + $members =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; + $members =~ s/__aligned\s*\([^;]*\)//gos; + $members =~ s/\s*CRYPTO_MINALIGN_ATTR//gos; + + create_parameterlist($members, ';', $file); + check_sections($file, $declaration_name, "struct", $sectcheck, $struct_actual, $nested); + + output_declaration($declaration_name, + 'struct', + {'struct' => $declaration_name, + 'module' => $modulename, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose, + 'type' => $decl_type + }); + } + else { + print STDERR "${file}:$.: error: Cannot parse struct or union!\n"; + ++$errors; + } +} + +sub dump_enum($$) { + my $x = shift; + my $file = shift; + + $x =~ s@/\*.*?\*/@@gos; # strip comments. + $x =~ s/^#\s*define\s+.*$//; # strip #define macros inside enums + + if ($x =~ /enum\s+(\w+)\s*{(.*)}/) { + $declaration_name = $1; + my $members = $2; + + foreach my $arg (split ',', $members) { + $arg =~ s/^\s*(\w+).*/$1/; + push @parameterlist, $arg; + if (!$parameterdescs{$arg}) { + $parameterdescs{$arg} = $undescribed; + print STDERR "${file}:$.: warning: Enum value '$arg' ". + "not described in enum '$declaration_name'\n"; + } + + } + + output_declaration($declaration_name, + 'enum', + {'enum' => $declaration_name, + 'module' => $modulename, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); + } + else { + print STDERR "${file}:$.: error: Cannot parse enum!\n"; + ++$errors; + } +} + +sub dump_typedef($$) { + my $x = shift; + my $file = shift; + + $x =~ s@/\*.*?\*/@@gos; # strip comments. + while (($x =~ /\(*.\)\s*;$/) || ($x =~ /\[*.\]\s*;$/)) { + $x =~ s/\(*.\)\s*;$/;/; + $x =~ s/\[*.\]\s*;$/;/; + } + + if ($x =~ /typedef.*\s+(\w+)\s*;/) { + $declaration_name = $1; + + output_declaration($declaration_name, + 'typedef', + {'typedef' => $declaration_name, + 'module' => $modulename, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); + } + else { + print STDERR "${file}:$.: error: Cannot parse typedef!\n"; + ++$errors; + } +} + +sub save_struct_actual($) { + my $actual = shift; + + # strip all spaces from the actual param so that it looks like one string item + $actual =~ s/\s*//g; + $struct_actual = $struct_actual . $actual . " "; +} + +sub create_parameterlist($$$) { + my $args = shift; + my $splitter = shift; + my $file = shift; + my $type; + my $param; + + # temporarily replace commas inside function pointer definition + while ($args =~ /(\([^\),]+),/) { + $args =~ s/(\([^\),]+),/$1#/g; + } + + foreach my $arg (split($splitter, $args)) { + # strip comments + $arg =~ s/\/\*.*\*\///; + # strip leading/trailing spaces + $arg =~ s/^\s*//; + $arg =~ s/\s*$//; + $arg =~ s/\s+/ /; + + if ($arg =~ /^#/) { + # Treat preprocessor directive as a typeless variable just to fill + # corresponding data structures "correctly". Catch it later in + # output_* subs. + push_parameter($arg, "", $file); + } elsif ($arg =~ m/\(.+\)\s*\(/) { + # pointer-to-function + $arg =~ tr/#/,/; + $arg =~ m/[^\(]+\(\*?\s*(\w*)\s*\)/; + $param = $1; + $type = $arg; + $type =~ s/([^\(]+\(\*?)\s*$param/$1/; + save_struct_actual($param); + push_parameter($param, $type, $file); + } elsif ($arg) { + $arg =~ s/\s*:\s*/:/g; + $arg =~ s/\s*\[/\[/g; + + my @args = split('\s*,\s*', $arg); + if ($args[0] =~ m/\*/) { + $args[0] =~ s/(\*+)\s*/ $1/; + } + + my @first_arg; + if ($args[0] =~ /^(.*\s+)(.*?\[.*\].*)$/) { + shift @args; + push(@first_arg, split('\s+', $1)); + push(@first_arg, $2); + } else { + @first_arg = split('\s+', shift @args); + } + + unshift(@args, pop @first_arg); + $type = join " ", @first_arg; + + foreach $param (@args) { + if ($param =~ m/^(\*+)\s*(.*)/) { + save_struct_actual($2); + push_parameter($2, "$type $1", $file); + } + elsif ($param =~ m/(.*?):(\d+)/) { + if ($type ne "") { # skip unnamed bit-fields + save_struct_actual($1); + push_parameter($1, "$type:$2", $file) + } + } + else { + save_struct_actual($param); + push_parameter($param, $type, $file); + } + } + } + } +} + +sub push_parameter($$$) { + my $param = shift; + my $type = shift; + my $file = shift; + + if (($anon_struct_union == 1) && ($type eq "") && + ($param eq "}")) { + return; # ignore the ending }; from anon. struct/union + } + + $anon_struct_union = 0; + my $param_name = $param; + $param_name =~ s/\[.*//; + + if ($type eq "" && $param =~ /\.\.\.$/) + { + if (!defined $parameterdescs{$param} || $parameterdescs{$param} eq "") { + $parameterdescs{$param} = "variable arguments"; + } + } + elsif ($type eq "" && ($param eq "" or $param eq "void")) + { + $param="void"; + $parameterdescs{void} = "no arguments"; + } + elsif ($type eq "" && ($param eq "struct" or $param eq "union")) + # handle unnamed (anonymous) union or struct: + { + $type = $param; + $param = "{unnamed_" . $param . "}"; + $parameterdescs{$param} = "anonymous\n"; + $anon_struct_union = 1; + } + + # warn if parameter has no description + # (but ignore ones starting with # as these are not parameters + # but inline preprocessor statements); + # also ignore unnamed structs/unions; + if (!$anon_struct_union) { + if (!defined $parameterdescs{$param_name} && $param_name !~ /^#/) { + + $parameterdescs{$param_name} = $undescribed; + + if (($type eq 'function') || ($type eq 'enum')) { + print STDERR "${file}:$.: warning: Function parameter ". + "or member '$param' not " . + "described in '$declaration_name'\n"; + } + print STDERR "${file}:$.: warning:" . + " No description found for parameter '$param'\n"; + ++$warnings; + } + } + + $param = xml_escape($param); + + # strip spaces from $param so that it is one continuous string + # on @parameterlist; + # this fixes a problem where check_sections() cannot find + # a parameter like "addr[6 + 2]" because it actually appears + # as "addr[6", "+", "2]" on the parameter list; + # but it's better to maintain the param string unchanged for output, + # so just weaken the string compare in check_sections() to ignore + # "[blah" in a parameter string; + ###$param =~ s/\s*//g; + push @parameterlist, $param; + $parametertypes{$param} = $type; +} + +sub check_sections($$$$$$) { + my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck, $nested) = @_; + my @sects = split ' ', $sectcheck; + my @prms = split ' ', $prmscheck; + my $err; + my ($px, $sx); + my $prm_clean; # strip trailing "[array size]" and/or beginning "*" + + foreach $sx (0 .. $#sects) { + $err = 1; + foreach $px (0 .. $#prms) { + $prm_clean = $prms[$px]; + $prm_clean =~ s/\[.*\]//; + $prm_clean =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; + # ignore array size in a parameter string; + # however, the original param string may contain + # spaces, e.g.: addr[6 + 2] + # and this appears in @prms as "addr[6" since the + # parameter list is split at spaces; + # hence just ignore "[..." for the sections check; + $prm_clean =~ s/\[.*//; + + ##$prm_clean =~ s/^\**//; + if ($prm_clean eq $sects[$sx]) { + $err = 0; + last; + } + } + if ($err) { + if ($decl_type eq "function") { + print STDERR "${file}:$.: warning: " . + "Excess function parameter " . + "'$sects[$sx]' " . + "description in '$decl_name'\n"; + ++$warnings; + } else { + if ($nested !~ m/\Q$sects[$sx]\E/) { + print STDERR "${file}:$.: warning: " . + "Excess struct/union/enum/typedef member " . + "'$sects[$sx]' " . + "description in '$decl_name'\n"; + ++$warnings; + } + } + } + } +} + +## +# Checks the section describing the return value of a function. +sub check_return_section { + my $file = shift; + my $declaration_name = shift; + my $return_type = shift; + + # Ignore an empty return type (It's a macro) + # Ignore functions with a "void" return type. (But don't ignore "void *") + if (($return_type eq "") || ($return_type =~ /void\s*\w*\s*$/)) { + return; + } + + if (!defined($sections{$section_return}) || + $sections{$section_return} eq "") { + print STDERR "${file}:$.: warning: " . + "No description found for return value of " . + "'$declaration_name'\n"; + ++$warnings; + } +} + +## +# takes a function prototype and the name of the current file being +# processed and spits out all the details stored in the global +# arrays/hashes. +sub dump_function($$) { + my $prototype = shift; + my $file = shift; + my $noret = 0; + + $prototype =~ s/^static +//; + $prototype =~ s/^extern +//; + $prototype =~ s/^asmlinkage +//; + $prototype =~ s/^inline +//; + $prototype =~ s/^__inline__ +//; + $prototype =~ s/^__inline +//; + $prototype =~ s/^__always_inline +//; + $prototype =~ s/^noinline +//; + $prototype =~ s/__init +//; + $prototype =~ s/__init_or_module +//; + $prototype =~ s/__meminit +//; + $prototype =~ s/__must_check +//; + $prototype =~ s/__weak +//; + my $define = $prototype =~ s/^#\s*define\s+//; #ak added + $prototype =~ s/__attribute__\s*\(\([a-z,]*\)\)//; + + # Yes, this truly is vile. We are looking for: + # 1. Return type (may be nothing if we're looking at a macro) + # 2. Function name + # 3. Function parameters. + # + # All the while we have to watch out for function pointer parameters + # (which IIRC is what the two sections are for), C types (these + # regexps don't even start to express all the possibilities), and + # so on. + # + # If you mess with these regexps, it's a good idea to check that + # the following functions' documentation still comes out right: + # - parport_register_device (function pointer parameters) + # - atomic_set (macro) + # - pci_match_device, __copy_to_user (long return type) + + if ($define && $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s+/) { + # This is an object-like macro, it has no return type and no parameter + # list. + # Function-like macros are not allowed to have spaces between + # declaration_name and opening parenthesis (notice the \s+). + $return_type = $1; + $declaration_name = $2; + $noret = 1; + } elsif ($prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s*\*\s*\w+\s*\*\s*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/) { + $return_type = $1; + $declaration_name = $2; + my $args = $3; + + create_parameterlist($args, ',', $file); + } else { + print STDERR "${file}:$.: warning: cannot understand function prototype: '$prototype'\n"; + return; + } + + my $prms = join " ", @parameterlist; + check_sections($file, $declaration_name, "function", $sectcheck, $prms, ""); + + # This check emits a lot of warnings at the moment, because many + # functions don't have a 'Return' doc section. So until the number + # of warnings goes sufficiently down, the check is only performed in + # verbose mode. + # TODO: always perform the check. + if ($verbose && !$noret) { + check_return_section($file, $declaration_name, $return_type); + } + + output_declaration($declaration_name, + 'function', + {'function' => $declaration_name, + 'module' => $modulename, + 'functiontype' => $return_type, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); +} + +sub reset_state { + $function = ""; + %constants = (); + %parameterdescs = (); + %parametertypes = (); + @parameterlist = (); + %sections = (); + @sectionlist = (); + $sectcheck = ""; + $struct_actual = ""; + $prototype = ""; + + $state = 0; + $split_doc_state = 0; +} + +sub tracepoint_munge($) { + my $file = shift; + my $tracepointname = 0; + my $tracepointargs = 0; + + if ($prototype =~ m/TRACE_EVENT\((.*?),/) { + $tracepointname = $1; + } + if ($prototype =~ m/DEFINE_SINGLE_EVENT\((.*?),/) { + $tracepointname = $1; + } + if ($prototype =~ m/DEFINE_EVENT\((.*?),(.*?),/) { + $tracepointname = $2; + } + $tracepointname =~ s/^\s+//; #strip leading whitespace + if ($prototype =~ m/TP_PROTO\((.*?)\)/) { + $tracepointargs = $1; + } + if (($tracepointname eq 0) || ($tracepointargs eq 0)) { + print STDERR "${file}:$.: warning: Unrecognized tracepoint format: \n". + "$prototype\n"; + } else { + $prototype = "static inline void trace_$tracepointname($tracepointargs)"; + } +} + +sub syscall_munge() { + my $void = 0; + + $prototype =~ s@[\r\n\t]+@ @gos; # strip newlines/CR's/tabs +## if ($prototype =~ m/SYSCALL_DEFINE0\s*\(\s*(a-zA-Z0-9_)*\s*\)/) { + if ($prototype =~ m/SYSCALL_DEFINE0/) { + $void = 1; +## $prototype = "long sys_$1(void)"; + } + + $prototype =~ s/SYSCALL_DEFINE.*\(/long sys_/; # fix return type & func name + if ($prototype =~ m/long (sys_.*?),/) { + $prototype =~ s/,/\(/; + } elsif ($void) { + $prototype =~ s/\)/\(void\)/; + } + + # now delete all of the odd-number commas in $prototype + # so that arg types & arg names don't have a comma between them + my $count = 0; + my $len = length($prototype); + if ($void) { + $len = 0; # skip the for-loop + } + for (my $ix = 0; $ix < $len; $ix++) { + if (substr($prototype, $ix, 1) eq ',') { + $count++; + if ($count % 2 == 1) { + substr($prototype, $ix, 1) = ' '; + } + } + } +} + +sub process_state3_function($$) { + my $x = shift; + my $file = shift; + + $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line + + if ($x =~ m#\s*/\*\s+MACDOC\s*#io || ($x =~ /^#/ && $x !~ /^#\s*define/)) { + # do nothing + } + elsif ($x =~ /([^\{]*)/) { + $prototype .= $1; + } + + if (($x =~ /\{/) || ($x =~ /\#\s*define/) || ($x =~ /;/)) { + $prototype =~ s@/\*.*?\*/@@gos; # strip comments. + $prototype =~ s@[\r\n]+@ @gos; # strip newlines/cr's. + $prototype =~ s@^\s+@@gos; # strip leading spaces + if ($prototype =~ /SYSCALL_DEFINE/) { + syscall_munge(); + } + if ($prototype =~ /TRACE_EVENT/ || $prototype =~ /DEFINE_EVENT/ || + $prototype =~ /DEFINE_SINGLE_EVENT/) + { + tracepoint_munge($file); + } + dump_function($prototype, $file); + reset_state(); + } +} + +sub process_state3_type($$) { + my $x = shift; + my $file = shift; + + $x =~ s@[\r\n]+@ @gos; # strip newlines/cr's. + $x =~ s@^\s+@@gos; # strip leading spaces + $x =~ s@\s+$@@gos; # strip trailing spaces + $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line + + if ($x =~ /^#/) { + # To distinguish preprocessor directive from regular declaration later. + $x .= ";"; + } + + while (1) { + if ( $x =~ /([^{};]*)([{};])(.*)/ ) { + $prototype .= $1 . $2; + ($2 eq '{') && $brcount++; + ($2 eq '}') && $brcount--; + if (($2 eq ';') && ($brcount == 0)) { + dump_declaration($prototype, $file); + reset_state(); + last; + } + $x = $3; + } else { + $prototype .= $x; + last; + } + } +} + +# xml_escape: replace <, >, and & in the text stream; +# +# however, formatting controls that are generated internally/locally in the +# kernel-doc script are not escaped here; instead, they begin life like +# $blankline_html (4 of '\' followed by a mnemonic + ':'), then these strings +# are converted to their mnemonic-expected output, without the 4 * '\' & ':', +# just before actual output; (this is done by local_unescape()) +sub xml_escape($) { + my $text = shift; + if (($output_mode eq "text") || ($output_mode eq "man")) { + return $text; + } + $text =~ s/\&/\\\\\\amp;/g; + $text =~ s/\</\\\\\\lt;/g; + $text =~ s/\>/\\\\\\gt;/g; + return $text; +} + +# convert local escape strings to html +# local escape strings look like: '\\\\menmonic:' (that's 4 backslashes) +sub local_unescape($) { + my $text = shift; + if (($output_mode eq "text") || ($output_mode eq "man")) { + return $text; + } + $text =~ s/\\\\\\\\lt:/</g; + $text =~ s/\\\\\\\\gt:/>/g; + return $text; +} + +sub process_file($) { + my $file; + my $identifier; + my $func; + my $descr; + my $in_purpose = 0; + my $initial_section_counter = $section_counter; + + if (defined($ENV{'SRCTREE'})) { + $file = "$ENV{'SRCTREE'}" . "/" . "@_"; + } + else { + $file = "@_"; + } + if (defined($source_map{$file})) { + $file = $source_map{$file}; + } + + if (!open(IN,"<$file")) { + print STDERR "Error: Cannot open file $file\n"; + ++$errors; + return; + } + + $. = 1; + + $section_counter = 0; + while (<IN>) { + while (s/\\\s*$//) { + $_ .= <IN>; + } + if ($state == 0) { + if (/$doc_start/o) { + $state = 1; # next line is always the function name + $in_doc_sect = 0; + } + } elsif ($state == 1) { # this line is the function name (always) + if (/$doc_block/o) { + $state = 4; + $contents = ""; + if ( $1 eq "" ) { + $section = $section_intro; + } else { + $section = $1; + } + } + elsif (/$doc_decl/o) { + $identifier = $1; + if (/\s*([\w\s]+?)\s*-/) { + $identifier = $1; + } + + $state = 2; + if (/-(.*)/) { + # strip leading/trailing/multiple spaces + $descr= $1; + $descr =~ s/^\s*//; + $descr =~ s/\s*$//; + $descr =~ s/\s+/ /g; + $declaration_purpose = xml_escape($descr); + $in_purpose = 1; + } else { + $declaration_purpose = ""; + } + + if (($declaration_purpose eq "") && $verbose) { + print STDERR "${file}:$.: warning: missing initial short description on line:\n"; + print STDERR $_; + ++$warnings; + } + + if ($identifier =~ m/^struct/) { + $decl_type = 'struct'; + } elsif ($identifier =~ m/^union/) { + $decl_type = 'union'; + } elsif ($identifier =~ m/^enum/) { + $decl_type = 'enum'; + } elsif ($identifier =~ m/^typedef/) { + $decl_type = 'typedef'; + } else { + $decl_type = 'function'; + } + + if ($verbose) { + print STDERR "${file}:$.: info: Scanning doc for $identifier\n"; + } + } else { + print STDERR "${file}:$.: warning: Cannot understand $_ on line $.", + " - I thought it was a doc line\n"; + ++$warnings; + $state = 0; + } + } elsif ($state == 2) { # look for head: lines, and include content + if (/$doc_sect/o) { + $newsection = $1; + $newcontents = $2; + + if (($contents ne "") && ($contents ne "\n")) { + if (!$in_doc_sect && $verbose) { + print STDERR "${file}:$.: warning: contents before sections\n"; + ++$warnings; + } + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + } + + $in_doc_sect = 1; + $in_purpose = 0; + $contents = $newcontents; + if ($contents ne "") { + while ((substr($contents, 0, 1) eq " ") || + substr($contents, 0, 1) eq "\t") { + $contents = substr($contents, 1); + } + $contents .= "\n"; + } + $section = $newsection; + } elsif (/$doc_end/) { + if (($contents ne "") && ($contents ne "\n")) { + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + $contents = ""; + } + # look for doc_com + <text> + doc_end: + if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') { + print STDERR "${file}:$.: warning: suspicious ending line: $_"; + ++$warnings; + } + + $prototype = ""; + $state = 3; + $brcount = 0; +# print STDERR "end of doc comment, looking for prototype\n"; + } elsif (/$doc_content/) { + # miguel-style comment kludge, look for blank lines after + # @parameter line to signify start of description + if ($1 eq "") { + if ($section =~ m/^@/ || $section eq $section_context) { + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + $contents = ""; + } else { + $contents .= "\n"; + } + $in_purpose = 0; + } elsif ($in_purpose == 1) { + # Continued declaration purpose + chomp($declaration_purpose); + $declaration_purpose .= " " . xml_escape($1); + $declaration_purpose =~ s/\s+/ /g; + } else { + $contents .= $1 . "\n"; + } + } else { + # i dont know - bad line? ignore. + print STDERR "${file}:$.: warning: bad line: $_"; + ++$warnings; + } + } elsif ($state == 5) { # scanning for split parameters + # First line (state 1) needs to be a @parameter + if ($split_doc_state == 1 && /$doc_split_sect/o) { + $section = $1; + $contents = $2; + if ($contents ne "") { + while ((substr($contents, 0, 1) eq " ") || + substr($contents, 0, 1) eq "\t") { + $contents = substr($contents, 1); + } + $contents .= "\n"; + } + $split_doc_state = 2; + # Documentation block end */ + } elsif (/$doc_split_end/) { + if (($contents ne "") && ($contents ne "\n")) { + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + $contents = ""; + } + $state = 3; + $split_doc_state = 0; + # Regular text + } elsif (/$doc_content/) { + if ($split_doc_state == 2) { + $contents .= $1 . "\n"; + } elsif ($split_doc_state == 1) { + $split_doc_state = 4; + print STDERR "Warning(${file}:$.): "; + print STDERR "Incorrect use of kernel-doc format: $_"; + ++$warnings; + } + } + } elsif ($state == 3) { # scanning for function '{' (end of prototype) + if (/$doc_split_start/) { + $state = 5; + $split_doc_state = 1; + } elsif ($decl_type eq 'function') { + process_state3_function($_, $file); + } else { + process_state3_type($_, $file); + } + } elsif ($state == 4) { + # Documentation block + if (/$doc_block/) { + dump_doc_section($file, $section, xml_escape($contents)); + $contents = ""; + $function = ""; + %constants = (); + %parameterdescs = (); + %parametertypes = (); + @parameterlist = (); + %sections = (); + @sectionlist = (); + $prototype = ""; + if ( $1 eq "" ) { + $section = $section_intro; + } else { + $section = $1; + } + } + elsif (/$doc_end/) + { + dump_doc_section($file, $section, xml_escape($contents)); + $contents = ""; + $function = ""; + %constants = (); + %parameterdescs = (); + %parametertypes = (); + @parameterlist = (); + %sections = (); + @sectionlist = (); + $prototype = ""; + $state = 0; + } + elsif (/$doc_content/) + { + if ( $1 eq "" ) + { + $contents .= $blankline; + } + else + { + $contents .= $1 . "\n"; + } + } + } + } + if ($initial_section_counter == $section_counter) { + print STDERR "${file}:1: warning: no structured comments found\n"; + if (($function_only == 1) && ($show_not_found == 1)) { + print STDERR " Was looking for '$_'.\n" for keys %function_table; + } + if ($output_mode eq "xml") { + # The template wants at least one RefEntry here; make one. + print "<refentry>\n"; + print " <refnamediv>\n"; + print " <refname>\n"; + print " ${file}\n"; + print " </refname>\n"; + print " <refpurpose>\n"; + print " Document generation inconsistency\n"; + print " </refpurpose>\n"; + print " </refnamediv>\n"; + print " <refsect1>\n"; + print " <title>\n"; + print " Oops\n"; + print " </title>\n"; + print " <warning>\n"; + print " <para>\n"; + print " The template for this document tried to insert\n"; + print " the structured comment from the file\n"; + print " <filename>${file}</filename> at this point,\n"; + print " but none was found.\n"; + print " This dummy section is inserted to allow\n"; + print " generation to continue.\n"; + print " </para>\n"; + print " </warning>\n"; + print " </refsect1>\n"; + print "</refentry>\n"; + } + } +} + + +$kernelversion = get_kernel_version(); + +# generate a sequence of code that will splice in highlighting information +# using the s// operator. +foreach my $pattern (sort keys %highlights) { +# print STDERR "scanning pattern:$pattern, highlight:($highlights{$pattern})\n"; + $dohighlight .= "\$contents =~ s:$pattern:$highlights{$pattern}:gs;\n"; +} + +# Read the file that maps relative names to absolute names for +# separate source and object directories and for shadow trees. +if (open(SOURCE_MAP, "<.tmp_filelist.txt")) { + my ($relname, $absname); + while(<SOURCE_MAP>) { + chop(); + ($relname, $absname) = (split())[0..1]; + $relname =~ s:^/+::; + $source_map{$relname} = $absname; + } + close(SOURCE_MAP); +} + +foreach (@ARGV) { + chomp; + process_file($_); +} +if ($verbose && $errors) { + print STDERR "$errors errors\n"; +} +if ($verbose && $warnings) { + print STDERR "$warnings warnings\n"; +} + +exit($errors); diff --git a/libdmmp/docs/libdmmp.h.3 b/libdmmp/docs/libdmmp.h.3 new file mode 100644 index 0000000..bb11781 --- /dev/null +++ b/libdmmp/docs/libdmmp.h.3 @@ -0,0 +1,110 @@ +.TH "libdmmp.h" 3 "January 2016" "Device Mapper Multipath API - libdmmp Manual" + +.SH NAME +libdmmp.h \- Device Mapper Multipath API. + +.SH SYNOPSIS +#include <libdmmp/libdmmp.h> + +.SH "DESCRIPTION" + +All the libdmmp ships man pages for all its public functions, +use 'man 3 <function_name>' to check the detail usage. + +.SH "USAGE" + +To use libdmmp in your project, we suggest to use the 'pkg-config' way: + + * Add this line into your configure.ac: + + PKG_CHECK_MODULES([LIBDMMP], [libdmmp]) + + * Add these lines into your Makefile.am: + + foo_LDFLAGS += $(LIBDMMP_LIBS) + foo_CFLAGS += $(LIBDMMP_CFLAGS) + +.SH LOG HANDLING + +The log handler function could be set via 'dmmp_context_log_func_set()'. +The log priority could be set via 'dmmp_context_log_priority_set()'. + +By default, the log priorities is 'DMMP_LOG_PRIORITY_WARNING'. +By default, the log handler is print log to STDERR, and its code is listed +below in case you want to create your own log handler. + + static int _DMMP_LOG_STRERR_ALIGN_WIDTH = 80; + + static void _log_stderr(struct dmmp_context *ctx, + enum dmmp_log_priority priority, + const char *file, int line, + const char *func_name, + const char *format, va_list args) + { + int printed_bytes = 0; + + printed_bytes += fprintf(stderr, "libdmmp %s: ", + dmmp_log_priority_str(priority)); + printed_bytes += vfprintf(stderr, format, args); + + if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) { + fprintf(stderr, "%*s # %s:%s():%d\n", + _DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file, + func_name, line); + } else { + fprintf(stderr, " # %s:%s():%d\n", file, func_name, line); + } + } + + + +.SH "SAMPLE CODE" + + #include <libdmmp/libdmmp.h> + + int main(int argc, char *argv[]) { + struct dmmp_context *ctx = NULL; + struct dmmp_mpath **dmmp_mps = NULL; + struct dmmp_path_group **dmmp_pgs = NULL; + struct dmmp_path **dmmp_ps = NULL; + uint32_t dmmp_mp_count = 0; + uint32_t dmmp_pg_count = 0; + uint32_t dmmp_p_count = 0; + const char *name = NULL; + const char *wwid = NULL; + uint32_t i = 0; + int rc = DMMP_OK; + + ctx = dmmp_context_new(); + dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG); + // By default, log will be printed to STDERR, you could + // change that via dmmp_context_log_func_set() + rc = dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count); + if (rc != DMMP_OK) { + printf("dmmp_mpath_array_get() failed with %d: %s", rc, + dmmp_strerror(rc)); + goto out; + } + for (i = 0; i < dmmp_mp_count; ++i) { + name = dmmp_mpath_name_get(dmmp_mps[i]); + wwid = dmmp_mpath_wwid_get(dmmp_mps[i]); + printf("dmmp_mpath_array_get(): Got mpath: %s %s\n", name, + wwid); + // You could use dmmp_path_group_array_get() to retrieve + // path group information and then invoke dmmp_path_array_get() + // for path information. + } + + out: + dmmp_context_free(ctx); + dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count); + if (rc != DMMP_OK) + exit(1); + exit(0); + } + +.SH "LICENSE" +GPLv2+ + +.SH "BUG" +Please report bug to <dm-devel@xxxxxxxxxx> diff --git a/libdmmp/docs/split-man.pl b/libdmmp/docs/split-man.pl new file mode 100755 index 0000000..452fd8a --- /dev/null +++ b/libdmmp/docs/split-man.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl +# Originally From: +# https://www.kernel.org/doc/Documentation/kernel-doc-nano-HOWTO.txt +# +# Changes: +# * Create manpage section 3 instead of 9. +# * Replace 'Kernel Hackers Manual' to +# 'Device Mapper Multipath API - libdmmp Manual' +# * Remove LINUX from header. +# * Remove DMMP_DLL_EXPORT. +$man_sec_num = 3; +$title = 'Device Mapper Multipath API - libdmmp Manual'; + +if ( $#ARGV < 0 ) { + die "where do I put the results?\n"; +} + +mkdir $ARGV[0], 0777; +$state = 0; +while (<STDIN>) { + s/DMMP_DLL_EXPORT//g; + if (/^\.TH \"[^\"]*\" 9 \"([^\"]*)\"/) { + if ( $state == 1 ) { close OUT } + $state = 1; + $fn = "$ARGV[0]/$1.$man_sec_num"; + print STDERR "Creating $fn\n"; + open OUT, ">$fn" or die "can't open $fn: $!\n"; + + # Change man page code from 9 to $man_sec_num; + s/^\.TH (\"[^\"]*\") 9 \"([^\"]*)\"/\.TH $1 $man_sec_num \"$2\"/; + s/Kernel Hacker's Manual/$title/g; + s/LINUX//g; + + print OUT $_; + } + elsif ( $state != 0 ) { + print OUT $_; + } +} + +close OUT; diff --git a/libdmmp/libdmmp.c b/libdmmp/libdmmp.c new file mode 100644 index 0000000..183fb86 --- /dev/null +++ b/libdmmp/libdmmp.c @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2015 - 2016 Red Hat, Inc. + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Gris Ge <fge@xxxxxxxxxx> + * Todd Gill <tgill@xxxxxxxxxx> + */ + +#include <stdint.h> +#include <string.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <libudev.h> +#include <errno.h> +#include <libdevmapper.h> +#include <stdbool.h> + +/* Loading libmultipath header BEGIN */ +#include <vector.h> +#include <structs.h> +#include <devmapper.h> +#include <memory.h> +#include <config.h> +#include <debug.h> +#include <defaults.h> +#include <discovery.h> +#include <dmparser.h> +#include <switchgroup.h> +#include <checkers.h> +#include <structs_vec.h> +#include <util.h> +/* Loading libmultipath header DONE */ + +#include "libdmmp/libdmmp.h" +#include "libdmmp_private.h" + +/* TODO(Gris Ge): Make it static which require change libmultipath debug.h */ +int logsink = 0; + +struct dmmp_context { + void (*log_func)(struct dmmp_context *ctx, + enum dmmp_log_priority priority, + const char *file, int line, const char *func_name, + const char *format, va_list args); + struct rlimit _rlimit; + bool _has_rlimit; + int log_priority; +}; + +_dmmp_getter_func_gen(dmmp_context_log_priority_get, + struct dmmp_context, ctx, log_priority, int, + DMMP_LOG_PRIORITY_DEFAULT); + +_dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath, + _dmmp_mpath_free); + +static int _DMMP_LOG_STRERR_ALIGN_WIDTH = 80; +/* ^ Used in _log_stderr(). If provided log message is less than 80 bytes, + * fill it with space, then print code file name, function name, line after + * the 80th bytes. + */ + +static int _init(struct dmmp_context *ctx); + +static void _close(struct dmmp_context *ctx); + +static int _get_pathvec(struct dmmp_context *ctx, vector *pathvec); + +static void _log_stderr(struct dmmp_context *ctx, + enum dmmp_log_priority priority, + const char *file, int line, const char *func_name, + const char *format, va_list args); +static void _update_path_status(struct multipath *mpp); + +static void _log_stderr(struct dmmp_context *ctx, + enum dmmp_log_priority priority, + const char *file, int line, const char *func_name, + const char *format, va_list args) +{ + int printed_bytes = 0; + + printed_bytes += fprintf(stderr, "libdmmp %s: ", + dmmp_log_priority_str(priority)); + printed_bytes += vfprintf(stderr, format, args); + + if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) { + fprintf(stderr, "%*s # %s:%s():%d\n", + _DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file, + func_name, line); + } else { + fprintf(stderr, " # %s:%s():%d\n", file, func_name, line); + } +} + +static int _init(struct dmmp_context *ctx) +{ + struct rlimit fd_limit; + int rc = DMMP_OK; + struct udev *udev = NULL; + + udev = udev_new(); + if (udev == NULL) { + rc = DMMP_ERR_NO_MEMORY; + _error(ctx, dmmp_strerror(rc)); + return rc; + } + + if (load_config(DEFAULT_CONFIGFILE, udev) != 0) { + rc = DMMP_ERR_LOAD_CONFIG_FAIL; + /* TODO(Gris Ge): Update load_config() to provide better error + * message. + */ + _error(ctx, "%s: %s", dmmp_strerror(rc), DEFAULT_CONFIGFILE); + goto out; + } + _debug(ctx, "Config %s loaded", DEFAULT_CONFIGFILE); + + if (conf->max_fds) { + /* Save current limit and restore it at _close() */ + if (getrlimit(RLIMIT_NOFILE, &ctx->_rlimit) != 0) { + rc = DMMP_ERR_BUG; + _error(ctx, "Failed to get current " + "RLIMIT_NOFILE limit: %d:%s", errno, + strerror(errno)); + goto out; + } + + fd_limit.rlim_cur = conf->max_fds; + fd_limit.rlim_max = conf->max_fds; + if (setrlimit(RLIMIT_NOFILE, &fd_limit) != 0) + _warn(ctx, "can't set open fds limit to %d : %s", + conf->max_fds, strerror(errno)); + } + +out: + if (udev != NULL) + udev_unref(udev); + + if (rc != DMMP_OK) + _close(ctx); + return rc; +} + +static void _close(struct dmmp_context *ctx) +{ + dm_lib_release(); + dm_lib_exit(); + + free_config(conf); + conf = NULL; + + if (ctx->_has_rlimit == true) { + if (setrlimit(RLIMIT_NOFILE, &ctx->_rlimit) != 0) + _warn(ctx, "_close(): failed to restore rlimit: %d: %s", + errno, strerror(errno)); + } +} + +/* + * Copy from multipath/main.c update_paths(). Just changed the return to void. + */ +static void _update_path_status(struct multipath *mpp) +{ + int i, j; + struct pathgroup * pgp; + struct path * pp; + + if (!mpp->pg) + return; + + vector_foreach_slot (mpp->pg, pgp, i) { + if (!pgp->paths) + continue; + + vector_foreach_slot (pgp->paths, pp, j) { + if (!strlen(pp->dev)) { + if (devt2devname(pp->dev, FILE_NAME_SIZE, + pp->dev_t)) { + /* + * path is not in sysfs anymore + */ + pp->chkrstate = pp->state = PATH_DOWN; + continue; + } + pp->mpp = mpp; + if (pathinfo(pp, conf->hwtable, DI_ALL)) + pp->state = PATH_UNCHECKED; + continue; + } + pp->mpp = mpp; + if (pp->state == PATH_UNCHECKED || + pp->state == PATH_WILD) { + if (pathinfo(pp, conf->hwtable, DI_CHECKER)) + pp->state = PATH_UNCHECKED; + } + + if (pp->priority == PRIO_UNDEF) { + if (pathinfo(pp, conf->hwtable, DI_PRIO)) + pp->priority = PRIO_UNDEF; + } + } + } + return; +} + +static int _get_pathvec(struct dmmp_context *ctx, vector *pathvec) +{ + int rc = DMMP_OK; + int di_flag = DI_SYSFS | DI_CHECKER; + /* ^ like multipath -ll to check path status */ + int rc_discovery = 0; + + *pathvec = vector_alloc(); + /* No need to whether pathvec is NULL, caller already done */ + + _dmmp_alloc_null_check(ctx, *pathvec, rc, out); + + rc_discovery = path_discovery(*pathvec, conf, di_flag); + if (rc == -ENOMEM) { + rc = DMMP_ERR_NO_MEMORY; + _error(ctx, dmmp_strerror(rc)); + goto out; + } else if (rc < 0) { + rc = DMMP_ERR_BUG; + _error(ctx, "BUG: Got unexpected return code %d from " + "path_discovery", rc_discovery); + goto out; + } + +out: + if (rc != DMMP_OK) { + vector_free(*pathvec); + *pathvec = NULL; + } + + return rc; +} + +struct dmmp_context *dmmp_context_new(void) +{ + struct dmmp_context *ctx = NULL; + + ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context)); + + if (ctx == NULL) + return NULL; + + ctx->log_func = _log_stderr; + ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT; + ctx->_has_rlimit = false; + + return ctx; +} + +void dmmp_context_free(struct dmmp_context *ctx) +{ + free(ctx); +} + +void dmmp_context_log_priority_set(struct dmmp_context *ctx, + enum dmmp_log_priority priority) +{ + ctx->log_priority = priority; +} + +void dmmp_context_log_func_set + (struct dmmp_context *ctx, + void (*log_func)(struct dmmp_context *ctx, + enum dmmp_log_priority priority, + const char *file, int line, const char *func_name, + const char *format, va_list args)) +{ + ctx->log_func = log_func; +} + + +void _dmmp_log(struct dmmp_context *ctx, enum dmmp_log_priority priority, + const char *file, int line, const char *func_name, + const char *format, ...) +{ + va_list args; + + va_start(args, format); + ctx->log_func(ctx, priority, file, line, func_name, format, args); + va_end(args); +} + +int dmmp_mpath_array_get(struct dmmp_context *ctx, + struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count) +{ + struct multipath *mpp = NULL; + struct dmmp_mpath *dmmp_mp = NULL; + vector mppvec = NULL; + vector pathvec = NULL; + int rc = DMMP_OK; + int i = 0; + char params[PARAMS_SIZE]; + char status[PARAMS_SIZE]; + int rc_dm = 0; + + if ((ctx == NULL) || (dmmp_mps == NULL) || (dmmp_mp_count == NULL)) { + rc = DMMP_ERR_INVALID_ARGUMENT; + _error(ctx, + "Argument ctx or dmmp_mps or dmmp_mp_count is NULL"); + goto out; + } + + *dmmp_mps = NULL; + *dmmp_mp_count = 0; + + rc = _init(ctx); + + if (rc != 0) { + _debug(ctx, "Initialization failed: %d, %s", rc, + dmmp_strerror(rc)); + goto out; + } + + mppvec = vector_alloc(); + _dmmp_alloc_null_check(ctx, mppvec, rc, out); + + rc_dm = dm_get_maps (mppvec); + if (rc_dm != 0) { + /* TODO(Gris Ge): No idea what was failed */ + _error(ctx, "BUG: unexpected return from dm_get_maps(): %d", + rc_dm); + rc = DMMP_ERR_BUG; + goto out; + } + + *dmmp_mps = (struct dmmp_mpath **) + malloc(sizeof(struct dmmp_mpath *) * VECTOR_SIZE(mppvec)); + _dmmp_alloc_null_check(ctx, mppvec, rc, out); + + *dmmp_mp_count = VECTOR_SIZE(mppvec); + + /* Init dmmp_mps */ + for (i = 0; i < VECTOR_SIZE(mppvec); ++i) { + (*dmmp_mps)[i] = NULL; + } + + rc = _get_pathvec(ctx, &pathvec); + if (rc != DMMP_OK) + goto out; + + vector_foreach_slot(mppvec, mpp, i) { + if (mpp == NULL) { + _error(ctx, "BUG: got NULL mpp"); + rc = DMMP_ERR_BUG; + goto out; + } + if (dm_get_map(mpp->alias, &mpp->size, params)) { + rc = DMMP_ERR_BUG; + _error(ctx, "BUG: dm_get_maps() failed"); + goto out; + } + if (dm_get_status(mpp->alias, status)) { + rc = DMMP_ERR_BUG; + _error(ctx, "BUG: dm_get_status() failed"); + goto out; + } + if (disassemble_map(pathvec, params, mpp)) { + rc = DMMP_ERR_BUG; + _error(ctx, "BUG: disassemble_status() failed"); + goto out; + } + if (update_mpp_paths(mpp, pathvec)) { + rc = DMMP_ERR_BUG; + _error(ctx, "BUG: update_mpp_paths() failed"); + goto out; + } + + _update_path_status(mpp); + mpp->bestpg = select_path_group(mpp); + if (disassemble_status(status, mpp)) { + rc = DMMP_ERR_BUG; + _error(ctx, "BUG: disassemble_status() failed"); + goto out; + } + + dmmp_mp = _dmmp_mpath_new(); + _dmmp_alloc_null_check(ctx, dmmp_mp, rc, out); + (*dmmp_mps)[i] = dmmp_mp; + + rc = _dmmp_mpath_update(ctx, dmmp_mp, mpp, pathvec); + if (rc != DMMP_OK) + goto out; + } + + goto out; + +out: + if (rc != DMMP_OK) { + dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count); + *dmmp_mps = NULL; + *dmmp_mp_count = 0; + } + + if (mppvec != NULL) + free_multipathvec(mppvec, KEEP_PATHS); + if (pathvec != NULL) + free_pathvec(pathvec, FREE_PATHS); + _close(ctx); + return rc; +} + +/* vim: set ts=8 sts=8 sw=8 tw=80 wrap noet : */ +/* vim: set cindent fo=tcqlron cino=\:0,l1,t0,g0,(0 : */ +/* vim>702: set cc=80 : */ diff --git a/libdmmp/libdmmp.pc.in b/libdmmp/libdmmp.pc.in new file mode 100644 index 0000000..3fb8564 --- /dev/null +++ b/libdmmp/libdmmp.pc.in @@ -0,0 +1,10 @@ +libmultipathdir=__LIBMPDIR__ +includedir=__INCLUDEDIR__ +libdir=__LIBDIR__ + +Name: libdmmp +Version: __VERSION__ +Description: Device mapper multipath management library +Requires: +Libs: -L${libmultipathdir} -L${libdir} -ldmmp -lmultipath +Cflags: -I${includedir} diff --git a/libdmmp/libdmmp/libdmmp.h b/libdmmp/libdmmp/libdmmp.h new file mode 100644 index 0000000..5cc7840 --- /dev/null +++ b/libdmmp/libdmmp/libdmmp.h @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2015 - 2016 Red Hat, Inc. + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Gris Ge <fge@xxxxxxxxxx> + * Todd Gill <tgill@xxxxxxxxxx> + */ + + +#ifndef _LIB_DMMP_H_ +#define _LIB_DMMP_H_ + +#include <stdint.h> +#include <stdarg.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define DMMP_DLL_EXPORT __attribute__ ((visibility ("default"))) +#define DMMP_DLL_LOCAL __attribute__ ((visibility ("hidden"))) + +// TODO(Gris Ge): Create better comment/document for each function and constants. +// + +#define DMMP_OK 0 +#define DMMP_ERR_BUG 1 +#define DMMP_ERR_NO_MEMORY 2 +#define DMMP_ERR_INVALID_ARGUMENT 3 +#define DMMP_ERR_LOAD_CONFIG_FAIL 4 + +/* + * Use the syslog severity level as log priority + */ +DMMP_DLL_EXPORT enum dmmp_log_priority { + DMMP_LOG_PRIORITY_ERROR = 3, + DMMP_LOG_PRIORITY_WARNING = 4, + DMMP_LOG_PRIORITY_INFO = 6, + DMMP_LOG_PRIORITY_DEBUG = 7, +}; + +#define DMMP_LOG_PRIORITY_DEFAULT DMMP_LOG_PRIORITY_WARNING + +/** + * dmmp_log_priority_str() - Convert log priority to string. + * + * Convert log priority (enum dmmp_log_priority) to string (const char *). + * + * @priority: + * enum dmmp_log_priority. Log priority. + * Return: + * const char *. Valid string are\:: + * + * * "ERROR", + * + * * "WARN ", + * + * * "INFO ", + * + * * "DEBUG", + */ +DMMP_DLL_EXPORT const char *dmmp_log_priority_str + (enum dmmp_log_priority priority); + +DMMP_DLL_EXPORT struct dmmp_context; + +DMMP_DLL_EXPORT struct dmmp_mpath; + +DMMP_DLL_EXPORT struct dmmp_path_group; + +#define DMMP_PATH_GROUP_ID_UNKNOWN 0 +#define DMMP_PATH_GROUP_PRIORITY_UNKNOWN 0 + +#define DMMP_PATH_GROUP_STATUS_UNKNOWN 0 +#define DMMP_PATH_GROUP_STATUS_ENABLED 1 +#define DMMP_PATH_GROUP_STATUS_DISABLED 2 +#define DMMP_PATH_GROUP_STATUS_ACTIVE 3 + +DMMP_DLL_EXPORT struct dmmp_path; + +#define DMMP_PATH_PG_ID_UNKNOWN 0 + +#define DMMP_PATH_STATUS_UNKNOWN 0 +#define DMMP_PATH_STATUS_UNCHECKED 1 +#define DMMP_PATH_STATUS_DOWN 2 +#define DMMP_PATH_STATUS_UP 3 +#define DMMP_PATH_STATUS_SHAKY 4 +#define DMMP_PATH_STATUS_GHOST 5 +#define DMMP_PATH_STATUS_PENDING 6 +#define DMMP_PATH_STATUS_TIMEOUT 7 +#define DMMP_PATH_STATUS_REMOVED 8 +#define DMMP_PATH_STATUS_DELAYED 9 + +/** + * dmmp_strerror() - Convert error code to string. + * + * Convert error code (int) to string (const char *). + * + * @rc: + * int. Return code fomr libdmmp functions. + * Return: + * const char *. Valid string are\:: + * + * * "OK" + * + * * "Out of memory" + * + * * "Invalid argument" + * + * * "Failed to load multipath config file" + * + */ +DMMP_DLL_EXPORT const char *dmmp_strerror(int rc); + +/** + * dmmp_context_new() - Create struct dmmp_context. + * + * The default logging level is + * DMMP_LOG_PRIORITY_WARNING/DMMP_LOG_PRIORITY_DEFAULT which means + * only warning and error message will be forward to log handler function. + * The default log handler function will print log message to STDERR, + * to change so, please use dmmp_context_log_func_set(). + * + * Return: + * Pointer of 'struct dmmp_context'. Should be freed by + * dmmp_context_free(). + */ +DMMP_DLL_EXPORT struct dmmp_context *dmmp_context_new(void); + +/** + * dmmp_context_free() - Release the memory of struct dmmp_context. + * @ctx: + * Pointer of 'struct dmmp_context'. + */ +DMMP_DLL_EXPORT void dmmp_context_free(struct dmmp_context *ctx); + +/** + * dmmp_context_log_priority_set() - Set log priority. + * + * When library generates log message, only higher or equal priority message + * will be forwarded to log handler function. + * + * @ctx: + * Pointer of 'struct dmmp_context'. + * @priority: + * enum dmmp_log_priority. Valid priorities are(from high to low)\:: + * * DMMP_LOG_PRIORITY_ERROR + * * DMMP_LOG_PRIORITY_WARNING + * * DMMP_LOG_PRIORITY_INFO + * * DMMP_LOG_PRIORITY_DEBUG + */ +DMMP_DLL_EXPORT void dmmp_context_log_priority_set + (struct dmmp_context *ctx, enum dmmp_log_priority priority); + +/** + * dmmp_context_log_priority_get() - Get log priority. + * + * @ctx: + * Pointer of 'struct dmmp_context'. + * Return: + * enum dmmp_log_priority. Valid priorities are(from high to low)\:: + * + * * DMMP_LOG_PRIORITY_ERROR, + * + * * DMMP_LOG_PRIORITY_WARNING, + * + * * DMMP_LOG_PRIORITY_INFO, + * + * * DMMP_LOG_PRIORITY_DEBUG. + */ +DMMP_DLL_EXPORT int dmmp_context_log_priority_get(struct dmmp_context *ctx); + +/** + * dmmp_context_log_func_set() - Set log handler function. + * + * @ctx: + * Pointer of 'struct dmmp_context'. + * @log_func: + * Pointer of log handler function. + */ +DMMP_DLL_EXPORT void dmmp_context_log_func_set + (struct dmmp_context *ctx, + void (*log_func) + (struct dmmp_context *ctx, enum dmmp_log_priority priority, + const char *file, int line, const char *func_name, + const char *format, va_list args)); + +/** + * dmmp_mpath_array_get() - Query all existing multipath devices. + * + * Query all existing multipath devices and store them into a pointer array. + * + * @ctx: + * Pointer of 'struct dmmp_context'. + * @dmmp_mps: + * Output pointer array of 'struct dmmp_mpath'. + * @dmmp_mp_count: + * Output pointer of uint32_t. Hold the size of 'dmmp_mps' pointer array. + * + * Return: + * int. Valid error numbers are\:: + * + * * DMMP_OK + * + * * DMMP_ERR_BUG + * + * * DMMP_ERR_NO_MEMORY + * + * * DMMP_ERR_INVALID_ARGUMENT + * + * * DMMP_ERR_LOAD_CONFIG_FAIL + * + * Error number could be converted to string by dmmp_strerror(). + */ +DMMP_DLL_EXPORT int dmmp_mpath_array_get(struct dmmp_context *ctx, + struct dmmp_mpath ***dmmp_mps, + uint32_t *dmmp_mp_count); + +/** + * dmmp_mpath_array_free() - Free 'struct dmmp_mpath' pointer array. + * + * Free the pointer array generated by dmmp_mpath_array_get(). + * + * @dmmp_mps: + * Pointer of 'struct dmmp_mpath' array. + * @dmmp_mp_count: + * uint32_t, the size of 'dmmp_mps' pointer array. + */ +DMMP_DLL_EXPORT void dmmp_mpath_array_free(struct dmmp_mpath **dmmp_mps, + uint32_t dmmp_mp_count); + +/** + * dmmp_mpath_wwid_get() - Retrieve WWID of certain mpath. + * + * @dmmp_mp: + * Pointer of 'struct dmmp_mpath'. + * Return: + * const char *. No need to free this memory, the resources will get + * freed when dmmp_mpath_array_free(). NULL if dmmp_mp is NULL. + */ +DMMP_DLL_EXPORT const char *dmmp_mpath_wwid_get(struct dmmp_mpath *dmmp_mp); + +/** + * dmmp_mpath_name_get() - Retrieve name(alias) of certain mpath. + * + * @dmmp_mp: + * Pointer of 'struct dmmp_mpath'. + * Return: + * const char *. No need to free this memory, the resources will get + * freed when dmmp_mpath_array_free(). NULL if dmmp_mp is NULL. + */ +DMMP_DLL_EXPORT const char *dmmp_mpath_name_get(struct dmmp_mpath *dmmp_mp); + +/** + * dmmp_path_group_array_get() - Retrieve path group pointer array. + * + * The memory of output pointer array is hold by 'struct dmmp_mpath', no + * need to free this memory, the resources will got freed when + * dmmp_mpath_array_free(). + * + * @dmmp_mp: + * Pointer of 'struct dmmp_mpath'. + * @dmmp_pgs: + * Output pointer of 'struct dmmp_path_group' pointer array. + * @dmmp_pg_count: + * Output pointer of uint32_t. Hold the size of 'dmmp_pgs' pointer array. + */ +DMMP_DLL_EXPORT void dmmp_path_group_array_get + (struct dmmp_mpath *dmmp_mp, struct dmmp_path_group ***dmmp_pgs, + uint32_t *dmmp_pg_count); + +/** + * dmmp_path_group_id_get() - Retrieve path group ID. + * + * The path group ID could be used to switch group via command: + * multipathd -k'switch multipath mpathb group $id' + * + * @dmmp_pg: + * Pointer of 'struct dmmp_path_group'. + * Return: + * uint32_t. 0(DMMP_PATH_GROUP_ID_UNKNOWN) when input argument is NULL. + */ +DMMP_DLL_EXPORT uint32_t dmmp_path_group_id_get + (struct dmmp_path_group *dmmp_pg); + +/** + * dmmp_path_group_priority_get() - Retrieve path group priority. + * + * The enabled path group with highest priority will be next active path group + * if active path group down. + * + * @dmmp_pg: + * Pointer of 'struct dmmp_path_group'. + * Return: + * uint32_t. 0(DMMP_PATH_GROUP_PRIORITY_UNKNOWN) when input argument is + * NULL. + */ +DMMP_DLL_EXPORT uint32_t dmmp_path_group_priority_get + (struct dmmp_path_group *dmmp_pg); + +/** + * dmmp_path_group_status_get() - Retrieve path group status. + * + * The valid path group statuses are\:: + * + * DMMP_PATH_GROUP_STATUS_UNKNOWN + * + * DMMP_PATH_GROUP_STATUS_ENABLED # standby to be active + * + * DMMP_PATH_GROUP_STATUS_DISABLED # disabled due to all path down + * + * DMMP_PATH_GROUP_STATUS_ACTIVE # selected to handle I/O + * + * @dmmp_pg: + * Pointer of 'struct dmmp_path_group'. + * Return: + * uint32_t. 0(DMMP_PATH_GROUP_STATUS_UNKNOWN) when input argument is + * NULL. + */ +DMMP_DLL_EXPORT uint32_t dmmp_path_group_status_get + (struct dmmp_path_group *dmmp_pg); + +/** + * dmmp_path_group_status_str() - Convert path group status to string. + * + * Convert path group status uint32_t to string (const char *). + * + * @pg_status: + * uint32_t. Path group status. + * Return: + * const char *. Valid string are\:: + * + * * "INVALID_ARGUMENT" + * + * * "UNKNOWN" + * + * * "ENABLED" + * + * * "DISABLED" + * + * * "ACTIVE" + */ +DMMP_DLL_EXPORT const char *dmmp_path_group_status_str(int pg_status); + +/** + * dmmp_path_group_selector_get() - Retrieve path group selector. + * + * Path group selector determine which path in active path group will be + * use to next I/O. + * + * @dmmp_pg: + * Pointer of 'struct dmmp_path_group'. + * Return: + * const char *. NULL if input 'dmmp_pg' is NULL. + */ +DMMP_DLL_EXPORT const char *dmmp_path_group_selector_get + (struct dmmp_path_group *dmmp_pg); + +/** + * dmmp_path_array_get() - Retrieve path pointer array. + * + * The memory of output pointer array is hold by 'struct dmmp_mpath', no + * need to free this memory, the resources will got freed when + * dmmp_mpath_array_free(). + * + * @dmmp_pg: + * Pointer of 'struct dmmp_path_group'. + * @dmmp_ps: + * Output pointer of 'struct dmmp_path' pointer array. + * @dmmp_p_count: + * Output pointer of uint32_t. Hold the size of 'dmmp_ps' pointer array. + */ +DMMP_DLL_EXPORT void dmmp_path_array_get(struct dmmp_path_group *dmmp_pg, + struct dmmp_path ***dmmp_ps, + uint32_t *dmmp_p_count); + +/** + * dmmp_path_blk_name_get() - Retrieve block name. + * + * The example of block name is 'sda' or 'nvme0n1'. + * + * @dmmp_p: + * Pointer of 'struct dmmp_path'. + * Return: + * const char *. No need to free this memory, the resources will get + * freed when dmmp_mpath_array_free(). NULL if dmmp_p is NULL. + */ +DMMP_DLL_EXPORT const char *dmmp_path_blk_name_get(struct dmmp_path *dmmp_p); + +/** + * dmmp_path_pg_id_get() - Retrieve the path group id of given path. + * + * @dmmp_p: + * Pointer of 'struct dmmp_path'. + * Return: + * uint32_t. 0(DMMP_PATH_PG_ID_UNKNOWN) if input dmmp_p is NULL. + */ +DMMP_DLL_EXPORT uint32_t dmmp_path_pg_id_get(struct dmmp_path *dmmp_p); + +/** + * dmmp_path_status_get() - Retrieve the path status. + * + * The valid path statuses are\:: + * + * * DMMP_PATH_STATUS_UNKNOWN + * + * * DMMP_PATH_STATUS_UNCHECKED + * + * Only in directio checker when fcntl(F_GETFL) fails to return flags + * or O_DIRECT not include in flags, or O_DIRECT read fails. + * + * * DMMP_PATH_STATUS_DOWN + * + * Path is down and you shouldn't try to send commands to it. + * + * * DMMP_PATH_STATUS_UP + * + * Path is up and I/O can be sent to it. + * + * * DMMP_PATH_STATUS_SHAKY + * + * Only emc_clariion checker when path not available for "normal" + * operations. + * + * * DMMP_PATH_STATUS_GHOST + * + * Only hp_sw and rdac checkers. + * Indicates a "passive/standby" path on active/passive + * HP arrays. These paths will return valid answers to certain SCSI + * commands (tur, read_capacity, inquiry, start_stop), but will fail I/O + * commands. + * The path needs an initialization command to be sent to it in order for + * I/Os to succeed. + * + * * DMMP_PATH_STATUS_PENDING + * Available for all async checkers when a check IO is in flight. + * + * * DMMP_PATH_STATUS_TIMEOUT + * + * Only tur checker when command timed out. + * + * * DMMP_PATH_STATUS_REMOVED + * + * Device has been removed from the system. + * + * * DMMP_PATH_STATUS_DELAYED + * If a path fails after being up for less than delay_watch_checks checks, + * when it comes back up again, it will not be marked as up until it has + * been up for delay_wait_checks checks. During this time, it is marked as + * "delayed". + * + * @dmmp_p: + * Pointer of 'struct dmmp_path'. + * Return: + * uint32_t. 0(DMMP_PATH_PG_ID_UNKNOWN) if input dmmp_p is NULL. + */ +DMMP_DLL_EXPORT uint32_t dmmp_path_status_get(struct dmmp_path *dmmp_p); + +/** + * dmmp_path_status_str() - Convert path status to string. + * + * Convert path status uint32_t to string (const char *). + * + * @path_status: + * uint32_t. Path status. + * Return: + * const char *. Valid string are\:: + * + * * "INVALID_ARGUMENT" + * + * * "UNKNOWN" + * + * * "UNCHECKED" + * + * * "DOWN" + * + * * "UP" + * + * * "SHAKY" + * + * * "GHOST" + * + * * "PENDING" + * + * * "TIMEOUT" + * + * * "REMOVED" + * + * * "DELAYED" + * + */ +DMMP_DLL_EXPORT const char *dmmp_path_status_str(int path_status); + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif + +#endif /* End of _LIB_DMMP_H_ */ + +/* vim: set ts=8 sts=8 sw=8 tw=80 wrap noet : */ +/* vim: set cindent fo=tcqlron cino=\:0,l1,t0,g0,(0 : */ +/* vim>702: set cc=80 : */ diff --git a/libdmmp/libdmmp_misc.c b/libdmmp/libdmmp_misc.c new file mode 100644 index 0000000..7bf551b --- /dev/null +++ b/libdmmp/libdmmp_misc.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 - 2016 Red Hat, Inc. + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Gris Ge <fge@xxxxxxxxxx> + * Todd Gill <tgill@xxxxxxxxxx> + */ + +#include "libdmmp/libdmmp.h" +#include "libdmmp_private.h" + +#define _dmmp_str_func_gen(func_name, var_type, var, str_array) \ + const char *func_name(var_type var) {\ + if (var >= sizeof(str_array)/sizeof(str_array[0])) \ + return "INVALID_ARGUMENT"; \ + return str_array[var]; \ + } + +static const char * const _DMMP_RC_MSG[] = { + "OK", + "Out of memory", + "Invalid argument", + "Failed to load multipath config file", +}; + +static const char * const _DMMP_PRI_STR[] = { + "INVALID_ARGUMENT", + /* since we are enforce via enum, there is no way to hit + * INVALID_ARGUMENT here. + */ + "INVALID_ARGUMENT", + "INVALID_ARGUMENT", + "ERROR", + "WARN ", + "INVALID_ARGUMENT", + "INFO ", + "DEBUG", +}; + +static const char * const _DMMP_PATH_STATUS_STR[] = { + "UNKNOWN", + "UNCHECKED", + "DOWN", + "UP", + "SHAKY", + "GHOST", + "PENDING", + "TIMEOUT", + "REMOVED", + "DELAYED", +}; + +static const char * const _DMMP_PATH_GROUP_STATUS_STR[] = { + "UNKNOWN", + "ENABLED", + "DISABLED", + "ACTIVE", +}; + +_dmmp_str_func_gen(dmmp_strerror, int, rc, _DMMP_RC_MSG); +_dmmp_str_func_gen(dmmp_path_status_str, int, path_status, + _DMMP_PATH_STATUS_STR); +_dmmp_str_func_gen(dmmp_path_group_status_str, int, pg_status, + _DMMP_PATH_GROUP_STATUS_STR); + +const char *dmmp_log_priority_str(enum dmmp_log_priority priority) +{ + return _DMMP_PRI_STR[priority]; +} + +/* vim: set ts=8 sts=8 sw=8 tw=80 wrap noet : */ +/* vim: set cindent fo=tcqlron cino=\:0,l1,t0,g0,(0 : */ +/* vim>702: set cc=80 : */ diff --git a/libdmmp/libdmmp_mp.c b/libdmmp/libdmmp_mp.c new file mode 100644 index 0000000..387d34f --- /dev/null +++ b/libdmmp/libdmmp_mp.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015 - 2016 Red Hat, Inc. + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Gris Ge <fge@xxxxxxxxxx> + * Todd Gill <tgill@xxxxxxxxxx> + */ + +#include <stdlib.h> +#include <inttypes.h> + +/* Loading libmultipath header BEGIN */ +#include <devmapper.h> +#include <dmparser.h> +#include <structs_vec.h> +#include <switchgroup.h> +/* Loading libmultipath header DONE */ + +#include "libdmmp/libdmmp.h" +#include "libdmmp_private.h" + +struct dmmp_mpath { + char wwid[WWID_SIZE]; + char *alias; + uint32_t dmmp_pg_count; + struct dmmp_path_group **dmmp_pgs; +}; + +_dmmp_getter_func_gen(dmmp_mpath_name_get, struct dmmp_mpath, dmmp_mp, + alias, const char *, NULL); +_dmmp_getter_func_gen(dmmp_mpath_wwid_get, struct dmmp_mpath, dmmp_mp, + alias, const char *, NULL); + +struct dmmp_mpath *_dmmp_mpath_new(void) +{ + struct dmmp_mpath *dmmp_mp = NULL; + + dmmp_mp = (struct dmmp_mpath *) malloc(sizeof(struct dmmp_mpath)); + + if (dmmp_mp != NULL) { + dmmp_mp->wwid[0] = '\0'; + dmmp_mp->alias = NULL; + dmmp_mp->dmmp_pg_count = 0; + dmmp_mp->dmmp_pgs = NULL; + } + return dmmp_mp; +} + +int _dmmp_mpath_update(struct dmmp_context *ctx, struct dmmp_mpath *dmmp_mp, + struct multipath *mpp, vector pathvec) +{ + int i = 0; + struct pathgroup *pgp = NULL; + struct dmmp_path_group *dmmp_pg = NULL; + int rc = DMMP_OK; + + if (mpp->wwid != NULL) { + snprintf(dmmp_mp->wwid, WWID_SIZE, "%s", mpp->wwid); + } else { + _error(ctx, "BUG: Got NULL wwid from struct multipath"); + rc = DMMP_ERR_BUG; + goto out; + } + + if (mpp->alias != NULL) { + dmmp_mp->alias = strdup(mpp->alias); + _dmmp_alloc_null_check(ctx, dmmp_mp->alias, rc, out); + } else { + _error(ctx, "BUG: Got NULL alias from struct multipath"); + rc = DMMP_ERR_BUG; + goto out; + } + + if (VECTOR_SIZE(mpp->pg) == 0) { + _info(ctx, "mpath %s has no path group", dmmp_mp->alias); + rc = DMMP_OK; + goto out; + } + + _debug(ctx, "Got mpath %s, wwid %s", dmmp_mp->alias, dmmp_mp->wwid); + + dmmp_mp->dmmp_pgs = (struct dmmp_path_group **) + malloc(sizeof(struct dmmp_path_group *) * VECTOR_SIZE(mpp->pg)); + + _dmmp_alloc_null_check(ctx, dmmp_mp->dmmp_pgs, rc, out); + + dmmp_mp->dmmp_pg_count = VECTOR_SIZE(mpp->pg); + _debug(ctx, "Got %d path group for mpath %s", dmmp_mp->dmmp_pg_count, + dmmp_mp->alias); + + /* Init dmmp_mp->dmmp_pgs */ + for (i = 0; i < VECTOR_SIZE(mpp->pg); ++i) { + dmmp_mp->dmmp_pgs[i] = NULL; + } + + vector_foreach_slot(mpp->pg, pgp, i) { + if (pgp == NULL) { + _error(ctx, "Got NULL struct path_group pointer"); + rc = DMMP_ERR_BUG; + goto out; + } + dmmp_pg = _dmmp_path_group_new(); + _dmmp_alloc_null_check(ctx, dmmp_pg, rc, out); + + dmmp_mp->dmmp_pgs[i] = dmmp_pg; + pgp->selector = mpp->selector; + /* This is marked as HACK in snprint_multipath_topology() of + * libmultipath/print.c + */ + rc = _dmmp_path_group_update(ctx, dmmp_mp, dmmp_pg, pgp); + if (rc != DMMP_OK) + goto out; + } + +out: + if (rc != DMMP_OK) { + if (dmmp_mp->dmmp_pgs != NULL) + _dmmp_path_group_array_free(dmmp_mp->dmmp_pgs, + dmmp_mp->dmmp_pg_count); + dmmp_mp->dmmp_pgs = NULL; + dmmp_mp->dmmp_pg_count = 0; + } + return rc; +} + +void _dmmp_mpath_free(struct dmmp_mpath *dmmp_mp) +{ + if (dmmp_mp == NULL) + return ; + if (dmmp_mp->alias) + free((char *) dmmp_mp->alias); + _dmmp_path_group_array_free(dmmp_mp->dmmp_pgs, dmmp_mp->dmmp_pg_count); + free(dmmp_mp); +} + +void dmmp_path_group_array_get(struct dmmp_mpath *dmmp_mp, + struct dmmp_path_group ***dmmp_pgs, + uint32_t *dmmp_pg_count) +{ + if ((dmmp_pgs == NULL) || (dmmp_pg_count == NULL) ) + return; + + *dmmp_pgs = NULL; + *dmmp_pg_count = 0; + if (dmmp_mp != NULL) { + *dmmp_pgs = dmmp_mp->dmmp_pgs; + *dmmp_pg_count = dmmp_mp->dmmp_pg_count; + } +} + +/* vim: set ts=8 sts=8 sw=8 tw=80 wrap noet : */ +/* vim: set cindent fo=tcqlron cino=\:0,l1,t0,g0,(0 : */ +/* vim>702: set cc=80 : */ diff --git a/libdmmp/libdmmp_path.c b/libdmmp/libdmmp_path.c new file mode 100644 index 0000000..51c72e6 --- /dev/null +++ b/libdmmp/libdmmp_path.c @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 - 2016 Red Hat, Inc. + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Gris Ge <fge@xxxxxxxxxx> + * Todd Gill <tgill@xxxxxxxxxx> + */ + +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> + +#include "libdmmp/libdmmp.h" +#include "libdmmp_private.h" + +struct dmmp_path { + uint32_t pg_id; + char *blk_name; + uint32_t status; +}; + +_dmmp_getter_func_gen(dmmp_path_pg_id_get, struct dmmp_path, dmmp_p, + pg_id, uint32_t, DMMP_PATH_PG_ID_UNKNOWN); +_dmmp_getter_func_gen(dmmp_path_blk_name_get, struct dmmp_path, dmmp_p, + blk_name, const char *, NULL); +_dmmp_getter_func_gen(dmmp_path_status_get, struct dmmp_path, dmmp_p, + status, uint32_t, DMMP_PATH_STATUS_UNKNOWN); + +struct dmmp_path *_dmmp_path_new(void) +{ + struct dmmp_path *dmmp_p = NULL; + + dmmp_p = (struct dmmp_path *) malloc(sizeof(struct dmmp_path)); + + if (dmmp_p != NULL) { + dmmp_p->pg_id = 0; + dmmp_p->blk_name = NULL; + dmmp_p->status = DMMP_PATH_STATUS_UNKNOWN; + } + return dmmp_p; +} + +int _dmmp_path_update(struct dmmp_context *ctx, + struct dmmp_mpath *dmmp_mp, + struct dmmp_path_group *dmmp_pg, + struct dmmp_path *dmmp_p, struct path *pp) +{ + int rc = DMMP_OK; + + if (pp->dev == NULL) { + _error(ctx, "Got NULL struct path->dev"); + rc = DMMP_ERR_BUG; + goto out; + } + + dmmp_p->blk_name = strdup(pp->dev); + _dmmp_alloc_null_check(ctx, dmmp_p->blk_name, rc, out); + + dmmp_p->pg_id = dmmp_path_group_id_get(dmmp_pg); + + if (pp->state > 0) { + dmmp_p->status = pp->state; + } else { + _warn(ctx, "Got mpath %s path %s unexpected path status %d", + dmmp_mpath_name_get(dmmp_mp), dmmp_p->blk_name, + pp->state); + dmmp_p->status = DMMP_PATH_STATUS_UNKNOWN; + } + _debug(ctx, "Got path: path group id: %" PRIu32 ", blk_name: %s, " + "status %" PRIu32 "", + dmmp_p->pg_id, dmmp_p->blk_name, dmmp_p->status); + +out: + return rc; +} + +void _dmmp_path_free(struct dmmp_path *dmmp_p) +{ + if (dmmp_p != NULL) + free(dmmp_p->blk_name); + free(dmmp_p); +} + +/* vim: set ts=8 sts=8 sw=8 tw=80 wrap noet : */ +/* vim: set cindent fo=tcqlron cino=\:0,l1,t0,g0,(0 : */ +/* vim>702: set cc=80 : */ diff --git a/libdmmp/libdmmp_pg.c b/libdmmp/libdmmp_pg.c new file mode 100644 index 0000000..f9380f7 --- /dev/null +++ b/libdmmp/libdmmp_pg.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2015 - 2016 Red Hat, Inc. + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Gris Ge <fge@xxxxxxxxxx> + * Todd Gill <tgill@xxxxxxxxxx> + */ + +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <string.h> + +#include "libdmmp/libdmmp.h" +#include "libdmmp_private.h" + +struct dmmp_path_group { + uint32_t id; + /* ^ pgindex of struct path, will be used for path group switch */ + uint32_t status; + uint32_t priority; + char *selector; /* TODO(Gris Ge): Use static allocation */ + uint32_t dmmp_p_count; + struct dmmp_path **dmmp_ps; +}; + +_dmmp_getter_func_gen(dmmp_path_group_id_get, struct dmmp_path_group, dmmp_pg, + id, uint32_t, DMMP_PATH_GROUP_ID_UNKNOWN); +_dmmp_getter_func_gen(dmmp_path_group_status_get, struct dmmp_path_group, + dmmp_pg, status, uint32_t, DMMP_PATH_GROUP_STATUS_UNKNOWN); +_dmmp_getter_func_gen(dmmp_path_group_priority_get, struct dmmp_path_group, + dmmp_pg, priority, uint32_t, + DMMP_PATH_GROUP_PRIORITY_UNKNOWN); +_dmmp_getter_func_gen(dmmp_path_group_selector_get, struct dmmp_path_group, + dmmp_pg, selector, const char *, NULL); +_dmmp_array_free_func_gen(_dmmp_path_group_array_free, struct dmmp_path_group, + _dmmp_path_group_free); + + +struct dmmp_path_group *_dmmp_path_group_new(void) +{ + struct dmmp_path_group *dmmp_pg = NULL; + + dmmp_pg = (struct dmmp_path_group *) + malloc(sizeof(struct dmmp_path_group)); + + if (dmmp_pg != NULL) { + dmmp_pg->id = 0; + dmmp_pg->status = DMMP_PATH_GROUP_STATUS_UNKNOWN; + dmmp_pg->priority = 0; + dmmp_pg->selector = NULL; + dmmp_pg->dmmp_p_count = 0; + dmmp_pg->dmmp_ps = NULL; + } + return dmmp_pg; +} + +int _dmmp_path_group_update(struct dmmp_context *ctx, + struct dmmp_mpath *dmmp_mp, + struct dmmp_path_group *dmmp_pg, + struct pathgroup *pgp) +{ + int rc = DMMP_OK; + struct path *pp = NULL; + struct dmmp_path *dmmp_p = NULL; + int i = 0; + + if (VECTOR_SIZE(pgp->paths) == 0) { + _error(ctx, "BUG: mpath %s has a empty(no path) path group", + dmmp_mpath_name_get(dmmp_mp)); + rc = DMMP_ERR_BUG; + goto out; + } + + pp = VECTOR_LAST_SLOT(pgp->paths); + dmmp_pg->id = pp->pgindex & UINT32_MAX; + dmmp_pg->status = pgp->status & UINT32_MAX; + + /* ^ TODO(Gris Ge): Assume struct pathgroup does not have invalid + * status, might need to check that. + */ + if (pgp->priority <= 0) + dmmp_pg->priority = DMMP_PATH_GROUP_PRIORITY_UNKNOWN; + else + dmmp_pg->priority = pgp->priority & UINT32_MAX; + + if (pgp->selector != NULL) { + dmmp_pg->selector = strdup(pgp->selector); + _dmmp_alloc_null_check(ctx, dmmp_pg->selector, rc, out); + } else { + _error(ctx, "BUG: Got NULL pgp->selector"); + rc = DMMP_ERR_BUG; + goto out; + } + + _debug(ctx, "Got path group %" PRIu32 ", status %" PRIu32 + ", priority %" PRIu32 ", selector '%s'", + dmmp_pg->id, dmmp_pg->status, dmmp_pg->priority, + dmmp_pg->selector); + + dmmp_pg->dmmp_ps = (struct dmmp_path **) + malloc(sizeof(struct dmmp_path *) * VECTOR_SIZE(pgp->paths)); + _dmmp_alloc_null_check(ctx, dmmp_pg->dmmp_ps, rc, out); + dmmp_pg->dmmp_p_count = VECTOR_SIZE(pgp->paths); + + _debug(ctx, "Got %d path from path group %" PRIu32"", + dmmp_pg->dmmp_p_count, dmmp_pg->id); + + /* Init dmmp_mp->dmmp_pgs */ + for (i = 0; i < VECTOR_SIZE(pgp->paths); ++i) { + dmmp_pg->dmmp_ps[i] = NULL; + } + + vector_foreach_slot(pgp->paths, pp, i) { + if (pp == NULL) { + _error(ctx, "Got NULL struct path pointer"); + rc = DMMP_ERR_BUG; + goto out; + } + dmmp_p = _dmmp_path_new(); + _dmmp_alloc_null_check(ctx, dmmp_p, rc, out); + dmmp_pg->dmmp_ps[i] = dmmp_p; + + rc = _dmmp_path_update(ctx, dmmp_mp, dmmp_pg, dmmp_p, pp); + if (rc != DMMP_OK) + goto out; + } + +out: + return rc; +} + +void _dmmp_path_group_free(struct dmmp_path_group *dmmp_pg) +{ + uint32_t i = 0; + if (dmmp_pg == NULL) + return; + + if (dmmp_pg->selector != NULL) + free((char *) dmmp_pg->selector); + + if (dmmp_pg->dmmp_ps != NULL) { + for (i =0; i < dmmp_pg->dmmp_p_count; ++i) { + _dmmp_path_free(dmmp_pg->dmmp_ps[i]); + } + free(dmmp_pg->dmmp_ps); + } + free(dmmp_pg); +} + +void dmmp_path_array_get(struct dmmp_path_group *mp_pg, + struct dmmp_path ***mp_paths, + uint32_t *dmmp_p_count) +{ + *mp_paths = NULL; + *dmmp_p_count = 0; + if (mp_pg != NULL) { + *mp_paths = mp_pg->dmmp_ps, + *dmmp_p_count = mp_pg->dmmp_p_count; + } +} + +/* vim: set ts=8 sts=8 sw=8 tw=80 wrap noet : */ +/* vim: set cindent fo=tcqlron cino=\:0,l1,t0,g0,(0 : */ +/* vim>702: set cc=80 : */ diff --git a/libdmmp/libdmmp_private.h b/libdmmp/libdmmp_private.h new file mode 100644 index 0000000..8a8c785 --- /dev/null +++ b/libdmmp/libdmmp_private.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 - 2016 Red Hat, Inc. + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Gris Ge <fge@xxxxxxxxxx> + * Todd Gill <tgill@xxxxxxxxxx> + */ + +#ifndef _LIB_DMMP_PRIVATE_H_ +#define _LIB_DMMP_PRIVATE_H_ + +/* + * Notes: + * Internal/Private functions does not check input argument, it + * should be done by caller and log error via dmmp_context. + */ + +#include <stdint.h> + +/* Including libmultipath internal headers BEGIN */ +#include <structs.h> +#include <vector.h> + +/* Including libmultipath internal headers END*/ + +#include "libdmmp/libdmmp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DMMP_DLL_LOCAL struct dmmp_mpath *_dmmp_mpath_new(void); +DMMP_DLL_LOCAL struct dmmp_path_group *_dmmp_path_group_new(void); +DMMP_DLL_LOCAL struct dmmp_path *_dmmp_path_new(void); + +DMMP_DLL_LOCAL int _dmmp_mpath_update(struct dmmp_context *ctx, + struct dmmp_mpath *dmmp_mp, + struct multipath *mpp, + vector pathvec); +DMMP_DLL_LOCAL int _dmmp_path_group_update(struct dmmp_context *ctx, + struct dmmp_mpath *dmmp_mp, + struct dmmp_path_group *dmmp_pg, + struct pathgroup *pgp); +DMMP_DLL_LOCAL int _dmmp_path_update(struct dmmp_context *ctx, + struct dmmp_mpath *dmmp_mp, + struct dmmp_path_group *dmmp_pg, + struct dmmp_path *dmmp_p, + struct path *pp); + +DMMP_DLL_LOCAL void _dmmp_mpath_free(struct dmmp_mpath *dmmp_mp); +DMMP_DLL_LOCAL void _dmmp_path_group_free(struct dmmp_path_group *dmmp_pg); +DMMP_DLL_LOCAL void _dmmp_path_group_array_free + (struct dmmp_path_group **dmmp_pgs, uint32_t dmmp_pg_count); +DMMP_DLL_LOCAL void _dmmp_path_free(struct dmmp_path *dmmp_p); + + +DMMP_DLL_LOCAL void _dmmp_log(struct dmmp_context *ctx, + enum dmmp_log_priority priority, + const char *file, int line, + const char *func_name, + const char *format, ...); +DMMP_DLL_LOCAL void _dmmp_log_err_str(struct dmmp_context *ctx, int rc); + +#define _dmmp_log_cond(ctx, prio, arg...) \ + do { \ + if (dmmp_context_log_priority_get(ctx) >= prio) \ + _dmmp_log(ctx, prio, __FILE__, __LINE__, __FUNCTION__, \ + ## arg); \ + } while (0) + +#define _debug(ctx, arg...) \ + _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_DEBUG, ## arg) +#define _info(ctx, arg...) \ + _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_INFO, ## arg) +#define _warn(ctx, arg...) \ + _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_WARNING, ## arg) +#define _error(ctx, arg...) \ + _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_ERROR, ## arg) + +/* + * Check pointer returned by malloc() or strdup(), if NULL, set + * rc as DMMP_ERR_NO_MEMORY, report error and goto goto_out. + */ +#define _dmmp_alloc_null_check(ctx, ptr, rc, goto_out) \ + do { \ + if (ptr == NULL) { \ + rc = DMMP_ERR_NO_MEMORY; \ + _error(ctx, dmmp_strerror(rc)); \ + goto goto_out; \ + } \ + } while (0) + +#define _dmmp_getter_func_gen(func_name, struct_name, struct_data, \ + prop_name, prop_type, err_value) \ + prop_type func_name(struct_name *struct_data) \ + { \ + if (struct_data == NULL) \ + return err_value; \ + return struct_data->prop_name; \ + } + +#define _dmmp_array_free_func_gen(func_name, struct_name, struct_free_func) \ + void func_name(struct_name **ptr_array, uint32_t ptr_count) \ + { \ + int i = 0; \ + if (ptr_array == NULL) \ + return; \ + for (i = 0; i < ptr_count; ++i) \ + struct_free_func(ptr_array[i]); \ + free(ptr_array); \ + } + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif + +#endif /* End of _LIB_DMMP_PRIVATE_H_ */ + +/* vim: set ts=8 sts=8 sw=8 tw=80 wrap noet : */ +/* vim: set cindent fo=tcqlron cino=\:0,l1,t0,g0,(0 : */ +/* vim>702: set cc=80 : */ diff --git a/libdmmp/test/Makefile b/libdmmp/test/Makefile new file mode 100644 index 0000000..0ceb438 --- /dev/null +++ b/libdmmp/test/Makefile @@ -0,0 +1,27 @@ +# Makefile +# +# Copyright (C) 2015-2016 Gris Ge <fge@xxxxxxxxxx> +# +include ../../Makefile.inc + +_libdmmpdir=../$(libdmmpdir) +_multipathdir=../$(multipathdir) + +TEST_EXEC = libdmmp_test +CFLAGS += -I$(_libdmmpdir) +LDFLAGS += -L$(_libdmmpdir) -L$(_multipathdir) -ldmmp -lmultipath + +all: $(TEST_EXEC) + +check: $(TEST_EXEC) + sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_multipathdir) ./$(TEST_EXEC) + +memcheck: $(TEST_EXEC) + sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_multipathdir) \ + valgrind --quiet --leak-check=full \ + --show-reachable=no --show-possibly-lost=no \ + --trace-children=yes --error-exitcode=1 \ + ./$(TEST_EXEC) + +clean: + rm -f $(TEST_EXEC) diff --git a/libdmmp/test/libdmmp_test.c b/libdmmp/test/libdmmp_test.c new file mode 100644 index 0000000..a9b0f08 --- /dev/null +++ b/libdmmp/test/libdmmp_test.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2015-2016 Red Hat, Inc. + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Gris Ge <fge@xxxxxxxxxx> + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> +#include <pthread.h> +#include <unistd.h> + +#include <libdmmp/libdmmp.h> + +#define FAIL(...) {fprintf(stderr, "FAIL: "__VA_ARGS__ ); exit(1);} +#define PASS(...) fprintf(stdout, "PASS: "__VA_ARGS__ ); +#define FILE_NAME_SIZE 256 + +void test_paths(struct dmmp_path_group *mp_pg) +{ + struct dmmp_path **mp_ps = NULL; + uint32_t mp_p_count = 0; + uint32_t i = 0; + const char *blk_name = NULL; + + dmmp_path_array_get(mp_pg, &mp_ps, &mp_p_count); + if (mp_p_count == 0) + FAIL("dmmp_path_array_get(): Got no path\n"); + for (i = 0; i < mp_p_count; ++i) { + blk_name = dmmp_path_blk_name_get(mp_ps[i]); + if (blk_name == NULL) + FAIL("dmmp_path_blk_name_get(): Got NULL\n"); + PASS("dmmp_path_blk_name_get(): %s\n", blk_name); + PASS("dmmp_path_status_get(): %" PRIu32 " -- %s\n", + dmmp_path_status_get(mp_ps[i]), + dmmp_path_status_str(dmmp_path_status_get(mp_ps[i]))); + PASS("dmmp_path_pg_id_get(): %" PRIu32 "\n", + dmmp_path_pg_id_get(mp_ps[i])); + } +} + +void test_path_groups(struct dmmp_mpath *dmmp_mp) +{ + struct dmmp_path_group **dmmp_pgs = NULL; + uint32_t dmmp_pg_count = 0; + uint32_t i = 0; + + dmmp_path_group_array_get(dmmp_mp, &dmmp_pgs, &dmmp_pg_count); + if ((dmmp_pg_count == 0) && (dmmp_pgs != NULL)) + FAIL("dmmp_path_group_array_get(): mp_pgs is not NULL " + "but mp_pg_count is 0\n"); + if ((dmmp_pg_count != 0) && (dmmp_pgs == NULL)) + FAIL("dmmp_path_group_array_get(): mp_pgs is NULL " + "but mp_pg_count is not 0\n"); + if (dmmp_pg_count == 0) + FAIL("dmmp_path_group_array_get(): Got 0 path group\n"); + + PASS("dmmp_path_group_array_get(): Got %" PRIu32 " path groups\n", + dmmp_pg_count); + + for (i = 0; i < dmmp_pg_count; ++i) { + PASS("dmmp_path_group_id_get(): %" PRIu32 "\n", + dmmp_path_group_id_get(dmmp_pgs[i])); + PASS("dmmp_path_group_priority_get(): %" PRIu32 "\n", + dmmp_path_group_priority_get(dmmp_pgs[i])); + PASS("dmmp_path_group_status_get(): %" PRIu32 " -- %s\n", + dmmp_path_group_status_get(dmmp_pgs[i]), + dmmp_path_group_status_str + (dmmp_path_group_status_get(dmmp_pgs[i]))); + PASS("dmmp_path_group_selector_get(): %s\n", + dmmp_path_group_selector_get(dmmp_pgs[i])); + test_paths(dmmp_pgs[i]); + } +} + +int main(int argc, char *argv[]) +{ + struct dmmp_context *ctx = NULL; + struct dmmp_mpath **dmmp_mps = NULL; + uint32_t dmmp_mp_count = 0; + const char *name = NULL; + const char *wwid = NULL; + uint32_t i = 0; + + ctx = dmmp_context_new(); + dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG); + + if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0) + FAIL("dmmp_mpath_array_get(): rc != 0\n"); + if (dmmp_mp_count == 0) + FAIL("dmmp_mpath_array_get(): Got no multipath devices\n"); + PASS("dmmp_mpath_array_get(): Got %" PRIu32 " mpath\n", dmmp_mp_count); + for (i = 0; i < dmmp_mp_count; ++i) { + name = dmmp_mpath_name_get(dmmp_mps[i]); + wwid = dmmp_mpath_wwid_get(dmmp_mps[i]); + if ((name == NULL) ||(wwid == NULL)) + FAIL("dmmp_mpath_array_get(): Got NULL name or wwid"); + PASS("dmmp_mpath_array_get(): Got mpath: %s %s\n", name, wwid); + test_path_groups(dmmp_mps[i]); + } + dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count); + dmmp_context_free(ctx); + exit(0); +} + +/* vim: set ts=8 sts=8 sw=8 tw=80 wrap noet : */ +/* vim: set cindent fo=tcqlron cino=\:0,l1,t0,g0,(0 : */ +/* vim: set cc=80 : */ -- 2.7.0 -- dm-devel mailing list dm-devel@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/dm-devel