Warning: Declaration of action_plugin_subjectindex_indexer::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php on line 15

Warning: Declaration of action_plugin_mathjax_enable::register(Doku_Event_Handler &$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/mathjax/action/enable.php on line 62

Warning: Declaration of action_plugin_googleanalytics::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/googleanalytics/action.php on line 40

Warning: Declaration of action_plugin_folded::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/folded/action.php on line 40

Warning: Declaration of action_plugin_hidden::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/hidden/action.php on line 28

Warning: Declaration of action_plugin_include::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/include/action.php on line 354

Warning: Declaration of action_plugin_tag::register(&$contr) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/tag/action.php on line 175

Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/auth.php on line 532

Warning: preg_replace(): The /e modifier is no longer supported, use preg_replace_callback instead in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/auth.php on line 818

Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656

Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656
lcthw:ex26

======Exercise 26: Write A First Real Program====== You are at the half-way mark in the book, so you need to take a mid-term. In this mid-term you're going to recreate a piece of software I wrote specifically for this book called devpkg. You'll then extend it in a few key ways and improve the code, most importantly by writing some unit tests for it. Note I wrote this exercise before writing some of the exercises you might need to complete this. If you are attempting this one now, please keep in mind that the software may have bugs, that you might have problems because of my mistakes, and that you might not know everything you need to finish it. If so, tell me at help@learncodethehardway.org and then wait until I finish the other exercises. ======What Is devpkg?====== Devpkg is a simple C program that installs other software. I made it specifically for this book as a way to teach you how a real software project is structured, and also how to reuse other people's libraries. It uses a portability library called The Apache Portable Runtime (APR) that has many handy C functions which work on tons of platforms, including Windows. Other than that, it just grabs code from the internet (or local files) and does the usual ./configure ; make ; make install every programmer does. Your goal in this exercise is to build devpkg from source, finish each Challenge I give, and use the source to understand what devpkg does and why. ======What We Want To Make====== We want a tool that has three commands: devpkg -S Sets up a new install on a computer. devpkg -I Installs a piece of software from a URL. devpkg -L Lists all the software that's been installed. devpkg -F Fetches some source code for manual building. devpkg -B Builds fetches source code and installs it, even if already installed. We want devpkg to be able to take almost any URL, figure out what kind of project it is, download it, install it, and register that it downloaded that software. We'd also like it to process a simple dependency list so it can install all the software that a project might need as well. ======The Design====== To accomplish this goal devpkg will have a very simple design: Use external commands You'll do most of the work through external commands like curl, git, and tar. This reduces the amount of code devpkg needs to get things done. Simple File Database You could easily make it more complex, but to start you'll just make a single simple file database at /usr/local/.devpkg/db to keep track of what's installed. /usr/local Always Again you could make this more advanced, but for starters just assume it's always /usr/local which is a standard install path for most software on Unix. configure, make, make install It's assumed that most software can install with just a configure; make; make install and maybe configure is optional. If the software can't at a minimum do that, then there's some options to modify the commands, but otherwise devpkg won't bother. The User Can Be root We'll assume the user can become root using sudo, but that they don't want to become root until the end. This will keep our program small at first and work well enough to get it going, at which point you'll be able to modify it further for this exercise. ======The Apache Portable Runtime====== One more thing you'll do is leverage the The Apache Portable Runtime (APR) libraries to get a good set of portable routines for doing this kind of work. The APR isn't necessary, and you could probably write this program without them, but it'd take more code than necessary. I'm also forcing you to use APR now so you get used to linking and using other libraries. Finally, the APR also works on Windows so your skills with it are transferable to many other platforms. You should go get both the apr-1.4.5 and the apr-util-1.3 libraries, as well as browse through the documentation available at the main APR site at apr.apache.org Here's a shell script that will install all the stuff you need. You should write this into a file by hand, and then run it until it can install APR without any errors. set -e # go somewhere safe cd /tmp # get the source to base APR 1.4.6 curl -L -O http://archive.apache.org/dist/apr/apr-1.4.6.tar.gz # extract it and go into the source tar -xzvf apr-1.4.6.tar.gz cd apr-1.4.6 # configure, make, make install ./configure make sudo make install # reset and cleanup cd /tmp rm -rf apr-1.4.6 apr-1.4.6.tar.gz # do the same with apr-util curl -L -O http://archive.apache.org/dist/apr/apr-util-1.4.1.tar.gz # extract tar -xzvf apr-util-1.4.1.tar.gz cd apr-util-1.4.1 # configure, make, make install ./configure --with-apr=/usr/local/apr # you need that extra parameter to configure because # apr-util can't really find it because...who knows. make sudo make install #cleanup cd /tmp rm -rf apr-util-1.4.1* apr-1.4.6* I'm having you write this script out because this is basically what we want devpkg to do, but with extra options and checks. In fact, you could just do it all in shell with less code, but then that wouldn't be a very good program for a C book would it? Simply run this script and fix it until it works, then you'll have the libraries you need to complete the rest of this project. ======Project Layout====== You need to setup some simple project files to get started. Here's how I usually craft a new project: mkdir devpkg cd devpkg touch README Makefile ======Other Dependencies====== You should have already installed APR and APR-util, so now you need a few more files as basic dependencies: * dbg.h from Exercise 20. * bstrlib.h and bstrlib.c from http://bstring.sourceforge.net/. Download the .zip file, extract it, and copy just those two files out. * Type make bstrlib.o and if it doesn't work, read the "Fixing bstring" instructions below. Note In some platforms the bstring.c file will have an error like: bstrlib.c:2762: error: expected declaration specifiers or '...' before numeric c onstant This is from a bad define the authors added which doesn't work always. You just need to change the line 2759 that reads #ifdef __GNUC__ and make it: #if defined(__GNUC__) && !defined(__APPLE__) Then it should work on Apple Mac OSX. When that's all done, you should have a Makefile, README, dbg.h, bstrlib.h, and bstrlib.c ready to go. ======The Makefile====== A good place to start is the Makefile since this lays out how things are built and what source files you'll be creating. ======PREFIX?=/usr/local====== ======CFLAGS=-g -Wall -I${PREFIX}/apr/include/apr-1 -I${PREFIX}/apr/include/apr-util-====== 1 ======LDFLAGS=-L${PREFIX}/apr/lib -lapr-1 -pthread -laprutil-1====== all: devpkg devpkg: bstrlib.o db.o shell.o commands.o install: all install -d $(DESTDIR)/$(PREFIX)/bin/ install devpkg $(DESTDIR)/$(PREFIX)/bin/ clean: rm -f *.o rm -f devpkg rm -rf *.dSYM There's nothing in this that you haven't seen before, except maybe the strange ?= syntax, which says "set PREFIX equal to this unless PREFIX is already set". Note If you are on more recent versions of Ubuntu and you get errors about apr_off_t or off64_t then add -D_LARGEFILE64_SOURCE=1 to CFLAGS. Another thing is you need to add /usr/local/apr/lib to a file in /etc/ld.conf.so.d/ then run ldconfig so that it picks up the libraries correctly. ======The Source Files====== From the make file, we see that there's four dependencies for devpkg which are: bstrlib.o Comes from bstrlib.c and header file bstlib.h which you already have. db.o From db.c and header file db.h, and it will contain code for our little "database" routines. shell.o From shell.c and header shell.h, with a couple functions that make running other commands like curl easier. commands.o From command.c and header command.h, and contains all the commands that devpkg needs to be useful. devpkg It's not explicitly mentioned, but instead is the target (on the left) in this part of the Makefile. It comes from devpkg.c which contains the main function for the whole program. Your job is to now create each of these files and type in their code and get them correct. Note You may read this description and think, "Man! How is it that Zed is so smart he just sat down and typed these files out like this!? I could never do that." I didn't magically craft devpkg in this form with my awesome code powers. Instead, what I did is this: * I wrote a quick little README to get an idea of how I wanted it to work. * I created a simple bash script (like the one you did) to figure out all the pieces that you need. * I made one .c file and hacked on it for a few days working through the idea and figuring it out. * I got it mostly working and debugged, then I started breaking up the one big file into these four files. * After getting these files laid down, I renamed and refined the functions and data structures so they'd be more logical and "pretty". * Finally, after I had it working the exact same but with the new structure, I added a few features like the -F and -B options. You're reading this in the order I want to teach it to you, but don't think this is how I always build software. Sometimes I already know the subject and I use more planning. Sometimes I just hack up an idea and see how well it'd work. Sometimes I write one, then throw it away and plan out a better one. It all depends on what my experience tells me is best, or where my inspiration takes me. If you run into an "expert" who tries to tell you there's only one way to solve a programming problem, then they're lying to you. Either they actually use multiple tactics, or they're not very good. ======The DB Functions====== There must be a way to record URLs that have been installed, list these URLs, and check if something has already been installed so we can skip it. I'll use a simple flat file database and the bstrlib.h library to do it. First, create the db.h header file so you know what you'll be implementing. #ifndef _db_h #define _db_h #define DB_FILE "/usr/local/.devpkg/db" #define DB_DIR "/usr/local/.devpkg" int DB_init(); int DB_list(); int DB_update(const char *url); int DB_find(const char *url); #endif Then implement those functions in db.c, as you build this, use make like you've been to get it to compile cleanly. #include <unistd.h> #include <apr_errno.h> #include <apr_file_io.h> #include "db.h" #include "bstrlib.h" #include "dbg.h" static FILE *DB_open(const char *path, const char *mode) { return fopen(path, mode); } static void DB_close(FILE *db) { fclose(db); } static bstring DB_load() { FILE *db = NULL; bstring data = NULL; db = DB_open(DB_FILE, "r"); check(db, "Failed to open database: %s", DB_FILE); data = bread((bNread)fread, db); check(data, "Failed to read from db file: %s", DB_FILE); DB_close(db); return data; error: if(db) DB_close(db); if(data) bdestroy(data); return NULL; } int DB_update(const char *url) { if(DB_find(url)) { log_info("Already recorded as installed: %s", url); } FILE *db = DB_open(DB_FILE, "a+"); check(db, "Failed to open DB file: %s", DB_FILE); bstring line = bfromcstr(url); bconchar(line, '\n'); int rc = fwrite(line->data, blength(line), 1, db); check(rc == 1, "Failed to append to the db."); return 0; error: if(db) DB_close(db); return -1; } int DB_find(const char *url) { bstring data = NULL; bstring line = bfromcstr(url); int res = -1; data = DB_load(); check(data, "Failed to load: %s", DB_FILE); if(binstr(data, 0, line) == BSTR_ERR) { res = 0; } else { res = 1; } error: // fallthrough if(data) bdestroy(data); if(line) bdestroy(line); return res; } int DB_init() { apr_pool_t *p = NULL; apr_pool_initialize(); apr_pool_create(&p, NULL); if(access(DB_DIR, W_OK | X_OK) == -1) { apr_status_t rc = apr_dir_make_recursive(DB_DIR, APR_UREAD | APR_UWRITE | APR_UEXECUTE | APR_GREAD | APR_GWRITE | APR_GEXECUTE, p); check(rc == APR_SUCCESS, "Failed to make database dir: %s", DB_DIR); } if(access(DB_FILE, W_OK) == -1) { FILE *db = DB_open(DB_FILE, "w"); check(db, "Cannot open database: %s", DB_FILE); DB_close(db); } apr_pool_destroy(p); return 0; error: apr_pool_destroy(p); return -1; } int DB_list() { bstring data = DB_load(); check(data, "Failed to read load: %s", DB_FILE); printf("%s", bdata(data)); bdestroy(data); return 0; error: return -1; } ======Challenge 1: Code Review====== Before continuing, read every line of these files carefully and confirm that you have them entered in exactly. Read them line-by-line backwards to practice that. Also trace each function call and make sure you are using check to validate the return codes. Finally, look up every function that you don't recognize either on the APR web site documentation, or in the bstrlib.h and bstrlib.c source. ======The Shell Functions====== A key design decision for devpkg is to do most of the work using external tools like curl, tar, and git. We could find libraries to do all of this internally, but it's pointless if we just need the base features of these programs. There is no shame in running another command in Unix. To do this I'm going to use the apr_thread_proc.h functions to run programs, but I also want to make a simple kind of "template" system. I'll use a struct Shell that holds all the information needed to run a program, but has "holes" in the arguments list where I can replace them with values. Look at the shell.h file to see the structure and the commands I'll use. You can see I'm using extern to indicate that other .c files can access variables I'm defining in shell.c. #ifndef _shell_h #define _shell_h #define MAX_COMMAND_ARGS 100 #include <apr_thread_proc.h> typedef struct Shell { const char *dir; const char *exe; apr_procattr_t *attr; apr_proc_t proc; apr_exit_why_e exit_why; int exit_code; const char *args[MAX_COMMAND_ARGS]; } Shell; int Shell_run(apr_pool_t *p, Shell *cmd); int Shell_exec(Shell cmd, ...); extern Shell CLEANUP_SH; extern Shell GIT_SH; extern Shell TAR_SH; extern Shell CURL_SH; extern Shell CONFIGURE_SH; extern Shell MAKE_SH; extern Shell INSTALL_SH; #endif Make sure you've created shell.h exactly, and that you've got the same names and number of extern Shell variables. Those are used by the Shell_run and Shell_exec functions to run commands. I define these two functions, and create the real variables in shell.c. #include "shell.h" #include "dbg.h" #include <stdarg.h> int Shell_exec(Shell template, ...) { apr_pool_t *p = NULL; int rc = -1; apr_status_t rv = APR_SUCCESS; va_list argp; const char *key = NULL; const char *arg = NULL; int i = 0; rv = apr_pool_create(&p, NULL); check(rv == APR_SUCCESS, "Failed to create pool."); va_start(argp, template); for(key = va_arg(argp, const char *); key != NULL; key = va_arg(argp, const char *)) { arg = va_arg(argp, const char *); for(i = 0; template.args[i] != NULL; i++) { if(strcmp(template.args[i], key) == 0) { template.args[i] = arg; break; // found it } } } rc = Shell_run(p, &template); apr_pool_destroy(p); va_end(argp); return rc; error: if(p) { apr_pool_destroy(p); } return rc; } int Shell_run(apr_pool_t *p, Shell *cmd) { apr_procattr_t *attr; apr_status_t rv; apr_proc_t newproc; rv = apr_procattr_create(&attr, p); check(rv == APR_SUCCESS, "Failed to create proc attr."); rv = apr_procattr_io_set(attr, APR_NO_PIPE, APR_NO_PIPE, APR_NO_PIPE); check(rv == APR_SUCCESS, "Failed to set IO of command."); rv = apr_procattr_dir_set(attr, cmd->dir); check(rv == APR_SUCCESS, "Failed to set root to %s", cmd->dir); rv = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); check(rv == APR_SUCCESS, "Failed to set cmd type."); rv = apr_proc_create(&newproc, cmd->exe, cmd->args, NULL, attr, p); check(rv == APR_SUCCESS, "Failed to run command."); rv = apr_proc_wait(&newproc, &cmd->exit_code, &cmd->exit_why, APR_WAIT); check(rv == APR_CHILD_DONE, "Failed to wait."); check(cmd->exit_code == 0, "%s exited badly.", cmd->exe); check(cmd->exit_why == APR_PROC_EXIT, "%s was killed or crashed", cmd->exe); return 0; error: return -1; } ======Shell CLEANUP_SH = {====== .exe = "rm", .dir = "/tmp", .args = {"rm", "-rf", "/tmp/pkg-build", "/tmp/pkg-src.tar.gz", "/tmp/pkg-src.tar.bz2", "/tmp/DEPENDS", NULL} }; ======Shell GIT_SH = {====== .dir = "/tmp", .exe = "git", .args = {"git", "clone", "URL", "pkg-build", NULL} }; ======Shell TAR_SH = {====== .dir = "/tmp/pkg-build", .exe = "tar", .args = {"tar", "-xzf", "FILE", "--strip-components", "1", NULL} }; ======Shell CURL_SH = {====== .dir = "/tmp", .exe = "curl", .args = {"curl", "-L", "-o", "TARGET", "URL", NULL} }; ======Shell CONFIGURE_SH = {====== .exe = "./configure", .dir = "/tmp/pkg-build", .args = {"configure", "OPTS", NULL}, }; ======Shell MAKE_SH = {====== .exe = "make", .dir = "/tmp/pkg-build", .args = {"make", "OPTS", NULL} }; ======Shell INSTALL_SH = {====== .exe = "sudo", .dir = "/tmp/pkg-build", .args = {"sudo", "make", "TARGET", NULL} }; Read the shell.c from the bottom to the top (which is a common C source layout) and you see I've created the actual Shell variables that you indicated were extern in shell.h. They live here, but are available to the rest of the program. This is how you make global variables that live in one .o file but are used everywhere. You shouldn't make many of these, but they are handy for things like this. Continuing up the file we get to the Shell_run function, which is a "base" function that just runs a command based on what's in a Shell struct. It uses many of the functions defined in apr_thread_proc.h so go look up each one to see how it works. This seems like a lot of work compared to just using the system function call, but this also gives you more control over the other program's execution. For example, in our Shell struct we have a .dir attribute which forces the program to be in a specific directory before running. Finally, I have the Shell_exec function, which is a "variable arguments" function. You've seen this before, but make sure you grasp the stdarg.h functions and how to write one of these. In the challenge for this section you are going to analyze this function. ======Challenge 2: Analyze Shell_exec====== Challenge for these files (in addition to a full code review just like you did in Challenge 1) is to fully analyze Shell_exec and break down exactly how it works. You should be able to understand each line, how the two for-loops work, and how arguments are being replaced. Once you have it analyzed, add a field to struct Shell that gives the number of variable args that must be replaced. Update all the commands to have the right count of args, and then have an error check that confirms these args have been replaced and error exit. ======The Command Functions====== Now you get to make the actual commands that do the work. These commands will use functions from APR, db.h and shell.h to do the real work of downloading and building software you want it to build. This is the most complex set of files, so do them carefully. As before, you start by making the commands.h file, then implementing its functions in the commands.c file. #ifndef _commands_h #define _commands_h #include <apr_pools.h> #define DEPENDS_PATH "/tmp/DEPENDS" #define TAR_GZ_SRC "/tmp/pkg-src.tar.gz" #define TAR_BZ2_SRC "/tmp/pkg-src.tar.bz2" #define BUILD_DIR "/tmp/pkg-build" #define GIT_PAT "*.git" #define DEPEND_PAT "*DEPENDS" #define TAR_GZ_PAT "*.tar.gz" #define TAR_BZ2_PAT "*.tar.bz2" #define CONFIG_SCRIPT "/tmp/pkg-build/configure" enum CommandType { COMMAND_NONE, COMMAND_INSTALL, COMMAND_LIST, COMMAND_FETCH, COMMAND_INIT, COMMAND_BUILD }; int Command_fetch(apr_pool_t *p, const char *url, int fetch_only); int Command_install(apr_pool_t *p, const char *url, const char *configure_opts, const char *make_opts, const char *install_opts); int Command_depends(apr_pool_t *p, const char *path); int Command_build(apr_pool_t *p, const char *url, const char *configure_opts, const char *make_opts, const char *install_opts); #endif There's not much in commands.h that you haven't seen already. You should see that there's some defines for strings that are used everywhere. The real interesting code is in commands.c. #include <apr_uri.h> #include <apr_fnmatch.h> #include <unistd.h> #include "commands.h" #include "dbg.h" #include "bstrlib.h" #include "db.h" #include "shell.h" int Command_depends(apr_pool_t *p, const char *path) { FILE *in = NULL; bstring line = NULL; in = fopen(path, "r"); check(in != NULL, "Failed to open downloaded depends: %s", path); for(line = bgets((bNgetc)fgetc, in, '\n'); line != NULL; line = bgets((bNgetc)fgetc, in, '\n')) { btrimws(line); log_info("Processing depends: %s", bdata(line)); int rc = Command_install(p, bdata(line), NULL, NULL, NULL); check(rc == 0, "Failed to install: %s", bdata(line)); bdestroy(line); } fclose(in); return 0; error: if(line) bdestroy(line); if(in) fclose(in); return -1; } int Command_fetch(apr_pool_t *p, const char *url, int fetch_only) { apr_uri_t info = {.port = 0}; int rc = 0; const char *depends_file = NULL; apr_status_t rv = apr_uri_parse(p, url, &info); check(rv == APR_SUCCESS, "Failed to parse URL: %s", url); if(apr_fnmatch(GIT_PAT, info.path, 0) == APR_SUCCESS) { rc = Shell_exec(GIT_SH, "URL", url, NULL); check(rc == 0, "git failed."); } else if(apr_fnmatch(DEPEND_PAT, info.path, 0) == APR_SUCCESS) { check(!fetch_only, "No point in fetching a DEPENDS file."); if(info.scheme) { depends_file = DEPENDS_PATH; rc = Shell_exec(CURL_SH, "URL", url, "TARGET", depends_file, NULL); check(rc == 0, "Curl failed."); } else { depends_file = info.path; } // recursively process the devpkg list log_info("Building according to DEPENDS: %s", url); rv = Command_depends(p, depends_file); check(rv == 0, "Failed to process the DEPENDS: %s", url); // this indicates that nothing needs to be done return 0; } else if(apr_fnmatch(TAR_GZ_PAT, info.path, 0) == APR_SUCCESS) { if(info.scheme) { rc = Shell_exec(CURL_SH, "URL", url, "TARGET", TAR_GZ_SRC, NULL); check(rc == 0, "Failed to curl source: %s", url); } rv = apr_dir_make_recursive(BUILD_DIR, APR_UREAD | APR_UWRITE | APR_UEXECUTE, p); check(rv == APR_SUCCESS, "Failed to make directory %s", BUILD_DIR); rc = Shell_exec(TAR_SH, "FILE", TAR_GZ_SRC, NULL); check(rc == 0, "Failed to untar %s", TAR_GZ_SRC); } else if(apr_fnmatch(TAR_BZ2_PAT, info.path, 0) == APR_SUCCESS) { if(info.scheme) { rc = Shell_exec(CURL_SH, "URL", url, "TARGET", TAR_BZ2_SRC, NULL); check(rc == 0, "Curl failed."); } apr_status_t rc = apr_dir_make_recursive(BUILD_DIR, APR_UREAD | APR_UWRITE | APR_UEXECUTE, p); check(rc == 0, "Failed to make directory %s", BUILD_DIR); rc = Shell_exec(TAR_SH, "FILE", TAR_BZ2_SRC, NULL); check(rc == 0, "Failed to untar %s", TAR_BZ2_SRC); } else { sentinel("Don't now how to handle %s", url); } // indicates that an install needs to actually run return 1; error: return -1; } int Command_build(apr_pool_t *p, const char *url, const char *configure_opts, const char *make_opts, const char *install_opts) { int rc = 0; check(access(BUILD_DIR, X_OK | R_OK | W_OK) == 0, "Build directory doesn't exist: %s", BUILD_DIR); // actually do an install if(access(CONFIG_SCRIPT, X_OK) == 0) { log_info("Has a configure script, running it."); rc = Shell_exec(CONFIGURE_SH, "OPTS", configure_opts, NULL); check(rc == 0, "Failed to configure."); } rc = Shell_exec(MAKE_SH, "OPTS", make_opts, NULL); check(rc == 0, "Failed to build."); rc = Shell_exec(INSTALL_SH, "TARGET", install_opts ? install_opts : "install", NULL); check(rc == 0, "Failed to install."); rc = Shell_exec(CLEANUP_SH, NULL); check(rc == 0, "Failed to cleanup after build."); rc = DB_update(url); check(rc == 0, "Failed to add this package to the database."); return 0; error: return -1; } int Command_install(apr_pool_t *p, const char *url, const char *configure_opts, const char *make_opts, const char *install_opts) { int rc = 0; check(Shell_exec(CLEANUP_SH, NULL) == 0, "Failed to cleanup before building. "); rc = DB_find(url); check(rc != -1, "Error checking the install database."); if(rc == 1) { log_info("Package %s already installed.", url); return 0; } rc = Command_fetch(p, url, 0); if(rc == 1) { rc = Command_build(p, url, configure_opts, make_opts, install_opts); check(rc == 0, "Failed to build: %s", url); } else if(rc == 0) { // no install needed log_info("Depends successfully installed: %s", url); } else { // had an error sentinel("Install failed: %s", url); } Shell_exec(CLEANUP_SH, NULL); return 0; error: Shell_exec(CLEANUP_SH, NULL); return -1; } After you have this entered in and compiling, you can analyze it. If you've don the challenges until now, you should see how the shell.c functions are being used to run shells and how the arguments are being replaced. If not then go back and make sure you truly understand how Shell_exec actually works. ======Challenge 3: Critique My Design====== As before, do a complete review of this code and make sure it's exactly the same. Then go through each function and make sure you know how it works and what it's doing. You also should trace how each function calls the other functions you've written in this file and other files. Finally, confirm that you understand all the functions you're calling from APR here. Once you have the file correct and analyzed, go back through and assume I'm an idiot. Then, criticize the design I have to see how you can improve it if you can. Don't actually change the code, just create a little notes.txt file and write down your thoughts and what you might change. ======The devpkg Main Function====== The last and most important file, but probably the simplest, is devpkg.c where the main function lives. There's no .h file for this, since this one includes all the others. Instead this just creates the executable devpkg when combined with the other .o files from our Makefile. Enter in the code for this file, and make sure it's correct. #include <stdio.h> #include <apr_general.h> #include <apr_getopt.h> #include <apr_strings.h> #include <apr_lib.h> #include "dbg.h" #include "db.h" #include "commands.h" int main(int argc, const char const *argv[]) { apr_pool_t *p = NULL; apr_pool_initialize(); apr_pool_create(&p, NULL); apr_getopt_t *opt; apr_status_t rv; char ch = '\0'; const char *optarg = NULL; const char *config_opts = NULL; const char *install_opts = NULL; const char *make_opts = NULL; const char *url = NULL; enum CommandType request = COMMAND_NONE; rv = apr_getopt_init(&opt, p, argc, argv); while(apr_getopt(opt, "I:Lc:m:i:d:SF:B:", &ch, &optarg) == APR_SUCCESS) { switch (ch) { case 'I': request = COMMAND_INSTALL; url = optarg; break; case 'L': request = COMMAND_LIST; break; case 'c': config_opts = optarg; break; case 'm': make_opts = optarg; break; case 'i': install_opts = optarg; break; case 'S': request = COMMAND_INIT; break; case 'F': request = COMMAND_FETCH; url = optarg; break; case 'B': request = COMMAND_BUILD; url = optarg; break; } } switch(request) { case COMMAND_INSTALL: check(url, "You must at least give a URL."); Command_install(p, url, config_opts, make_opts, install_opts); break; case COMMAND_LIST: DB_list(); break; case COMMAND_FETCH: check(url != NULL, "You must give a URL."); Command_fetch(p, url, 1); log_info("Downloaded to %s and in /tmp/", BUILD_DIR); break; case COMMAND_BUILD: check(url, "You must at least give a URL."); Command_build(p, url, config_opts, make_opts, install_opts); break; case COMMAND_INIT: rv = DB_init(); check(rv == 0, "Failed to make the database."); break; default: sentinel("Invalid command given."); } return 0; error: return 1; } ======Challenge 4: The README And Test Files====== The challenge for this file is to understand how the arguments are being processed, what the arguments are, and then create the README file with instructions on how to use it. As you write the README, also write a simple test.sh that runs ./devpkg to check that each command is actually working against real live code. Use the set -e at the top of your script so that it aborts on the first error. Finally, run the program under valgrind and make sure it's all working before moving on to the mid-term exam. ======The Mid-Term Exam====== Your final challenge is the mid-term exam and it involves three things: * Compare your code to my code available online and starting with 100%, remove 1% for each line you got wrong. * Take your notes.txt on how you would improve the code and functionality of devpkg and implement your improvements. * Write an alternative version of devpkg using your other favorite language or the one you think can do this the best. Compare the two, then improve your C version of devpkg based on what you've learned. To compare your code with mine, do the following: cd .. # get one directory above your current one git clone git://gitorious.org/devpkg/devpkg.git devpkgzed diff -r devpkg devpkgzed This will clone my version of devpkg into a directory devpkgzed and then use the tool diff to compare what you've done to what I did. The files you're working with in this book come directly from this project, so if you get different lines then that's an error. Keep in mind that there's no real pass or fail on this exercise, just a way for you to challenge yourself to be as exact and meticulous as possible. Copyright (C) 2010 Zed. A. Shaw Credits