diff options
Diffstat (limited to 'dir.c')
| -rw-r--r-- | dir.c | 1432 |
1 files changed, 1045 insertions, 387 deletions
@@ -22,10 +22,6 @@ #include <unistd.h> #endif -#ifndef O_CLOEXEC -# define O_CLOEXEC 0 -#endif - #ifndef USE_OPENDIR_AT # if defined(HAVE_FDOPENDIR) && defined(HAVE_DIRFD) && \ defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) @@ -35,8 +31,12 @@ # endif #endif -#if USE_OPENDIR_AT -# include <fcntl.h> +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif + +#ifndef O_CLOEXEC +# define O_CLOEXEC 0 #endif #undef HAVE_DIRENT_NAMLEN @@ -113,6 +113,7 @@ char *strchr(char*,char); #include "internal/gc.h" #include "internal/io.h" #include "internal/object.h" +#include "internal/imemo.h" #include "internal/vm.h" #include "ruby/encoding.h" #include "ruby/ruby.h" @@ -142,6 +143,50 @@ char *strchr(char*,char); # define IS_WIN32 0 #endif +#ifdef HAVE_GETATTRLIST +struct getattrlist_args { + const char *path; + int fd; + struct attrlist *list; + void *buf; + size_t size; + unsigned int options; +}; + +# define GETATTRLIST_ARGS(list_, buf_, options_) (struct getattrlist_args) \ + {.list = list_, .buf = buf_, .size = sizeof(buf_), .options = options_} + +static void * +nogvl_getattrlist(void *args) +{ + struct getattrlist_args *arg = args; + return (void *)(VALUE)getattrlist(arg->path, arg->list, arg->buf, arg->size, arg->options); +} + +static int +gvl_getattrlist(struct getattrlist_args *args, const char *path) +{ + args->path = path; + return IO_WITHOUT_GVL_INT(nogvl_getattrlist, args); +} + +# ifdef HAVE_FGETATTRLIST +static void * +nogvl_fgetattrlist(void *args) +{ + struct getattrlist_args *arg = args; + return (void *)(VALUE)fgetattrlist(arg->fd, arg->list, arg->buf, arg->size, arg->options); +} + +static int +gvl_fgetattrlist(struct getattrlist_args *args, int fd) +{ + args->fd = fd; + return IO_WITHOUT_GVL_INT(nogvl_fgetattrlist, args); +} +# endif +#endif + #if NORMALIZE_UTF8PATH # if defined HAVE_FGETATTRLIST || !defined HAVE_GETATTRLIST # define need_normalization(dirp, path) need_normalization(dirp) @@ -154,10 +199,11 @@ need_normalization(DIR *dirp, const char *path) # if defined HAVE_FGETATTRLIST || defined HAVE_GETATTRLIST u_int32_t attrbuf[SIZEUP32(fsobj_tag_t)]; struct attrlist al = {ATTR_BIT_MAP_COUNT, 0, ATTR_CMN_OBJTAG,}; + struct getattrlist_args args = GETATTRLIST_ARGS(&al, attrbuf, 0); # if defined HAVE_FGETATTRLIST - int ret = fgetattrlist(dirfd(dirp), &al, attrbuf, sizeof(attrbuf), 0); + int ret = gvl_fgetattrlist(&args, dirfd(dirp)); # else - int ret = getattrlist(path, &al, attrbuf, sizeof(attrbuf), 0); + int ret = gvl_getattrlist(&args, path); # endif if (!ret) { const fsobj_tag_t *tag = (void *)(attrbuf+1); @@ -458,6 +504,20 @@ fnmatch( } VALUE rb_cDir; +static VALUE sym_directory, sym_link, sym_file, sym_unknown; + +#if defined(DT_BLK) || defined(S_IFBLK) +static VALUE sym_block_device; +#endif +#if defined(DT_CHR) || defined(S_IFCHR) +static VALUE sym_character_device; +#endif +#if defined(DT_FIFO) || defined(S_IFIFO) +static VALUE sym_fifo; +#endif +#if defined(DT_SOCK) || defined(S_IFSOCK) +static VALUE sym_socket; +#endif struct dir_data { DIR *dir; @@ -466,31 +526,26 @@ struct dir_data { }; static void -dir_mark(void *ptr) -{ - struct dir_data *dir = ptr; - rb_gc_mark(dir->path); -} - -static void dir_free(void *ptr) { struct dir_data *dir = ptr; if (dir->dir) closedir(dir->dir); - xfree(dir); } -static size_t -dir_memsize(const void *ptr) -{ - return sizeof(struct dir_data); -} +RUBY_REFERENCES(dir_refs) = { + RUBY_REF_EDGE(struct dir_data, path), + RUBY_REF_END +}; static const rb_data_type_t dir_data_type = { "dir", - {dir_mark, dir_free, dir_memsize,}, - 0, 0, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY + { + RUBY_REFS_LIST_PTR(dir_refs), + dir_free, + NULL, // Nothing allocated externally, so don't need a memsize function + }, + 0, NULL, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_DECL_MARKING | RUBY_TYPED_EMBEDDABLE }; static VALUE dir_close(VALUE); @@ -513,7 +568,7 @@ nogvl_opendir(void *ptr) { const char *path = ptr; - return (void *)opendir(path); + return opendir(path); } static DIR * @@ -524,12 +579,31 @@ opendir_without_gvl(const char *path) u.in = path; - return rb_thread_call_without_gvl(nogvl_opendir, u.out, RUBY_UBF_IO, 0); + return IO_WITHOUT_GVL(nogvl_opendir, u.out); } else return opendir(path); } +static void +close_dir_data(struct dir_data *dp) +{ + if (dp->dir) { + if (closedir(dp->dir) < 0) { + dp->dir = NULL; + rb_sys_fail("closedir"); + } + dp->dir = NULL; + } +} + +static void +check_closedir(DIR *dirp) +{ + if (closedir(dirp) < 0) + rb_sys_fail("closedir"); +} + static VALUE dir_initialize(rb_execution_context_t *ec, VALUE dir, VALUE dirname, VALUE enc) { @@ -544,8 +618,7 @@ dir_initialize(rb_execution_context_t *ec, VALUE dir, VALUE dirname, VALUE enc) dirname = rb_str_dup_frozen(dirname); TypedData_Get_Struct(dir, struct dir_data, &dir_data_type, dp); - if (dp->dir) closedir(dp->dir); - dp->dir = NULL; + close_dir_data(dp); RB_OBJ_WRITE(dir, &dp->path, Qnil); dp->enc = fsenc; path = RSTRING_PTR(dirname); @@ -559,7 +632,8 @@ dir_initialize(rb_execution_context_t *ec, VALUE dir, VALUE dirname, VALUE enc) else if (e == EIO) { u_int32_t attrbuf[1]; struct attrlist al = {ATTR_BIT_MAP_COUNT, 0}; - if (getattrlist(path, &al, attrbuf, sizeof(attrbuf), FSOPT_NOFOLLOW) == 0) { + struct getattrlist_args args = GETATTRLIST_ARGS(&al, attrbuf, FSOPT_NOFOLLOW); + if (gvl_getattrlist(&args, path) == 0) { dp->dir = opendir_without_gvl(path); } } @@ -591,6 +665,51 @@ dir_s_close(rb_execution_context_t *ec, VALUE klass, VALUE dir) return dir_close(dir); } +# if defined(HAVE_FDOPENDIR) && defined(HAVE_DIRFD) +static void * +nogvl_fdopendir(void *fd) +{ + return fdopendir((int)(VALUE)fd); +} + +/* + * call-seq: + * Dir.for_fd(fd) -> dir + * + * Returns a new \Dir object representing the directory specified by the given + * integer directory file descriptor +fd+: + * + * d0 = Dir.new('..') + * d1 = Dir.for_fd(d0.fileno) + * + * Note that the returned +d1+ does not have an associated path: + * + * d0.path # => '..' + * d1.path # => nil + * + * This method uses the + * {fdopendir()}[https://www.man7.org/linux/man-pages/man3/fdopendir.3p.html] + * function defined by POSIX 2008; + * the method is not implemented on non-POSIX platforms (raises NotImplementedError). + */ +static VALUE +dir_s_for_fd(VALUE klass, VALUE fd) +{ + struct dir_data *dp; + VALUE dir = TypedData_Make_Struct(klass, struct dir_data, &dir_data_type, dp); + + if (!(dp->dir = IO_WITHOUT_GVL(nogvl_fdopendir, (void *)(VALUE)NUM2INT(fd)))) { + rb_sys_fail("fdopendir"); + UNREACHABLE_RETURN(Qnil); + } + + RB_OBJ_WRITE(dir, &dp->path, Qnil); + return dir; +} +#else +#define dir_s_for_fd rb_f_notimplement +#endif + NORETURN(static void dir_closed(void)); static void @@ -618,10 +737,13 @@ dir_check(VALUE dir) /* - * call-seq: - * dir.inspect -> string + * call-seq: + * inspect -> string + * + * Returns a string description of +self+: + * + * Dir.new('example').inspect # => "#<Dir:example>" * - * Return a string describing this Dir object. */ static VALUE dir_inspect(VALUE dir) @@ -655,18 +777,18 @@ dir_inspect(VALUE dir) #ifdef HAVE_DIRFD /* - * call-seq: - * dir.fileno -> integer - * - * Returns the file descriptor used in <em>dir</em>. + * call-seq: + * fileno -> integer * - * d = Dir.new("..") - * d.fileno #=> 8 + * Returns the file descriptor used in <em>dir</em>. * - * This method uses dirfd() function defined by POSIX 2008. - * NotImplementedError is raised on other platforms, such as Windows, - * which doesn't provide the function. + * d = Dir.new('..') + * d.fileno # => 8 * + * This method uses the + * {dirfd()}[https://www.man7.org/linux/man-pages/man3/dirfd.3.html] + * function defined by POSIX 2008; + * the method is not implemented on non-POSIX platforms (raises NotImplementedError). */ static VALUE dir_fileno(VALUE dir) @@ -685,14 +807,14 @@ dir_fileno(VALUE dir) #endif /* - * call-seq: - * dir.path -> string or nil - * dir.to_path -> string or nil + * call-seq: + * path -> string or nil * - * Returns the path parameter passed to <em>dir</em>'s constructor. + * Returns the +dirpath+ string that was used to create +self+ + * (or +nil+ if created by method Dir.for_fd): + * + * Dir.new('example').path # => "example" * - * d = Dir.new("..") - * d.path #=> ".." */ static VALUE dir_path(VALUE dir) @@ -718,8 +840,28 @@ fundamental_encoding_p(rb_encoding *enc) } } # define READDIR(dir, enc) rb_w32_readdir((dir), (enc)) +# define READDIR_NOGVL READDIR #else -# define READDIR(dir, enc) readdir((dir)) +NORETURN(static void *sys_failure(void *function)); +static void * +sys_failure(void *function) +{ + rb_sys_fail(function); +} + +static void * +nogvl_readdir(void *dir) +{ + rb_errno_set(0); + if ((dir = readdir(dir)) == NULL) { + if (rb_errno()) + rb_thread_call_with_gvl(sys_failure, (void *)"readdir"); + } + return dir; +} + +# define READDIR(dir, enc) IO_WITHOUT_GVL(nogvl_readdir, (void *)(dir)) +# define READDIR_NOGVL(dir, enc) nogvl_readdir((dir)) #endif /* safe to use without GVL */ @@ -746,16 +888,18 @@ to_be_skipped(const struct dirent *dp) } /* - * call-seq: - * dir.read -> string or nil + * call-seq: + * read -> string or nil + * + * Reads and returns the next entry name from +self+; + * returns +nil+ if at end-of-stream; + * see {Dir As Stream-Like}[rdoc-ref:Dir@Dir+As+Stream-Like]: * - * Reads the next entry from <em>dir</em> and returns it as a string. - * Returns <code>nil</code> at the end of the stream. + * dir = Dir.new('example') + * dir.read # => "." + * dir.read # => ".." + * dir.read # => "config.h" * - * d = Dir.new("testdir") - * d.read #=> "." - * d.read #=> ".." - * d.read #=> "config.h" */ static VALUE dir_read(VALUE dir) @@ -764,7 +908,7 @@ dir_read(VALUE dir) struct dirent *dp; GetDIR(dir, dirp); - errno = 0; + rb_errno_set(0); if ((dp = READDIR(dirp->dir, dirp->enc)) != NULL) { return rb_external_str_new_with_enc(dp->d_name, NAMLEN(dp), dirp->enc); } @@ -775,33 +919,127 @@ dir_read(VALUE dir) } } -static VALUE dir_each_entry(VALUE, VALUE (*)(VALUE, VALUE), VALUE, int); +struct dir_entry_args { + struct dir_data *dirp; + struct dirent *dp; +}; + +static VALUE dir_each_entry(VALUE, VALUE (*)(VALUE, VALUE, struct dir_entry_args *), VALUE, int); static VALUE -dir_yield(VALUE arg, VALUE path) +dir_yield(VALUE arg, VALUE path, struct dir_entry_args *_unused) { return rb_yield(path); } +static int do_lstat(int fd, const char *path, struct stat *pst, int flags, rb_encoding *enc); + +static VALUE +dir_yield_with_type(VALUE arg, VALUE path, struct dir_entry_args *dir_entry) +{ + VALUE type; + switch (dir_entry->dp->d_type) { +#ifdef DT_BLK + case DT_BLK: + type = sym_block_device; + break; +#endif +#ifdef DT_CHR + case DT_CHR: + type = sym_character_device; + break; +#endif + case DT_DIR: + type = sym_directory; + break; +#ifdef DT_FIFO + case DT_FIFO: + type = sym_fifo; + break; +#endif + case DT_LNK: + type = sym_link; + break; + case DT_REG: + type = sym_file; + break; +#ifdef DT_SOCK + case DT_SOCK: + type = sym_socket; + break; +#endif + default: + type = sym_unknown; + break; + } + +#ifdef HAVE_DIRFD + if (RUBY_DEBUG || RB_UNLIKELY(type == sym_unknown)) { + struct stat st; + if (do_lstat(dirfd(dir_entry->dirp->dir), dir_entry->dp->d_name, &st, 0, rb_filesystem_encoding()) == 0) { + switch (st.st_mode & S_IFMT) { + case S_IFDIR: + type = sym_directory; + break; + case S_IFLNK: + type = sym_link; + break; + case S_IFREG: + type = sym_file; + break; +#ifdef S_IFSOCK + case S_IFSOCK: + type = sym_socket; + break; +#endif +#ifdef S_IFIFO + case S_IFIFO: + type = sym_fifo; + break; +#endif +#ifdef S_IFBLK + case S_IFBLK: + type = sym_block_device; + break; +#endif +#ifdef S_IFCHR + case S_IFCHR: + type = sym_character_device; + break; +#endif + default: + break; + } + } + } +#endif // HAVE_DIRFD + + if (NIL_P(arg)) { + return rb_yield_values(2, path, type); + } + else { + return rb_ary_push(arg, rb_assoc_new(path, type)); + } +} + /* - * call-seq: - * dir.each { |filename| block } -> dir - * dir.each -> an_enumerator + * call-seq: + * each {|entry_name| ... } -> self * - * Calls the block once for each entry in this directory, passing the - * filename of each entry as a parameter to the block. + * Calls the block with each entry name in +self+: * - * If no block is given, an enumerator is returned instead. + * Dir.new('example').each {|entry_name| p entry_name } * - * d = Dir.new("testdir") - * d.each {|x| puts "Got #{x}" } + * Output: + + * "." + * ".." + * "config.h" + * "lib" + * "main.rb" * - * <em>produces:</em> + * With no block given, returns an Enumerator. * - * Got . - * Got .. - * Got config.h - * Got main.rb */ static VALUE dir_each(VALUE dir) @@ -811,7 +1049,7 @@ dir_each(VALUE dir) } static VALUE -dir_each_entry(VALUE dir, VALUE (*each)(VALUE, VALUE), VALUE arg, int children_only) +dir_each_entry(VALUE dir, VALUE (*each)(VALUE, VALUE, struct dir_entry_args *), VALUE arg, int children_only) { struct dir_data *dirp; struct dirent *dp; @@ -837,23 +1075,28 @@ dir_each_entry(VALUE dir, VALUE (*each)(VALUE, VALUE), VALUE arg, int children_o else #endif path = rb_external_str_new_with_enc(name, namlen, dirp->enc); - (*each)(arg, path); + struct dir_entry_args each_args = { + .dirp = dirp, + .dp = dp, + }; + (*each)(arg, path, &each_args); } return dir; } #ifdef HAVE_TELLDIR /* - * call-seq: - * dir.pos -> integer - * dir.tell -> integer + * call-seq: + * tell -> integer + * + * Returns the current position of +self+; + * see {Dir As Stream-Like}[rdoc-ref:Dir@Dir+As+Stream-Like]: * - * Returns the current position in <em>dir</em>. See also Dir#seek. + * dir = Dir.new('example') + * dir.tell # => 0 + * dir.read # => "." + * dir.tell # => 1 * - * d = Dir.new("testdir") - * d.tell #=> 0 - * d.read #=> "." - * d.tell #=> 12 */ static VALUE dir_tell(VALUE dir) @@ -862,7 +1105,8 @@ dir_tell(VALUE dir) long pos; GetDIR(dir, dirp); - pos = telldir(dirp->dir); + if((pos = telldir(dirp->dir)) < 0) + rb_sys_fail("telldir"); return rb_int2inum(pos); } #else @@ -871,18 +1115,24 @@ dir_tell(VALUE dir) #ifdef HAVE_SEEKDIR /* - * call-seq: - * dir.seek( integer ) -> dir - * - * Seeks to a particular location in <em>dir</em>. <i>integer</i> - * must be a value returned by Dir#tell. - * - * d = Dir.new("testdir") #=> #<Dir:0x401b3c40> - * d.read #=> "." - * i = d.tell #=> 12 - * d.read #=> ".." - * d.seek(i) #=> #<Dir:0x401b3c40> - * d.read #=> ".." + * call-seq: + * seek(position) -> self + * + * Sets the position in +self+ and returns +self+. + * The value of +position+ should have been returned from an earlier call to #tell; + * if not, the return values from subsequent calls to #read are unspecified. + * + * See {Dir As Stream-Like}[rdoc-ref:Dir@Dir+As+Stream-Like]. + * + * Examples: + * + * dir = Dir.new('example') + * dir.pos # => 0 + * dir.seek(3) # => #<Dir:example> + * dir.pos # => 3 + * dir.seek(30) # => #<Dir:example> + * dir.pos # => 5 + * */ static VALUE dir_seek(VALUE dir, VALUE pos) @@ -900,17 +1150,24 @@ dir_seek(VALUE dir, VALUE pos) #ifdef HAVE_SEEKDIR /* - * call-seq: - * dir.pos = integer -> integer + * call-seq: + * pos = position -> integer * - * Synonym for Dir#seek, but returns the position parameter. + * Sets the position in +self+ and returns +position+. + * The value of +position+ should have been returned from an earlier call to #tell; + * if not, the return values from subsequent calls to #read are unspecified. + * + * See {Dir As Stream-Like}[rdoc-ref:Dir@Dir+As+Stream-Like]. + * + * Examples: + * + * dir = Dir.new('example') + * dir.pos # => 0 + * dir.pos = 3 # => 3 + * dir.pos # => 3 + * dir.pos = 30 # => 30 + * dir.pos # => 5 * - * d = Dir.new("testdir") #=> #<Dir:0x401b3c40> - * d.read #=> "." - * i = d.pos #=> 12 - * d.read #=> ".." - * d.pos = i #=> 12 - * d.read #=> ".." */ static VALUE dir_set_pos(VALUE dir, VALUE pos) @@ -923,15 +1180,19 @@ dir_set_pos(VALUE dir, VALUE pos) #endif /* - * call-seq: - * dir.rewind -> dir + * call-seq: + * rewind -> self * - * Repositions <em>dir</em> to the first entry. + * Sets the position in +self+ to zero; + * see {Dir As Stream-Like}[rdoc-ref:Dir@Dir+As+Stream-Like]: + * + * dir = Dir.new('example') + * dir.read # => "." + * dir.read # => ".." + * dir.pos # => 2 + * dir.rewind # => #<Dir:example> + * dir.pos # => 0 * - * d = Dir.new("testdir") - * d.read #=> "." - * d.rewind #=> #<Dir:0x401b3fb0> - * d.read #=> "." */ static VALUE dir_rewind(VALUE dir) @@ -944,14 +1205,18 @@ dir_rewind(VALUE dir) } /* - * call-seq: - * dir.close -> nil + * call-seq: + * close -> nil + * + * Closes the stream in +self+, if it is open, and returns +nil+; + * ignored if +self+ is already closed: * - * Closes the directory stream. - * Calling this method on closed Dir object is ignored since Ruby 2.3. + * dir = Dir.new('example') + * dir.read # => "." + * dir.close # => nil + * dir.close # => nil + * dir.read # Raises IOError. * - * d = Dir.new("testdir") - * d.close #=> nil */ static VALUE dir_close(VALUE dir) @@ -960,8 +1225,7 @@ dir_close(VALUE dir) dirp = dir_get(dir); if (!dirp->dir) return Qnil; - closedir(dirp->dir); - dirp->dir = NULL; + close_dir_data(dirp); return Qnil; } @@ -975,30 +1239,80 @@ nogvl_chdir(void *ptr) } static void -dir_chdir(VALUE path) +dir_chdir0(VALUE path) { - if (chdir(RSTRING_PTR(path)) < 0) + if (IO_WITHOUT_GVL_INT(nogvl_chdir, (void*)RSTRING_PTR(path)) < 0) rb_sys_fail_path(path); } -static int chdir_blocking = 0; -static VALUE chdir_thread = Qnil; +static struct { + VALUE thread; + VALUE path; + int line; + int blocking; +} chdir_lock = { + .blocking = 0, .thread = Qnil, + .path = Qnil, .line = 0, +}; + +static void +chdir_enter(void) +{ + if (chdir_lock.blocking == 0) { + chdir_lock.path = rb_source_location(&chdir_lock.line); + } + chdir_lock.blocking++; + if (NIL_P(chdir_lock.thread)) { + chdir_lock.thread = rb_thread_current(); + } +} + +static void +chdir_leave(void) +{ + chdir_lock.blocking--; + if (chdir_lock.blocking == 0) { + chdir_lock.thread = Qnil; + chdir_lock.path = Qnil; + chdir_lock.line = 0; + } +} + +static int +chdir_alone_block_p(void) +{ + int block_given = rb_block_given_p(); + if (chdir_lock.blocking > 0) { + if (rb_thread_current() != chdir_lock.thread) + rb_raise(rb_eRuntimeError, "conflicting chdir during another chdir block"); + if (!block_given) { + if (!NIL_P(chdir_lock.path)) { + rb_warn("conflicting chdir during another chdir block\n" + "%" PRIsVALUE ":%d: note: previous chdir was here", + chdir_lock.path, chdir_lock.line); + } + else { + rb_warn("conflicting chdir during another chdir block"); + } + } + } + return block_given; +} struct chdir_data { VALUE old_path, new_path; int done; + bool yield_path; }; static VALUE chdir_yield(VALUE v) { struct chdir_data *args = (void *)v; - dir_chdir(args->new_path); + dir_chdir0(args->new_path); args->done = TRUE; - chdir_blocking++; - if (NIL_P(chdir_thread)) - chdir_thread = rb_thread_current(); - return rb_yield(args->new_path); + chdir_enter(); + return args->yield_path ? rb_yield(args->new_path) : rb_yield_values2(0, NULL); } static VALUE @@ -1006,53 +1320,96 @@ chdir_restore(VALUE v) { struct chdir_data *args = (void *)v; if (args->done) { - chdir_blocking--; - if (chdir_blocking == 0) - chdir_thread = Qnil; - dir_chdir(args->old_path); + chdir_leave(); + dir_chdir0(args->old_path); } return Qnil; } +static VALUE +chdir_path(VALUE path, bool yield_path) +{ + if (chdir_alone_block_p()) { + struct chdir_data args; + + args.old_path = rb_str_encode_ospath(rb_dir_getwd()); + args.new_path = path; + args.done = FALSE; + args.yield_path = yield_path; + return rb_ensure(chdir_yield, (VALUE)&args, chdir_restore, (VALUE)&args); + } + else { + char *p = RSTRING_PTR(path); + int r = IO_WITHOUT_GVL_INT(nogvl_chdir, p); + if (r < 0) + rb_sys_fail_path(path); + } + + return INT2FIX(0); +} + /* - * call-seq: - * Dir.chdir( [ string] ) -> 0 - * Dir.chdir( [ string] ) {| path | block } -> anObject - * - * Changes the current working directory of the process to the given - * string. When called without an argument, changes the directory to - * the value of the environment variable <code>HOME</code>, or - * <code>LOGDIR</code>. SystemCallError (probably Errno::ENOENT) if - * the target directory does not exist. - * - * If a block is given, it is passed the name of the new current - * directory, and the block is executed with that as the current - * directory. The original working directory is restored when the block - * exits. The return value of <code>chdir</code> is the value of the - * block. <code>chdir</code> blocks can be nested, but in a - * multi-threaded program an error will be raised if a thread attempts - * to open a <code>chdir</code> block while another thread has one - * open or a call to <code>chdir</code> without a block occurs inside - * a block passed to <code>chdir</code> (even in the same thread). - * - * Dir.chdir("/var/spool/mail") - * puts Dir.pwd - * Dir.chdir("/tmp") do - * puts Dir.pwd - * Dir.chdir("/usr") do - * puts Dir.pwd - * end - * puts Dir.pwd + * call-seq: + * Dir.chdir(new_dirpath) -> 0 + * Dir.chdir -> 0 + * Dir.chdir(new_dirpath) {|new_dirpath| ... } -> object + * Dir.chdir {|cur_dirpath| ... } -> object + * + * Changes the current working directory. + * + * With argument +new_dirpath+ and no block, + * changes to the given +dirpath+: + * + * Dir.pwd # => "/example" + * Dir.chdir('..') # => 0 + * Dir.pwd # => "/" + * + * With no argument and no block: + * + * - Changes to the value of environment variable +HOME+ if defined. + * - Otherwise changes to the value of environment variable +LOGDIR+ if defined. + * - Otherwise makes no change. + * + * With argument +new_dirpath+ and a block, temporarily changes the working directory: + * + * - Calls the block with the argument. + * - Changes to the given directory. + * - Executes the block (yielding the new path). + * - Restores the previous working directory. + * - Returns the block's return value. + * + * Example: + * + * Dir.chdir('/var/spool/mail') + * Dir.pwd # => "/var/spool/mail" + * Dir.chdir('/tmp') do + * Dir.pwd # => "/tmp" + * end + * Dir.pwd # => "/var/spool/mail" + * + * With no argument and a block, + * calls the block with the current working directory (string) + * and returns the block's return value. + * + * Calls to \Dir.chdir with blocks may be nested: + * + * Dir.chdir('/var/spool/mail') + * Dir.pwd # => "/var/spool/mail" + * Dir.chdir('/tmp') do + * Dir.pwd # => "/tmp" + * Dir.chdir('/usr') do + * Dir.pwd # => "/usr" * end - * puts Dir.pwd + * Dir.pwd # => "/tmp" + * end + * Dir.pwd # => "/var/spool/mail" * - * <em>produces:</em> + * In a multi-threaded program an error is raised if a thread attempts + * to open a +chdir+ block while another thread has one open, + * or a call to +chdir+ without a block occurs inside + * a block passed to +chdir+ (even in the same thread). * - * /var/spool/mail - * /tmp - * /usr - * /tmp - * /var/spool/mail + * Raises an exception if the target directory does not exist. */ static VALUE dir_s_chdir(int argc, VALUE *argv, VALUE obj) @@ -1071,54 +1428,212 @@ dir_s_chdir(int argc, VALUE *argv, VALUE obj) path = rb_str_new2(dist); } - if (chdir_blocking > 0) { - if (rb_thread_current() != chdir_thread) - rb_raise(rb_eRuntimeError, "conflicting chdir during another chdir block"); - if (!rb_block_given_p()) - rb_warn("conflicting chdir during another chdir block"); + return chdir_path(path, true); +} + +#if defined(HAVE_FCHDIR) && defined(HAVE_DIRFD) && HAVE_FCHDIR && HAVE_DIRFD +static void * +nogvl_fchdir(void *ptr) +{ + const int *fd = ptr; + + return (void *)(VALUE)fchdir(*fd); +} + +static void +dir_fchdir(int fd) +{ + if (IO_WITHOUT_GVL_INT(nogvl_fchdir, (void *)&fd) < 0) + rb_sys_fail("fchdir"); +} + +struct fchdir_data { + VALUE old_dir; + int fd; + int done; +}; + +static VALUE +fchdir_yield(VALUE v) +{ + struct fchdir_data *args = (void *)v; + dir_fchdir(args->fd); + args->done = TRUE; + chdir_enter(); + return rb_yield_values(0); +} + +static VALUE +fchdir_restore(VALUE v) +{ + struct fchdir_data *args = (void *)v; + if (args->done) { + chdir_leave(); + dir_fchdir(RB_NUM2INT(dir_fileno(args->old_dir))); } + dir_close(args->old_dir); + return Qnil; +} - if (rb_block_given_p()) { - struct chdir_data args; +/* + * call-seq: + * Dir.fchdir(fd) -> 0 + * Dir.fchdir(fd) { ... } -> object + * + * Changes the current working directory to the directory + * specified by the integer file descriptor +fd+. + * + * When passing a file descriptor over a UNIX socket or to a child process, + * using +fchdir+ instead of +chdir+ avoids the + * {time-of-check to time-of-use vulnerability}[https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use] + * + * With no block, changes to the directory given by +fd+: + * + * Dir.chdir('/var/spool/mail') + * Dir.pwd # => "/var/spool/mail" + * dir = Dir.new('/usr') + * fd = dir.fileno + * Dir.fchdir(fd) + * Dir.pwd # => "/usr" + * + * With a block, temporarily changes the working directory: + * + * - Calls the block with the argument. + * - Changes to the given directory. + * - Executes the block (yields no args). + * - Restores the previous working directory. + * - Returns the block's return value. + * + * Example: + * + * Dir.chdir('/var/spool/mail') + * Dir.pwd # => "/var/spool/mail" + * dir = Dir.new('/tmp') + * fd = dir.fileno + * Dir.fchdir(fd) do + * Dir.pwd # => "/tmp" + * end + * Dir.pwd # => "/var/spool/mail" + * + * This method uses the + * {fchdir()}[https://www.man7.org/linux/man-pages/man3/fchdir.3p.html] + * function defined by POSIX 2008; + * the method is not implemented on non-POSIX platforms (raises NotImplementedError). + * + * Raises an exception if the file descriptor is not valid. + * + * In a multi-threaded program an error is raised if a thread attempts + * to open a +chdir+ block while another thread has one open, + * or a call to +chdir+ without a block occurs inside + * a block passed to +chdir+ (even in the same thread). + */ +static VALUE +dir_s_fchdir(VALUE klass, VALUE fd_value) +{ + int fd = RB_NUM2INT(fd_value); - args.old_path = rb_str_encode_ospath(rb_dir_getwd()); - args.new_path = path; + if (chdir_alone_block_p()) { + struct fchdir_data args; + args.old_dir = dir_s_alloc(klass); + dir_initialize(NULL, args.old_dir, rb_fstring_cstr("."), Qnil); + args.fd = fd; args.done = FALSE; - return rb_ensure(chdir_yield, (VALUE)&args, chdir_restore, (VALUE)&args); + return rb_ensure(fchdir_yield, (VALUE)&args, fchdir_restore, (VALUE)&args); } else { - char *p = RSTRING_PTR(path); - int r = (int)(VALUE)rb_thread_call_without_gvl(nogvl_chdir, p, - RUBY_UBF_IO, 0); + int r = IO_WITHOUT_GVL_INT(nogvl_fchdir, &fd); if (r < 0) - rb_sys_fail_path(path); + rb_sys_fail("fchdir"); } return INT2FIX(0); } +#else +#define dir_s_fchdir rb_f_notimplement +#endif + +/* + * call-seq: + * chdir -> 0 + * chdir { ... } -> object + * + * Changes the current working directory to +self+: + * + * Dir.pwd # => "/" + * dir = Dir.new('example') + * dir.chdir + * Dir.pwd # => "/example" + * + * With a block, temporarily changes the working directory: + * + * - Calls the block. + * - Changes to the given directory. + * - Executes the block (yields no args). + * - Restores the previous working directory. + * - Returns the block's return value. + * + * Uses Dir.fchdir if available, and Dir.chdir if not, see those + * methods for caveats. + */ +static VALUE +dir_chdir(VALUE dir) +{ +#if defined(HAVE_FCHDIR) && defined(HAVE_DIRFD) && HAVE_FCHDIR && HAVE_DIRFD + return dir_s_fchdir(rb_cDir, dir_fileno(dir)); +#else + return chdir_path(dir_get(dir)->path, false); +#endif +} + +static VALUE last_cwd; #ifndef _WIN32 +static VALUE +getcwd_to_str(VALUE arg) +{ + const char *path = (const char *)arg; +#ifdef __APPLE__ + return rb_str_normalize_ospath(path, strlen(path)); +#else + return rb_str_new2(path); +#endif +} + +static VALUE +getcwd_xfree(VALUE arg) +{ + xfree((void *)arg); + return Qnil; +} + +static VALUE +rb_dir_getwd_ospath_slowpath(void) +{ + char *path = ruby_getcwd(); + return rb_ensure(getcwd_to_str, (VALUE)path, getcwd_xfree, (VALUE)path); +} + VALUE rb_dir_getwd_ospath(void) { - char *path; - VALUE cwd; - VALUE path_guard; + char buf[PATH_MAX]; + char *path = getcwd(buf, PATH_MAX); + if (!path) { + return rb_dir_getwd_ospath_slowpath(); + } + + VALUE cached_cwd = RUBY_ATOMIC_VALUE_LOAD(last_cwd); -#undef RUBY_UNTYPED_DATA_WARNING -#define RUBY_UNTYPED_DATA_WARNING 0 - path_guard = Data_Wrap_Struct((VALUE)0, NULL, RUBY_DEFAULT_FREE, NULL); - path = ruby_getcwd(); - DATA_PTR(path_guard) = path; + if (!cached_cwd || strcmp(RSTRING_PTR(cached_cwd), path) != 0) { #ifdef __APPLE__ - cwd = rb_str_normalize_ospath(path, strlen(path)); + cached_cwd = rb_str_normalize_ospath(path, strlen(path)); #else - cwd = rb_str_new2(path); + cached_cwd = rb_str_new2(path); #endif - DATA_PTR(path_guard) = 0; - - xfree(path); - return cwd; + rb_str_freeze(cached_cwd); + RUBY_ATOMIC_VALUE_SET(last_cwd, cached_cwd); + } + return cached_cwd; } #endif @@ -1127,7 +1642,7 @@ rb_dir_getwd(void) { rb_encoding *fs = rb_filesystem_encoding(); int fsenc = rb_enc_to_index(fs); - VALUE cwd = rb_dir_getwd_ospath(); + VALUE cwd = rb_str_new_shared(rb_dir_getwd_ospath()); switch (fsenc) { case ENCINDEX_US_ASCII: @@ -1143,16 +1658,14 @@ rb_dir_getwd(void) } /* - * call-seq: - * Dir.getwd -> string - * Dir.pwd -> string + * call-seq: + * Dir.pwd -> string + * + * Returns the path to the current working directory: * - * Returns the path to the current working directory of this process as - * a string. + * Dir.chdir("/tmp") # => 0 + * Dir.pwd # => "/tmp" * - * Dir.chdir("/tmp") #=> 0 - * Dir.getwd #=> "/tmp" - * Dir.pwd #=> "/tmp" */ static VALUE dir_s_getwd(VALUE dir) @@ -1181,20 +1694,29 @@ check_dirname(VALUE dir) } #if defined(HAVE_CHROOT) +static void * +nogvl_chroot(void *dirname) +{ + return (void *)(VALUE)chroot((const char *)dirname); +} + /* - * call-seq: - * Dir.chroot( string ) -> 0 + * call-seq: + * Dir.chroot(dirpath) -> 0 + * + * Changes the root directory of the calling process to that specified in +dirpath+. + * The new root directory is used for pathnames beginning with <tt>'/'</tt>. + * The root directory is inherited by all children of the calling process. + * + * Only a privileged process may call +chroot+. * - * Changes this process's idea of the file system root. Only a - * privileged process may make this call. Not available on all - * platforms. On Unix systems, see <code>chroot(2)</code> for more - * information. + * See {Linux chroot}[https://man7.org/linux/man-pages/man2/chroot.2.html]. */ static VALUE dir_s_chroot(VALUE dir, VALUE path) { path = check_dirname(path); - if (chroot(RSTRING_PTR(path)) == -1) + if (IO_WITHOUT_GVL_INT(nogvl_chroot, (void *)RSTRING_PTR(path)) == -1) rb_sys_fail_path(path); return INT2FIX(0); @@ -1217,18 +1739,20 @@ nogvl_mkdir(void *ptr) } /* - * call-seq: - * Dir.mkdir( string [, integer] ) -> 0 + * call-seq: + * Dir.mkdir(dirpath, permissions = 0775) -> 0 * - * Makes a new directory named by <i>string</i>, with permissions - * specified by the optional parameter <i>anInteger</i>. The - * permissions may be modified by the value of File::umask, and are - * ignored on NT. Raises a SystemCallError if the directory cannot be - * created. See also the discussion of permissions in the class - * documentation for File. + * Creates a directory in the underlying file system + * at +dirpath+ with the given +permissions+; + * returns zero: * - * Dir.mkdir(File.join(Dir.home, ".foo"), 0700) #=> 0 + * Dir.mkdir('foo') + * File.stat(Dir.new('foo')).mode.to_s(8)[1..4] # => "0755" + * Dir.mkdir('bar', 0644) + * File.stat(Dir.new('bar')).mode.to_s(8)[1..4] # => "0644" * + * See {File Permissions}[rdoc-ref:File@File+Permissions]. + * Note that argument +permissions+ is ignored on Windows. */ static VALUE dir_s_mkdir(int argc, VALUE *argv, VALUE obj) @@ -1246,7 +1770,7 @@ dir_s_mkdir(int argc, VALUE *argv, VALUE obj) path = check_dirname(path); m.path = RSTRING_PTR(path); - r = (int)(VALUE)rb_thread_call_without_gvl(nogvl_mkdir, &m, RUBY_UBF_IO, 0); + r = IO_WITHOUT_GVL_INT(nogvl_mkdir, &m); if (r < 0) rb_sys_fail_path(path); @@ -1262,13 +1786,14 @@ nogvl_rmdir(void *ptr) } /* - * call-seq: - * Dir.delete( string ) -> 0 - * Dir.rmdir( string ) -> 0 - * Dir.unlink( string ) -> 0 + * call-seq: + * Dir.rmdir(dirpath) -> 0 + * + * Removes the directory at +dirpath+ from the underlying file system: + * + * Dir.rmdir('foo') # => 0 * - * Deletes the named directory. Raises a subclass of SystemCallError - * if the directory isn't empty. + * Raises an exception if the directory is not empty. */ static VALUE dir_s_rmdir(VALUE obj, VALUE dir) @@ -1278,7 +1803,7 @@ dir_s_rmdir(VALUE obj, VALUE dir) dir = check_dirname(dir); p = RSTRING_PTR(dir); - r = (int)(VALUE)rb_thread_call_without_gvl(nogvl_rmdir, (void *)p, RUBY_UBF_IO, 0); + r = IO_WITHOUT_GVL_INT(nogvl_rmdir, (void *)p); if (r < 0) rb_sys_fail_path(dir); @@ -1368,11 +1893,11 @@ to_be_ignored(int e) } #ifdef _WIN32 -#define STAT(p, s) rb_w32_ustati128((p), (s)) -#undef lstat -#define lstat(p, s) rb_w32_ulstati128((p), (s)) +#define STAT(args) (int)(VALUE)nogvl_stat(&(args)) +#define LSTAT(args) (int)(VALUE)nogvl_lstat(&(args)) #else -#define STAT(p, s) stat((p), (s)) +#define STAT(args) IO_WITHOUT_GVL_INT(nogvl_stat, (void *)&(args)) +#define LSTAT(args) IO_WITHOUT_GVL_INT(nogvl_lstat, (void *)&(args)) #endif typedef int ruby_glob_errfunc(const char*, VALUE, const void*, int); @@ -1393,14 +1918,50 @@ at_subpath(int fd, size_t baselen, const char *path) return *path ? path : "."; } +#if USE_OPENDIR_AT +struct fstatat_args { + int fd; + int flag; + const char *path; + struct stat *pst; +}; + +static void * +nogvl_fstatat(void *args) +{ + struct fstatat_args *arg = (struct fstatat_args *)args; + return (void *)(VALUE)fstatat(arg->fd, arg->path, arg->pst, arg->flag); +} +#else +struct stat_args { + const char *path; + struct stat *pst; +}; + +static void * +nogvl_stat(void *args) +{ + struct stat_args *arg = (struct stat_args *)args; + return (void *)(VALUE)stat(arg->path, arg->pst); +} +#endif + /* System call with warning */ static int -do_stat(int fd, size_t baselen, const char *path, struct stat *pst, int flags, rb_encoding *enc) +do_stat(int fd, const char *path, struct stat *pst, int flags, rb_encoding *enc) { #if USE_OPENDIR_AT - int ret = fstatat(fd, at_subpath(fd, baselen, path), pst, 0); + struct fstatat_args args; + args.fd = fd; + args.path = path; + args.pst = pst; + args.flag = 0; + int ret = IO_WITHOUT_GVL_INT(nogvl_fstatat, (void *)&args); #else - int ret = STAT(path, pst); + struct stat_args args; + args.path = path; + args.pst = pst; + int ret = STAT(args); #endif if (ret < 0 && !to_be_ignored(errno)) sys_warning(path, enc); @@ -1409,13 +1970,30 @@ do_stat(int fd, size_t baselen, const char *path, struct stat *pst, int flags, r } #if defined HAVE_LSTAT || defined lstat || USE_OPENDIR_AT +#if !USE_OPENDIR_AT +static void * +nogvl_lstat(void *args) +{ + struct stat_args *arg = (struct stat_args *)args; + return (void *)(VALUE)lstat(arg->path, arg->pst); +} +#endif + static int -do_lstat(int fd, size_t baselen, const char *path, struct stat *pst, int flags, rb_encoding *enc) +do_lstat(int fd, const char *path, struct stat *pst, int flags, rb_encoding *enc) { #if USE_OPENDIR_AT - int ret = fstatat(fd, at_subpath(fd, baselen, path), pst, AT_SYMLINK_NOFOLLOW); + struct fstatat_args args; + args.fd = fd; + args.path = path; + args.pst = pst; + args.flag = AT_SYMLINK_NOFOLLOW; + int ret = IO_WITHOUT_GVL_INT(nogvl_fstatat, (void *)&args); #else - int ret = lstat(path, pst); + struct stat_args args; + args.path = path; + args.pst = pst; + int ret = LSTAT(args); #endif if (ret < 0 && !to_be_ignored(errno)) sys_warning(path, enc); @@ -1476,7 +2054,7 @@ nogvl_opendir_at(void *ptr) /* fallthrough*/ case 0: if (fd >= 0) close(fd); - errno = e; + rb_errno_set(e); } } #else /* !USE_OPENDIR_AT */ @@ -1497,7 +2075,7 @@ opendir_at(int basefd, const char *path) oaa.path = path; if (vm_initialized) - return rb_thread_call_without_gvl(nogvl_opendir_at, &oaa, RUBY_UBF_IO, 0); + return IO_WITHOUT_GVL(nogvl_opendir_at, &oaa); else return nogvl_opendir_at(&oaa); } @@ -1776,14 +2354,15 @@ is_case_sensitive(DIR *dirp, const char *path) const vol_capabilities_attr_t *const cap = attrbuf[0].cap; const int idx = VOL_CAPABILITIES_FORMAT; const uint32_t mask = VOL_CAP_FMT_CASE_SENSITIVE; - + struct getattrlist_args args = GETATTRLIST_ARGS(&al, attrbuf, FSOPT_NOFOLLOW); # if defined HAVE_FGETATTRLIST - if (fgetattrlist(dirfd(dirp), &al, attrbuf, sizeof(attrbuf), FSOPT_NOFOLLOW)) - return -1; + int ret = gvl_fgetattrlist(&args, dirfd(dirp)); # else - if (getattrlist(path, &al, attrbuf, sizeof(attrbuf), FSOPT_NOFOLLOW)) - return -1; + int ret = gvl_getattrlist(&args, path); # endif + if (ret) + return -1; + if (!(cap->valid[idx] & mask)) return -1; return (cap->capabilities[idx] & mask) != 0; @@ -1806,7 +2385,8 @@ replace_real_basename(char *path, long base, rb_encoding *enc, int norm_p, int f IF_NORMALIZE_UTF8PATH(VALUE utf8str = Qnil); *type = path_noent; - if (getattrlist(path, &al, attrbuf, sizeof(attrbuf), FSOPT_NOFOLLOW)) { + struct getattrlist_args args = GETATTRLIST_ARGS(&al, attrbuf, FSOPT_NOFOLLOW); + if (gvl_getattrlist(&args, path)) { if (!to_be_ignored(errno)) sys_warning(path, enc); return path; @@ -2179,7 +2759,7 @@ static void glob_dir_finish(ruby_glob_entries_t *ent, int flags) { if (flags & FNM_GLOB_NOSORT) { - closedir(ent->nosort.dirp); + check_closedir(ent->nosort.dirp); ent->nosort.dirp = NULL; } else if (ent->sort.entries) { @@ -2210,7 +2790,7 @@ glob_opendir(ruby_glob_entries_t *ent, DIR *dirp, int flags, rb_encoding *enc) #ifdef _WIN32 if ((capacity = dirp->nfiles) > 0) { if (!(newp = GLOB_ALLOC_N(rb_dirent_t, capacity))) { - closedir(dirp); + check_closedir(dirp); return NULL; } ent->sort.entries = newp; @@ -2223,14 +2803,16 @@ glob_opendir(ruby_glob_entries_t *ent, DIR *dirp, int flags, rb_encoding *enc) } if (count >= capacity) { capacity += 256; - if (!(newp = GLOB_REALLOC_N(ent->sort.entries, capacity))) + if (!(newp = GLOB_REALLOC_N(ent->sort.entries, capacity))) { + GLOB_FREE(rdp); goto nomem; + } ent->sort.entries = newp; } ent->sort.entries[count++] = rdp; ent->sort.count = count; } - closedir(dirp); + check_closedir(dirp); if (count < capacity) { if (!(newp = GLOB_REALLOC_N(ent->sort.entries, count))) { glob_dir_finish(ent, 0); @@ -2245,7 +2827,7 @@ glob_opendir(ruby_glob_entries_t *ent, DIR *dirp, int flags, rb_encoding *enc) nomem: glob_dir_finish(ent, 0); - closedir(dirp); + check_closedir(dirp); return NULL; } @@ -2343,7 +2925,7 @@ glob_helper( if (*path) { if (match_all && pathtype == path_unknown) { - if (do_lstat(fd, baselen, path, &st, flags, enc) == 0) { + if (do_lstat(fd, path, &st, flags, enc) == 0) { pathtype = IFTODT(st.st_mode); } else { @@ -2351,7 +2933,7 @@ glob_helper( } } if (match_dir && (pathtype == path_unknown || pathtype == path_symlink)) { - if (do_stat(fd, baselen, path, &st, flags, enc) == 0) { + if (do_stat(fd, path, &st, flags, enc) == 0) { pathtype = IFTODT(st.st_mode); } else { @@ -2408,7 +2990,7 @@ glob_helper( # if NORMALIZE_UTF8PATH if (!(norm_p || magical || recursive)) { - closedir(dirp); + check_closedir(dirp); goto literally; } # endif @@ -2479,7 +3061,7 @@ glob_helper( if (recursive && dotfile < ((flags & FNM_DOTMATCH) ? 2 : 1) && new_pathtype == path_unknown) { /* RECURSIVE never match dot files unless FNM_DOTMATCH is set */ - if (do_lstat(fd, baselen, buf, &st, flags, enc) == 0) + if (do_lstat(fd, buf, &st, flags, enc) == 0) new_pathtype = IFTODT(st.st_mode); else new_pathtype = path_noent; @@ -2869,7 +3451,7 @@ push_glob(VALUE ary, VALUE str, VALUE base, int flags) fd = AT_FDCWD; if (!NIL_P(base)) { if (!RB_TYPE_P(base, T_STRING) || !rb_enc_check(str, base)) { - struct dir_data *dirp = DATA_PTR(base); + struct dir_data *dirp = RTYPEDDATA_GET_DATA(base); if (!dirp->dir) dir_closed(); #ifdef HAVE_DIRFD if ((fd = dirfd(dirp->dir)) == -1) @@ -2993,26 +3575,35 @@ dir_open_dir(int argc, VALUE *argv) /* - * call-seq: - * Dir.foreach( dirname ) {| filename | block } -> nil - * Dir.foreach( dirname, encoding: enc ) {| filename | block } -> nil - * Dir.foreach( dirname ) -> an_enumerator - * Dir.foreach( dirname, encoding: enc ) -> an_enumerator + * call-seq: + * Dir.foreach(dirpath, encoding: 'UTF-8') {|entry_name| ... } -> nil + * + * Calls the block with each entry name in the directory at +dirpath+; + * sets the given encoding onto each passed +entry_name+: * - * Calls the block once for each entry in the named directory, passing - * the filename of each entry as a parameter to the block. + * Dir.foreach('/example') {|entry_name| p entry_name } * - * If no block is given, an enumerator is returned instead. + * Output: * - * Dir.foreach("testdir") {|x| puts "Got #{x}" } + * "config.h" + * "lib" + * "main.rb" + * ".." + * "." * - * <em>produces:</em> + * Encoding: * - * Got . - * Got .. - * Got config.h - * Got main.rb + * Dir.foreach('/example') {|entry_name| p entry_name.encoding; break } + * Dir.foreach('/example', encoding: 'US-ASCII') {|entry_name| p entry_name.encoding; break } * + * Output: + * + * #<Encoding:UTF-8> + * #<Encoding:US-ASCII> + * + * See {String Encoding}[rdoc-ref:encodings.rdoc@String+Encoding]. + * + * Returns an enumerator if no block is given. */ static VALUE dir_foreach(int argc, VALUE *argv, VALUE io) @@ -3026,27 +3617,35 @@ dir_foreach(int argc, VALUE *argv, VALUE io) } static VALUE +dir_entry_ary_push(VALUE ary, VALUE entry, struct dir_entry_args *_unused) +{ + return rb_ary_push(ary, entry); +} + +static VALUE dir_collect(VALUE dir) { VALUE ary = rb_ary_new(); - dir_each_entry(dir, rb_ary_push, ary, FALSE); + dir_each_entry(dir, dir_entry_ary_push, ary, FALSE); return ary; } /* - * call-seq: - * Dir.entries( dirname ) -> array - * Dir.entries( dirname, encoding: enc ) -> array + * call-seq: + * Dir.entries(dirname, encoding: 'UTF-8') -> array * - * Returns an array containing all of the filenames in the given - * directory. Will raise a SystemCallError if the named directory - * doesn't exist. + * Returns an array of the entry names in the directory at +dirpath+; + * sets the given encoding onto each returned entry name: * - * The optional <i>encoding</i> keyword argument specifies the encoding of the - * directory. If not specified, the filesystem encoding is used. + * Dir.entries('/example') # => ["config.h", "lib", "main.rb", "..", "."] + * Dir.entries('/example').first.encoding + * # => #<Encoding:UTF-8> + * Dir.entries('/example', encoding: 'US-ASCII').first.encoding + * # => #<Encoding:US-ASCII> * - * Dir.entries("testdir") #=> [".", "..", "config.h", "main.rb"] + * See {String Encoding}[rdoc-ref:encodings.rdoc@String+Encoding]. * + * Raises an exception if the directory does not exist. */ static VALUE dir_entries(int argc, VALUE *argv, VALUE io) @@ -3064,25 +3663,12 @@ dir_each_child(VALUE dir) } /* - * call-seq: - * Dir.each_child( dirname ) {| filename | block } -> nil - * Dir.each_child( dirname, encoding: enc ) {| filename | block } -> nil - * Dir.each_child( dirname ) -> an_enumerator - * Dir.each_child( dirname, encoding: enc ) -> an_enumerator - * - * Calls the block once for each entry except for "." and ".." in the - * named directory, passing the filename of each entry as a parameter - * to the block. - * - * If no block is given, an enumerator is returned instead. - * - * Dir.each_child("testdir") {|x| puts "Got #{x}" } - * - * <em>produces:</em> - * - * Got config.h - * Got main.rb + * call-seq: + * Dir.each_child(dirpath) {|entry_name| ... } -> nil + * Dir.each_child(dirpath, encoding: 'UTF-8') {|entry_name| ... } -> nil * + * Like Dir.foreach, except that entries <tt>'.'</tt> and <tt>'..'</tt> + * are not included. */ static VALUE dir_s_each_child(int argc, VALUE *argv, VALUE io) @@ -3096,24 +3682,22 @@ dir_s_each_child(int argc, VALUE *argv, VALUE io) } /* - * call-seq: - * dir.each_child {| filename | block } -> dir - * dir.each_child -> an_enumerator - * - * Calls the block once for each entry except for "." and ".." in - * this directory, passing the filename of each entry as a parameter - * to the block. + * call-seq: + * each_child {|entry_name| ... } -> self * - * If no block is given, an enumerator is returned instead. + * Calls the block with each entry name in +self+ + * except <tt>'.'</tt> and <tt>'..'</tt>: * - * d = Dir.new("testdir") - * d.each_child {|x| puts "Got #{x}" } + * dir = Dir.new('/example') + * dir.each_child {|entry_name| p entry_name } * - * <em>produces:</em> + * Output: * - * Got config.h - * Got main.rb + * "config.h" + * "lib" + * "main.rb" * + * If no block is given, returns an enumerator. */ static VALUE dir_each_child_m(VALUE dir) @@ -3123,38 +3707,67 @@ dir_each_child_m(VALUE dir) } /* - * call-seq: - * dir.children -> array + * call-seq: + * children -> array * - * Returns an array containing all of the filenames except for "." - * and ".." in this directory. + * Returns an array of the entry names in +self+ + * except for <tt>'.'</tt> and <tt>'..'</tt>: * - * d = Dir.new("testdir") - * d.children #=> ["config.h", "main.rb"] + * dir = Dir.new('/example') + * dir.children # => ["config.h", "lib", "main.rb"] * */ static VALUE dir_collect_children(VALUE dir) { VALUE ary = rb_ary_new(); - dir_each_entry(dir, rb_ary_push, ary, TRUE); + dir_each_entry(dir, dir_entry_ary_push, ary, TRUE); return ary; } /* - * call-seq: - * Dir.children( dirname ) -> array - * Dir.children( dirname, encoding: enc ) -> array + * call-seq: + * children -> array + * + * Returns an array of the entry names in +self+ along with their type + * except for <tt>'.'</tt> and <tt>'..'</tt>: + * + * dir = Dir.new('/example') + * dir.scan # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]] + * + */ +static VALUE +dir_scan_children(VALUE dir) +{ + if (rb_block_given_p()) { + dir_each_entry(dir, dir_yield_with_type, Qnil, TRUE); + return Qnil; + } + else { + VALUE ary = rb_ary_new(); + dir_each_entry(dir, dir_yield_with_type, ary, TRUE); + return ary; + } +} + +/* + * call-seq: + * Dir.children(dirpath) -> array + * Dir.children(dirpath, encoding: 'UTF-8') -> array * - * Returns an array containing all of the filenames except for "." - * and ".." in the given directory. Will raise a SystemCallError if - * the named directory doesn't exist. + * Returns an array of the entry names in the directory at +dirpath+ + * except for <tt>'.'</tt> and <tt>'..'</tt>; + * sets the given encoding onto each returned entry name: * - * The optional <i>encoding</i> keyword argument specifies the encoding of the - * directory. If not specified, the filesystem encoding is used. + * Dir.children('/example') # => ["config.h", "lib", "main.rb"] + * Dir.children('/example').first.encoding + * # => #<Encoding:UTF-8> + * Dir.children('/example', encoding: 'US-ASCII').first.encoding + * # => #<Encoding:US-ASCII> * - * Dir.children("testdir") #=> ["config.h", "main.rb"] + * See {String Encoding}[rdoc-ref:encodings.rdoc@String+Encoding]. * + * Raises an exception if the directory does not exist. */ static VALUE dir_s_children(int argc, VALUE *argv, VALUE io) @@ -3165,6 +3778,40 @@ dir_s_children(int argc, VALUE *argv, VALUE io) return rb_ensure(dir_collect_children, dir, dir_close, dir); } +/* + * call-seq: + * Dir.scan(dirpath) {|entry_name, entry_type| ... } -> nil + * Dir.scan(dirpath, encoding: 'UTF-8') {|entry_name, entry_type| ... } -> nil + * Dir.scan(dirpath) -> [[entry_name, entry_type], ...] + * Dir.scan(dirpath, encoding: 'UTF-8') -> [[entry_name, entry_type], ...] + * + * Yields or returns an array of the entry names in the directory at +dirpath+ + * associated with their type, except for <tt>'.'</tt> and <tt>'..'</tt>; + * sets the given encoding onto each returned entry name. + * + * The type symbol is one of: + * ``<code>:file</code>'', ``<code>:directory</code>'', + * ``<code>:characterSpecial</code>'', ``<code>:blockSpecial</code>'', + * ``<code>:fifo</code>'', ``<code>:link</code>'', + * or ``<code>:socket</code>'': + * + * Dir.children('/example') # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]] + * Dir.children('/example').first.first.encoding + * # => #<Encoding:UTF-8> + * Dir.children('/example', encoding: 'US-ASCII').first.encoding + * # => #<Encoding:US-ASCII> + * + * See {String Encoding}[rdoc-ref:encodings.rdoc@String+Encoding]. + * + * Raises an exception if the directory does not exist. + */ +static VALUE +dir_s_scan(int argc, VALUE *argv, VALUE klass) +{ + VALUE dir = dir_open_dir(argc, argv); + return rb_ensure(dir_scan_children, dir, dir_close, dir); +} + static int fnmatch_brace(const char *pattern, VALUE val, void *enc) { @@ -3228,12 +3875,16 @@ file_s_fnmatch(int argc, VALUE *argv, VALUE obj) } /* - * call-seq: - * Dir.home() -> "/home/me" - * Dir.home("root") -> "/root" + * call-seq: + * Dir.home(user_name = nil) -> dirpath * - * Returns the home directory of the current user or the named user - * if given. + * Returns the home directory path of the user specified with +user_name+ + * if it is not +nil+, or the current login user: + * + * Dir.home # => "/home/me" + * Dir.home('root') # => "/root" + * + * Raises ArgumentError if +user_name+ is not a user name. */ static VALUE dir_s_home(int argc, VALUE *argv, VALUE obj) @@ -3244,7 +3895,7 @@ dir_s_home(int argc, VALUE *argv, VALUE obj) rb_check_arity(argc, 0, 1); user = (argc > 0) ? argv[0] : Qnil; if (!NIL_P(user)) { - SafeStringValue(user); + StringValue(user); rb_must_asciicompat(user); u = StringValueCStr(user); if (*u) { @@ -3258,10 +3909,15 @@ dir_s_home(int argc, VALUE *argv, VALUE obj) #if 0 /* * call-seq: - * Dir.exist?(file_name) -> true or false + * Dir.exist?(dirpath) -> true or false + * + * Returns whether +dirpath+ is a directory in the underlying file system: * - * Returns <code>true</code> if the named file is a directory, - * <code>false</code> otherwise. + * Dir.exist?('/example') # => true + * Dir.exist?('/nosuch') # => false + * Dir.exist?('/example/main.rb') # => false + * + * Same as File.directory?. * */ VALUE @@ -3288,26 +3944,33 @@ nogvl_dir_empty_p(void *ptr) /* fall through */ case 0: if (e == ENOTDIR) return (void *)Qfalse; - errno = e; /* for rb_sys_fail_path */ - return (void *)Qundef; + return (void *)INT2FIX(e); } } - while ((dp = READDIR(dir, NULL)) != NULL) { + while ((dp = READDIR_NOGVL(dir, NULL)) != NULL) { if (!to_be_skipped(dp)) { result = Qfalse; break; } } - closedir(dir); + check_closedir(dir); return (void *)result; } /* * call-seq: - * Dir.empty?(path_name) -> true or false + * Dir.empty?(dirpath) -> true or false + * + * Returns whether +dirpath+ specifies an empty directory: + * + * dirpath = '/tmp/foo' + * Dir.mkdir(dirpath) + * Dir.empty?(dirpath) # => true + * Dir.empty?('/example') # => false + * Dir.empty?('/example/main.rb') # => false * - * Returns <code>true</code> if the named file is an empty directory, - * <code>false</code> if it is not a directory or non-empty. + * Raises an exception if +dirpath+ does not specify a directory or file + * in the underlying file system. */ static VALUE rb_dir_s_empty_p(VALUE obj, VALUE dirname) @@ -3326,12 +3989,13 @@ rb_dir_s_empty_p(VALUE obj, VALUE dirname) { u_int32_t attrbuf[SIZEUP32(fsobj_tag_t)]; struct attrlist al = {ATTR_BIT_MAP_COUNT, 0, ATTR_CMN_OBJTAG,}; - if (getattrlist(path, &al, attrbuf, sizeof(attrbuf), 0) != 0) + struct getattrlist_args args = GETATTRLIST_ARGS(&al, attrbuf, 0); + if (gvl_getattrlist(&args, path) != 0) rb_sys_fail_path(orig); if (*(const fsobj_tag_t *)(attrbuf+1) == VT_HFS) { al.commonattr = 0; al.dirattr = ATTR_DIR_ENTRYCOUNT; - if (getattrlist(path, &al, attrbuf, sizeof(attrbuf), 0) == 0) { + if (gvl_getattrlist(&args, path) == 0) { if (attrbuf[0] >= 2 * sizeof(u_int32_t)) return RBOOL(attrbuf[1] == 0); if (false_on_notdir) return Qfalse; @@ -3341,10 +4005,9 @@ rb_dir_s_empty_p(VALUE obj, VALUE dirname) } #endif - result = (VALUE)rb_thread_call_without_gvl(nogvl_dir_empty_p, (void *)path, - RUBY_UBF_IO, 0); - if (UNDEF_P(result)) { - rb_sys_fail_path(orig); + result = (VALUE)IO_WITHOUT_GVL(nogvl_dir_empty_p, (void *)path); + if (FIXNUM_P(result)) { + rb_syserr_fail_path((int)FIX2LONG(result), orig); } return result; } @@ -3352,15 +4015,39 @@ rb_dir_s_empty_p(VALUE obj, VALUE dirname) void Init_Dir(void) { + sym_directory = ID2SYM(rb_intern("directory")); + sym_link = ID2SYM(rb_intern("link")); + sym_file = ID2SYM(rb_intern("file")); + sym_unknown = ID2SYM(rb_intern("unknown")); + +#if defined(DT_BLK) || defined(S_IFBLK) + sym_block_device = ID2SYM(rb_intern("blockSpecial")); +#endif +#if defined(DT_CHR) || defined(S_IFCHR) + sym_character_device = ID2SYM(rb_intern("characterSpecial")); +#endif +#if defined(DT_FIFO) || defined(S_IFIFO) + sym_fifo = ID2SYM(rb_intern("fifo")); +#endif +#if defined(DT_SOCK) || defined(S_IFSOCK) + sym_socket = ID2SYM(rb_intern("socket")); +#endif + + rb_gc_register_address(&chdir_lock.path); + rb_gc_register_address(&chdir_lock.thread); + rb_gc_register_address(&last_cwd); + rb_cDir = rb_define_class("Dir", rb_cObject); rb_include_module(rb_cDir, rb_mEnumerable); rb_define_alloc_func(rb_cDir, dir_s_alloc); + rb_define_singleton_method(rb_cDir,"for_fd", dir_s_for_fd, 1); rb_define_singleton_method(rb_cDir, "foreach", dir_foreach, -1); rb_define_singleton_method(rb_cDir, "entries", dir_entries, -1); rb_define_singleton_method(rb_cDir, "each_child", dir_s_each_child, -1); rb_define_singleton_method(rb_cDir, "children", dir_s_children, -1); + rb_define_singleton_method(rb_cDir, "scan", dir_s_scan, -1); rb_define_method(rb_cDir,"fileno", dir_fileno, 0); rb_define_method(rb_cDir,"path", dir_path, 0); @@ -3370,13 +4057,16 @@ Init_Dir(void) rb_define_method(rb_cDir,"each", dir_each, 0); rb_define_method(rb_cDir,"each_child", dir_each_child_m, 0); rb_define_method(rb_cDir,"children", dir_collect_children, 0); + rb_define_method(rb_cDir,"scan", dir_scan_children, 0); rb_define_method(rb_cDir,"rewind", dir_rewind, 0); rb_define_method(rb_cDir,"tell", dir_tell, 0); rb_define_method(rb_cDir,"seek", dir_seek, 1); rb_define_method(rb_cDir,"pos", dir_tell, 0); rb_define_method(rb_cDir,"pos=", dir_set_pos, 1); rb_define_method(rb_cDir,"close", dir_close, 0); + rb_define_method(rb_cDir,"chdir", dir_chdir, 0); + rb_define_singleton_method(rb_cDir,"fchdir", dir_s_fchdir, 1); rb_define_singleton_method(rb_cDir,"chdir", dir_s_chdir, -1); rb_define_singleton_method(rb_cDir,"getwd", dir_s_getwd, 0); rb_define_singleton_method(rb_cDir,"pwd", dir_s_getwd, 0); @@ -3393,51 +4083,19 @@ Init_Dir(void) rb_define_singleton_method(rb_cFile,"fnmatch", file_s_fnmatch, -1); rb_define_singleton_method(rb_cFile,"fnmatch?", file_s_fnmatch, -1); - /* Document-const: File::Constants::FNM_NOESCAPE - * - * Disables escapes in File.fnmatch and Dir.glob patterns - */ + /* {File::FNM_NOESCAPE}[rdoc-ref:File::Constants@File-3A-3AFNM_NOESCAPE] */ rb_file_const("FNM_NOESCAPE", INT2FIX(FNM_NOESCAPE)); - - /* Document-const: File::Constants::FNM_PATHNAME - * - * Wildcards in File.fnmatch and Dir.glob patterns do not match directory - * separators - */ + /* {File::FNM_PATHNAME}[rdoc-ref:File::Constants@File-3A-3AFNM_PATHNAME] */ rb_file_const("FNM_PATHNAME", INT2FIX(FNM_PATHNAME)); - - /* Document-const: File::Constants::FNM_DOTMATCH - * - * The '*' wildcard matches filenames starting with "." in File.fnmatch - * and Dir.glob patterns - */ + /* {File::FNM_DOTMATCH}[rdoc-ref:File::Constants@File-3A-3AFNM_DOTMATCH] */ rb_file_const("FNM_DOTMATCH", INT2FIX(FNM_DOTMATCH)); - - /* Document-const: File::Constants::FNM_CASEFOLD - * - * Makes File.fnmatch patterns case insensitive (but not Dir.glob - * patterns). - */ + /* {File::FNM_CASEFOLD}[rdoc-ref:File::Constants@File-3A-3AFNM_CASEFOLD] */ rb_file_const("FNM_CASEFOLD", INT2FIX(FNM_CASEFOLD)); - - /* Document-const: File::Constants::FNM_EXTGLOB - * - * Allows file globbing through "{a,b}" in File.fnmatch patterns. - */ + /* {File::FNM_EXTGLOB}[rdoc-ref:File::Constants@File-3A-3AFNM_EXTGLOB] */ rb_file_const("FNM_EXTGLOB", INT2FIX(FNM_EXTGLOB)); - - /* Document-const: File::Constants::FNM_SYSCASE - * - * System default case insensitiveness, equals to FNM_CASEFOLD or - * 0. - */ + /* {File::FNM_SYSCASE}[rdoc-ref:File::Constants@File-3A-3AFNM_SYSCASE] */ rb_file_const("FNM_SYSCASE", INT2FIX(FNM_SYSCASE)); - - /* Document-const: File::Constants::FNM_SHORTNAME - * - * Makes patterns to match short names if existing. Valid only - * on Microsoft Windows. - */ + /* {File::FNM_SHORTNAME}[rdoc-ref:File::Constants@File-3A-3AFNM_SHORTNAME] */ rb_file_const("FNM_SHORTNAME", INT2FIX(FNM_SHORTNAME)); } |
