diff --git a/.gitignore b/.gitignore index 18cb5b0268..e5ca7fc248 100644 --- a/.gitignore +++ b/.gitignore @@ -110,6 +110,9 @@ y.tab.c /ext/dl/callback/callback-*.c /ext/dl/callback/callback.c +# /ext/etc/ +/ext/etc/constdefs.h + # /ext/rbconfig/ /ext/rbconfig/sizeof/sizes.c diff --git a/ChangeLog b/ChangeLog index 40ee58baba..8ad0182a19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +Sun May 18 10:46:04 2014 Tanaka Akira + + * ext/etc/etc.c: Etc.sysconf, Etc.confstr and IO#pathconf implemented. + + * ext/etc/extconf.rb: Check sysconf(), confstr() and fpathconf(). + + * ext/etc/mkconstants.rb: New file. + + [ruby-core:62600] [Feature #9842] + Sun May 18 09:58:17 2014 Tanaka Akira * ext/etc/etc.c: Etc.uname method implemented. diff --git a/NEWS b/NEWS index 5e84dbc376..6dd64dca0a 100644 --- a/NEWS +++ b/NEWS @@ -86,6 +86,9 @@ with all sufficient information, see the ChangeLog file. * Etc * New methods: * Etc.uname + * Etc.sysconf + * Etc.confstr + * IO#pathconf * Find, Pathname * Extended methods: diff --git a/ext/etc/depend b/ext/etc/depend index 2db89d969c..6fc0ce8c91 100644 --- a/ext/etc/depend +++ b/ext/etc/depend @@ -1,3 +1,8 @@ -etc.o : etc.c $(HDRS) $(ruby_headers) \ +etc.o : etc.c constdefs.h $(HDRS) $(ruby_headers) \ $(hdrdir)/ruby/encoding.h \ $(hdrdir)/ruby/oniguruma.h + +constdefs.h : $(srcdir)/mkconstants.rb + @echo "generating constant definitions" + @$(RUBY) $(srcdir)/mkconstants.rb -o constdefs.h + diff --git a/ext/etc/etc.c b/ext/etc/etc.c index e805f0e9b1..577ac9223c 100644 --- a/ext/etc/etc.c +++ b/ext/etc/etc.c @@ -9,6 +9,7 @@ #include "ruby.h" #include "ruby/encoding.h" +#include "ruby/io.h" #include #ifdef HAVE_UNISTD_H @@ -23,6 +24,8 @@ #include #endif +#include + #ifdef HAVE_SYS_UTSNAME_H #include #endif @@ -44,6 +47,8 @@ char *getenv(); #endif char *getlogin(); +#include "constdefs.h" + /* call-seq: * getlogin -> String * @@ -684,6 +689,126 @@ etc_uname(VALUE obj) #define etc_uname rb_f_notimplement #endif +#ifdef HAVE_SYSCONF +/* + * Returns system configuration variable using sysconf(). + * + * _name_ should be a constant undef Etc which begins with SC_. + * + * The return value is an integer or nil. + * nil means indefinite limit. (sysconf() returns -1 but errno is not set.) + * + * Etc.sysconf(Etc::SC_ARG_MAX) #=> 2097152 + * + * # Number of processors. + * # It is not standardized. + * Etc.sysconf(Etc::SC_NPROCESSORS_ONLN) #=> 4 + * + */ +static VALUE +etc_sysconf(VALUE obj, VALUE arg) +{ + int name; + long ret; + + name = NUM2INT(arg); + + errno = 0; + ret = sysconf(name); + if (ret == -1) { + if (errno == 0) /* no limit */ + return Qnil; + rb_sys_fail("sysconf"); + } + return LONG2NUM(ret); +} +#else +#define etc_sysconf rb_f_notimplement +#endif + +#ifdef HAVE_CONFSTR +/* + * Returns system configuration variable using confstr(). + * + * _name_ should be a constant undef Etc which begins with CS_. + * + * The return value is a string or nil. + * nil means no configuration-defined value. (confstr() returns 0 but errno is not set.) + * + * Etc.confstr(Etc::CS_PATH) #=> "/bin:/usr/bin" + * + * # GNU/Linux + * Etc.confstr(Etc::CS_GNU_LIBC_VERSION) #=> "glibc 2.18" + * Etc.confstr(Etc::CS_GNU_LIBPTHREAD_VERSION) #=> "NPTL 2.18" + * + */ +static VALUE +etc_confstr(VALUE obj, VALUE arg) +{ + int name; + char localbuf[128], *buf = localbuf; + size_t bufsize = sizeof(localbuf), ret; + VALUE tmp; + + name = NUM2INT(arg); + + errno = 0; + ret = confstr(name, buf, bufsize); + if (bufsize < ret) { + bufsize = ret; + buf = ALLOCV_N(char, tmp, bufsize); + errno = 0; + ret = confstr(name, buf, bufsize); + } + if (bufsize < ret) + rb_bug("required buffer size for confstr() changed dynamically."); + if (ret == 0) { + if (errno == 0) /* no configuration-defined value */ + return Qnil; + rb_sys_fail("confstr"); + } + return rb_str_new_cstr(buf); +} +#else +#define etc_confstr rb_f_notimplement +#endif + +#ifdef HAVE_FPATHCONF +/* + * Returns pathname configuration variable using fpathconf(). + * + * _name_ should be a constant undef Etc which begins with PC_. + * + * The return value is an integer or nil. + * nil means indefinite limit. (fpathconf() returns -1 but errno is not set.) + * + * open("/") {|f| p f.pathconf(Etc::PC_NAME_MAX) } #=> 255 + * + */ +static VALUE +io_pathconf(VALUE io, VALUE arg) +{ + int name; + long ret; + rb_io_t *fptr; + + name = NUM2INT(arg); + + GetOpenFile(io, fptr); + + errno = 0; + ret = fpathconf(fptr->fd, name); + if (ret == -1) { + if (errno == 0) /* no limit */ + return Qnil; + rb_sys_fail("fpathconf"); + } + return LONG2NUM(ret); +} +#else +#define io_pathconf rb_f_notimplement +#endif + /* * The Etc module provides access to information typically stored in * files in the /etc directory on Unix systems. @@ -716,6 +841,8 @@ Init_etc(void) VALUE mEtc; mEtc = rb_define_module("Etc"); + init_constants(mEtc); + rb_define_module_function(mEtc, "getlogin", etc_getlogin, 0); rb_define_module_function(mEtc, "getpwuid", etc_getpwuid, -1); @@ -734,6 +861,9 @@ Init_etc(void) rb_define_module_function(mEtc, "sysconfdir", etc_sysconfdir, 0); rb_define_module_function(mEtc, "systmpdir", etc_systmpdir, 0); rb_define_module_function(mEtc, "uname", etc_uname, 0); + rb_define_module_function(mEtc, "sysconf", etc_sysconf, 1); + rb_define_module_function(mEtc, "confstr", etc_confstr, 1); + rb_define_method(rb_cIO, "pathconf", io_pathconf, 1); sPasswd = rb_struct_define_under(mEtc, "Passwd", "name", diff --git a/ext/etc/extconf.rb b/ext/etc/extconf.rb index 26f0ad1364..0609eaa2e3 100644 --- a/ext/etc/extconf.rb +++ b/ext/etc/extconf.rb @@ -14,6 +14,10 @@ have_func("getgrent") sysconfdir = RbConfig.expand(RbConfig::CONFIG["sysconfdir"].dup, "prefix"=>"", "DESTDIR"=>"") $defs.push("-DSYSCONFDIR=#{Shellwords.escape(sysconfdir.dump)}") +have_func("sysconf") +have_func("confstr") +have_func("fpathconf") + have_struct_member('struct passwd', 'pw_gecos', 'pwd.h') have_struct_member('struct passwd', 'pw_change', 'pwd.h') have_struct_member('struct passwd', 'pw_quota', 'pwd.h') diff --git a/ext/etc/mkconstants.rb b/ext/etc/mkconstants.rb new file mode 100644 index 0000000000..548a02227a --- /dev/null +++ b/ext/etc/mkconstants.rb @@ -0,0 +1,329 @@ +require 'optparse' +require 'erb' + +C_ESC = { + "\\" => "\\\\", + '"' => '\"', + "\n" => '\n', +} + +0x00.upto(0x1f) {|ch| C_ESC[[ch].pack("C")] ||= "\\%03o" % ch } +0x7f.upto(0xff) {|ch| C_ESC[[ch].pack("C")] = "\\%03o" % ch } +C_ESC_PAT = Regexp.union(*C_ESC.keys) + +def c_str(str) + '"' + str.gsub(C_ESC_PAT) {|s| C_ESC[s]} + '"' +end + +opt = OptionParser.new + +opt.def_option('-h', 'help') { + puts opt + exit 0 +} + +opt_o = nil +opt.def_option('-o FILE', 'specify output file') {|filename| + opt_o = filename +} + +opt_H = nil +opt.def_option('-H FILE', 'specify output header file') {|filename| + opt_H = filename +} + +opt.parse! + +h = {} +COMMENTS = Hash.new { |h, name| h[name] = name } + +DATA.each_line {|s| + next if /\A\s*(\#|\z)/ =~ s + name, default_value, comment = s.chomp.split(/\s+/, 3) + + default_value = nil if default_value == 'nil' + + if h.has_key? name + warn "#{$.}: warning: duplicate name: #{name}" + next + end + h[name] = default_value + COMMENTS[name] = comment +} +DEFS = h.to_a + +def each_const + DEFS.each {|name, default_value| + yield name, default_value + } +end + +def each_name(pat) + DEFS.each {|name, default_value| + next if pat !~ name + yield name + } +end + +ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_const_decls") +% each_const {|name, default_value| +#if !defined(<%=name%>) +# if defined(HAVE_CONST_<%=name.upcase%>) +# define <%=name%> <%=name%> +%if default_value +# else +# define <%=name%> <%=default_value%> +%end +# endif +#endif +% } +EOS + +ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_const_defs") +% each_const {|name, default_value| +#if defined(<%=name%>) + /* <%= COMMENTS[name] %> */ + rb_define_const(mod, <%=c_str name.sub(/\A_*/, '')%>, INTEGER2NUM(<%=name%>)); +#endif +% } +EOS + +header_result = ERB.new(<<'EOS', nil, '%').result(binding) +/* autogenerated file */ + +<%= gen_const_decls %> +EOS + +result = ERB.new(<<'EOS', nil, '%').result(binding) +/* autogenerated file */ + +#ifdef HAVE_LONG_LONG +#define INTEGER2NUM(n) \ + (FIXNUM_MAX < (n) ? ULL2NUM(n) : \ + FIXNUM_MIN > (LONG_LONG)(n) ? LL2NUM(n) : \ + LONG2FIX(n)) +#else +#define INTEGER2NUM(n) \ + (FIXNUM_MAX < (n) ? ULONG2NUM(n) : \ + FIXNUM_MIN > (long)(n) ? LONG2NUM(n) : \ + LONG2FIX(n)) +#endif + +static void +init_constants(VALUE mod) +{ +<%= gen_const_defs %> +} +EOS + +if opt_H + File.open(opt_H, 'w') {|f| + f << header_result + } +else + result = header_result + result +end + +if opt_o + File.open(opt_o, 'w') {|f| + f << result + } +else + $stdout << result +end + +__END__ +# SUSv4 +_SC_AIO_LISTIO_MAX +_SC_AIO_MAX +_SC_AIO_PRIO_DELTA_MAX +_SC_ARG_MAX +_SC_ATEXIT_MAX +_SC_BC_BASE_MAX +_SC_BC_DIM_MAX +_SC_BC_SCALE_MAX +_SC_BC_STRING_MAX +_SC_CHILD_MAX +_SC_CLK_TCK +_SC_COLL_WEIGHTS_MAX +_SC_DELAYTIMER_MAX +_SC_EXPR_NEST_MAX +_SC_HOST_NAME_MAX +_SC_IOV_MAX +_SC_LINE_MAX +_SC_LOGIN_NAME_MAX +_SC_NGROUPS_MAX +_SC_GETGR_R_SIZE_MAX +_SC_GETPW_R_SIZE_MAX +_SC_MQ_OPEN_MAX +_SC_MQ_PRIO_MAX +_SC_OPEN_MAX +_SC_ADVISORY_INFO +_SC_BARRIERS +_SC_ASYNCHRONOUS_IO +_SC_CLOCK_SELECTION +_SC_CPUTIME +_SC_FSYNC +_SC_IPV6 +_SC_JOB_CONTROL +_SC_MAPPED_FILES +_SC_MEMLOCK +_SC_MEMLOCK_RANGE +_SC_MEMORY_PROTECTION +_SC_MESSAGE_PASSING +_SC_MONOTONIC_CLOCK +_SC_PRIORITIZED_IO +_SC_PRIORITY_SCHEDULING +_SC_RAW_SOCKETS +_SC_READER_WRITER_LOCKS +_SC_REALTIME_SIGNALS +_SC_REGEXP +_SC_SAVED_IDS +_SC_SEMAPHORES +_SC_SHARED_MEMORY_OBJECTS +_SC_SHELL +_SC_SPAWN +_SC_SPIN_LOCKS +_SC_SPORADIC_SERVER +_SC_SS_REPL_MAX +_SC_SYNCHRONIZED_IO +_SC_THREAD_ATTR_STACKADDR +_SC_THREAD_ATTR_STACKSIZE +_SC_THREAD_CPUTIME +_SC_THREAD_PRIO_INHERIT +_SC_THREAD_PRIO_PROTECT +_SC_THREAD_PRIORITY_SCHEDULING +_SC_THREAD_PROCESS_SHARED +_SC_THREAD_ROBUST_PRIO_INHERIT +_SC_THREAD_ROBUST_PRIO_PROTECT +_SC_THREAD_SAFE_FUNCTIONS +_SC_THREAD_SPORADIC_SERVER +_SC_THREADS +_SC_TIMEOUTS +_SC_TIMERS +_SC_TRACE +_SC_TRACE_EVENT_FILTER +_SC_TRACE_EVENT_NAME_MAX +_SC_TRACE_INHERIT +_SC_TRACE_LOG +_SC_TRACE_NAME_MAX +_SC_TRACE_SYS_MAX +_SC_TRACE_USER_EVENT_MAX +_SC_TYPED_MEMORY_OBJECTS +_SC_VERSION +_SC_V7_ILP32_OFF32 +_SC_V7_ILP32_OFFBIG +_SC_V7_LP64_OFF64 +_SC_V7_LPBIG_OFFBIG +_SC_V6_ILP32_OFF32 +_SC_V6_ILP32_OFFBIG +_SC_V6_LP64_OFF64 +_SC_V6_LPBIG_OFFBIG +_SC_2_C_BIND +_SC_2_C_DEV +_SC_2_CHAR_TERM +_SC_2_FORT_DEV +_SC_2_FORT_RUN +_SC_2_LOCALEDEF +_SC_2_PBS +_SC_2_PBS_ACCOUNTING +_SC_2_PBS_CHECKPOINT +_SC_2_PBS_LOCATE +_SC_2_PBS_MESSAGE +_SC_2_PBS_TRACK +_SC_2_SW_DEV +_SC_2_UPE +_SC_2_VERSION +_SC_PAGE_SIZE +_SC_PAGESIZE +_SC_THREAD_DESTRUCTOR_ITERATIONS +_SC_THREAD_KEYS_MAX +_SC_THREAD_STACK_MIN +_SC_THREAD_THREADS_MAX +_SC_RE_DUP_MAX +_SC_RTSIG_MAX +_SC_SEM_NSEMS_MAX +_SC_SEM_VALUE_MAX +_SC_SIGQUEUE_MAX +_SC_STREAM_MAX +_SC_SYMLOOP_MAX +_SC_TIMER_MAX +_SC_TTY_NAME_MAX +_SC_TZNAME_MAX +_SC_XOPEN_CRYPT +_SC_XOPEN_ENH_I18N +_SC_XOPEN_REALTIME +_SC_XOPEN_REALTIME_THREADS +_SC_XOPEN_SHM +_SC_XOPEN_STREAMS +_SC_XOPEN_UNIX +_SC_XOPEN_UUCP +_SC_XOPEN_VERSION + +# non-standard +_SC_PHYS_PAGES +_SC_AVPHYS_PAGES +_SC_NPROCESSORS_CONF +_SC_NPROCESSORS_ONLN +_SC_CPUSET_SIZE + +# SUSv4 +_CS_PATH +_CS_POSIX_V7_ILP32_OFF32_CFLAGS +_CS_POSIX_V7_ILP32_OFF32_LDFLAGS +_CS_POSIX_V7_ILP32_OFF32_LIBS +_CS_POSIX_V7_ILP32_OFFBIG_CFLAGS +_CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS +_CS_POSIX_V7_ILP32_OFFBIG_LIBS +_CS_POSIX_V7_LP64_OFF64_CFLAGS +_CS_POSIX_V7_LP64_OFF64_LDFLAGS +_CS_POSIX_V7_LP64_OFF64_LIBS +_CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS +_CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS +_CS_POSIX_V7_LPBIG_OFFBIG_LIBS +_CS_POSIX_V7_THREADS_CFLAGS +_CS_POSIX_V7_THREADS_LDFLAGS +_CS_POSIX_V7_WIDTH_RESTRICTED_ENVS +_CS_V7_ENV +_CS_POSIX_V6_ILP32_OFF32_CFLAGS +_CS_POSIX_V6_ILP32_OFF32_LDFLAGS +_CS_POSIX_V6_ILP32_OFF32_LIBS +_CS_POSIX_V6_ILP32_OFFBIG_CFLAGS +_CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS +_CS_POSIX_V6_ILP32_OFFBIG_LIBS +_CS_POSIX_V6_LP64_OFF64_CFLAGS +_CS_POSIX_V6_LP64_OFF64_LDFLAGS +_CS_POSIX_V6_LP64_OFF64_LIBS +_CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS +_CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS +_CS_POSIX_V6_LPBIG_OFFBIG_LIBS +_CS_POSIX_V6_WIDTH_RESTRICTED_ENVS +_CS_V6_ENV + +# non-standard +_CS_GNU_LIBC_VERSION +_CS_GNU_LIBPTHREAD_VERSION + +# SUSv4 +_PC_FILESIZEBITS +_PC_LINK_MAX +_PC_MAX_CANON +_PC_MAX_INPUT +_PC_NAME_MAX +_PC_PATH_MAX +_PC_PIPE_BUF +_PC_2_SYMLINKS +_PC_ALLOC_SIZE_MIN +_PC_REC_INCR_XFER_SIZE +_PC_REC_MAX_XFER_SIZE +_PC_REC_MIN_XFER_SIZE +_PC_REC_XFER_ALIGN +_PC_SYMLINK_MAX +_PC_CHOWN_RESTRICTED +_PC_NO_TRUNC +_PC_VDISABLE +_PC_ASYNC_IO +_PC_PRIO_IO +_PC_SYNC_IO +_PC_TIMESTAMP_RESOLUTION + diff --git a/test/etc/test_etc.rb b/test/etc/test_etc.rb index 56fa4fbfbc..4c2b1f841d 100644 --- a/test/etc/test_etc.rb +++ b/test/etc/test_etc.rb @@ -125,4 +125,38 @@ class TestEtc < Test::Unit::TestCase assert_kind_of(String, uname[sym]) } end + + def test_sysconf + begin + Etc.sysconf + rescue NotImplementedError + return + rescue ArgumentError + end + assert_kind_of(Integer, Etc.sysconf(Etc::SC_CLK_TCK)) + end + + def test_confstr + begin + Etc.confstr + rescue NotImplementedError + return + rescue ArgumentError + end + assert_kind_of(String, Etc.confstr(Etc::CS_PATH)) + end + + def test_pathconf + begin + Etc.confstr + rescue NotImplementedError + return + rescue ArgumentError + end + IO.pipe {|r, w| + val = r.pathconf(Etc::PC_PIPE_BUF) + assert(val.nil? || val.kind_of?(Integer)) + } + end + end