From: Thomas Walker Lynch Date: Tue, 12 Mar 2019 17:47:50 +0000 (+0100) Subject: checkpoint X-Git-Url: https://git.reasoningtechnology.com/style/static/git-logo.png?a=commitdiff_plain;h=f2cca4f60e07298a5e4c11ad163cb65403b98b04;p=subu checkpoint --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de56908 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ + +__pycache__/ +tmp/ +.* +!.gitignore + + diff --git a/7_makefile b/7_makefile new file mode 100755 index 0000000..24dfafb --- /dev/null +++ b/7_makefile @@ -0,0 +1,28 @@ +#subdirectories=$(shell /usr/bin/find . -maxdepth 1 -printf "%f " | sed y/\./\ /) +#subdirectories=src src/1_tests src/1_try +subdirectories=src + +all : + $(foreach dir, $(subdirectories), \ + if [ -f $(dir)/makefile ]; then \ + make -C $(dir) all && make -C $(dir) install; \ + fi;\ + ) + +clean : + $(foreach dir, $(subdirectories), \ + if [ -f ./$(dir)/makefile ]; then \ + make -C $(dir) clean; \ + fi;\ + ) + +dist_clean : + $(foreach dir, $(subdirectories), \ + if [ -f ./$(dir)/makefile ]; then \ + make -C $(dir) dist_clean; \ + fi;\ + ) + + + + diff --git a/bin/gitadd b/bin/gitadd deleted file mode 100755 index 7097d77..0000000 --- a/bin/gitadd +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -set -x -make clean -git add "$@" - diff --git a/doc/dir-structure.txt b/doc/dir-structure.txt new file mode 100644 index 0000000..668b909 --- /dev/null +++ b/doc/dir-structure.txt @@ -0,0 +1,57 @@ + +~ is the project directory + + +-------------------------------------------------------------------------------- +~/tools + +Some tools are normally installed on the system. Others are local. Local tools +are typically standard tools that have been customized for the project or are +unusual to the project. + + ~/tools/bin is for the executables of locally used tools. + ~/tools/lib is locally used libraries. + ~/tools/doc is for collected docs on tools + +typically a developer will want ~/tools/bin in his search path + +-------------------------------------------------------------------------------- +~/staged + +These are make file build targets. They represent either intermediate or +finished executables and libraries. When a project has multiple src +directories and they use each other's work product for the builds, the +items in the staged area are those that are used by each src directory. + +Typically the install target copies material in the staging area to +system locations. + +-------------------------------------------------------------------------------- +~/src + +The prefixed numbered directories are overhead for building the src code. + +src/1_tests these are programs that typically must pass before the compiled +code may be staged. Tests typically make use of the library in 2_lib and +the header files in 2_include. + +src/1_try a free area for the programmer to try things out. For example when +learning a language. Programs and code left in this directory might be interesting +to newcomers. + +src/2_bin these are cli executables that have been created by the makefile +and will be staged after testing + +src/2_include interface header files to be released with the lib. These might +be different from the build header files found in the src dir. + +src/2_lib holds libraries being tested. Currently each src directory only +builds one library. + +3_documents these are for developers working on src. A user manual might +be under development. + + + + + diff --git a/makefile b/makefile deleted file mode 100755 index f77a99c..0000000 --- a/makefile +++ /dev/null @@ -1,16 +0,0 @@ -#subdirectories=$(shell /usr/bin/find . -maxdepth 1 -printf "%f " | sed y/\./\ /) -subdirectories=src - -all : - $(foreach dir, $(subdirectories), \ - if [ -f $(dir)/makefile ]; then \ - make -C $(dir) all && make -C $(dir) install; \ - fi;\ - ) - -clean : - $(foreach dir, $(subdirectories), \ - if [ -f ./$(dir)/makefile ]; then \ - make -C $(dir) clean; \ - fi;\ - ) diff --git a/src/1_try/mh_main_prob/command1.c b/src/1_try/mh_main_prob/command1.c deleted file mode 100644 index 637df55..0000000 --- a/src/1_try/mh_main_prob/command1.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "command1.h" -#include -int main(int argc, char **argv){ - printf("command1 %d\n", f()); - return 0; -} diff --git a/src/1_try/mh_main_prob/command2.c b/src/1_try/mh_main_prob/command2.c deleted file mode 100644 index 5d8c612..0000000 --- a/src/1_try/mh_main_prob/command2.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "command2.h" -#include -int main(int argc, char **argv){ - printf("command2 %d\n", f() + argc); - return 0; -} diff --git a/src/1_try/mh_main_prob/just_fun.c b/src/1_try/mh_main_prob/just_fun.c deleted file mode 100644 index 67625f4..0000000 --- a/src/1_try/mh_main_prob/just_fun.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "just_fun.h" -int f(){ - return 5; -} diff --git a/src/1_try/mh_main_prob/transcript1.txt b/src/1_try/mh_main_prob/transcript1.txt deleted file mode 100644 index c5511fe..0000000 --- a/src/1_try/mh_main_prob/transcript1.txt +++ /dev/null @@ -1,36 +0,0 @@ -Various commmand files each with its own main for testing a library. makeheaders gets confused and puts all the -declarations in the headers, leading to a failure. - - -> ls -command1.c command2.c just_fun.c -> cat just_fun.c -#include "just_fun.h" -int f(){ - return 5; -} -> cat command1.c -#include "command1.h" -#include -int main(){ - printf("command1 %d\n", f()); - return 0; -} -> cat command2.c -#include "command2.h" -#include -int main(int argc, char **argv){ - printf("command2 %d\n", f() + argc); - return 0; -} -> makeheaders *.c -> gcc -o command1 command1.c -command1.c: In function ‘main’: -command1.c:3:1: error: number of arguments doesn’t match prototype - int main(){ - ^~~ -In file included from command1.c:1: -command1.h:5:5: error: prototype declaration - int main(int argc,char **argv); - ^~~~ -> diff --git a/src/1_try/mh_main_prob/transcript2.txt b/src/1_try/mh_main_prob/transcript2.txt deleted file mode 100644 index 77ec819..0000000 --- a/src/1_try/mh_main_prob/transcript2.txt +++ /dev/null @@ -1,20 +0,0 @@ -Making each main call static so it won't be in the header. gcc can't find main. - -> cat command1.c -#include "command1.h" -#include -static int main(){ - printf("command1 %d\n", f()); - return 0; -} -> cat command2.c -#include "command2.h" -#include -static int main(int argc, char **argv){ - printf("command2 %d\n", f() + argc); - return 0; -} -> gcc -o command1 command1.c just_fun.c -/usr/bin/ld: /usr/lib/gcc/x86_64-redhat-linux/8/../../../../lib64/crt1.o: in function `_start': -(.text+0x24): undefined reference to `main' -collect2: error: ld returned 1 exit status diff --git a/src/1_try/mh_main_prob/transcript3.txt b/src/1_try/mh_main_prob/transcript3.txt deleted file mode 100644 index b3a00b7..0000000 --- a/src/1_try/mh_main_prob/transcript3.txt +++ /dev/null @@ -1,27 +0,0 @@ -This time making each main definition have the same prototype. Still end up with multiple main declarations, -it is just that they agree. - -> rm *.h -> makeheaders *.c -> cat command1.c -#include "command1.h" -#include -int main(int argc, char **argv){ - printf("command1 %d\n", f()); - return 0; -} -> cat command1.h -/* This file was automatically generated. Do not edit! */ -#undef INTERFACE -int f(); -int main(int argc,char **argv); -int main(int argc,char **argv); -> cat command2.c -#include "command2.h" -#include -int main(int argc, char **argv){ - printf("command2 %d\n", f() + argc); - return 0; -} -> gcc -o command1 command1.c just_fun.c -> .. worked diff --git a/src/2_bin/setuid_root.sh b/src/2_bin/setuid_root.sh deleted file mode 100755 index bea2d00..0000000 --- a/src/2_bin/setuid_root.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# must be run under sudo -# be sure to cat it before running it, better yet just type this in manually -# - -chown root $1 && \ -chmod u+rsx,u-w,go+rx-s-w $1 - diff --git a/src/2_doc/README.txt b/src/2_doc/README.txt new file mode 100644 index 0000000..a9a2b21 --- /dev/null +++ b/src/2_doc/README.txt @@ -0,0 +1,23 @@ + + +filename.tag.extension + +extension: + .c for C source + .cc for C++ source + .h for C header file + .hh for C++ header file + .o an object file + +tag: + .lib. The resulting .o file to be placed in release library and is part of the + programming interface. + .aux. The resulting.o file not directly part of the programming interface, but + it might be called by functions that are. + .cli. The source file has a main call and is to be relased as part of the command line interface + .loc. file has a main call to be made into a local uitlity function + +We carry the source file tag and extension to the .o file. We do not put tags +nor extensions on command line executables. + +local_common.h should be included in all source files diff --git a/src/2_doc/makefile.txt b/src/2_doc/makefile.txt new file mode 100644 index 0000000..722de84 --- /dev/null +++ b/src/2_doc/makefile.txt @@ -0,0 +1,91 @@ +these are the comments from my RT makefile: + +# +# 2010 11 20 TWL Created +# 2011 05 26 TWL Modified to generalize +# 2012 02 23 NLS Add ECHO variable to use on different environnement +# corrected setup macro --> add include directory in the path to copy +# corrected install macro --> change the name of installed library : lib$(LIB)$(LIBSUFFIX) +# changed DOC_DIR directory name to 5_documents +# 2012 02 23 TWL removed LIB variable which is now set from the command line so +# so that all source dirs can use the same makefile +# 2012 02 23 TWL added target make dist_clean which also deletes the 2_makefile_deps file +# 2012 04 11 AWW added creation of temporary disk before each test is run +# 2012 06 05 TWL moved tests and try .cc files to directories. caused rtmake tests to +# dist_clean and make deps +# +# +#---------------------------------------------------------------------------- +# use this makefile to compile and test the code: +# +# for a first time run, or for regression use the following: +# +# $ make setup # makes the directories, though should already exist +# $ make regress +# +# the usual development workflow makes use of these: +# +# $ make deps # only when needed, for example if headers includes change or new files introduced +# $ cd tests; make deps # only when needed +# $ make lib # this makes the local library +# $ make tests # this updates tests and compiles +# $ make clean # deletes the .o files and library to force a recompile +# $ cd 1_tests; make clean +# +# for a release of a component +# +# $ make regress +# $ make install # this will only work if all the tests in 1_tests are passing +# +# before a checkin +# +# $ make dist_clean # will also clean the tests and try directories +# +# .lib.cc c++ files taken as source of object files for local build library +# .exl.cc c++ files taken to have main calls and are linked against local build libary +# .ex.cc c++ files taken to have main calls and are not linked against the local build library +# there are no rules for other files in this makefile +# +# about dependencies +# The makefile has no way of knowing if an edit changed the dependencies. Often they do not +# and it would be unwieldy to make the deps every time. Hence *the programmer* must delete +# the deps file if he has made any changes that change the dependencies. +# +# The makefile will make the 2_makefile_deps if the file is missing. +# +# +# about testing +# +# the name of the directory you run make in is taken to also be: the name of the library, +# the name of the main include file (with a .h added), and the name of the include directory +# where the individual headers are found. It is called LIB +# +# test programs are kept in a subdirectory called 1_tests, and are either .exl.cc, ex.cc, +# .sh files. When 'make tests' target is invoked they are all run. Test executables return 0 +# if the test fails, non-zero otherwise. +# +# to remove a test from the pool move it into the subdirectory in 1_tests, 9_broken, +# 5_more_tests of 5_scratch. broken tests are things that are known but must be fixed +# before a release. 5_more_tests are tests being worked on. 5_scratch is stuff that is +# probably going to be deleted. if there is a 5_deprecated, that is for good stuff but it +# is no longer used for some reason or other. +# +# There is a standard source code template and a +# messaging convention. Also, the names, by convention,are test_xxxx_ where xxx is a +# hexadecimal series nummber. If all the test executables pass the file 1_TESTS_PASSED is +# left in the directory. Otherwise the file 1_TESTS_FAILED is left in the directory. +# +# about release directory +# +# this is set in the ApplicationBase variable by rt_init +# +# after the tests pass stuff might be copied to the release directory using +# +# make install +# +# the release directory must have these subdirectories: +# +# bin documents include src +# +# +# diff --git a/src/2_doc/makeheaders.txt b/src/2_doc/makeheaders.txt new file mode 100644 index 0000000..5aebda9 --- /dev/null +++ b/src/2_doc/makeheaders.txt @@ -0,0 +1,16 @@ + +This worked to force the include to be part of the interface: + +#if INTERFACE +#include +#endif + +But this did not: + +#if INTERFACE + #include +#endif + +makeheaders looks to be sensitive to indentation + + diff --git a/src/2_doc/sqlite.txt b/src/2_doc/sqlite.txt new file mode 100644 index 0000000..b7f8cbb --- /dev/null +++ b/src/2_doc/sqlite.txt @@ -0,0 +1,15 @@ + +1. + This sql: + + char *sql = + "BEGIN TRANSACTION;" + "UPDATE Key_Int SET value = value + 1 WHERE key = 'max_subu_number';" + "SELECT value FROM Key_Int WHERE key = 'max_subu_number';" + "COMMIT;" + ; + + with sqlite_exec, the call back is called with the data from the select. + + with sqlite_prepare_v2, sqlite_step just returns SQLITE_DONE, and we never + get to see our data from the select. diff --git a/src/2_doc/to_do.txt b/src/2_doc/to_do.txt new file mode 100644 index 0000000..eebf52a --- /dev/null +++ b/src/2_doc/to_do.txt @@ -0,0 +1,19 @@ +2019-02-05T23:14:40Z + error can cause subu-mk-0 to leave the creating of a subu in an intermediate + state. Rather than bailing on some of the errors we need to clean up instead. + Perhaps the yet to be written subu-rm program will be resilent enough to do + more general cleanup. + +2019-02-23T18:56:31Z + need to modify subu-init to take a configuration file name argument instead of + using a global variabel value. might want to add arguments to other subu + commands also + +2019-03-11T13:48:03Z + in subu.lib.c append cascading rmdir failure mess to useradd failure mess + +2019-03-11T13:48:03Z + want to add subu-type to masteru_subu(), I imagine there will be static, + permanent, and temporary subu types. + + diff --git a/src/3_documents/README.txt b/src/3_documents/README.txt deleted file mode 100644 index a9a2b21..0000000 --- a/src/3_documents/README.txt +++ /dev/null @@ -1,23 +0,0 @@ - - -filename.tag.extension - -extension: - .c for C source - .cc for C++ source - .h for C header file - .hh for C++ header file - .o an object file - -tag: - .lib. The resulting .o file to be placed in release library and is part of the - programming interface. - .aux. The resulting.o file not directly part of the programming interface, but - it might be called by functions that are. - .cli. The source file has a main call and is to be relased as part of the command line interface - .loc. file has a main call to be made into a local uitlity function - -We carry the source file tag and extension to the .o file. We do not put tags -nor extensions on command line executables. - -local_common.h should be included in all source files diff --git a/src/3_documents/makefile.txt b/src/3_documents/makefile.txt deleted file mode 100644 index 722de84..0000000 --- a/src/3_documents/makefile.txt +++ /dev/null @@ -1,91 +0,0 @@ -these are the comments from my RT makefile: - -# -# 2010 11 20 TWL Created -# 2011 05 26 TWL Modified to generalize -# 2012 02 23 NLS Add ECHO variable to use on different environnement -# corrected setup macro --> add include directory in the path to copy -# corrected install macro --> change the name of installed library : lib$(LIB)$(LIBSUFFIX) -# changed DOC_DIR directory name to 5_documents -# 2012 02 23 TWL removed LIB variable which is now set from the command line so -# so that all source dirs can use the same makefile -# 2012 02 23 TWL added target make dist_clean which also deletes the 2_makefile_deps file -# 2012 04 11 AWW added creation of temporary disk before each test is run -# 2012 06 05 TWL moved tests and try .cc files to directories. caused rtmake tests to -# dist_clean and make deps -# -# -#---------------------------------------------------------------------------- -# use this makefile to compile and test the code: -# -# for a first time run, or for regression use the following: -# -# $ make setup # makes the directories, though should already exist -# $ make regress -# -# the usual development workflow makes use of these: -# -# $ make deps # only when needed, for example if headers includes change or new files introduced -# $ cd tests; make deps # only when needed -# $ make lib # this makes the local library -# $ make tests # this updates tests and compiles -# $ make clean # deletes the .o files and library to force a recompile -# $ cd 1_tests; make clean -# -# for a release of a component -# -# $ make regress -# $ make install # this will only work if all the tests in 1_tests are passing -# -# before a checkin -# -# $ make dist_clean # will also clean the tests and try directories -# -# .lib.cc c++ files taken as source of object files for local build library -# .exl.cc c++ files taken to have main calls and are linked against local build libary -# .ex.cc c++ files taken to have main calls and are not linked against the local build library -# there are no rules for other files in this makefile -# -# about dependencies -# The makefile has no way of knowing if an edit changed the dependencies. Often they do not -# and it would be unwieldy to make the deps every time. Hence *the programmer* must delete -# the deps file if he has made any changes that change the dependencies. -# -# The makefile will make the 2_makefile_deps if the file is missing. -# -# -# about testing -# -# the name of the directory you run make in is taken to also be: the name of the library, -# the name of the main include file (with a .h added), and the name of the include directory -# where the individual headers are found. It is called LIB -# -# test programs are kept in a subdirectory called 1_tests, and are either .exl.cc, ex.cc, -# .sh files. When 'make tests' target is invoked they are all run. Test executables return 0 -# if the test fails, non-zero otherwise. -# -# to remove a test from the pool move it into the subdirectory in 1_tests, 9_broken, -# 5_more_tests of 5_scratch. broken tests are things that are known but must be fixed -# before a release. 5_more_tests are tests being worked on. 5_scratch is stuff that is -# probably going to be deleted. if there is a 5_deprecated, that is for good stuff but it -# is no longer used for some reason or other. -# -# There is a standard source code template and a -# messaging convention. Also, the names, by convention,are test_xxxx_ where xxx is a -# hexadecimal series nummber. If all the test executables pass the file 1_TESTS_PASSED is -# left in the directory. Otherwise the file 1_TESTS_FAILED is left in the directory. -# -# about release directory -# -# this is set in the ApplicationBase variable by rt_init -# -# after the tests pass stuff might be copied to the release directory using -# -# make install -# -# the release directory must have these subdirectories: -# -# bin documents include src -# -# -# diff --git a/src/3_documents/makeheaders.txt b/src/3_documents/makeheaders.txt deleted file mode 100644 index 5aebda9..0000000 --- a/src/3_documents/makeheaders.txt +++ /dev/null @@ -1,16 +0,0 @@ - -This worked to force the include to be part of the interface: - -#if INTERFACE -#include -#endif - -But this did not: - -#if INTERFACE - #include -#endif - -makeheaders looks to be sensitive to indentation - - diff --git a/src/3_documents/sqlite.txt b/src/3_documents/sqlite.txt deleted file mode 100644 index b7f8cbb..0000000 --- a/src/3_documents/sqlite.txt +++ /dev/null @@ -1,15 +0,0 @@ - -1. - This sql: - - char *sql = - "BEGIN TRANSACTION;" - "UPDATE Key_Int SET value = value + 1 WHERE key = 'max_subu_number';" - "SELECT value FROM Key_Int WHERE key = 'max_subu_number';" - "COMMIT;" - ; - - with sqlite_exec, the call back is called with the data from the select. - - with sqlite_prepare_v2, sqlite_step just returns SQLITE_DONE, and we never - get to see our data from the select. diff --git a/src/3_to_do.txt b/src/3_to_do.txt deleted file mode 100644 index eebf52a..0000000 --- a/src/3_to_do.txt +++ /dev/null @@ -1,19 +0,0 @@ -2019-02-05T23:14:40Z - error can cause subu-mk-0 to leave the creating of a subu in an intermediate - state. Rather than bailing on some of the errors we need to clean up instead. - Perhaps the yet to be written subu-rm program will be resilent enough to do - more general cleanup. - -2019-02-23T18:56:31Z - need to modify subu-init to take a configuration file name argument instead of - using a global variabel value. might want to add arguments to other subu - commands also - -2019-03-11T13:48:03Z - in subu.lib.c append cascading rmdir failure mess to useradd failure mess - -2019-03-11T13:48:03Z - want to add subu-type to masteru_subu(), I imagine there will be static, - permanent, and temporary subu types. - - diff --git a/src/5_deprecated/subudb-number-next.cli.c b/src/5_deprecated/subudb-number-next.cli.c new file mode 100644 index 0000000..3373173 --- /dev/null +++ b/src/5_deprecated/subudb-number-next.cli.c @@ -0,0 +1,45 @@ +/* +Set or get a new maximum subu number. Currently doesn't do the setting part. + +*/ +#include "subudb-number-next.cli.h" +#include +#include +#include + +int main(int argc, char **argv){ + + if( argc != 2 ){ + fprintf(stderr, "usage: %s masteru_name \n",argv[0]); + return SUBU_ERR_ARG_CNT; + } + char *masteru_name = argv[1]; + + int rc; + sqlite3 *db; + rc = sqlite3_open_v2(DB_File, &db, SQLITE_OPEN_READWRITE, NULL); + if( rc != SQLITE_OK ){ + sqlite3_close(db); + fprintf(stderr, "error exit, could not open db file\n"); + return SUBU_ERR_DB_FILE; + } + + // read and print the current max + char *mess; + int n; + rc = subudb_number_next(db, masteru_name, &n, &mess); + if( rc == SQLITE_DONE ){ + printf("%d\n", n); + }else{ + fprintf(stderr, "subudb_number_next indicates failure by returning %d\n",rc); + fprintf(stderr, "and issues message, %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + return SUBU_ERR_DB_FILE; + } + rc = sqlite3_close(db); + if( rc != SQLITE_OK ){ + fprintf(stderr, "when closing db, %s\n", sqlite3_errmsg(db)); + return SUBU_ERR_DB_FILE; + } + return 0; +} diff --git a/src/5_scratch/common.lib.h b/src/5_scratch/common.lib.h index 2454943..cfdc520 100644 --- a/src/5_scratch/common.lib.h +++ b/src/5_scratch/common.lib.h @@ -2,8 +2,8 @@ #undef INTERFACE extern char Subuland_Extension[]; typedef unsigned int uint; -extern uint First_Max_Subu_number; +extern uint First_Max_Subunumber; extern uint Subuhome_Perms; -extern char Config_File[]; +extern char DB_File[]; #define BUG_SSS_CACHE_RUID 1 #define INTERFACE 0 diff --git a/src/5_scratch/subu-common.lib.h b/src/5_scratch/subu-common.lib.h new file mode 100644 index 0000000..cfdc520 --- /dev/null +++ b/src/5_scratch/subu-common.lib.h @@ -0,0 +1,9 @@ +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +extern char Subuland_Extension[]; +typedef unsigned int uint; +extern uint First_Max_Subunumber; +extern uint Subuhome_Perms; +extern char DB_File[]; +#define BUG_SSS_CACHE_RUID 1 +#define INTERFACE 0 diff --git a/src/5_scratch/subu-config.lib.c.~ceea6e7d697546c47f7736b72e7fb60b15c104de~ b/src/5_scratch/subu-config.lib.c.~ceea6e7d697546c47f7736b72e7fb60b15c104de~ new file mode 100644 index 0000000..de7bcbb --- /dev/null +++ b/src/5_scratch/subu-config.lib.c.~ceea6e7d697546c47f7736b72e7fb60b15c104de~ @@ -0,0 +1,147 @@ +/* +The config file is maintained in SQLite + +Because user names of are of limited length, subu user names are always named _s. +A separate table translates the numbers into the subu names. + +The first argument is the biggest subu number in the system, or one minus an +starting point for subu numbering. + +currently a unit converted to base 10 will always fit in a 21 bit buffer. + +*/ +#include "subu-config.lib.h" + +#if INTERFACE +#include +#define ERR_CONFIG_FILE 1 +#endif + +#include +#include +#include + +//-------------------------------------------------------------------------------- +int subudb_schema(sqlite3 *db, uint max_subu_number){ + char max_subu_number_string[32]; + uint max_subu_number_string_len = snprintf(max_subu_number_string, 32, "%u", max_subu_number); + if( max_subu_number_string_len >= 32 ){ + fprintf(stderr, "error exit, max_subu_number too big to deal with\n"); + return ERR_CONFIG_FILE; + } + char sql1[] = "CREATE TABLE Masteru_Subu(masteru_name TEXT, subuname TEXT, subu_username TEXT); "; + char sql2[] = "CREATE TABLE Key_Int(key TEXT, value INT); "; + + char sql3_1[] = "INSERT INTO Key_Int VALUES( 'max_subu_number', "; + char sql3_2[] = " ); "; + char sql3_len = strlen(sql3_1) + max_subu_number_string_len + strlen(sql3_2) + 1; + char sql3[sql3_len]; + strcpy(sql3, sql3_1); + strcpy(sql3 + strlen(sql3_1), max_subu_number_string); + strcpy(sql3 + strlen(sql3_1) + max_subu_number_string_len, sql3_2); + + char sql[strlen(sql1) + strlen(sql2) + strlen(sql3) + 1]; + strcpy(sql, sql1); + strcpy(sql + strlen(sql1), sql2); + strcpy(sql + strlen(sql1) + strlen(sql2), sql3); + + return sqlite3_exec(db, sql, NULL, NULL, NULL); +} + +//-------------------------------------------------------------------------------- + +// the call back for subu_number_next, note also 3_doc/sqlite3.txt +static int subu_number_extract(void *nsp, int colcnt, char **colvals, char **colnames){ + if(colcnt >= 1){ + *(char **)nsp = strdup( colvals[0] ); + return 0; + } + return -1; +} +int subu_number_next(sqlite3 *db, char **nsp, char **mess){ + char *sql = + "BEGIN TRANSACTION;" + "UPDATE Key_Int SET value = value + 1 WHERE key = 'max_subu_number';" + "SELECT value FROM Key_Int WHERE key = 'max_subu_number';" + "COMMIT;"; + int rc = sqlite3_exec(db, sql, subu_number_extract, (void *)nsp, mess); + return rc; +} +int subu_number_get(sqlite3 *db, int *n){ + char *sql = "SELECT value FROM Key_Int WHERE key = 'max_subu_number';"; + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + int rc = sqlite3_step(stmt); + if( rc == SQLITE_ROW ){ + *n = sqlite3_column_int(stmt,0); + }else{ + sqlite3_finalize(stmt); + return rc; // woops this needs to return an error!, be sure it is not SQLITE_DONE + } + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + return rc; +} +int subu_number_set(sqlite3 *db, int n){ + char *sql = "UPDATE Key_Int SET value = ?1 WHERE key = 'max_subu_number';"; + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, n); + int rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + return rc; +} + + +//-------------------------------------------------------------------------------- +// put relation into Masteru_Subu table +int subu_Masteru_Subu_put(sqlite3 *db, char *masteru_name, char *subuname, char *subu_username){ + char *sql = "INSERT INTO Masteru_Subu VALUES (?1, ?2, ?3);"; + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, subuname, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, subu_username, -1, SQLITE_STATIC); + int rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + return rc; +} + +//-------------------------------------------------------------------------------- +int subu_Masteru_Subu_get(sqlite3 *db, char *masteru_name, char *subuname, char **subu_username){ + char *sql = "SELECT subu_username FROM Masteru_Subu WHERE masteru_name = ?1 AND subuname = ?2;"; + size_t sql_len = strlen(sql); + sqlite3_stmt *stmt; + int rc; + rc = sqlite3_prepare_v2(db, sql, sql_len, &stmt, NULL); + if( rc != SQLITE_OK ) return rc; + sqlite3_bind_text(stmt, 1, masteru_name, strlen(masteru_name), SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, subuname, strlen(subuname), SQLITE_STATIC); + rc = sqlite3_step(stmt); + if( rc == SQLITE_ROW ){ + const char *username = sqlite3_column_text(stmt, 0); + *subu_username = strdup(username); + }else{ + sqlite3_finalize(stmt); + return rc; // woops this needs to return an error!, be sure it is not SQLITE_DONE + } + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + return rc; +} + +//-------------------------------------------------------------------------------- +int subu_Masteru_Subu_rm(sqlite3 *db, char *masteru_name, char *subuname, char *subu_username){ + char *sql = "DELETE FROM Masteru_Subu WHERE masteru_name = ?1 AND subuname = ?2 AND subu_username = ?3;"; + size_t sql_len = strlen(sql); + sqlite3_stmt *stmt; + int rc; + rc = sqlite3_prepare_v2(db, sql, sql_len, &stmt, NULL); + if( rc != SQLITE_OK ) return rc; + sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, subuname, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, subu_username, -1, SQLITE_STATIC); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + return rc; +} diff --git a/src/5_scratch/subu-mk-0.cli.h b/src/5_scratch/subu-mk-0.cli.h index 56f6cf6..487b509 100644 --- a/src/5_scratch/subu-mk-0.cli.h +++ b/src/5_scratch/subu-mk-0.cli.h @@ -5,6 +5,6 @@ #include void subu_err(char *fname,int err,char *mess); int subu_mk_0(char **mess,sqlite3 *db,char *subuname); -#define SUBU_ERR_CONFIG_FILE 8 -extern char Config_File[]; +#define SUBU_ERR_DB_FILE 8 +extern char DB_File[]; #define SUBU_ERR_ARG_CNT 1 diff --git a/src/5_scratch/subu-rm-0.cli.h b/src/5_scratch/subu-rm-0.cli.h index 56f6cf6..070bfe8 100644 --- a/src/5_scratch/subu-rm-0.cli.h +++ b/src/5_scratch/subu-rm-0.cli.h @@ -4,7 +4,7 @@ #include #include void subu_err(char *fname,int err,char *mess); -int subu_mk_0(char **mess,sqlite3 *db,char *subuname); -#define SUBU_ERR_CONFIG_FILE 8 -extern char Config_File[]; +int subu_rm_0(char **mess,sqlite3 *db,char *subuname); +#define SUBU_ERR_DB_FILE 8 +extern char DB_File[]; #define SUBU_ERR_ARG_CNT 1 diff --git a/src/5_scratch/subu.lib.h b/src/5_scratch/subu.lib.h index 199daab..45c0b81 100644 --- a/src/5_scratch/subu.lib.h +++ b/src/5_scratch/subu.lib.h @@ -1,12 +1,12 @@ /* This file was automatically generated. Do not edit! */ #undef INTERFACE #include -int subu_rm_masteru_subu(sqlite3 *db,char *masteru_name,char *subuname,char *subu_username); -int subu_get_masteru_subu(sqlite3 *db,char *masteru_name,char *subuname,char **subu_username); +int subudb_Masteru_Subu_rm(sqlite3 *db,char *masteru_name,char *subuname,char *subu_username); +int subudb_Masteru_Subu_get(sqlite3 *db,char *masteru_name,char *subuname,char **subu_username); #include #include int subu_rm_0(char **mess,sqlite3 *db,char *subuname); -int subu_put_masteru_subu(sqlite3 *db,char *masteru_name,char *subuname,char *subu_username); +int subudb_Masteru_Subu_put(sqlite3 *db,char *masteru_name,char *subuname,char *subu_username); #include #include int dispatch_exec(char **argv,char **envp); @@ -14,13 +14,19 @@ int dispatch_exec(char **argv,char **envp); void dispatch_f_mess(char *fname,int err,char *dispatchee); #define ERR_DISPATCH -1024 int dispatch_f_euid_egid(char *fname,int(*f)(void *arg),void *f_arg,uid_t euid,gid_t egid); +int db_commit(sqlite3 *db); +int subudb_number_init(sqlite3 *db,char *masteru_name,int n); +typedef unsigned int uint; +extern uint First_Max_Subunumber; +int db_rollback(sqlite3 *db); +int subudb_number_set(sqlite3 *db,char *masteru_name,int n); +int subudb_number_get(sqlite3 *db,char *masteru_name,int *n); +int db_begin(sqlite3 *db); int dbprintf(const char *format,...); int subu_mk_0(char **mess,sqlite3 *db,char *subuname); extern char Subuland_Extension[]; -int subu_number_next(sqlite3 *db,char **nsp,char **mess); -typedef unsigned int uint; extern uint Subuhome_Perms; -extern char Config_File[]; +extern char DB_File[]; void subu_err(char *fname,int err,char *mess); #define SUBU_ERR_N 14 #define SUBU_ERR_CONFIG_SUBU_NOT_FOUND 13 @@ -28,7 +34,7 @@ void subu_err(char *fname,int err,char *mess); #define SUBU_ERR_FAILED_USERADD 11 #define SUBU_ERR_BUG_SSS 10 #define SUBU_ERR_SUBUHOME_EXISTS 9 -#define SUBU_ERR_CONFIG_FILE 8 +#define SUBU_ERR_DB_FILE 8 #define SUBU_ERR_MASTERU_HOMELESS 7 #define SUBU_ERR_SUBUNAME_MALFORMED 6 #define SUBU_ERR_RMDIR_SUBUHOME 5 diff --git a/src/5_scratch/subudb-init.cli.h b/src/5_scratch/subudb-init.cli.h new file mode 100644 index 0000000..36fa2c9 --- /dev/null +++ b/src/5_scratch/subudb-init.cli.h @@ -0,0 +1,8 @@ +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +#include +int subudb_schema(sqlite3 *db); +#include +#include +#define SUBU_ERR_DB_FILE 8 +extern char DB_File[]; diff --git a/src/5_scratch/subudb-number-next.cli.h b/src/5_scratch/subudb-number-next.cli.h new file mode 100644 index 0000000..2c295dc --- /dev/null +++ b/src/5_scratch/subudb-number-next.cli.h @@ -0,0 +1,9 @@ +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +#include +int subudb_number_next(sqlite3 *db,char *masteru_name,int *n,char **mess); +#include +#include +#define SUBU_ERR_DB_FILE 8 +extern char DB_File[]; +#define SUBU_ERR_ARG_CNT 1 diff --git a/src/5_scratch/subudb-number.cli.h b/src/5_scratch/subudb-number.cli.h new file mode 100644 index 0000000..3e4e351 --- /dev/null +++ b/src/5_scratch/subudb-number.cli.h @@ -0,0 +1,11 @@ +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +#include +int subudb_number_get(sqlite3 *db,char *masteru_name,int *n); +int subudb_number_set(sqlite3 *db,char *masteru_name,int n); +#include +#include +#define SUBU_ERR_N 14 +#define SUBU_ERR_DB_FILE 8 +extern char DB_File[]; +#define SUBU_ERR_ARG_CNT 1 diff --git a/src/5_scratch/subudb-rel-get.cli.h b/src/5_scratch/subudb-rel-get.cli.h new file mode 100644 index 0000000..23c41b1 --- /dev/null +++ b/src/5_scratch/subudb-rel-get.cli.h @@ -0,0 +1,9 @@ +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +#include +int subudb_Masteru_Subu_get(sqlite3 *db,char *masteru_name,char *subuname,char **subu_username); +#include +#include +#define SUBU_ERR_DB_FILE 8 +extern char DB_File[]; +#define SUBU_ERR_ARG_CNT 1 diff --git a/src/5_scratch/subudb-rel-put.cli.h b/src/5_scratch/subudb-rel-put.cli.h new file mode 100644 index 0000000..243b3a9 --- /dev/null +++ b/src/5_scratch/subudb-rel-put.cli.h @@ -0,0 +1,8 @@ +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +#include +int subudb_Masteru_Subu_put(sqlite3 *db,char *masteru_name,char *subuname,char *subu_username); +#include +#include +#define SUBU_ERR_DB_FILE 8 +extern char DB_File[]; diff --git a/src/5_scratch/subudb-rel-rm.cli.h b/src/5_scratch/subudb-rel-rm.cli.h new file mode 100644 index 0000000..595427f --- /dev/null +++ b/src/5_scratch/subudb-rel-rm.cli.h @@ -0,0 +1,8 @@ +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +#include +int subudb_Masteru_Subu_rm(sqlite3 *db,char *masteru_name,char *subuname,char *subu_username); +#include +#include +#define SUBU_ERR_DB_FILE 8 +extern char DB_File[]; diff --git a/src/5_scratch/subudb.lib.h b/src/5_scratch/subudb.lib.h new file mode 100644 index 0000000..1c854b3 --- /dev/null +++ b/src/5_scratch/subudb.lib.h @@ -0,0 +1,15 @@ +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +#include +int subudb_Masteru_Subu_rm(sqlite3 *db,char *masteru_name,char *subuname,char *subu_username); +int subudb_Masteru_Subu_get(sqlite3 *db,char *masteru_name,char *subuname,char **subu_username); +int subudb_Masteru_Subu_put(sqlite3 *db,char *masteru_name,char *subuname,char *subu_username); +int subudb_number_rm(sqlite3 *db,char *masteru_name); +int subudb_number_set(sqlite3 *db,char *masteru_name,int n); +int subudb_number_get(sqlite3 *db,char *masteru_name,int *n); +int subudb_number_init(sqlite3 *db,char *masteru_name,int n); +int subudb_schema(sqlite3 *db); +int db_rollback(sqlite3 *db); +int db_commit(sqlite3 *db); +int db_begin(sqlite3 *db); +#define INTERFACE 0 diff --git a/src/7_makefile b/src/7_makefile new file mode 100755 index 0000000..c16155b --- /dev/null +++ b/src/7_makefile @@ -0,0 +1,169 @@ + +# a single space literal, for example if you wanted to subsitute commas to +# spaces: $(subst $(space),;,$(string)) we ran into this out of a need to send +# multiple separate command arguments to a shell script from one variable value +blank := +space :=$(blank) $(blank) + +# some versions of Linux need a -e option others complain if there is a -e .. and it isn't the binary for echo .. +ECHO= echo +#ECHO= echo -e + +SHELL=/bin/bash +SCRATCHDIR= 5_scratch # clean and others put things here +CC=gcc +CFLAGS=-std=gnu11 -fPIC -I. -ggdb -Werror -DDEBUG -DDEBUGDB +#CFLAGS=-std=gnu11 -fPIC -I. -Werror +LIB=2_lib/libsubu.a +LINKFLAGS=-L2_lib -lsubu -lsqlite3 + +#these are the source files that exist +SOURCES_LIB= $(wildcard *.lib.c) +SOURCES_CLI= $(wildcard *.cli.c) +SOURCES= $(SOURCES_LIB) $(SOURCES_CLI) + +#these are the object files to be made +OBJECTS_LIB= $(patsubst %.c, %.o, $(SOURCES_LIB)) +OBJECTS_CLI= $(patsubst %.c, %.o, $(SOURCES_CLI)) +OBJECTS= $(OBJECTS_LIB) $(OBJECTS_CLI) + +#these are the header files that exist, makeheaders will want to see them +HFILES = $(wildcard *.lib.h) $(wildcard *.cli.h) + +# sort causes compiles to go in lexical order by file name, this is used to order the tests e.g. +EXECS= $(sort $(patsubst %.cli.c, %, $(wildcard *.cli.c))) + +SUID=$(realpath ../tools/bin/setuid_root.sh) + + +all: version deps lib execs + +lists: + @echo '---- make $@:------------------------------------------------------------' + @echo "SOURCES_LIB: " $(SOURCES_LIB) + @echo "SOURCES_CLI: " $(SOURCES_CLI) + @echo "SOURCES: " $(SOURCES) + @echo "OBJECTS_LIB: " $(OBJECTS_LIB) + @echo "OBJECTS_CLI: " $(OBJECTS_CLI) + @echo "OBJECTS: " $(OBJECTS) + @echo "HFILES: " $(HFILES) + @echo "EXECS: " $(EXECS) + @echo '______end make $@_____' + +version: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + @echo makefile version 2.0 + @echo "CC: " $(CC) + @echo "CFLAGS: " $(CFLAGS) + @echo "LINKFLAGS: " $(LINKFLAGS) + @echo "LIB: " $(LIB) + @echo "SUID: " $(SUID) + @echo '______end make $@_____' + +# safe to run this in an already setup or partially setup directory +setup: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + if [ ! -e $(SCRATCHDIR) ]; then mkdir $(SCRATCHDIR); fi + if [ ! -e 1_tests ]; then mkdir 1_tests; fi + if [ ! -e 1_try ]; then mkdir 1_try; fi + if [ ! -e 2_bin ]; then mkdir 2_bin; fi + if [ ! -e 2_lib ]; then mkdir 2_lib; fi + if [ ! -e 2_doc ]; then mkdir 2_doc; fi + if [ ! -e 2_include ]; then mkdir 2_include; fi + if [ ! -e 5_deprecated ]; then mkdir 5_deprecated; fi + if [ ! -e 5_scratch ]; then mkdir 5_scratch; fi + @echo '______end make $@_____' + + +deps: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + makeheaders $(SOURCES) $(HFILES) + sed -i '/^ *int *main *(.*)/d' *.h + $(CC) $(CFLAGS) -MM $(SOURCES) 1> 7_makefile_deps + for i in $(EXECS) ; do\ + $(ECHO) >> 7_makefile_deps;\ + $(ECHO) "2_bin/$$i : $$i.cli.o $(LIB)" >> 7_makefile_deps;\ + $(ECHO) " $(CC) -o 2_bin/$$i $$i.cli.o $(LINKFLAGS)" >> 7_makefile_deps;\ + done + @echo '______end make $@_____' + +lib: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + if [ ! -e 7_makefile_deps ]; then make deps; fi + make sub_lib + @echo '______end make $@_____' + +sub_lib: $(LIB) + + +execs: $(LIB) + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + if [ ! -e 7_makefile_deps ]; then make deps; fi + make sub_execs + @echo "-> $(SUID) 2_bin/subu-mk-0 2_bin/subu-rm-0" + cat $(SUID) + @echo -n "Are you sure? [y/N] " && read ans && [ $${ans:-N} == y ] + sudo $(SUID) 2_bin/subu-mk-0 2_bin/subu-rm-0 + @echo '______end make $@_____' + +sub_execs: $(patsubst %, 2_bin/%, $(EXECS)) + +#not ready yet +install: all + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + @if[ ! -e 1_tests_passed ]; then echo "can't install as tests have not passed"; fi + @test -e test_passed + for i in $(BIN); do cp $$i $(RT_BASE)/bin; done + cp $(LIB) $(RT_BASE)/lib + cp $(APPLICATION).h $(RT_BASE)/include + if [ -d $(APPLICATION) ]; then cp $(APPLICATION)/*.h $(RT_BASE)/include/$(APPLICATION); fi + @echo '______end make $@_____' + +# not written yet +# copies stuff from the src dir to the stage dirs +# stage: + +clean: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + if [ -f subudb ]; then rm subudb; fi + for i in $(wildcard *~); do mv $$i $(SCRATCHDIR); done + for i in $(wildcard *.lib.o) $(wildcard *.cli.o); do rm $$i; done + for i in $(HFILES); do mv $$i 5_scratch; done # just in case someone wrote a header file + for i in $(EXECS); do if [ -e 2_bin/$$i ]; then rm 2_bin/$$i; fi; done + if [ -f $(LIB) ]; then rm $(LIB); fi + if [ -f 7_makefile_deps ]; then rm 7_makefile_deps; fi + @echo '______end make $@_____' + + +# not ready ... +# dist_clean is used to clean thing up before doing a checkin, hg add should be safe after a dist_clean +# dist_clean will recurse into the include directory = $(APPLICATION), tests, and try if they are present +# +dist-clean: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + make clean + if [ -d $(APPLICATION) ]; then cd $(APPLICATION); make clean; fi + if [ -d 1_tests ]; then cd 1_tests; make dist_clean; fi + if [ -d 1_try ] ; then cd 1_try; make dist_clean; fi + @echo '______end make $@_____' + +# +$(LIB) : $(OBJECTS_LIB) + ar rcs $(LIB) $(OBJECTS_LIB) + +-include 7_makefile_deps + +# recipe for making object files: +# +%.o : %.c + $(CC) $(CFLAGS) -c $< + + diff --git a/src/common.lib.c b/src/common.lib.c deleted file mode 100644 index b949af4..0000000 --- a/src/common.lib.c +++ /dev/null @@ -1,20 +0,0 @@ - -#include "common.lib.h" - -#if INTERFACE -typedef unsigned int uint; -/* - Fedora 29's sss_cache is checking the inherited uid instead of the effective - uid, so setuid root scripts will fail when calling sss_cache. - - Fedora 29's 'useradd' calls sss_cache, and useradd is called by our setuid root - program subu-mk-0. -*/ -#define BUG_SSS_CACHE_RUID 1 -#endif - -// char *Config_File = "/etc/subu.db"; -char Config_File[] = "subu.db"; -uint Subuhome_Perms = 0700; -uint First_Max_Subu_number = 114; -char Subuland_Extension[] = "/subuland/"; diff --git a/src/makefile b/src/makefile deleted file mode 100755 index 0fbf5a1..0000000 --- a/src/makefile +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright 2011 (C) Reasoning Technology Ltd. All Rights Reserved -# -# 2010 11 20 TWL Created -# 2019 02 24 TWL modified for subu project and placed under MIT license - -# a single space literal, for example if you wanted to subsitute commas to -# spaces: $(subst $(space),;,$(string)) we ran into this out of a need to send -# multiple separate command arguments to a shell script from one variable value -blank := -space :=$(blank) $(blank) - -# some versions of Linux need a -e option others complain if there is a -e .. and it isn't the binary for echo .. -ECHO= echo -#ECHO= echo -e - -SHELL=/bin/bash -SCRATCHDIR= 5_scratch # clean and others put things here -CC=gcc -CFLAGS=-std=gnu11 -fPIC -I. -ggdb -Werror -DDEBUG -DDEBUGDB -#CFLAGS=-std=gnu11 -fPIC -I. -Werror -LIB="libsubu.a" -LINKFLAGS=-L. -lsubu -lsqlite3 - -#these are the source files that exist -SOURCES_LIB= $(wildcard *.lib.c) -SOURCES_CLI= $(wildcard *.cli.c) -SOURCES= $(SOURCES_LIB) $(SOURCES_CLI) - -#these are the object files to be made -OBJECTS_LIB= $(patsubst %.c, %.o, $(SOURCES_LIB)) -OBJECTS_CLI= $(patsubst %.c, %.o, $(SOURCES_CLI)) -OBJECTS= $(OBJECTS_LIB) $(OBJECTS_CLI) - -#these are the header files that exist, makeheaders will want to see them -HFILES = $(wildcard *.lib.h) $(wildcard *.cli.h) - -# sort causes compiles to go in lexical order by file name, this is used to order the tests e.g. -EXECS= $(sort $(patsubst %.cli.c, %, $(wildcard *.cli.c))) - -all: version deps libs execs - -version: - @echo '---- make $@:------------------------------------------------------------' - @echo `pwd`'>' - @echo makefile version 2.0 - @echo "CC: " $(CC) - @echo "CFLAGS: " $(CFLAGS) - @echo "LIB: " $(LIB) - @echo "LINKFLAGS: " $(LINKFLAGS) - @echo '______end make $@_____' - -# safe to run this in an already setup or partially setup directory -setup: - @echo '---- make $@:------------------------------------------------------------' - @echo `pwd`'>' - if [ ! -e $(SCRATCHDIR) ]; then mkdir $(SCRATCHDIR); fi - if [ ! -e 1_tests ]; then mkdir 1_tests; fi - if [ ! -e 1_try ]; then mkdir 1_try; fi - if [ ! -e 2_bin ]; then mkdir 2_bin; fi - if [ ! -e 3_documents ]; then mkdir 3_documents; fi - if [ ! -e 3_to_do.txt ]; then touch 3_to_do.txt; fi - if [ ! -e 5_deprecated ]; then mkdir 5_deprecated; fi - @echo '______end make $@_____' - - -deps: - @echo '---- make $@:------------------------------------------------------------' - @echo `pwd`'>' - makeheaders $(SOURCES) $(HFILES) - sed -i '/^ *int *main *(.*)/d' *.h - $(CC) $(CFLAGS) -MM $(SOURCES) 1> 2_makefile_deps - for i in $(EXECS) ; do\ - $(ECHO) >> 2_makefile_deps;\ - $(ECHO) "$$i : $$i.cli.o $(LIB)" >> 2_makefile_deps;\ - $(ECHO) " $(CXX) -o $$i $$i.cli.o $(LINKFLAGS)" >> 2_makefile_deps;\ - done - @echo '______end make $@_____' - -libs: - @echo '---- make $@:------------------------------------------------------------' - @echo `pwd`'>' - if [ ! -e 2_makefile_deps ]; then make deps; fi - make sub_libs - @echo '______end make $@_____' - -sub_libs: $(LIB) - - -execs: $(LIB) - @echo '---- make $@:------------------------------------------------------------' - @echo `pwd`'>' - if [ ! -e 2_makefile_deps ]; then make deps; fi - make sub_execs - @echo '______end make $@_____' - -sub_execs: $(EXECS) - -#not ready yet -install: all - @echo '---- make $@:------------------------------------------------------------' - @echo `pwd`'>' - @if[ ! -e 1_tests_passed ]; then echo "can't install as tests have not passed"; fi - @test -e test_passed - for i in $(BIN); do cp $$i $(RT_BASE)/bin; done - cp $(LIB) $(RT_BASE)/lib - cp $(APPLICATION).h $(RT_BASE)/include - if [ -d $(APPLICATION) ]; then cp $(APPLICATION)/*.h $(RT_BASE)/include/$(APPLICATION); fi - @echo '______end make $@_____' - -clean: - @echo '---- make $@:------------------------------------------------------------' - @echo `pwd`'>' - if [ -f subu.db ]; then rm subu.db; fi - for i in $(wildcard *~); do mv $$i $(SCRATCHDIR); done - for i in $(wildcard *.lib.o) $(wildcard *.cli.o); do rm $$i; done - for i in $(HFILES); do mv $$i 5_scratch; done # just in case someone wrote a header file - if [ -f 2_makefile_deps ]; then rm 2_makefile_deps; fi - if [ -f $(LIB) ]; then rm $(LIB); fi - for i in $(EXECS); do if [ -e $$i ]; then rm $$i; fi; done - @echo '______end make $@_____' - - -# not ready ... -# dist_clean is used to clean thing up before doing a checkin, hg add should be safe after a dist_clean -# dist_clean will recurse into the include directory = $(APPLICATION), tests, and try if they are present -# -dist_clean: - @echo '---- make $@:------------------------------------------------------------' - @echo `pwd`'>' - make clean - if [ -d $(APPLICATION) ]; then cd $(APPLICATION); make clean; fi - if [ -d 1_tests ]; then cd 1_tests; make clean; fi - if [ -d 1_try ] ; then cd 1_try; make clean; fi - @echo '______end make $@_____' - - --include 2_makefile_deps - -# recipe for making object files: -# -%.o : %.c - $(CC) $(CFLAGS) -c $< - - -# -$(LIB) : $(OBJECTS_LIB) - ar rcs $(LIB) $(OBJECTS_LIB) diff --git a/src/subu-common.lib.c b/src/subu-common.lib.c new file mode 100644 index 0000000..9ce5a27 --- /dev/null +++ b/src/subu-common.lib.c @@ -0,0 +1,20 @@ + +#include "subu-common.lib.h" + +#if INTERFACE +typedef unsigned int uint; +/* + Fedora 29's sss_cache is checking the inherited uid instead of the effective + uid, so setuid root scripts will fail when calling sss_cache. + + Fedora 29's 'useradd' calls sss_cache, and useradd is called by our setuid root + program subu-mk-0. +*/ +#define BUG_SSS_CACHE_RUID 1 +#endif + +// char *DB_File = "/etc/subudb"; +char DB_File[] = "subudb"; +uint Subuhome_Perms = 0700; +uint First_Max_Subunumber = 114; +char Subuland_Extension[] = "/subuland/"; diff --git a/src/subu-config.lib.c b/src/subu-config.lib.c deleted file mode 100644 index de7bcbb..0000000 --- a/src/subu-config.lib.c +++ /dev/null @@ -1,147 +0,0 @@ -/* -The config file is maintained in SQLite - -Because user names of are of limited length, subu user names are always named _s. -A separate table translates the numbers into the subu names. - -The first argument is the biggest subu number in the system, or one minus an -starting point for subu numbering. - -currently a unit converted to base 10 will always fit in a 21 bit buffer. - -*/ -#include "subu-config.lib.h" - -#if INTERFACE -#include -#define ERR_CONFIG_FILE 1 -#endif - -#include -#include -#include - -//-------------------------------------------------------------------------------- -int subudb_schema(sqlite3 *db, uint max_subu_number){ - char max_subu_number_string[32]; - uint max_subu_number_string_len = snprintf(max_subu_number_string, 32, "%u", max_subu_number); - if( max_subu_number_string_len >= 32 ){ - fprintf(stderr, "error exit, max_subu_number too big to deal with\n"); - return ERR_CONFIG_FILE; - } - char sql1[] = "CREATE TABLE Masteru_Subu(masteru_name TEXT, subuname TEXT, subu_username TEXT); "; - char sql2[] = "CREATE TABLE Key_Int(key TEXT, value INT); "; - - char sql3_1[] = "INSERT INTO Key_Int VALUES( 'max_subu_number', "; - char sql3_2[] = " ); "; - char sql3_len = strlen(sql3_1) + max_subu_number_string_len + strlen(sql3_2) + 1; - char sql3[sql3_len]; - strcpy(sql3, sql3_1); - strcpy(sql3 + strlen(sql3_1), max_subu_number_string); - strcpy(sql3 + strlen(sql3_1) + max_subu_number_string_len, sql3_2); - - char sql[strlen(sql1) + strlen(sql2) + strlen(sql3) + 1]; - strcpy(sql, sql1); - strcpy(sql + strlen(sql1), sql2); - strcpy(sql + strlen(sql1) + strlen(sql2), sql3); - - return sqlite3_exec(db, sql, NULL, NULL, NULL); -} - -//-------------------------------------------------------------------------------- - -// the call back for subu_number_next, note also 3_doc/sqlite3.txt -static int subu_number_extract(void *nsp, int colcnt, char **colvals, char **colnames){ - if(colcnt >= 1){ - *(char **)nsp = strdup( colvals[0] ); - return 0; - } - return -1; -} -int subu_number_next(sqlite3 *db, char **nsp, char **mess){ - char *sql = - "BEGIN TRANSACTION;" - "UPDATE Key_Int SET value = value + 1 WHERE key = 'max_subu_number';" - "SELECT value FROM Key_Int WHERE key = 'max_subu_number';" - "COMMIT;"; - int rc = sqlite3_exec(db, sql, subu_number_extract, (void *)nsp, mess); - return rc; -} -int subu_number_get(sqlite3 *db, int *n){ - char *sql = "SELECT value FROM Key_Int WHERE key = 'max_subu_number';"; - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - int rc = sqlite3_step(stmt); - if( rc == SQLITE_ROW ){ - *n = sqlite3_column_int(stmt,0); - }else{ - sqlite3_finalize(stmt); - return rc; // woops this needs to return an error!, be sure it is not SQLITE_DONE - } - rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - return rc; -} -int subu_number_set(sqlite3 *db, int n){ - char *sql = "UPDATE Key_Int SET value = ?1 WHERE key = 'max_subu_number';"; - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, n); - int rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - return rc; -} - - -//-------------------------------------------------------------------------------- -// put relation into Masteru_Subu table -int subu_Masteru_Subu_put(sqlite3 *db, char *masteru_name, char *subuname, char *subu_username){ - char *sql = "INSERT INTO Masteru_Subu VALUES (?1, ?2, ?3);"; - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, subuname, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, subu_username, -1, SQLITE_STATIC); - int rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - return rc; -} - -//-------------------------------------------------------------------------------- -int subu_Masteru_Subu_get(sqlite3 *db, char *masteru_name, char *subuname, char **subu_username){ - char *sql = "SELECT subu_username FROM Masteru_Subu WHERE masteru_name = ?1 AND subuname = ?2;"; - size_t sql_len = strlen(sql); - sqlite3_stmt *stmt; - int rc; - rc = sqlite3_prepare_v2(db, sql, sql_len, &stmt, NULL); - if( rc != SQLITE_OK ) return rc; - sqlite3_bind_text(stmt, 1, masteru_name, strlen(masteru_name), SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, subuname, strlen(subuname), SQLITE_STATIC); - rc = sqlite3_step(stmt); - if( rc == SQLITE_ROW ){ - const char *username = sqlite3_column_text(stmt, 0); - *subu_username = strdup(username); - }else{ - sqlite3_finalize(stmt); - return rc; // woops this needs to return an error!, be sure it is not SQLITE_DONE - } - rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - return rc; -} - -//-------------------------------------------------------------------------------- -int subu_Masteru_Subu_rm(sqlite3 *db, char *masteru_name, char *subuname, char *subu_username){ - char *sql = "DELETE FROM Masteru_Subu WHERE masteru_name = ?1 AND subuname = ?2 AND subu_username = ?3;"; - size_t sql_len = strlen(sql); - sqlite3_stmt *stmt; - int rc; - rc = sqlite3_prepare_v2(db, sql, sql_len, &stmt, NULL); - if( rc != SQLITE_OK ) return rc; - sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, subuname, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, subu_username, -1, SQLITE_STATIC); - rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - return rc; -} diff --git a/src/subu-init.cli.c b/src/subu-init.cli.c deleted file mode 100644 index 1dcd7b1..0000000 --- a/src/subu-init.cli.c +++ /dev/null @@ -1,23 +0,0 @@ -/* -This command initializes the configuration file. - -*/ -#include "subu-init.cli.h" -#include - -int main(){ - sqlite3 *db; - if( sqlite3_open(Config_File, &db) != SQLITE_OK ){ - fprintf(stderr, "error exit, could not open configuration file \"%s\"\n", Config_File); - return ERR_CONFIG_FILE; - } - if( subudb_schema(db, First_Max_Subu_number) != SQLITE_OK ){ - fprintf(stderr, "error exit, opened config file but could not build scheme\n"); - return ERR_CONFIG_FILE; - } - if( sqlite3_close(db) != SQLITE_OK ){ - fprintf(stderr, "error exit, could not close the db\n"); - return ERR_CONFIG_FILE; - } - return 0; -} diff --git a/src/subu-mk-0.cli.c b/src/subu-mk-0.cli.c index 93c79d3..d9f9c74 100644 --- a/src/subu-mk-0.cli.c +++ b/src/subu-mk-0.cli.c @@ -14,19 +14,29 @@ int main(int argc, char **argv){ } char *subuname = argv[1]; + int rc; sqlite3 *db; - { - int ret = sqlite3_open_v2(Config_File, &db, SQLITE_OPEN_READWRITE, NULL); - if( ret != SQLITE_OK ){ - fprintf(stderr, "error exit, could not open configuration file \"%s\"\n", Config_File); - return SUBU_ERR_CONFIG_FILE; - }} + rc = sqlite3_open_v2(DB_File, &db, SQLITE_OPEN_READWRITE, NULL); + if( rc != SQLITE_OK ){ + fprintf(stderr, "error exit, could not open db file\n"); + sqlite3_close(db); + return SUBU_ERR_DB_FILE; + } - { - char *mess; - int ret = subu_mk_0(&mess, db, subuname); - subu_err("subu_mk_0", ret, mess); + char *mess; + rc = subu_mk_0(&mess, db, subuname); + if( rc ){ + subu_err("subu_mk_0", rc, mess); free(mess); - return ret; + sqlite3_close(db); + return rc; } + + rc = sqlite3_close(db); + if( rc != SQLITE_OK ){ + fprintf(stderr, "when closing db, %s\n", sqlite3_errmsg(db)); + return SUBU_ERR_DB_FILE; + } + return 0; + } diff --git a/src/subu-number.cli.c b/src/subu-number.cli.c deleted file mode 100644 index c4690e4..0000000 --- a/src/subu-number.cli.c +++ /dev/null @@ -1,59 +0,0 @@ -/* -Set or get a new maximum subu number. Currently doesn't do the setting part. - -*/ -#include "subu-number.cli.h" -#include -#include -#include - -int main(int argc, char **argv){ - - if( argc > 2 ){ - fprintf(stderr, "usage: %s [n]\n",argv[0]); - return SUBU_ERR_ARG_CNT; - } - - int rc; - sqlite3 *db; - rc = sqlite3_open_v2(Config_File, &db, SQLITE_OPEN_READWRITE, NULL); - if( rc != SQLITE_OK ){ - sqlite3_close(db); - fprintf(stderr, "error exit, could not open configuration file\n"); - return SUBU_ERR_CONFIG_FILE; - } - - // then arg[1] holds a number to set the max to - if(argc == 2){ - long int i = strtol(argv[1], NULL, 10); - if( i < 0 ){ - fprintf(stderr, "n must be positive\n"); - sqlite3_close(db); - return SUBU_ERR_N; - } - if( i > INT_MAX ){ - fprintf(stderr, "n is too big, max supported by this program is %d\n", INT_MAX); - sqlite3_close(db); - return SUBU_ERR_N; - } - int n = i; - subu_number_set(db, n); - } - - // read and print the current max - int n; - rc = subu_number_get(db, &n); - if( rc == SQLITE_DONE ){ - printf("%d\n", n); - }else{ - sqlite3_close(db); - return SUBU_ERR_CONFIG_FILE; - } - rc = sqlite3_close(db); - if( rc != SQLITE_OK ){ - fprintf(stderr, "when closing db, %s\n", sqlite3_errmsg(db)); - return SUBU_ERR_CONFIG_FILE; - } - return 0; - -} diff --git a/src/subu-rel-get.cli.c b/src/subu-rel-get.cli.c deleted file mode 100644 index 737080c..0000000 --- a/src/subu-rel-get.cli.c +++ /dev/null @@ -1,42 +0,0 @@ -/* -get the username from the config file -for testing subu_Masteru_Subu_get_user - -*/ -#include "subu-rel-get.cli.h" -#include -#include - -int main(int argc, char **argv){ - - if(argc != 3){ - fprintf(stderr, "usage: %s masteru_name subuname\n", argv[0]); - return SUBU_ERR_ARG_CNT; - } - - int rc; - sqlite3 *db; - rc = sqlite3_open_v2(Config_File, &db, SQLITE_OPEN_READWRITE, NULL); - if( rc != SQLITE_OK ){ - fprintf(stderr, "could not open configuration file \"%s\"\n", Config_File); - return SUBU_ERR_CONFIG_FILE; - } - - char *masteru_name = argv[1]; - char *subuname = argv[2]; - char *subu_username; - - int ret = subu_Masteru_Subu_get(db, masteru_name, subuname, &subu_username); - if( ret != SQLITE_DONE ){ - fprintf(stderr, "subu_Masteru_Subu_get indicates failure by returning %d\n",ret); - fprintf(stderr, "sqlite3 issues message, %s\n", sqlite3_errmsg(db)); - return SUBU_ERR_CONFIG_FILE; - } - ret = sqlite3_close(db) - if( ret != SQLITE_OK ){ - fprintf(stderr, "sqlite3_close(db) indicates failure by returning %d\n",ret); - fprintf(stderr, "sqlite3 issues message: %s\n", sqlite3_errmsg(db)); - return SUBU_ERR_CONFIG_FILE; - } - return 0; -} diff --git a/src/subu-rel-put.cli.c b/src/subu-rel-put.cli.c deleted file mode 100644 index c930465..0000000 --- a/src/subu-rel-put.cli.c +++ /dev/null @@ -1,41 +0,0 @@ -/* -puts a relation in the masteru/subu table - -*/ -#include "subu-rel-put.cli.h" -#include -#include - -int main(int argc, char **argv){ - - if(argc != 4){ - fprintf(stderr, "expected: %s masteru_name subuname subu_username\n", argv[0]); - return 1; - } - char *masteru_name = argv[1]; - char *subuname = argv[2]; - char *subu_username = argv[3]; - - sqlite3 *db; - { - int ret = sqlite3_open_v2(Config_File, &db, SQLITE_OPEN_READWRITE, NULL); - if( ret != SQLITE_OK ){ - fprintf(stderr, "could not open configuration file \"%s\"\n", Config_File); - return SUBU_ERR_CONFIG_FILE; - }} - - int ret = subu_Masteru_Subu_put(db, masteru_name, subuname, subu_username); - if( ret != SQLITE_DONE ){ - fprintf(stderr, "subu_Masteru_Subu_put indicates failure by returning %d\n",ret); - fprintf(stderr, "sqlite3 issues message, %s\n", sqlite3_errmsg(db)); - printf("put failed\n"); - return 2; - } - ret = sqlite3_close(db) - if( ret != SQLITE_OK ){ - fprintf(stderr, "sqlite3_close(db) indicates failure by returning %d\n",ret); - fprintf(stderr, "sqlite3 issues message: %s\n", sqlite3_errmsg(db)); - return SUBU_ERR_CONFIG_FILE; - } - return 0; -} diff --git a/src/subu-rel-rm.cli.c b/src/subu-rel-rm.cli.c deleted file mode 100644 index 06eb4e5..0000000 --- a/src/subu-rel-rm.cli.c +++ /dev/null @@ -1,41 +0,0 @@ -/* -puts a relation in the masteru/subu table - -*/ -#include "subu-rel-rm.cli.h" -#include -#include - -int main(int argc, char **argv){ - - if(argc != 4){ - fprintf(stderr, "expected: %s masteru_name subuname subu_username\n", argv[0]); - return 1; - } - char *masteru_name = argv[1]; - char *subuname = argv[2]; - char *subu_username = argv[3]; - - sqlite3 *db; - { - int ret = sqlite3_open_v2(Config_File, &db, SQLITE_OPEN_READWRITE, NULL); - if( ret != SQLITE_OK ){ - fprintf(stderr, "could not open configuration file \"%s\"\n", Config_File); - return SUBU_ERR_CONFIG_FILE; - }} - - int ret = subu_Masteru_Subu_rm(db, masteru_name, subuname, subu_username); - if( ret != SQLITE_DONE ){ - fprintf(stderr, "subu_Masteru_Subu_rm indicates failure by returning %d\n",ret); - fprintf(stderr, "sqlite3 issues message, %s\n", sqlite3_errmsg(db)); - printf("put failed\n"); - return 2; - } - ret = sqlite3_close(db) - if( ret != SQLITE_OK ){ - fprintf(stderr, "sqlite3_close(db) indicates failure by returning %d\n",ret); - fprintf(stderr, "sqlite3 issues message: %s\n", sqlite3_errmsg(db)); - return SUBU_ERR_CONFIG_FILE; - } - return 0; -} diff --git a/src/subu-rm-0.cli.c b/src/subu-rm-0.cli.c index 93c79d3..a7e5926 100644 --- a/src/subu-rm-0.cli.c +++ b/src/subu-rm-0.cli.c @@ -2,7 +2,7 @@ subu-mk-0 command */ -#include "subu-mk-0.cli.h" +#include "subu-rm-0.cli.h" #include #include @@ -16,16 +16,16 @@ int main(int argc, char **argv){ sqlite3 *db; { - int ret = sqlite3_open_v2(Config_File, &db, SQLITE_OPEN_READWRITE, NULL); + int ret = sqlite3_open_v2(DB_File, &db, SQLITE_OPEN_READWRITE, NULL); if( ret != SQLITE_OK ){ - fprintf(stderr, "error exit, could not open configuration file \"%s\"\n", Config_File); - return SUBU_ERR_CONFIG_FILE; + fprintf(stderr, "error exit, could not open db file \"%s\"\n", DB_File); + return SUBU_ERR_DB_FILE; }} { - char *mess; - int ret = subu_mk_0(&mess, db, subuname); - subu_err("subu_mk_0", ret, mess); + char *mess=0; + int ret = subu_rm_0(&mess, db, subuname); + subu_err("subu_rm_0", ret, mess); free(mess); return ret; } diff --git a/src/subu.lib.c b/src/subu.lib.c index e20fac3..d456c53 100644 --- a/src/subu.lib.c +++ b/src/subu.lib.c @@ -1,5 +1,5 @@ /* - sqllite3 is used to maintain the configuration file, which is currently compiled + sqllite3 is used to maintain the db file, which is currently compiled in as /etc/subu.db, (or just subu.db for testing). masteru is the user who ran this script. The masteru name comes from getuid @@ -9,11 +9,11 @@ subu-mk-0 synthesizes a new user user name s, calls useradd to creat the new uswer account, and enters the relationship between masteru, subu, and - s in the config file. It is this relation in the config file that + s in the db file. It is this relation in the db file that associates the subuname with the s user. subu-rm-0 uses userdel to delete the related s user account. It - then removes the relaton from the config file. + then removes the relaton from the db file. subu-mk-0 and subu-rm-0 are setuid root scripts. @@ -86,7 +86,6 @@ char *userdel_mess(int err){ //-------------------------------------------------------------------------------- -// an instance is subu_mk_0_ctx is returned by subu_mk_0 // #if INTERFACE #define SUBU_ERR_ARG_CNT 1 @@ -96,7 +95,7 @@ char *userdel_mess(int err){ #define SUBU_ERR_RMDIR_SUBUHOME 5 #define SUBU_ERR_SUBUNAME_MALFORMED 6 #define SUBU_ERR_MASTERU_HOMELESS 7 -#define SUBU_ERR_CONFIG_FILE 8 +#define SUBU_ERR_DB_FILE 8 #define SUBU_ERR_SUBUHOME_EXISTS 9 #define SUBU_ERR_BUG_SSS 10 #define SUBU_ERR_FAILED_USERADD 11 @@ -121,8 +120,8 @@ void subu_err(char *fname, int err, char *mess){ case SUBU_ERR_MALLOC: perror(fname); break; - case SUBU_ERR_CONFIG_FILE: - fprintf(stderr, "config file error: %s", Config_File); // Config_File is in common + case SUBU_ERR_DB_FILE: + fprintf(stderr, "db file error: %s", DB_File); // DB_File is in common break; case SUBU_ERR_MASTERU_HOMELESS: fprintf(stderr,"Masteru, \"%s\", has no home directory", mess); @@ -209,14 +208,11 @@ static int masteru_rmdir_subuhome(void *arg){ //-------------------------------------------------------------------------------- // build strings // -static int mk_subu_user(sqlite3 *db, char **mess, char **subu_username){ - char *ns=0; // 'ns' Number as String - if( subu_number_next( db, &ns, mess ) != SQLITE_OK ) return SUBU_ERR_CONFIG_FILE; - size_t ns_len = strlen(ns); - *subu_username = malloc(1 + ns_len + 1); // the first 1 is for the "s" prefix - if( !*subu_username ) SUBU_ERR_MALLOC; - strcpy(*subu_username, "s"); - strcpy(*subu_username + 1, ns); +static int mk_subu_user(char **mess, sqlite3 *db, char *masteru_name, int n, char **subu_username){ + size_t len = 0; + FILE* name_stream = open_memstream(subu_username, &len); + fprintf(name_stream, "s%x", n); + fclose(name_stream); return 0; } @@ -264,21 +260,18 @@ static int mk_subuhome(char *subuland, char *subuname, char **subuhome){ //=============================================================================== int subu_mk_0(char **mess, sqlite3 *db, char *subuname){ - int ret; + int rc; if(mess)*mess = 0; //-------------------------------------------------------------------------------- + size_t subuname_len; + rc = allowed_subuname(mess, subuname, &subuname_len); + if(rc) return rc; #ifdef DEBUG - dbprintf("Check that subuname is well formed and find its length\n"); + dbprintf("subuname is well formed\n"); #endif - size_t subuname_len; - ret = allowed_subuname(mess, subuname, &subuname_len); - if(ret) return ret; //-------------------------------------------------------------------------------- - #ifdef DEBUG - dbprintf("Check that we are running from a user and are setuid root.\n"); - #endif uid_t masteru_uid; gid_t masteru_gid; uid_t set_euid; @@ -295,30 +288,59 @@ int subu_mk_0(char **mess, sqlite3 *db, char *subuname){ } //-------------------------------------------------------------------------------- - // various strings that we will need - char *subu_username = 0; + // lookup the masteru name char *masteru_name = 0; char *masteru_home = 0; + rc = mk_masteru_name(masteru_uid, &masteru_name, &masteru_home); + if(rc) return rc; + #ifdef DEBUG + dbprintf("masteru_name: \"%s\"\n", masteru_name); + #endif + + db_begin(db); + int n; + rc = subudb_number_get(db, masteru_name, &n); + if( rc == SQLITE_OK ){ + n++; + rc = subudb_number_set(db, masteru_name, n); + if( rc != SQLITE_OK ){ + db_rollback(db); + return SUBU_ERR_DB_FILE; + } + }else{ // perhaps this masteru's first subu, so we will try init + n = First_Max_Subunumber; + rc = subudb_number_init(db, masteru_name, n); + if( rc != SQLITE_OK ){ + db_rollback(db); + return SUBU_ERR_DB_FILE; + } + } + db_commit(db); + #ifdef DEBUG + dbprintf("masteru max subunumber: %d\n", n); + #endif + + //-------------------------------------------------------------------------------- + // subu details + char *subu_username = 0; char *subuland = 0; char *subuhome = 0; // the name of the directory to put in subuland, not subu_user home dir - ret = - mk_subu_user(db, mess, &subu_username) - || - mk_masteru_name(masteru_uid, &masteru_name, &masteru_home) + rc = + mk_subu_user(mess, db, masteru_name, n, &subu_username) || mk_subuland(masteru_home, &subuland) || mk_subuhome(subuland, subuname, &subuhome) ; - if(ret) RETURN(ret); + if(rc) RETURN(rc); + #ifdef DEBUG + dbprintf("subu_username, subuland, subuhome: \"%s\"\"%s\"\"%s\"\n", subu_username, subuland, subuhome); + #endif //-------------------------------------------------------------------------------- // By having masteru create the subuhome, we know that masteru has rights to // to access this directory. This will be the mount point for bindfs { - #ifdef DEBUG - dbprintf("as masteru, making the directory \"%s\"\n", subuhome); - #endif struct stat st; if( stat(subuhome, &st) != -1 ){ if(mess)*mess = strdup(subuhome); @@ -341,7 +363,7 @@ int subu_mk_0(char **mess, sqlite3 *db, char *subuname){ } } #ifdef DEBUG - dbprintf("masteru made directory \"%s\"\n", subuhome); + dbprintf("made directory \"%s\"\n", subuhome); #endif //-------------------------------------------------------------------------------- @@ -393,10 +415,10 @@ int subu_mk_0(char **mess, sqlite3 *db, char *subuname){ dbprintf("setting the masteru_name, subuname, subu_username relation\n"); #endif { - int ret = subu_Masteru_Subu_put(db, masteru_name, subuname, subu_username); - if( ret != SQLITE_DONE ){ + int rc = subudb_Masteru_Subu_put(db, masteru_name, subuname, subu_username); + if( rc != SQLITE_DONE ){ if(mess)*mess = strdup("insert of masteru subu relation failed"); - RETURN(SUBU_ERR_CONFIG_FILE); + RETURN(SUBU_ERR_DB_FILE); } } #ifdef DEBUG @@ -408,7 +430,7 @@ int subu_mk_0(char **mess, sqlite3 *db, char *subuname){ //================================================================================ int subu_rm_0(char **mess, sqlite3 *db, char *subuname){ - int ret; + int rc; if(mess)*mess = 0; //-------------------------------------------------------------------------------- @@ -416,8 +438,8 @@ int subu_rm_0(char **mess, sqlite3 *db, char *subuname){ dbprintf("Check that subuname is well formed and find its length\n"); #endif size_t subuname_len; - ret = allowed_subuname(mess, subuname, &subuname_len); - if(ret) return ret; + rc = allowed_subuname(mess, subuname, &subuname_len); + if(rc) return rc; //-------------------------------------------------------------------------------- #ifdef DEBUG @@ -440,44 +462,47 @@ int subu_rm_0(char **mess, sqlite3 *db, char *subuname){ //-------------------------------------------------------------------------------- // various strings that we will need + #ifdef DEBUG + dbprintf("building strings.\n"); + #endif char *subu_username = 0; char *masteru_name = 0; char *masteru_home = 0; char *subuland = 0; char *subuhome = 0; // the name of the directory to put in subuland, not subu_user home dir - ret = + rc = mk_masteru_name(masteru_uid, &masteru_name, &masteru_home) || mk_subuland(masteru_home, &subuland) || mk_subuhome(subuland, subuname, &subuhome) ; - if(ret) RETURN(ret); + if(rc) RETURN(rc); #ifdef DEBUG dbprintf("looking up subu_username given masteru_name/subuname\n"); #endif { - int sgret = subu_Masteru_Subu_get(db, masteru_name, subuname, &subu_username); + int sgret = subudb_Masteru_Subu_get(db, masteru_name, subuname, &subu_username); if( sgret != SQLITE_DONE ){ - if(mess) *mess = strdup("subu requested for removal not found under this masteru in config file"); - ret = SUBU_ERR_CONFIG_SUBU_NOT_FOUND; - RETURN(ret); + if(mess) *mess = strdup("subu requested for removal not found under this masteru in db file"); + rc = SUBU_ERR_CONFIG_SUBU_NOT_FOUND; + RETURN(rc); } - #ifdef DEBUG - printf("subu_username: %s\n", subu_username); - #endif } + #ifdef DEBUG + printf("subu_username: %s\n", subu_username); + #endif //-------------------------------------------------------------------------------- #ifdef DEBUG dbprintf("remove the masteru_name, subuname, subu_username relation\n"); #endif { - int ret = subu_Masteru_Subu_rm(db, masteru_name, subuname, subu_username); - if( ret != SQLITE_DONE ){ + int rc = subudb_Masteru_Subu_rm(db, masteru_name, subuname, subu_username); + if( rc != SQLITE_DONE ){ if(mess)*mess = strdup("removal of masteru subu relation failed"); - RETURN(SUBU_ERR_CONFIG_FILE); + RETURN(SUBU_ERR_DB_FILE); } } @@ -517,8 +542,8 @@ int subu_rm_0(char **mess, sqlite3 *db, char *subuname){ dbprintf("setting inherited real uid to 0 to accomodate SSS_CACHE UID BUG\n"); #endif if( setuid(0) == -1 ){ - ret = SUBU_ERR_BUG_SSS; - RETURN(ret); + rc = SUBU_ERR_BUG_SSS; + RETURN(rc); } #endif char *command = "/usr/sbin/userdel"; diff --git a/src/subudb-init.cli.c b/src/subudb-init.cli.c new file mode 100644 index 0000000..429c98e --- /dev/null +++ b/src/subudb-init.cli.c @@ -0,0 +1,23 @@ +/* +This command initializes the db file. + +*/ +#include "subudb-init.cli.h" +#include + +int main(){ + sqlite3 *db; + if( sqlite3_open(DB_File, &db) != SQLITE_OK ){ + fprintf(stderr, "error exit, could not open db file \"%s\"\n", DB_File); + return SUBU_ERR_DB_FILE; + } + if( subudb_schema(db) != SQLITE_OK ){ + fprintf(stderr, "error exit, opened db file but could not build schema\n"); + return SUBU_ERR_DB_FILE; + } + if( sqlite3_close(db) != SQLITE_OK ){ + fprintf(stderr, "error exit, could not close the db\n"); + return SUBU_ERR_DB_FILE; + } + return 0; +} diff --git a/src/subudb-number.cli.c b/src/subudb-number.cli.c new file mode 100644 index 0000000..265e7e9 --- /dev/null +++ b/src/subudb-number.cli.c @@ -0,0 +1,61 @@ +/* +Set or get a new maximum subu number. Currently doesn't do the setting part. + +*/ +#include "subudb-number.cli.h" +#include +#include +#include + +int main(int argc, char **argv){ + + if( argc < 2 || argc > 3){ + fprintf(stderr, "usage: %s masteru_name [n]\n",argv[0]); + return SUBU_ERR_ARG_CNT; + } + char *masteru_name = argv[1]; + + int rc; + sqlite3 *db; + rc = sqlite3_open_v2(DB_File, &db, SQLITE_OPEN_READWRITE, NULL); + if( rc != SQLITE_OK ){ + fprintf(stderr, "error exit, could not open db file\n"); + sqlite3_close(db); + return SUBU_ERR_DB_FILE; + } + + // then arg[2] holds a number to set the max to + if(argc == 3){ + long int i = strtol(argv[2], NULL, 10); + if( i < 0 ){ + fprintf(stderr, "n must be positive\n"); + sqlite3_close(db); + return SUBU_ERR_N; + } + if( i > INT_MAX ){ + fprintf(stderr, "n is too big, max supported by this program is %d\n", INT_MAX); + sqlite3_close(db); + return SUBU_ERR_N; + } + int n = i; + subudb_number_set(db, masteru_name, n); + } + + // read and print the current max + int n; + rc = subudb_number_get(db, masteru_name, &n); + if( rc == SQLITE_DONE ){ + printf("%d\n", n); + }else{ + fprintf(stderr, "lookup failed %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + return SUBU_ERR_DB_FILE; + } + rc = sqlite3_close(db); + if( rc != SQLITE_OK ){ + fprintf(stderr, "when closing db, %s\n", sqlite3_errmsg(db)); + return SUBU_ERR_DB_FILE; + } + return 0; + +} diff --git a/src/subudb-rel-get.cli.c b/src/subudb-rel-get.cli.c new file mode 100644 index 0000000..442d61e --- /dev/null +++ b/src/subudb-rel-get.cli.c @@ -0,0 +1,42 @@ +/* +get the username from the db file +for testing subudb_Masteru_Subu_get_user + +*/ +#include "subudb-rel-get.cli.h" +#include +#include + +int main(int argc, char **argv){ + + if(argc != 3){ + fprintf(stderr, "usage: %s masteru_name subuname\n", argv[0]); + return SUBU_ERR_ARG_CNT; + } + + int rc; + sqlite3 *db; + rc = sqlite3_open_v2(DB_File, &db, SQLITE_OPEN_READWRITE, NULL); + if( rc != SQLITE_OK ){ + fprintf(stderr, "could not open db file \"%s\"\n", DB_File); + return SUBU_ERR_DB_FILE; + } + + char *masteru_name = argv[1]; + char *subuname = argv[2]; + char *subu_username; + + int ret = subudb_Masteru_Subu_get(db, masteru_name, subuname, &subu_username); + if( ret != SQLITE_DONE ){ + fprintf(stderr, "subudb_Masteru_Subu_get indicates failure by returning %d\n",ret); + fprintf(stderr, "sqlite3 issues message, %s\n", sqlite3_errmsg(db)); + return SUBU_ERR_DB_FILE; + } + ret = sqlite3_close(db); + if( ret != SQLITE_OK ){ + fprintf(stderr, "sqlite3_close(db) indicates failure by returning %d\n",ret); + fprintf(stderr, "sqlite3 issues message: %s\n", sqlite3_errmsg(db)); + return SUBU_ERR_DB_FILE; + } + return 0; +} diff --git a/src/subudb-rel-put.cli.c b/src/subudb-rel-put.cli.c new file mode 100644 index 0000000..f679d24 --- /dev/null +++ b/src/subudb-rel-put.cli.c @@ -0,0 +1,41 @@ +/* +puts a relation in the masteru/subu table + +*/ +#include "subudb-rel-put.cli.h" +#include +#include + +int main(int argc, char **argv){ + + if(argc != 4){ + fprintf(stderr, "expected: %s masteru_name subuname subu_username\n", argv[0]); + return 1; + } + char *masteru_name = argv[1]; + char *subuname = argv[2]; + char *subu_username = argv[3]; + + sqlite3 *db; + { + int ret = sqlite3_open_v2(DB_File, &db, SQLITE_OPEN_READWRITE, NULL); + if( ret != SQLITE_OK ){ + fprintf(stderr, "could not open db file \"%s\"\n", DB_File); + return SUBU_ERR_DB_FILE; + }} + + int ret = subudb_Masteru_Subu_put(db, masteru_name, subuname, subu_username); + if( ret != SQLITE_DONE ){ + fprintf(stderr, "subudb_Masteru_Subu_put indicates failure by returning %d\n",ret); + fprintf(stderr, "sqlite3 issues message, %s\n", sqlite3_errmsg(db)); + printf("put failed\n"); + return SUBU_ERR_DB_FILE; + } + ret = sqlite3_close(db); + if( ret != SQLITE_OK ){ + fprintf(stderr, "sqlite3_close(db) indicates failure by returning %d\n",ret); + fprintf(stderr, "sqlite3 issues message: %s\n", sqlite3_errmsg(db)); + return SUBU_ERR_DB_FILE; + } + return 0; +} diff --git a/src/subudb-rel-rm.cli.c b/src/subudb-rel-rm.cli.c new file mode 100644 index 0000000..3d15ca9 --- /dev/null +++ b/src/subudb-rel-rm.cli.c @@ -0,0 +1,41 @@ +/* +puts a relation in the masteru/subu table + +*/ +#include "subudb-rel-rm.cli.h" +#include +#include + +int main(int argc, char **argv){ + + if(argc != 4){ + fprintf(stderr, "expected: %s masteru_name subuname subu_username\n", argv[0]); + return 1; + } + char *masteru_name = argv[1]; + char *subuname = argv[2]; + char *subu_username = argv[3]; + + sqlite3 *db; + { + int ret = sqlite3_open_v2(DB_File, &db, SQLITE_OPEN_READWRITE, NULL); + if( ret != SQLITE_OK ){ + fprintf(stderr, "could not open db file \"%s\"\n", DB_File); + return SUBU_ERR_DB_FILE; + }} + + int ret = subudb_Masteru_Subu_rm(db, masteru_name, subuname, subu_username); + if( ret != SQLITE_DONE ){ + fprintf(stderr, "subudb_Masteru_Subu_rm indicates failure by returning %d\n",ret); + fprintf(stderr, "sqlite3 issues message, %s\n", sqlite3_errmsg(db)); + printf("put failed\n"); + return 2; + } + ret = sqlite3_close(db); + if( ret != SQLITE_OK ){ + fprintf(stderr, "sqlite3_close(db) indicates failure by returning %d\n",ret); + fprintf(stderr, "sqlite3 issues message: %s\n", sqlite3_errmsg(db)); + return SUBU_ERR_DB_FILE; + } + return 0; +} diff --git a/src/subudb.lib.c b/src/subudb.lib.c new file mode 100644 index 0000000..99ec7f0 --- /dev/null +++ b/src/subudb.lib.c @@ -0,0 +1,157 @@ +/* +The db file is maintained in SQLite + +Because user names of are of limited length, subu user names are always named _s. +A separate table translates the numbers into the subu names. + +The first argument is the biggest subu number in the system, or one minus an +starting point for subu numbering. + +currently a unit converted to base 10 will always fit in a 21 bit buffer. + +Each of these returns SQLITE_OK upon success +*/ +#include "subudb.lib.h" + +#if INTERFACE +#include +#endif + +#include +#include +#include + +//-------------------------------------------------------------------------------- +// sqlite transactions don't nest. There is a way to use save points, but still +// we can't just nest transactions. Instead use these wrappers around the whole +// of something that needs to be in a transaction. +int db_begin(sqlite3 *db){ + return sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); +} +int db_commit(sqlite3 *db){ + return sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); +} +int db_rollback(sqlite3 *db){ + return sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL); +} + +//-------------------------------------------------------------------------------- +int subudb_schema(sqlite3 *db){ + char sql[] = + "CREATE TABLE Masteru_Subu(masteru_name TEXT, subuname TEXT, subu_username TEXT);" + "CREATE TABLE Masteru_Max(masteru_name TEXT, max_subu_number INT);" + ; + return sqlite3_exec(db, sql, NULL, NULL, NULL); +} + +//-------------------------------------------------------------------------------- +int subudb_number_init(sqlite3 *db, char *masteru_name, int n){ + int rc; + char *sql = "INSERT INTO Masteru_Max (masteru_name, max_subu_number) VALUES (?1, ?2);"; + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 2, n); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if( rc == SQLITE_DONE ) return SQLITE_OK; + return rc; +} + +int subudb_number_get(sqlite3 *db, char *masteru_name, int *n){ + char *sql = "SELECT max_subu_number FROM Masteru_Max WHERE masteru_name = ?1;"; + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); + int rc = sqlite3_step(stmt); + if( rc == SQLITE_ROW ){ + *n = sqlite3_column_int(stmt,0); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if( rc == SQLITE_DONE ) return SQLITE_OK; + return rc; + } + // should have a message return, suppose + sqlite3_finalize(stmt); + return SQLITE_NOTFOUND; +} + +// on success returns SQLITE_DONE +int subudb_number_set(sqlite3 *db, char *masteru_name, int n){ + int rc; + char *sql = "UPDATE Masteru_Max SET max_subu_number = ?1 WHERE masteru_name = ?2;"; + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, n); + sqlite3_bind_text(stmt, 2, masteru_name, -1, SQLITE_STATIC); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if( rc == SQLITE_DONE ) return SQLITE_OK; + return rc; +} + +// returns SQLITE_DONE or an error code +// removes masteru/max_number relation from table +int subudb_number_rm(sqlite3 *db, char *masteru_name){ + char *sql = "DELETE FROM Masteru_Max WHERE masteru_name = ?1;"; + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); + int rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if( rc == SQLITE_DONE ) return SQLITE_OK; + return rc; +} + +//-------------------------------------------------------------------------------- +// put relation into Masteru_Subu table +int subudb_Masteru_Subu_put(sqlite3 *db, char *masteru_name, char *subuname, char *subu_username){ + char *sql = "INSERT INTO Masteru_Subu VALUES (?1, ?2, ?3);"; + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, subuname, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, subu_username, -1, SQLITE_STATIC); + int rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + return rc; +} + +//-------------------------------------------------------------------------------- +int subudb_Masteru_Subu_get(sqlite3 *db, char *masteru_name, char *subuname, char **subu_username){ + char *sql = "SELECT subu_username FROM Masteru_Subu WHERE masteru_name = ?1 AND subuname = ?2;"; + size_t sql_len = strlen(sql); + sqlite3_stmt *stmt; + int rc; + rc = sqlite3_prepare_v2(db, sql, sql_len, &stmt, NULL); + if( rc != SQLITE_OK ) return rc; + sqlite3_bind_text(stmt, 1, masteru_name, strlen(masteru_name), SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, subuname, strlen(subuname), SQLITE_STATIC); + rc = sqlite3_step(stmt); + if( rc == SQLITE_ROW ){ + const char *username = sqlite3_column_text(stmt, 0); + *subu_username = strdup(username); + }else{ + sqlite3_finalize(stmt); + return rc; // woops this needs to return an error!, be sure it is not SQLITE_DONE + } + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + return rc; +} + +//-------------------------------------------------------------------------------- +int subudb_Masteru_Subu_rm(sqlite3 *db, char *masteru_name, char *subuname, char *subu_username){ + char *sql = "DELETE FROM Masteru_Subu WHERE masteru_name = ?1 AND subuname = ?2 AND subu_username = ?3;"; + size_t sql_len = strlen(sql); + sqlite3_stmt *stmt; + int rc; + rc = sqlite3_prepare_v2(db, sql, sql_len, &stmt, NULL); + if( rc != SQLITE_OK ) return rc; + sqlite3_bind_text(stmt, 1, masteru_name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, subuname, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, subu_username, -1, SQLITE_STATIC); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + return rc; +} diff --git a/tools/bin/gitadd b/tools/bin/gitadd new file mode 100755 index 0000000..c2f2e02 --- /dev/null +++ b/tools/bin/gitadd @@ -0,0 +1,5 @@ +#!/bin/bash +set -x +make dist_clean +git add "$@" + diff --git a/tools/bin/make b/tools/bin/make new file mode 100755 index 0000000..3312575 --- /dev/null +++ b/tools/bin/make @@ -0,0 +1,2 @@ +#!/bin/bash +/usr/bin/make -f 7_makefile "$@" diff --git a/tools/bin/makeheaders b/tools/bin/makeheaders new file mode 100755 index 0000000..a50d9a0 Binary files /dev/null and b/tools/bin/makeheaders differ diff --git a/tools/bin/setuid_root.sh b/tools/bin/setuid_root.sh new file mode 100755 index 0000000..aedd564 --- /dev/null +++ b/tools/bin/setuid_root.sh @@ -0,0 +1,4 @@ +#!/bin/bash +chown root "$@" && \ +chmod u+rsx,u-w,go+rx-s-w "$@" + diff --git a/tools/doc/makeheaders.html b/tools/doc/makeheaders.html new file mode 100644 index 0000000..289da58 --- /dev/null +++ b/tools/doc/makeheaders.html @@ -0,0 +1,1150 @@ + +The Makeheaders Program + +

