Prevent integer overflow on exabyte filesystems

patch by Matt Wringe and Benjamin Lerer; reviewed by Alex Petrov for 
CASSANDRA-13067


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/270f690f
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/270f690f
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/270f690f

Branch: refs/heads/cassandra-3.0
Commit: 270f690ff6047cc3e797a3f34b7efa26e7232183
Parents: 739cd2b
Author: Benjamin Lerer <b.le...@gmail.com>
Authored: Tue Aug 8 16:51:03 2017 +0200
Committer: Benjamin Lerer <b.le...@gmail.com>
Committed: Tue Aug 8 16:51:03 2017 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../cassandra/config/DatabaseDescriptor.java    |  28 ++-
 .../org/apache/cassandra/db/Directories.java    |   2 +-
 .../org/apache/cassandra/io/util/FileUtils.java | 182 +++++++++++++++++--
 4 files changed, 193 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/270f690f/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 36c34a1..f712333 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.2.11
+ * Prevent integer overflow on exabyte filesystems (CASSANDRA-13067) 
  * Fix queries with LIMIT and filtering on clustering columns (CASSANDRA-11223)
  * Fix potential NPE when resume bootstrap fails (CASSANDRA-13272)
  * Fix toJSONString for the UDT, tuple and collection types (CASSANDRA-13592)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/270f690f/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java 
b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 981026d..90a82fe 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -21,7 +21,6 @@ import java.io.File;
 import java.io.IOException;
 import java.net.*;
 import java.nio.file.FileStore;
-import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -56,6 +55,9 @@ import org.apache.cassandra.thrift.ThriftServer;
 import org.apache.cassandra.utils.FBUtilities;
 import org.apache.cassandra.utils.memory.*;
 
+import static org.apache.cassandra.io.util.FileUtils.ONE_GB;
+import static org.apache.cassandra.io.util.FileUtils.ONE_MB;
+
 public class DatabaseDescriptor
 {
     private static final Logger logger = 
LoggerFactory.getLogger(DatabaseDescriptor.class);
@@ -530,7 +532,7 @@ public class DatabaseDescriptor
             try
             {
                 // use 1/4 of available space.  See discussion on #10013 and 
#10199
-                minSize = 
Ints.checkedCast((guessFileStore(conf.commitlog_directory).getTotalSpace() / 
1048576) / 4);
+                minSize = 
Ints.saturatedCast((guessFileStore(conf.commitlog_directory).getTotalSpace() / 
1048576) / 4);
             }
             catch (IOException e)
             {
@@ -576,7 +578,7 @@ public class DatabaseDescriptor
 
             try
             {
-                dataFreeBytes += guessFileStore(datadir).getUnallocatedSpace();
+                dataFreeBytes = saturatedSum(dataFreeBytes, 
guessFileStore(datadir).getUnallocatedSpace());
             }
             catch (IOException e)
             {
@@ -585,9 +587,9 @@ public class DatabaseDescriptor
                                                                datadir), e);
             }
         }
-        if (dataFreeBytes < 64L * 1024 * 1048576) // 64 GB
+        if (dataFreeBytes < 64 * ONE_GB)
             logger.warn("Only {} MB free across all data volumes. Consider 
adding more capacity to your cluster or removing obsolete snapshots",
-                        dataFreeBytes / 1048576);
+                        dataFreeBytes / ONE_MB);
 
 
         if (conf.commitlog_directory.equals(conf.saved_caches_directory))
@@ -697,6 +699,20 @@ public class DatabaseDescriptor
             throw new 
ConfigurationException("otc_coalescing_enough_coalesced_messages must be 
positive", false);
     }
 
