Package freenet.support.io

Source Code of freenet.support.io.PersistentTempBucketFactory

/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.support.io;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;

import freenet.crypt.EncryptedRandomAccessBucket;
import freenet.crypt.EncryptedRandomAccessBufferType;
import freenet.crypt.MasterSecret;
import freenet.crypt.RandomSource;
import freenet.keys.CHKBlock;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.api.BucketFactory;
import freenet.support.api.RandomAccessBucket;

/**
* Handles persistent temp files. These are used for e.g. persistent downloads. These are
* temporary files in the directory specified for the PersistentFileTracker (which supports
* changing the directory, i.e. moving the files).
*
* These temporary files are encrypted using an ephemeral key (unless the node is configured not to encrypt
* temporary files as happens with physical security level LOW). FIXME NO CRYPTO AT THE MOMENT.
*
* Note that the files are only deleted *after* the transaction containing their deletion reaches
* disk - so we should not leak temporary files, or forget that we deleted a bucket and try to
* reuse it, if there is an unclean shutdown.
*
* PERSISTENCE: This class is involved in persistence but is not itself Serializable; it is
* recreated on every startup, and persistent Bucket's register themselves with it.
*/
public class PersistentTempBucketFactory implements BucketFactory, PersistentFileTracker {

  /** Original contents of directory. This used to be used to delete any files that we can't account for.
   * However at the moment we do not support garbage collection for non-blob persistent temp files.
   * When we implement it it will probably not use this structure... FIXME! */
  private HashSet<File> originalFiles;
 
  /** Filename generator. Tracks the directory and the prefix for temp files, can move them if these
   * change, generates filenames. */
  public final FilenameGenerator fg;
 
  /** Cryptographically strong random number generator */
  private transient RandomSource strongPRNG;
  /** Weak but fast random number generator. */
  private transient Random weakPRNG;
 
  /** Buckets to free. When buckets are freed, we write them to this list, and delete the files *after*
   * the transaction recording the buckets being deleted hits the disk. */
  private final ArrayList<DelayedFree> bucketsToFree;

  private final Object encryptLock = new Object();
  /** Should we encrypt temporary files? */
  private boolean encrypt;
    private MasterSecret secret;
 
  private DiskSpaceChecker checker;
 
 
  private long commitID;

  static final int BLOB_SIZE = CHKBlock.DATA_LENGTH;
 
