#!/usr/local/bin/perl ####################################################################### # Flush the Perl Buffer. # ####################################################################### # The script begins by telling the Perl interpreter that # it should continuously flush its buffer so that text # from this script is sent directly to the Web Browser. # We do this to streamline debugging and make sure that # the script operates with the flow we want it to. $| = 1; ####################################################################### # Send out the http Header # ####################################################################### # Next, the script sends out the http header so that we # can debug more easily and so that the browser won't time # the script out if it take too long to process. print "Content-type: text/html\n\n"; ####################################################################### # Read and Parse Form Data # ####################################################################### # Then, the script loads some supporting files # using the subroutine, require_supporting_libraries # documented at the end of this script. cgi-lib.pl is # used to read and parse form data. auth-lib.pl is used to # authenticate users of the database management system. # db-lib.pl is used to search through the database for # matches to the users search criteria. &require_supporting_libraries (__FILE__, __LINE__, "../dbcMan/Library/cgi-lib.pl", "../dbcMan/Library/auth-lib.pl", "../dbcMan/Library/db-lib.pl"); # Now that cgi-lib.pl has been loaded, we can take # advantage of its subroutines. In particular, we will use # the ReadParse to read the incoming form data. However, # the subroutine is sent "form_data" as a parameter so # that the associative array of form keys/values comes # back with a descriptive name rather than just %in. # # In a nutshell, if we have two input fields on our HTML # form such as the follwoing: # # # # # Then the ReadParse routine will create an associative # array like: # # %form_data = ("id_number", "1", # "setup_file", "data.setup"); # # Throught this script, we will be able to access those # dynamically assigned values when we need them as we # would for an associtive array which was hard coded into # the setup file. We access elemetns in an associative # array using key notation. For example, to assign the # value of "setup_file" to $sf, we'll use: # # $sf = $form_data{'setup_file'}. # # In other words, $sf = "data.setup"; &ReadParse(*form_data); ####################################################################### # Load Supporting Files # ####################################################################### # Once it has read the incoming form data, the script # will be able to determine which setup file it should # use to process the incoming form data. # # Perhaps a bit of explanation is in order. # # Whenever you run this application, you MUST pass to it # the name of the setup file which it will use to process # the management request. # # This variable will provide the name of the file which # this script will use to define all of the customizable # aspects of its operation. For example, the setup file # defines what is contained in the database and which # fields should be displayed to the user. # # The reason for this is that this one script can handle # an infinite amount of databases. # # Each database has a corresponding setup file which defines # how the script performs. The logic (and programming) # remains the same for all db's. All that changes are # the variables and subroutines in the setup files. This # makes it very easy for you to quickly generate diverse # databases with the one backend. # # The script first takes the value of "setup_file" # coming in from the form (which cgi-lib.pl has already # parsed into the %form_data associative array) and # assigns it to the variable $setup_file. # # So how do you get this information to the script? # # There are two ways to do that. Firstly, you can encode # the information into the URL if you are executing this # script directly from a hyperlink. # # For example, you might use the following hyperlink to # direct the script to access address_book.setup: # # http://www.you.com/cgi/db_manager.cgi?setup_file=address_book.setup # # You can also send this information as a hidden field in # an HTML form using something like the following. # # # # For example, the following code would define a setup # file called address_book.setup: # # # # You might also create a select box so that the user can # choose from a number of databases dynamically: # # # # All these examples assume you placed the setup files in # the provided subdirectory called Setup_files. # # The script uses the subroutine require_supporting_libraries # documented later in this script to actually load the # setup file and all of its configuration options. $setup_file = "dbcsearch.setup"; &require_supporting_libraries (__FILE__, __LINE__, "$setup_file"); ####################################################################### # Authenticate User # ####################################################################### # Next the script must make sure that if the admin has # instructed it to authenticate all users through the # setup file variable $should_i_authenticate, that it # provides a level of authentication. Authentication is # handled by the authentication subroutine which is # documented later in this script. if ($should_i_authenticate eq "yes") { &authentication; } ####################################################################### # Perform Management Functions # ####################################################################### # After the user has been authenticated (if necessary), it # is time for the script to perform any or all of the # management functions. # # There are 5 general functions that this script must # provide. The script must be able to 1) add a new item # to the database, 2) modify an item in the database, # 3) delete an item in the database, 4) provide the user # with a view of the database and finally 5) present # the user with a frontpage to give the user the previous # four options. # # These five general functions are broken down into # several other steps each. For example, the general step # of modifying an item in the database can be broken down # into three sub-steps. 1) The user must choose to # "Modify an Item" from a submit button on the frontpage # 2) they must be taken to a screen on which they can # enter search criteria so that the script can dynamically # generate a list of matches from which they can then # choose a specific item to modify and 3) they user must # be able to choose an item from that list, describe the # changes she wants made and then submit the changes to # the database. # # When all is said and done, there are 11 general and # sub-functions that make up the main body of logic of # this application. We will go over each as they appear # in the code below. # # The logic is broken down into a series of "if" tests. # # Specifically, the script checks the values of incoming # administrative form variables (mainly supplied from # the SUBMIT buttons on dynamically generated HTML forms) # and will perform its operations depending on whether # those administrative variables have values associated # with them or not. # # The basic format for such an "if" test follows the # syntax: # # if (the value of some submit button ne "") # { # process that type of request; # exit; # } # # For example, consider the first case in which the # customer has clicked on the "Add an Item" submit # button denoted with the NAME value of # "add_item_button". # # if ($form_data{'add_item_button'} ne "") # { # &generic_header("Add an Item to the Database Form"); # &generic_form_header; # &add_form_header; # &add_modify_data_entry_form; # &add_form_footer; # &generic_form_footer; # exit; # } # # Because the submit button will have some value # like "Add this new item", when the script reaches # this statement block, it will answer true to the test. # # Since the customer can only click on one submit button # at a time, we can be assured that only one operation # will answer true. # # The beauty of using the not equal (ne) test is that # regardless of what the submit button actually says # (it might say "Add a weiner dog to the chopping block") # the if test will still be satisfied if they have clicked # the button, since whatever the VALUE is, it will # certainly not be equal to "nothing". Of course, this # assumes that you do not rename the NAME argument of the # submit buttons. If you do so, you must harmonize the # variable you use on the input forms, with the variables # used here to test. # # Similarly, if you wish to have graphical submit buttons # instead of the ugly default buttons supplied by the # browser, you will have to modify the if tests so that # they follow the standard image map test: # # if ($form_data{'some_button.x'} ne "") # { # do something # } # # where the HTML code looks like the following: # # # # Thus, if the button actually has an X-dimension value, # it means that the button had been clicked. # # Finally, note that every if test is concluded with an # exit statement. This is because once the script is done # executing the routine specified in the submit button, it # is done with its work and should exit immediately. # # Get used to the idea that this script is "self-referencing". # The application itself contains many mini-routines # which all refer back to the routine community. Every # instance of the script need only execute maybe 1/11th of # the routines in the whole file, but in the lifetime of # the application, most, if not all, routines are # executed. # # Okay, so now let's look at each of the routines which # this applicaiton must execute. # # 1. If the user clicks the "add an item" button on the # frontpage, they must be presented with an HTML form with # input fields for each field in the database which they # have access to define. The user can then fill in the # input fields and submit the info to be added to the # database. # # The generation of this add form is handled by # several subroutines which are all defined in the setup # file. generic_header will print out the basic HTML # header with the passed parameter placed between the # and tags. generic_form_header will # print out the basic
tag as well as the important # hidden state fields"setup_file" and "session_file" which # must appear on ever screen. add_form_header displays # the header of the add form and # add_modify_data_entry_form displays the actual input # fields for the form. add_form_footer and # generic_form_footer display the page footer information. # # 2. Once the user submits the information that they want # to add to the database by clicking the button NAMED # "submit_addition", the script must be prepared to # actually modify the database with the new information. # # This is handled in the next if test. The subroutine # submit_addition which is the meet of this function is # discussed later in this script. # # 3. On the other hand, the user might be asking to modify # an item by clicking on the frontpage button NAMED # "modify_item_button". If this is the case, the user # must be presented a form upon which she can enter search # criteria so that the database can bring back a list of # database rows which match her search criteria. The user # can then choose one of them and modify it. We need this # search filter because in a large database, it would be # hard to select which row you wanted to modify without # dome level of filtering. The search form is actually # generated with the modify_search_form subroutine # discussed further down in this script. # # 4. Once the user fills out the search criteria on the # form generated by the routine discussed above, and # clicked on the button NAMED # "search_and_display_for_modification_button" the # script must search through the database and come up with # a list of hits. The user then can select one of the # rows to modify and then type in any new information she # wants. # # This requires two things. 1) The script must generate a # list of hits with a radio button for selection of each # row (only one row may be modified at one time) and 2) # the script must provide a form similar to the add an # item form so that the user can enter new info (if the # user does not enter info into an inpout field, the old # data will be kept.) These chores are handled by # the subroutine search_and_display_for_modification which # is discussed later in this script. # # 5. Finally, the user will have selected an item and # filled in some new information to modify and clicked on # the button NAMED "submit_modification_button". Now it # is time to actually submit this modification to the # database. We'll do this with the subroutine # "submit_modification" discussed below. # # 6. Besides adding an item and modifying an item, the # user may wish to delete an item. If the user clicks the # frontpage buttun NAMED "delete_item_button", the script # will display a form identical to the one which the user # got in step 3 so that the can define some search # criteria with which the script can use to generate a # dynamic list of database rows which may be deleted. # # 7. As with modification, the user will enter in some # search criteria and click the submit button named # "search_and_display_for_deletion_button". The script # will then prepare the list of hits from which the user # can delete. Unlike in the case of modification however, # there is no need to also include a form similar to the # "add" form, since we will not be modifying the data, we # will instead be deleteing the row entirely. # # 8. Finally, the user will select one of the database # rows displayed by routine 7 and click on the # "submit_deletion_button" button. At this point, the # script must actually delete the item from the database. # This is performed with the submit_deletion subrtoutine # discussed later in this script. # # 9. Next, the user might have asked to simply view the # dataabse by clicking the "view_database_button" button # on the frontpage. If this is the case, the user needs #to be presented with the familiar search form so that # they can enter in search criteria which the script can # use to generate a dynamic list of database rows to view. # # 10. As before, once the user choose some search criteria # and hits the submit button, the script must display the # list of hits. # # Finally, the script must display the very frontpage from # which the previous 10 functions were derrived. This # frontpage must have 4 buttons, one for add, modify, # delete and view functions. # # Okay, well that was quite a mouthful...I have a # suspicion that a picture is worth those thousand # words (each step is in parenthesis)... # # ----------------Display Frontpage (11)---------- # | | | | # User Clicks User Clicks User Clicks User Clicks # Add Button Modify Button Delete Button View Button # | | | | # Display Add Display (3) Display (6) Display (9) # Form (1) Search Form Search Form Search Form # | | | | # User Adds User Submits User Submits User Submits # New Item (2) Search Search Search # | | | # Mod Form (4) Delete Form Search Hits # Displayed Displayed (7) Displayed # | | (10) # User Submits User Submits # Modification Deletion (8) # (5) # # Below are the 11 routines discussed above. # here is the check for the categories so that it uses only one of the search fields. if ($form_data{'categorytext'} ne "") { $form_data{'category'} = $form_data{'categorytext'} } #a possible fix for the info field bug $form_data{info}=~ s/%0D%0A//g; $form_data{info}=~ s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge; #### check to see if at least one of the fields is filed out. if ( ($form_data{category} eq "All Categories") && (!($form_data{member})) && (!($form_data{contact}))) { print ("

