1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.tools; |
7 | |
8 | import java.io.FileNotFoundException; |
9 | import java.io.IOException; |
10 | import java.io.InputStream; |
11 | import java.io.OutputStream; |
12 | import java.sql.SQLException; |
13 | import java.util.List; |
14 | import java.util.zip.ZipEntry; |
15 | import java.util.zip.ZipOutputStream; |
16 | import org.h2.command.dml.BackupCommand; |
17 | import org.h2.engine.Constants; |
18 | import org.h2.message.DbException; |
19 | import org.h2.store.FileLister; |
20 | import org.h2.store.fs.FileUtils; |
21 | import org.h2.util.IOUtils; |
22 | import org.h2.util.Tool; |
23 | |
24 | /** |
25 | * Creates a backup of a database. |
26 | * <br /> |
27 | * This tool copies all database files. The database must be closed before using |
28 | * this tool. To create a backup while the database is in use, run the BACKUP |
29 | * SQL statement. In an emergency, for example if the application is not |
30 | * responding, creating a backup using the Backup tool is possible by using the |
31 | * quiet mode. However, if the database is changed while the backup is running |
32 | * in quiet mode, the backup could be corrupt. |
33 | * |
34 | * @h2.resource |
35 | */ |
36 | public class Backup extends Tool { |
37 | |
38 | /** |
39 | * Options are case sensitive. Supported options are: |
40 | * <table> |
41 | * <tr><td>[-help] or [-?]</td> |
42 | * <td>Print the list of options</td></tr> |
43 | * <tr><td>[-file <filename>]</td> |
44 | * <td>The target file name (default: backup.zip)</td></tr> |
45 | * <tr><td>[-dir <dir>]</td> |
46 | * <td>The source directory (default: .)</td></tr> |
47 | * <tr><td>[-db <database>]</td> |
48 | * <td>Source database; not required if there is only one</td></tr> |
49 | * <tr><td>[-quiet]</td> |
50 | * <td>Do not print progress information</td></tr> |
51 | * </table> |
52 | * @h2.resource |
53 | * |
54 | * @param args the command line arguments |
55 | */ |
56 | public static void main(String... args) throws SQLException { |
57 | new Backup().runTool(args); |
58 | } |
59 | |
60 | @Override |
61 | public void runTool(String... args) throws SQLException { |
62 | String zipFileName = "backup.zip"; |
63 | String dir = "."; |
64 | String db = null; |
65 | boolean quiet = false; |
66 | for (int i = 0; args != null && i < args.length; i++) { |
67 | String arg = args[i]; |
68 | if (arg.equals("-dir")) { |
69 | dir = args[++i]; |
70 | } else if (arg.equals("-db")) { |
71 | db = args[++i]; |
72 | } else if (arg.equals("-quiet")) { |
73 | quiet = true; |
74 | } else if (arg.equals("-file")) { |
75 | zipFileName = args[++i]; |
76 | } else if (arg.equals("-help") || arg.equals("-?")) { |
77 | showUsage(); |
78 | return; |
79 | } else { |
80 | showUsageAndThrowUnsupportedOption(arg); |
81 | } |
82 | } |
83 | try { |
84 | process(zipFileName, dir, db, quiet); |
85 | } catch (Exception e) { |
86 | throw DbException.toSQLException(e); |
87 | } |
88 | } |
89 | |
90 | /** |
91 | * Backs up database files. |
92 | * |
93 | * @param zipFileName the name of the target backup file (including path) |
94 | * @param directory the source directory name |
95 | * @param db the source database name (null if there is only one database, |
96 | * and and empty string to backup all files in this directory) |
97 | * @param quiet don't print progress information |
98 | */ |
99 | public static void execute(String zipFileName, String directory, String db, |
100 | boolean quiet) throws SQLException { |
101 | try { |
102 | new Backup().process(zipFileName, directory, db, quiet); |
103 | } catch (Exception e) { |
104 | throw DbException.toSQLException(e); |
105 | } |
106 | } |
107 | |
108 | private void process(String zipFileName, String directory, String db, |
109 | boolean quiet) throws SQLException { |
110 | List<String> list; |
111 | boolean allFiles = db != null && db.length() == 0; |
112 | if (allFiles) { |
113 | list = FileUtils.newDirectoryStream(directory); |
114 | } else { |
115 | list = FileLister.getDatabaseFiles(directory, db, true); |
116 | } |
117 | if (list.size() == 0) { |
118 | if (!quiet) { |
119 | printNoDatabaseFilesFound(directory, db); |
120 | } |
121 | return; |
122 | } |
123 | if (!quiet) { |
124 | FileLister.tryUnlockDatabase(list, "backup"); |
125 | } |
126 | zipFileName = FileUtils.toRealPath(zipFileName); |
127 | FileUtils.delete(zipFileName); |
128 | OutputStream fileOut = null; |
129 | try { |
130 | fileOut = FileUtils.newOutputStream(zipFileName, false); |
131 | ZipOutputStream zipOut = new ZipOutputStream(fileOut); |
132 | String base = ""; |
133 | for (String fileName : list) { |
134 | if (allFiles || |
135 | fileName.endsWith(Constants.SUFFIX_PAGE_FILE) || |
136 | fileName.endsWith(Constants.SUFFIX_MV_FILE)) { |
137 | base = FileUtils.getParent(fileName); |
138 | break; |
139 | } |
140 | } |
141 | for (String fileName : list) { |
142 | String f = FileUtils.toRealPath(fileName); |
143 | if (!f.startsWith(base)) { |
144 | DbException.throwInternalError(f + " does not start with " + base); |
145 | } |
146 | if (f.endsWith(zipFileName)) { |
147 | continue; |
148 | } |
149 | if (FileUtils.isDirectory(fileName)) { |
150 | continue; |
151 | } |
152 | f = f.substring(base.length()); |
153 | f = BackupCommand.correctFileName(f); |
154 | ZipEntry entry = new ZipEntry(f); |
155 | zipOut.putNextEntry(entry); |
156 | InputStream in = null; |
157 | try { |
158 | in = FileUtils.newInputStream(fileName); |
159 | IOUtils.copyAndCloseInput(in, zipOut); |
160 | } catch (FileNotFoundException e) { |
161 | // the file could have been deleted in the meantime |
162 | // ignore this (in this case an empty file is created) |
163 | } finally { |
164 | IOUtils.closeSilently(in); |
165 | } |
166 | zipOut.closeEntry(); |
167 | if (!quiet) { |
168 | out.println("Processed: " + fileName); |
169 | } |
170 | } |
171 | zipOut.close(); |
172 | } catch (IOException e) { |
173 | throw DbException.convertIOException(e, zipFileName); |
174 | } finally { |
175 | IOUtils.closeSilently(fileOut); |
176 | } |
177 | } |
178 | |
179 | } |