001: /*
002: * This file is part of DrFTPD, Distributed FTP Daemon.
003: *
004: * DrFTPD is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * DrFTPD is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with DrFTPD; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018: package org.drftpd.slave;
019:
020: import org.apache.log4j.Logger;
021: import org.drftpd.remotefile.AbstractLightRemoteFile;
022: import org.drftpd.remotefile.LightRemoteFileInterface;
023:
024: import java.io.File;
025: import java.io.FileInputStream;
026: import java.io.IOException;
027:
028: import java.util.ArrayList;
029: import java.util.Collection;
030: import java.util.Hashtable;
031: import java.util.Iterator;
032: import java.util.List;
033: import java.util.zip.CRC32;
034: import java.util.zip.CheckedInputStream;
035:
036: /**
037: * A wrapper for java.io.File to the net.sf.drftpd.RemoteFile structure.
038: *
039: * @author mog
040: * @version $Id: FileRemoteFile.java 1562 2007-01-05 12:37:07Z zubov $
041: */
042: public class FileRemoteFile extends AbstractLightRemoteFile {
043: private static final Logger logger = Logger
044: .getLogger(FileRemoteFile.class);
045: Hashtable _filefiles;
046: String _path;
047: RootCollection _roots;
048: private boolean isDirectory;
049: private boolean isFile;
050: private long lastModified;
051: private long length;
052:
053: public FileRemoteFile(RootCollection rootBasket) throws IOException {
054: this (rootBasket, "");
055: }
056:
057: public FileRemoteFile(RootCollection roots, String path)
058: throws IOException {
059: _path = path;
060: _roots = roots;
061:
062: List files = roots.getMultipleFiles(path);
063: File firstFile;
064: // sanity checking
065: {
066: {
067: Iterator iter = files.iterator();
068: firstFile = (File) iter.next();
069:
070: isFile = firstFile.isFile();
071: isDirectory = firstFile.isDirectory();
072:
073: if ((isFile && isDirectory)
074: || (!isFile && !isDirectory)) {
075: throw new IOException("isFile && isDirectory: "
076: + path);
077: }
078:
079: checkSymlink(firstFile);
080:
081: while (iter.hasNext()) {
082: File file = (File) iter.next();
083: checkSymlink(file);
084:
085: if ((isFile != file.isFile())
086: || (isDirectory != file.isDirectory())) {
087: throw new IOException(
088: "roots are out of sync, file&dir mix: "
089: + path);
090: }
091: }
092: }
093:
094: if (isFile && (files.size() > 1)) {
095: ArrayList checksummers = new ArrayList(files.size());
096:
097: for (Iterator iter = files.iterator(); iter.hasNext();) {
098: File file = (File) iter.next();
099: Checksummer checksummer = new Checksummer(file);
100: checksummer.start();
101: checksummers.add(checksummer);
102: }
103:
104: while (true) {
105: boolean waiting = false;
106:
107: for (Iterator iter = checksummers.iterator(); iter
108: .hasNext();) {
109: Checksummer cs = (Checksummer) iter.next();
110:
111: if (cs.isAlive()) {
112: waiting = true;
113:
114: try {
115: synchronized (cs) {
116: cs.wait(10000); // wait a max of 10 seconds
117: // race condition could
118: // occur between above
119: // isAlive() statement and
120: // when the Checksummer
121: // finishes
122: }
123: } catch (InterruptedException e) {
124: }
125:
126: break;
127: }
128: }
129:
130: if (!waiting) {
131: break;
132: }
133: }
134:
135: Iterator iter = checksummers.iterator();
136: long checksum = ((Checksummer) iter.next())
137: .getCheckSum().getValue();
138:
139: for (; iter.hasNext();) {
140: Checksummer cs = (Checksummer) iter.next();
141:
142: if (cs.getCheckSum().getValue() != checksum) {
143: throw new IOException(
144: "File collisions with different checksums - "
145: + files);
146: }
147: }
148:
149: iter = files.iterator();
150: iter.next();
151:
152: for (; iter.hasNext();) {
153: File file = (File) iter.next();
154: file.delete();
155: logger
156: .info("Deleted colliding and identical file: "
157: + file.getPath());
158: iter.remove();
159: }
160: }
161: } // end sanity checking
162:
163: if (isDirectory) {
164: length = 0;
165: } else {
166: length = firstFile.length();
167: }
168:
169: lastModified = firstFile.lastModified();
170: }
171:
172: public static void checkSymlink(File file) throws IOException {
173: if (!file.getCanonicalPath().equalsIgnoreCase(
174: file.getAbsolutePath())) {
175: throw new InvalidDirectoryException(
176: "Not following symlink: " + file.getAbsolutePath());
177: }
178: }
179:
180: /**
181: * @return true if directory contained no files and is now deleted, false
182: * otherwise.
183: * @throws IOException
184: */
185: private static boolean isEmpty(File dir) throws IOException {
186: File[] listfiles = dir.listFiles();
187:
188: if (listfiles == null) {
189: throw new RuntimeException("Not a directory or IO error: "
190: + dir);
191: }
192:
193: for (int i = 0; i < listfiles.length; i++) {
194: File file = listfiles[i];
195:
196: if (file.isFile()) {
197: return false;
198: }
199: }
200:
201: for (int i = 0; i < listfiles.length; i++) {
202: File file = listfiles[i];
203:
204: // parent directory not empty
205: if (!isEmpty(file)) {
206: return false;
207: }
208: }
209:
210: if (!dir.delete()) {
211: throw new IOException("Permission denied deleting "
212: + dir.getPath());
213: }
214:
215: return true;
216: }
217:
218: private void buildFileFiles() throws IOException {
219: if (_filefiles != null) {
220: return;
221: }
222:
223: _filefiles = new Hashtable();
224:
225: if (!isDirectory()) {
226: throw new IllegalArgumentException(
227: "listFiles() called on !isDirectory()");
228: }
229:
230: for (Iterator iter = _roots.iterator(); iter.hasNext();) {
231: Root root = (Root) iter.next();
232: File file = new File(root.getPath() + "/" + _path);
233:
234: if (!file.exists()) {
235: continue;
236: }
237:
238: if (!file.isDirectory()) {
239: throw new RuntimeException(
240: file.getPath()
241: + " is not a directory, attempt to getFiles() on it");
242: }
243:
244: if (!file.canRead()) {
245: throw new RuntimeException("Cannot read: " + file);
246: }
247:
248: File[] tmpFiles = file.listFiles();
249:
250: //returns null if not a dir, blah!
251: if (tmpFiles == null) {
252: throw new NullPointerException("list() on " + file
253: + " returned null");
254: }
255:
256: for (int i = 0; i < tmpFiles.length; i++) {
257: // try {
258: if (tmpFiles[i].isDirectory() && isEmpty(tmpFiles[i])) {
259: continue;
260: }
261:
262: FileRemoteFile listfile = new FileRemoteFile(_roots,
263: _path + File.separatorChar
264: + tmpFiles[i].getName());
265: _filefiles.put(tmpFiles[i].getName(), listfile);
266:
267: // } catch (IOException e) {
268: // e.printStackTrace();
269: // }
270: }
271: }
272:
273: if (!getName().equals("") && _filefiles.isEmpty()) {
274: throw new RuntimeException("Empty (not-root) directory "
275: + getPath() + ", shouldn't happen");
276: }
277: }
278:
279: public Collection getFiles() {
280: try {
281: buildFileFiles();
282:
283: return _filefiles.values();
284: } catch (IOException e) {
285: logger.debug("RuntimeException here", new Throwable());
286: throw new RuntimeException(e);
287: }
288: }
289:
290: public String getGroupname() {
291: return "drftpd";
292: }
293:
294: public String getName() {
295: return _path
296: .substring(_path.lastIndexOf(File.separatorChar) + 1);
297: }
298:
299: public String getParent() {
300: throw new UnsupportedOperationException();
301:
302: //return file.getParent();
303: }
304:
305: public String getPath() {
306: return _path;
307:
308: //throw new UnsupportedOperationException();
309: //return file.getPath();
310: }
311:
312: public Collection getSlaves() {
313: return new ArrayList();
314: }
315:
316: public String getUsername() {
317: return "drftpd";
318: }
319:
320: public boolean isDeleted() {
321: return false;
322: }
323:
324: public boolean isDirectory() {
325: return isDirectory;
326: }
327:
328: public boolean isFile() {
329: return isFile;
330: }
331:
332: public long lastModified() {
333: return lastModified;
334: }
335:
336: public long length() {
337: return length;
338: }
339:
340: public static class InvalidDirectoryException extends IOException {
341: /**
342: * Constructor for InvalidDirectoryException.
343: */
344: public InvalidDirectoryException() {
345: super ();
346: }
347:
348: /**
349: * Constructor for InvalidDirectoryException.
350: * @param arg0
351: */
352: public InvalidDirectoryException(String arg0) {
353: super (arg0);
354: }
355: }
356: }
357:
358: class Checksummer extends Thread {
359: private static final Logger logger = Logger
360: .getLogger(Checksummer.class);
361: private File _f;
362: private CRC32 _checkSum;
363: private IOException _e;
364:
365: public Checksummer(File f) {
366: super ("Checksummer - " + f.getPath());
367: _f = f;
368: }
369:
370: /**
371: * @return
372: */
373: public CRC32 getCheckSum() {
374: return _checkSum;
375: }
376:
377: public void run() {
378: synchronized (this ) {
379: _checkSum = new CRC32();
380:
381: CheckedInputStream cis = null;
382: try {
383: cis = new CheckedInputStream(new FileInputStream(_f),
384: _checkSum);
385: byte[] b = new byte[1024];
386:
387: while (cis.read(b) > 0)
388: ;
389: } catch (IOException e) {
390: logger.warn("", e);
391: _e = e;
392: } finally {
393: if (cis != null) {
394: try {
395: cis.close();
396: } catch (IOException e) {
397: }
398: }
399: }
400:
401: notifyAll();
402: }
403: }
404: }
|