HdInfo and NetInfo
Vou descorrer aqui sobre minha primeira experiência integrando Ruby com o Sistema Operacional chamado de Extending Ruby. Se você veio do Java como eu chamaria de Ruby Native Interface.
Instalação
Para compilação em C utilizamos os pacotes básicos do gcc e para ruby o adicional ruby-dev, portanto segue os abaixo.
(Não testei as versões anteriores do gcc ou do ruby).
gcc-4.1 #The GNU C compiler gcc-4.1-base #The GNU Compiler Collection (base package) ruby1.8 #Interpreter of object-oriented scripting lan ruby1.8-dev #Header files for compiling extension modules
Algumas coisas antes do teste
Serão necessárias conversões de tipos entre C e Ruby para isso utilizaremos as funções:
char * STR2CSTR(String) #converte uma String Ruby em um ponteiro de char em C (char *) VALUE rb_str_new2(char *) #cria uma String em Ruby baseado em uma String em C
retorno dos métodos e variáveis convertidos em Ruby serão tipados para VALUE, contudo para STR2CSTR receberemos um VALUE(uma coisa ruby).
Uma Prévia do Init_(Nome)
Toda classe ou módulo define uma função Init_Nome, onde Nome é o nome da classe/módulo, que será invocada inicialmente antes de quaisquer outras funções/métodos.
Hello Humans
Vamos criar nosso “Hello Humans”. Crie um arquivo hello.c contendo o seguinte trecho.
#include <ruby.h> static VALUE t_minha_string(VALUE self){ VALUE r; //declara r como VALUE char *str_c = "Hello Humans!!"; //Cria a string em C //Cria a string em Ruby e atribui a r r=rb_str_new2(str_c); return r; } //Declaração da futura classe Hello VALUE cHello; void Init_Hello() { //Definindo a classe Hello em ruby herdando de Object cHello = rb_define_class("Hello", rb_cObject); //Mapeando o t_minha_string para to_s em ruby, 0 é a quantidade de parâmetros rb_define_method(cHello, "to_s", t_minha_string, 0); }
Dá pra simplifica t_minha_string em
static VALUE t_minha_string(VALUE self){ return rb_str_new2("Hello Humans!!"); }
Observe a declaração cHello ele deverá ser global como no exemplo acima.
Veja que as coisas se encaixam, cHello recebe uma nova referência com o nome de Hello herdando da classe rb_cObject que é uma constante para classe Object em ruby. E t_minha_string em C refletindo em to_s em Ruby.
Make
Vamos criar agora o MAKEFILE. Você deve estar pensando, “nossa que droga! Aquela tripa de parâmetros que sempre dá erro faltando alguma coisa…”. Pare com isso! Em ruby é sempre mais fácil :). Crie um arquivo extconfig.rb com o conteúdo.
require "mkmf" create_makefile("Hello");
Rode com
ruby extconfig.rb
A mensagem “creating Makefile” aparecerá no output.
Agora dê um
make
Veja a saída:
gcc -fPIC -Wall -g -fno-strict-aliasing -O2 -fPIC -I. -I/usr/lib/ruby/1.8/i486-linux -I/usr/lib/ruby/1.8/i486-linux -I. -c hello.c gcc -shared -L"/usr/lib" -o Hello.so hello.o -lruby1.8 -lpthread -ldl -lcrypt -lm -lc
Linka e já cria o shared object.
Agora com permissão de escria em /usr/lib, instale com
make install
Veja a saída:
/usr/bin/install -c -m 0755 Hello.so /usr/local/lib/site_ruby/1.8/i486-linux
já copia o .so para o diretório correto do ruby.
Testando
Abra seu irb e digite na seqüencia:
require "Hello" h=Hello.new puts h.to_s
Ficando assim.
irb(main):001:0> require "Hello" => true irb(main):002:0> h=Hello.new => Hello Humans!! irb(main):003:0> puts h.to_s Hello Humans!! => nil irb(main):004:0>
Fácil né :).
Acessando informações do HD e das Interfaces de Rede
Criei duas classes uma HdInfo e outra NetInfo em Extend Ruby.
HdInfo
Aqui poderemos demonstrar mais poder em nossas mãos, vamos obter informações sobre nosso hd e depois setar os keys/values em uma classe herdanda de Hash.
serial_no, model, fw_rev são variáveis da biblioteca linux/hdreg.h
Acompanhe pelos comentários.
/* Shairon Toledo shairon.toledo@gmail.com http://www.hashcode.eti.br Seg Abr 2 13:58:17 BRT 2007 */ #include "ruby.h" #include <stdio.h> #include <stdlib.h> #include <linux/hdreg.h> #include <fcntl.h> #include <sys/ioctl.h> static VALUE t_init(VALUE self,VALUE anObject){ struct hd_driveid id; int disk ; int opened; //Abre o ponteiro inicial do disco passando //paramentro de entrada, ex: /dev/hda, /dev/hdb disk=open(STR2CSTR(anObject), O_RDONLY|O_NONBLOCK); //seta as props do driver do disco em id //opened armazena o estado para tratar erro, a seguir opened = ioctl(disk, HDIO_GET_IDENTITY, &id); //Cria os key convertendo um (const char *) para RubyString VALUE k[4],v[4]; k[0]=rb_str_new2("serial_no"); k[1]=rb_str_new2("model"); k[2]=rb_str_new2("fw_rev"); k[3]=rb_str_new2("device_name"); //cria as String baseados nos params do so struct hd_driveid v[0]=rb_str_new2((char*) id.serial_no); v[1]=rb_str_new2((char*) id.model); v[2]=rb_str_new2((char*) id.fw_rev); v[3]=anObject;//copia o valor do param de entrada para v[3] int i; for (i=0;i<4;i++){ //laço de atribuição rb_hash_aset(self,k[i],v[i]); } VALUE err=(opened)?Qtrue:Qfalse; //seta o erro. Q(true|false) são constantes equivanete a TrueClass/FalseClass em Ruby rb_iv_set(self, "@error",err); return self; } //Método para leitura da variável @error static VALUE t_error(VALUE self, VALUE anObject){ //Obtem e retorna o valor return rb_iv_get(self, "@error"); } VALUE cHdInfo; void Init_HdInfo() { //HdInfo < Hash em C cHdInfo = rb_define_class("HdInfo", rb_cHash); //Mapeia t_init para initialize do ruby , 1 parametro => nome do device rb_define_method(cHdInfo, "initialize", t_init, 1); //Mapeia t_error para error? em ruby, 0 parametro rb_define_method(cHdInfo, "error?", t_error, 0); }
Para criar o makefile é o mesmo processo do “Hello Humans” mas com a classe HdInfo. Ex. do extconfig.rb para HdInfo:
require "mkmf" create_makefile("HdInfo");
Rode com
ruby extconfig.rb
depois
make
e
make install
Vamos testar com irb
irb(main):001:0> require "HdInfo" => true irb(main):002:0> hd=HdInfo.new("/dev/hda") => {"serial_no"=>"0881J1FX406355", "fw_rev"=>"TW100-11", "device_name"=>"/dev/hda", "model"=>"SAMSUNG SP0411N"} irb(main):003:0> hd.each{|k,v| p "#{k}=#{v}"} "serial_no=0881J1FX406355" "fw_rev=TW100-11" "device_name=/dev/hda" "model=SAMSUNG SP0411N" => {"serial_no"=>"0881J1FX406355", "fw_rev"=>"TW100-11", "device_name"=>"/dev/hda", "model"=>"SAMSUNG SP0411N"} irb(main):004:0> hd.error? => false
Lagalzin!
NetInfo
A classe NetInfo segue o mesmo princípio da HdInfo. E a geração do .so da mesma forma, extconfig.rb, make, make install …
Vamos obter as informações de ipaddress, netmask e mac address da interface de rede(lib net/if.h e arpa/inet.h).
Acompanhe pelos comentários.
/* Shairon Toledo shairon.toledo@gmail.com http://www.hashcode.eti.br Seg Abr 2 11:30:26 BRT 2007 */ #include "ruby.h" #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #include <errno.h> #include <unistd.h> static VALUE t_init(VALUE self,VALUE anObject){ int sock, i; struct sockaddr_in *sin; struct ifreq ifdev; char *ifname=STR2CSTR(anObject); //converte anObject(RubyString) em char* VALUE k[4]; //Declara k como objetos ruby //Nossas chaves k[0]=rb_str_new2("netmask"); k[1]=rb_str_new2("ipaddress"); k[2]=rb_str_new2("macaddress"); k[3]=rb_str_new2("ifname"); //ifname = ao parâmetro de inicialização rb_hash_aset(self,k[3],anObject); //Cria o socket para obter as informações sock = socket(AF_INET, SOCK_DGRAM, 0); sprintf(ifdev.ifr_name, "%s", ifname); //SIOCGIFNETMASK infomacões de máscara ioctl(sock, SIOCGIFNETMASK, &ifdev); sin = (struct sockaddr_in *)&ifdev.ifr_broadaddr; //Atribui o valor do ip para a chave k[0] convertendo em RubyString rb_hash_aset(self,k[0],rb_str_new2(inet_ntoa(sin->sin_addr))); //Idem acima mas para IFADDR ioctl(sock, SIOCGIFADDR, &ifdev); sin = (struct sockaddr_in *)&ifdev.ifr_broadaddr; rb_hash_aset(self,k[1],rb_str_new2(inet_ntoa(sin->sin_addr))); //Obtem propriedades físicas da interface ioctl(sock, SIOCGIFHWADDR, &ifdev); char m[2]; char *mac_addr= malloc(500*sizeof(char)); for (i=0;i<6;i++){ //Formatando sprintf(m, "%02X", (unsigned char)ifdev.ifr_hwaddr.sa_data[i] ); strcat(mac_addr,m); } //Seta o mac address no self usando o key k[2] rb_hash_aset(self,k[2],rb_str_new2(mac_addr)); close(sock); free(mac_addr); return self; } VALUE cNetInfo; void Init_NetInfo() { //NetInfo < Hash em C cNetInfo = rb_define_class("NetInfo", rb_cHash); //Mapeia t_init para o initialize do ruby, 1 parametro => if_name rb_define_method(cNetInfo, "initialize", t_init, 1); }
Testando no irb
irb(main):001:0> require "NetInfo" => true irb(main):002:0> net=NetInfo.new("eth0") => {"ipaddress"=>"192.168.1.1", "netmask"=>"255.255.255.0", "ifname"=>"eth0", "macaddress"=>"00112F3683C9"}
outro teste, criando um teste.rb
puts NetInfo.new("eth0").inspect puts NetInfo.new("eth1").inspect puts NetInfo.new("lo").inspect
output
{"ipaddress"=>"192.168.1.1", "netmask"=>"255.255.255.0", "ifname"=>"eth0", "macaddress"=>"00112F3683C9"} {"ipaddress"=>"10.1.1.101", "netmask"=>"255.0.0.0", "ifname"=>"eth1", "macaddress"=>"00E07DC96741"} {"ipaddress"=>"127.0.0.1", "netmask"=>"255.0.0.0", "ifname"=>"lo", "macaddress"=>"000000000000"}
Bem é isso aí. Gostei muito disso principalmente pela facilidada da criação do makefile. Os artefatos dos exemplos estão aqui ext_ruby_info_hd_net.tar.gz.
Referências
http://kasperd.net/~kasperd/comp.os.linux.development.faq
http://www.rubycentral.com/book/ext_ruby.html
http://www.ruby-doc.org/core/classes/Hash.html
file:///usr/include/net/if.h
file:///usr/include/arpa/inet.h