Please enter a search field.

"); exit } if ($form_data{'search_and_display_db_button'} ne "") { &search_and_display_db_for_view; exit; } else { exit; } # Well, that is it! That is the whole script. Well, not # exaclty, now it is time to explain the subroutines which # make the above if tests work. ####################################################################### # Require Supporting Libraries. # ####################################################################### # require_supporting_libraries is used to read in some of # the supporting files that this script will take # advantage of. # # require_supporting_libraries takes a list of arguments # beginning with the current filename, the current line # number and continuing with the list of files which must # be required using the following syntax: # # &require_supporting_libraries (__FILE__, __LINE__, # "file1", "file2", # "file3"...); # # Note: __FILE__ and __LINE__ are special Perl variables # which contain the current filename and line number # respectively. We'll continually use these two variables # throughout the rest of this script in order to generate # useful error messages. sub require_supporting_libraries { # The incoming file and line arguments are split into # the local variables $file and $line while the file list # is assigned to the local list array @require_files. # # $require_file which will just be a temporary holder # variable for our foreach processing is also defined as a # local variable. local ($file, $line, @require_files) = @_; local ($require_file); # Next, the script checks to see if every file in the # @require_files list array exists (-e) and is readable by # it (-r). If so, the script goes ahead and requires it. foreach $require_file (@require_files) { if (-e "$require_file" && -r "$require_file") { require "$require_file"; } # If not, the scripts sends back an error message that # will help the admin isolate the problem with the script. else { print "Content-type: text/html\n\n"; print "I am sorry but I was unable to require $require_file at line $line in $file. Would you please make sure that you have the path correct and that the permissions are set so that I have read access? Thank you."; exit; } } # End of foreach $require_file (@require_files) } # End of sub require_supporting_libraries ####################################################################### # Perform Authentication # ####################################################################### # Authentication is performed by the set of "auth" # libraries in the Library subdirectory. However, the # only one "you" need to worry about is auth-lib.pl. # Specifically, you call GetSessionInfo from auth-lib.pl # and it worries about all the other supporting libraries # on its own. # # Authentication demands that you define several variables # in the setup file as well and &GetSessionInfo will send # you back several variables hich you can use within this # script (particularly $session_username and # $session_group which are used for authentication of # modifications. # # The basic flow of authentication works like this: # Initially, when you install the application, you set # $auth_add_register and $auth_allow_register to "on" and # you set $auth_default_group to "Admin". This will allow # you to add yourself as an administrator. go ahead and # run the script from the web and click on the "Register # New User Button" and fill out the resulting form. # # Once you have registered yourself as an admin, you have # two options. Firstly, you can turn off registration so # that only you can use this applicaton. To do so, turn # "off" $auth_add_register and $auth_allow_register. Now # you will be able togon, but noone else will be able to # do so without your username and encrypted password. # # You might also decide to allow "users" to have access to # the database manager. These people will be able to add # entries and delete and modify each others entries but # not touch yours. To do so, change $auth_default_group # to "user". sub authentication { # Make sure if the session_file variable is coming in as # form data that it is converted to a local variable name. # This is important. We do not want to have to ask the # user to logon for every screen they want to see! Thus, # we must make sure that session_file is passed (in a self # referential way) between every screen in this # application through the use of hidden form variables. $session_file = "$form_data{'session_file'}"; # Now perform the authentication and gather the returned # values. ($session_file, $session_username, $session_group,$session_first_name, $session_last_name, $session_email) = &GetSessionInfo($session_file, $this_script_url, *form_data); } ####################################################################### # Search and Display the Database # ####################################################################### sub search_and_display_db { # Before we go in and search however, we format any # incoming sort_by information. We'll discuss the sorting # algorithm in just a minute. However, I want to note # here that there are two ways to define a field by which # this script will sort the returned database rows. You # can set a default row in the setup file by setting # $index_of_field_to_be_sorted_by equal to the index of # the field that you want sorted by. Thus, you may just # want to sort automatically by last name and not even # give the user the option to sort by another row. # # On the other hand, you might want to allow the user to # choose which field the returned rows are sorted by. If # this is the case, you need to add another form variable # to your HTML interface. This variable MUST be called # "sort_by" and will usually be in the form of a # select box such as the following: # # Sort by which field # # # If you allow the user to define which field to sort on, # then this information will override the information in # the setup file using the following it test. # # Remember that arrays start counting from zero so the # first filed in your dataabse has an index value of 0, # not 1 if ($form_data{'sort_by'} ne "") { $index_of_field_to_be_sorted_by = $form_data{'sort_by'}; } # okay, now display the header and grab our lisdt of # database rows using &submit_query in db-lib.pl. Notice # that you need to redefine # $index_of_field_to_be_sorted_by "before" you display the # header because the header displays the hidden form field # which will carry that data throughout further # self-referencing screens. ($total_row_count) = &submit_query(*database_rows); # Now here is where the real fun comes in. We want to # sort the database rows that are displayed to the user. # The process of this is fairly simple. For every # database row contained in @database_rows, we are going # to grab the value of the field defined as the field to # be sorted by and append that value to the very begining # of the line (so that the field will be repeated twice.) # Then you sort the rows (sort will sort on the first # characters first which is why you need to append the # sortable field to the front.) Then, finally, you remove # the appended field so that the database rows are as they # began, but in a sorted order. # # Thus, if you were sorting by last name and you had the # following database rows ($row) in the @database_rows # array: # # Eric|Tachibanaerict@eff.org # Selena|Sol|selena@eff.org # Gunther|Birznieks|birzniek@hlsun.redcross.org # # The script would then take each row and append the last # name field to the front like so: # # Tachibana|Eric|Tachibana|erict@eff.org # Sol|Selena|Sol|selena@eff.org # Birznieks|Gunther|Birznieks|birzniek@hlsun.redcross.org foreach $row (@database_rows) { @row = split (/\|/, $row); $sortable_field = $row[$index_of_field_to_be_sorted_by]; unshift (@row, $sortable_field); $new_row = join ("\|", @row); if ($row[$index_for_web + 1] ne "" && $row[$index_for_web+1] ne " ") { push (@new_rows_web, $new_row); } else { push (@new_rows, $new_row); } } # Once we have the rows reformatted as above, we are ready # to sort them. First however, we erase the contents of # @database_rows since we are going to want to recreate # that array with the sorted rows from @new_rows in just a # moment. @database_rows = (); # Then we are ready to sort...guess what the result is: # # Birznieks|Gunther|Birznieks|birzniek@hlsun.redcross.org # Sol|Selena|Sol|selena@eff.org # Tachibana|Eric|Tachibana|erict@eff.org @sorted_rows_web = sort (@new_rows_web); @sorted_rows = sort (@new_rows); unshift (@sorted_rows, @sorted_rows_web); # append the rows with web to the beggining # Next, we need remove that first sortable field so that # we have the following: # # Gunther|Birznieks|birzniek@hlsun.redcross.org # Selena|Sol|selena@eff.org # Eric|Tachibana|erict@eff.org # # Look! They are now sorted by last name! By the way, if # you sort by a field with numbers, remember that # computers sort with their own funky rules. That is, if # you don't put a 0 before the nuber 1, it will sort after # 9 but alphabetical sorting should be just fine. foreach $sorted_row (@sorted_rows) { @row = split (/\|/, $sorted_row); $sorted_field = shift (@row); $old_but_sorted_row = join ("\|", @row); push (@database_rows, $old_but_sorted_row); } # now that we have sorted the rows, lets figure out how to # display them all. # # The reason that we wanted to get the $total_row_count # back from the search libraries is so that we can then # check to make sure that if their search returned no hits # we can let them know rather than just sending them a # blank screen. if ($total_row_count < 1) { &no_hits_message; exit; } # So what exactly do we show the user if their search did # turn up some hits. Well, that depends on 1) how many # rows were returned from the database as scoring matches # to their search criteria, 2) how many rows we have # defined in the setup file to allow them to see and 3) # how many rows they have already seen. # # Let me expound. Let's assume that we have set # $max_rows_returned to 2 in the setup file and that their # search turned up 11 hits which have just been sorted. # # The first screen that they should see should say, "You # scored 10 hits and I have been instructed to show you # two at a time". It should then display the first two # sorted rows and then provide a button which says "See # next 2 hits". When the user clicks on that button, she # should then get the next two sorted rows. The script # needs to remember that she already saw the first two # rows as well as remembering that it should only show her # two at a time. # # Finally, the script will have gone through all the rows # up to 9 and 10. The final trick is that it must then # tell her that she can click the button to see the next 1 # hi(t)...no "s" on the end of that....the script has to # know some grammar rules. # # So first, we will collect any incoming information about # the hits that the client has seen so far. This # information will be stored in a hidden inpout field # called "hits_seen" which must accompany every submit # button that promises to show "x more hits". Note that # the first time around, there will be no new_hits_seen # value coming in from the form since the user will not # have seen any hits yet. # Then we need to remove all of the rows from # @database_rows that will not be shown to the user quite # yet because we are only allowed to display # max_rows_returned at any one time. # # To do this we will first figure out how many elements # are left in the array. Then, we will pop out (remove # from the end of the list) all of the extra rows. $hits_seen = $form_data{'new_hits_seen'}; # Now the script will go through and remove from # @database_rows all of the rows that have already been # seen by shifting off (remove from the front of # the array) one element up to the value of $hits_seen for ($i = 1;$i <= $hits_seen;$i++) { $seen_row = shift (@database_rows); } # Then we need to remove all of the rows from # @database_rows that will not be shown to the user quite # yet because we are only allowed to display # max_rows_returned at any one time. # # To do this we will first figure out how many elements # are left in the array. Then, we will pop out (remove # from the end of the list) all of the extra rows. $length_of_database_rows = @database_rows; if ($length_of_database_rows >($max_rows_returned)) { for ($i = $length_of_database_rows-1;$i >= $max_rows_returned;$i--) { $extra_row = pop (@database_rows); } } # Now we will reset $new_hits_seen so that we can # incorporate this value in the hidden form tag generated # by &search_results_footer so that the next time we come # to this routine, we can give the user the next set of # hits. #$new_hits_seen = $hits_seen + $max_rows_returned; # Now we will actually send the results of the search to # the user as well as send the HTML footer. And that, # they say, is that. &search_results_body; &search_results_footer; } ################################################################# # get_date Subroutine # ################################################################# # get_date is used to get the current date and time and # format it into a readable form. The subroutine takes no # arguments and is called with the following syntax: # # $date = &get_date; # # It will return the value of the current date, so you # must assign it to a variable in the calling routine if # you are going to use the value. sub get_date { # The subroutine begins by defining some local working # variables local ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst,$date); local (@days, @months); @days = ('Sunday','Monday','Tuesday','Wednesday','Thursday', 'Friday','Saturday'); @months = ('January','February','March','April','May','June','July', 'August','September','October','November','December'); # Next, it uses the localtime command to get the current # time, from the value returned by the time # command, splitting it into variables. ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); # Then the script formats the variables and assign them to # the final $date variable. Note that $sc_current_century # is defined in web_store.setup. Since the 20th centruy # is really 1900-1999, we'll need to subtract 1 from this # value in order to format the year correctly. if ($hour < 10) { $hour = "0$hour"; } if ($min < 10) { $min = "0$min"; } if ($sec < 10) { $sec = "0$sec"; } $mon++; $year = ($current_century-1) . "$year"; $date = "$mon/$mday/$year at $hour\:$min\:$sec"; return $date; } ####################################################################### # get_file_lock # ####################################################################### # get_file_lock is a subroutine used to create a lockfile. # Lockfiles are used to make sure that no more than one # instance of the script can modify a file at one time. A # lock file is vital to the integrity of your data. # Imagine what would happen if two or three people # were using the same script to modify a shared file (like # the error log) and each accessed the file at the same # time. At best, the data entered by some of the users # would be lost. Worse, the conflicting demands could # possibly result in the corruption of the file. # # Thus, it is crucial to provide a way to monitor and # control access to the file. This is the goal of the # lock file routines. When an instance of this script # tries to access a shared file, it must first check for # the existence of a lock file by using the file lock # checks in get_file_lock. # # If get_file_lock determines that there is an existing # lock file, it instructs the instance that called it to # wait until the lock file disappears. The script then # waits and checks back after some time interval. If the # lock file still remains, it continues to wait until some # point at which the admin has given it permissios to just # overwrite the file because some other error must have # occurred. # # If, on the other hand, the lock file has dissappeared, # the script asks get_file_lock to create a new lock file # and then goes ahead and edits the file. # # The subroutine takes one argumnet, the name to use for # the lock file and is called with the following syntax: # # &get_file_lock("file.name"); sub get_file_lock { local ($lock_file) = @_; local ($endtime); $endtime = 20; $endtime = time + $endtime; # We set endtime to wait 20 seconds. If the lockfile has # not been removed by then, there must be some other # problem with the file system. Perhaps an instance of # the script crashed and never could delete the lock file. while (-e $lock_file && time < $endtime) { sleep(1); } open(LOCK_FILE, ">$lock_file") || &file_open_error ("$lock_file", "Lock File Routine", __FILE__, __LINE__); # Note: If flock is available on your system, feel free to # use it. flock is an even safer method of locking your # file because it locks it at the system level. The above # routine is "pretty good" and it will server for most # systems. But if youare lucky enough to have a server # with flock routines built in, go ahead and uncomment # the next line and comment the one above. # flock(LOCK_FILE, 2); # 2 exclusively locks the file } ####################################################################### # release_file_lock # ####################################################################### # release_file_lock is the partner of get_file_lock. When # an instance of this script is done using the file it # needs to manipulate, it calls release_file_lock to # delete the lock file that it put in place so that other # instances of the script can get to the shared file. It # takes one argument, the name of the lock file, and is # called with the following syntax: # # &release_file_lock("file.name"); sub release_file_lock { local ($lock_file) = @_; # flock(LOCK_FILE, 8); # 8 unlocks the file # As we mentioned in the discussion of get_file_lock, # flock is a superior file locking system. If your system # has it, go ahead and use it instead of the hand rolled # version here. Uncomment the above line and comment the # two that follow. close(LOCK_FILE); unlink($lock_file); } ####################################################################### # file_open_error Subroutine # ####################################################################### # If there is a problem opening a file or a directory, it # is useful for the script to output some information # pertaining to what problem has occurred. This # subroutine is used to generate those error messages. # # file_open_error takes four arguments: the file or # directory which failed, the section in the code in which # the call was made, the current file name and # line number, and is called with the following syntax: # # &file_open_error("file.name", "ROUTINE", __FILE__, # __LINE__); sub file_open_error { # The subroutine simply uses the update_error_log # subroutine discussed later to modify the error log and # then uses CgiDie in cgi-lib.pl to gracefully exit the # application with a useful debugging error message sent # to the browser window. local ($bad_file, $script_section, $this_file, $line_number) = @_; print "Content-type: text/html\n\n"; &CgiDie ("I am sorry, but I was not able to access $bad_file in the $script_section routine of $this_file at line number $line_number. Would you please make sure the path is correctly defined in web_store.setup and that the permissions are correct.") }