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.command.dml; |
7 | |
8 | import java.io.IOException; |
9 | import java.io.InputStream; |
10 | import java.io.OutputStream; |
11 | import java.util.ArrayList; |
12 | import java.util.zip.ZipEntry; |
13 | import java.util.zip.ZipOutputStream; |
14 | import org.h2.api.DatabaseEventListener; |
15 | import org.h2.api.ErrorCode; |
16 | import org.h2.command.CommandInterface; |
17 | import org.h2.command.Prepared; |
18 | import org.h2.engine.Constants; |
19 | import org.h2.engine.Database; |
20 | import org.h2.engine.Session; |
21 | import org.h2.expression.Expression; |
22 | import org.h2.message.DbException; |
23 | import org.h2.mvstore.MVStore; |
24 | import org.h2.mvstore.db.MVTableEngine.Store; |
25 | import org.h2.result.ResultInterface; |
26 | import org.h2.store.FileLister; |
27 | import org.h2.store.PageStore; |
28 | import org.h2.store.fs.FileUtils; |
29 | import org.h2.util.IOUtils; |
30 | |
31 | /** |
32 | * This class represents the statement |
33 | * BACKUP |
34 | */ |
35 | public class BackupCommand extends Prepared { |
36 | |
37 | private Expression fileNameExpr; |
38 | |
39 | public BackupCommand(Session session) { |
40 | super(session); |
41 | } |
42 | |
43 | public void setFileName(Expression fileName) { |
44 | this.fileNameExpr = fileName; |
45 | } |
46 | |
47 | @Override |
48 | public int update() { |
49 | String name = fileNameExpr.getValue(session).getString(); |
50 | session.getUser().checkAdmin(); |
51 | backupTo(name); |
52 | return 0; |
53 | } |
54 | |
55 | private void backupTo(String fileName) { |
56 | Database db = session.getDatabase(); |
57 | if (!db.isPersistent()) { |
58 | throw DbException.get(ErrorCode.DATABASE_IS_NOT_PERSISTENT); |
59 | } |
60 | try { |
61 | Store mvStore = db.getMvStore(); |
62 | if (mvStore != null) { |
63 | mvStore.flush(); |
64 | } |
65 | String name = db.getName(); |
66 | name = FileUtils.getName(name); |
67 | OutputStream zip = FileUtils.newOutputStream(fileName, false); |
68 | ZipOutputStream out = new ZipOutputStream(zip); |
69 | db.flush(); |
70 | if (db.getPageStore() != null) { |
71 | String fn = db.getName() + Constants.SUFFIX_PAGE_FILE; |
72 | backupPageStore(out, fn, db.getPageStore()); |
73 | } |
74 | // synchronize on the database, to avoid concurrent temp file |
75 | // creation / deletion / backup |
76 | String base = FileUtils.getParent(db.getName()); |
77 | synchronized (db.getLobSyncObject()) { |
78 | String prefix = db.getDatabasePath(); |
79 | String dir = FileUtils.getParent(prefix); |
80 | dir = FileLister.getDir(dir); |
81 | ArrayList<String> fileList = FileLister.getDatabaseFiles(dir, name, true); |
82 | for (String n : fileList) { |
83 | if (n.endsWith(Constants.SUFFIX_LOB_FILE)) { |
84 | backupFile(out, base, n); |
85 | } |
86 | if (n.endsWith(Constants.SUFFIX_MV_FILE) && mvStore != null) { |
87 | MVStore s = mvStore.getStore(); |
88 | boolean before = s.getReuseSpace(); |
89 | s.setReuseSpace(false); |
90 | try { |
91 | InputStream in = mvStore.getInputStream(); |
92 | backupFile(out, base, n, in); |
93 | } finally { |
94 | s.setReuseSpace(before); |
95 | } |
96 | } |
97 | } |
98 | } |
99 | out.close(); |
100 | zip.close(); |
101 | } catch (IOException e) { |
102 | throw DbException.convertIOException(e, fileName); |
103 | } |
104 | } |
105 | |
106 | private void backupPageStore(ZipOutputStream out, String fileName, |
107 | PageStore store) throws IOException { |
108 | Database db = session.getDatabase(); |
109 | fileName = FileUtils.getName(fileName); |
110 | out.putNextEntry(new ZipEntry(fileName)); |
111 | int pos = 0; |
112 | try { |
113 | store.setBackup(true); |
114 | while (true) { |
115 | pos = store.copyDirect(pos, out); |
116 | if (pos < 0) { |
117 | break; |
118 | } |
119 | int max = store.getPageCount(); |
120 | db.setProgress(DatabaseEventListener.STATE_BACKUP_FILE, fileName, pos, max); |
121 | } |
122 | } finally { |
123 | store.setBackup(false); |
124 | } |
125 | out.closeEntry(); |
126 | } |
127 | |
128 | private static void backupFile(ZipOutputStream out, String base, String fn) |
129 | throws IOException { |
130 | InputStream in = FileUtils.newInputStream(fn); |
131 | backupFile(out, base, fn, in); |
132 | } |
133 | |
134 | private static void backupFile(ZipOutputStream out, String base, String fn, |
135 | InputStream in) throws IOException { |
136 | String f = FileUtils.toRealPath(fn); |
137 | base = FileUtils.toRealPath(base); |
138 | if (!f.startsWith(base)) { |
139 | DbException.throwInternalError(f + " does not start with " + base); |
140 | } |
141 | f = f.substring(base.length()); |
142 | f = correctFileName(f); |
143 | out.putNextEntry(new ZipEntry(f)); |
144 | IOUtils.copyAndCloseInput(in, out); |
145 | out.closeEntry(); |
146 | } |
147 | |
148 | @Override |
149 | public boolean isTransactional() { |
150 | return true; |
151 | } |
152 | |
153 | /** |
154 | * Fix the file name, replacing backslash with slash. |
155 | * |
156 | * @param f the file name |
157 | * @return the corrected file name |
158 | */ |
159 | public static String correctFileName(String f) { |
160 | f = f.replace('\\', '/'); |
161 | if (f.startsWith("/")) { |
162 | f = f.substring(1); |
163 | } |
164 | return f; |
165 | } |
166 | |
167 | @Override |
168 | public boolean needRecompile() { |
169 | return false; |
170 | } |
171 | |
172 | @Override |
173 | public ResultInterface queryMeta() { |
174 | return null; |
175 | } |
176 | |
177 | @Override |
178 | public int getType() { |
179 | return CommandInterface.BACKUP; |
180 | } |
181 | |
182 | } |