The Makeheaders Program

+ + +

+This document describes makeheaders, +a tool that automatically generates “.h” +files for a C or C++ programming project. +

+ + +

Table Of Contents

+ + +

1.0 Background

+ +

+A piece of C source code can be one of two things: +a declaration or a definition. +A declaration is source text that gives information to the +compiler but doesn't directly result in any code being generated. +A definition is source text that results in executable machine +instructions or initialization data. +(These two terms are sometimes used inconsistently by other authors. +In particular, many people reverse the meanings of these words when +discussing Pascal or Ada code. +The meanings described here are the same as used in the ANSI-C +standards document.) +

+ +

+Declarations in C include things such as the following: +

    +
  • Typedefs. +
  • Structure, union and enumeration declarations. +
  • Function and procedure prototypes. +
  • Preprocessor macros and #defines. +
  • extern” variable declarations. +
+

+ +

+Definitions in C, on the other hand, include these kinds of things: +

    +
  • Variable definitions. +
  • The bodies of functions and procedures. +
  • Initialization data. +
+

+ +

+The distinction between a declaration and a definition is common in +modern software engineering. +Another way of looking at the difference is that the declaration +is the interface and the definition is the implementation. +

+ +

+In C programs, it has always been the tradition that declarations are +put in files with the “.h” suffix and definitions are +placed in “.c” files. +The .c files contain “#include” preprocessor statements +that cause the contents of .h files to be included as part of the +source code when the .c file is compiled. +In this way, the .h files define the interface to a subsystem and +the .c files define how the subsystem is implemented. +