+    /**
+     * Computes the sum of the 2 specified positive values returning {@code 
Long.MAX_VALUE} if the sum overflow.
+     *
+     * @param left the left operand
+     * @param right the right operand
+     * @return the sum of the 2 specified positive values of {@code 
Long.MAX_VALUE} if the sum overflow.
+     */
+    private static long saturatedSum(long left, long right)
+    {
+        assert left >= 0 && right >= 0;
+        long sum = left + right;
+        return sum < 0 ? Long.MAX_VALUE : sum;
+    }
+
     private static FileStore guessFileStore(String dir) throws IOException
     {
         Path path = Paths.get(dir);
@@ -704,7 +720,7 @@ public class DatabaseDescriptor
         {
             try
             {
-                return Files.getFileStore(path);
+                return FileUtils.getFileStore(path);
             }
             catch (IOException e)
             {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/270f690f/src/java/org/apache/cassandra/db/Directories.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/Directories.java 
b/src/java/org/apache/cassandra/db/Directories.java
index 2b3662f..fa76b61 100644
--- a/src/java/org/apache/cassandra/db/Directories.java
+++ b/src/java/org/apache/cassandra/db/Directories.java
@@ -482,7 +482,7 @@ public class Directories
 
         public long getAvailableSpace()
         {
-            return location.getUsableSpace();
+            return FileUtils.getUsableSpace(location);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/270f690f/src/java/org/apache/cassandra/io/util/FileUtils.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/io/util/FileUtils.java 
b/src/java/org/apache/cassandra/io/util/FileUtils.java
index 8d122dd..bf0fae5 100644
--- a/src/java/org/apache/cassandra/io/util/FileUtils.java
+++ b/src/java/org/apache/cassandra/io/util/FileUtils.java
@@ -21,11 +21,12 @@ import java.io.*;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.file.*;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
 import java.text.DecimalFormat;
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.cassandra.config.Config;
 import sun.nio.ch.DirectBuffer;
 
 import org.slf4j.Logger;
@@ -45,10 +46,10 @@ import static org.apache.cassandra.utils.Throwables.merge;
 public final class FileUtils
 {
     private static final Logger logger = 
LoggerFactory.getLogger(FileUtils.class);
-    private static final double KB = 1024d;
-    private static final double MB = 1024*1024d;
-    private static final double GB = 1024*1024*1024d;
-    private static final double TB = 1024*1024*1024*1024d;
+    public static final long ONE_KB = 1024;
+    public static final long ONE_MB = 1024 * ONE_KB;
+    public static final long ONE_GB = 1024 * ONE_MB;
+    public static final long ONE_TB = 1024 * ONE_GB;
 
     private static final DecimalFormat df = new DecimalFormat("#.##");
     private static final boolean canCleanDirectBuffers;
@@ -330,27 +331,27 @@ public final class FileUtils
     public static String stringifyFileSize(double value)
     {
         double d;
-        if ( value >= TB )
+        if ( value >= ONE_TB )
         {
-            d = value / TB;
+            d = value / ONE_TB;
             String val = df.format(d);
             return val + " TB";
         }
-        else if ( value >= GB )
+        else if ( value >= ONE_GB )
         {
-            d = value / GB;
+            d = value / ONE_GB;
             String val = df.format(d);
             return val + " GB";
         }
-        else if ( value >= MB )
+        else if ( value >= ONE_MB )
         {
-            d = value / MB;
+            d = value / ONE_MB;
             String val = df.format(d);
             return val + " MB";
         }
-        else if ( value >= KB )
+        else if ( value >= ONE_KB )
         {
-            d = value / KB;
+            d = value / ONE_KB;
             String val = df.format(d);
             return val + " KB";
         }
@@ -478,4 +479,159 @@ public final class FileUtils
     {
         fsErrorHandler.getAndSet(handler);
     }
+
+    /**
+     * Returns the size of the specified partition.
+     * <p>This method handles large file system by returning {@code 
Long.MAX_VALUE} if the  size overflow.
+     * See <a 
href='https://bugs.openjdk.java.net/browse/JDK-8179320'>JDK-8179320</a> for 
more information.</p>
+     *
+     * @param file the partition
+     * @return the size, in bytes, of the partition or {@code 0L} if the 
abstract pathname does not name a partition
+     */
+    public static long getTotalSpace(File file)
+    {
+        return handleLargeFileSystem(file.getTotalSpace());
+    }
+
+    /**
+     * Returns the number of unallocated bytes on the specified partition.
+     * <p>This method handles large file system by returning {@code 
Long.MAX_VALUE} if the  number of unallocated bytes
+     * overflow. See <a 
href='https://bugs.openjdk.java.net/browse/JDK-8179320'>JDK-8179320</a> for 
more information</p>
+     *
+     * @param file the partition
+     * @return the number of unallocated bytes on the partition or {@code 0L}
+     * if the abstract pathname does not name a partition.
+     */
+    public static long getFreeSpace(File file)
+    {
+        return handleLargeFileSystem(file.getFreeSpace());
+    }
+
+    /**
+     * Returns the number of available bytes on the specified partition.
+     * <p>This method handles large file system by returning {@code 
Long.MAX_VALUE} if the  number of available bytes
+     * overflow. See <a 
href='https://bugs.openjdk.java.net/browse/JDK-8179320'>JDK-8179320</a> for 
more information</p>
+     *
+     * @param file the partition
+     * @return the number of available bytes on the partition or {@code 0L}
+     * if the abstract pathname does not name a partition.
+     */
+    public static long getUsableSpace(File file)
+    {
+        return handleLargeFileSystem(file.getUsableSpace());
+    }
+
+    /**
+     * Returns the {@link FileStore} representing the file store where a file
+     * is located. This {@link FileStore} handles large file system by 
returning {@code Long.MAX_VALUE}
+     * from {@code FileStore#getTotalSpace()}, {@code 
FileStore#getUnallocatedSpace()} and {@code FileStore#getUsableSpace()}
+     * it the value is bigger than {@code Long.MAX_VALUE}. See <a 
href='https://bugs.openjdk.java.net/browse/JDK-8162520'>JDK-8162520</a>
+     * for more information.
+     *
+     * @param path the path to the file
+     * @return the file store where the file is stored
+     */
+    public static FileStore getFileStore(Path path) throws IOException
+    {
+        return new SafeFileStore(Files.getFileStore(path));
+    }
+
+    /**
+     * Handle large file system by returning {@code Long.MAX_VALUE} when the 
size overflows.
+     * @param size returned by the Java's FileStore methods
+     * @return the size or {@code Long.MAX_VALUE} if the size was bigger than 
{@code Long.MAX_VALUE}
+     */
+    private static long handleLargeFileSystem(long size)
+    {
+        return size < 0 ? Long.MAX_VALUE : size;
+    }
+
+    /**
+     * Private constructor as the class contains only static methods.
+     */
+    private FileUtils()
+    {
+    }
+
+    /**
+     * FileStore decorator used to safely handle large file system.
+     *
+     * <p>Java's FileStore methods 
(getTotalSpace/getUnallocatedSpace/getUsableSpace) are limited to reporting 
bytes as
+     * signed long (2^63-1), if the filesystem is any bigger, then the size 
overflows. {@code SafeFileStore} will
+     * return {@code Long.MAX_VALUE} if the size overflow.</p>
+     *
+     * @see https://bugs.openjdk.java.net/browse/JDK-8162520.
+     */
+    private static final class SafeFileStore extends FileStore
+    {
+        /**
+         * The decorated {@code FileStore}
+         */
+        private final FileStore fileStore;
+
+        public SafeFileStore(FileStore fileStore)
+        {
+            this.fileStore = fileStore;
+        }
+
+        @Override
+        public String name()
+        {
+            return fileStore.name();
+        }
+
+        @Override
+        public String type()
+        {
+            return fileStore.type();
+        }
+
+        @Override
+        public boolean isReadOnly()
+        {
+            return fileStore.isReadOnly();
+        }
+
+        @Override
+        public long getTotalSpace() throws IOException
+        {
+            return handleLargeFileSystem(fileStore.getTotalSpace());
+        }
+
+        @Override
+        public long getUsableSpace() throws IOException
+        {
+            return handleLargeFileSystem(fileStore.getUsableSpace());
+        }
+
+        @Override
+        public long getUnallocatedSpace() throws IOException
+        {
+            return handleLargeFileSystem(fileStore.getUnallocatedSpace());
+        }
+
+        @Override
+        public boolean supportsFileAttributeView(Class<? extends 
FileAttributeView> type)
+        {
+            return fileStore.supportsFileAttributeView(type);
+        }
+
+        @Override
+        public boolean supportsFileAttributeView(String name)
+        {
+            return fileStore.supportsFileAttributeView(name);
+        }
+
+        @Override
+        public <V extends FileStoreAttributeView> V 
getFileStoreAttributeView(Class<V> type)
+        {
+            return fileStore.getFileStoreAttributeView(type);
+        }
+
+        @Override
+        public Object getAttribute(String attribute) throws IOException
+        {
+            return fileStore.getAttribute(attribute);
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to