From 1ec5112f3487d7a2ca7aa9b1fe4825a8e3777bc3 Mon Sep 17 00:00:00 2001 From: Keuin Date: Fri, 11 Jun 2021 17:24:46 +0800 Subject: fix silent-fail IO exception in ZipUtil; Improve code quality in ZipUtil --- .../backup/feedback/PrimitiveBackupFeedback.java | 17 +- .../method/ConfiguredPrimitiveBackupMethod.java | 19 ++- .../java/com/keuin/kbackupfabric/util/ZipUtil.java | 171 ++++++++++----------- 3 files changed, 106 insertions(+), 101 deletions(-) (limited to 'src/main') diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java index 094438c..0d99cf5 100644 --- a/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java @@ -3,12 +3,23 @@ package com.keuin.kbackupfabric.operation.backup.feedback; import static com.keuin.kbackupfabric.backup.BackupFilesystemUtil.getFriendlyFileSizeString; public class PrimitiveBackupFeedback implements BackupFeedback { + private final boolean success; - private final long backupSizeBytes; + private final long backupSizeBytes; // if success==false, this is invalid + private final String message; // if success==true, this is invalid - public PrimitiveBackupFeedback(boolean success, long backupSizeBytes) { + private PrimitiveBackupFeedback(boolean success, long backupSizeBytes, String message) { this.success = success; this.backupSizeBytes = backupSizeBytes; + this.message = message; + } + + public static PrimitiveBackupFeedback createSuccessFeedback(long backupSizeBytes) { + return new PrimitiveBackupFeedback(true, backupSizeBytes, ""); + } + + public static PrimitiveBackupFeedback createFailFeedback(String message) { + return new PrimitiveBackupFeedback(false, -1, message); } @Override @@ -25,6 +36,6 @@ public class PrimitiveBackupFeedback implements BackupFeedback { if (success && backupSizeBytes >= 0) return String.format("Backup file size: %s.", getFriendlyFileSizeString(backupSizeBytes)); else - return ""; + return message; } } diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java index 0047c44..ded514c 100644 --- a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java @@ -12,6 +12,7 @@ import com.keuin.kbackupfabric.util.ZipUtil; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.logging.Logger; @@ -47,20 +48,26 @@ public class ConfiguredPrimitiveBackupMethod implements ConfiguredBackupMethod { PrintUtil.info(String.format("zip(srcPath=%s, destPath=%s)", levelPath, backupSavePath)); PrintUtil.info("Compressing level ..."); ZipUtil.makeBackupZip(levelPath, backupSavePath, backupFileName, backupMetadata); - feedback = new PrimitiveBackupFeedback(true, FilesystemUtil.getFileSizeBytes(backupSavePath, backupFileName)); + feedback = PrimitiveBackupFeedback.createSuccessFeedback( + FilesystemUtil.getFileSizeBytes(backupSavePath, backupFileName)); } catch (ZipUtilException exception) { - PrintUtil.info("Infinite recursive of directory tree detected, backup was aborted."); - feedback = new PrimitiveBackupFeedback(false, 0); + String msg = "Infinite recursive of directory tree detected, backup was aborted."; + PrintUtil.info(msg); + feedback = PrimitiveBackupFeedback.createFailFeedback(msg); } catch (IOException e) { - feedback = new PrimitiveBackupFeedback(false, 0); + feedback = PrimitiveBackupFeedback.createFailFeedback(e.getMessage()); } if (!feedback.isSuccess()) { // do clean-up if failed File backupFile = new File(backupSavePath, backupFileName); if (backupFile.exists()) { - if (!backupFile.delete()) { - LOGGER.warning("Failed to clean up: cannot delete file " + backupFile.getName()); + LOGGER.info(String.format("Deleting incomplete backup file \"%s\"...", backupFile.getPath())); + try { + Files.delete(backupFile.toPath()); + LOGGER.info("Failed to backup, all files are cleaned up."); + } catch (IOException e) { + LOGGER.warning("Cannot delete: " + e.getMessage()); } } } diff --git a/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java b/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java index d0d0a61..c23527f 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java +++ b/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java @@ -46,12 +46,13 @@ public final class ZipUtil { ZipEntry entry = new ZipEntry(subPath); zipOutputStream.putNextEntry(entry); // BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file)); - InputStream inputStream = new FileInputStream(file); - while ((count = inputStream.read(buffer, 0, zipBufferSize)) != -1) { - zipOutputStream.write(buffer, 0, count); + try (InputStream inputStream = new FileInputStream(file)) { + while ((count = inputStream.read(buffer, 0, zipBufferSize)) != -1) { + zipOutputStream.write(buffer, 0, count); + } + } finally { + zipOutputStream.closeEntry(); } - inputStream.close(); - zipOutputStream.closeEntry(); } else { // 如果是目录,则压缩整个目录 // 压缩目录中的文件或子目录 @@ -88,49 +89,45 @@ public final class ZipUtil { throw new IllegalArgumentException("zipFileName cannot be empty"); } - CheckedOutputStream checkedOutputStream; - ZipOutputStream zipOutputStream = null; - try { - File srcFile = new File(srcPath); + File srcFile = new File(srcPath); - //判断压缩文件保存的路径是否为源文件路径的子文件夹,如果是,则抛出异常(防止无限递归压缩的发生) - if (srcFile.isDirectory() && zipPath.contains(srcPath)) { - throw new ZipUtilException("Detected loop recursion in directory structure, please check symlink linking to parent directories."); - } + //判断压缩文件保存的路径是否为源文件路径的子文件夹,如果是,则抛出异常(防止无限递归压缩的发生) + if (srcFile.isDirectory() && zipPath.contains(srcPath)) { + throw new ZipUtilException("Detected loop recursion in directory structure, please check symlink linking to parent directories."); + } - //判断压缩文件保存的路径是否存在,如果不存在,则创建目录 - File zipDir = new File(zipPath); - if (!zipDir.exists() || !zipDir.isDirectory()) { - if (!zipDir.mkdirs()) { - throw new IOException(String.format("Failed to make directory tree %s", zipDir.toString())); - } + //判断压缩文件保存的路径是否存在,如果不存在,则创建目录 + File zipDir = new File(zipPath); + if (!zipDir.exists() || !zipDir.isDirectory()) { + if (!zipDir.mkdirs()) { + throw new IOException(String.format("Failed to make directory tree %s", zipDir.toString())); } + } - //创建压缩文件保存的文件对象 - String zipFilePath = zipPath + File.separator + zipFileName; - File zipFile = new File(zipFilePath); - if (zipFile.exists()) { - //检测文件是否允许删除,如果不允许删除,将会抛出SecurityException - SecurityManager securityManager = new SecurityManager(); - securityManager.checkDelete(zipFilePath); - //删除已存在的目标文件 - if (!zipFile.delete()) { - throw new IOException(String.format("Failed to delete existing file %s", zipFile.toString())); - } + //创建压缩文件保存的文件对象 + String zipFilePath = zipPath + File.separator + zipFileName; + File zipFile = new File(zipFilePath); + if (zipFile.exists()) { + //删除已存在的目标文件 + if (!zipFile.delete()) { + throw new IOException(String.format("Failed to delete existing file %s", zipFile.toString())); } + } - checkedOutputStream = new CheckedOutputStream(new FileOutputStream(zipFile), new CRC32()); - zipOutputStream = new ZipOutputStream(checkedOutputStream); + try (FileOutputStream fileOutputStream = new FileOutputStream(zipFile); + CheckedOutputStream checkedOutputStream = new CheckedOutputStream(fileOutputStream, new CRC32()); + ZipOutputStream zipOutputStream = new ZipOutputStream(checkedOutputStream)) { zipOutputStream.setLevel(zipLevel); // If with backup metadata, we serialize it and write it into file "kbackup_metadata" ZipEntry metadataEntry = new ZipEntry(BackupMetadata.metadataFileName); + zipOutputStream.putNextEntry(metadataEntry); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(backupMetadata); - oos.close(); - zipOutputStream.write(baos.toByteArray()); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(backupMetadata); + zipOutputStream.write(baos.toByteArray()); + } zipOutputStream.closeEntry(); //如果只是压缩一个文件,则需要截取该文件的父目录 @@ -145,14 +142,6 @@ public final class ZipUtil { //调用递归压缩方法进行目录或文件压缩 zip(srcRootDir, srcFile, zipOutputStream, Collections.singleton("session.lock"), new byte[zipBufferSize]); zipOutputStream.flush(); - } finally { - try { - if (zipOutputStream != null) { - zipOutputStream.close(); - } - } catch (Exception e) { - e.printStackTrace(); - } } } @@ -193,56 +182,54 @@ public final class ZipUtil { String entryFilePath, entryDirPath; File entryFile, entryDir; int index, count; - ZipFile zip = new ZipFile(zipFile); - Enumeration entries = zip.entries(); - // 循环对压缩包里的每一个文件进行解压 - while (entries.hasMoreElements()) { - entry = entries.nextElement(); - // 构建压缩包中一个文件解压后保存的文件全路径 - entryFilePath = unzipFilePath + File.separator + entry.getName(); - - // 构建解压后保存的文件夹路径 - index = entryFilePath.lastIndexOf(File.separator); - if (index != -1) { - entryDirPath = entryFilePath.substring(0, index); - } else { - entryDirPath = ""; - } - entryDir = new File(entryDirPath); - // 如果文件夹路径不存在,则创建文件夹 - if (!entryDir.exists() || !entryDir.isDirectory()) { - if (!entryDir.mkdirs()) - throw new IOException(String.format("Failed to make directory tree %s", entryDir.toString())); - } + try (ZipFile zip = new ZipFile(zipFile)) { + Enumeration entries = zip.entries(); + // 循环对压缩包里的每一个文件进行解压 + while (entries.hasMoreElements()) { + entry = entries.nextElement(); + // 构建压缩包中一个文件解压后保存的文件全路径 + entryFilePath = unzipFilePath + File.separator + entry.getName(); + + // 构建解压后保存的文件夹路径 + index = entryFilePath.lastIndexOf(File.separator); + if (index != -1) { + entryDirPath = entryFilePath.substring(0, index); + } else { + entryDirPath = ""; + } + entryDir = new File(entryDirPath); + // 如果文件夹路径不存在,则创建文件夹 + if (!entryDir.exists() || !entryDir.isDirectory()) { + if (!entryDir.mkdirs()) + throw new IOException(String.format("Failed to make directory tree %s", entryDir.toString())); + } - // 创建解压文件 - entryFile = new File(entryFilePath); - if (entryFile.exists()) { - // 检测文件是否允许删除,如果不允许删除,将会抛出SecurityException - SecurityManager securityManager = new SecurityManager(); - securityManager.checkDelete(entryFilePath); - // 删除已存在的目标文件 - if (!entryFile.delete()) - throw new IOException(String.format("Failed to delete existing file %s", entryFile.toString())); - } - if (entry.isDirectory()) { - // If the entry is a directory, we make its corresponding directory. - if (!entryFile.mkdir()) - throw new IOException(String.format("Failed to create directory %s", entryFile.toString())); - } else { - // Is a file, we write the data - // 写入文件 - OutputStream outputStream = new FileOutputStream(entryFile); - InputStream inputStream = zip.getInputStream(entry); - while ((count = inputStream.read(buffer, 0, unzipBufferSize)) != -1) { - outputStream.write(buffer, 0, count); + // 创建解压文件 + entryFile = new File(entryFilePath); + if (entryFile.exists()) { + // 检测文件是否允许删除,如果不允许删除,将会抛出SecurityException + SecurityManager securityManager = new SecurityManager(); + securityManager.checkDelete(entryFilePath); + // 删除已存在的目标文件 + if (!entryFile.delete()) + throw new IOException(String.format("Failed to delete existing file %s", entryFile.toString())); + } + if (entry.isDirectory()) { + // If the entry is a directory, we make its corresponding directory. + if (!entryFile.mkdir()) + throw new IOException(String.format("Failed to create directory %s", entryFile.toString())); + } else { + // Is a file, we write the data + // 写入文件 + try (OutputStream outputStream = new FileOutputStream(entryFile); + InputStream inputStream = zip.getInputStream(entry)) { + while ((count = inputStream.read(buffer, 0, unzipBufferSize)) != -1) { + outputStream.write(buffer, 0, count); + } + outputStream.flush(); + } } - outputStream.flush(); - outputStream.close(); } - - } - zip.close(); } } \ No newline at end of file -- cgit v1.2.3