+ + +

1.1 Problems With The Traditional Approach

+ +

+As the art of computer programming continues to advance, and the size +and complexity of programs continues to swell, the traditional C +approach of placing declarations and definitions in separate files begins +to present the programmer with logistics and +maintenance problems. +To wit: +

+ +

+

    +

  1. +In large codes with many source files, it becomes difficult to determine +which .h files should be included in which .c files. +

  2. +It is typically the case that a .h file will be forced to include +another .h files, which in turn might include other .h files, +and so forth. +The .c file must be recompiled when any of the .h files in this chain +are altered, but it can be difficult to determine what .h files are found +in the include chain. +A frequent Makefile error is to omit some .h files from a dependency +list even though those files are on the include file chain. +

  3. +Some information is common to both the declaration and the definition of +an object in C, and so must be repeated in both the .h and the .c files +for that object. +In a large project, it can become increasingly difficult to keep the two +files in sync. +

  4. +When a .c file includes a .h file and the .h files changes, the .c file +must be recompiled, even if the part of the .h file that changed is not +actually used by the .c file. +In a large program, it is generally the case that almost every .c file ends up +depending on one or two of the more important .h files, and so when those .h +files change, the entire program must be recompiled. +It also happens that those important .h files tend to be the ones that +change most frequently. +This means that the entire program must be recompiled frequently, +leading to a lengthy modify-compile-test cycle and a corresponding +decrease in programmer productivity. +

  5. +The C programming language requires that declarations depending upon +each other must occur in a particular order. +In a program with complex, interwoven data structures, the correct +declaration order can become very difficult to determine manually, +especially when the declarations involved are spread out over several +files. +
