001: /***** BEGIN LICENSE BLOCK *****
002: * Version: CPL 1.0/GPL 2.0/LGPL 2.1
003: *
004: * The contents of this file are subject to the Common Public
005: * License Version 1.0 (the "License"); you may not use this file
006: * except in compliance with the License. You may obtain a copy of
007: * the License at http://www.eclipse.org/legal/cpl-v10.html
008: *
009: * Software distributed under the License is distributed on an "AS
010: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
011: * implied. See the License for the specific language governing
012: * rights and limitations under the License.
013: *
014: * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
015: * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
016: * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
017: * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
018: * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
019: *
020: * Alternatively, the contents of this file may be used under the terms of
021: * either of the GNU General Public License Version 2 or later (the "GPL"),
022: * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
023: * in which case the provisions of the GPL or the LGPL are applicable instead
024: * of those above. If you wish to allow use of your version of this file only
025: * under the terms of either the GPL or the LGPL, and not to allow others to
026: * use your version of this file under the terms of the CPL, indicate your
027: * decision by deleting the provisions above and replace them with the notice
028: * and other provisions required by the GPL or the LGPL. If you do not delete
029: * the provisions above, a recipient may use your version of this file under
030: * the terms of any one of the CPL, the GPL or the LGPL.
031: ***** END LICENSE BLOCK *****/package org.jruby;
032:
033: import java.io.File;
034: import java.io.FileInputStream;
035: import java.io.IOException;
036: import java.util.ArrayList;
037: import java.util.Iterator;
038: import java.util.List;
039:
040: import org.jruby.javasupport.JavaUtil;
041: import org.jruby.runtime.Arity;
042: import org.jruby.runtime.Block;
043: import org.jruby.runtime.CallbackFactory;
044: import org.jruby.runtime.ObjectAllocator;
045: import org.jruby.runtime.ThreadContext;
046: import org.jruby.runtime.builtin.IRubyObject;
047: import org.jruby.util.Dir;
048: import org.jruby.util.JRubyFile;
049: import org.jruby.util.ByteList;
050:
051: /**
052: * .The Ruby built-in class Dir.
053: *
054: * @author jvoegele
055: */
056: public class RubyDir extends RubyObject {
057: // What we passed to the constructor for method 'path'
058: private RubyString path;
059: protected JRubyFile dir;
060: private String[] snapshot; // snapshot of contents of directory
061: private int pos; // current position in directory
062: private boolean isOpen = true;
063:
064: public RubyDir(Ruby runtime, RubyClass type) {
065: super (runtime, type);
066: }
067:
068: private static ObjectAllocator DIR_ALLOCATOR = new ObjectAllocator() {
069: public IRubyObject allocate(Ruby runtime, RubyClass klass) {
070: return new RubyDir(runtime, klass);
071: }
072: };
073:
074: public static RubyClass createDirClass(Ruby runtime) {
075: RubyClass dirClass = runtime.defineClass("Dir", runtime
076: .getObject(), DIR_ALLOCATOR);
077:
078: dirClass.includeModule(runtime.getModule("Enumerable"));
079:
080: CallbackFactory callbackFactory = runtime
081: .callbackFactory(RubyDir.class);
082:
083: dirClass.getMetaClass().defineMethod("glob",
084: callbackFactory.getOptSingletonMethod("glob"));
085: dirClass.getMetaClass().defineFastMethod(
086: "entries",
087: callbackFactory.getFastSingletonMethod("entries",
088: RubyKernel.IRUBY_OBJECT));
089: dirClass.getMetaClass().defineMethod("[]",
090: callbackFactory.getOptSingletonMethod("glob"));
091: dirClass.getMetaClass().defineMethod("chdir",
092: callbackFactory.getOptSingletonMethod("chdir"));
093: dirClass.getMetaClass().defineFastMethod(
094: "chroot",
095: callbackFactory.getFastSingletonMethod("chroot",
096: RubyKernel.IRUBY_OBJECT));
097: //dirClass.defineSingletonMethod("delete", callbackFactory.getSingletonMethod(RubyDir.class, "delete", RubyString.class));
098: dirClass.getMetaClass().defineMethod(
099: "foreach",
100: callbackFactory.getSingletonMethod("foreach",
101: RubyKernel.IRUBY_OBJECT));
102: dirClass.getMetaClass().defineFastMethod("getwd",
103: callbackFactory.getFastSingletonMethod("getwd"));
104: dirClass.getMetaClass().defineFastMethod("pwd",
105: callbackFactory.getFastSingletonMethod("getwd"));
106: // dirClass.defineAlias("pwd", "getwd");
107: dirClass.getMetaClass().defineFastMethod("mkdir",
108: callbackFactory.getFastOptSingletonMethod("mkdir"));
109: dirClass.getMetaClass().defineMethod(
110: "open",
111: callbackFactory.getSingletonMethod("open",
112: RubyKernel.IRUBY_OBJECT));
113: dirClass.getMetaClass().defineFastMethod(
114: "rmdir",
115: callbackFactory.getFastSingletonMethod("rmdir",
116: RubyKernel.IRUBY_OBJECT));
117: dirClass.getMetaClass().defineFastMethod(
118: "unlink",
119: callbackFactory.getFastSingletonMethod("rmdir",
120: RubyKernel.IRUBY_OBJECT));
121: dirClass.getMetaClass().defineFastMethod(
122: "delete",
123: callbackFactory.getFastSingletonMethod("rmdir",
124: RubyKernel.IRUBY_OBJECT));
125: // dirClass.defineAlias("unlink", "rmdir");
126: // dirClass.defineAlias("delete", "rmdir");
127:
128: dirClass.defineFastMethod("close", callbackFactory
129: .getFastMethod("close"));
130: dirClass
131: .defineMethod("each", callbackFactory.getMethod("each"));
132: dirClass.defineFastMethod("entries", callbackFactory
133: .getFastMethod("entries"));
134: dirClass.defineFastMethod("path", callbackFactory
135: .getFastMethod("path"));
136: dirClass.defineFastMethod("tell", callbackFactory
137: .getFastMethod("tell"));
138: dirClass.defineAlias("pos", "tell");
139: dirClass.defineFastMethod("seek", callbackFactory
140: .getFastMethod("seek", RubyKernel.IRUBY_OBJECT));
141: dirClass.defineFastMethod("pos=", callbackFactory
142: .getFastMethod("setPos", RubyKernel.IRUBY_OBJECT));
143: dirClass.defineFastMethod("read", callbackFactory
144: .getFastMethod("read"));
145: dirClass.defineFastMethod("rewind", callbackFactory
146: .getFastMethod("rewind"));
147: dirClass.defineMethod("initialize", callbackFactory.getMethod(
148: "initialize", RubyKernel.IRUBY_OBJECT));
149:
150: return dirClass;
151: }
152:
153: private final void checkDir() {
154: if (!isTaint() && getRuntime().getSafeLevel() >= 4)
155: throw getRuntime().newSecurityError(
156: "Insecure: operation on untainted Dir");
157:
158: testFrozen("");
159:
160: if (!isOpen)
161: throw getRuntime().newIOError("closed directory");
162: }
163:
164: /**
165: * Creates a new <code>Dir</code>. This method takes a snapshot of the
166: * contents of the directory at creation time, so changes to the contents
167: * of the directory will not be reflected during the lifetime of the
168: * <code>Dir</code> object returned, so a new <code>Dir</code> instance
169: * must be created to reflect changes to the underlying file system.
170: */
171: public IRubyObject initialize(IRubyObject _newPath,
172: Block unusedBlock) {
173: RubyString newPath = _newPath.convertToString();
174: getRuntime().checkSafeString(newPath);
175: dir = JRubyFile.create(getRuntime().getCurrentDirectory(),
176: newPath.toString());
177: if (!dir.isDirectory()) {
178: dir = null;
179: throw getRuntime().newErrnoENOENTError(
180: newPath.toString() + " is not a directory");
181: }
182: path = newPath;
183: List snapshotList = new ArrayList();
184: snapshotList.add(".");
185: snapshotList.add("..");
186: snapshotList.addAll(getContents(dir));
187: snapshot = (String[]) snapshotList
188: .toArray(new String[snapshotList.size()]);
189: pos = 0;
190:
191: return this ;
192: }
193:
194: // ----- Ruby Class Methods ----------------------------------------------------
195:
196: /**
197: * Returns an array of filenames matching the specified wildcard pattern
198: * <code>pat</code>. If a block is given, the array is iterated internally
199: * with each filename is passed to the block in turn. In this case, Nil is
200: * returned.
201: */
202: public static IRubyObject glob(IRubyObject recv,
203: IRubyObject[] args, Block block) {
204: String cwd = recv.getRuntime().getCurrentDirectory();
205: int flags = 0;
206: if (Arity.checkArgumentCount(recv.getRuntime(), args, 1, 2) == 2) {
207: flags = RubyNumeric.num2int(args[1]);
208: }
209: ByteList pt = args[0].convertToString().getByteList();
210:
211: String cwd2;
212: try {
213: cwd2 = new org.jruby.util.NormalizedFile(cwd)
214: .getCanonicalPath();
215: } catch (Exception e) {
216: cwd2 = cwd;
217: }
218:
219: List l = Dir.push_glob(cwd2, pt.bytes, pt.begin, pt.realSize,
220: flags);
221:
222: if (block.isGiven()) {
223: ThreadContext context = recv.getRuntime()
224: .getCurrentContext();
225: for (Iterator iter = l.iterator(); iter.hasNext();) {
226: block.yield(context, RubyString.newString(recv
227: .getRuntime(), (ByteList) iter.next()));
228: }
229: return recv.getRuntime().getNil();
230: }
231: IRubyObject[] l2 = new IRubyObject[l.size()];
232: int i = 0;
233: for (Iterator iter = l.iterator(); iter.hasNext(); i++) {
234: l2[i] = RubyString.newString(recv.getRuntime(),
235: (ByteList) iter.next());
236: }
237: return recv.getRuntime().newArrayNoCopy(l2);
238: }
239:
240: /**
241: * @return all entries for this Dir
242: */
243: public RubyArray entries() {
244: return getRuntime()
245: .newArrayNoCopy(
246: JavaUtil.convertJavaArrayToRuby(getRuntime(),
247: snapshot));
248: }
249:
250: /**
251: * Returns an array containing all of the filenames in the given directory.
252: */
253: public static RubyArray entries(IRubyObject recv, IRubyObject path) {
254: final JRubyFile directory = JRubyFile.create(recv.getRuntime()
255: .getCurrentDirectory(), path.convertToString()
256: .toString());
257:
258: if (!directory.isDirectory()) {
259: throw recv.getRuntime().newErrnoENOENTError(
260: "No such directory");
261: }
262: List fileList = getContents(directory);
263: fileList.add(0, ".");
264: fileList.add(1, "..");
265: Object[] files = fileList.toArray();
266: return recv.getRuntime().newArrayNoCopy(
267: JavaUtil.convertJavaArrayToRuby(recv.getRuntime(),
268: files));
269: }
270:
271: /** Changes the current directory to <code>path</code> */
272: public static IRubyObject chdir(IRubyObject recv,
273: IRubyObject[] args, Block block) {
274: Arity.checkArgumentCount(recv.getRuntime(), args, 0, 1);
275: RubyString path = args.length == 1 ? (RubyString) args[0]
276: .convertToString() : getHomeDirectoryPath(recv);
277: JRubyFile dir = getDir(recv.getRuntime(), path.toString(), true);
278: String realPath = null;
279: String oldCwd = recv.getRuntime().getCurrentDirectory();
280:
281: // We get canonical path to try and flatten the path out.
282: // a dir '/subdir/..' should return as '/'
283: // cnutter: Do we want to flatten path out?
284: try {
285: realPath = dir.getCanonicalPath();
286: } catch (IOException e) {
287: realPath = dir.getAbsolutePath();
288: }
289:
290: IRubyObject result = null;
291: if (block.isGiven()) {
292: // FIXME: Don't allow multiple threads to do this at once
293: recv.getRuntime().setCurrentDirectory(realPath);
294: try {
295: result = block.yield(recv.getRuntime()
296: .getCurrentContext(), path);
297: } finally {
298: recv.getRuntime().setCurrentDirectory(oldCwd);
299: }
300: } else {
301: recv.getRuntime().setCurrentDirectory(realPath);
302: result = recv.getRuntime().newFixnum(0);
303: }
304:
305: return result;
306: }
307:
308: /**
309: * Changes the root directory (only allowed by super user). Not available
310: * on all platforms.
311: */
312: public static IRubyObject chroot(IRubyObject recv, IRubyObject path) {
313: throw recv
314: .getRuntime()
315: .newNotImplementedError(
316: "chroot not implemented: chroot is non-portable and is not supported.");
317: }
318:
319: /**
320: * Deletes the directory specified by <code>path</code>. The directory must
321: * be empty.
322: */
323: public static IRubyObject rmdir(IRubyObject recv, IRubyObject path) {
324: JRubyFile directory = getDir(recv.getRuntime(), path
325: .convertToString().toString(), true);
326:
327: if (!directory.delete()) {
328: throw recv.getRuntime().newSystemCallError(
329: "No such directory");
330: }
331:
332: return recv.getRuntime().newFixnum(0);
333: }
334:
335: /**
336: * Executes the block once for each file in the directory specified by
337: * <code>path</code>.
338: */
339: public static IRubyObject foreach(IRubyObject recv,
340: IRubyObject _path, Block block) {
341: RubyString path = _path.convertToString();
342: recv.getRuntime().checkSafeString(path);
343:
344: RubyClass dirClass = recv.getRuntime().getClass("Dir");
345: RubyDir dir = (RubyDir) dirClass.newInstance(
346: new IRubyObject[] { path }, block);
347:
348: dir.each(block);
349: return recv.getRuntime().getNil();
350: }
351:
352: /** Returns the current directory. */
353: public static RubyString getwd(IRubyObject recv) {
354: return recv.getRuntime().newString(
355: recv.getRuntime().getCurrentDirectory());
356: }
357:
358: /**
359: * Creates the directory specified by <code>path</code>. Note that the
360: * <code>mode</code> parameter is provided only to support existing Ruby
361: * code, and is ignored.
362: */
363: public static IRubyObject mkdir(IRubyObject recv, IRubyObject[] args) {
364: if (args.length < 1) {
365: throw recv.getRuntime().newArgumentError(args.length, 1);
366: }
367: if (args.length > 2) {
368: throw recv.getRuntime().newArgumentError(args.length, 2);
369: }
370:
371: recv.getRuntime().checkSafeString(args[0]);
372: String path = args[0].toString();
373:
374: File newDir = getDir(recv.getRuntime(), path, false);
375: if (File.separatorChar == '\\') {
376: newDir = new File(newDir.getPath());
377: }
378:
379: return newDir.mkdirs() ? RubyFixnum.zero(recv.getRuntime())
380: : RubyFixnum.one(recv.getRuntime());
381: }
382:
383: /**
384: * Returns a new directory object for <code>path</code>. If a block is
385: * provided, a new directory object is passed to the block, which closes the
386: * directory object before terminating.
387: */
388: public static IRubyObject open(IRubyObject recv, IRubyObject path,
389: Block block) {
390: RubyDir directory = (RubyDir) recv.getRuntime().getClass("Dir")
391: .newInstance(new IRubyObject[] { path },
392: Block.NULL_BLOCK);
393:
394: if (!block.isGiven())
395: return directory;
396:
397: try {
398: block.yield(recv.getRuntime().getCurrentContext(),
399: directory);
400: } finally {
401: directory.close();
402: }
403:
404: return recv.getRuntime().getNil();
405: }
406:
407: // ----- Ruby Instance Methods -------------------------------------------------
408:
409: /**
410: * Closes the directory stream.
411: */
412: public IRubyObject close() {
413: // Make sure any read()s after close fail.
414: checkDir();
415:
416: isOpen = false;
417:
418: return getRuntime().getNil();
419: }
420:
421: /**
422: * Executes the block once for each entry in the directory.
423: */
424: public IRubyObject each(Block block) {
425: checkDir();
426:
427: String[] contents = snapshot;
428: ThreadContext context = getRuntime().getCurrentContext();
429: for (int i = 0; i < contents.length; i++) {
430: block.yield(context, getRuntime().newString(contents[i]));
431: }
432: return this ;
433: }
434:
435: /**
436: * Returns the current position in the directory.
437: */
438: public RubyInteger tell() {
439: checkDir();
440: return getRuntime().newFixnum(pos);
441: }
442:
443: /**
444: * Moves to a position <code>d</code>. <code>pos</code> must be a value
445: * returned by <code>tell</code> or 0.
446: */
447: public IRubyObject seek(IRubyObject newPos) {
448: checkDir();
449:
450: setPos(newPos);
451: return this ;
452: }
453:
454: public IRubyObject setPos(IRubyObject newPos) {
455: this .pos = RubyNumeric.fix2int(newPos);
456: return newPos;
457: }
458:
459: public IRubyObject path() {
460: checkDir();
461:
462: return path.strDup();
463: }
464:
465: /** Returns the next entry from this directory. */
466: public IRubyObject read() {
467: checkDir();
468:
469: if (pos >= snapshot.length) {
470: return getRuntime().getNil();
471: }
472: RubyString result = getRuntime().newString(snapshot[pos]);
473: pos++;
474: return result;
475: }
476:
477: /** Moves position in this directory to the first entry. */
478: public IRubyObject rewind() {
479: if (!isTaint() && getRuntime().getSafeLevel() >= 4)
480: throw getRuntime()
481: .newSecurityError("Insecure: can't close");
482: checkDir();
483:
484: pos = 0;
485: return this ;
486: }
487:
488: // ----- Helper Methods --------------------------------------------------------
489:
490: /** Returns a Java <code>File</code> object for the specified path. If
491: * <code>path</code> is not a directory, throws <code>IOError</code>.
492: *
493: * @param path path for which to return the <code>File</code> object.
494: * @param mustExist is true the directory must exist. If false it must not.
495: * @throws IOError if <code>path</code> is not a directory.
496: */
497: protected static JRubyFile getDir(final Ruby runtime,
498: final String path, final boolean mustExist) {
499: JRubyFile result = JRubyFile.create(runtime
500: .getCurrentDirectory(), path);
501: boolean isDirectory = result.isDirectory();
502:
503: if (mustExist && !isDirectory) {
504: throw runtime.newErrnoENOENTError(path
505: + " is not a directory");
506: } else if (!mustExist && isDirectory) {
507: throw runtime.newErrnoEEXISTError("File exists - " + path);
508: }
509:
510: return result;
511: }
512:
513: /**
514: * Returns the contents of the specified <code>directory</code> as an
515: * <code>ArrayList</code> containing the names of the files as Java Strings.
516: */
517: protected static List getContents(File directory) {
518: String[] contents = directory.list();
519: List result = new ArrayList();
520:
521: // If an IO exception occurs (something odd, but possible)
522: // A directory may return null.
523: if (contents != null) {
524: for (int i = 0; i < contents.length; i++) {
525: result.add(contents[i]);
526: }
527: }
528: return result;
529: }
530:
531: /**
532: * Returns the contents of the specified <code>directory</code> as an
533: * <code>ArrayList</code> containing the names of the files as Ruby Strings.
534: */
535: protected static List getContents(File directory, Ruby runtime) {
536: List result = new ArrayList();
537: String[] contents = directory.list();
538:
539: for (int i = 0; i < contents.length; i++) {
540: result.add(runtime.newString(contents[i]));
541: }
542: return result;
543: }
544:
545: /**
546: * Returns the home directory of the specified <code>user</code> on the
547: * system. If the home directory of the specified user cannot be found,
548: * an <code>ArgumentError it thrown</code>.
549: */
550: public static IRubyObject getHomeDirectoryPath(IRubyObject recv,
551: String user) {
552: /*
553: * TODO: This version is better than the hackish previous one. Windows
554: * behavior needs to be defined though. I suppose this version
555: * could be improved more too.
556: * TODO: /etc/passwd is also inadequate for MacOSX since it does not
557: * use /etc/passwd for regular user accounts
558: */
559:
560: String passwd = null;
561: try {
562: FileInputStream stream = new FileInputStream("/etc/passwd");
563: int totalBytes = stream.available();
564: byte[] bytes = new byte[totalBytes];
565: stream.read(bytes);
566: passwd = new String(bytes);
567: } catch (IOException e) {
568: return recv.getRuntime().getNil();
569: }
570:
571: String[] rows = passwd.split("\n");
572: int rowCount = rows.length;
573: for (int i = 0; i < rowCount; i++) {
574: String[] fields = rows[i].split(":");
575: if (fields[0].equals(user)) {
576: return recv.getRuntime().newString(fields[5]);
577: }
578: }
579:
580: throw recv.getRuntime().newArgumentError(
581: "user " + user + " doesn't exist");
582: }
583:
584: public static RubyString getHomeDirectoryPath(IRubyObject recv) {
585: RubyHash systemHash = (RubyHash) recv.getRuntime().getObject()
586: .getConstant("ENV_JAVA");
587: RubyHash envHash = (RubyHash) recv.getRuntime().getObject()
588: .getConstant("ENV");
589: IRubyObject home = envHash.aref(recv.getRuntime().newString(
590: "HOME"));
591:
592: if (home == null || home.isNil()) {
593: home = systemHash.aref(recv.getRuntime().newString(
594: "user.home"));
595: }
596:
597: if (home == null || home.isNil()) {
598: home = envHash.aref(recv.getRuntime().newString("LOGDIR"));
599: }
600:
601: if (home == null || home.isNil()) {
602: throw recv.getRuntime().newArgumentError(
603: "user.home/LOGDIR not set");
604: }
605:
606: return (RubyString) home;
607: }
608: }
|