Ticket #16296: 0001-Make-LockFile-even-more-robust.-Per-16296.patch

File 0001-Make-LockFile-even-more-robust.-Per-16296.patch, 7.5 KB (added by leeroy, 4 years ago)

Improved permission handling. Also fixes 16401.

  • src/main/java/org/torproject/onionoo/util/LockFile.java

    From ea6f26d383aba697bd58e68b1b2f530045968c03 Mon Sep 17 00:00:00 2001
    From: leeroy <ter.one.leeboi@hush.com>
    Date: Fri, 3 Jul 2015 13:07:44 -0400
    Subject: [PATCH] Make LockFile even more robust. Per 16296.
    
    ---
     .../java/org/torproject/onionoo/util/LockFile.java | 186 ++++++++++++++++-----
     1 file changed, 147 insertions(+), 39 deletions(-)
    
    diff --git a/src/main/java/org/torproject/onionoo/util/LockFile.java b/src/main/java/org/torproject/onionoo/util/LockFile.java
    index fc8a792..ee29836 100644
    a b  
    1 /* Copyright 2013 The Tor Project
     1/* Copyright 2015 The Tor Project
    22 * See LICENSE for licensing information */
    33package org.torproject.onionoo.util;
    44
    5 import java.io.BufferedWriter;
    65import java.io.File;
    7 import java.io.FileWriter;
    8 import java.io.IOException;
     6import java.nio.file.Path;
     7import java.nio.file.Paths;
     8import java.nio.file.Files;
     9import java.nio.file.DirectoryStream;
     10import java.nio.file.OpenOption;
     11import java.nio.channels.FileLock;
     12import java.nio.channels.FileChannel;
     13import static java.nio.file.StandardOpenOption.CREATE;
     14import static java.nio.file.StandardOpenOption.READ;
     15import static java.nio.file.StandardOpenOption.WRITE;
    916
    1017import org.slf4j.Logger;
    1118import org.slf4j.LoggerFactory;
    1219
    1320public class LockFile {
    1421
    15   private final static Logger log = LoggerFactory.getLogger(
    16       LockFile.class);
    17 
    18   private final File lockFile = new File("lock");
     22  private final static Logger log =
     23    LoggerFactory.getLogger(LockFile.class);
     24  private FileChannel channel;
     25  private FileLock lock;
     26 
     27  /**
     28   * Default constructor using a lock file named `lock`. 
     29   *
     30   * @return A LockFile object backed by a file named `lock`. 
     31   */
     32  public LockFile() { this("lock"); }
     33 
     34  /**
     35   * Construct a lock file using the filename specified. 
     36   *
     37   * @param file The filename to use when creating a lock file. 
     38   * @return A LockFile object backed by a file named `file`. 
     39   */
     40  public LockFile(String file) {
     41    if (LockFile.checkAccessWithPath(file, true))
     42      channel = openLockFile(file);
     43    else
     44      log.error("Lock file location failed read/write check");
     45  }
    1946
    2047  /**
    21    * Acquire the lock by writing a lock file with the current time in
    22    * milliseconds and return whether this operation was successful.
     48   * Acquire the lock by asking the file channel object for an exclusive
     49   * lock then return whether this operation was successful. 
    2350   *
    24    * @return <code>true</code> if the lock file did not exist and writing
    25    * that file now succeeded, <code>false</code> otherwise.
     51   * @return <code>true</code> if successful, <code>false</code>
     52   * otherwise. 
    2653   */
    2754  public boolean acquireLock() {
    28     Time time = TimeFactory.getTime();
     55 
    2956    try {
    30       if (this.lockFile.exists()) {
    31         return false;
    32       }
    33       if (this.lockFile.getParentFile() != null) {
    34         this.lockFile.getParentFile().mkdirs();
    35       }
    36     } catch (SecurityException e) {
    37       log.error("Unable to access lock file location", e);
    38       return false;
    39     }
    40 
    41     try (BufferedWriter bw = new BufferedWriter(new FileWriter(
    42         this.lockFile))) {
    43       bw.append(String.valueOf(time.currentTimeMillis()));
    44       bw.newLine();
    45       return true;
    46     } catch (IOException e) {
    47       log.error("Caught exception while trying to acquire lock!", e);
    48       return false;
     57      if (channel != null && lock == null) lock = channel.tryLock();
     58    } catch (Exception e) {
     59      log.debug("Failed to acquire lock", e);
    4960    }
     61   
     62    return lock == null ? false: true;
    5063  }
    5164
     65
    5266  /**
    53    * Release the lock by deleting the lock file if it exists and return
    54    * whether the file was successfully deleted.
     67   * Release the lock by asking the file channel to release an existing
     68   * exclusive lock, then return whether this operation was successful. 
    5569   *
    56    * @return <code>true</code> if the lock file does not exist anymore
    57    * when returning, <code>false</code> otherwise.
     70   * @return <code>true</code> if the lock was removed,
     71   * <code>false</code> otherwise. 
    5872   */
    5973  public boolean releaseLock() {
    60     if (this.lockFile.exists()) {
    61       this.lockFile.delete();
     74   
     75    try {
     76      if (channel != null && lock != null) {
     77        lock.release();
     78        lock = null;
     79        return true;
     80      }
     81    } catch (Exception e) {
     82      log.debug("Failed to release lock", e);
    6283    }
    63     return !this.lockFile.exists();
     84   
     85    return false;
     86  }
     87 
     88  /**
     89   * Open a lock file for read/write access. 
     90   *
     91   * @param file The file to use when creating a lock file. 
     92   * @return A FileChannel object for `file` access, null otherwise. 
     93   */
     94  private FileChannel openLockFile(String file) {
     95   
     96    FileChannel fileChannel = null;
     97 
     98    try {
     99      File lockfile = new File(file);   
     100      OpenOption[] openOptions = { CREATE, READ, WRITE };
     101      fileChannel = FileChannel.open(lockfile.toPath(), openOptions );
     102    } catch (Exception e) {
     103      log.debug("Failed to open lock file", e);
     104    }
     105   
     106    return fileChannel;
     107  }
     108 
     109  /**
     110   * Indicate if the path provided has read/write permissions for file
     111   * I/O.  For directory paths that already exist a check will be
     112   * performed on both the specified directory and it's contents.  If a
     113   * path does not exist, a check on the parent-path will be performed
     114   * to see if the child-element can be created.  Optionally specify if
     115   * file paths are allowed to enforce directory checks. 
     116   *
     117   * @param fileOrDir The path to a file or directory to check. 
     118   * @param files When <code>true</code> file paths are allowed. 
     119   * @return <code>true</code> if the path provided is a file or
     120   * directory having read/write access, <code>false</code> otherwise. 
     121   */
     122  public static
     123    boolean checkAccessWithPath(String fileOrDir, boolean files) {
     124     
     125    try {
     126   
     127      Path path = Paths.get(fileOrDir);
     128     
     129      /* Paths which already exist will be handled by this block */
     130      if (Files.exists(path)) {
     131           
     132        if (!Files.isDirectory(path)) { /* File */
     133          return files ? readWriteWithPath(path): false;
     134        }
     135        else if (!readWriteWithPath(path)) return false; /* Directory */
     136        DirectoryStream<Path> contents = Files.newDirectoryStream(path);
     137        for (Path entry: contents) { /* Directory contents */
     138          if (!readWriteWithPath(entry)) return false;
     139        }
     140        contents.close();
     141     
     142      /* Paths which do not exist will be handled by this block */
     143      } else if (Files.notExists(path)) {
     144     
     145        Path parent = path.toAbsolutePath().normalize().getParent();
     146        if (!readWriteWithPath(parent)) return false;
     147     
     148      /* Invalid paths will be handled by this block */
     149      } else { return false; }
     150     
     151      return true; /* Path checks passed */
     152     
     153    } catch (Exception e) {
     154      log.debug("Exception checking access on " + fileOrDir, e);
     155    }
     156   
     157    return false;
     158  }
     159 
     160  /**
     161   * Indicate if the path provided has read/write permissions for file
     162   * I/O. 
     163   *
     164   * @param path The path to a file or directory to check. 
     165   * @return <code>true</code> if the path provided is a file or
     166   * directory having read/write access, <code>false</code> otherwise. 
     167   */
     168  private static boolean readWriteWithPath(Path path) {
     169    return Files.isReadable(path) && Files.isWritable(path);
    64170  }
    65171}
    66172
     173
     174