+

+ + +

1.2 The Makeheaders Solution

+ +

+The makeheaders program is designed to ameliorate the problems associated +with the traditional C programming model by automatically generating +the interface information in the .h files from +interface information contained in other .h files and +from implementation information in the .c files. +When the makeheaders program is run, it scans the source +files for a project, +then generates a series of new .h files, one for each .c file. +The generated .h files contain exactly those declarations required by the +corresponding .c files, no more and no less. +

+ +

+The makeheaders programming model overcomes all of the objections to the +traditional C programming model. +

    +

  1. +Because all declarations needed by a .c file are contained in a +single .h file, there is never any question about what .h files +a .c will need to include. If the .c file is named +alpha.c then it must include only the single .h file +named alpha.h. +(The .c file might also use some include files from the standard +library, such as <stdio.h>, but that is another matter.) +

  2. +The generated .h files do not include other .h files, and so there +are no include chains to worry about. +The file alpha.c depends on alpha.h and +nothing more. +

  3. +There is still duplication in the .h and the .c file, but because +the duplicate information is automatically generated, it is no longer +a problem. +Simply rerun makeheaders to resynchronize everything. +

  4. +The generated .h file contains the minimal set of declarations needed +by the .c file. +This means that when something changes, a minimal amount of recompilation +is required to produce an updated executable. +Experience has shown that this gives a dramatic improvement +in programmer productivity by facilitating a rapid modify-compile-test +cycle during development. +

  5. +The makeheaders program automatically sorts declarations into the +correct order, completely eliminating the wearisome and error-prone +task of sorting declarations by hand. +
+

+ +

+In addition, the makeheaders program is fast and unintrusive. +It is a simple matter to incorporate makeheaders into a Makefile +so that makeheaders will be run automatically whenever the project +is rebuilt. +And the burden of running makeheaders is light. +It will easily process tens of thousands of lines of source +code per second. +

+ + +

2.0 Running The Makeheaders Program

+ +

+The makeheaders program is very easy to run. +If you have a collection of C source code and include files in the working +directory, then you can run makeheaders to generate appropriate .h +files using the following command: +

+   makeheaders *.[ch]
+
+That's really all there is to it! +This command will generate one .h file for every .c file. +Any .h files that were generated by a prior run of makeheaders +are ignored, +but manually entered .h files +that contain structure declarations and so forth will be scanned and +the declarations will be copied into the generated .h files as +appropriate. +But if makeheaders sees that the .h file that it has generated is no +different from the .h file it generated last time, it doesn't update +the file. +This prevents the corresponding .c files from having to +be needlessly recompiled. +

+ +

+There are several options to the makeheaders program that can +be used to alter its behavior. +The default behavior is to write a single .h file for each .c file and +to give the .h file the same base name as the .c file. +Instead of generating a whole mess of .h files, you can, if you choose, +generate a single big .h file that contains all declarations needed +by all the .c files. Do this using the -h option to makeheaders. +As follows: +

+   makeheaders -h *.[ch] >common.h
+
+With the -h option, the .h file is not actually written to a disk file but +instead appears on standard output, where you are free to redirect it +into the file of your choice. +

+ +

+A similar option is -H. Like the lower-case -h option, big -H +generates a single include file on standard output. But unlike +small -h, the big -H only emits prototypes and declarations that +have been designated as “exportable”. +The idea is that -H will generate an include file that defines +the interface to a library. +More will be said about this in section 3.4. +

+ +

+Sometimes you want the base name of the .c file and the .h file to +be different. +For example, suppose you want the include file for alpha.c +to be called beta.h. +In this case, you would invoke makeheaders as follows: +

+   makeheaders alpha.c:beta.h
+
+Any time a filename argument contains a colon, the name before the +colon is taken to be the name of the .c file and the name after the +colon is taken to be the name of the .h file. +You can't use the shell's wildcard mechanism with this approach, but that +normally isn't a problem in Makefiles, which is where this stuff +comes in handy. +

+ +

+If you want a particular file to be scanned by makeheaders but you +don't want makeheaders to generate a header file for that file, +then you can supply an empty header filename, like this: +

+   makeheaders alpha.c beta.c gamma.c:
+
+In this example, makeheaders will scan the three files named +“alpha.c”, +“beta.c” and +“gamma.c” +but because of the colon on the end of third filename +it will only generate headers for the first two files. +Unfortunately, +it is not possible to get makeheaders to process any file whose +name contains a colon. +

+ +

+In a large project, the length of the command line for makeheaders +can become very long. +If the operating system doesn't support long command lines +(example: DOS and Win32) you may not be able to list all of the +input files in the space available. +In that case, you can use the “-f” option followed +by the name of a file to cause makeheaders to read command line +options and filename from the file instead of from the command line. +For example, you might prepare a file named “mkhdr.dat” +that contains text like this: +

+  src/alpha.c:hdr/alpha.h
+  src/beta.c:hdr/beta.h
+  src/gamma.c:hdr/gamma.h
+  ...
+
+Then invoke makeheaders as follows: +
+  makeheaders -f mkhdr.dat
+
+

+ +

+The “-local” option causes makeheaders to +generate of prototypes for “static” functions and +procedures. +Such prototypes are normally omitted. +

+ +

+Finally, makeheaders also includes a “-doc” option. +This command line option prevents makeheaders from generating any +headers at all. +Instead, makeheaders will write to standard output +information about every definition and declaration that it encounters +in its scan of source files. +The information output includes the type of the definition or +declaration and any comment that preceeds the definition or +declaration. +The output is in a format that can be easily parsed, and is +intended to be read by another program that will generate +documentation about the program. +We'll talk more about this feature later. +

+ +

+If you forget what command line options are available, or forget +their exact name, you can invoke makeheaders using an unknown +command line option (like “--help” or +“-?”) +and it will print a summary of the available options on standard +error. +If you need to process a file whose name begins with +“-”, +you can prepend a “./” to its name in order to get it +accepted by the command line parser. +Or, you can insert the special option “--” on the +command line to cause all subsequent command line arguments to be treated as +filenames even if their names begin with “-”. +

+ + +

3.0 Preparing Source Files For Use With Makeheaders

+ +

+Very little has to be done to prepare source files for use with +makeheaders since makeheaders will read and understand ordinary +C code. +But it is important that you structure your files in a way that +makes sense in the makeheaders context. +This section will describe several typical uses of makeheaders. +

+ + +

3.1 The Basic Setup

+ +

+The simplest way to use makeheaders is to put all definitions in +one or more .c files and all structure and type declarations in +separate .h files. +The only restriction is that you should take care to chose basenames +for your .h files that are different from the basenames for your +.c files. +Recall that if your .c file is named (for example) +“alpha.c” +makeheaders will attempt to generate a corresponding header file +named “alpha.h”. +For that reason, you don't want to use that name for +any of the .h files you write since that will prevent makeheaders +from generating the .h file automatically. +

+ +

+The structure of a .c file intented for use with makeheaders is very +simple. +All you have to do is add a single “#include” to the +top of the file that sources the header file that makeheaders will generate. +Hence, the beginning of a source file named “alpha.c” +might look something like this: +

+ +
+   /*
+    * Introductory comment...
+    */
+   #include "alpha.h"
+
+   /* The rest of your code... */
+
+ +

+Your manually generated header files require no special attention at all. +Code them as you normally would. +However, makeheaders will work better if you omit the +“#if” statements people often put around the outside of +header files that prevent the files from being included more than once. +For example, to create a header file named “beta.h”, +many people will habitually write the following: + +

+   #ifndef BETA_H
+   #define BETA_H
+
+   /* declarations for beta.h go here */
+
+   #endif
+
+ +You can forego this cleverness with makeheaders. +Remember that the header files you write will never really be +included by any C code. +Instead, makeheaders will scan your header files to extract only +those declarations that are needed by individual .c files and then +copy those declarations to the .h files corresponding to the .c files. +Hence, the “#if” wrapper serves no useful purpose. +But it does make makeheaders work harder, forcing it to put +the statements + +
+   #if !defined(BETA_H)
+   #endif
+
+ +around every declaration that it copies out of your header file. +No ill effect should come of this, but neither is there any benefit. +

+ +

+Having prepared your .c and .h files as described above, you can +cause makeheaders to generate its .h files using the following simple +command: + +

+   makeheaders *.[ch]
+
+ +The makeheaders program will scan all of the .c files and all of the +manually written .h files and then automatically generate .h files +corresponding to all .c files. +

+ +

+Note that +the wildcard expression used in the above example, +“*.[ch]”, +will expand to include all .h files in the current directory, both +those entered manually be the programmer and others generated automatically +by a prior run of makeheaders. +But that is not a problem. +The makeheaders program will recognize and ignore any files it +has previously generated that show up on its input list. +

+ + +

3.2 What Declarations Get Copied

+ +

+The following list details all of the code constructs that makeheaders +will extract and place in +the automatically generated .h files: +

+ +
    +

  • +When a function is defined in any .c file, a prototype of that function +is placed in the generated .h file of every .c file that +calls the function.

    + +

    If the “static” keyword of C appears at the +beginning of the function definition, the prototype is suppressed. +If you use the “LOCAL” keyword where you would normally +say “static”, then a prototype is generated, but it +will only appear in the single header file that corresponds to the +source file containing the function. For example, if the file +alpha.c contains the following: +

    +  LOCAL int testFunc(void){
    +    return 0;
    +  }
    +
    +Then the header file alpha.h will contain +
    +  #define LOCAL static
    +  LOCAL int testFunc(void);
    +
    +However, no other generated header files will contain a prototype for +testFunc() since the function has only file scope.

    + +

    When the “LOCAL” keyword is used, makeheaders will +also generate a #define for LOCAL, like this: +

    +   #define LOCAL static
    +
    +so that the C compiler will know what it means.

    + +

    If you invoke makeheaders with a “-local” +command-line option, then it treats the “static” +keyword like “LOCAL” and generates prototypes in the +header file that corresponds to the source file containing the function +definition.

    + +

  • +When a global variable is defined in a .c file, an +“extern” +declaration of that variable is placed in the header of every +.c file that uses the variable. +

    + +

  • +When a structure, union or enumeration declaration or a +function prototype or a C++ class declaration appears in a +manually produced .h file, that declaration is copied into the +automatically generated +.h files of all .c files that use the structure, union, enumeration, +function or class. +But declarations that appear in a +.c file are considered private to that .c file and are not copied into +any automatically generated files. +

    + +

  • +All #defines and typedefs that appear in manually produced .h files +are copied into automatically generated .h files as needed. +Similar constructs that appear in .c files are considered private to +those files and are not copied. +

    + +

  • +When a structure, union or enumeration declaration appears in a .h +file, makeheaders will automatically +generate a typedef that allows the declaration to be referenced without +the “struct”, “union” or +“enum” qualifier. +In other words, if makeheaders sees the code: +
    +  struct Examp { /* ... */ };
    +
    +it will automatically generate a corresponding typedef like this: +
    +  typedef struct Examp Examp;
    +
    +

    + +

  • +Makeheaders generates an error message if it encounters a function or +variable definition within a .h file. +The .h files are suppose to contain only interface, not implementation. +C compilers will not enforce this convention, but makeheaders does. +
+ +

+As a final note, we observe that automatically generated declarations +are ordered as required by the ANSI-C programming language. +If the declaration of some structure “X” requires a +prior declaration of another structure “Y”, then Y will +appear first in the generated headers. +

+ + +

3.3 How To Avoid Having To Write Any Header Files

+ +

+In my experience, large projects work better if all of the manually +written code is placed in .c files and all .h files are generated +automatically. +This is slightly different for the traditional C method of placing +the interface in .h files and the implementation in .c files, but +it is a refreshing change that brings a noticable improvement to the +coding experience. +Others, I believe, share this view since I've +noticed recent languages (ex: java, tcl, perl, awk) tend to +support the one-file approach to coding as the only option. +

+ +

+The makeheaders program supports putting both +interface and implementation into the same source file. +But you do have to tell makeheaders which part of the source file is the +interface and which part is the implementation. +Makeheaders has to know this in order to be able to figure out whether or +not structures declarations, typedefs, #defines and so forth should +be copied into the generated headers of other source files. +

+ +

+You can instruct makeheaders to treat any part of a .c file as if +it were a .h file by enclosing that part of the .c file within: +

+   #if INTERFACE
+   #endif
+
+Thus any structure definitions that appear after the +“#if INTERFACE” but before the corresponding +“#endif” are eligable to be copied into the +automatically generated +.h files of other .c files. +

+ +

+If you use the “#if INTERFACE” mechanism in a .c file, +then the generated header for that .c file will contain a line +like this: +

+   #define INTERFACE 0
+
+In other words, the C compiler will never see any of the text that +defines the interface. +But makeheaders will copy all necessary definitions and declarations +into the .h file it generates, so .c files will compile as if the +declarations were really there. +This approach has the advantage that you don't have to worry with +putting the declarations in the correct ANSI-C order -- makeheaders +will do that for you automatically. +

+ +

+Note that you don't have to use this approach exclusively. +You can put some declarations in .h files and others within the +“#if INTERFACE” regions of .c files. +Makeheaders treats all declarations alike, no matter where they +come from. +You should also note that a single .c file can contain as many +“#if INTERFACE” regions as desired. +

+ + +

3.4 Designating Declarations For Export

+ +

+In a large project, one will often construct a hierarchy of +interfaces. +For example, you may have a group of 20 or so files that form +a library used in several other parts of the system. +Each file in this library will present two interfaces. +One interface will be the routines and data structures it is +willing to share with other files in the same library, and the +second interface is those routines and data structures it wishes +to make available to other subsystems. +(The second interface is normally a subset of the first.) +Ordinary C does not provide support for a tiered interface +like this, but makeheaders does. +

+ +

+Using makeheaders, it is possible to designate routines and data +structures as being for “export”. +Exported objects are visible not only to other files within the +same library or subassembly but also to other +libraries and subassemblies in the larger program. +By default, makeheaders only makes objects visible to other members +of the same library. +

+ +

+That isn't the complete truth, actually. +The semantics of C are such that once an object becomes visible +outside of a single source file, it is also visible to any user +of the library that is made from the source file. +Makeheaders can not prevent outsiders for using non-exported resources, +but it can discourage the practice by refusing to provide prototypes +and declarations for the services it does not want to export. +Thus the only real effect of the making an object exportable is +to include it in the output makeheaders generates when it is run +using the -H command line option. +This is not a perfect solution, but it works well in practice. +

+ +

+But trouble quickly arises when we attempt to devise a mechanism for +telling makeheaders which prototypes it should export and which it should +keep local. +The built-in “static” keyword of C works well for +prohibiting prototypes from leaving a single source file, but because C doesn't +support a linkage hierarchy, there is nothing in the C language to help us. +We'll have to invite our own keyword: “EXPORT” +

+ +

+Makeheaders allows the EXPORT keyword to precede any function or +procedure definition. +The routine following the EXPORT keyword is then eligable to appear +in the header file generated using the -H command line option. +Note that if a .c file contains the EXPORT keyword, makeheaders will +put the macro +

+   #define EXPORT
+
+in the header file it generates for the .c file so that the EXPORT keyword +will never be seen by the C compiler. +

+ +

+But the EXPORT keyword only works for function and procedure definitions. +For structure, union and enum definitions, typedefs, #defines and +class declarations, a second mechanism is used. +Just as any declarations or definition contained within +

+   #if INTERFACE
+   #endif
+
+are visible to all files within the library, any declarations +or definitions within +
+   #if EXPORT_INTERFACE
+   #endif
+
+will become part of the exported interface. +The “#if EXPORT_INTERFACE” mechanism can be used in +either .c or .h files. +(The “#if INTERFACE” can also be used in both .h and +.c files, but since it's use in a .h file would be redundant, we haven't +mentioned it before.) +

+ + +

3.5 Local declarations processed by makeheaders

+ +

+Structure declarations and typedefs that appear in .c files are normally +ignored by makeheaders. +Such declarations are only intended for use by the source file in which +they appear and so makeheaders doesn't need to copy them into any +generated header files. +We call such declarations “private”. +

+ +

+Sometimes it is convenient to have makeheaders sort a sequence +of private declarations into the correct order for us automatically. +Or, we could have static functions and procedures for which we would like +makeheaders to generate prototypes, but the arguments to these +functions and procedures uses private declarations. +In both of these cases, we want makeheaders to be aware of the +private declarations and copy them into the local header file, +but we don't want makeheaders to propagate the +declarations outside of the file in which they are declared. +

+ +

+When this situation arises, enclose the private declarations +within +

+  #if LOCAL_INTERFACE
+  #endif
+
+A “LOCAL_INTERFACE” block works very much like the +“INTERFACE” and +“EXPORT_INTERFACE” +blocks described above, except that makeheaders insures that the +objects declared in a LOCAL_INTERFACE are only visible to the +file containing the LOCAL_INTERFACE. +

+ + +

3.6 Using Makeheaders With C++ Code

+ +

+You can use makeheaders to generate header files for C++ code, in +addition to C. +Makeheaders will recognize and copy both “class” +declarations +and inline function definitions, and it knows not to try to generate +prototypes for methods. +

+ +

+In fact, makeheaders is smart enough to be used in projects that employ +a mixture of C and C++. +For example, if a C function is called from within a C++ code module, +makeheaders will know to prepend the text +

+   extern "C"
+
+to the prototype for that function in the C++ header file. +Going the other way, +if you try to call a C++ function from within C, an +appropriate error message is issued, since C++ routines can not +normally be called by C code (due to fact that most C++ compilers +use name mangling to facilitate type-safe linkage.) +

+ +

+No special command-line options are required to use makeheaders with +C++ input. Makeheaders will recognize that its source code is C++ +by the suffix on the source code filename. Simple ".c" or ".h" suffixes +are assumed to be ANSI-C. Anything else, including ".cc", ".C" and +".cpp" is assumed to be C++. +The name of the header file generated by makeheaders is derived from +the name of the source file by converting every "c" to "h" and +every "C" to "H" in the suffix of the filename. +Thus the C++ source +file “alpha.cpp” will induce makeheaders to +generate a header file named “alpha.hpp”. +

+ +

+Makeheaders augments class definitions by inserting prototypes to +methods where appropriate. If a method definition begins with one +of the special keywords PUBLIC, PROTECTED, or +PRIVATE (in upper-case to distinguish them from the regular +C++ keywords with the same meaning) then a prototype for that +method will be inserted into the class definition. If none of +these keywords appear, then the prototype is not inserted. For +example, in the following code, the constructor is not explicitly +declared in the class definition but makeheaders will add it there +because of the PUBLIC keyword that appears before the constructor +definition. +

+ +
+#if INTERFACE
+class Example1 {
+private:
+  int v1;
+};
+#endif
+PUBLIC Example1::Example1(){
+  v1 = 0;
+}
+
+ +

+The code above is equivalent to the following: +

+ +
+#if INTERFACE
+class Example1 {
+private:
+  int v1;
+public:
+  Example1();
+};
+#endif
+Example1::Example1(){
+  v1 = 0;
+}
+
+ +

+The first form is preferred because only a single declaration of +the constructor is required. The second form requires two declarations, +one in the class definition and one on the defintion of the constructor. +

+ +

3.6.1 C++ Limitations

+ +

+Makeheaders does not understand more recent +C++ syntax such as templates and namespaces. +Perhaps these issues will be addressed in future revisions. +

+ + +

3.7 Conditional Compilation

+ +

+The makeheaders program understands and tracks the conditional +compilation constructs in the source code files it scans. +Hence, if the following code appears in a source file +

+  #ifdef UNIX
+  #  define WORKS_WELL 1
+  #else
+  #  define WORKS_WELL 0
+  #endif
+
+then the next patch of code will appear in the generated header for +every .c file that uses the WORKS_WELL constant: +
+  #if defined(UNIX)
+  #  define WORKS_WELL 1
+  #endif
+  #if !defined(UNIX)
+  #  define WORKS_WELL 0
+  #endif
+
+The conditional compilation constructs can be nested to any depth. +Makeheaders also recognizes the special case of +
+  #if 0
+  #endif
+
+and treats the enclosed text as a comment. +

+ + +

3.8 Caveats

+ +

+The makeheaders system is designed to be robust +but it is possible for a devious programmer to fool the system, +usually with unhelpful consequences. +This subsection is a guide to helping you avoid trouble. +

+ +

+Makeheaders does not understand the old K&R style of function +and procedure definitions. +It only understands the modern ANSI-C style, and will probably +become very confused if it encounters an old K&R function. +Therefore you should take care to avoid putting K&R function definitions +in your code. +

+ +

+Makeheaders does not understand when you define more than one +global variable with the same type separated by a comma. +In other words, makeheaders does not understand this: +

+   int a = 4, b = 5;
+
+The makeheaders program wants every variable to have its own +definition. Like this: +
+   int a = 4;
+   int b = 5;
+
+Notice that this applies to global variables only, not to variables +you declare inside your functions. +Since global variables ought to be exceedingly rare, and since it is +good style to declare them separately anyhow, this restriction is +not seen as a terrible hardship. +

+ +

+Makeheaders does not support defining an enumerated or aggregate type in +the same statement as a variable declaration. None of the following +statements work completely: +

+struct {int field;} a;
+struct Tag {int field;} b;
+struct Tag c;
+
+Instead, define types separately from variables: +
+#if INTERFACE
+struct Tag {int field;};
+#endif
+Tag a;
+Tag b; /* No more than one variable per declaration. */
+Tag c; /* So must put each on its own line. */
+
+See 3.2 What Declarations Get Copied for details, +including on the automatic typedef. +

+ +

+The makeheaders program processes its source file prior to sending +those files through the C preprocessor. +Hence, if you hide important structure information in preprocessor defines, +makeheaders might not be able to successfully extract the information +it needs from variables, functions and procedure definitions. +For example, if you write this: +

+  #define BEGIN {
+  #define END }
+
+at the beginning of your source file, and then try to create a function +definition like this: +
+  char *StrDup(const char *zSrc)
+    BEGIN
+      /* Code here */
+    END
+
+then makeheaders won't be able to find the end of the function definition +and bad things are likely to happen. +

+ +

+For most projects the code constructs that makeheaders cannot +handle are very rare. +As long as you avoid excessive cleverness, makeheaders will +probably be able to figure out what you want and will do the right +thing. +

+ +

+Makeheaders has limited understanding of enums. In particular, it does +not realize the significance of enumerated values, so the enum is not +emitted in the header files when its enumerated values are used unless +the name associated with the enum is also used. Moreover, enums can be +completely anonymous, e.g. “enum {X, Y, Z};”. +Makeheaders ignores such enums so they can at least be used within a +single source file. Makeheaders expects you to use #define constants +instead. If you want enum features that #define lacks, and you need the +enum in the interface, bypass makeheaders and write a header file by +hand, or teach makeheaders to emit the enum definition when any of the +enumerated values are used, rather than only when the top-level name (if +any) is used. +

+ + +

4.0 Using Makeheaders To Generate Documentation

+ +

+Many people have observed the advantages of generating program +documentation directly from the source code: +

    +
  • Less effort is involved. It is easier to write a program than + it is to write a program and a document. +
  • The documentation is more likely to agree with the code. + When documentation is derived directly from the code, or is + contained in comments immediately adjacent to the code, it is much + more likely to be correct than if it is contained in a separate + unrelated file in a different part of the source tree. +
  • Information is kept in only one place. When a change occurs + in the code, it is not necessary to make a corresponding change + in a separate document. Just rerun the documentation generator. +
+The makeheaders program does not generate program documentation itself. +But you can use makeheaders to parse the program source code, extract +the information that is relevant to the documentation and to pass this +information to another tool to do the actual documentation preparation. +

+ +

+When makeheaders is run with the “-doc” option, it +emits no header files at all. +Instead, it does a complete dump of its internal tables to standard +output in a form that is easily parsed. +This output can then be used by another program (the implementation +of which is left as an exercise to the reader) that will use the +information to prepare suitable documentation. +

+ +

