sbuild-lock.cc

Go to the documentation of this file.
00001 /* Copyright © 2005-2006  Roger Leigh <rleigh@debian.org>
00002  *
00003  * schroot is free software; you can redistribute it and/or modify it
00004  * under the terms of the GNU General Public License as published by
00005  * the Free Software Foundation; either version 2 of the License, or
00006  * (at your option) any later version.
00007  *
00008  * schroot is distributed in the hope that it will be useful, but
00009  * WITHOUT ANY WARRANTY; without even the implied warranty of
00010  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011  * General Public License for more details.
00012  *
00013  * You should have received a copy of the GNU General Public License
00014  * along with this program; if not, write to the Free Software
00015  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
00016  * MA  02111-1307  USA
00017  *
00018  *********************************************************************/
00019 
00020 #include <config.h>
00021 
00022 #include "sbuild-lock.h"
00023 
00024 #include <cerrno>
00025 #include <cstdlib>
00026 
00027 #include <unistd.h>
00028 
00029 #include <boost/format.hpp>
00030 
00031 #include <lockdev.h>
00032 
00033 using boost::format;
00034 using namespace sbuild;
00035 
00036 namespace
00037 {
00038 
00039   typedef std::pair<lock::error_code,const char *> emap;
00040 
00045   emap init_errors[] =
00046     {
00047       emap(lock::TIMEOUT_HANDLER,        N_("Failed to set timeout handler")),
00048       emap(lock::TIMEOUT_SET,            N_("Failed to set timeout")),
00049       emap(lock::TIMEOUT_CANCEL,         N_("Failed to cancel timeout")),
00050       emap(lock::LOCK,                   N_("Failed to lock file")),
00051       // TRANSLATORS: %4% = time in seconds
00052       emap(lock::LOCK_TIMEOUT,           N_("Failed to lock file (timed out after %4% seconds)")),
00053       emap(lock::DEVICE_LOCK,            N_("Failed to lock device")),
00054       // TRANSLATORS: %4% = time in seconds
00055       // TRANSLATORS: %5% = integer process ID
00056       emap(lock::DEVICE_LOCK_TIMEOUT,    N_("Failed to lock device (timed out after %4% seconds; lock held by PID %5%)")),
00057       emap(lock::DEVICE_TEST,            N_("Failed to test device lock")),
00058       emap(lock::DEVICE_UNLOCK,         N_("Failed to unlock device")),
00059       // TRANSLATORS: %4% = time in seconds
00060       // TRANSLATORS: %5% = integer process ID
00061       emap(lock::DEVICE_UNLOCK, N_("Failed to unlock device (timed out after %4% seconds; lock held by PID %5%)"))
00062     };
00063 
00064 }
00065 
00066 template<>
00067 error<lock::error_code>::map_type
00068 error<lock::error_code>::error_strings
00069 (init_errors,
00070  init_errors + (sizeof(init_errors) / sizeof(init_errors[0])));
00071 
00072 namespace
00073 {
00074 
00075   volatile bool lock_timeout = false;
00076 
00082   void
00083   alarm_handler (int ignore)
00084   {
00085     /* This exists so that system calls get interrupted. */
00086     /* lock_timeout is used for polling for a timeout, rather than
00087        interruption. */
00088     lock_timeout = true;
00089   }
00090 }
00091 
00092 lock::lock ():
00093   saved_signals()
00094 {
00095 }
00096 
00097 lock::~lock ()
00098 {
00099 }
00100 
00101 void
00102 lock::set_alarm ()
00103 {
00104   struct sigaction new_sa;
00105   sigemptyset(&new_sa.sa_mask);
00106   new_sa.sa_flags = 0;
00107   new_sa.sa_handler = alarm_handler;
00108 
00109   if (sigaction(SIGALRM, &new_sa, &this->saved_signals) != 0)
00110     throw error(TIMEOUT_HANDLER, strerror(errno));
00111 }
00112 
00113 void
00114 lock::clear_alarm ()
00115 {
00116   /* Restore original handler */
00117   sigaction (SIGALRM, &this->saved_signals, 0);
00118 }
00119 
00120 void
00121 lock::set_timer(struct itimerval const& timer)
00122 {
00123   set_alarm();
00124 
00125   if (setitimer(ITIMER_REAL, &timer, 0) == -1)
00126     {
00127       clear_alarm();
00128       throw error(TIMEOUT_SET, strerror(errno));
00129     }
00130 }
00131 
00132 void
00133 lock::unset_timer ()
00134 {
00135   struct itimerval disable_timer;
00136   disable_timer.it_interval.tv_sec = disable_timer.it_interval.tv_usec = 0;
00137   disable_timer.it_value.tv_sec = disable_timer.it_value.tv_usec = 0;
00138 
00139   if (setitimer(ITIMER_REAL, &disable_timer, 0) == -1)
00140     {
00141       clear_alarm();
00142       throw error(TIMEOUT_CANCEL, strerror(errno));
00143     }
00144 
00145   clear_alarm();
00146 }
00147 
00148 file_lock::file_lock (int fd):
00149   lock(),
00150   fd(fd)
00151 {
00152 }
00153 
00154 file_lock::~file_lock ()
00155 {
00156 }
00157 
00158 void
00159 file_lock::set_lock (lock::type   lock_type,
00160                      unsigned int timeout)
00161 {
00162   try
00163     {
00164       struct itimerval timeout_timer;
00165       timeout_timer.it_interval.tv_sec = timeout_timer.it_interval.tv_usec = 0;
00166       timeout_timer.it_value.tv_sec = timeout;
00167       timeout_timer.it_value.tv_usec = 0;
00168       set_timer(timeout_timer);
00169 
00170       /* Now the signal handler and itimer are set, the function can't
00171          return without stopping the timer and restoring the signal
00172          handler to its original state. */
00173 
00174       /* Wait on lock until interrupted by a signal if a timeout was set,
00175          otherwise return immediately. */
00176       struct flock read_lock =
00177         {
00178           lock_type,
00179           SEEK_SET,
00180           0,
00181           0, // Lock entire file
00182           0
00183         };
00184 
00185       if (fcntl(this->fd,
00186                 (timeout != 0) ? F_SETLKW : F_SETLK,
00187                 &read_lock) == -1)
00188         {
00189           if (errno == EINTR)
00190             throw error(LOCK_TIMEOUT, timeout);
00191           else
00192             throw error(LOCK, strerror(errno));
00193         }
00194       unset_timer();
00195     }
00196   catch (error const& e)
00197     {
00198       unset_timer();
00199       throw;
00200     }
00201 }
00202 
00203 void
00204 file_lock::unset_lock ()
00205 {
00206   set_lock(LOCK_NONE, 0);
00207 }
00208 
00209 device_lock::device_lock (std::string const& device):
00210   lock(),
00211   device(device)
00212 {
00213 }
00214 
00215 device_lock::~device_lock ()
00216 {
00217 }
00218 
00219 void
00220 device_lock::set_lock (lock::type   lock_type,
00221                        unsigned int timeout)
00222 {
00223   try
00224     {
00225       lock_timeout = false;
00226 
00227       struct itimerval timeout_timer;
00228       timeout_timer.it_interval.tv_sec = timeout_timer.it_interval.tv_usec = 0;
00229       timeout_timer.it_value.tv_sec = timeout;
00230       timeout_timer.it_value.tv_usec = 0;
00231       set_timer(timeout_timer);
00232 
00233       /* Now the signal handler and itimer are set, the function can't
00234          return without stopping the timer and restoring the signal
00235          handler to its original state. */
00236 
00237       /* Wait on lock until interrupted by a signal if a timeout was set,
00238          otherwise return immediately. */
00239       pid_t status = 0;
00240       while (lock_timeout == false)
00241         {
00242           if (lock_type == LOCK_SHARED || lock_type == LOCK_EXCLUSIVE)
00243             {
00244               status = dev_lock(this->device.c_str());
00245               if (status == 0) // Success
00246                 break;
00247               else if (status < 0) // Failure
00248                 {
00249                   throw error(DEVICE_LOCK);
00250                 }
00251             }
00252           else
00253             {
00254               pid_t cur_lock_pid = dev_testlock(this->device.c_str());
00255               if (cur_lock_pid < 0) // Test failure
00256                 {
00257                   throw error(DEVICE_TEST);
00258                 }
00259               else if (cur_lock_pid > 0 && cur_lock_pid != getpid())
00260                 {
00261                   // Another process owns the lock, so we successfully
00262                   // "drop" our nonexistent lock.
00263                   break;
00264                 }
00265               status = dev_unlock(this->device.c_str(), getpid());
00266               if (status == 0) // Success
00267                 break;
00268               else if (status < 0) // Failure
00269                 {
00270                   throw error(DEVICE_UNLOCK);
00271                 }
00272             }
00273         }
00274 
00275       if (lock_timeout)
00276         {
00277           throw error(((lock_type == LOCK_SHARED || lock_type == LOCK_EXCLUSIVE)
00278                        ? DEVICE_LOCK_TIMEOUT : DEVICE_UNLOCK_TIMEOUT),
00279                       timeout, status);
00280         }
00281       unset_timer();
00282     }
00283   catch (error const& e)
00284     {
00285       unset_timer();
00286       throw;
00287     }
00288 }
00289 
00290 void
00291 device_lock::unset_lock ()
00292 {
00293   set_lock(LOCK_NONE, 0);
00294 }

Generated on Sat Jan 27 16:11:03 2007 for schroot by  doxygen 1.5.1