PageRenderTime 33ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/org/jruby/RubyFile.java

https://bitbucket.org/nicksieger/jruby
Java | 1880 lines | 1364 code | 261 blank | 255 comment | 354 complexity | 7fdd9f30c60493e9347eb80ef2f20f89 MD5 | raw file
Possible License(s): GPL-3.0, JSON
  1. /*
  2. ***** BEGIN LICENSE BLOCK *****
  3. * Version: CPL 1.0/GPL 2.0/LGPL 2.1
  4. *
  5. * The contents of this file are subject to the Common Public
  6. * License Version 1.0 (the "License"); you may not use this file
  7. * except in compliance with the License. You may obtain a copy of
  8. * the License at http://www.eclipse.org/legal/cpl-v10.html
  9. *
  10. * Software distributed under the License is distributed on an "AS
  11. * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  12. * implied. See the License for the specific language governing
  13. * rights and limitations under the License.
  14. *
  15. * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
  16. * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
  17. * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
  18. * Copyright (C) 2003 Joey Gibson <joey@joeygibson.com>
  19. * Copyright (C) 2004-2007 Thomas E Enebo <enebo@acm.org>
  20. * Copyright (C) 2004-2007 Charles O Nutter <headius@headius.com>
  21. * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
  22. * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
  23. *
  24. * Alternatively, the contents of this file may be used under the terms of
  25. * either of the GNU General Public License Version 2 or later (the "GPL"),
  26. * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27. * in which case the provisions of the GPL or the LGPL are applicable instead
  28. * of those above. If you wish to allow use of your version of this file only
  29. * under the terms of either the GPL or the LGPL, and not to allow others to
  30. * use your version of this file under the terms of the CPL, indicate your
  31. * decision by deleting the provisions above and replace them with the notice
  32. * and other provisions required by the GPL or the LGPL. If you do not delete
  33. * the provisions above, a recipient may use your version of this file under
  34. * the terms of any one of the CPL, the GPL or the LGPL.
  35. ***** END LICENSE BLOCK *****/
  36. package org.jruby;
  37. import com.kenai.constantine.platform.OpenFlags;
  38. import org.jcodings.Encoding;
  39. import org.jruby.util.io.OpenFile;
  40. import org.jruby.util.io.ChannelDescriptor;
  41. import java.io.File;
  42. import java.io.FileNotFoundException;
  43. import java.io.IOException;
  44. import java.io.InputStream;
  45. import java.io.Reader;
  46. import java.net.URI;
  47. import java.net.URL;
  48. import java.nio.channels.Channels;
  49. import java.nio.channels.FileChannel;
  50. import java.nio.channels.FileLock;
  51. import java.util.regex.Matcher;
  52. import java.util.regex.Pattern;
  53. import java.util.zip.ZipEntry;
  54. import java.util.zip.ZipFile;
  55. import org.jruby.anno.JRubyClass;
  56. import org.jruby.anno.JRubyMethod;
  57. import org.jruby.anno.JRubyModule;
  58. import org.jruby.ext.posix.FileStat;
  59. import org.jruby.ext.posix.util.Platform;
  60. import org.jruby.runtime.Block;
  61. import org.jruby.runtime.ClassIndex;
  62. import org.jruby.runtime.ObjectAllocator;
  63. import org.jruby.runtime.ThreadContext;
  64. import static org.jruby.runtime.Visibility.*;
  65. import org.jruby.runtime.builtin.IRubyObject;
  66. import org.jruby.runtime.encoding.EncodingCapable;
  67. import org.jruby.util.ByteList;
  68. import org.jruby.util.io.DirectoryAsFileException;
  69. import org.jruby.util.io.PermissionDeniedException;
  70. import org.jruby.util.io.Stream;
  71. import org.jruby.util.io.ChannelStream;
  72. import org.jruby.util.io.ModeFlags;
  73. import org.jruby.util.JRubyFile;
  74. import org.jruby.util.TypeConverter;
  75. import org.jruby.util.io.BadDescriptorException;
  76. import org.jruby.util.io.FileExistsException;
  77. import org.jruby.util.io.InvalidValueException;
  78. import org.jruby.util.io.PipeException;
  79. import static org.jruby.CompatVersion.*;
  80. /**
  81. * Ruby File class equivalent in java.
  82. **/
  83. @JRubyClass(name="File", parent="IO", include="FileTest")
  84. public class RubyFile extends RubyIO implements EncodingCapable {
  85. private static final long serialVersionUID = 1L;
  86. public static final int LOCK_SH = 1;
  87. public static final int LOCK_EX = 2;
  88. public static final int LOCK_NB = 4;
  89. public static final int LOCK_UN = 8;
  90. private static final int FNM_NOESCAPE = 1;
  91. private static final int FNM_PATHNAME = 2;
  92. private static final int FNM_DOTMATCH = 4;
  93. private static final int FNM_CASEFOLD = 8;
  94. private static final int FNM_SYSCASE;
  95. private static int _cachedUmask = 0;
  96. private static final Object _umaskLock = new Object();
  97. static {
  98. if (Platform.IS_WINDOWS) {
  99. FNM_SYSCASE = FNM_CASEFOLD;
  100. } else {
  101. FNM_SYSCASE = 0;
  102. }
  103. }
  104. public Encoding getEncoding() {
  105. return null;
  106. }
  107. public void setEncoding(Encoding encoding) {
  108. // :)
  109. }
  110. private static boolean startsWithDriveLetterOnWindows(String path) {
  111. return (path != null)
  112. && Platform.IS_WINDOWS &&
  113. ((path.length()>1 && path.charAt(0) == '/') ?
  114. (path.length() > 2
  115. && isWindowsDriveLetter(path.charAt(1))
  116. && path.charAt(2) == ':') :
  117. (path.length() > 1
  118. && isWindowsDriveLetter(path.charAt(0))
  119. && path.charAt(1) == ':'));
  120. }
  121. // adjusts paths started with '/' or '\\', on windows.
  122. static String adjustRootPathOnWindows(Ruby runtime, String path, String dir) {
  123. if (path == null || !Platform.IS_WINDOWS) return path;
  124. // MRI behavior on Windows: it treats '/' as a root of
  125. // a current drive (but only if SINGLE slash is present!):
  126. // E.g., if current work directory is
  127. // 'D:/home/directory', then '/' means 'D:/'.
  128. //
  129. // Basically, '/path' is treated as a *RELATIVE* path,
  130. // relative to the current drive. '//path' is treated
  131. // as absolute one.
  132. if ((path.startsWith("/") && !(path.length() > 2 && path.charAt(2) == ':')) || path.startsWith("\\")) {
  133. if (path.length() > 1 && (path.charAt(1) == '/' || path.charAt(1) == '\\')) {
  134. return path;
  135. }
  136. // First try to use drive letter from supplied dir value,
  137. // then try current work dir.
  138. if (!startsWithDriveLetterOnWindows(dir)) {
  139. dir = runtime.getCurrentDirectory();
  140. }
  141. if (dir.length() >= 2) {
  142. path = dir.substring(0, 2) + path;
  143. }
  144. } else if (startsWithDriveLetterOnWindows(path) && path.length() == 2) {
  145. // compensate for missing slash after drive letter on windows
  146. path += "/";
  147. }
  148. return path;
  149. }
  150. protected String path;
  151. private FileLock currentLock;
  152. public RubyFile(Ruby runtime, RubyClass type) {
  153. super(runtime, type);
  154. }
  155. // XXX This constructor is a hack to implement the __END__ syntax.
  156. // Converting a reader back into an InputStream doesn't generally work.
  157. public RubyFile(Ruby runtime, String path, final Reader reader) {
  158. this(runtime, path, new InputStream() {
  159. public int read() throws IOException {
  160. return reader.read();
  161. }
  162. });
  163. }
  164. public RubyFile(Ruby runtime, String path, InputStream in) {
  165. super(runtime, runtime.getFile());
  166. this.path = path;
  167. try {
  168. this.openFile.setMainStream(ChannelStream.open(runtime, new ChannelDescriptor(Channels.newChannel(in))));
  169. } catch (InvalidValueException ex) {
  170. throw runtime.newErrnoEINVALError();
  171. }
  172. this.openFile.setMode(openFile.getMainStream().getModes().getOpenFileFlags());
  173. }
  174. private static ObjectAllocator FILE_ALLOCATOR = new ObjectAllocator() {
  175. public IRubyObject allocate(Ruby runtime, RubyClass klass) {
  176. RubyFile instance = new RubyFile(runtime, klass);
  177. instance.setMetaClass(klass);
  178. return instance;
  179. }
  180. };
  181. public String getPath() {
  182. return path;
  183. }
  184. @JRubyModule(name="File::Constants")
  185. public static class Constants {}
  186. public static RubyClass createFileClass(Ruby runtime) {
  187. RubyClass fileClass = runtime.defineClass("File", runtime.getIO(), FILE_ALLOCATOR);
  188. // Create Constants class
  189. RubyModule constants = fileClass.defineModuleUnder("Constants");
  190. runtime.setFile(fileClass);
  191. fileClass.index = ClassIndex.FILE;
  192. fileClass.setReifiedClass(RubyFile.class);
  193. RubyString separator = runtime.newString("/");
  194. ThreadContext context = runtime.getCurrentContext();
  195. fileClass.kindOf = new RubyModule.KindOf() {
  196. @Override
  197. public boolean isKindOf(IRubyObject obj, RubyModule type) {
  198. return obj instanceof RubyFile;
  199. }
  200. };
  201. separator.freeze(context);
  202. fileClass.defineConstant("SEPARATOR", separator);
  203. fileClass.defineConstant("Separator", separator);
  204. if (File.separatorChar == '\\') {
  205. RubyString altSeparator = runtime.newString("\\");
  206. altSeparator.freeze(context);
  207. fileClass.defineConstant("ALT_SEPARATOR", altSeparator);
  208. } else {
  209. fileClass.defineConstant("ALT_SEPARATOR", runtime.getNil());
  210. }
  211. RubyString pathSeparator = runtime.newString(File.pathSeparator);
  212. pathSeparator.freeze(context);
  213. fileClass.defineConstant("PATH_SEPARATOR", pathSeparator);
  214. // TODO: why are we duplicating the constants here, and then in
  215. // File::Constants below? File::Constants is included in IO.
  216. // TODO: These were missing, so we're not handling them elsewhere?
  217. fileClass.fastSetConstant("FNM_NOESCAPE", runtime.newFixnum(FNM_NOESCAPE));
  218. fileClass.fastSetConstant("FNM_CASEFOLD", runtime.newFixnum(FNM_CASEFOLD));
  219. fileClass.fastSetConstant("FNM_SYSCASE", runtime.newFixnum(FNM_SYSCASE));
  220. fileClass.fastSetConstant("FNM_DOTMATCH", runtime.newFixnum(FNM_DOTMATCH));
  221. fileClass.fastSetConstant("FNM_PATHNAME", runtime.newFixnum(FNM_PATHNAME));
  222. // Create constants for open flags
  223. for (OpenFlags f : OpenFlags.values()) {
  224. // Strip off the O_ prefix, so they become File::RDONLY, and so on
  225. final String name = f.name();
  226. if (name.startsWith("O_")) {
  227. final String cname = name.substring(2);
  228. // Special case for handling ACCMODE, since constantine will generate
  229. // an invalid value if it is not defined by the platform.
  230. final RubyFixnum cvalue = f == OpenFlags.O_ACCMODE
  231. ? runtime.newFixnum(ModeFlags.ACCMODE)
  232. : runtime.newFixnum(f.value());
  233. fileClass.fastSetConstant(cname, cvalue);
  234. constants.fastSetConstant(cname, cvalue);
  235. }
  236. }
  237. // Create constants for flock
  238. fileClass.fastSetConstant("LOCK_SH", runtime.newFixnum(RubyFile.LOCK_SH));
  239. fileClass.fastSetConstant("LOCK_EX", runtime.newFixnum(RubyFile.LOCK_EX));
  240. fileClass.fastSetConstant("LOCK_NB", runtime.newFixnum(RubyFile.LOCK_NB));
  241. fileClass.fastSetConstant("LOCK_UN", runtime.newFixnum(RubyFile.LOCK_UN));
  242. constants.fastSetConstant("FNM_NOESCAPE", runtime.newFixnum(FNM_NOESCAPE));
  243. constants.fastSetConstant("FNM_CASEFOLD", runtime.newFixnum(FNM_CASEFOLD));
  244. constants.fastSetConstant("FNM_SYSCASE", runtime.newFixnum(FNM_SYSCASE));
  245. constants.fastSetConstant("FNM_DOTMATCH", runtime.newFixnum(FNM_DOTMATCH));
  246. constants.fastSetConstant("FNM_PATHNAME", runtime.newFixnum(FNM_PATHNAME));
  247. // Create constants for flock
  248. constants.fastSetConstant("LOCK_SH", runtime.newFixnum(RubyFile.LOCK_SH));
  249. constants.fastSetConstant("LOCK_EX", runtime.newFixnum(RubyFile.LOCK_EX));
  250. constants.fastSetConstant("LOCK_NB", runtime.newFixnum(RubyFile.LOCK_NB));
  251. constants.fastSetConstant("LOCK_UN", runtime.newFixnum(RubyFile.LOCK_UN));
  252. // File::Constants module is included in IO.
  253. runtime.getIO().includeModule(constants);
  254. runtime.getFileTest().extend_object(fileClass);
  255. fileClass.defineAnnotatedMethods(RubyFile.class);
  256. // For JRUBY-5276, physically define FileTest methods on File's singleton
  257. fileClass.getSingletonClass().defineAnnotatedMethods(RubyFileTest.FileTestFileMethods.class);
  258. return fileClass;
  259. }
  260. @JRubyMethod
  261. @Override
  262. public IRubyObject close() {
  263. // Make sure any existing lock is released before we try and close the file
  264. if (currentLock != null) {
  265. try {
  266. currentLock.release();
  267. } catch (IOException e) {
  268. throw getRuntime().newIOError(e.getMessage());
  269. }
  270. }
  271. return super.close();
  272. }
  273. @JRubyMethod(required = 1)
  274. public IRubyObject flock(ThreadContext context, IRubyObject lockingConstant) {
  275. // TODO: port exact behavior from MRI, and move most locking logic into ChannelDescriptor
  276. // TODO: for all LOCK_NB cases, return false if they would block
  277. ChannelDescriptor descriptor = openFile.getMainStream().getDescriptor();
  278. // null channel always succeeds for all locking operations
  279. if (descriptor.isNull()) return RubyFixnum.zero(context.getRuntime());
  280. if (descriptor.getChannel() instanceof FileChannel) {
  281. FileChannel fileChannel = (FileChannel)descriptor.getChannel();
  282. int lockMode = RubyNumeric.num2int(lockingConstant);
  283. // Exclusive locks in Java require the channel to be writable, otherwise
  284. // an exception is thrown (terminating JRuby execution).
  285. // But flock behavior of MRI is that it allows
  286. // exclusive locks even on non-writable file. So we convert exclusive
  287. // lock to shared lock if the channel is not writable, to better match
  288. // the MRI behavior.
  289. if (!openFile.isWritable() && (lockMode & LOCK_EX) > 0) {
  290. lockMode = (lockMode ^ LOCK_EX) | LOCK_SH;
  291. }
  292. try {
  293. switch (lockMode) {
  294. case LOCK_UN:
  295. case LOCK_UN | LOCK_NB:
  296. if (currentLock != null) {
  297. currentLock.release();
  298. currentLock = null;
  299. return RubyFixnum.zero(context.getRuntime());
  300. }
  301. break;
  302. case LOCK_EX:
  303. if (currentLock != null) {
  304. currentLock.release();
  305. currentLock = null;
  306. }
  307. currentLock = fileChannel.lock();
  308. if (currentLock != null) {
  309. return RubyFixnum.zero(context.getRuntime());
  310. }
  311. break;
  312. case LOCK_EX | LOCK_NB:
  313. if (currentLock != null) {
  314. currentLock.release();
  315. currentLock = null;
  316. }
  317. currentLock = fileChannel.tryLock();
  318. if (currentLock != null) {
  319. return RubyFixnum.zero(context.getRuntime());
  320. }
  321. break;
  322. case LOCK_SH:
  323. if (currentLock != null) {
  324. currentLock.release();
  325. currentLock = null;
  326. }
  327. currentLock = fileChannel.lock(0L, Long.MAX_VALUE, true);
  328. if (currentLock != null) {
  329. return RubyFixnum.zero(context.getRuntime());
  330. }
  331. break;
  332. case LOCK_SH | LOCK_NB:
  333. if (currentLock != null) {
  334. currentLock.release();
  335. currentLock = null;
  336. }
  337. currentLock = fileChannel.tryLock(0L, Long.MAX_VALUE, true);
  338. if (currentLock != null) {
  339. return RubyFixnum.zero(context.getRuntime());
  340. }
  341. break;
  342. default:
  343. }
  344. } catch (IOException ioe) {
  345. if (context.getRuntime().getDebug().isTrue()) {
  346. ioe.printStackTrace(System.err);
  347. }
  348. } catch (java.nio.channels.OverlappingFileLockException ioe) {
  349. if (context.getRuntime().getDebug().isTrue()) {
  350. ioe.printStackTrace(System.err);
  351. }
  352. }
  353. return (lockMode & LOCK_EX) == 0 ? RubyFixnum.zero(context.getRuntime()) : context.getRuntime().getFalse();
  354. } else {
  355. // We're not actually a real file, so we can't flock
  356. return context.getRuntime().getFalse();
  357. }
  358. }
  359. @JRubyMethod(required = 1, optional = 2, visibility = PRIVATE, compat = RUBY1_8)
  360. @Override
  361. public IRubyObject initialize(IRubyObject[] args, Block block) {
  362. if (openFile == null) {
  363. throw getRuntime().newRuntimeError("reinitializing File");
  364. }
  365. if (args.length > 0 && args.length < 3) {
  366. IRubyObject fd = TypeConverter.convertToTypeWithCheck(args[0], getRuntime().getFixnum(), "to_int");
  367. if (!fd.isNil()) {
  368. args[0] = fd;
  369. return super.initialize(args, block);
  370. }
  371. }
  372. return openFile(args);
  373. }
  374. @JRubyMethod(name = "initialize", required = 1, optional = 2, visibility = PRIVATE, compat = RUBY1_9)
  375. public IRubyObject initialize19(ThreadContext context, IRubyObject[] args, Block block) {
  376. if (openFile == null) {
  377. throw context.getRuntime().newRuntimeError("reinitializing File");
  378. }
  379. if (args.length > 0 && args.length <= 3) {
  380. IRubyObject fd = TypeConverter.convertToTypeWithCheck(args[0], context.getRuntime().getFixnum(), "to_int");
  381. if (!fd.isNil()) {
  382. args[0] = fd;
  383. if (args.length == 1) {
  384. return super.initialize19(context, args[0], block);
  385. } else if (args.length == 2) {
  386. return super.initialize19(context, args[0], args[1], block);
  387. }
  388. return super.initialize19(context, args[0], args[1], args[2], block);
  389. }
  390. }
  391. return openFile19(context, args);
  392. }
  393. private IRubyObject openFile19(ThreadContext context, IRubyObject args[]) {
  394. Ruby runtime = context.getRuntime();
  395. RubyString filename = get_path(context, args[0]);
  396. runtime.checkSafeString(filename);
  397. path = adjustRootPathOnWindows(runtime, filename.getUnicodeValue(), runtime.getCurrentDirectory());
  398. String modeString = "r";
  399. ModeFlags modes = new ModeFlags();
  400. int perm = 0;
  401. try {
  402. if (args.length > 1) {
  403. modes = parseModes19(context, args[1]);
  404. if (args[1] instanceof RubyFixnum) {
  405. perm = getFilePermissions(args);
  406. } else {
  407. modeString = args[1].convertToString().toString();
  408. }
  409. } else {
  410. modes = parseModes19(context, RubyString.newString(runtime, modeString));
  411. }
  412. if (args.length > 2 && !args[2].isNil()) {
  413. if (args[2] instanceof RubyHash) {
  414. modes = parseOptions(context, args[2], modes);
  415. } else {
  416. perm = getFilePermissions(args);
  417. }
  418. }
  419. if (perm > 0) {
  420. sysopenInternal(path, modes, perm);
  421. } else {
  422. openInternal(path, modeString, modes);
  423. }
  424. } catch (InvalidValueException ex) {
  425. throw runtime.newErrnoEINVALError();
  426. }
  427. return this;
  428. }
  429. private IRubyObject openFile(IRubyObject args[]) {
  430. Ruby runtime = getRuntime();
  431. RubyString filename = get_path(runtime.getCurrentContext(), args[0]);
  432. runtime.checkSafeString(filename);
  433. path = adjustRootPathOnWindows(runtime, filename.getUnicodeValue(), runtime.getCurrentDirectory());
  434. String modeString;
  435. ModeFlags modes;
  436. int perm;
  437. try {
  438. if ((args.length > 1 && args[1] instanceof RubyFixnum) || (args.length > 2 && !args[2].isNil())) {
  439. modes = parseModes(args[1]);
  440. perm = getFilePermissions(args);
  441. sysopenInternal(path, modes, perm);
  442. } else {
  443. modeString = "r";
  444. if (args.length > 1 && !args[1].isNil()) {
  445. modeString = args[1].convertToString().toString();
  446. }
  447. openInternal(path, modeString);
  448. }
  449. } catch (InvalidValueException ex) {
  450. throw getRuntime().newErrnoEINVALError();
  451. } finally {}
  452. return this;
  453. }
  454. private int getFilePermissions(IRubyObject[] args) {
  455. return (args.length > 2 && !args[2].isNil()) ? RubyNumeric.num2int(args[2]) : 438;
  456. }
  457. protected void sysopenInternal(String path, ModeFlags modes, int perm) throws InvalidValueException {
  458. openFile = new OpenFile();
  459. openFile.setPath(path);
  460. openFile.setMode(modes.getOpenFileFlags());
  461. int umask = getUmaskSafe( getRuntime() );
  462. perm = perm - (perm & umask);
  463. ChannelDescriptor descriptor = sysopen(path, modes, perm);
  464. openFile.setMainStream(fdopen(descriptor, modes));
  465. }
  466. protected void openInternal(String path, String modeString, ModeFlags modes) throws InvalidValueException {
  467. openFile = new OpenFile();
  468. openFile.setMode(modes.getOpenFileFlags());
  469. openFile.setPath(path);
  470. openFile.setMainStream(fopen(path, modeString));
  471. }
  472. protected void openInternal(String path, String modeString) throws InvalidValueException {
  473. openFile = new OpenFile();
  474. openFile.setMode(getIOModes(getRuntime(), modeString).getOpenFileFlags());
  475. openFile.setPath(path);
  476. openFile.setMainStream(fopen(path, modeString));
  477. }
  478. private ChannelDescriptor sysopen(String path, ModeFlags modes, int perm) throws InvalidValueException {
  479. try {
  480. ChannelDescriptor descriptor = ChannelDescriptor.open(
  481. getRuntime().getCurrentDirectory(),
  482. path,
  483. modes,
  484. perm,
  485. getRuntime().getPosix(),
  486. getRuntime().getJRubyClassLoader());
  487. // TODO: check if too many open files, GC and try again
  488. return descriptor;
  489. } catch (PermissionDeniedException pde) {
  490. // PDException can be thrown only when creating the file and
  491. // permission is denied. See JavaDoc of PermissionDeniedException.
  492. throw getRuntime().newErrnoEACCESError(path);
  493. } catch (FileNotFoundException fnfe) {
  494. // FNFException can be thrown in both cases, when the file
  495. // is not found, or when permission is denied.
  496. if (Ruby.isSecurityRestricted() || new File(path).exists()) {
  497. throw getRuntime().newErrnoEACCESError(path);
  498. }
  499. throw getRuntime().newErrnoENOENTError(path);
  500. } catch (DirectoryAsFileException dafe) {
  501. throw getRuntime().newErrnoEISDirError();
  502. } catch (FileExistsException fee) {
  503. throw getRuntime().newErrnoEEXISTError(path);
  504. } catch (IOException ioe) {
  505. throw getRuntime().newIOErrorFromException(ioe);
  506. }
  507. }
  508. private Stream fopen(String path, String modeString) {
  509. try {
  510. Stream stream = ChannelStream.fopen(
  511. getRuntime(),
  512. path,
  513. getIOModes(getRuntime(), modeString));
  514. if (stream == null) {
  515. // TODO
  516. // if (errno == EMFILE || errno == ENFILE) {
  517. // rb_gc();
  518. // file = fopen(fname, mode);
  519. // }
  520. // if (!file) {
  521. // rb_sys_fail(fname);
  522. // }
  523. }
  524. // Do we need to be in SETVBUF mode for buffering to make sense? This comes up elsewhere.
  525. // #ifdef USE_SETVBUF
  526. // if (setvbuf(file, NULL, _IOFBF, 0) != 0)
  527. // rb_warn("setvbuf() can't be honoured for %s", fname);
  528. // #endif
  529. // #ifdef __human68k__
  530. // fmode(file, _IOTEXT);
  531. // #endif
  532. return stream;
  533. } catch (BadDescriptorException e) {
  534. throw getRuntime().newErrnoEBADFError();
  535. } catch (PermissionDeniedException pde) {
  536. // PDException can be thrown only when creating the file and
  537. // permission is denied. See JavaDoc of PermissionDeniedException.
  538. throw getRuntime().newErrnoEACCESError(path);
  539. } catch (FileNotFoundException ex) {
  540. // FNFException can be thrown in both cases, when the file
  541. // is not found, or when permission is denied.
  542. if (Ruby.isSecurityRestricted() || new File(path).exists()) {
  543. throw getRuntime().newErrnoEACCESError(path);
  544. }
  545. throw getRuntime().newErrnoENOENTError(path);
  546. } catch (DirectoryAsFileException ex) {
  547. throw getRuntime().newErrnoEISDirError();
  548. } catch (FileExistsException ex) {
  549. throw getRuntime().newErrnoEEXISTError(path);
  550. } catch (IOException ex) {
  551. throw getRuntime().newIOErrorFromException(ex);
  552. } catch (InvalidValueException ex) {
  553. throw getRuntime().newErrnoEINVALError();
  554. } catch (PipeException ex) {
  555. throw getRuntime().newErrnoEPIPEError();
  556. } catch (SecurityException ex) {
  557. throw getRuntime().newErrnoEACCESError(path);
  558. }
  559. }
  560. @JRubyMethod(required = 1)
  561. public IRubyObject chmod(ThreadContext context, IRubyObject arg) {
  562. checkClosed(context);
  563. int mode = (int) arg.convertToInteger().getLongValue();
  564. if (!new File(path).exists()) {
  565. throw context.getRuntime().newErrnoENOENTError(path);
  566. }
  567. return context.getRuntime().newFixnum(context.getRuntime().getPosix().chmod(path, mode));
  568. }
  569. @JRubyMethod(required = 2)
  570. public IRubyObject chown(ThreadContext context, IRubyObject arg1, IRubyObject arg2) {
  571. checkClosed(context);
  572. int owner = -1;
  573. if (!arg1.isNil()) {
  574. owner = RubyNumeric.num2int(arg1);
  575. }
  576. int group = -1;
  577. if (!arg2.isNil()) {
  578. group = RubyNumeric.num2int(arg2);
  579. }
  580. if (!new File(path).exists()) {
  581. throw context.getRuntime().newErrnoENOENTError(path);
  582. }
  583. return context.getRuntime().newFixnum(context.getRuntime().getPosix().chown(path, owner, group));
  584. }
  585. @JRubyMethod
  586. public IRubyObject atime(ThreadContext context) {
  587. checkClosed(context);
  588. return context.getRuntime().newFileStat(path, false).atime();
  589. }
  590. @JRubyMethod
  591. public IRubyObject ctime(ThreadContext context) {
  592. checkClosed(context);
  593. return context.getRuntime().newFileStat(path, false).ctime();
  594. }
  595. @JRubyMethod(required = 1)
  596. public IRubyObject lchmod(ThreadContext context, IRubyObject arg) {
  597. int mode = (int) arg.convertToInteger().getLongValue();
  598. if (!new File(path).exists()) {
  599. throw context.getRuntime().newErrnoENOENTError(path);
  600. }
  601. return context.getRuntime().newFixnum(context.getRuntime().getPosix().lchmod(path, mode));
  602. }
  603. // TODO: this method is not present in MRI!
  604. @JRubyMethod(required = 2)
  605. public IRubyObject lchown(ThreadContext context, IRubyObject arg1, IRubyObject arg2) {
  606. int owner = -1;
  607. if (!arg1.isNil()) {
  608. owner = RubyNumeric.num2int(arg1);
  609. }
  610. int group = -1;
  611. if (!arg2.isNil()) {
  612. group = RubyNumeric.num2int(arg2);
  613. }
  614. if (!new File(path).exists()) {
  615. throw context.getRuntime().newErrnoENOENTError(path);
  616. }
  617. return context.getRuntime().newFixnum(context.getRuntime().getPosix().lchown(path, owner, group));
  618. }
  619. @JRubyMethod
  620. public IRubyObject lstat(ThreadContext context) {
  621. checkClosed(context);
  622. return context.getRuntime().newFileStat(path, true);
  623. }
  624. @JRubyMethod
  625. public IRubyObject mtime(ThreadContext context) {
  626. checkClosed(context);
  627. return getLastModified(context.getRuntime(), path);
  628. }
  629. @JRubyMethod(meta = true, compat = RUBY1_9)
  630. public static IRubyObject path(ThreadContext context, IRubyObject self, IRubyObject str) {
  631. return get_path(context, str);
  632. }
  633. /**
  634. * similar in spirit to rb_get_path from 1.9 source
  635. * @param context
  636. * @param obj
  637. * @return
  638. */
  639. public static RubyString get_path(ThreadContext context, IRubyObject obj) {
  640. if (context.getRuntime().is1_9()) {
  641. if (obj instanceof RubyString) {
  642. return (RubyString)obj;
  643. }
  644. if (obj.respondsTo("to_path")) {
  645. obj = obj.callMethod(context, "to_path");
  646. }
  647. }
  648. return obj.convertToString();
  649. }
  650. /**
  651. * Get the fully-qualified JRubyFile object for the path, taking into
  652. * account the runtime's current directory.
  653. */
  654. public static JRubyFile file(IRubyObject pathOrFile) {
  655. Ruby runtime = pathOrFile.getRuntime();
  656. if (pathOrFile instanceof RubyFile) {
  657. return JRubyFile.create(runtime.getCurrentDirectory(), ((RubyFile) pathOrFile).getPath());
  658. } else {
  659. RubyString pathStr = get_path(runtime.getCurrentContext(), pathOrFile);
  660. String path = pathStr.getUnicodeValue();
  661. String[] pathParts = splitURI(path);
  662. if (pathParts != null && pathParts[0].startsWith("file:")) {
  663. path = pathParts[1];
  664. }
  665. return JRubyFile.create(runtime.getCurrentDirectory(), path);
  666. }
  667. }
  668. @JRubyMethod(name = {"path", "to_path"})
  669. public IRubyObject path(ThreadContext context) {
  670. IRubyObject newPath = context.getRuntime().getNil();
  671. if (path != null) {
  672. newPath = context.getRuntime().newString(path);
  673. newPath.setTaint(true);
  674. }
  675. return newPath;
  676. }
  677. @JRubyMethod
  678. @Override
  679. public IRubyObject stat(ThreadContext context) {
  680. checkClosed(context);
  681. return context.getRuntime().newFileStat(path, false);
  682. }
  683. @JRubyMethod(required = 1)
  684. public IRubyObject truncate(ThreadContext context, IRubyObject arg) {
  685. RubyInteger newLength = arg.convertToInteger();
  686. if (newLength.getLongValue() < 0) {
  687. throw context.getRuntime().newErrnoEINVALError(path);
  688. }
  689. try {
  690. openFile.checkWritable(context.getRuntime());
  691. openFile.getMainStream().ftruncate(newLength.getLongValue());
  692. } catch (BadDescriptorException e) {
  693. throw context.getRuntime().newErrnoEBADFError();
  694. } catch (PipeException e) {
  695. throw context.getRuntime().newErrnoESPIPEError();
  696. } catch (InvalidValueException ex) {
  697. throw context.getRuntime().newErrnoEINVALError();
  698. } catch (IOException e) {
  699. // Should we do anything?
  700. }
  701. return RubyFixnum.zero(context.getRuntime());
  702. }
  703. @Override
  704. public String toString() {
  705. return "RubyFile(" + path + ", " + openFile.getMode() + ", " + getRuntime().getFileno(openFile.getMainStream().getDescriptor()) + ")";
  706. }
  707. // TODO: This is also defined in the MetaClass too...Consolidate somewhere.
  708. private static ModeFlags getModes(Ruby runtime, IRubyObject object) throws InvalidValueException {
  709. if (object instanceof RubyString) {
  710. return getIOModes(runtime, ((RubyString) object).toString());
  711. } else if (object instanceof RubyFixnum) {
  712. return new ModeFlags(((RubyFixnum) object).getLongValue());
  713. }
  714. throw runtime.newTypeError("Invalid type for modes");
  715. }
  716. @JRubyMethod
  717. @Override
  718. public IRubyObject inspect() {
  719. StringBuilder val = new StringBuilder();
  720. val.append("#<File:").append(path);
  721. if(!openFile.isOpen()) {
  722. val.append(" (closed)");
  723. }
  724. val.append(">");
  725. return getRuntime().newString(val.toString());
  726. }
  727. /* File class methods */
  728. @JRubyMethod(required = 1, optional = 1, meta = true)
  729. public static IRubyObject basename(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  730. String name = get_path(context,args[0]).getUnicodeValue();
  731. // MRI-compatible basename handling for windows drive letter paths
  732. if (Platform.IS_WINDOWS) {
  733. if (name.length() > 1 && name.charAt(1) == ':' && Character.isLetter(name.charAt(0))) {
  734. switch (name.length()) {
  735. case 2:
  736. return RubyString.newEmptyString(context.getRuntime()).infectBy(args[0]);
  737. case 3:
  738. return context.getRuntime().newString(name.substring(2)).infectBy(args[0]);
  739. default:
  740. switch (name.charAt(2)) {
  741. case '/':
  742. case '\\':
  743. break;
  744. default:
  745. // strip c: away from relative-pathed name
  746. name = name.substring(2);
  747. break;
  748. }
  749. break;
  750. }
  751. }
  752. }
  753. while (name.length() > 1 && name.charAt(name.length() - 1) == '/') {
  754. name = name.substring(0, name.length() - 1);
  755. }
  756. // Paths which end in "/" or "\\" must be stripped off.
  757. int slashCount = 0;
  758. int length = name.length();
  759. for (int i = length - 1; i >= 0; i--) {
  760. char c = name.charAt(i);
  761. if (c != '/' && c != '\\') {
  762. break;
  763. }
  764. slashCount++;
  765. }
  766. if (slashCount > 0 && length > 1) {
  767. name = name.substring(0, name.length() - slashCount);
  768. }
  769. int index = name.lastIndexOf('/');
  770. if (index == -1) {
  771. // XXX actually only on windows...
  772. index = name.lastIndexOf('\\');
  773. }
  774. if (!name.equals("/") && index != -1) {
  775. name = name.substring(index + 1);
  776. }
  777. if (args.length == 2) {
  778. String ext = RubyString.stringValue(args[1]).toString();
  779. if (".*".equals(ext)) {
  780. index = name.lastIndexOf('.');
  781. if (index > 0) { // -1 no match; 0 it is dot file not extension
  782. name = name.substring(0, index);
  783. }
  784. } else if (name.endsWith(ext)) {
  785. name = name.substring(0, name.length() - ext.length());
  786. }
  787. }
  788. return context.getRuntime().newString(name).infectBy(args[0]);
  789. }
  790. @JRubyMethod(required = 2, rest = true, meta = true)
  791. public static IRubyObject chmod(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  792. Ruby runtime = context.getRuntime();
  793. int count = 0;
  794. RubyInteger mode = args[0].convertToInteger();
  795. for (int i = 1; i < args.length; i++) {
  796. JRubyFile filename = file(args[i]);
  797. if (!filename.exists()) {
  798. throw runtime.newErrnoENOENTError(filename.toString());
  799. }
  800. boolean result = 0 == runtime.getPosix().chmod(filename.getAbsolutePath(), (int)mode.getLongValue());
  801. if (result) {
  802. count++;
  803. }
  804. }
  805. return runtime.newFixnum(count);
  806. }
  807. @JRubyMethod(required = 3, rest = true, meta = true)
  808. public static IRubyObject chown(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  809. Ruby runtime = context.getRuntime();
  810. int count = 0;
  811. int owner = -1;
  812. if (!args[0].isNil()) {
  813. owner = RubyNumeric.num2int(args[0]);
  814. }
  815. int group = -1;
  816. if (!args[1].isNil()) {
  817. group = RubyNumeric.num2int(args[1]);
  818. }
  819. for (int i = 2; i < args.length; i++) {
  820. JRubyFile filename = file(args[i]);
  821. if (!filename.exists()) {
  822. throw runtime.newErrnoENOENTError(filename.toString());
  823. }
  824. boolean result = 0 == runtime.getPosix().chown(filename.getAbsolutePath(), owner, group);
  825. if (result) {
  826. count++;
  827. }
  828. }
  829. return runtime.newFixnum(count);
  830. }
  831. @JRubyMethod(required = 1, meta = true)
  832. public static IRubyObject dirname(ThreadContext context, IRubyObject recv, IRubyObject arg) {
  833. RubyString filename = get_path(context, arg);
  834. String jfilename = filename.getUnicodeValue();
  835. String name = jfilename.replace('\\', '/');
  836. int minPathLength = 1;
  837. boolean trimmedSlashes = false;
  838. boolean startsWithDriveLetterOnWindows = startsWithDriveLetterOnWindows(name);
  839. if (startsWithDriveLetterOnWindows) {
  840. minPathLength = 3;
  841. }
  842. while (name.length() > minPathLength && name.charAt(name.length() - 1) == '/') {
  843. trimmedSlashes = true;
  844. name = name.substring(0, name.length() - 1);
  845. }
  846. String result;
  847. if (startsWithDriveLetterOnWindows && name.length() == 2) {
  848. if (trimmedSlashes) {
  849. // C:\ is returned unchanged
  850. result = jfilename.substring(0, 3);
  851. } else {
  852. result = jfilename.substring(0, 2) + '.';
  853. }
  854. } else {
  855. //TODO deal with UNC names
  856. int index = name.lastIndexOf('/');
  857. if (index == -1) {
  858. if (startsWithDriveLetterOnWindows) {
  859. return context.getRuntime().newString(jfilename.substring(0, 2) + ".");
  860. } else {
  861. return context.getRuntime().newString(".");
  862. }
  863. }
  864. if (index == 0) return context.getRuntime().newString("/");
  865. if (startsWithDriveLetterOnWindows && index == 2) {
  866. // Include additional path separator
  867. // (so that dirname of "C:\file.txt" is "C:\", not "C:")
  868. index++;
  869. }
  870. result = jfilename.substring(0, index);
  871. }
  872. char endChar;
  873. // trim trailing slashes
  874. while (result.length() > minPathLength) {
  875. endChar = result.charAt(result.length() - 1);
  876. if (endChar == '/' || endChar == '\\') {
  877. result = result.substring(0, result.length() - 1);
  878. } else {
  879. break;
  880. }
  881. }
  882. return context.getRuntime().newString(result).infectBy(filename);
  883. }
  884. private static boolean isWindowsDriveLetter(char c) {
  885. return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
  886. }
  887. /**
  888. * Returns the extension name of the file. An empty string is returned if
  889. * the filename (not the entire path) starts or ends with a dot.
  890. * @param recv
  891. * @param arg Path to get extension name of
  892. * @return Extension, including the dot, or an empty string
  893. */
  894. @JRubyMethod(required = 1, meta = true)
  895. public static IRubyObject extname(ThreadContext context, IRubyObject recv, IRubyObject arg) {
  896. IRubyObject baseFilename = basename(context, recv, new IRubyObject[]{arg});
  897. String filename = RubyString.stringValue(baseFilename).getUnicodeValue();
  898. String result = "";
  899. int dotIndex = filename.lastIndexOf(".");
  900. if (dotIndex > 0 && dotIndex != (filename.length() - 1)) {
  901. // Dot is not at beginning and not at end of filename.
  902. result = filename.substring(dotIndex);
  903. }
  904. return context.getRuntime().newString(result);
  905. }
  906. /**
  907. * Converts a pathname to an absolute pathname. Relative paths are
  908. * referenced from the current working directory of the process unless
  909. * a second argument is given, in which case it will be used as the
  910. * starting point. If the second argument is also relative, it will
  911. * first be converted to an absolute pathname.
  912. * @param recv
  913. * @param args
  914. * @return Resulting absolute path as a String
  915. */
  916. @JRubyMethod(required = 1, optional = 1, meta = true, compat = CompatVersion.RUBY1_8)
  917. public static IRubyObject expand_path(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  918. return expandPathInternal(context, recv, args, true);
  919. }
  920. @JRubyMethod(name = "expand_path", required = 1, optional = 1, meta = true, compat = CompatVersion.RUBY1_9)
  921. public static IRubyObject expand_path19(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  922. RubyString path = (RubyString) expandPathInternal(context, recv, args, true);
  923. path.force_encoding(context, context.getRuntime().getEncodingService().getDefaultExternal());
  924. return path;
  925. }
  926. /**
  927. * ---------------------------------------------------- File::absolute_path
  928. * File.absolute_path(file_name [, dir_string] ) -> abs_file_name
  929. *
  930. * From Ruby 1.9.1
  931. * ------------------------------------------------------------------------
  932. * Converts a pathname to an absolute pathname. Relative paths are
  933. * referenced from the current working directory of the process unless
  934. * _dir_string_ is given, in which case it will be used as the
  935. * starting point. If the given pathname starts with a ``+~+'' it is
  936. * NOT expanded, it is treated as a normal directory name.
  937. *
  938. * File.absolute_path("~oracle/bin") #=> "<relative_path>/~oracle/bin"
  939. *
  940. * @param context
  941. * @param recv
  942. * @param args
  943. * @return
  944. */
  945. @JRubyMethod(required = 1, optional = 1, meta = true, compat = RUBY1_9)
  946. public static IRubyObject absolute_path(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  947. return expandPathInternal(context, recv, args, false);
  948. }
  949. @JRubyMethod(name = {"realdirpath"}, required = 1, optional = 1, meta = true, compat = RUBY1_9)
  950. public static IRubyObject realdirpath(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  951. return expandPathInternal(context, recv, args, false);
  952. }
  953. @JRubyMethod(name = {"realpath"}, required = 1, optional = 1, meta = true, compat = RUBY1_9)
  954. public static IRubyObject realpath(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  955. IRubyObject file = expandPathInternal(context, recv, args, false);
  956. if (!RubyFileTest.exist_p(recv, file).isTrue()) {
  957. throw context.getRuntime().newErrnoENOENTError(file.toString());
  958. }
  959. return file;
  960. }
  961. private static IRubyObject expandPathInternal(ThreadContext context, IRubyObject recv, IRubyObject[] args, boolean expandUser) {
  962. Ruby runtime = context.getRuntime();
  963. String relativePath = get_path(context, args[0]).getUnicodeValue();
  964. String[] uriParts = splitURI(relativePath);
  965. String cwd = null;
  966. // Handle ~user paths
  967. if (expandUser) {
  968. relativePath = expandUserPath(context, relativePath);
  969. }
  970. if (uriParts != null) {
  971. relativePath = uriParts[1];
  972. }
  973. // If there's a second argument, it's the path to which the first
  974. // argument is relative.
  975. if (args.length == 2 && !args[1].isNil()) {
  976. cwd = get_path(context, args[1]).getUnicodeValue();
  977. // Handle ~user paths.
  978. if (expandUser) {
  979. cwd = expandUserPath(context, cwd);
  980. }
  981. String[] cwdURIParts = splitURI(cwd);
  982. if (uriParts == null && cwdURIParts != null) {
  983. uriParts = cwdURIParts;
  984. cwd = cwdURIParts[1];
  985. }
  986. cwd = adjustRootPathOnWindows(runtime, cwd, null);
  987. boolean startsWithSlashNotOnWindows = (cwd != null)
  988. && !Platform.IS_WINDOWS && cwd.length() > 0
  989. && cwd.charAt(0) == '/';
  990. // TODO: better detection when path is absolute or not.
  991. // If the path isn't absolute, then prepend the current working
  992. // directory to the path.
  993. if (!startsWithSlashNotOnWindows && !startsWithDriveLetterOnWindows(cwd)) {
  994. cwd = new File(runtime.getCurrentDirectory(), cwd).getAbsolutePath();
  995. }
  996. } else {
  997. // If there's no second argument, simply use the working directory
  998. // of the runtime.
  999. cwd = runtime.getCurrentDirectory();
  1000. }
  1001. // Something wrong we don't know the cwd...
  1002. // TODO: Is this behavior really desirable? /mov
  1003. if (cwd == null) return runtime.getNil();
  1004. /* The counting of slashes that follows is simply a way to adhere to
  1005. * Ruby's UNC (or something) compatibility. When Ruby's expand_path is
  1006. * called with "//foo//bar" it will return "//foo/bar". JRuby uses
  1007. * java.io.File, and hence returns "/foo/bar". In order to retain
  1008. * java.io.File in the lower layers and provide full Ruby
  1009. * compatibility, the number of extra slashes must be counted and
  1010. * prepended to the result.
  1011. */
  1012. // TODO: special handling on windows for some corner cases
  1013. // if (IS_WINDOWS) {
  1014. // if (relativePath.startsWith("//")) {
  1015. // if (relativePath.length() > 2 && relativePath.charAt(2) != '/') {
  1016. // int nextSlash = relativePath.indexOf('/', 3);
  1017. // if (nextSlash != -1) {
  1018. // return runtime.newString(
  1019. // relativePath.substring(0, nextSlash)
  1020. // + canonicalize(relativePath.substring(nextSlash)));
  1021. // } else {
  1022. // return runtime.newString(relativePath);
  1023. // }
  1024. // }
  1025. // }
  1026. // }
  1027. // Find out which string to check.
  1028. String padSlashes = "";
  1029. if (uriParts != null) {
  1030. padSlashes = uriParts[0];
  1031. } else if (!Platform.IS_WINDOWS) {
  1032. if (relativePath.length() > 0 && relativePath.charAt(0) == '/') {
  1033. padSlashes = countSlashes(relativePath);
  1034. } else if (cwd.length() > 0 && cwd.charAt(0) == '/') {
  1035. padSlashes = countSlashes(cwd);
  1036. }
  1037. }
  1038. JRubyFile path;
  1039. if (relativePath.length() == 0) {
  1040. path = JRubyFile.create(relativePath, cwd);
  1041. } else {
  1042. relativePath = adjustRootPathOnWindows(runtime, relativePath, cwd);
  1043. path = JRubyFile.create(cwd, relativePath);
  1044. }
  1045. return runtime.newString(padSlashes + canonicalize(path.getAbsolutePath()));
  1046. }
  1047. private static Pattern URI_PREFIX = Pattern.compile("^[a-z]{2,}:");
  1048. public static String[] splitURI(String path) {
  1049. Matcher m = URI_PREFIX.matcher(path);
  1050. if (m.find()) {
  1051. try {
  1052. URI u = new URI(path);
  1053. String pathPart = u.getPath();
  1054. return new String[] {path.substring(0, path.indexOf(pathPart)), pathPart};
  1055. } catch (Exception e) {
  1056. try {
  1057. URL u = new URL(path);
  1058. String pathPart = u.getPath();
  1059. return new String[] {path.substring(0, path.indexOf(pathPart)), pathPart};
  1060. } catch (Exception e2) {
  1061. }
  1062. }
  1063. }
  1064. return null;
  1065. }
  1066. /**
  1067. * This method checks a path, and if it starts with ~, then it expands
  1068. * the path to the absolute path of the user's home directory. If the
  1069. * string does not begin with ~, then the string is simply returned.
  1070. * unaltered.
  1071. * @param recv
  1072. * @param path Path to check
  1073. * @return Expanded path
  1074. */
  1075. public static String expandUserPath(ThreadContext context, String path) {
  1076. int pathLength = path.length();
  1077. if (pathLength >= 1 && path.charAt(0) == '~') {
  1078. // Enebo : Should ~frogger\\foo work (it doesnt in linux ruby)?
  1079. int userEnd = path.indexOf('/');
  1080. if (userEnd == -1) {
  1081. if (pathLength == 1) {
  1082. // Single '~' as whole path to expand
  1083. path = RubyDir.getHomeDirectoryPath(context).toString();
  1084. } else {
  1085. // No directory delimeter. Rest of string is username
  1086. userEnd = pathLength;
  1087. }
  1088. }
  1089. if (userEnd == 1) {
  1090. // '~/...' as path to expand
  1091. path = RubyDir.getHomeDirectoryPath(context).toString() +
  1092. path.substring(1);
  1093. } else if (userEnd > 1){
  1094. // '~user/...' as path to expand
  1095. String user = path.substring(1, userEnd);
  1096. IRubyObject dir = RubyDir.getHomeDirectoryPath(context, user);
  1097. if (dir.isNil()) {
  1098. throw context.getRuntime().newArgumentError("user " + user + " does not exist");
  1099. }
  1100. path = "" + dir + (pathLength == userEnd ? "" : path.substring(userEnd));
  1101. }
  1102. }
  1103. return path;
  1104. }
  1105. private static final String[] SLASHES = {"", "/", "//"};
  1106. /**
  1107. * Returns a string consisting of <code>n-1</code> slashes, where
  1108. * <code>n</code> is the number of slashes at the beginning of the input
  1109. * string.
  1110. * @param stringToCheck
  1111. * @return
  1112. */
  1113. private static String countSlashes( String stringToCheck ) {
  1114. // Count number of extra slashes in the beginning of the string.
  1115. int slashCount = 0;
  1116. for (int i = 0; i < stringToCheck.length(); i++) {
  1117. if (stringToCheck.charAt(i) == '/') {
  1118. slashCount++;
  1119. } else {
  1120. break;
  1121. }
  1122. }
  1123. // If there are N slashes, then we want N-1.
  1124. if (slashCount > 0) {
  1125. slashCount--;
  1126. }
  1127. if (slashCount < SLASHES.length) {
  1128. return SLASHES[slashCount];
  1129. }
  1130. // Prepare a string with the same number of redundant slashes so that
  1131. // we easily can prepend it to the result.
  1132. char[] slashes = new char[slashCount];
  1133. for (int i = 0; i < slashCount; i++) {
  1134. slashes[i] = '/';
  1135. }
  1136. return new String(slashes);
  1137. }
  1138. public static String canonicalize(String path) {
  1139. return canonicalize(null, path);
  1140. }
  1141. private static String canonicalize(String canonicalPath, String remaining) {
  1142. if (remaining == null) {
  1143. if ("".equals(canonicalPath)) {
  1144. return "/";
  1145. } else {
  1146. // compensate for missing slash after drive letter on windows
  1147. if (startsWithDriveLetterOnWindows(canonicalPath)
  1148. && canonicalPath.length() == 2) {
  1149. canonicalPath += "/";
  1150. }
  1151. }
  1152. return canonicalPath;
  1153. }
  1154. String child;
  1155. int slash = remaining.indexOf('/');
  1156. if (slash == -1) {
  1157. child = remaining;
  1158. remaining = null;
  1159. } else {
  1160. child = remaining.substring(0, slash);
  1161. remaining = remaining.substring(slash + 1);
  1162. }
  1163. if (child.equals(".")) {
  1164. // no canonical path yet or length is zero, and we have a / followed by a dot...
  1165. if (slash == -1) {
  1166. // we don't have another slash after this, so replace /. with /
  1167. if (canonicalPath != null && canonicalPath.length() == 0 && slash == -1) canonicalPath += "/";
  1168. } else {
  1169. // we do have another slash; omit both / and . (JRUBY-1606)
  1170. }
  1171. } else if (child.equals("..")) {
  1172. if (canonicalPath == null) throw new IllegalArgumentException("Cannot have .. at the start of an absolute path");
  1173. int lastDir = canonicalPath.lastIndexOf('/');
  1174. if (lastDir == -1) {
  1175. if (startsWithDriveLetterOnWindows(canonicalPath)) {
  1176. // do nothing, we should not delete the drive letter
  1177. } else {
  1178. canonicalPath = "";
  1179. }
  1180. } else {
  1181. canonicalPath = canonicalPath.substring(0, lastDir);
  1182. }
  1183. } else if (canonicalPath == null) {
  1184. canonicalPath = child;
  1185. } else {
  1186. canonicalPath += "/" + child;
  1187. }
  1188. return canonicalize(canonicalPath, remaining);
  1189. }
  1190. /**
  1191. * Returns true if path matches against pattern The pattern is not a regular expression;
  1192. * instead it follows rules similar to shell filename globbing. It may contain the following
  1193. * metacharacters:
  1194. * *: Glob - match any sequence chars (re: .*). If like begins with '.' then it doesn't.
  1195. * ?: Matches a single char (re: .).
  1196. * [set]: Matches a single char in a set (re: [...]).
  1197. *
  1198. */
  1199. @JRubyMethod(name = {"fnmatch", "fnmatch?"}, required = 2, optional = 1, meta = true)
  1200. public static IRubyObject fnmatch(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  1201. int flags = args.length == 3 ? RubyNumeric.num2int(args[2]) : 0;
  1202. ByteList pattern = args[0].convertToString().getByteList();
  1203. ByteList path = get_path(context, args[1]).getByteList();
  1204. if (org.jruby.util.Dir.fnmatch(pattern.getUnsafeBytes(), pattern.getBegin(), pattern.getBegin()+pattern.getRealSize(), path.getUnsafeBytes(), path.getBegin(), path.getBegin()+path.getRealSize(), flags) == 0) {
  1205. return context.getRuntime().getTrue();
  1206. }
  1207. return context.getRuntime().getFalse();
  1208. }
  1209. @JRubyMethod(name = "ftype", required = 1, meta = true)
  1210. public static IRubyObject ftype(ThreadContext context, IRubyObject recv, IRubyObject filename) {
  1211. return context.getRuntime().newFileStat(get_path(context, filename).getUnicodeValue(), true).ftype();
  1212. }
  1213. private static String inspectJoin(ThreadContext context, IRubyObject recv, RubyArray parent, RubyArray array) {
  1214. Ruby runtime = context.getRuntime();
  1215. // If already inspecting, there is no need to register/unregister again.
  1216. if (runtime.isInspecting(parent)) return join(context, recv, array).toString();
  1217. try {
  1218. runtime.registerInspecting(parent);
  1219. return join(context, recv, array).toString();
  1220. } finally {
  1221. runtime.unregisterInspecting(parent);
  1222. }
  1223. }
  1224. private static RubyString join(ThreadContext context, IRubyObject recv, RubyArray ary) {
  1225. IRubyObject[] args = ary.toJavaArray();
  1226. boolean isTainted = false;
  1227. StringBuilder buffer = new StringBuilder();
  1228. Ruby runtime = context.getRuntime();
  1229. for (int i = 0; i < args.length; i++) {
  1230. if (args[i].isTaint()) {
  1231. isTainted = true;
  1232. }
  1233. String element;
  1234. if (args[i] instanceof RubyString) {
  1235. element = args[i].convertToString().getUnicodeValue();
  1236. } else if (args[i] instanceof RubyArray) {
  1237. if (runtime.isInspecting(args[i])) {
  1238. throw runtime.newArgumentError("recursive array");
  1239. } else {
  1240. element = inspectJoin(context, recv, ary, ((RubyArray)args[i]));
  1241. }
  1242. } else {
  1243. RubyString path = get_path(context, args[i]);
  1244. element = path.getUnicodeValue();
  1245. }
  1246. chomp(buffer);
  1247. if (i > 0 && !element.startsWith("/") && !element.startsWith("\\")) {
  1248. buffer.append("/");
  1249. }
  1250. buffer.append(element);
  1251. }
  1252. RubyString fixedStr = RubyString.newString(runtime, buffer.toString());
  1253. fixedStr.setTaint(isTainted);
  1254. return fixedStr;
  1255. }
  1256. /*
  1257. * Fixme: This does not have exact same semantics as RubyArray.join, but they
  1258. * probably could be consolidated (perhaps as join(args[], sep, doChomp)).
  1259. */
  1260. @JRubyMethod(rest = true, meta = true)
  1261. public static RubyString join(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  1262. return join(context, recv, RubyArray.newArrayNoCopyLight(context.getRuntime(), args));
  1263. }
  1264. private static void chomp(StringBuilder buffer) {
  1265. int lastIndex = buffer.length() - 1;
  1266. while (lastIndex >= 0 && (buffer.lastIndexOf("/") == lastIndex || buffer.lastIndexOf("\\") == lastIndex)) {
  1267. buffer.setLength(lastIndex);
  1268. lastIndex--;
  1269. }
  1270. }
  1271. @JRubyMethod(name = "lstat", required = 1, meta = true)
  1272. public static IRubyObject lstat(ThreadContext context, IRubyObject recv, IRubyObject filename) {
  1273. String f = get_path(context, filename).getUnicodeValue();
  1274. return context.getRuntime().newFileStat(f, true);
  1275. }
  1276. @JRubyMethod(name = "stat", required = 1, meta = true)
  1277. public static IRubyObject stat(ThreadContext context, IRubyObject recv, IRubyObject filename) {
  1278. String f = get_path(context, filename).getUnicodeValue();
  1279. return context.getRuntime().newFileStat(f, false);
  1280. }
  1281. @JRubyMethod(name = "atime", required = 1, meta = true)
  1282. public static IRubyObject atime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
  1283. String f = get_path(context, filename).getUnicodeValue();
  1284. return context.getRuntime().newFileStat(f, false).atime();
  1285. }
  1286. @JRubyMethod(name = "ctime", required = 1, meta = true)
  1287. public static IRubyObject ctime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
  1288. String f = get_path(context, filename).getUnicodeValue();
  1289. return context.getRuntime().newFileStat(f, false).ctime();
  1290. }
  1291. @JRubyMethod(required = 2, rest = true, meta = true)
  1292. public static IRubyObject lchmod(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  1293. Ruby runtime = context.getRuntime();
  1294. int count = 0;
  1295. RubyInteger mode = args[0].convertToInteger();
  1296. for (int i = 1; i < args.length; i++) {
  1297. RubyString filename = get_path(context, args[i]);
  1298. if (!RubyFileTest.exist_p(filename, filename).isTrue()) {
  1299. throw runtime.newErrnoENOENTError(filename.toString());
  1300. }
  1301. boolean result = 0 == runtime.getPosix().lchmod(filename.getUnicodeValue(), (int)mode.getLongValue());
  1302. if (result) {
  1303. count++;
  1304. }
  1305. }
  1306. return runtime.newFixnum(count);
  1307. }
  1308. @JRubyMethod(required = 2, rest = true, meta = true)
  1309. public static IRubyObject lchown(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  1310. Ruby runtime = context.getRuntime();
  1311. int owner = !args[0].isNil() ? RubyNumeric.num2int(args[0]) : -1;
  1312. int group = !args[1].isNil() ? RubyNumeric.num2int(args[1]) : -1;
  1313. int count = 0;
  1314. for (int i = 2; i < args.length; i++) {
  1315. IRubyObject filename = args[i];
  1316. if (0 != runtime.getPosix().lchown(filename.toString(), owner, group)) {
  1317. throw runtime.newErrnoFromLastPOSIXErrno();
  1318. } else {
  1319. count++;
  1320. }
  1321. }
  1322. return runtime.newFixnum(count);
  1323. }
  1324. @JRubyMethod(required = 2, meta = true, backtrace = true)
  1325. public static IRubyObject link(ThreadContext context, IRubyObject recv, IRubyObject from, IRubyObject to) {
  1326. Ruby runtime = context.getRuntime();
  1327. RubyString fromStr = RubyString.stringValue(from);
  1328. RubyString toStr = RubyString.stringValue(to);
  1329. int ret = runtime.getPosix().link(fromStr.getUnicodeValue(), toStr.getUnicodeValue());
  1330. if (ret != 0) {
  1331. // In most cases, when there is an error during the call,
  1332. // the POSIX handler throws an exception, but not in case
  1333. // with pure Java POSIX layer (when native support is disabled),
  1334. // so we deal with it like this:
  1335. throw runtime.newErrnoEEXISTError(fromStr + " or " + toStr);
  1336. }
  1337. return runtime.newFixnum(ret);
  1338. }
  1339. @JRubyMethod(name = "mtime", required = 1, meta = true)
  1340. public static IRubyObject mtime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
  1341. return getLastModified(context.getRuntime(), get_path(context, filename).getUnicodeValue());
  1342. }
  1343. @JRubyMethod(required = 2, meta = true)
  1344. public static IRubyObject rename(ThreadContext context, IRubyObject recv, IRubyObject oldName, IRubyObject newName) {
  1345. Ruby runtime = context.getRuntime();
  1346. RubyString oldNameString = RubyString.stringValue(oldName);
  1347. RubyString newNameString = RubyString.stringValue(newName);
  1348. runtime.checkSafeString(oldNameString);
  1349. runtime.checkSafeString(newNameString);
  1350. String newNameJavaString = newNameString.getUnicodeValue();
  1351. String oldNameJavaString = oldNameString.getUnicodeValue();
  1352. JRubyFile oldFile = JRubyFile.create(runtime.getCurrentDirectory(), oldNameJavaString);
  1353. JRubyFile newFile = JRubyFile.create(runtime.getCurrentDirectory(), newNameJavaString);
  1354. if (!oldFile.exists() || !newFile.getParentFile().exists()) {
  1355. throw runtime.newErrnoENOENTError(oldNameJavaString + " or " + newNameJavaString);
  1356. }
  1357. JRubyFile dest = JRubyFile.create(runtime.getCurrentDirectory(), newNameJavaString);
  1358. if (oldFile.renameTo(dest)) { // rename is successful
  1359. return RubyFixnum.zero(runtime);
  1360. }
  1361. // rename via Java API call wasn't successful, let's try some tricks, similar to MRI
  1362. if (newFile.exists()) {
  1363. runtime.getPosix().chmod(newNameJavaString, 0666);
  1364. newFile.delete();
  1365. }
  1366. if (oldFile.renameTo(dest)) { // try to rename one more time
  1367. return RubyFixnum.zero(runtime);
  1368. }
  1369. throw runtime.newErrnoEACCESError(oldNameJavaString + " or " + newNameJavaString);
  1370. }
  1371. @JRubyMethod(required = 1, meta = true)
  1372. public static RubyArray split(ThreadContext context, IRubyObject recv, IRubyObject arg) {
  1373. RubyString filename = get_path(context, arg);
  1374. return context.getRuntime().newArray(dirname(context, recv, filename),
  1375. basename(context, recv, new IRubyObject[] { filename }));
  1376. }
  1377. @JRubyMethod(required = 2, meta = true)
  1378. public static IRubyObject symlink(ThreadContext context, IRubyObject recv, IRubyObject from, IRubyObject to) {
  1379. Ruby runtime = context.getRuntime();
  1380. RubyString fromStr = get_path(context, from);
  1381. RubyString toStr = get_path(context, to);
  1382. String tovalue = toStr.getUnicodeValue();
  1383. tovalue = JRubyFile.create(runtime.getCurrentDirectory(), tovalue).getAbsolutePath();
  1384. try {
  1385. if (runtime.getPosix().symlink(
  1386. fromStr.getUnicodeValue(), tovalue) == -1) {
  1387. // FIXME: When we get JNA3 we need to properly write this to errno.
  1388. throw runtime.newErrnoEEXISTError(fromStr + " or " + toStr);
  1389. }
  1390. } catch (java.lang.UnsatisfiedLinkError ule) {
  1391. throw runtime.newNotImplementedError("symlink() function is unimplemented on this machine");
  1392. }
  1393. return runtime.newFixnum(0);
  1394. }
  1395. @JRubyMethod(required = 1, meta = true)
  1396. public static IRubyObject readlink(ThreadContext context, IRubyObject recv, IRubyObject path) {
  1397. Ruby runtime = context.getRuntime();
  1398. try {
  1399. String realPath = runtime.getPosix().readlink(path.convertToString().getUnicodeValue());
  1400. if (!RubyFileTest.exist_p(recv, path).isTrue()) {
  1401. throw runtime.newErrnoENOENTError(path.toString());
  1402. }
  1403. if (!RubyFileTest.symlink_p(recv, path).isTrue()) {
  1404. throw runtime.newErrnoEINVALError(path.toString());
  1405. }
  1406. if (realPath == null) {
  1407. //FIXME: When we get JNA3 we need to properly write this to errno.
  1408. }
  1409. return runtime.newString(realPath);
  1410. } catch (IOException e) {
  1411. throw runtime.newIOError(e.getMessage());
  1412. }
  1413. }
  1414. // Can we produce IOError which bypasses a close?
  1415. @JRubyMethod(required = 2, meta = true, compat = RUBY1_8)
  1416. public static IRubyObject truncate(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
  1417. return truncateCommon(context, recv, arg1, arg2);
  1418. }
  1419. @JRubyMethod(name = "truncate", required = 2, meta = true, compat = RUBY1_9)
  1420. public static IRubyObject truncate19(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
  1421. if (!(arg1 instanceof RubyString) && arg1.respondsTo("to_path")) {
  1422. arg1 = arg1.callMethod(context, "to_path");
  1423. }
  1424. return truncateCommon(context, recv, arg1, arg2);
  1425. }
  1426. private static IRubyObject truncateCommon(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
  1427. RubyString filename = arg1.convertToString(); // TODO: SafeStringValue here
  1428. Ruby runtime = context.getRuntime();
  1429. RubyInteger newLength = arg2.convertToInteger();
  1430. File testFile ;
  1431. File childFile = new File(filename.getUnicodeValue() );
  1432. if ( childFile.isAbsolute() ) {
  1433. testFile = childFile ;
  1434. } else {
  1435. testFile = new File(runtime.getCurrentDirectory(), filename.getByteList().toString());
  1436. }
  1437. if (!testFile.exists()) {
  1438. throw runtime.newErrnoENOENTError(filename.getByteList().toString());
  1439. }
  1440. if (newLength.getLongValue() < 0) {
  1441. throw runtime.newErrnoEINVALError(filename.toString());
  1442. }
  1443. IRubyObject[] args = new IRubyObject[] { filename, runtime.newString("r+") };
  1444. RubyFile file = (RubyFile) open(context, recv, args, Block.NULL_BLOCK);
  1445. file.truncate(context, newLength);
  1446. file.close();
  1447. return RubyFixnum.zero(runtime);
  1448. }
  1449. @JRubyMethod(meta = true, optional = 1)
  1450. public static IRubyObject umask(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  1451. Ruby runtime = context.getRuntime();
  1452. int oldMask = 0;
  1453. if (args.length == 0) {
  1454. oldMask = getUmaskSafe( runtime );
  1455. } else if (args.length == 1) {
  1456. int newMask = (int) args[0].convertToInteger().getLongValue();
  1457. synchronized (_umaskLock) {
  1458. oldMask = runtime.getPosix().umask(newMask);
  1459. _cachedUmask = newMask;
  1460. }
  1461. } else {
  1462. runtime.newArgumentError("wrong number of arguments");
  1463. }
  1464. return runtime.newFixnum(oldMask);
  1465. }
  1466. @JRubyMethod(required = 2, rest = true, meta = true)
  1467. public static IRubyObject utime(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  1468. Ruby runtime = context.getRuntime();
  1469. long[] atimeval = null;
  1470. long[] mtimeval = null;
  1471. if (args[0] != runtime.getNil() || args[1] != runtime.getNil()) {
  1472. atimeval = extractTimeval(runtime, args[0]);
  1473. mtimeval = extractTimeval(runtime, args[1]);
  1474. }
  1475. for (int i = 2, j = args.length; i < j; i++) {
  1476. RubyString filename = get_path(context, args[i]);
  1477. runtime.checkSafeString(filename);
  1478. JRubyFile fileToTouch = JRubyFile.create(runtime.getCurrentDirectory(),filename.getUnicodeValue());
  1479. if (!fileToTouch.exists()) {
  1480. throw runtime.newErrnoENOENTError(filename.toString());
  1481. }
  1482. runtime.getPosix().utimes(fileToTouch.getAbsolutePath(), atimeval, mtimeval);
  1483. }
  1484. return runtime.newFixnum(args.length - 2);
  1485. }
  1486. @JRubyMethod(name = {"unlink", "delete"}, rest = true, meta = true)
  1487. public static IRubyObject unlink(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  1488. Ruby runtime = context.getRuntime();
  1489. for (int i = 0; i < args.length; i++) {
  1490. RubyString filename = get_path(context, args[i]);
  1491. runtime.checkSafeString(filename);
  1492. JRubyFile lToDelete = JRubyFile.create(runtime.getCurrentDirectory(), filename.getUnicodeValue());
  1493. boolean isSymlink = RubyFileTest.symlink_p(recv, filename).isTrue();
  1494. // Broken symlinks considered by exists() as non-existing,
  1495. // so we need to check for symlinks explicitly.
  1496. if (!lToDelete.exists() && !isSymlink) {
  1497. throw runtime.newErrnoENOENTError(filename.getUnicodeValue());
  1498. }
  1499. if (lToDelete.isDirectory() && !isSymlink) {
  1500. throw runtime.newErrnoEPERMError(filename.getUnicodeValue());
  1501. }
  1502. if (!lToDelete.delete()) {
  1503. throw runtime.newErrnoEACCESError(filename.getUnicodeValue());
  1504. }
  1505. }
  1506. return runtime.newFixnum(args.length);
  1507. }
  1508. @JRubyMethod(name = "size", backtrace = true, compat = RUBY1_9)
  1509. public IRubyObject size(ThreadContext context) {
  1510. Ruby runtime = context.getRuntime();
  1511. if ((openFile.getMode() & OpenFile.WRITABLE) != 0) {
  1512. flush();
  1513. }
  1514. FileStat stat = runtime.getPosix().fstat(
  1515. getOpenFileChecked().getMainStream().getDescriptor().getFileDescriptor());
  1516. if (stat == null) {
  1517. throw runtime.newErrnoEACCESError(path);
  1518. }
  1519. return runtime.newFixnum(stat.st_size());
  1520. }
  1521. public static ZipEntry getFileEntry(ZipFile zf, String path) throws IOException {
  1522. ZipEntry entry = zf.getEntry(path);
  1523. if (entry == null) {
  1524. // try canonicalizing the path to eliminate . and .. (JRUBY-4760, JRUBY-4879)
  1525. String prefix = new File(".").getCanonicalPath();
  1526. entry = zf.getEntry(new File(path).getCanonicalPath().substring(prefix.length() + 1).replaceAll("\\\\", "/"));
  1527. }
  1528. return entry;
  1529. }
  1530. public static ZipEntry getDirOrFileEntry(ZipFile zf, String path) throws IOException {
  1531. ZipEntry entry = zf.getEntry(path + "/"); // first try as directory
  1532. if (entry == null) {
  1533. // try canonicalizing the path to eliminate . and .. (JRUBY-4760, JRUBY-4879)
  1534. String prefix = new File(".").getCanonicalPath();
  1535. entry = zf.getEntry(new File(path + "/").getCanonicalPath().substring(prefix.length() + 1).replaceAll("\\\\", "/"));
  1536. if (entry == null) {
  1537. // try as file
  1538. entry = getFileEntry(zf, path);
  1539. }
  1540. }
  1541. return entry;
  1542. }
  1543. /**
  1544. * Joy of POSIX, only way to get the umask is to set the umask,
  1545. * then set it back. That's unsafe in a threaded program. We
  1546. * minimize but may not totally remove this race by caching the
  1547. * obtained or previously set (see umask() above) umask and using
  1548. * that as the initial set value which, cross fingers, is a
  1549. * no-op. The cache access is then synchronized. TODO: Better?
  1550. */
  1551. private static int getUmaskSafe( Ruby runtime ) {
  1552. synchronized (_umaskLock) {
  1553. final int umask = runtime.getPosix().umask(_cachedUmask);
  1554. if (_cachedUmask != umask ) {
  1555. runtime.getPosix().umask(umask);
  1556. _cachedUmask = umask;
  1557. }
  1558. return umask;
  1559. }
  1560. }
  1561. /**
  1562. * Extract a timeval (an array of 2 longs: seconds and microseconds from epoch) from
  1563. * an IRubyObject.
  1564. */
  1565. private static long[] extractTimeval(Ruby runtime, IRubyObject value) {
  1566. long[] timeval = new long[2];
  1567. if (value instanceof RubyFloat) {
  1568. timeval[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(value) : RubyNumeric.num2long(value);
  1569. double fraction = ((RubyFloat) value).getDoubleValue() % 1.0;
  1570. timeval[1] = (long)(fraction * 1e6 + 0.5);
  1571. } else if (value instanceof RubyNumeric) {
  1572. timeval[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(value) : RubyNumeric.num2long(value);
  1573. timeval[1] = 0;
  1574. } else {
  1575. RubyTime time;
  1576. if (value instanceof RubyTime) {
  1577. time = ((RubyTime) value);
  1578. } else {
  1579. time = (RubyTime) TypeConverter.convertToType(value, runtime.getTime(), "to_time", true);
  1580. }
  1581. timeval[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(time.to_i()) : RubyNumeric.num2long(time.to_i());
  1582. timeval[1] = Platform.IS_32_BIT ? RubyNumeric.num2int(time.usec()) : RubyNumeric.num2long(time.usec());
  1583. }
  1584. return timeval;
  1585. }
  1586. // Fast path since JNA stat is about 10x slower than this
  1587. private static IRubyObject getLastModified(Ruby runtime, String path) {
  1588. JRubyFile file = JRubyFile.create(runtime.getCurrentDirectory(), path);
  1589. if (!file.exists()) {
  1590. throw runtime.newErrnoENOENTError(path);
  1591. }
  1592. return runtime.newTime(file.lastModified());
  1593. }
  1594. private void checkClosed(ThreadContext context) {
  1595. openFile.checkClosed(context.getRuntime());
  1596. }
  1597. }