├── .gitignore ├── .travis.yml ├── Dockerfile ├── README.md ├── Rakefile ├── bintest └── pfds.rb ├── build_config.rb ├── docker-compose.yml ├── mrbgem.rake ├── mrblib ├── pfds.rb └── pfds │ ├── config.rb │ ├── search.rb │ ├── top.rb │ └── version.rb ├── pfds.json ├── test └── test_pfds.rb └── tools └── pfds └── pfds.c /.gitignore: -------------------------------------------------------------------------------- 1 | mruby/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | sudo: false 4 | 5 | matrix: 6 | include: 7 | - os: linux 8 | sudo: 9000 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - gperf 14 | 15 | script: "rake" 16 | 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hone/mruby-cli 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pfds [![Build Status](https://travis-ci.org/matsumotory/pfds.svg?branch=master)](https://travis-ci.org/matsumotory/pfds) 2 | 3 | pfds - report a snapshot of the current processes fd 4 | 5 | pfds is one-binary and supports multi process. It's simple and powerful. 6 | 7 | ## usage 8 | 9 | - short version 10 | 11 | ```sh 12 | $ sudo pfds short `pgrep nginx` `pgrep httpd` | head 13 | 13963 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-includes/js/jquery/jquery-migrate.min.js 14 | 13963 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-includes/js/jquery/jquery.js 15 | 13963 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-includes/js/wp-emoji-release.min.js 16 | 13963 matsumoto_r /usr/local/apache-2.4.16/htdocs/moblog/robots.txt 17 | 13964 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/favicon.ico 18 | 13964 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/akismet/_inc/form.js 19 | 13964 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/codecolorer/codecolorer.css 20 | 13964 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/oembed-instagram/oembed-instagram.js 21 | 13964 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/shadowbox-js/css/extras.css 22 | 19940 matsumoto_r /usr/local/apache-2.4.16/htdocs/moblog/wp-content/uploads/2012/12/20121229-180225.jpg 23 | 19940 matsumoto_r /usr/local/apache-2.4.16/htdocs/moblog/wp-content/uploads/2012/12/20121224-202408.jpg 24 | ``` 25 | 26 | - output with cpu and memory usage 27 | 28 | ```sh 29 | $ sudo pfds `pgrep nginx` `pgrep httpd` | sort -nk2 | tail 30 | 13963 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-includes/js/jquery/jquery-migrate.min.js 31 | 13963 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-includes/js/jquery/jquery.js 32 | 13963 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-includes/js/wp-emoji-release.min.js 33 | 13963 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/moblog/robots.txt 34 | 13964 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/favicon.ico 35 | 13964 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/akismet/_inc/form.js 36 | 13964 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/codecolorer/codecolorer.css 37 | 13964 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/oembed-instagram/oembed-instagram.js 38 | 13964 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/shadowbox-js/css/extras.css 39 | 19940 0.0 0.3 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/wp-content/plugins/wp-social-bookmarking-light/images/hatena.gif 40 | 29391 24.5 2.4 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/index.php 41 | 29395 29.7 3.0 matsumoto_r /usr/local/apache-2.4.16/htdocs/blog/index.php 42 | ``` 43 | 44 | - Adjust multi process (default 4 process) 45 | 46 | ```sh 47 | sudo MRUBY_MULTI=24 pfds `pgrep httpd` 48 | ``` 49 | 50 | ```sh 51 | sudo MRUBY_MULTI=1 pfds `pgrep httpd` 52 | ``` 53 | 54 | ## build 55 | 56 | - custom `mrblib/pfds/config.rb` 57 | 58 | ```ruby 59 | module Pfds 60 | module Config 61 | 62 | MULTI_PROCESS = (ENV["MRUBY_MULTI"] || 4).to_i 63 | 64 | CONFIG_PATH = (ENV.key?("PFDS_CONFIG") ? ENV["PFDS_CONFIG"] : "/etc/pfds.json") 65 | 66 | end 67 | end 68 | ``` 69 | 70 | - build into `mruby/bin/pfds` 71 | 72 | ```sh 73 | rake 74 | ``` 75 | 76 | ## install to `/usr/local/bin` 77 | 78 | ```sh 79 | rake install 80 | ``` 81 | 82 | * Also put configration file to `/etc/pfds.json`. 83 | 84 | ## configure 85 | 86 | ```sh 87 | { 88 | "ignore_files": [ 89 | "/dev/null", 90 | "/var/log/httpd/access_log", 91 | "/var/log/httpd/error_log", 92 | "/var/log/httpd/ssl_access_log", 93 | "/var/log/httpd/ssl_error_log" 94 | ], 95 | "ignore_patterns": [ 96 | "^pipe:", 97 | "^anon_inode:", 98 | "^socket:", 99 | "^/dev/pts/" 100 | ] 101 | } 102 | ``` 103 | 104 | * config file path will be set by `PFDS_CONFIG` of `ENV`. 105 | 106 | # License 107 | under the MIT License: 108 | 109 | * http://www.opensource.org/licenses/mit-license.php 110 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | file :mruby do 2 | sh "git clone --depth=1 https://github.com/mruby/mruby" 3 | end 4 | 5 | APP_NAME=ENV["APP_NAME"] || "pfds" 6 | APP_ROOT=ENV["APP_ROOT"] || Dir.pwd 7 | # avoid redefining constants in mruby Rakefile 8 | mruby_root=File.expand_path(ENV["MRUBY_ROOT"] || "#{APP_ROOT}/mruby") 9 | mruby_config=File.expand_path(ENV["MRUBY_CONFIG"] || "build_config.rb") 10 | ENV['MRUBY_ROOT'] = mruby_root 11 | ENV['MRUBY_CONFIG'] = mruby_config 12 | Rake::Task[:mruby].invoke unless Dir.exist?(mruby_root) 13 | Dir.chdir(mruby_root) 14 | load "#{mruby_root}/Rakefile" 15 | 16 | desc "compile binary" 17 | task :compile => [:all] do 18 | %W(#{mruby_root}/build/x86_64-pc-linux-gnu/bin/#{APP_NAME} #{mruby_root}/build/i686-pc-linux-gnu/#{APP_NAME}").each do |bin| 19 | sh "strip --strip-unneeded #{bin}" if File.exist?(bin) 20 | end 21 | end 22 | 23 | namespace :test do 24 | desc "run mruby & unit tests" 25 | # only build mtest for host 26 | task :mtest => :compile do 27 | # in order to get mruby/test/t/synatx.rb __FILE__ to pass, 28 | # we need to make sure the tests are built relative from mruby_root 29 | MRuby.each_target do |target| 30 | # only run unit tests here 31 | target.enable_bintest = true 32 | run_test if target.test_enabled? 33 | end 34 | end 35 | 36 | def clean_env(envs) 37 | old_env = {} 38 | envs.each do |key| 39 | old_env[key] = ENV[key] 40 | ENV[key] = nil 41 | end 42 | yield 43 | envs.each do |key| 44 | ENV[key] = old_env[key] 45 | end 46 | end 47 | 48 | desc "run integration tests" 49 | task :bintest => :compile do 50 | MRuby.each_target do |target| 51 | clean_env(%w(MRUBY_ROOT MRUBY_CONFIG)) do 52 | run_bintest if target.bintest_enabled? 53 | end 54 | end 55 | end 56 | end 57 | 58 | desc "run all tests" 59 | Rake::Task['test'].clear 60 | task :test => ["test:mtest", "test:bintest"] 61 | 62 | desc "install to /usr/local/bin/" 63 | task :install do 64 | sh "sudo install -m 755 #{mruby_root}/bin/#{APP_NAME} /usr/local/bin" 65 | sh "sudo install -m 644 #{APP_ROOT}/#{APP_NAME}.json /etc" 66 | end 67 | 68 | desc "cleanup" 69 | task :clean do 70 | sh "rake deep_clean" 71 | end 72 | -------------------------------------------------------------------------------- /bintest/pfds.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | 3 | BIN_PATH = File.join(File.dirname(__FILE__), "../mruby/bin/pfds") 4 | 5 | assert('short self') do 6 | output, status = Open3.capture2(BIN_PATH, "short", "self") 7 | 8 | assert_true status.success?, "Process did not exit cleanly" 9 | assert_equal output.split("\n").size, 1 10 | assert_include output, "#{ENV['LOGNAME']} /proc/" 11 | end 12 | 13 | assert('self') do 14 | output, status = Open3.capture2(BIN_PATH, "self") 15 | 16 | assert_true status.success?, "Process did not exit cleanly" 17 | assert_equal output.split("\n").size, 1 18 | assert_include output, "#{ENV['LOGNAME']} /proc/" 19 | end 20 | -------------------------------------------------------------------------------- /build_config.rb: -------------------------------------------------------------------------------- 1 | def gem_config(conf) 2 | conf.gembox 'full-core' 3 | conf.gem :mgem => 'mruby-dir' 4 | conf.gem :mgem => 'mruby-io' 5 | conf.gem :mgem => 'mruby-iijson' 6 | conf.gem :mgem => 'mruby-process' 7 | conf.gem :mgem => 'mruby-env' 8 | conf.gem :mgem => 'mruby-mutex' 9 | conf.gem :mgem => 'mruby-regexp-pcre' 10 | conf.gem :mgem => 'mruby-file-stat' 11 | conf.gem :github => 'mattn/mruby-xquote' 12 | 13 | # be sure to include this gem (the cli app) 14 | conf.gem File.expand_path(File.dirname(__FILE__)) 15 | end 16 | 17 | MRuby::Build.new do |conf| 18 | toolchain :gcc 19 | 20 | conf.enable_bintest 21 | conf.enable_debug 22 | conf.enable_test 23 | 24 | gem_config(conf) 25 | end 26 | 27 | #MRuby::Build.new('x86_64-pc-linux-gnu') do |conf| 28 | # toolchain :gcc 29 | # 30 | # gem_config(conf) 31 | #end 32 | # 33 | #MRuby::CrossBuild.new('i686-pc-linux-gnu') do |conf| 34 | # toolchain :gcc 35 | # 36 | # [conf.cc, conf.cxx, conf.linker].each do |cc| 37 | # cc.flags << "-m32" 38 | # end 39 | # 40 | # gem_config(conf) 41 | #end 42 | # 43 | #MRuby::CrossBuild.new('x86_64-apple-darwin14') do |conf| 44 | # toolchain :clang 45 | # 46 | # [conf.cc, conf.linker].each do |cc| 47 | # cc.command = 'x86_64-apple-darwin14-clang' 48 | # end 49 | # conf.cxx.command = 'x86_64-apple-darwin14-clang++' 50 | # conf.archiver.command = 'x86_64-apple-darwin14-ar' 51 | # 52 | # conf.build_target = 'x86_64-pc-linux-gnu' 53 | # conf.host_target = 'x86_64-apple-darwin14' 54 | # 55 | # gem_config(conf) 56 | #end 57 | # 58 | #MRuby::CrossBuild.new('i386-apple-darwin14') do |conf| 59 | # toolchain :clang 60 | # 61 | # [conf.cc, conf.linker].each do |cc| 62 | # cc.command = 'i386-apple-darwin14-clang' 63 | # end 64 | # conf.cxx.command = 'i386-apple-darwin14-clang++' 65 | # conf.archiver.command = 'i386-apple-darwin14-ar' 66 | # 67 | # conf.build_target = 'i386-pc-linux-gnu' 68 | # conf.host_target = 'i386-apple-darwin14' 69 | # 70 | # gem_config(conf) 71 | #end 72 | # 73 | #MRuby::CrossBuild.new('x86_64-w64-mingw32') do |conf| 74 | # toolchain :gcc 75 | # 76 | # [conf.cc, conf.linker].each do |cc| 77 | # cc.command = 'x86_64-w64-mingw32-gcc' 78 | # end 79 | # conf.cxx.command = 'x86_64-w64-mingw32-cpp' 80 | # conf.archiver.command = 'x86_64-w64-mingw32-gcc-ar' 81 | # conf.exts.executable = ".exe" 82 | # 83 | # conf.build_target = 'x86_64-pc-linux-gnu' 84 | # conf.host_target = 'x86_64-w64-mingw32' 85 | # 86 | # gem_config(conf) 87 | #end 88 | # 89 | #MRuby::CrossBuild.new('i686-w64-mingw32') do |conf| 90 | # toolchain :gcc 91 | # 92 | # [conf.cc, conf.linker].each do |cc| 93 | # cc.command = 'i686-w64-mingw32-gcc' 94 | # end 95 | # conf.cxx.command = 'i686-w64-mingw32-cpp' 96 | # conf.archiver.command = 'i686-w64-mingw32-gcc-ar' 97 | # conf.exts.executable = ".exe" 98 | # 99 | # conf.build_target = 'i686-pc-linux-gnu' 100 | # conf.host_target = 'i686-w64-mingw32' 101 | # 102 | # gem_config(conf) 103 | #end 104 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | compile: &defaults 2 | build: . 3 | volumes: 4 | - .:/home/mruby/code:rw 5 | command: rake compile 6 | test: 7 | <<: *defaults 8 | command: rake test 9 | bintest: 10 | <<: *defaults 11 | command: rake test:bintest 12 | mtest: 13 | <<: *defaults 14 | command: rake test:mtest 15 | clean: 16 | <<: *defaults 17 | command: rake clean 18 | shell: 19 | <<: *defaults 20 | command: bash 21 | -------------------------------------------------------------------------------- /mrbgem.rake: -------------------------------------------------------------------------------- 1 | MRuby::Gem::Specification.new('pfds') do |spec| 2 | spec.license = 'MIT' 3 | spec.author = 'MATSUMOTO Ryosuke' 4 | spec.summary = 'pfds' 5 | spec.bins = ['pfds'] 6 | 7 | spec.add_dependency 'mruby-print', :core => 'mruby-print' 8 | spec.add_dependency 'mruby-array-ext', :core => 'mruby-array-ext' 9 | spec.add_dependency 'mruby-hash-ext', :core => 'mruby-hash-ext' 10 | spec.add_dependency 'mruby-mtest', :mgem => 'mruby-mtest' 11 | spec.add_dependency 'mruby-env', :mgem => 'mruby-env' 12 | spec.add_dependency 'mruby-dir', :mgem => 'mruby-dir' 13 | spec.add_dependency 'mruby-io', :mgem => 'mruby-io' 14 | spec.add_dependency 'mruby-process', :mgem => 'mruby-process' 15 | spec.add_dependency 'mruby-env', :mgem => 'mruby-env' 16 | spec.add_dependency 'mruby-mutex', :mgem => 'mruby-mutex' 17 | spec.add_dependency 'mruby-regexp-pcre', :mgem => 'mruby-regexp-pcre' 18 | spec.add_dependency 'mruby-file-stat', :mgem => 'mruby-file-stat' 19 | end 20 | -------------------------------------------------------------------------------- /mrblib/pfds.rb: -------------------------------------------------------------------------------- 1 | def __main__(argv) 2 | exit if argv.size == 1 3 | if argv[1] == "version" 4 | puts "v#{Pfds::VERSION}" 5 | else 6 | pids = argv[1..-1] 7 | Pfds.run pids 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /mrblib/pfds/config.rb: -------------------------------------------------------------------------------- 1 | module Pfds 2 | module Config 3 | 4 | MULTI_PROCESS = (ENV["MRUBY_MULTI"] || 4).to_i 5 | 6 | CONFIG_PATH = (ENV.key?("PFDS_CONFIG") ? ENV["PFDS_CONFIG"] : "/etc/pfds.json") 7 | 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /mrblib/pfds/search.rb: -------------------------------------------------------------------------------- 1 | module Pfds 2 | 3 | @@config = {} 4 | @@gmutex = nil 5 | @@top = nil 6 | 7 | class << self 8 | 9 | def file_ignore? path, ignore = [] 10 | ignore = @@config["ignore_files"] if ignore.empty? and @@config.key? "ignore_files" 11 | (path == "" or ignore.include? path) ? true : false 12 | end 13 | 14 | def pattern_ignore? path, ignore = [] 15 | ignore = @@config["ignore_patterns"] if ignore.empty? and @@config.key? "ignore_patterns" 16 | is_next = false 17 | ignore.each do |pattern| 18 | if Regexp.new(pattern).match(path) 19 | is_next = true 20 | break 21 | end 22 | end 23 | is_next 24 | end 25 | 26 | def search_from_pids pids 27 | files = [] 28 | pids.each do |pid| 29 | fd_dir = "/proc/#{pid}/fd/" 30 | begin 31 | Dir.foreach(fd_dir) do |fd| 32 | begin 33 | realpath = pfds_readlink("#{fd_dir}/#{fd}").to_s 34 | rescue 35 | next 36 | end 37 | next if self.file_ignore? realpath 38 | next if self.pattern_ignore? realpath 39 | username = nil 40 | begin 41 | username = pfds_get_username(File::Stat.new(realpath).uid) 42 | rescue 43 | username = pfds_get_username(File::Stat.new("#{fd_dir}/#{fd}").uid) 44 | end 45 | pid = Process.pid.to_s if pid == "self" 46 | if @@top.nil? 47 | files << sprintf("% 6d %s %s", pid.to_i, username, realpath) 48 | else 49 | files << sprintf("% 6d %5.1f %4.1f %s %s", pid.to_i, @@top[pid][7].to_f, @@top[pid][8].to_f, username, realpath) 50 | end 51 | end 52 | rescue 53 | next 54 | end 55 | end 56 | files 57 | end 58 | 59 | def output files 60 | @@gmutex.lock unless @@gmutex.nil? 61 | files.uniq.each { |f| puts f } 62 | @@gmutex.unlock unless @@gmutex.nil? 63 | end 64 | 65 | def run_task pids 66 | self.output search_from_pids(pids) 67 | end 68 | 69 | def multi_run_task pids 70 | wait_pids = [] 71 | multi_pids = pids.group_by { |pid| pid.to_i % Config::MULTI_PROCESS } 72 | multi_pids.each_value { |pids| wait_pids << Process.fork { self.run_task pids} } 73 | wait_pids.each { |pid| Process.waitpid pid } 74 | end 75 | 76 | def short? pids 77 | pids[0] == "short" 78 | end 79 | 80 | def load_config path 81 | if File.exist?(path) 82 | begin 83 | @@config = JSON.parse(File.open(path).read()) 84 | rescue => e 85 | STDERR.print "JSON parse error: #{path} #{e}\n" 86 | exit false 87 | end 88 | end 89 | end 90 | 91 | def run argv 92 | self.load_config Config::CONFIG_PATH 93 | 94 | @@top = Top.new.run.data unless short? argv 95 | pids = (@@top.nil?) ? argv[1..-1] : argv 96 | 97 | if pids.size > Config::MULTI_PROCESS 98 | @@gmutex = Mutex.new :global => true 99 | self.multi_run_task pids 100 | else 101 | self.run_task pids 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /mrblib/pfds/top.rb: -------------------------------------------------------------------------------- 1 | module Pfds 2 | class Top 3 | attr_reader :data 4 | 5 | def initialize 6 | @data = {} 7 | self 8 | end 9 | 10 | def create_top_data output 11 | output.split("\n").each do |line| 12 | line_ary = line.gsub(/^\s+/, "").split(/\s+/) 13 | if !line_ary[0].nil? and line_ary[0].to_i != 0 and line_ary.size == 12 14 | @data.merge!(line_ary[0] => line_ary[1..11]) 15 | end 16 | end 17 | end 18 | 19 | def run 20 | create_top_data `top -b -n1` 21 | self 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /mrblib/pfds/version.rb: -------------------------------------------------------------------------------- 1 | module Pfds 2 | VERSION = "0.2.0" 3 | end 4 | -------------------------------------------------------------------------------- /pfds.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_files": [ 3 | "/dev/null", 4 | "/var/log/httpd/access_log", 5 | "/var/log/httpd/error_log", 6 | "/var/log/httpd/ssl_access_log", 7 | "/var/log/httpd/ssl_error_log" 8 | ], 9 | "ignore_patterns": [ 10 | "^pipe:", 11 | "^anon_inode:", 12 | "^socket:", 13 | "^/dev/pts/" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /test/test_pfds.rb: -------------------------------------------------------------------------------- 1 | class TestPfds < MTest::Unit::TestCase 2 | @@test_config = { 3 | "ignore_files" => [ 4 | "/var/log/httpd/access_log" 5 | ], 6 | "ignore_patterns" => [ 7 | "^socket:" 8 | ] 9 | } 10 | 11 | def test_main 12 | assert_nil __main__(["self"]) 13 | end 14 | def test_file_ignore 15 | assert_true Pfds.file_ignore?("/var/log/httpd/access_log", @@test_config["ignore_files"]) 16 | end 17 | def test_file_ignore_false 18 | assert_false Pfds.file_ignore?("/var/log/httpd/access_log.1", @@test_config["ignore_files"]) 19 | end 20 | def test_pattern_ignore 21 | assert_true Pfds.pattern_ignore?("socket:00000", @@test_config["ignore_patterns"]) 22 | end 23 | def test_pattern_ignore_false 24 | assert_false Pfds.pattern_ignore?("tcp:00000", @@test_config["ignore_patterns"]) 25 | end 26 | end 27 | 28 | MTest::Unit.new.run 29 | -------------------------------------------------------------------------------- /tools/pfds/pfds.c: -------------------------------------------------------------------------------- 1 | // This file is generated by mruby-cli. Do not touch. 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /* Include the mruby header */ 13 | #include 14 | #include 15 | 16 | static char *mrb_get_username(mrb_state *mrb, uid_t uid) 17 | { 18 | struct passwd *pw; 19 | 20 | if (uid < 0) { 21 | mrb_raise(mrb, E_RUNTIME_ERROR, "invalid uid"); 22 | } 23 | 24 | pw = getpwuid(uid); 25 | 26 | if (pw == NULL) { 27 | mrb_raise(mrb, E_RUNTIME_ERROR, "getpwuid failed"); 28 | } 29 | 30 | return pw->pw_name; 31 | } 32 | 33 | static mrb_value mrb_f_get_username(mrb_state *mrb, mrb_value self) 34 | { 35 | mrb_int uid; 36 | 37 | mrb_get_args(mrb, "i", &uid); 38 | 39 | return mrb_str_new_cstr(mrb, mrb_get_username(mrb, uid)); 40 | } 41 | 42 | static int is_symlink(char *filepath) 43 | { 44 | struct stat sb = {0}; 45 | int rc = 0; 46 | 47 | rc = lstat(filepath, &sb); 48 | if (rc < 0) { 49 | return -1; 50 | } 51 | if (S_ISLNK(sb.st_mode)) { 52 | return 1; 53 | } 54 | 55 | return 0; 56 | } 57 | 58 | static mrb_value mrb_read_symlink(mrb_state *mrb, char *filepath) 59 | { 60 | ssize_t len = 0; 61 | char buf[4096] = {"\0"}; 62 | char *path; 63 | int rc = 0; 64 | 65 | rc = is_symlink(filepath); 66 | if (rc != 1) { 67 | return mrb_str_new_lit(mrb, ""); 68 | } 69 | len = readlink(filepath, buf, sizeof(buf) - 1); 70 | if (len < 0) { 71 | return mrb_false_value(); 72 | } 73 | path = mrb_malloc(mrb, len + 1); 74 | memcpy(path, buf, len); 75 | path[len] = '\0'; 76 | 77 | return mrb_str_new_cstr(mrb, path); 78 | } 79 | 80 | static mrb_value mrb_f_readlink(mrb_state *mrb, mrb_value self) 81 | { 82 | char *path; 83 | mrb_value new_path; 84 | 85 | mrb_get_args(mrb, "z", &path); 86 | new_path = mrb_read_symlink(mrb, path); 87 | if (mrb_type(new_path) == MRB_TT_FALSE) { 88 | mrb_raise(mrb, E_RUNTIME_ERROR, "readlink failed"); 89 | } 90 | 91 | return new_path; 92 | } 93 | 94 | int main(int argc, char *argv[]) 95 | { 96 | mrb_state *mrb = mrb_open(); 97 | mrb_value ARGV = mrb_ary_new_capa(mrb, argc); 98 | int i; 99 | int return_value; 100 | 101 | for (i = 0; i < argc; i++) { 102 | mrb_ary_push(mrb, ARGV, mrb_str_new_cstr(mrb, argv[i])); 103 | } 104 | mrb_define_global_const(mrb, "ARGV", ARGV); 105 | 106 | mrb_define_method(mrb, mrb->kernel_module, "pfds_readlink", mrb_f_readlink, MRB_ARGS_REQ(1)); 107 | mrb_define_method(mrb, mrb->kernel_module, "pfds_get_username", mrb_f_get_username, MRB_ARGS_REQ(1)); 108 | 109 | // call __main__(ARGV) 110 | mrb_funcall(mrb, mrb_top_self(mrb), "__main__", 1, ARGV); 111 | 112 | return_value = EXIT_SUCCESS; 113 | 114 | if (mrb->exc) { 115 | mrb_print_error(mrb); 116 | return_value = EXIT_FAILURE; 117 | } 118 | mrb_close(mrb); 119 | 120 | return return_value; 121 | } 122 | --------------------------------------------------------------------------------