+The “-doc” option causes makeheaders to print +information to standard output about all of the following objects: +

    +
  • C++ class declarations +
  • Structure and union declarations +
  • Enumerations +
  • Typedefs +
  • Procedure and function definitions +
  • Global variables +
  • Preprocessor macros (ex: “#define”) +
+For each of these objects, the following information is output: +
    +
  • The name of the object. +
  • The type of the object. (Structure, typedef, macro, etc.) +
  • Flags to indicate if the declaration is exported (contained within + an EXPORT_INTERFACE block) or local (contained with LOCAL_INTERFACE). +
  • A flag to indicate if the object is declared in a C++ file. +
  • The name of the file in which the object was declared. +
  • The complete text of any block comment that preceeds the declarations. +
  • If the declaration occurred inside a preprocessor conditional + (“#if”) then the text of that conditional is + provided. +
  • The complete text of a declaration for the object. +
+The exact output format will not be described here. +It is simple to understand and parse and should be obvious to +anyone who inspects some sample output. +

+ + +

5.0 Compiling The Makeheaders Program

+ +

+The source code for makeheaders is a single file of ANSI-C code, +approximately 3000 lines in length. +The program makes only modest demands of the system and C library +and should compile without alteration on most ANSI C compilers +and on most operating systems. +It is known to compile using several variations of GCC for Unix +as well as Cygwin32 and MSVC 5.0 for Win32. +

+ + +

6.0 History

+ +

+The makeheaders program was first written by D. Richard Hipp +(also the original author of +SQLite and +Fossil) in 1993. +Hipp open-sourced the project immediately, but it never caught +on with any other developers and it continued to be used mostly +by Hipp himself for over a decade. When Hipp was first writing +the Fossil version control system in 2006 and 2007, he used +makeheaders on that project to help simplify the source code. +As the popularity of Fossil increased, the makeheaders +that was incorporated into the Fossil source tree became the +"official" makeheaders implementation. +

+ +

+As this paragraph is being composed (2016-11-05), Fossil is the +only project known to Hipp that is still using makeheaders. On +the other hand, makeheaders has served the Fossil project well and +there are no plans remove it. +

+ + +

7.0 Summary And Conclusion

+ +

+The makeheaders program will automatically generate a minimal header file +for each of a set of C source and header files, and will +generate a composite header file for the entire source file suite, +for either internal or external use. +It can also be used as the parser in an automated program +documentation system. +

+ +

+The makeheaders program has been in use since 1994, +in a wide variety of projects under both UNIX and Win32. +In every project where it has been used, makeheaders has proven +to be a very helpful aid +in the construction and maintenance of large C codes. +In at least two cases, makeheaders has facilitated development +of programs that would have otherwise been all but impossible +due to their size and complexity. +

+ + diff --git a/tools/lib/7_makefile b/tools/lib/7_makefile new file mode 100755 index 0000000..ae06d60 --- /dev/null +++ b/tools/lib/7_makefile @@ -0,0 +1,156 @@ +# Copyright 2011 (C) Reasoning Technology Ltd. All Rights Reserved +# +# 2010 11 20 TWL Created +# 2019 02 24 TWL modified for subu project and placed under MIT license + +# a single space literal, for example if you wanted to subsitute commas to +# spaces: $(subst $(space),;,$(string)) we ran into this out of a need to send +# multiple separate command arguments to a shell script from one variable value +blank := +space :=$(blank) $(blank) + +# some versions of Linux need a -e option others complain if there is a -e .. and it isn't the binary for echo .. +ECHO= echo +#ECHO= echo -e + +SHELL=/bin/bash +SCRATCHDIR= 5_scratch # clean and others put things here +CC=gcc +CFLAGS=-std=gnu11 -fPIC -I. -ggdb -Werror -DDEBUG -DDEBUGDB +#CFLAGS=-std=gnu11 -fPIC -I. -Werror +LIB="2_lib/libsubu.a" +LINKFLAGS=-L2_lib -lsubu -lsqlite3 + +#these are the source files that exist +SOURCES_LIB= $(wildcard *.lib.c) +SOURCES_CLI= $(wildcard *.cli.c) +SOURCES= $(SOURCES_LIB) $(SOURCES_CLI) + +#these are the object files to be made +OBJECTS_LIB= $(patsubst %.c, %.o, $(SOURCES_LIB)) +OBJECTS_CLI= $(patsubst %.c, %.o, $(SOURCES_CLI)) +OBJECTS= $(OBJECTS_LIB) $(OBJECTS_CLI) + +#these are the header files that exist, makeheaders will want to see them +HFILES = $(wildcard *.lib.h) $(wildcard *.cli.h) + +# sort causes compiles to go in lexical order by file name, this is used to order the tests e.g. +EXECS= $(sort $(patsubst %.cli.c, %, $(wildcard *.cli.c))) + +all: version deps lib execs + +version: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + @echo makefile version 2.0 + @echo "CC: " $(CC) + @echo "CFLAGS: " $(CFLAGS) + @echo "LIB: " $(LIB) + @echo "LINKFLAGS: " $(LINKFLAGS) + @echo '______end make $@_____' + +# safe to run this in an already setup or partially setup directory +setup: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + if [ ! -e $(SCRATCHDIR) ]; then mkdir $(SCRATCHDIR); fi + if [ ! -e 1_tests ]; then mkdir 1_tests; fi + if [ ! -e 1_try ]; then mkdir 1_try; fi + if [ ! -e 2_bin ]; then mkdir 2_bin; fi + if [ ! -e 2_lib ]; then mkdir 2_lib; fi + if [ ! -e 2_doc ]; then mkdir 2_doc; fi + if [ ! -e 2_include ]; then mkdir 2_include; fi + if [ ! -e 5_deprecated ]; then mkdir 5_deprecated; fi + if [ ! -e 5_scratch ]; then mkdir 5_scratch; fi + @echo '______end make $@_____' + + +deps: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + makeheaders $(SOURCES) $(HFILES) + sed -i '/^ *int *main *(.*)/d' *.h + $(CC) $(CFLAGS) -MM $(SOURCES) 1> 7_makefile_deps + for i in $(EXECS) ; do\ + $(ECHO) >> 7_makefile_deps;\ + $(ECHO) "2_bin/$$i : $$i.cli.o $(LIB)" >> 7_makefile_deps;\ + $(ECHO) " $(CXX) -o 2_bin/$$i $$i.cli.o $(LINKFLAGS)" >> 7_makefile_deps;\ + done + @echo '______end make $@_____' + +lib: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + if [ ! -e 7_makefile_deps ]; then make deps; fi + make sub_lib + @echo '______end make $@_____' + +sub_lib: $(LIB) + + +execs: $(LIB) + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + if [ ! -e 7_makefile_deps ]; then make deps; fi + make sub_execs + @echo '-> sudo 2_bin/setuid_root.sh subu-mk-0 subu-rm-0' + cat 2_bin/setuid_root.sh + @echo -n "Are you sure? [y/N] " && read ans && [ $${ans:-N} == y ] + sudo 2_bin/setuid_root.sh subu-mk-0 subu-rm-0 + @echo '______end make $@_____' + +sub_execs: $(patsubst %, 2_bin/%, $(EXECS)) + +#not ready yet +install: all + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + @if[ ! -e 1_tests_passed ]; then echo "can't install as tests have not passed"; fi + @test -e test_passed + for i in $(BIN); do cp $$i $(RT_BASE)/bin; done + cp $(LIB) $(RT_BASE)/lib + cp $(APPLICATION).h $(RT_BASE)/include + if [ -d $(APPLICATION) ]; then cp $(APPLICATION)/*.h $(RT_BASE)/include/$(APPLICATION); fi + @echo '______end make $@_____' + +clean: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + if [ -f subudb ]; then rm subudb; fi + for i in $(wildcard *~); do mv $$i $(SCRATCHDIR); done + for i in $(wildcard *.lib.o) $(wildcard *.cli.o); do rm $$i; done + for i in $(HFILES); do mv $$i 5_scratch; done # just in case someone wrote a header file + if [ -f 7_makefile_deps ]; then rm 7_makefile_deps; fi + @echo '______end make $@_____' + + +# not ready ... +# dist_clean is used to clean thing up before doing a checkin, hg add should be safe after a dist_clean +# dist_clean will recurse into the include directory = $(APPLICATION), tests, and try if they are present +# +dist_clean: + @echo '---- make $@:------------------------------------------------------------' + @echo `pwd`'>' + make clean + if [ -d $(APPLICATION) ]; then cd $(APPLICATION); make clean; fi + if [ -f $(LIB) ]; then rm $(LIB); fi + for i in $(EXECS); do if [ -e 2_bin/$$i ]; then rm 2_bin/$$i; fi; done + if [ -d 1_tests ]; then cd 1_tests; make dist_clean; fi + if [ -d 1_try ] ; then cd 1_try; make dist_clean; fi + @echo '______end make $@_____' + +# not written yet +# copies stuff from the src dir to the stage dirs +# stage: + + +-include 7_makefile_deps + +# recipe for making object files: +# +%.o : %.c + $(CC) $(CFLAGS) -c $< + +# +$(LIB) : $(OBJECTS_LIB) + ar rcs $(LIB) $(OBJECTS_LIB) diff --git a/tools/lib/bashrc b/tools/lib/bashrc new file mode 100755 index 0000000..5cb8f77 --- /dev/null +++ b/tools/lib/bashrc @@ -0,0 +1,19 @@ +# + +umask 0077 + +if [ $EMACS ]; then + echo Hello Emacs +fi +export PS1='\n$(/usr/local/bin/Z)\n\u@\h§\w§\n> ' +export PS2='>' + +PATH=~/tools/bin:$PATH + +PS_FORMAT=user:15,pid,%cpu,%mem,vsz,rss,tty,stat,start,time,command +export PS_FORMAT + +EDITOR=emacs +export EDITOR + + diff --git a/tools/src/makeheaders.c b/tools/src/makeheaders.c new file mode 100644 index 0000000..b58e787 --- /dev/null +++ b/tools/src/makeheaders.c @@ -0,0 +1,3739 @@ + + + + + + +Fossil: File Content + + + + +
+

Fossil

File Content
+ +
+ +
+
+ + + +
+ +
+

Latest version of file 'src/makeheaders.c':

+
    +
  • File +src/makeheaders.c +— part of check-in +[8cecc544] +at +2018-11-02 15:21:54 +on branch trunk +— Enhance makeheaders so that it is able to deal with static_assert() statements. +(These do not come up in Fossil itself. This check-in is in response to use +of Makeheaders by external projects.) + (user: +drh +size: 100011) +[more...] +
+
+
+
+/*
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** Copyright 1993 D. Richard Hipp. All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or
+** without modification, are permitted provided that the following
+** conditions are met:
+**
+**   1. Redistributions of source code must retain the above copyright
+**      notice, this list of conditions and the following disclaimer.
+**
+**   2. Redistributions in binary form must reproduce the above copyright
+**      notice, this list of conditions and the following disclaimer in
+**      the documentation and/or other materials provided with the
+**      distribution.
+**
+** This software is provided "as is" and any express or implied warranties,
+** including, but not limited to, the implied warranties of merchantability
+** and fitness for a particular purpose are disclaimed.  In no event shall
+** the author or contributors be liable for any direct, indirect, incidental,
+** special, exemplary, or consequential damages (including, but not limited
+** to, procurement of substitute goods or services; loss of use, data or
+** profits; or business interruption) however caused and on any theory of
+** liability, whether in contract, strict liability, or tort (including
+** negligence or otherwise) arising in any way out of the use of this
+** software, even if advised of the possibility of such damage.
+**
+** 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.
+** appropriate header files.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <memory.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <string.h>
+
+#if defined( __MINGW32__) ||  defined(__DMC__) || defined(_MSC_VER) || defined(__POCC__)
+#  ifndef WIN32
+#    define WIN32
+#  endif
+#else
+# include <unistd.h>
+#endif
+
+/*
+** Macros for debugging.
+*/
+#ifdef DEBUG
+static int debugMask = 0;
+# define debug0(F,M)       if( (F)&debugMask ){ fprintf(stderr,M); }
+# define debug1(F,M,A)     if( (F)&debugMask ){ fprintf(stderr,M,A); }
+# define debug2(F,M,A,B)   if( (F)&debugMask ){ fprintf(stderr,M,A,B); }
+# define debug3(F,M,A,B,C) if( (F)&debugMask ){ fprintf(stderr,M,A,B,C); }
+# define PARSER      0x00000001
+# define DECL_DUMP   0x00000002
+# define TOKENIZER   0x00000004
+#else
+# define debug0(Flags, Format)
+# define debug1(Flags, Format, A)
+# define debug2(Flags, Format, A, B)
+# define debug3(Flags, Format, A, B, C)
+#endif
+
+/*
+** The following macros are purely for the purpose of testing this
+** program on itself.  They don't really contribute to the code.
+*/
+#define INTERFACE 1
+#define EXPORT_INTERFACE 1
+#define EXPORT
+
+/*
+** Each token in a source file is represented by an instance of
+** the following structure.  Tokens are collected onto a list.
+*/
+typedef struct Token Token;
+struct Token {
+  const char *zText;      /* The text of the token */
+  int nText;              /* Number of characters in the token's text */
+  int eType;              /* The type of this token */
+  int nLine;              /* The line number on which the token starts */
+  Token *pComment;        /* Most recent block comment before this token */
+  Token *pNext;           /* Next token on the list */
+  Token *pPrev;           /* Previous token on the list */
+};
+
+/*
+** During tokenization, information about the state of the input
+** stream is held in an instance of the following structure
+*/
+typedef struct InStream InStream;
+struct InStream {
+  const char *z;          /* Complete text of the input */
+  int i;                  /* Next character to read from the input */
+  int nLine;              /* The line number for character z[i] */
+};
+
+/*
+** Each declaration in the C or C++ source files is parsed out and stored as
+** an instance of the following structure.
+**
+** A "forward declaration" is a declaration that an object exists that
+** doesn't tell about the objects structure.  A typical forward declaration
+** is:
+**
+**          struct Xyzzy;
+**
+** Not every object has a forward declaration.  If it does, thought, the
+** forward declaration will be contained in the zFwd field for C and
+** the zFwdCpp for C++.  The zDecl field contains the complete
+** declaration text.
+*/
+typedef struct Decl Decl;
+struct Decl {
+  char *zName;       /* Name of the object being declared.  The appearance
+                     ** of this name is a source file triggers the declaration
+                     ** to be added to the header for that file. */
+  const char *zFile; /* File from which extracted.  */
+  char *zIf;         /* Surround the declaration with this #if */
+  char *zFwd;        /* A forward declaration.  NULL if there is none. */
+  char *zFwdCpp;     /* Use this forward declaration for C++. */
+  char *zDecl;       /* A full declaration of this object */
+  char *zExtra;      /* Extra declaration text inserted into class objects */
+  int extraType;     /* Last public:, protected: or private: in zExtraDecl */
+  struct Include *pInclude;   /* #includes that come before this declaration */
+  int flags;         /* See the "Properties" below */
+  Token *pComment;   /* A block comment associated with this declaration */
+  Token tokenCode;   /* Implementation of functions and procedures */
+  Decl *pSameName;   /* Next declaration with the same "zName" */
+  Decl *pSameHash;   /* Next declaration with same hash but different zName */
+  Decl *pNext;       /* Next declaration with a different name */
+};
+
+/*
+** Properties associated with declarations.
+**
+** DP_Forward and DP_Declared are used during the generation of a single
+** header file in order to prevent duplicate declarations and definitions.
+** DP_Forward is set after the object has been given a forward declaration
+** and DP_Declared is set after the object gets a full declarations.
+** (Example:  A forward declaration is "typedef struct Abc Abc;" and the
+** full declaration is "struct Abc { int a; float b; };".)
+**
+** The DP_Export and DP_Local flags are more permanent.  They mark objects
+** that have EXPORT scope and LOCAL scope respectively.  If both of these
+** marks are missing, then the object has library scope.  The meanings of
+** the scopes are as follows:
+**
+**    LOCAL scope         The object is only usable within the file in
+**                        which it is declared.
+**
+**    library scope       The object is visible and usable within other
+**                        files in the same project.  By if the project is
+**                        a library, then the object is not visible to users
+**                        of the library.  (i.e. the object does not appear
+**                        in the output when using the -H option.)
+**
+**    EXPORT scope        The object is visible and usable everywhere.
+**
+** The DP_Flag is a temporary use flag that is used during processing to
+** prevent an infinite loop.  It's use is localized.
+**
+** The DP_Cplusplus, DP_ExternCReqd and DP_ExternReqd flags are permanent
+** and are used to specify what type of declaration the object requires.
+*/
+#define DP_Forward      0x001   /* Has a forward declaration in this file */
+#define DP_Declared     0x002   /* Has a full declaration in this file */
+#define DP_Export       0x004   /* Export this declaration */
+#define DP_Local        0x008   /* Declare in its home file only */
+#define DP_Flag         0x010   /* Use to mark a subset of a Decl list
+                                ** for special processing */
+#define DP_Cplusplus    0x020   /* Has C++ linkage and cannot appear in a
+                                ** C header file */
+#define DP_ExternCReqd  0x040   /* Prepend 'extern "C"' in a C++ header.
+                                ** Prepend nothing in a C header */
+#define DP_ExternReqd   0x080   /* Prepend 'extern "C"' in a C++ header if
+                                ** DP_Cplusplus is not also set. If DP_Cplusplus
+                                ** is set or this is a C header then
+                                ** prepend 'extern' */
+
+/*
+** Convenience macros for dealing with declaration properties
+*/
+#define DeclHasProperty(D,P)    (((D)->flags&(P))==(P))
+#define DeclHasAnyProperty(D,P) (((D)->flags&(P))!=0)
+#define DeclSetProperty(D,P)    (D)->flags |= (P)
+#define DeclClearProperty(D,P)  (D)->flags &= ~(P)
+
+/*
+** These are state properties of the parser.  Each of the values is
+** distinct from the DP_ values above so that both can be used in
+** the same "flags" field.
+**
+** Be careful not to confuse PS_Export with DP_Export or
+** PS_Local with DP_Local.  Their names are similar, but the meanings
+** of these flags are very different.
+*/
+#define PS_Extern        0x000800    /* "extern" has been seen */
+#define PS_Export        0x001000    /* If between "#if EXPORT_INTERFACE"
+                                     ** and "#endif" */
+#define PS_Export2       0x002000    /* If "EXPORT" seen */
+#define PS_Typedef       0x004000    /* If "typedef" has been seen */
+#define PS_Static        0x008000    /* If "static" has been seen */
+#define PS_Interface     0x010000    /* If within #if INTERFACE..#endif */
+#define PS_Method        0x020000    /* If "::" token has been seen */
+#define PS_Local         0x040000    /* If within #if LOCAL_INTERFACE..#endif */
+#define PS_Local2        0x080000    /* If "LOCAL" seen. */
+#define PS_Public        0x100000    /* If "PUBLIC" seen. */
+#define PS_Protected     0x200000    /* If "PROTECTED" seen. */
+#define PS_Private       0x400000    /* If "PRIVATE" seen. */
+#define PS_PPP           0x700000    /* If any of PUBLIC, PRIVATE, PROTECTED */
+
+/*
+** The following set of flags are ORed into the "flags" field of
+** a Decl in order to identify what type of object is being
+** declared.
+*/
+#define TY_Class         0x00100000
+#define TY_Subroutine    0x00200000
+#define TY_Macro         0x00400000
+#define TY_Typedef       0x00800000
+#define TY_Variable      0x01000000
+#define TY_Structure     0x02000000
+#define TY_Union         0x04000000
+#define TY_Enumeration   0x08000000
+#define TY_Defunct       0x10000000  /* Used to erase a declaration */
+
+/*
+** Each nested #if (or #ifdef or #ifndef) is stored in a stack of
+** instances of the following structure.
+*/
+typedef struct Ifmacro Ifmacro;
+struct Ifmacro {
+  int nLine;         /* Line number where this macro occurs */
+  char *zCondition;  /* Text of the condition for this macro */
+  Ifmacro *pNext;    /* Next down in the stack */
+  int flags;         /* Can hold PS_Export, PS_Interface or PS_Local flags */
+};
+
+/*
+** When parsing a file, we need to keep track of what other files have
+** be #include-ed.  For each #include found, we create an instance of
+** the following structure.
+*/
+typedef struct Include Include;
+struct Include {
+  char *zFile;       /* The name of file include.  Includes "" or <> */
+  char *zIf;         /* If not NULL, #include should be enclosed in #if */
+  char *zLabel;      /* A unique label used to test if this #include has
+                      * appeared already in a file or not */
+  Include *pNext;    /* Previous include file, or NULL if this is the first */
+};
+
+/*
+** Identifiers found in a source file that might be used later to provoke
+** the copying of a declaration into the corresponding header file are
+** stored in a hash table as instances of the following structure.
+*/
+typedef struct Ident Ident;
+struct Ident {
+  char *zName;        /* The text of this identifier */
+  Ident *pCollide;    /* Next identifier with the same hash */
+  Ident *pNext;       /* Next identifier in a list of them all */
+};
+
+/*
+** A complete table of identifiers is stored in an instance of
+** the next structure.
+*/
+#define IDENT_HASH_SIZE 2237
+typedef struct IdentTable IdentTable;
+struct IdentTable {
+  Ident *pList;                     /* List of all identifiers in this table */
+  Ident *apTable[IDENT_HASH_SIZE];  /* The hash table */
+};
+
+/*
+** The following structure holds all information for a single
+** source file named on the command line of this program.
+*/
+typedef struct InFile InFile;
+struct InFile {
+  char *zSrc;              /* Name of input file */
+  char *zHdr;              /* Name of the generated .h file for this input.
+                           ** Will be NULL if input is to be scanned only */
+  int flags;               /* One or more DP_, PS_ and/or TY_ flags */
+  InFile *pNext;           /* Next input file in the list of them all */
+  IdentTable idTable;      /* All identifiers in this input file */
+};
+
+/*
+** An unbounded string is able to grow without limit.  We use these
+** to construct large in-memory strings from lots of smaller components.
+*/
+typedef struct String String;
+struct String {
+  int nAlloc;      /* Number of bytes allocated */
+  int nUsed;       /* Number of bytes used (not counting null terminator) */
+  char *zText;     /* Text of the string */
+};
+
+/*
+** The following structure contains a lot of state information used
+** while generating a .h file.  We put the information in this structure
+** and pass around a pointer to this structure, rather than pass around
+** all of the information separately.  This helps reduce the number of
+** arguments to generator functions.
+*/
+typedef struct GenState GenState;
+struct GenState {
+  String *pStr;          /* Write output to this string */
+  IdentTable *pTable;    /* A table holding the zLabel of every #include that
+                          * has already been generated.  Used to avoid
+                          * generating duplicate #includes. */
+  const char *zIf;       /* If not NULL, then we are within a #if with
+                          * this argument. */
+  int nErr;              /* Number of errors */
+  const char *zFilename; /* Name of the source file being scanned */
+  int flags;             /* Various flags (DP_ and PS_ flags above) */
+};
+
+/*
+** The following text line appears at the top of every file generated
+** by this program.  By recognizing this line, the program can be sure
+** never to read a file that it generated itself.
+**
+** The "#undef INTERFACE" part is a hack to work around a name collision
+** in MSVC 2008.
+*/
+const char zTopLine[] =
+  "/* \aThis file was automatically generated.  Do not edit! */\n"
+  "#undef INTERFACE\n";
+#define nTopLine (sizeof(zTopLine)-1)
+
+/*
+** The name of the file currently being parsed.
+*/
+static const char *zFilename;
+
+/*
+** The stack of #if macros for the file currently being parsed.
+*/
+static Ifmacro *ifStack = 0;
+
+/*
+** A list of all files that have been #included so far in a file being
+** parsed.
+*/
+static Include *includeList = 0;
+
+/*
+** The last block comment seen.
+*/
+static Token *blockComment = 0;
+
+/*
+** The following flag is set if the -doc flag appears on the
+** command line.
+*/
+static int doc_flag = 0;
+
+/*
+** If the following flag is set, then makeheaders will attempt to
+** generate prototypes for static functions and procedures.
+*/
+static int proto_static = 0;
+
+/*
+** A list of all declarations.  The list is held together using the
+** pNext field of the Decl structure.
+*/
+static Decl *pDeclFirst;    /* First on the list */
+static Decl *pDeclLast;     /* Last on the list */
+
+/*
+** A hash table of all declarations
+*/
+#define DECL_HASH_SIZE 3371
+static Decl *apTable[DECL_HASH_SIZE];
+
+/*
+** The TEST macro must be defined to something.  Make sure this is the
+** case.
+*/
+#ifndef TEST
+# define TEST 0
+#endif
+
+#ifdef NOT_USED
+/*
+** We do our own assertion macro so that we can have more control
+** over debugging.
+*/
+#define Assert(X)    if(!(X)){ CantHappen(__LINE__); }
+#define CANT_HAPPEN  CantHappen(__LINE__)
+static void CantHappen(int iLine){
+  fprintf(stderr,"Assertion failed on line %d\n",iLine);
+  *(char*)1 = 0;  /* Force a core-dump */
+}
+#endif
+
+/*
+** Memory allocation functions that are guaranteed never to return NULL.
+*/
+static void *SafeMalloc(int nByte){
+  void *p = malloc( nByte );
+  if( p==0 ){
+    fprintf(stderr,"Out of memory.  Can't allocate %d bytes.\n",nByte);
+    exit(1);
+  }
+  return p;
+}
+static void SafeFree(void *pOld){
+  if( pOld ){
+    free(pOld);
+  }
+}
+static void *SafeRealloc(void *pOld, int nByte){
+  void *p;
+  if( pOld==0 ){
+    p = SafeMalloc(nByte);
+  }else{
+    p = realloc(pOld, nByte);
+    if( p==0 ){
+      fprintf(stderr,
+        "Out of memory.  Can't enlarge an allocation to %d bytes\n",nByte);
+      exit(1);
+    }
+  }
+  return p;
+}
+static char *StrDup(const char *zSrc, int nByte){
+  char *zDest;
+  if( nByte<=0 ){
+    nByte = strlen(zSrc);
+  }
+  zDest = SafeMalloc( nByte + 1 );
+  strncpy(zDest,zSrc,nByte);
+  zDest[nByte] = 0;
+  return zDest;
+}
+
+/*
+** Return TRUE if the character X can be part of an identifier
+*/
+#define ISALNUM(X)  ((X)=='_' || isalnum(X))
+
+/*
+** Routines for dealing with unbounded strings.
+*/
+static void StringInit(String *pStr){
+  pStr->nAlloc = 0;
+  pStr->nUsed = 0;
+  pStr->zText = 0;
+}
+static void StringReset(String *pStr){
+  SafeFree(pStr->zText);
+  StringInit(pStr);
+}
+static void StringAppend(String *pStr, const char *zText, int nByte){
+  if( nByte<=0 ){
+    nByte = strlen(zText);
+  }
+  if( pStr->nUsed + nByte >= pStr->nAlloc ){
+    if( pStr->nAlloc==0 ){
+      pStr->nAlloc = nByte + 100;
+      pStr->zText = SafeMalloc( pStr->nAlloc );
+    }else{
+      pStr->nAlloc = pStr->nAlloc*2 + nByte;
+      pStr->zText = SafeRealloc(pStr->zText, pStr->nAlloc);
+    }
+  }
+  strncpy(&pStr->zText[pStr->nUsed],zText,nByte);
+  pStr->nUsed += nByte;
+  pStr->zText[pStr->nUsed] = 0;
+}
+#define StringGet(S) ((S)->zText?(S)->zText:"")
+
+/*
+** Compute a hash on a string.  The number returned is a non-negative
+** value between 0 and 2**31 - 1
+*/
+static int Hash(const char *z, int n){
+  int h = 0;
+  if( n<=0 ){
+    n = strlen(z);
+  }
+  while( n-- ){
+    h = h ^ (h<<5) ^ *z++;
+  }
+  return h & 0x7fffffff;
+}
+
+/*
+** Given an identifier name, try to find a declaration for that
+** identifier in the hash table.  If found, return a pointer to
+** the Decl structure.  If not found, return 0.
+*/
+static Decl *FindDecl(const char *zName, int len){
+  int h;
+  Decl *p;
+
+  if( len<=0 ){
+    len = strlen(zName);
+  }
+  h = Hash(zName,len) % DECL_HASH_SIZE;
+  p = apTable[h];
+  while( p && (strncmp(p->zName,zName,len)!=0 || p->zName[len]!=0) ){
+    p = p->pSameHash;
+  }
+  return p;
+}
+
+/*
+** Install the given declaration both in the hash table and on
+** the list of all declarations.
+*/
+static void InstallDecl(Decl *pDecl){
+  int h;
+  Decl *pOther;
+
+  h = Hash(pDecl->zName,0) % DECL_HASH_SIZE;
+  pOther = apTable[h];
+  while( pOther && strcmp(pDecl->zName,pOther->zName)!=0 ){
+    pOther = pOther->pSameHash;
+  }
+  if( pOther ){
+    pDecl->pSameName = pOther->pSameName;
+    pOther->pSameName = pDecl;
+  }else{
+    pDecl->pSameName = 0;
+    pDecl->pSameHash = apTable[h];
+    apTable[h] = pDecl;
+  }
+  pDecl->pNext = 0;
+  if( pDeclFirst==0 ){
+    pDeclFirst = pDeclLast = pDecl;
+  }else{
+    pDeclLast->pNext = pDecl;
+    pDeclLast = pDecl;
+  }
+}
+
+/*
+** Look at the current ifStack.  If anything declared at the current
+** position must be surrounded with
+**
+**      #if   STUFF
+**      #endif
+**
+** Then this routine computes STUFF and returns a pointer to it.  Memory
+** to hold the value returned is obtained from malloc().
+*/
+static char *GetIfString(void){
+  Ifmacro *pIf;
+  char *zResult = 0;
+  int hasIf = 0;
+  String str;
+
+  for(pIf = ifStack; pIf; pIf=pIf->pNext){
+    if( pIf->zCondition==0 || *pIf->zCondition==0 ) continue;
+    if( !hasIf ){
+      hasIf = 1;
+      StringInit(&str);
+    }else{
+      StringAppend(&str," && ",4);
+    }
+    StringAppend(&str,pIf->zCondition,0);
+  }
+  if( hasIf ){
+    zResult = StrDup(StringGet(&str),0);
+    StringReset(&str);
+  }else{
+    zResult = 0;
+  }
+  return zResult;
+}
+
+/*
+** Create a new declaration and put it in the hash table.  Also
+** return a pointer to it so that we can fill in the zFwd and zDecl
+** fields, and so forth.
+*/
+static Decl *CreateDecl(
+  const char *zName,       /* Name of the object being declared. */
+  int nName                /* Length of the name */
+){
+  Decl *pDecl;
+
+  pDecl = SafeMalloc( sizeof(Decl) + nName + 1);
+  memset(pDecl,0,sizeof(Decl));
+  pDecl->zName = (char*)&pDecl[1];
+  sprintf(pDecl->zName,"%.*s",nName,zName);
+  pDecl->zFile = zFilename;
+  pDecl->pInclude = includeList;
+  pDecl->zIf = GetIfString();
+  InstallDecl(pDecl);
+  return pDecl;
+}
+
+/*
+** Insert a new identifier into an table of identifiers.  Return TRUE if
+** a new identifier was inserted and return FALSE if the identifier was
+** already in the table.
+*/
+static int IdentTableInsert(
+  IdentTable *pTable,       /* The table into which we will insert */
+  const char *zId,          /* Name of the identifiers */
+  int nId                   /* Length of the identifier name */
+){
+  int h;
+  Ident *pId;
+
+  if( nId<=0 ){
+    nId = strlen(zId);
+  }
+  h = Hash(zId,nId) % IDENT_HASH_SIZE;
+  for(pId = pTable->apTable[h]; pId; pId=pId->pCollide){
+    if( strncmp(zId,pId->zName,nId)==0 && pId->zName[nId]==0 ){
+      /* printf("Already in table: %.*s\n",nId,zId); */
+      return 0;
+    }
+  }
+  pId = SafeMalloc( sizeof(Ident) + nId + 1 );
+  pId->zName = (char*)&pId[1];
+  sprintf(pId->zName,"%.*s",nId,zId);
+  pId->pNext = pTable->pList;
+  pTable->pList = pId;
+  pId->pCollide = pTable->apTable[h];
+  pTable->apTable[h] = pId;
+  /* printf("Add to table: %.*s\n",nId,zId); */
+  return 1;
+}
+
+/*
+** Check to see if the given value is in the given IdentTable.  Return
+** true if it is and false if it is not.
+*/
+static int IdentTableTest(
+  IdentTable *pTable,       /* The table in which to search */
+  const char *zId,          /* Name of the identifiers */
+  int nId                   /* Length of the identifier name */
+){
+  int h;
+  Ident *pId;
+
+  if( nId<=0 ){
+    nId = strlen(zId);
+  }
+  h = Hash(zId,nId) % IDENT_HASH_SIZE;
+  for(pId = pTable->apTable[h]; pId; pId=pId->pCollide){
+    if( strncmp(zId,pId->zName,nId)==0 && pId->zName[nId]==0 ){
+      return 1;
+    }
+  }
+  return 0;
+}
+
+/*
+** Remove every identifier from the given table.   Reset the table to
+** its initial state.
+*/
+static void IdentTableReset(IdentTable *pTable){
+  Ident *pId, *pNext;
+
+  for(pId = pTable->pList; pId; pId = pNext){
+    pNext = pId->pNext;
+    SafeFree(pId);
+  }
+  memset(pTable,0,sizeof(IdentTable));
+}
+
+#ifdef DEBUG
+/*
+** Print the name of every identifier in the given table, one per line
+*/
+static void IdentTablePrint(IdentTable *pTable, FILE *pOut){
+  Ident *pId;
+
+  for(pId = pTable->pList; pId; pId = pId->pNext){
+    fprintf(pOut,"%s\n",pId->zName);
+  }
+}
+#endif
+
+/*
+** Read an entire file into memory.  Return a pointer to the memory.
+**
+** The memory is obtained from SafeMalloc and must be freed by the
+** calling function.
+**
+** If the read fails for any reason, 0 is returned.
+*/
+static char *ReadFile(const char *zFilename){
+  struct stat sStat;
+  FILE *pIn;
+  char *zBuf;
+  int n;
+
+  if( stat(zFilename,&sStat)!=0
+#ifndef WIN32
+    || !S_ISREG(sStat.st_mode)
+#endif
+  ){
+    return 0;
+  }
+  pIn = fopen(zFilename,"r");
+  if( pIn==0 ){
+    return 0;
+  }
+  zBuf = SafeMalloc( sStat.st_size + 1 );
+  n = fread(zBuf,1,sStat.st_size,pIn);
+  zBuf[n] = 0;
+  fclose(pIn);
+  return zBuf;
+}
+
+/*
+** Write the contents of a string into a file.  Return the number of
+** errors
+*/
+static int WriteFile(const char *zFilename, const char *zOutput){
+  FILE *pOut;
+  pOut = fopen(zFilename,"w");
+  if( pOut==0 ){
+    return 1;
+  }
+  fwrite(zOutput,1,strlen(zOutput),pOut);
+  fclose(pOut);
+  return 0;
+}
+
+/*
+** Major token types
+*/
+#define TT_Space           1   /* Contiguous white space */
+#define TT_Id              2   /* An identifier */
+#define TT_Preprocessor    3   /* Any C preprocessor directive */
+#define TT_Comment         4   /* Either C or C++ style comment */
+#define TT_Number          5   /* Any numeric constant */
+#define TT_String          6   /* String or character constants. ".." or '.' */
+#define TT_Braces          7   /* All text between { and a matching } */
+#define TT_EOF             8   /* End of file */
+#define TT_Error           9   /* An error condition */
+#define TT_BlockComment    10  /* A C-Style comment at the left margin that
+                                * spans multiple lines */
+#define TT_Other           0   /* None of the above */
+
+/*
+** Get a single low-level token from the input file.  Update the
+** file pointer so that it points to the first character beyond the
+** token.
+**
+** A "low-level token" is any token except TT_Braces.  A TT_Braces token
+** consists of many smaller tokens and is assembled by a routine that
+** calls this one.
+**
+** The function returns the number of errors.  An error is an
+** unterminated string or character literal or an unterminated
+** comment.
+**
+** Profiling shows that this routine consumes about half the
+** CPU time on a typical run of makeheaders.
+*/
+static int GetToken(InStream *pIn, Token *pToken){
+  int i;
+  const char *z;
+  int cStart;
+  int c;
+  int startLine;   /* Line on which a structure begins */
+  int nlisc = 0;   /* True if there is a new-line in a ".." or '..' */
+  int nErr = 0;    /* Number of errors seen */
+
+  z = pIn->z;
+  i = pIn->i;
+  pToken->nLine = pIn->nLine;
+  pToken->zText = &z[i];
+  switch( z[i] ){
+    case 0:
+      pToken->eType = TT_EOF;
+      pToken->nText = 0;
+      break;
+
+    case '#':
+      if( i==0 || z[i-1]=='\n' || (i>1 && z[i-1]=='\r' && z[i-2]=='\n')){
+        /* We found a preprocessor statement */
+        pToken->eType = TT_Preprocessor;
+        i++;
+        while( z[i]!=0 && z[i]!='\n' ){
+          if( z[i]=='\\' ){
+            i++;
+            if( z[i]=='\n' ) pIn->nLine++;
+          }
+          i++;
+        }
+        pToken->nText = i - pIn->i;
+      }else{
+        /* Just an operator */
+        pToken->eType = TT_Other;
+        pToken->nText = 1;
+      }
+      break;
+
+    case ' ':
+    case '\t':
+    case '\r':
+    case '\f':
+    case '\n':
+      while( isspace(z[i]) ){
+        if( z[i]=='\n' ) pIn->nLine++;
+        i++;
+      }
+      pToken->eType = TT_Space;
+      pToken->nText = i - pIn->i;
+      break;
+
+    case '\\':
+      pToken->nText = 2;
+      pToken->eType = TT_Other;
+      if( z[i+1]=='\n' ){
+        pIn->nLine++;
+        pToken->eType = TT_Space;
+      }else if( z[i+1]==0 ){
+        pToken->nText = 1;
+      }
+      break;
+
+    case '\'':
+    case '\"':
+      cStart = z[i];
+      startLine = pIn->nLine;
+      do{
+        i++;
+        c = z[i];
+        if( c=='\n' ){
+          if( !nlisc ){
+            fprintf(stderr,
+              "%s:%d: (warning) Newline in string or character literal.\n",
+              zFilename, pIn->nLine);
+            nlisc = 1;
+          }
+          pIn->nLine++;
+        }
+        if( c=='\\' ){
+          i++;
+          c = z[i];
+          if( c=='\n' ){
+            pIn->nLine++;
+          }
+        }else if( c==cStart ){
+          i++;
+          c = 0;
+        }else if( c==0 ){
+          fprintf(stderr, "%s:%d: Unterminated string or character literal.\n",
+             zFilename, startLine);
+          nErr++;
+        }
+      }while( c );
+      pToken->eType = TT_String;
+      pToken->nText = i - pIn->i;
+      break;
+
+    case '/':
+      if( z[i+1]=='/' ){
+        /* C++ style comment */
+        while( z[i] && z[i]!='\n' ){ i++; }
+        pToken->eType = TT_Comment;
+        pToken->nText = i - pIn->i;
+      }else if( z[i+1]=='*' ){
+        /* C style comment */
+        int isBlockComment = i==0 || z[i-1]=='\n';
+        i += 2;
+        startLine = pIn->nLine;
+        while( z[i] && (z[i]!='*' || z[i+1]!='/') ){
+          if( z[i]=='\n' ){
+            pIn->nLine++;
+            if( isBlockComment ){
+              if( z[i+1]=='*' || z[i+2]=='*' ){
+                 isBlockComment = 2;
+              }else{
+                 isBlockComment = 0;
+              }
+            }
+          }
+          i++;
+        }
+        if( z[i] ){
+          i += 2;
+        }else{
+          isBlockComment = 0;
+          fprintf(stderr,"%s:%d: Unterminated comment\n",
+            zFilename, startLine);
+          nErr++;
+        }
+        pToken->eType = isBlockComment==2 ? TT_BlockComment : TT_Comment;
+        pToken->nText = i - pIn->i;
+      }else{
+        /* A divide operator */
+        pToken->eType = TT_Other;
+        pToken->nText = 1 + (z[i+1]=='+');
+      }
+      break;
+
+    case '0':
+      if( z[i+1]=='x' || z[i+1]=='X' ){
+        /* A hex constant */
+        i += 2;
+        while( isxdigit(z[i]) ){ i++; }
+      }else{
+        /* An octal constant */
+        while( isdigit(z[i]) ){ i++; }
+      }
+      pToken->eType = TT_Number;
+      pToken->nText = i - pIn->i;
+      break;
+
+    case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+      while( isdigit(z[i]) ){ i++; }
+      if( (c=z[i])=='.' ){
+         i++;
+         while( isdigit(z[i]) ){ i++; }
+         c = z[i];
+         if( c=='e' || c=='E' ){
+           i++;
+           if( ((c=z[i])=='+' || c=='-') && isdigit(z[i+1]) ){ i++; }
+           while( isdigit(z[i]) ){ i++; }
+           c = z[i];
+         }
+         if( c=='f' || c=='F' || c=='l' || c=='L' ){ i++; }
+      }else if( c=='e' || c=='E' ){
+         i++;
+         if( ((c=z[i])=='+' || c=='-') && isdigit(z[i+1]) ){ i++; }
+         while( isdigit(z[i]) ){ i++; }
+      }else if( c=='L' || c=='l' ){
+         i++;
+         c = z[i];
+         if( c=='u' || c=='U' ){ i++; }
+      }else if( c=='u' || c=='U' ){
+         i++;
+         c = z[i];
+         if( c=='l' || c=='L' ){ i++; }
+      }
+      pToken->eType = TT_Number;
+      pToken->nText = i - pIn->i;
+      break;
+
+    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
+    case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
+    case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
+    case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B':
+    case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':
+    case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P':
+    case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W':
+    case 'X': case 'Y': case 'Z': case '_':
+      while( isalnum(z[i]) || z[i]=='_' ){ i++; };
+      pToken->eType = TT_Id;
+      pToken->nText = i - pIn->i;
+      break;
+
+    case ':':
+      pToken->eType = TT_Other;
+      pToken->nText = 1 + (z[i+1]==':');
+      break;
+
+    case '=':
+    case '<':
+    case '>':
+    case '+':
+    case '-':
+    case '*':
+    case '%':
+    case '^':
+    case '&':
+    case '|':
+      pToken->eType = TT_Other;
+      pToken->nText = 1 + (z[i+1]=='=');
+      break;
+
+    default:
+      pToken->eType = TT_Other;
+      pToken->nText = 1;
+      break;
+  }
+  pIn->i += pToken->nText;
+  return nErr;
+}
+
+/*
+** This routine recovers the next token from the input file which is
+** not a space or a comment or any text between an "#if 0" and "#endif".
+**
+** This routine returns the number of errors encountered.  An error
+** is an unterminated token or unmatched "#if 0".
+**
+** Profiling shows that this routine uses about a quarter of the
+** CPU time in a typical run.
+*/
+static int GetNonspaceToken(InStream *pIn, Token *pToken){
+  int nIf = 0;
+  int inZero = 0;
+  const char *z;
+  int value;
+  int startLine;
+  int nErr = 0;
+
+  startLine = pIn->nLine;
+  while( 1 ){
+    nErr += GetToken(pIn,pToken);
+    /* printf("%04d: Type=%d nIf=%d [%.*s]\n",
+       pToken->nLine,pToken->eType,nIf,pToken->nText,
+       pToken->eType!=TT_Space ? pToken->zText : "<space>"); */
+    pToken->pComment = blockComment;
+    switch( pToken->eType ){
+      case TT_Comment:          /*0123456789 12345678 */
+       if( strncmp(pToken->zText, "/*MAKEHEADERS-STOP", 18)==0 ) return nErr;
+       break;
+
+      case TT_Space:
+        break;
+
+      case TT_BlockComment:
+        if( doc_flag ){
+          blockComment = SafeMalloc( sizeof(Token) );
+          *blockComment = *pToken;
+        }
+        break;
+
+      case TT_EOF:
+        if( nIf ){
+          fprintf(stderr,"%s:%d: Unterminated \"#if\"\n",
+             zFilename, startLine);
+          nErr++;
+        }
+        return nErr;
+
+      case TT_Preprocessor:
+        z = &pToken->zText[1];
+        while( *z==' ' || *z=='\t' ) z++;
+        if( sscanf(z,"if %d",&value)==1 && value==0 ){
+          nIf++;
+          inZero = 1;
+        }else if( inZero ){
+          if( strncmp(z,"if",2)==0 ){
+            nIf++;
+          }else if( strncmp(z,"endif",5)==0 ){
+            nIf--;
+            if( nIf==0 ) inZero = 0;
+          }
+        }else{
+          return nErr;
+        }
+        break;
+
+      default:
+        if( !inZero ){
+          return nErr;
+        }
+        break;
+    }
+  }
+  /* NOT REACHED */
+}
+
+/*
+** This routine looks for identifiers (strings of contiguous alphanumeric
+** characters) within a preprocessor directive and adds every such string
+** found to the given identifier table
+*/
+static void FindIdentifiersInMacro(Token *pToken, IdentTable *pTable){
+  Token sToken;
+  InStream sIn;
+  int go = 1;
+
+  sIn.z = pToken->zText;
+  sIn.i = 1;
+  sIn.nLine = 1;
+  while( go && sIn.i < pToken->nText ){
+    GetToken(&sIn,&sToken);
+    switch( sToken.eType ){
+      case TT_Id:
+        IdentTableInsert(pTable,sToken.zText,sToken.nText);
+        break;
+
+      case TT_EOF:
+        go = 0;
+        break;
+
+      default:
+        break;
+    }
+  }
+}
+
+/*
+** This routine gets the next token.  Everything contained within
+** {...} is collapsed into a single TT_Braces token.  Whitespace is
+** omitted.
+**
+** If pTable is not NULL, then insert every identifier seen into the
+** IdentTable.  This includes any identifiers seen inside of {...}.
+**
+** The number of errors encountered is returned.  An error is an
+** unterminated token.
+*/
+static int GetBigToken(InStream *pIn, Token *pToken, IdentTable *pTable){
+  const char *zStart;
+  int iStart;
+  int nBrace;
+  int c;
+  int nLine;
+  int nErr;
+
+  nErr = GetNonspaceToken(pIn,pToken);
+  switch( pToken->eType ){
+    case TT_Id:
+      if( pTable!=0 ){
+        IdentTableInsert(pTable,pToken->zText,pToken->nText);
+      }
+      return nErr;
+
+    case TT_Preprocessor:
+      if( pTable!=0 ){
+        FindIdentifiersInMacro(pToken,pTable);
+      }
+      return nErr;
+
+    case TT_Other:
+      if( pToken->zText[0]=='{' ) break;
+      return nErr;
+
+    default:
+      return nErr;
+  }
+
+  iStart = pIn->i;
+  zStart = pToken->zText;
+  nLine = pToken->nLine;
+  nBrace = 1;
+  while( nBrace ){
+    nErr += GetNonspaceToken(pIn,pToken);
+    /* printf("%04d: nBrace=%d [%.*s]\n",pToken->nLine,nBrace,
+       pToken->nText,pToken->zText); */
+    switch( pToken->eType ){
+      case TT_EOF:
+        fprintf(stderr,"%s:%d: Unterminated \"{\"\n",
+           zFilename, nLine);
+        nErr++;
+        pToken->eType = TT_Error;
+        return nErr;
+
+      case TT_Id:
+        if( pTable ){
+          IdentTableInsert(pTable,pToken->zText,pToken->nText);
+        }
+        break;
+
+      case TT_Preprocessor:
+        if( pTable!=0 ){
+          FindIdentifiersInMacro(pToken,pTable);
+        }
+        break;
+
+      case TT_Other:
+        if( (c = pToken->zText[0])=='{' ){
+          nBrace++;
+        }else if( c=='}' ){
+          nBrace--;
+        }
+        break;
+
+      default:
+        break;
+    }
+  }
+  pToken->eType = TT_Braces;
+  pToken->nText = 1 + pIn->i - iStart;
+  pToken->zText = zStart;
+  pToken->nLine = nLine;
+  return nErr;
+}
+
+/*
+** This routine frees up a list of Tokens.  The pComment tokens are
+** not cleared by this.  So we leak a little memory when using the -doc
+** option.  So what.
+*/
+static void FreeTokenList(Token *pList){
+  Token *pNext;
+  while( pList ){
+    pNext = pList->pNext;
+    SafeFree(pList);
+    pList = pNext;
+  }
+}
+
+/*
+** Tokenize an entire file.  Return a pointer to the list of tokens.
+**
+** Space for each token is obtained from a separate malloc() call.  The
+** calling function is responsible for freeing this space.
+**
+** If pTable is not NULL, then fill the table with all identifiers seen in
+** the input file.
+*/
+static Token *TokenizeFile(const char *zFile, IdentTable *pTable){
+  InStream sIn;
+  Token *pFirst = 0, *pLast = 0, *pNew;
+  int nErr = 0;
+
+  sIn.z = zFile;
+  sIn.i = 0;
+  sIn.nLine = 1;
+  blockComment = 0;
+
+  while( sIn.z[sIn.i]!=0 ){
+    pNew = SafeMalloc( sizeof(Token) );
+    nErr += GetBigToken(&sIn,pNew,pTable);
+    debug3(TOKENIZER, "Token on line %d: [%.*s]\n",
+       pNew->nLine, pNew->nText<50 ? pNew->nText : 50, pNew->zText);
+    if( pFirst==0 ){
+      pFirst = pLast = pNew;
+      pNew->pPrev = 0;
+    }else{
+      pLast->pNext = pNew;
+      pNew->pPrev = pLast;
+      pLast = pNew;
+    }
+    if( pNew->eType==TT_EOF ) break;
+  }
+  if( pLast ) pLast->pNext = 0;
+  blockComment = 0;
+  if( nErr ){
+    FreeTokenList(pFirst);
+    pFirst = 0;
+  }
+
+  return pFirst;
+}
+
+#if TEST==1
+/*
+** Use the following routine to test or debug the tokenizer.
+*/
+void main(int argc, char **argv){
+  char *zFile;
+  Token *pList, *p;
+  IdentTable sTable;
+
+  if( argc!=2 ){
+    fprintf(stderr,"Usage: %s filename\n",*argv);
+    exit(1);
+  }
+  memset(&sTable,0,sizeof(sTable));
+  zFile = ReadFile(argv[1]);
+  if( zFile==0 ){
+    fprintf(stderr,"Can't read file \"%s\"\n",argv[1]);
+    exit(1);
+  }
+  pList = TokenizeFile(zFile,&sTable);
+  for(p=pList; p; p=p->pNext){
+    int j;
+    switch( p->eType ){
+      case TT_Space:
+        printf("%4d: Space\n",p->nLine);
+        break;
+      case TT_Id:
+        printf("%4d: Id           %.*s\n",p->nLine,p->nText,p->zText);
+        break;
+      case TT_Preprocessor:
+        printf("%4d: Preprocessor %.*s\n",p->nLine,p->nText,p->zText);
+        break;
+      case TT_Comment:
+        printf("%4d: Comment\n",p->nLine);
+        break;
+      case TT_BlockComment:
+        printf("%4d: Block Comment\n",p->nLine);
+        break;
+      case TT_Number:
+        printf("%4d: Number       %.*s\n",p->nLine,p->nText,p->zText);
+        break;
+      case TT_String:
+        printf("%4d: String       %.*s\n",p->nLine,p->nText,p->zText);
+        break;
+      case TT_Other:
+        printf("%4d: Other        %.*s\n",p->nLine,p->nText,p->zText);
+        break;
+      case TT_Braces:
+        for(j=0; j<p->nText && j<30 && p->zText[j]!='\n'; j++){}
+        printf("%4d: Braces       %.*s...}\n",p->nLine,j,p->zText);
+        break;
+      case TT_EOF:
+        printf("%4d: End of file\n",p->nLine);
+        break;
+      default:
+        printf("%4d: type %d\n",p->nLine,p->eType);
+        break;
+    }
+  }
+  FreeTokenList(pList);
+  SafeFree(zFile);
+  IdentTablePrint(&sTable,stdout);
+}
+#endif
+
+#ifdef DEBUG
+/*
+** For debugging purposes, write out a list of tokens.
+*/
+static void PrintTokens(Token *pFirst, Token *pLast){
+  int needSpace = 0;
+  int c;
+
+  pLast = pLast->pNext;
+  while( pFirst!=pLast ){
+    switch( pFirst->eType ){
+      case TT_Preprocessor:
+        printf("\n%.*s\n",pFirst->nText,pFirst->zText);
+        needSpace = 0;
+        break;
+
+      case TT_Id:
+      case TT_Number:
+        printf("%s%.*s", needSpace ? " " : "", pFirst->nText, pFirst->zText);
+        needSpace = 1;
+        break;
+
+      default:
+        c = pFirst->zText[0];
+        printf("%s%.*s",
+          (needSpace && (c=='*' || c=='{')) ? " " : "",
+          pFirst->nText, pFirst->zText);
+        needSpace = pFirst->zText[0]==',';
+        break;
+    }
+    pFirst = pFirst->pNext;
+  }
+}
+#endif
+
+/*
+** Convert a sequence of tokens into a string and return a pointer
+** to that string.  Space to hold the string is obtained from malloc()
+** and must be freed by the calling function.
+**
+** Certain keywords (EXPORT, PRIVATE, PUBLIC, PROTECTED) are always
+** skipped.
+**
+** If pSkip!=0 then skip over nSkip tokens beginning with pSkip.
+**
+** If zTerm!=0 then append the text to the end.
+*/
+static char *TokensToString(
+  Token *pFirst,    /* First token in the string */
+  Token *pLast,     /* Last token in the string */
+  char *zTerm,      /* Terminate the string with this text if not NULL */
+  Token *pSkip,     /* Skip this token if not NULL */
+  int nSkip         /* Skip a total of this many tokens */
+){
+  char *zReturn;
+  String str;
+  int needSpace = 0;
+  int c;
+  int iSkip = 0;
+  int skipOne = 0;
+
+  StringInit(&str);
+  pLast = pLast->pNext;
+  while( pFirst!=pLast ){
+    if( pFirst==pSkip ){ iSkip = nSkip; }
+    if( iSkip>0 ){
+      iSkip--;
+      pFirst=pFirst->pNext;
+      continue;
+    }
+    switch( pFirst->eType ){
+      case TT_Preprocessor:
+        StringAppend(&str,"\n",1);
+        StringAppend(&str,pFirst->zText,pFirst->nText);
+        StringAppend(&str,"\n",1);
+        needSpace = 0;
+        break;
+
+      case TT_Id:
+        switch( pFirst->zText[0] ){
+          case 'E':
+            if( pFirst->nText==6 && strncmp(pFirst->zText,"EXPORT",6)==0 ){
+              skipOne = 1;
+            }
+            break;
+          case 'P':
+            switch( pFirst->nText ){
+              case 6:  skipOne = !strncmp(pFirst->zText,"PUBLIC", 6);    break;
+              case 7:  skipOne = !strncmp(pFirst->zText,"PRIVATE",7);    break;
+              case 9:  skipOne = !strncmp(pFirst->zText,"PROTECTED",9);  break;
+              default: break;
+            }
+            break;
+          default:
+            break;
+        }
+        if( skipOne ){
+          pFirst = pFirst->pNext;
+          continue;
+        }
+        /* Fall thru to the next case */
+      case TT_Number:
+        if( needSpace ){
+          StringAppend(&str," ",1);
+        }
+        StringAppend(&str,pFirst->zText,pFirst->nText);
+        needSpace = 1;
+        break;
+
+      default:
+        c = pFirst->zText[0];
+        if( needSpace && (c=='*' || c=='{') ){
+          StringAppend(&str," ",1);
+        }
+        StringAppend(&str,pFirst->zText,pFirst->nText);
+        /* needSpace = pFirst->zText[0]==','; */
+        needSpace = 0;
+        break;
+    }
+    pFirst = pFirst->pNext;
+  }
+  if( zTerm && *zTerm ){
+    StringAppend(&str,zTerm,strlen(zTerm));
+  }
+  zReturn = StrDup(StringGet(&str),0);
+  StringReset(&str);
+  return zReturn;
+}
+
+/*
+** This routine is called when we see one of the keywords "struct",
+** "enum", "union" or "class".  This might be the beginning of a
+** type declaration.  This routine will process the declaration and
+** remove the declaration tokens from the input stream.
+**
+** If this is a type declaration that is immediately followed by a
+** semicolon (in other words it isn't also a variable definition)
+** then set *pReset to ';'.  Otherwise leave *pReset at 0.  The
+** *pReset flag causes the parser to skip ahead to the next token
+** that begins with the value placed in the *pReset flag, if that
+** value is different from 0.
+*/
+static int ProcessTypeDecl(Token *pList, int flags, int *pReset){
+  Token *pName, *pEnd;
+  Decl *pDecl;
+  String str;
+  int need_to_collapse = 1;
+  int type = 0;
+
+  *pReset = 0;
+  if( pList==0 || pList->pNext==0 || pList->pNext->eType!=TT_Id ){
+    return 0;
+  }
+  pName = pList->pNext;
+
+  /* Catch the case of "struct Foo;" and skip it. */
+  if( pName->pNext && pName->pNext->zText[0]==';' ){
+    *pReset = ';';
+    return 0;
+  }
+
+  for(pEnd=pName->pNext; pEnd && pEnd->eType!=TT_Braces; pEnd=pEnd->pNext){
+    switch( pEnd->zText[0] ){
+      case '(':
+      case ')':
+      case '*':
+      case '[':
+      case '=':
+      case ';':
+        return 0;
+    }
+  }
+  if( pEnd==0 ){
+    return 0;
+  }
+
+  /*
+  ** At this point, we know we have a type declaration that is bounded
+  ** by pList and pEnd and has the name pName.
+  */
+
+  /*
+  ** If the braces are followed immediately by a semicolon, then we are
+  ** dealing a type declaration only.  There is not variable definition
+  ** following the type declaration.  So reset...
+  */
+  if( pEnd->pNext==0 || pEnd->pNext->zText[0]==';' ){
+    *pReset = ';';
+    need_to_collapse = 0;
+  }else{
+    need_to_collapse = 1;
+  }
+
+  if( proto_static==0 && (flags & (PS_Local|PS_Export|PS_Interface))==0 ){
+    /* Ignore these objects unless they are explicitly declared as interface,
+    ** or unless the "-local" command line option was specified. */
+    *pReset = ';';
+    return 0;
+  }
+
+#ifdef DEBUG
+  if( debugMask & PARSER ){
+    printf("**** Found type: %.*s %.*s...\n",
+      pList->nText, pList->zText, pName->nText, pName->zText);
+    PrintTokens(pList,pEnd);
+    printf(";\n");
+  }
+#endif
+
+  /*
+  ** Create a new Decl object for this definition.  Actually, if this
+  ** is a C++ class definition, then the Decl object might already exist,
+  ** so check first for that case before creating a new one.
+  */
+  switch( *pList->zText ){
+    case 'c':  type = TY_Class;        break;
+    case 's':  type = TY_Structure;    break;
+    case 'e':  type = TY_Enumeration;  break;
+    case 'u':  type = TY_Union;        break;
+    default:   /* Can't Happen */      break;
+  }
+  if( type!=TY_Class ){
+    pDecl = 0;
+  }else{
+    pDecl = FindDecl(pName->zText, pName->nText);
+    if( pDecl && (pDecl->flags & type)!=type ) pDecl = 0;
+  }
+  if( pDecl==0 ){
+    pDecl = CreateDecl(pName->zText,pName->nText);
+  }
+  if( (flags & PS_Static) || !(flags & (PS_Interface|PS_Export)) ){
+    DeclSetProperty(pDecl,DP_Local);
+  }
+  DeclSetProperty(pDecl,type);
+
+  /* The object has a full declaration only if it is contained within
+  ** "#if INTERFACE...#endif" or "#if EXPORT_INTERFACE...#endif" or
+  ** "#if LOCAL_INTERFACE...#endif".  Otherwise, we only give it a
+  ** forward declaration.
+  */
+  if( flags & (PS_Local | PS_Export | PS_Interface)  ){
+    pDecl->zDecl = TokensToString(pList,pEnd,";\n",0,0);
+  }else{
+    pDecl->zDecl = 0;
+  }
+  pDecl->pComment = pList->pComment;
+  StringInit(&str);
+  StringAppend(&str,"typedef ",0);
+  StringAppend(&str,pList->zText,pList->nText);
+  StringAppend(&str," ",0);
+  StringAppend(&str,pName->zText,pName->nText);
+  StringAppend(&str," ",0);
+  StringAppend(&str,pName->zText,pName->nText);
+  StringAppend(&str,";\n",2);
+  pDecl->zFwd = StrDup(StringGet(&str),0);
+  StringReset(&str);
+  StringInit(&str);
+  StringAppend(&str,pList->zText,pList->nText);
+  StringAppend(&str," ",0);
+  StringAppend(&str,pName->zText,pName->nText);
+  StringAppend(&str,";\n",2);
+  pDecl->zFwdCpp = StrDup(StringGet(&str),0);
+  StringReset(&str);
+  if( flags & PS_Export ){
+    DeclSetProperty(pDecl,DP_Export);
+  }else if( flags & PS_Local ){
+    DeclSetProperty(pDecl,DP_Local);
+  }
+
+  /* Here's something weird.  ANSI-C doesn't allow a forward declaration
+  ** of an enumeration.  So we have to build the typedef into the
+  ** definition.
+  */
+  if( pDecl->zDecl && DeclHasProperty(pDecl, TY_Enumeration) ){
+    StringInit(&str);
+    StringAppend(&str,pDecl->zDecl,0);
+    StringAppend(&str,pDecl->zFwd,0);
+    SafeFree(pDecl->zDecl);
+    SafeFree(pDecl->zFwd);
+    pDecl->zFwd = 0;
+    pDecl->zDecl = StrDup(StringGet(&str),0);
+    StringReset(&str);
+  }
+
+  if( pName->pNext->zText[0]==':' ){
+    DeclSetProperty(pDecl,DP_Cplusplus);
+  }
+  if( pName->nText==5 && strncmp(pName->zText,"class",5)==0 ){
+    DeclSetProperty(pDecl,DP_Cplusplus);
+  }
+
+  /*
+  ** Remove all but pList and pName from the input stream.
+  */
+  if( need_to_collapse ){
+    while( pEnd!=pName ){
+      Token *pPrev = pEnd->pPrev;
+      pPrev->pNext = pEnd->pNext;
+      pEnd->pNext->pPrev = pPrev;
+      SafeFree(pEnd);
+      pEnd = pPrev;
+    }
+  }
+  return 0;
+}
+
+/*
+** Given a list of tokens that declare something (a function, procedure,
+** variable or typedef) find the token which contains the name of the
+** thing being declared.
+**
+** Algorithm:
+**
+**   The name is:
+**
+**     1.  The first identifier that is followed by a "[", or
+**
+**     2.  The first identifier that is followed by a "(" where the
+**         "(" is followed by another identifier, or
+**
+**     3.  The first identifier followed by "::", or
+**
+**     4.  If none of the above, then the last identifier.
+**
+**   In all of the above, certain reserved words (like "char") are
+**   not considered identifiers.
+*/
+static Token *FindDeclName(Token *pFirst, Token *pLast){
+  Token *pName = 0;
+  Token *p;
+  int c;
+
+  if( pFirst==0 || pLast==0 ){
+    return 0;
+  }
+  pLast = pLast->pNext;
+  for(p=pFirst; p && p!=pLast; p=p->pNext){
+    if( p->eType==TT_Id ){
+      static IdentTable sReserved;
+      static int isInit = 0;
+      static const char *aWords[] = { "char", "class",
+       "const", "double", "enum", "extern", "EXPORT", "ET_PROC",
+       "float", "int", "long",
+       "PRIVATE", "PROTECTED", "PUBLIC",
+       "register", "static", "struct", "sizeof", "signed", "typedef",
+       "union", "volatile", "virtual", "void", };
+
+      if( !isInit ){
+        int i;
+        for(i=0; i<sizeof(aWords)/sizeof(aWords[0]); i++){
+          IdentTableInsert(&sReserved,aWords[i],0);
+        }
+        isInit = 1;
+      }
+      if( !IdentTableTest(&sReserved,p->zText,p->nText) ){
+        pName = p;
+      }
+    }else if( p==pFirst ){
+      continue;
+    }else if( (c=p->zText[0])=='[' && pName ){
+      break;
+    }else if( c=='(' && p->pNext && p->pNext->eType==TT_Id && pName ){
+      break;
+    }else if( c==':' && p->zText[1]==':' && pName ){
+      break;
+    }
+  }
+  return pName;
+}
+
+/*
+** This routine is called when we see a method for a class that begins
+** with the PUBLIC, PRIVATE, or PROTECTED keywords.  Such methods are
+** added to their class definitions.
+*/
+static int ProcessMethodDef(Token *pFirst, Token *pLast, int flags){
+  Token *pClass;
+  char *zDecl;
+  Decl *pDecl;
+  String str;
+  int type;
+
+  pLast = pLast->pPrev;
+  while( pFirst->zText[0]=='P' ){
+    int rc = 1;
+    switch( pFirst->nText ){
+      case 6:  rc = strncmp(pFirst->zText,"PUBLIC",6); break;
+      case 7:  rc = strncmp(pFirst->zText,"PRIVATE",7); break;
+      case 9:  rc = strncmp(pFirst->zText,"PROTECTED",9); break;
+      default:  break;
+    }
+    if( rc ) break;
+    pFirst = pFirst->pNext;
+  }
+  pClass = FindDeclName(pFirst,pLast);
+  if( pClass==0 ){
+    fprintf(stderr,"%s:%d: Unable to find the class name for this method\n",
+       zFilename, pFirst->nLine);
+    return 1;
+  }
+  pDecl = FindDecl(pClass->zText, pClass->nText);
+  if( pDecl==0 || (pDecl->flags & TY_Class)!=TY_Class ){
+    pDecl = CreateDecl(pClass->zText, pClass->nText);
+    DeclSetProperty(pDecl, TY_Class);
+  }
+  StringInit(&str);
+  if( pDecl->zExtra ){
+    StringAppend(&str, pDecl->zExtra, 0);
+    SafeFree(pDecl->zExtra);
+    pDecl->zExtra = 0;
+  }
+  type = flags & PS_PPP;
+  if( pDecl->extraType!=type ){
+    if( type & PS_Public ){
+      StringAppend(&str, "public:\n", 0);
+      pDecl->extraType = PS_Public;
+    }else if( type & PS_Protected ){
+      StringAppend(&str, "protected:\n", 0);
+      pDecl->extraType = PS_Protected;
+    }else if( type & PS_Private ){
+      StringAppend(&str, "private:\n", 0);
+      pDecl->extraType = PS_Private;
+    }
+  }
+  StringAppend(&str, "  ", 0);
+  zDecl = TokensToString(pFirst, pLast, ";\n", pClass, 2);
+  StringAppend(&str, zDecl, 0);
+  SafeFree(zDecl);
+  pDecl->zExtra = StrDup(StringGet(&str), 0);
+  StringReset(&str);
+  return 0;
+}
+
+/*
+** This routine is called when we see a function or procedure definition.
+** We make an entry in the declaration table that is a prototype for this
+** function or procedure.
+*/
+static int ProcessProcedureDef(Token *pFirst, Token *pLast, int flags){
+  Token *pName;
+  Decl *pDecl;
+  Token *pCode;
+
+  if( pFirst==0 || pLast==0 ){
+    return 0;
+  }
+  if( flags & PS_Method ){
+    if( flags & PS_PPP ){
+      return ProcessMethodDef(pFirst, pLast, flags);
+    }else{
+      return 0;
+    }
+  }
+  if( (flags & PS_Static)!=0 && !proto_static ){
+    return 0;
+  }
+  pCode = pLast;
+  while( pLast && pLast!=pFirst && pLast->zText[0]!=')' ){
+    pLast = pLast->pPrev;
+  }
+  if( pLast==0 || pLast==pFirst || pFirst->pNext==pLast ){
+    fprintf(stderr,"%s:%d: Unrecognized syntax.\n",
+      zFilename, pFirst->nLine);
+    return 1;
+  }
+  if( flags & (PS_Interface|PS_Export|PS_Local) ){
+    fprintf(stderr,"%s:%d: Missing \"inline\" on function or procedure.\n",
+      zFilename, pFirst->nLine);
+    return 1;
+  }
+  pName = FindDeclName(pFirst,pLast);
+  if( pName==0 ){
+    fprintf(stderr,"%s:%d: Malformed function or procedure definition.\n",
+      zFilename, pFirst->nLine);
+    return 1;
+  }
+
+  /*
+  ** At this point we've isolated a procedure declaration between pFirst
+  ** and pLast with the name pName.
+  */
+#ifdef DEBUG
+  if( debugMask & PARSER ){
+    printf("**** Found routine: %.*s on line %d...\n", pName->nText,
+       pName->zText, pFirst->nLine);
+    PrintTokens(pFirst,pLast);
+    printf(";\n");
+  }
+#endif
+  pDecl = CreateDecl(pName->zText,pName->nText);
+  pDecl->pComment = pFirst->pComment;
+  if( pCode && pCode->eType==TT_Braces ){
+    pDecl->tokenCode = *pCode;
+  }
+  DeclSetProperty(pDecl,TY_Subroutine);
+  pDecl->zDecl = TokensToString(pFirst,pLast,";\n",0,0);
+  if( (flags & (PS_Static|PS_Local2))!=0 ){
+    DeclSetProperty(pDecl,DP_Local);
+  }else if( (flags & (PS_Export2))!=0 ){
+    DeclSetProperty(pDecl,DP_Export);
+  }
+
+  if( flags & DP_Cplusplus ){
+    DeclSetProperty(pDecl,DP_Cplusplus);
+  }else{
+    DeclSetProperty(pDecl,DP_ExternCReqd);
+  }
+
+  return 0;
+}
+
+/*
+** This routine is called whenever we see the "inline" keyword.  We
+** need to seek-out the inline function or procedure and make a
+** declaration out of the entire definition.
+*/
+static int ProcessInlineProc(Token *pFirst, int flags, int *pReset){
+  Token *pName;
+  Token *pEnd;
+  Decl *pDecl;
+
+  for(pEnd=pFirst; pEnd; pEnd = pEnd->pNext){
+    if( pEnd->zText[0]=='{' || pEnd->zText[0]==';' ){
+      *pReset = pEnd->zText[0];
+      break;
+    }
+  }
+  if( pEnd==0 ){
+    *pReset = ';';
+    fprintf(stderr,"%s:%d: incomplete inline procedure definition\n",
+      zFilename, pFirst->nLine);
+    return 1;
+  }
+  pName = FindDeclName(pFirst,pEnd);
+  if( pName==0 ){
+    fprintf(stderr,"%s:%d: malformed inline procedure definition\n",
+      zFilename, pFirst->nLine);
+    return 1;
+  }
+
+#ifdef DEBUG
+  if( debugMask & PARSER ){
+    printf("**** Found inline routine: %.*s on line %d...\n",
+       pName->nText, pName->zText, pFirst->nLine);
+    PrintTokens(pFirst,pEnd);
+    printf("\n");
+  }
+#endif
+  pDecl = CreateDecl(pName->zText,pName->nText);
+  pDecl->pComment = pFirst->pComment;
+  DeclSetProperty(pDecl,TY_Subroutine);
+  pDecl->zDecl = TokensToString(pFirst,pEnd,";\n",0,0);
+  if( (flags & (PS_Static|PS_Local|PS_Local2)) ){
+    DeclSetProperty(pDecl,DP_Local);
+  }else if( flags & (PS_Export|PS_Export2) ){
+    DeclSetProperty(pDecl,DP_Export);
+  }
+
+  if( flags & DP_Cplusplus ){
+    DeclSetProperty(pDecl,DP_Cplusplus);
+  }else{
+    DeclSetProperty(pDecl,DP_ExternCReqd);
+  }
+
+  return 0;
+}
+
+/*
+** Determine if the tokens between pFirst and pEnd form a variable
+** definition or a function prototype.  Return TRUE if we are dealing
+** with a variable defintion and FALSE for a prototype.
+**
+** pEnd is the token that ends the object.  It can be either a ';' or
+** a '='.  If it is '=', then assume we have a variable definition.
+**
+** If pEnd is ';', then the determination is more difficult.  We have
+** to search for an occurrence of an ID followed immediately by '('.
+** If found, we have a prototype.  Otherwise we are dealing with a
+** variable definition.
+*/
+static int isVariableDef(Token *pFirst, Token *pEnd){
+  if( pEnd && pEnd->zText[0]=='=' &&
+    (pEnd->pPrev->nText!=8 || strncmp(pEnd->pPrev->zText,"operator",8)!=0)
+  ){
+    return 1;
+  }
+  while( pFirst && pFirst!=pEnd && pFirst->pNext && pFirst->pNext!=pEnd ){
+    if( pFirst->eType==TT_Id && pFirst->pNext->zText[0]=='(' ){
+      return 0;
+    }
+    pFirst = pFirst->pNext;
+  }
+  return 1;
+}
+
+/*
+** Return TRUE if pFirst is the first token of a static assert.
+*/
+static int isStaticAssert(Token *pFirst){
+  if( (pFirst->nText==13 && strncmp(pFirst->zText, "static_assert", 13)==0)
+   || (pFirst->nText==14 && strncmp(pFirst->zText, "_Static_assert", 14)==0)
+  ){
+    return 1;
+  }else{
+    return 0;
+  }
+}
+
+/*
+** This routine is called whenever we encounter a ";" or "=".  The stuff
+** between pFirst and pLast constitutes either a typedef or a global
+** variable definition.  Do the right thing.
+*/
+static int ProcessDecl(Token *pFirst, Token *pEnd, int flags){
+  Token *pName;
+  Decl *pDecl;
+  int isLocal = 0;
+  int isVar;
+  int nErr = 0;
+
+  if( pFirst==0 || pEnd==0 ){
+    return 0;
+  }
+  if( flags & PS_Typedef ){
+    if( (flags & (PS_Export2|PS_Local2))!=0 ){
+      fprintf(stderr,"%s:%d: \"EXPORT\" or \"LOCAL\" ignored before typedef.\n",
+        zFilename, pFirst->nLine);
+      nErr++;
+    }
+    if( (flags & (PS_Interface|PS_Export|PS_Local|DP_Cplusplus))==0 ){
+      /* It is illegal to duplicate a typedef in C (but OK in C++).
+      ** So don't record typedefs that aren't within a C++ file or
+      ** within #if INTERFACE..#endif */
+      return nErr;
+    }
+    if( (flags & (PS_Interface|PS_Export|PS_Local))==0 && proto_static==0 ){
+      /* Ignore typedefs that are not with "#if INTERFACE..#endif" unless
+      ** the "-local" command line option is used. */
+      return nErr;
+    }
+    if( (flags & (PS_Interface|PS_Export))==0 ){
+      /* typedefs are always local, unless within #if INTERFACE..#endif */
+      isLocal = 1;
+    }
+  }else if( flags & (PS_Static|PS_Local2) ){
+    if( proto_static==0 && (flags & PS_Local2)==0 ){
+      /* Don't record static variables unless the "-local" command line
+      ** option was specified or the "LOCAL" keyword is used. */
+      return nErr;
+    }
+    while( pFirst!=0 && pFirst->pNext!=pEnd &&
+       ((pFirst->nText==6 && strncmp(pFirst->zText,"static",6)==0)
+        || (pFirst->nText==5 && strncmp(pFirst->zText,"LOCAL",6)==0))
+    ){
+      /* Lose the initial "static" or local from local variables.
+      ** We'll prepend "extern" later. */
+      pFirst = pFirst->pNext;
+      isLocal = 1;
+    }
+    if( pFirst==0 || !isLocal ){
+      return nErr;
+    }
+  }else if( flags & PS_Method ){
+    /* Methods are declared by their class.  Don't declare separately. */
+    return nErr;
+  }else if( isStaticAssert(pFirst) ){
+    return 0;
+  }
+  isVar =  (flags & (PS_Typedef|PS_Method))==0 && isVariableDef(pFirst,pEnd);
+  if( isVar && (flags & (PS_Interface|PS_Export|PS_Local))!=0
+  && (flags & PS_Extern)==0 ){
+    fprintf(stderr,"%s:%d: Can't define a variable in this context\n",
+      zFilename, pFirst->nLine);
+    nErr++;
+  }
+  pName = FindDeclName(pFirst,pEnd->pPrev);
+  if( pName==0 ){
+    if( pFirst->nText==4 && strncmp(pFirst->zText,"enum",4)==0 ){
+      /* Ignore completely anonymous enums.  See documentation section 3.8.1. */
+      return nErr;
+    }else{
+      fprintf(stderr,"%s:%d: Can't find a name for the object declared here.\n",
+        zFilename, pFirst->nLine);
+      return nErr+1;
+    }
+  }
+
+#ifdef DEBUG
+  if( debugMask & PARSER ){
+    if( flags & PS_Typedef ){
+      printf("**** Found typedef %.*s at line %d...\n",
+        pName->nText, pName->zText, pName->nLine);
+    }else if( isVar ){
+      printf("**** Found variable %.*s at line %d...\n",
+        pName->nText, pName->zText, pName->nLine);
+    }else{
+      printf("**** Found prototype %.*s at line %d...\n",
+        pName->nText, pName->zText, pName->nLine);
+    }
+    PrintTokens(pFirst,pEnd->pPrev);
+    printf(";\n");
+  }
+#endif
+
+  pDecl = CreateDecl(pName->zText,pName->nText);
+  if( (flags & PS_Typedef) ){
+    DeclSetProperty(pDecl, TY_Typedef);
+  }else if( isVar ){
+    DeclSetProperty(pDecl,DP_ExternReqd | TY_Variable);
+    if( !(flags & DP_Cplusplus) ){
+      DeclSetProperty(pDecl,DP_ExternCReqd);
+    }
+  }else{
+    DeclSetProperty(pDecl, TY_Subroutine);
+    if( !(flags & DP_Cplusplus) ){
+      DeclSetProperty(pDecl,DP_ExternCReqd);
+    }
+  }
+  pDecl->pComment = pFirst->pComment;
+  pDecl->zDecl = TokensToString(pFirst,pEnd->pPrev,";\n",0,0);
+  if( isLocal || (flags & (PS_Local|PS_Local2))!=0 ){
+    DeclSetProperty(pDecl,DP_Local);
+  }else if( flags & (PS_Export|PS_Export2) ){
+    DeclSetProperty(pDecl,DP_Export);
+  }
+  if( flags & DP_Cplusplus ){
+    DeclSetProperty(pDecl,DP_Cplusplus);
+  }
+  return nErr;
+}
+
+/*
+** Push an if condition onto the if stack
+*/
+static void PushIfMacro(
+  const char *zPrefix,      /* A prefix, like "define" or "!" */
+  const char *zText,        /* The condition */
+  int nText,                /* Number of characters in zText */
+  int nLine,                /* Line number where this macro occurs */
+  int flags                 /* Either 0, PS_Interface, PS_Export or PS_Local */
+){
+  Ifmacro *pIf;
+  int nByte;
+
+  nByte = sizeof(Ifmacro);
+  if( zText ){
+    if( zPrefix ){
+      nByte += strlen(zPrefix) + 2;
+    }
+    nByte += nText + 1;
+  }
+  pIf = SafeMalloc( nByte );
+  if( zText ){
+    pIf->zCondition = (char*)&pIf[1];
+    if( zPrefix ){
+      sprintf(pIf->zCondition,"%s(%.*s)",zPrefix,nText,zText);
+    }else{
+      sprintf(pIf->zCondition,"%.*s",nText,zText);
+    }
+  }else{
+    pIf->zCondition = 0;
+  }
+  pIf->nLine = nLine;
+  pIf->flags = flags;
+  pIf->pNext = ifStack;
+  ifStack = pIf;
+}
+
+/*
+** This routine is called to handle all preprocessor directives.
+**
+** This routine will recompute the value of *pPresetFlags to be the
+** logical or of all flags on all nested #ifs.  The #ifs that set flags
+** are as follows:
+**
+**        conditional                   flag set
+**        ------------------------      --------------------
+**        #if INTERFACE                 PS_Interface
+**        #if EXPORT_INTERFACE          PS_Export
+**        #if LOCAL_INTERFACE           PS_Local
+**
+** For example, if after processing the preprocessor token given
+** by pToken there is an "#if INTERFACE" on the preprocessor
+** stack, then *pPresetFlags will be set to PS_Interface.
+*/
+static int ParsePreprocessor(Token *pToken, int flags, int *pPresetFlags){
+  const char *zCmd;
+  int nCmd;
+  const char *zArg;
+  int nArg;
+  int nErr = 0;
+  Ifmacro *pIf;
+
+  zCmd = &pToken->zText[1];
+  while( isspace(*zCmd) && *zCmd!='\n' ){
+    zCmd++;
+  }
+  if( !isalpha(*zCmd) ){
+    return 0;
+  }
+  nCmd = 1;
+  while( isalpha(zCmd[nCmd]) ){
+    nCmd++;
+  }
+
+  if( nCmd==5 && strncmp(zCmd,"endif",5)==0 ){
+    /*
+    ** Pop the if stack
+    */
+    pIf = ifStack;
+    if( pIf==0 ){
+      fprintf(stderr,"%s:%d: extra '#endif'.\n",zFilename,pToken->nLine);
+      return 1;
+    }
+    ifStack = pIf->pNext;
+    SafeFree(pIf);
+  }else if( nCmd==6 && strncmp(zCmd,"define",6)==0 ){
+    /*
+    ** Record a #define if we are in PS_Interface or PS_Export
+    */
+    Decl *pDecl;
+    if( !(flags & (PS_Local|PS_Interface|PS_Export)) ){ return 0; }
+    zArg = &zCmd[6];
+    while( *zArg && isspace(*zArg) && *zArg!='\n' ){
+      zArg++;
+    }
+    if( *zArg==0 || *zArg=='\n' ){ return 0; }
+    for(nArg=0; ISALNUM(zArg[nArg]); nArg++){}
+    if( nArg==0 ){ return 0; }
+    pDecl = CreateDecl(zArg,nArg);
+    pDecl->pComment = pToken->pComment;
+    DeclSetProperty(pDecl,TY_Macro);
+    pDecl->zDecl = SafeMalloc( pToken->nText + 2 );
+    sprintf(pDecl->zDecl,"%.*s\n",pToken->nText,pToken->zText);
+    if( flags & PS_Export ){
+      DeclSetProperty(pDecl,DP_Export);
+    }else if( flags & PS_Local ){
+      DeclSetProperty(pDecl,DP_Local);
+    }
+  }else if( nCmd==7 && strncmp(zCmd,"include",7)==0 ){
+    /*
+    ** Record an #include if we are in PS_Interface or PS_Export
+    */
+    Include *pInclude;
+    char *zIf;
+
+    if( !(flags & (PS_Interface|PS_Export)) ){ return 0; }
+    zArg = &zCmd[7];
+    while( *zArg && isspace(*zArg) ){ zArg++; }
+    for(nArg=0; !isspace(zArg[nArg]); nArg++){}
+    if( (zArg[0]=='"' && zArg[nArg-1]!='"')
+      ||(zArg[0]=='<' && zArg[nArg-1]!='>')
+    ){
+      fprintf(stderr,"%s:%d: malformed #include statement.\n",
+        zFilename,pToken->nLine);
+      return 1;
+    }
+    zIf = GetIfString();
+    if( zIf ){
+      pInclude = SafeMalloc( sizeof(Include) + nArg*2 + strlen(zIf) + 10 );
+      pInclude->zFile = (char*)&pInclude[1];
+      pInclude->zLabel = &pInclude->zFile[nArg+1];
+      sprintf(pInclude->zFile,"%.*s",nArg,zArg);
+      sprintf(pInclude->zLabel,"%.*s:%s",nArg,zArg,zIf);
+      pInclude->zIf = &pInclude->zLabel[nArg+1];
+      SafeFree(zIf);
+    }else{
+      pInclude = SafeMalloc( sizeof(Include) + nArg + 1 );
+      pInclude->zFile = (char*)&pInclude[1];
+      sprintf(pInclude->zFile,"%.*s",nArg,zArg);
+      pInclude->zIf = 0;
+      pInclude->zLabel = pInclude->zFile;
+    }
+    pInclude->pNext = includeList;
+    includeList = pInclude;
+  }else if( nCmd==2 && strncmp(zCmd,"if",2)==0 ){
+    /*
+    ** Push an #if.  Watch for the special cases of INTERFACE
+    ** and EXPORT_INTERFACE and LOCAL_INTERFACE
+    */
+    zArg = &zCmd[2];
+    while( *zArg && isspace(*zArg) && *zArg!='\n' ){
+      zArg++;
+    }
+    if( *zArg==0 || *zArg=='\n' ){ return 0; }
+    nArg = pToken->nText + (int)(pToken->zText - zArg);
+    if( nArg==9 && strncmp(zArg,"INTERFACE",9)==0 ){
+      PushIfMacro(0,0,0,pToken->nLine,PS_Interface);
+    }else if( nArg==16 && strncmp(zArg,"EXPORT_INTERFACE",16)==0 ){
+      PushIfMacro(0,0,0,pToken->nLine,PS_Export);
+    }else if( nArg==15 && strncmp(zArg,"LOCAL_INTERFACE",15)==0 ){
+      PushIfMacro(0,0,0,pToken->nLine,PS_Local);
+    }else if( nArg==15 && strncmp(zArg,"MAKEHEADERS_STOPLOCAL_INTERFACE",15)==0 ){
+      PushIfMacro(0,0,0,pToken->nLine,PS_Local);
+    }else{
+      PushIfMacro(0,zArg,nArg,pToken->nLine,0);
+    }
+  }else if( nCmd==5 && strncmp(zCmd,"ifdef",5)==0 ){
+    /*
+    ** Push an #ifdef.
+    */
+    zArg = &zCmd[5];
+    while( *zArg && isspace(*zArg) && *zArg!='\n' ){
+      zArg++;
+    }
+    if( *zArg==0 || *zArg=='\n' ){ return 0; }
+    nArg = pToken->nText + (int)(pToken->zText - zArg);
+    PushIfMacro("defined",zArg,nArg,pToken->nLine,0);
+  }else if( nCmd==6 && strncmp(zCmd,"ifndef",6)==0 ){
+    /*
+    ** Push an #ifndef.
+    */
+    zArg = &zCmd[6];
+    while( *zArg && isspace(*zArg) && *zArg!='\n' ){
+      zArg++;
+    }
+    if( *zArg==0 || *zArg=='\n' ){ return 0; }
+    nArg = pToken->nText + (int)(pToken->zText - zArg);
+    PushIfMacro("!defined",zArg,nArg,pToken->nLine,0);
+  }else if( nCmd==4 && strncmp(zCmd,"else",4)==0 ){
+    /*
+    ** Invert the #if on the top of the stack
+    */
+    if( ifStack==0 ){
+      fprintf(stderr,"%s:%d: '#else' without an '#if'\n",zFilename,
+         pToken->nLine);
+      return 1;
+    }
+    pIf = ifStack;
+    if( pIf->zCondition ){
+      ifStack = ifStack->pNext;
+      PushIfMacro("!",pIf->zCondition,strlen(pIf->zCondition),pIf->nLine,0);
+      SafeFree(pIf);
+    }else{
+      pIf->flags = 0;
+    }
+  }else{
+    /*
+    ** This directive can be safely ignored
+    */
+    return 0;
+  }
+
+  /*
+  ** Recompute the preset flags
+  */
+  *pPresetFlags = 0;
+  for(pIf = ifStack; pIf; pIf=pIf->pNext){
+    *pPresetFlags |= pIf->flags;
+  }
+
+  return nErr;
+}
+
+/*
+** Parse an entire file.  Return the number of errors.
+**
+** pList is a list of tokens in the file.  Whitespace tokens have been
+** eliminated, and text with {...} has been collapsed into a
+** single TT_Brace token.
+**
+** initFlags are a set of parse flags that should always be set for this
+** file.  For .c files this is normally 0.  For .h files it is PS_Interface.
+*/
+static int ParseFile(Token *pList, int initFlags){
+  int nErr = 0;
+  Token *pStart = 0;
+  int flags = initFlags;
+  int presetFlags = initFlags;
+  int resetFlag = 0;
+
+  includeList = 0;
+  while( pList ){
+    switch( pList->eType ){
+    case TT_EOF:
+      goto end_of_loop;
+
+    case TT_Preprocessor:
+      nErr += ParsePreprocessor(pList,flags,&presetFlags);
+      pStart = 0;
+      presetFlags |= initFlags;
+      flags = presetFlags;
+      break;
+
+    case TT_Other:
+      switch( pList->zText[0] ){
+      case ';':
+        nErr += ProcessDecl(pStart,pList,flags);
+        pStart = 0;
+        flags = presetFlags;
+        break;
+
+      case '=':
+        if( pList->pPrev->nText==8
+            && strncmp(pList->pPrev->zText,"operator",8)==0 ){
+          break;
+        }
+        nErr += ProcessDecl(pStart,pList,flags);
+        pStart = 0;
+        while( pList && pList->zText[0]!=';' ){
+          pList = pList->pNext;
+        }
+        if( pList==0 ) goto end_of_loop;
+        flags = presetFlags;
+        break;
+
+      case ':':
+        if( pList->zText[1]==':' ){
+          flags |= PS_Method;
+        }
+        break;
+
+      default:
+        break;
+      }
+      break;
+
+    case TT_Braces:
+      nErr += ProcessProcedureDef(pStart,pList,flags);
+      pStart = 0;
+      flags = presetFlags;
+      break;
+
+    case TT_Id:
+       if( pStart==0 ){
+          pStart = pList;
+          flags = presetFlags;
+       }
+       resetFlag = 0;
+       switch( pList->zText[0] ){
+       case 'c':
+         if( pList->nText==5 && strncmp(pList->zText,"class",5)==0 ){
+           nErr += ProcessTypeDecl(pList,flags,&resetFlag);
+         }
+         break;
+
+       case 'E':
+         if( pList->nText==6 && strncmp(pList->zText,"EXPORT",6)==0 ){
+           flags |= PS_Export2;
+           /* pStart = 0; */
+         }
+         break;
+
+       case 'e':
+         if( pList->nText==4 && strncmp(pList->zText,"enum",4)==0 ){
+           if( pList->pNext && pList->pNext->eType==TT_Braces ){
+             pList = pList->pNext;
+           }else{
+             nErr += ProcessTypeDecl(pList,flags,&resetFlag);
+           }
+         }else if( pList->nText==6 && strncmp(pList->zText,"extern",6)==0 ){
+           pList = pList->pNext;
+           if( pList && pList->nText==3 && strncmp(pList->zText,"\"C\"",3)==0 ){
+             pList = pList->pNext;
+             flags &= ~DP_Cplusplus;
+           }else{
+             flags |= PS_Extern;
+           }
+           pStart = pList;
+         }
+         break;
+
+       case 'i':
+         if( pList->nText==6 && strncmp(pList->zText,"inline",6)==0
+          && (flags & PS_Static)==0
+         ){
+           nErr += ProcessInlineProc(pList,flags,&resetFlag);
+         }
+         break;
+
+       case 'L':
+         if( pList->nText==5 && strncmp(pList->zText,"LOCAL",5)==0 ){
+           flags |= PS_Local2;
+           pStart = pList;
+         }
+         break;
+
+       case 'P':
+         if( pList->nText==6 && strncmp(pList->zText, "PUBLIC",6)==0 ){
+           flags |= PS_Public;
+           pStart = pList;
+         }else if( pList->nText==7 && strncmp(pList->zText, "PRIVATE",7)==0 ){
+           flags |= PS_Private;
+           pStart = pList;
+         }else if( pList->nText==9 && strncmp(pList->zText,"PROTECTED",9)==0 ){
+           flags |= PS_Protected;
+           pStart = pList;
+         }
+         break;
+
+       case 's':
+         if( pList->nText==6 && strncmp(pList->zText,"struct",6)==0 ){
+           if( pList->pNext && pList->pNext->eType==TT_Braces ){
+             pList = pList->pNext;
+           }else{
+             nErr += ProcessTypeDecl(pList,flags,&resetFlag);
+           }
+         }else if( pList->nText==6 && strncmp(pList->zText,"static",6)==0 ){
+           flags |= PS_Static;
+         }
+         break;
+
+       case 't':
+         if( pList->nText==7 && strncmp(pList->zText,"typedef",7)==0 ){
+           flags |= PS_Typedef;
+         }
+         break;
+
+       case 'u':
+         if( pList->nText==5 && strncmp(pList->zText,"union",5)==0 ){
+           if( pList->pNext && pList->pNext->eType==TT_Braces ){
+             pList = pList->pNext;
+           }else{
+             nErr += ProcessTypeDecl(pList,flags,&resetFlag);
+           }
+         }
+         break;
+
+       default:
+         break;
+       }
+       if( resetFlag!=0 ){
+         while( pList && pList->zText[0]!=resetFlag ){
+           pList = pList->pNext;
+         }
+         if( pList==0 ) goto end_of_loop;
+         pStart = 0;
+         flags = presetFlags;
+       }
+       break;
+
+    case TT_String:
+    case TT_Number:
+       break;
+
+    default:
+       pStart = pList;
+       flags = presetFlags;
+       break;
+    }
+    pList = pList->pNext;
+  }
+  end_of_loop:
+
+  /* Verify that all #ifs have a matching "#endif" */
+  while( ifStack ){
+    Ifmacro *pIf = ifStack;
+    ifStack = pIf->pNext;
+    fprintf(stderr,"%s:%d: This '#if' has no '#endif'\n",zFilename,
+      pIf->nLine);
+    SafeFree(pIf);
+  }
+
+  return nErr;
+}
+
+/*
+** If the given Decl object has a non-null zExtra field, then the text
+** of that zExtra field needs to be inserted in the middle of the
+** zDecl field before the last "}" in the zDecl.  This routine does that.
+** If the zExtra is NULL, this routine is a no-op.
+**
+** zExtra holds extra method declarations for classes.  The declarations
+** have to be inserted into the class definition.
+*/
+static void InsertExtraDecl(Decl *pDecl){
+  int i;
+  String str;
+
+  if( pDecl==0 || pDecl->zExtra==0 || pDecl->zDecl==0 ) return;
+  i = strlen(pDecl->zDecl) - 1;
+  while( i>0 && pDecl->zDecl[i]!='}' ){ i--; }
+  StringInit(&str);
+  StringAppend(&str, pDecl->zDecl, i);
+  StringAppend(&str, pDecl->zExtra, 0);
+  StringAppend(&str, &pDecl->zDecl[i], 0);
+  SafeFree(pDecl->zDecl);
+  SafeFree(pDecl->zExtra);
+  pDecl->zDecl = StrDup(StringGet(&str), 0);
+  StringReset(&str);
+  pDecl->zExtra = 0;
+}
+
+/*
+** Reset the DP_Forward and DP_Declared flags on all Decl structures.
+** Set both flags for anything that is tagged as local and isn't
+** in the file zFilename so that it won't be printing in other files.
+*/
+static void ResetDeclFlags(char *zFilename){
+  Decl *pDecl;
+
+  for(pDecl = pDeclFirst; pDecl; pDecl = pDecl->pNext){
+    DeclClearProperty(pDecl,DP_Forward|DP_Declared);
+    if( DeclHasProperty(pDecl,DP_Local) && pDecl->zFile!=zFilename ){
+      DeclSetProperty(pDecl,DP_Forward|DP_Declared);
+    }
+  }
+}
+
+/*
+** Forward declaration of the ScanText() function.
+*/
+static void ScanText(const char*, GenState *pState);
+
+/*
+** The output in pStr is currently within an #if CONTEXT where context
+** is equal to *pzIf.  (*pzIf might be NULL to indicate that we are
+** not within any #if at the moment.)  We are getting ready to output
+** some text that needs to be within the context of "#if NEW" where
+** NEW is zIf.  Make an appropriate change to the context.
+*/
+static void ChangeIfContext(
+  const char *zIf,       /* The desired #if context */
+  GenState *pState       /* Current state of the code generator */
+){
+  if( zIf==0 ){
+    if( pState->zIf==0 ) return;
+    StringAppend(pState->pStr,"#endif\n",0);
+    pState->zIf = 0;
+  }else{
+    if( pState->zIf ){
+      if( strcmp(zIf,pState->zIf)==0 ) return;
+      StringAppend(pState->pStr,"#endif\n",0);
+      pState->zIf = 0;
+    }
+    ScanText(zIf, pState);
+    if( pState->zIf!=0 ){
+      StringAppend(pState->pStr,"#endif\n",0);
+    }
+    StringAppend(pState->pStr,"#if ",0);
+    StringAppend(pState->pStr,zIf,0);
+    StringAppend(pState->pStr,"\n",0);
+    pState->zIf = zIf;
+  }
+}
+
+/*
+** Add to the string pStr a #include of every file on the list of
+** include files pInclude.  The table pTable contains all files that
+** have already been #included at least once.  Don't add any
+** duplicates.  Update pTable with every new #include that is added.
+*/
+static void AddIncludes(
+  Include *pInclude,       /* Write every #include on this list */
+  GenState *pState         /* Current state of the code generator */
+){
+  if( pInclude ){
+    if( pInclude->pNext ){
+      AddIncludes(pInclude->pNext,pState);
+    }
+    if( IdentTableInsert(pState->pTable,pInclude->zLabel,0) ){
+      ChangeIfContext(pInclude->zIf,pState);
+      StringAppend(pState->pStr,"#include ",0);
+      StringAppend(pState->pStr,pInclude->zFile,0);
+      StringAppend(pState->pStr,"\n",1);
+    }
+  }
+}
+
+/*
+** Add to the string pStr a declaration for the object described
+** in pDecl.
+**
+** If pDecl has already been declared in this file, detect that
+** fact and abort early.  Do not duplicate a declaration.
+**
+** If the needFullDecl flag is false and this object has a forward
+** declaration, then supply the forward declaration only.  A later
+** call to CompleteForwardDeclarations() will finish the declaration
+** for us.  But if needFullDecl is true, we must supply the full
+** declaration now.  Some objects do not have a forward declaration.
+** For those objects, we must print the full declaration now.
+**
+** Because it is illegal to duplicate a typedef in C, care is taken
+** to insure that typedefs for the same identifier are only issued once.
+*/
+static void DeclareObject(
+  Decl *pDecl,        /* The thing to be declared */
+  GenState *pState,   /* Current state of the code generator */
+  int needFullDecl    /* Must have the full declaration.  A forward
+                       * declaration isn't enough */
+){
+  Decl *p;               /* The object to be declared */
+  int flag;
+  int isCpp;             /* True if generating C++ */
+  int doneTypedef = 0;   /* True if a typedef has been done for this object */
+
+  /* printf("BEGIN %s of %s\n",needFullDecl?"FULL":"PROTOTYPE",pDecl->zName);*/
+  /*
+  ** For any object that has a forward declaration, go ahead and do the
+  ** forward declaration first.
+  */
+  isCpp = (pState->flags & DP_Cplusplus) != 0;
+  for(p=pDecl; p; p=p->pSameName){
+    if( p->zFwd ){
+      if( !DeclHasProperty(p,DP_Forward) ){
+        DeclSetProperty(p,DP_Forward);
+        if( strncmp(p->zFwd,"typedef",7)==0 ){
+          if( doneTypedef ) continue;
+          doneTypedef = 1;
+        }
+        ChangeIfContext(p->zIf,pState);
+        StringAppend(pState->pStr,isCpp ? p->zFwdCpp : p->zFwd,0);
+      }
+    }
+  }
+
+  /*
+  ** Early out if everything is already suitably declared.
+  **
+  ** This is a very important step because it prevents us from
+  ** executing the code the follows in a recursive call to this
+  ** function with the same value for pDecl.
+  */
+  flag = needFullDecl ? DP_Declared|DP_Forward : DP_Forward;
+  for(p=pDecl; p; p=p->pSameName){
+    if( !DeclHasProperty(p,flag) ) break;
+  }
+  if( p==0 ){
+    return;
+  }
+
+  /*
+  ** Make sure we have all necessary #includes
+  */
+  for(p=pDecl; p; p=p->pSameName){
+    AddIncludes(p->pInclude,pState);
+  }
+
+  /*
+  ** Go ahead an mark everything as being declared, to prevent an
+  ** infinite loop thru the ScanText() function.  At the same time,
+  ** we decide which objects need a full declaration and mark them
+  ** with the DP_Flag bit.  We are only able to use DP_Flag in this
+  ** way because we know we'll never execute this far into this
+  ** function on a recursive call with the same pDecl.  Hence, recursive
+  ** calls to this function (through ScanText()) can never change the
+  ** value of DP_Flag out from under us.
+  */
+  for(p=pDecl; p; p=p->pSameName){
+    if( !DeclHasProperty(p,DP_Declared)
+     && (p->zFwd==0 || needFullDecl)
+     && p->zDecl!=0
+    ){
+      DeclSetProperty(p,DP_Forward|DP_Declared|DP_Flag);
+    }else{
+      DeclClearProperty(p,DP_Flag);
+    }
+  }
+
+  /*
+  ** Call ScanText() recursively (this routine is called from ScanText())
+  ** to include declarations required to come before these declarations.
+  */
+  for(p=pDecl; p; p=p->pSameName){
+    if( DeclHasProperty(p,DP_Flag) ){
+      if( p->zDecl[0]=='#' ){
+        ScanText(&p->zDecl[1],pState);
+      }else{
+        InsertExtraDecl(p);
+        ScanText(p->zDecl,pState);
+      }
+    }
+  }
+
+  /*
+  ** Output the declarations.  Do this in two passes.  First
+  ** output everything that isn't a typedef.  Then go back and
+  ** get the typedefs by the same name.
+  */
+  for(p=pDecl; p; p=p->pSameName){
+    if( DeclHasProperty(p,DP_Flag) && !DeclHasProperty(p,TY_Typedef) ){
+      if( DeclHasAnyProperty(p,TY_Enumeration) ){
+        if( doneTypedef ) continue;
+        doneTypedef = 1;
+      }
+      ChangeIfContext(p->zIf,pState);
+      if( !isCpp && DeclHasAnyProperty(p,DP_ExternReqd) ){
+        StringAppend(pState->pStr,"extern ",0);
+      }else if( isCpp && DeclHasProperty(p,DP_Cplusplus|DP_ExternReqd) ){
+        StringAppend(pState->pStr,"extern ",0);
+      }else if( isCpp && DeclHasAnyProperty(p,DP_ExternCReqd|DP_ExternReqd) ){
+        StringAppend(pState->pStr,"extern \"C\" ",0);
+      }
+      InsertExtraDecl(p);
+      StringAppend(pState->pStr,p->zDecl,0);
+      if( !isCpp && DeclHasProperty(p,DP_Cplusplus) ){
+        fprintf(stderr,
+          "%s: C code ought not reference the C++ object \"%s\"\n",
+          pState->zFilename, p->zName);
+        pState->nErr++;
+      }
+      DeclClearProperty(p,DP_Flag);
+    }
+  }
+  for(p=pDecl; p && !doneTypedef; p=p->pSameName){
+    if( DeclHasProperty(p,DP_Flag) ){
+      /* This has to be a typedef */
+      doneTypedef = 1;
+      ChangeIfContext(p->zIf,pState);
+      InsertExtraDecl(p);
+      StringAppend(pState->pStr,p->zDecl,0);
+    }
+  }
+}
+
+/*
+** This routine scans the input text given, and appends to the
+** string in pState->pStr the text of any declarations that must
+** occur before the text in zText.
+**
+** If an identifier in zText is immediately followed by '*', then
+** only forward declarations are needed for that identifier.  If the
+** identifier name is not followed immediately by '*', we must supply
+** a full declaration.
+*/
+static void ScanText(
+  const char *zText,    /* The input text to be scanned */
+  GenState *pState      /* Current state of the code generator */
+){
+  int nextValid = 0;    /* True is sNext contains valid data */
+  InStream sIn;         /* The input text */
+  Token sToken;         /* The current token being examined */
+  Token sNext;          /* The next non-space token */
+
+  /* printf("BEGIN SCAN TEXT on %s\n", zText); */
+
+  sIn.z = zText;
+  sIn.i = 0;
+  sIn.nLine = 1;
+  while( sIn.z[sIn.i]!=0 ){
+    if( nextValid ){
+      sToken = sNext;
+      nextValid = 0;
+    }else{
+      GetNonspaceToken(&sIn,&sToken);
+    }
+    if( sToken.eType==TT_Id ){
+      int needFullDecl;   /* True if we need to provide the full declaration,
+                          ** not just the forward declaration */
+      Decl *pDecl;        /* The declaration having the name in sToken */
+
+      /*
+      ** See if there is a declaration in the database with the name given
+      ** by sToken.
+      */
+      pDecl = FindDecl(sToken.zText,sToken.nText);
+      if( pDecl==0 ) continue;
+
+      /*
+      ** If we get this far, we've found an identifier that has a
+      ** declaration in the database.  Now see if we the full declaration
+      ** or just a forward declaration.
+      */
+      GetNonspaceToken(&sIn,&sNext);
+      if( sNext.zText[0]=='*' ){
+        needFullDecl = 0;
+      }else{
+        needFullDecl = 1;
+        nextValid = sNext.eType==TT_Id;
+      }
+
+      /*
+      ** Generate the needed declaration.
+      */
+      DeclareObject(pDecl,pState,needFullDecl);
+    }else if( sToken.eType==TT_Preprocessor ){
+      sIn.i -= sToken.nText - 1;
+    }
+  }
+  /* printf("END SCANTEXT\n"); */
+}
+
+/*
+** Provide a full declaration to any object which so far has had only
+** a forward declaration.
+*/
+static void CompleteForwardDeclarations(GenState *pState){
+  Decl *pDecl;
+  int progress;
+
+  do{
+    progress = 0;
+    for(pDecl=pDeclFirst; pDecl; pDecl=pDecl->pNext){
+      if( DeclHasProperty(pDecl,DP_Forward)
+       && !DeclHasProperty(pDecl,DP_Declared)
+      ){
+        DeclareObject(pDecl,pState,1);
+        progress = 1;
+        assert( DeclHasProperty(pDecl,DP_Declared) );
+      }
+    }
+  }while( progress );
+}
+
+/*
+** Generate an include file for the given source file.  Return the number
+** of errors encountered.
+**
+** if nolocal_flag is true, then we do not generate declarations for
+** objected marked DP_Local.
+*/
+static int MakeHeader(InFile *pFile, FILE *report, int nolocal_flag){
+  int nErr = 0;
+  GenState sState;
+  String outStr;
+  IdentTable includeTable;
+  Ident *pId;
+  char *zNewVersion;
+  char *zOldVersion;
+
+  if( pFile->zHdr==0 || *pFile->zHdr==0 ) return 0;
+  sState.pStr = &outStr;
+  StringInit(&outStr);
+  StringAppend(&outStr,zTopLine,nTopLine);
+  sState.pTable = &includeTable;
+  memset(&includeTable,0,sizeof(includeTable));
+  sState.zIf = 0;
+  sState.nErr = 0;
+  sState.zFilename = pFile->zSrc;
+  sState.flags = pFile->flags & DP_Cplusplus;
+  ResetDeclFlags(nolocal_flag ? "no" : pFile->zSrc);
+  for(pId = pFile->idTable.pList; pId; pId=pId->pNext){
+    Decl *pDecl = FindDecl(pId->zName,0);
+    if( pDecl ){
+      DeclareObject(pDecl,&sState,1);
+    }
+  }
+  CompleteForwardDeclarations(&sState);
+  ChangeIfContext(0,&sState);
+  nErr += sState.nErr;
+  zOldVersion = ReadFile(pFile->zHdr);
+  zNewVersion = StringGet(&outStr);
+  if( report ) fprintf(report,"%s: ",pFile->zHdr);
+  if( zOldVersion==0 ){
+    if( report ) fprintf(report,"updated\n");
+    if( WriteFile(pFile->zHdr,zNewVersion) ){
+      fprintf(stderr,"%s: Can't write to file\n",pFile->zHdr);
+      nErr++;
+    }
+  }else if( strncmp(zOldVersion,zTopLine,nTopLine)!=0 ){
+    if( report ) fprintf(report,"error!\n");
+    fprintf(stderr,
+       "%s: Can't overwrite this file because it wasn't previously\n"
+       "%*s  generated by 'makeheaders'.\n",
+       pFile->zHdr, (int)strlen(pFile->zHdr), "");
+    nErr++;
+  }else if( strcmp(zOldVersion,zNewVersion)!=0 ){
+    if( report ) fprintf(report,"updated\n");
+    if( WriteFile(pFile->zHdr,zNewVersion) ){
+      fprintf(stderr,"%s: Can't write to file\n",pFile->zHdr);
+      nErr++;
+    }
+  }else if( report ){
+    fprintf(report,"unchanged\n");
+  }
+  SafeFree(zOldVersion);
+  IdentTableReset(&includeTable);
+  StringReset(&outStr);
+  return nErr;
+}
+
+/*
+** Generate a global header file -- a header file that contains all
+** declarations.  If the forExport flag is true, then only those
+** objects that are exported are included in the header file.
+*/
+static int MakeGlobalHeader(int forExport){
+  GenState sState;
+  String outStr;
+  IdentTable includeTable;
+  Decl *pDecl;
+
+  sState.pStr = &outStr;
+  StringInit(&outStr);
+  /* StringAppend(&outStr,zTopLine,nTopLine); */
+  sState.pTable = &includeTable;
+  memset(&includeTable,0,sizeof(includeTable));
+  sState.zIf = 0;
+  sState.nErr = 0;
+  sState.zFilename = "(all)";
+  sState.flags = 0;
+  ResetDeclFlags(0);
+  for(pDecl=pDeclFirst; pDecl; pDecl=pDecl->pNext){
+    if( forExport==0 || DeclHasProperty(pDecl,DP_Export) ){
+      DeclareObject(pDecl,&sState,1);
+    }
+  }
+  ChangeIfContext(0,&sState);
+  printf("%s",StringGet(&outStr));
+  IdentTableReset(&includeTable);
+  StringReset(&outStr);
+  return 0;
+}
+
+#ifdef DEBUG
+/*
+** Return the number of characters in the given string prior to the
+** first newline.
+*/
+static int ClipTrailingNewline(char *z){
+  int n = strlen(z);
+  while( n>0 && (z[n-1]=='\n' || z[n-1]=='\r') ){ n--; }
+  return n;
+}
+
+/*
+** Dump the entire declaration list for debugging purposes
+*/
+static void DumpDeclList(void){
+  Decl *pDecl;
+
+  for(pDecl = pDeclFirst; pDecl; pDecl=pDecl->pNext){
+    printf("**** %s from file %s ****\n",pDecl->zName,pDecl->zFile);
+    if( pDecl->zIf ){
+      printf("If: [%.*s]\n",ClipTrailingNewline(pDecl->zIf),pDecl->zIf);
+    }
+    if( pDecl->zFwd ){
+      printf("Decl: [%.*s]\n",ClipTrailingNewline(pDecl->zFwd),pDecl->zFwd);
+    }
+    if( pDecl->zDecl ){
+      InsertExtraDecl(pDecl);
+      printf("Def: [%.*s]\n",ClipTrailingNewline(pDecl->zDecl),pDecl->zDecl);
+    }
+    if( pDecl->flags ){
+      static struct {
+        int mask;
+        char *desc;
+      } flagSet[] = {
+        { TY_Class,       "class" },
+        { TY_Enumeration, "enum" },
+        { TY_Structure,   "struct" },
+        { TY_Union,       "union" },
+        { TY_Variable,    "variable" },
+        { TY_Subroutine,  "function" },
+        { TY_Typedef,     "typedef" },
+        { TY_Macro,       "macro" },
+        { DP_Export,      "export" },
+        { DP_Local,       "local" },
+        { DP_Cplusplus,   "C++" },
+      };
+      int i;
+      printf("flags:");
+      for(i=0; i<sizeof(flagSet)/sizeof(flagSet[0]); i++){
+        if( flagSet[i].mask & pDecl->flags ){
+          printf(" %s", flagSet[i].desc);
+        }
+      }
+      printf("\n");
+    }
+    if( pDecl->pInclude ){
+      Include *p;
+      printf("includes:");
+      for(p=pDecl->pInclude; p; p=p->pNext){
+        printf(" %s",p->zFile);
+      }
+      printf("\n");
+    }
+  }
+}
+#endif
+
+/*
+** When the "-doc" command-line option is used, this routine is called
+** to print all of the database information to standard output.
+*/
+static void DocumentationDump(void){
+  Decl *pDecl;
+  static struct {
+    int mask;
+    char flag;
+  } flagSet[] = {
+    { TY_Class,       'c' },
+    { TY_Enumeration, 'e' },
+    { TY_Structure,   's' },
+    { TY_Union,       'u' },
+    { TY_Variable,    'v' },
+    { TY_Subroutine,  'f' },
+    { TY_Typedef,     't' },
+    { TY_Macro,       'm' },
+    { DP_Export,      'x' },
+    { DP_Local,       'l' },
+    { DP_Cplusplus,   '+' },
+  };
+
+  for(pDecl = pDeclFirst; pDecl; pDecl=pDecl->pNext){
+    int i;
+    int nLabel = 0;
+    char *zDecl;
+    char zLabel[50];
+    for(i=0; i<sizeof(flagSet)/sizeof(flagSet[0]); i++){
+      if( DeclHasProperty(pDecl,flagSet[i].mask) ){
+        zLabel[nLabel++] = flagSet[i].flag;
+      }
+    }
+    if( nLabel==0 ) continue;
+    zLabel[nLabel] = 0;
+    InsertExtraDecl(pDecl);
+    zDecl = pDecl->zDecl;
+    if( zDecl==0 ) zDecl = pDecl->zFwd;
+    printf("%s %s %s %p %d %d %d %d %d\n",
+       pDecl->zName,
+       zLabel,
+       pDecl->zFile,
+       pDecl->pComment,
+       pDecl->pComment ? pDecl->pComment->nText+1 : 0,
+       pDecl->zIf ? (int)strlen(pDecl->zIf)+1 : 0,
+       zDecl ? (int)strlen(zDecl) : 0,
+       pDecl->pComment ? pDecl->pComment->nLine : 0,
+       pDecl->tokenCode.nText ? pDecl->tokenCode.nText+1 : 0
+    );
+    if( pDecl->pComment ){
+      printf("%.*s\n",pDecl->pComment->nText, pDecl->pComment->zText);
+    }
+    if( pDecl->zIf ){
+      printf("%s\n",pDecl->zIf);
+    }
+    if( zDecl ){
+      printf("%s",zDecl);
+    }
+    if( pDecl->tokenCode.nText ){
+      printf("%.*s\n",pDecl->tokenCode.nText, pDecl->tokenCode.zText);
+    }
+  }
+}
+
+/*
+** Given the complete text of an input file, this routine prints a
+** documentation record for the header comment at the beginning of the
+** file (if the file has a header comment.)
+*/
+void PrintModuleRecord(const char *zFile, const char *zFilename){
+  int i;
+  static int addr = 5;
+  while( isspace(*zFile) ){ zFile++; }
+  if( *zFile!='/' || zFile[1]!='*' ) return;
+  for(i=2; zFile[i] && (zFile[i-1]!='/' || zFile[i-2]!='*'); i++){}
+  if( zFile[i]==0 ) return;
+  printf("%s M %s %d %d 0 0 0 0\n%.*s\n",
+    zFilename, zFilename, addr, i+1, i, zFile);
+  addr += 4;
+}
+
+
+/*
+** Given an input argument to the program, construct a new InFile
+** object.
+*/
+static InFile *CreateInFile(char *zArg, int *pnErr){
+  int nSrc;
+  char *zSrc;
+  InFile *pFile;
+  int i;
+
+  /*
+  ** Get the name of the input file to be scanned.  The input file is
+  ** everything before the first ':' or the whole file if no ':' is seen.
+  **
+  ** Except, on windows, ignore any ':' that occurs as the second character
+  ** since it might be part of the drive specifier.  So really, the ":' has
+  ** to be the 3rd or later character in the name.  This precludes 1-character
+  ** file names, which really should not be a problem.
+  */
+  zSrc = zArg;
+  for(nSrc=2; zSrc[nSrc] && zArg[nSrc]!=':'; nSrc++){}
+  pFile = SafeMalloc( sizeof(InFile) );
+  memset(pFile,0,sizeof(InFile));
+  pFile->zSrc = StrDup(zSrc,nSrc);
+
+  /* Figure out if we are dealing with C or C++ code.  Assume any
+  ** file with ".c" or ".h" is C code and all else is C++.
+  */
+  if( nSrc>2 && zSrc[nSrc-2]=='.' && (zSrc[nSrc-1]=='c' || zSrc[nSrc-1]=='h')){
+    pFile->flags &= ~DP_Cplusplus;
+  }else{
+    pFile->flags |= DP_Cplusplus;
+  }
+
+  /*
+  ** If a separate header file is specified, use it
+  */
+  if( zSrc[nSrc]==':' ){
+    int nHdr;
+    char *zHdr;
+    zHdr = &zSrc[nSrc+1];
+    for(nHdr=0; zHdr[nHdr]; nHdr++){}
+    pFile->zHdr = StrDup(zHdr,nHdr);
+  }
+
+  /* Look for any 'c' or 'C' in the suffix of the file name and change
+  ** that character to 'h' or 'H' respectively.  If no 'c' or 'C' is found,
+  ** then assume we are dealing with a header.
+  */
+  else{
+    int foundC = 0;
+    pFile->zHdr = StrDup(zSrc,nSrc);
+    for(i = nSrc-1; i>0 && pFile->zHdr[i]!='.'; i--){
+      if( pFile->zHdr[i]=='c' ){
+        foundC = 1;
+        pFile->zHdr[i] = 'h';
+      }else if( pFile->zHdr[i]=='C' ){
+        foundC = 1;
+        pFile->zHdr[i] = 'H';
+      }
+    }
+    if( !foundC ){
+      SafeFree(pFile->zHdr);
+      pFile->zHdr = 0;
+    }
+  }
+
+  /*
+  ** If pFile->zSrc contains no 'c' or 'C' in its extension, it
+  ** must be a header file.   In that case, we need to set the
+  ** PS_Interface flag.
+  */
+  pFile->flags |= PS_Interface;
+  for(i=nSrc-1; i>0 && zSrc[i]!='.'; i--){
+    if( zSrc[i]=='c' || zSrc[i]=='C' ){
+      pFile->flags &= ~PS_Interface;
+      break;
+    }
+  }
+
+  /* Done!
+  */
+  return pFile;
+}
+
+/* MS-Windows and MS-DOS both have the following serious OS bug:  the
+** length of a command line is severely restricted.  But this program
+** occasionally requires long command lines.  Hence the following
+** work around.
+**
+** If the parameters "-f FILENAME" appear anywhere on the command line,
+** then the named file is scanned for additional command line arguments.
+** These arguments are substituted in place of the "FILENAME" argument
+** in the original argument list.
+**
+** This first parameter to this routine is the index of the "-f"
+** parameter in the argv[] array.  The argc and argv are passed by
+** pointer so that they can be changed.
+**
+** Parsing of the parameters in the file is very simple.  Parameters
+** can be separated by any amount of white-space (including newlines
+** and carriage returns.)  There are now quoting characters of any
+** kind.  The length of a token is limited to about 1000 characters.
+*/
+static void AddParameters(int index, int *pArgc, char ***pArgv){
+  int argc = *pArgc;      /* The original argc value */
+  char **argv = *pArgv;   /* The original argv value */
+  int newArgc;            /* Value for argc after inserting new arguments */
+  char **zNew = 0;        /* The new argv after this routine is done */
+  char *zFile;            /* Name of the input file */
+  int nNew = 0;           /* Number of new entries in the argv[] file */
+  int nAlloc = 0;         /* Space allocated for zNew[] */
+  int i;                  /* Loop counter */
+  int n;                  /* Number of characters in a new argument */
+  int c;                  /* Next character of input */
+  int startOfLine = 1;    /* True if we are where '#' can start a comment */
+  FILE *in;               /* The input file */
+  char zBuf[1000];        /* A single argument is accumulated here */
+
+  if( index+1==argc ) return;
+  zFile = argv[index+1];
+  in = fopen(zFile,"r");
+  if( in==0 ){
+    fprintf(stderr,"Can't open input file \"%s\"\n",zFile);
+    exit(1);
+  }
+  c = ' ';
+  while( c!=EOF ){
+    while( c!=EOF && isspace(c) ){
+      if( c=='\n' ){
+        startOfLine = 1;
+      }
+      c = getc(in);
+      if( startOfLine && c=='#' ){
+        while( c!=EOF && c!='\n' ){
+          c = getc(in);
+        }
+      }
+    }
+    n = 0;
+    while( c!=EOF && !isspace(c) ){
+      if( n<sizeof(zBuf)-1 ){ zBuf[n++] = c; }
+      startOfLine = 0;
+      c = getc(in);
+    }
+    zBuf[n] = 0;
+    if( n>0 ){
+      nNew++;
+      if( nNew + argc > nAlloc ){
+        if( nAlloc==0 ){
+          nAlloc = 100 + argc;
+          zNew = malloc( sizeof(char*) * nAlloc );
+        }else{
+          nAlloc *= 2;
+          zNew = realloc( zNew, sizeof(char*) * nAlloc );
+        }
+      }
+      if( zNew ){
+        int j = nNew + index;
+        zNew[j] = malloc( n + 1 );
+        if( zNew[j] ){
+          strcpy( zNew[j], zBuf );
+        }
+      }
+    }
+  }
+  newArgc = argc + nNew - 1;
+  for(i=0; i<=index; i++){
+    zNew[i] = argv[i];
+  }
+  for(i=nNew + index + 1; i<newArgc; i++){
+    zNew[i] = argv[i + 1 - nNew];
+  }
+  zNew[newArgc] = 0;
+  *pArgc = newArgc;
+  *pArgv = zNew;
+}
+
+#ifdef NOT_USED
+/*
+** Return the time that the given file was last modified.  If we can't
+** locate the file (because, for example, it doesn't exist), then
+** return 0.
+*/
+static unsigned int ModTime(const char *zFilename){
+  unsigned int mTime = 0;
+  struct stat sStat;
+  if( stat(zFilename,&sStat)==0 ){
+    mTime = sStat.st_mtime;
+  }
+  return mTime;
+}
+#endif
+
+/*
+** Print a usage comment for this program.
+*/
+static void Usage(const char *argv0, const char *argvN){
+  fprintf(stderr,"%s: Illegal argument \"%s\"\n",argv0,argvN);
+  fprintf(stderr,"Usage: %s [options] filename...\n"
+    "Options:\n"
+    "  -h          Generate a single .h to standard output.\n"
+    "  -H          Like -h, but only output EXPORT declarations.\n"
+    "  -v          (verbose) Write status information to the screen.\n"
+    "  -doc        Generate no header files.  Instead, output information\n"
+    "              that can be used by an automatic program documentation\n"
+    "              and cross-reference generator.\n"
+    "  -local      Generate prototypes for \"static\" functions and\n"
+    "              procedures.\n"
+    "  -f FILE     Read additional command-line arguments from the file named\n"
+    "              \"FILE\".\n"
+#ifdef DEBUG
+    "  -! MASK     Set the debugging mask to the number \"MASK\".\n"
+#endif
+    "  --          Treat all subsequent comment-line parameters as filenames,\n"
+    "              even if they begin with \"-\".\n",
+    argv0
+  );
+}
+
+/*
+** The following text contains a few simple #defines that we want
+** to be available to every file.
+*/
+static const char zInit[] =
+  "#define INTERFACE 0\n"
+  "#define EXPORT_INTERFACE 0\n"
+  "#define LOCAL_INTERFACE 0\n"
+  "#define EXPORT\n"
+  "#define LOCAL static\n"
+  "#define PUBLIC\n"
+  "#define PRIVATE\n"
+  "#define PROTECTED\n"
+;
+
+#if TEST==0
+int main(int argc, char **argv){
+  int i;                /* Loop counter */
+  int nErr = 0;         /* Number of errors encountered */
+  Token *pList;         /* List of input tokens for one file */
+  InFile *pFileList = 0;/* List of all input files */
+  InFile *pTail = 0;    /* Last file on the list */
+  InFile *pFile;        /* for looping over the file list */
+  int h_flag = 0;       /* True if -h is present.  Output unified header */
+  int H_flag = 0;       /* True if -H is present.  Output EXPORT header */
+  int v_flag = 0;       /* Verbose */
+  int noMoreFlags;      /* True if -- has been seen. */
+  FILE *report;         /* Send progress reports to this, if not NULL */
+
+  noMoreFlags = 0;
+  for(i=1; i<argc; i++){
+    if( argv[i][0]=='-' && !noMoreFlags ){
+      switch( argv[i][1] ){
+        case 'h':   h_flag = 1;   break;
+        case 'H':   H_flag = 1;   break;
+        case 'v':   v_flag = 1;   break;
+        case 'd':   doc_flag = 1; proto_static = 1; break;
+        case 'l':   proto_static = 1; break;
+        case 'f':   AddParameters(i, &argc, &argv); break;
+        case '-':   noMoreFlags = 1;   break;
+#ifdef DEBUG
+        case '!':   i++;  debugMask = strtol(argv[i],0,0); break;
+#endif
+        default:    Usage(argv[0],argv[i]); return 1;
+      }
+    }else{
+      pFile = CreateInFile(argv[i],&nErr);
+      if( pFile ){
+        if( pFileList ){
+          pTail->pNext = pFile;
+          pTail = pFile;
+        }else{
+          pFileList = pTail = pFile;
+        }
+      }
+    }
+  }
+  if( h_flag && H_flag ){
+    h_flag = 0;
+  }
+  if( v_flag ){
+    report = (h_flag || H_flag) ? stderr : stdout;
+  }else{
+    report = 0;
+  }
+  if( nErr>0 ){
+    return nErr;
+  }
+  for(pFile=pFileList; pFile; pFile=pFile->pNext){
+    char *zFile;
+
+    zFilename = pFile->zSrc;
+    if( zFilename==0 ) continue;
+    zFile = ReadFile(zFilename);
+    if( zFile==0 ){
+      fprintf(stderr,"Can't read input file \"%s\"\n",zFilename);
+      nErr++;
+      continue;
+    }
+    if( strncmp(zFile,zTopLine,nTopLine)==0 ){
+      pFile->zSrc = 0;
+    }else{
+      if( report ) fprintf(report,"Reading %s...\n",zFilename);
+      pList = TokenizeFile(zFile,&pFile->idTable);
+      if( pList ){
+        nErr += ParseFile(pList,pFile->flags);
+        FreeTokenList(pList);
+      }else if( zFile[0]==0 ){
+        fprintf(stderr,"Input file \"%s\" is empty.\n", zFilename);
+        nErr++;
+      }else{
+        fprintf(stderr,"Errors while processing \"%s\"\n", zFilename);
+        nErr++;
+      }
+    }
+    if( !doc_flag ) SafeFree(zFile);
+    if( doc_flag ) PrintModuleRecord(zFile,zFilename);
+  }
+  if( nErr>0 ){
+    return nErr;
+  }
+#ifdef DEBUG
+  if( debugMask & DECL_DUMP ){
+    DumpDeclList();
+    return nErr;
+  }
+#endif
+  if( doc_flag ){
+    DocumentationDump();
+    return nErr;
+  }
+  zFilename = "--internal--";
+  pList = TokenizeFile(zInit,0);
+  if( pList==0 ){
+    return nErr+1;
+  }
+  ParseFile(pList,PS_Interface);
+  FreeTokenList(pList);
+  if( h_flag || H_flag ){
+    nErr += MakeGlobalHeader(H_flag);
+  }else{
+    for(pFile=pFileList; pFile; pFile=pFile->pNext){
+      if( pFile->zSrc==0 ) continue;
+      nErr += MakeHeader(pFile,report,0);
+    }
+  }
+  return nErr;
+}
+#endif
+
+
+
+
+ + + + + + diff --git a/try/mh_main_prob/command1.c b/try/mh_main_prob/command1.c new file mode 100644 index 0000000..637df55 --- /dev/null +++ b/try/mh_main_prob/command1.c @@ -0,0 +1,6 @@ +#include "command1.h" +#include +int main(int argc, char **argv){ + printf("command1 %d\n", f()); + return 0; +} diff --git a/try/mh_main_prob/command2.c b/try/mh_main_prob/command2.c new file mode 100644 index 0000000..5d8c612 --- /dev/null +++ b/try/mh_main_prob/command2.c @@ -0,0 +1,6 @@ +#include "command2.h" +#include +int main(int argc, char **argv){ + printf("command2 %d\n", f() + argc); + return 0; +} diff --git a/try/mh_main_prob/just_fun.c b/try/mh_main_prob/just_fun.c new file mode 100644 index 0000000..67625f4 --- /dev/null +++ b/try/mh_main_prob/just_fun.c @@ -0,0 +1,4 @@ +#include "just_fun.h" +int f(){ + return 5; +} diff --git a/try/mh_main_prob/transcript1.txt b/try/mh_main_prob/transcript1.txt new file mode 100644 index 0000000..c5511fe --- /dev/null +++ b/try/mh_main_prob/transcript1.txt @@ -0,0 +1,36 @@ +Various commmand files each with its own main for testing a library. makeheaders gets confused and puts all the +declarations in the headers, leading to a failure. + + +> ls +command1.c command2.c just_fun.c +> cat just_fun.c +#include "just_fun.h" +int f(){ + return 5; +} +> cat command1.c +#include "command1.h" +#include +int main(){ + printf("command1 %d\n", f()); + return 0; +} +> cat command2.c +#include "command2.h" +#include +int main(int argc, char **argv){ + printf("command2 %d\n", f() + argc); + return 0; +} +> makeheaders *.c +> gcc -o command1 command1.c +command1.c: In function ‘main’: +command1.c:3:1: error: number of arguments doesn’t match prototype + int main(){ + ^~~ +In file included from command1.c:1: +command1.h:5:5: error: prototype declaration + int main(int argc,char **argv); + ^~~~ +> diff --git a/try/mh_main_prob/transcript2.txt b/try/mh_main_prob/transcript2.txt new file mode 100644 index 0000000..77ec819 --- /dev/null +++ b/try/mh_main_prob/transcript2.txt @@ -0,0 +1,20 @@ +Making each main call static so it won't be in the header. gcc can't find main. + +> cat command1.c +#include "command1.h" +#include +static int main(){ + printf("command1 %d\n", f()); + return 0; +} +> cat command2.c +#include "command2.h" +#include +static int main(int argc, char **argv){ + printf("command2 %d\n", f() + argc); + return 0; +} +> gcc -o command1 command1.c just_fun.c +/usr/bin/ld: /usr/lib/gcc/x86_64-redhat-linux/8/../../../../lib64/crt1.o: in function `_start': +(.text+0x24): undefined reference to `main' +collect2: error: ld returned 1 exit status diff --git a/try/mh_main_prob/transcript3.txt b/try/mh_main_prob/transcript3.txt new file mode 100644 index 0000000..b3a00b7 --- /dev/null +++ b/try/mh_main_prob/transcript3.txt @@ -0,0 +1,27 @@ +This time making each main definition have the same prototype. Still end up with multiple main declarations, +it is just that they agree. + +> rm *.h +> makeheaders *.c +> cat command1.c +#include "command1.h" +#include +int main(int argc, char **argv){ + printf("command1 %d\n", f()); + return 0; +} +> cat command1.h +/* This file was automatically generated. Do not edit! */ +#undef INTERFACE +int f(); +int main(int argc,char **argv); +int main(int argc,char **argv); +> cat command2.c +#include "command2.h" +#include +int main(int argc, char **argv){ + printf("command2 %d\n", f() + argc); + return 0; +} +> gcc -o command1 command1.c just_fun.c +> .. worked