        private static volatile boolean logMINOR;
  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
      }
    });
  }

  /**
   * Create a temporary bucket factory.
   * @param dir Where to put it.
   * @param prefix Prefix for temporary file names.
   * @param strongPRNG Cryptographically strong random number generator, for making keys etc.
   * @param weakPRNG Weak but fast random number generator.
   * @param encrypt Whether to encrypt temporary files.
   * @throws IOException If we are unable to read the directory, etc.
   */
  public PersistentTempBucketFactory(File dir, final String prefix, RandomSource strongPRNG, Random weakPRNG, boolean encrypt) throws IOException {
    this.strongPRNG = strongPRNG;
    this.weakPRNG = weakPRNG;
    this.encrypt = encrypt;
    this.fg = new FilenameGenerator(weakPRNG, false, dir, prefix);
    if(!dir.exists()) {
      dir.mkdir();
      if(!dir.exists()) {
        throw new IOException("Directory does not exist and cannot be created: "+dir);
      }
    }
    if(!dir.isDirectory())
      throw new IOException("Directory is not a directory: "+dir);
    originalFiles = new HashSet<File>();
    File[] files = dir.listFiles(new FileFilter() {

      @Override
      public boolean accept(File pathname) {
        if(!pathname.exists() || pathname.isDirectory())
          return false;
        String name = pathname.getName();
        if(name.startsWith(prefix))
          return true;
        return false;
      }
    });
    for(File f : files) {
      f = FileUtil.getCanonicalFile(f);
      if(logMINOR)
        Logger.minor(this, "Found " + f);
      originalFiles.add(f);
    }
   
    bucketsToFree = new ArrayList<DelayedFree>();
    commitID = 1; // Must start > 0.
  }
 
  public void setDiskSpaceChecker(DiskSpaceChecker checker) {
      this.checker = checker;
  }
 
  public void setMasterSecret(MasterSecret secret) {
      synchronized(encryptLock) {
          this.secret = secret;
      }
  }
 
  /** Notify the bucket factory that a file is a temporary file, and not to be deleted. FIXME this is not
   * currently used. @see #completedInit() */
  @Override
  public void register(File file) {
    synchronized(this) {
      if(originalFiles == null)
        throw new IllegalStateException("completed Init has already been called!");
      file = FileUtil.getCanonicalFile(file);
      if(logMINOR) Logger.minor(this, "Preserving "+file, new Exception("debug"));
      if(!originalFiles.remove(file))
        Logger.error(this, "Preserving "+file+" but it wasn't found!", new Exception("error"));
    }
  }
 
  /**
   * Called when boot-up is complete.
   * Deletes any old temp files still unclaimed.
   */
  public synchronized void completedInit() {
      if(originalFiles == null) {
          Logger.error(this, "Completed init called twice", new Exception("error"));
          return;
      }
    for(File f: originalFiles) {
      if(Logger.shouldLog(LogLevel.MINOR, this))
        Logger.minor(this, "Deleting old tempfile "+f);
      f.delete();
    }
    originalFiles = null;
  }

  /** Create a persistent temporary bucket. Encrypted if appropriate. Wrapped in a
   * DelayedFreeBucket so that they will not be deleted until after the transaction deleting
   * them in the database commits. */
  @Override
  public RandomAccessBucket makeBucket(long size) throws IOException {
    RandomAccessBucket rawBucket = null;
    boolean mustWrap = true;
    if(rawBucket == null)
      rawBucket = new PersistentTempFileBucket(fg.makeRandomFilename(), fg, this);
    synchronized(encryptLock) {
        if(encrypt) {
                rawBucket = new PaddedRandomAccessBucket(rawBucket);
            rawBucket = new EncryptedRandomAccessBucket(TempBucketFactory.CRYPT_TYPE,
                    rawBucket, secret);
        }
    }
    if(mustWrap)
      rawBucket = new DelayedFreeRandomAccessBucket(this, rawBucket);
    return rawBucket;
  }

  /**
   * Free an allocated bucket, but only after the change has been written to disk.
   */
  @Override
  public void delayedFree(DelayedFree b, long createdCommitID) {
    synchronized(this) {
      if(createdCommitID != commitID) {
          bucketsToFree.add(b);
          return;
      }
    }
    b.realFree();
  }

    /** Returns a list of buckets to free. The caller should write the buckets to the checkpoint,
     * and free them after the checkpoint has written successfully, by calling postCommit(). */
  public DelayedFree[] grabBucketsToFree() {
    synchronized(this) {
      if(bucketsToFree.isEmpty()) return null;
      DelayedFree[] buckets = bucketsToFree.toArray(new DelayedFree[bucketsToFree.size()]);
      bucketsToFree.clear();
      commitID++;
      return buckets;
    }
  }
 
    @Override
    public synchronized long commitID() {
        return commitID;
    }

  /** Get the directory we are creating temporary files in */
  @Override
  public File getDir() {
    return fg.getDir();
  }

  /** Get the FilenameGenerator */
  @Override
  public FilenameGenerator getGenerator() {
    return fg;
  }

  /** Are we encrypting temporary files? */
  public boolean isEncrypting() {
      synchronized(encryptLock) {
          return encrypt;
      }
  }

  /**
   * Set whether to encrypt new persistent temp buckets. Note that we do not encrypt/decrypt old ones when
   * this changes.
   */
  public void setEncryption(boolean encrypt) {
      synchronized(encryptLock) {
          this.encrypt = encrypt;
      }
  }

  /**
   * Delete the buckets.
   */
  public void finishDelayedFree(DelayedFree[] buckets) {
      if(buckets != null) {
          for(DelayedFree bucket : buckets) {
              try {
                  if(bucket.toFree())
                      bucket.realFree();
              } catch (Throwable t) {
                  Logger.error(this, "Caught "+t+" freeing bucket "+bucket+" after transaction commit", t);
              }
          }
      }
  }

    @Override
    public boolean checkDiskSpace(File file, int toWrite, int bufferSize) {
        return checker.checkDiskSpace(file, toWrite, bufferSize);
    }

}
TOP

Related Classes of freenet.support.io.PersistentTempBucketFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.