Usando POSIX.2 regular expressions em C

A desculpa para aprender PERL ou Python, às vezes, é a facilidade com que pode-se usar regular expressions (que vou chamar de regex daqui pra frente) nessas linguagens. Ou, melhor ainda, a impossibilidade de usá-las em C. Isso não é verdade se você está acostumado, como eu, a usar o GNU C/C++ Compiler. A biblioteca padrão de C (libc) possui 3 funções específicas para lidar com regex, no sabor POSIX.2.

Não vou explicar como construir uma regex. Basta dizer que usá-las significa poupar um bocado de código com comparação de padrões de strings. Uma regex que pode ser usada para verificar o padrão de um endereço de e-mail seria mais ou menos parecida com isso: “^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$”. No seu programa, ao testar uma string contra uma regex, obterá um “match” (traduzível como “string bate com o padrão da regex) ou “not match”.

Tudo o que você precisa fazer para usar as funções de regex é incorporar o header regex.h e as 3 funções declaradas nele:

int regcomp(regex_t *preg, const char *regex, int cflags);
int regexec(const regex_t *preg, const char *string, 
            size_t match, regmatch_t pmatch[], int eflags);
void regfree(regex_t *preg);

A função regcomp() retorna 0 se a regex estiver correta, a função regexc retorna 0 se houve um match da string contra a regex.

A primeira coisa a fazer é compilar a regex chamando a função regcomp(). Se ela falhar, significa que tem um erro na sua regex:

#define MY_REGEX "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$"

regex_t re;

if (regcomp(&re, MY_REGEX, REG_EXTENDED))
{
  /* Tratamento de erro aqui. */
}

Uma vez que a regex esteja compilada, basta usar regexec() para verificar se houve um match:

if (regexec(&re, str, 0, NULL, 0) == 0)
{
  /* Se a string 'str' *bate* com o padrão da regex, faz algo aqui! */
}

E, finalmente, temos que nos livrar da regex compilada:

regfree(&re);

Simples e fácil. Mas, porque regexec() tem esses 3 parâmetros no final da função? O último parâmtro são flags que podem ser usados para mudar o significado de ‘^’ e ‘$’ na regex. Os parâmetros matchpmatch são, respectivamente, um contador de matches e um array de strings matched. Isso está ai porque numa regex podemos ter múltiplos agrupamentos, que podem ser obtidos como matches individuais. Por exemplo, suponha que na regex que testa e-mails, acima, queiramos saber, separadamente, o nome do usuário e o nome do servidor. Poderíamos criar agrupamentos assim: “^([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,4})$”. Notou os parenteses?

A função regexec(), se receber um valor diferente de 0 no parâmetro match e um ponteiro diferente de NULL em pmatch, preencherá o array pmatch com o par de índices − início e fim − para string original onde estão as substrings dos agrupamentos. O primeiro item do array conterá os índices que batem com a regex inteira. O segundo item, para o primeiro agrupamento; O terceiro, para o segundo e assim por diante. O tipo regmatch_t contém apenas dois inteiros: rm_so e rm_eo (abreviações de “Regex Match Start Offset” e “Regex Match End Offset“. A especificação da função regexec() nos diz que, se o membro rm_so for -1, então não há um “match” na posição específica do array pmatch.

No código acima, se quisermos obter esses offsets dos matches da regex do e-mail, com os agrupamentos, teríamos que declarar o array e chamar regexec() assim:

/* Declara array com 3 itens: O match inteiro e os 2 agrupamentos. */
regmatch_t rm[3];

if (regexec(&re, str, sizeof(rm), rm, 0) == 0)
{
  /* Se a string 'str' *bate* com o padrão da regex, faz algo aqui! */
}

Cabe a você obter as substrings a partir desses offsets e da string original.

Um exemplo complexo (mas, nem tanto).

Eis como obter um endereço IP (que pode ser parcial) e pode ter um CIDR atachado a ele, usando regex:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <regex.h>

/* Macro usada para testar se há um "match" no array 
   preenchido por regexec(). */
#define MATCH(x) ((x).rm_so >= 0)

/* Conserta "bug" de strncpy().
   Parece que strncpy() não coloca '' no fim da 
   string copiada. */
#define COPY_SUBSTR(d, s, l) { \
    strncpy((d), (s), (l)); \
    *((d) + (l)) = ''; \
  }

typedef struct
{
  unsigned addr;
  unsigned cidr;
} CIDR;

/* Regex, no formato POSIX.2 ERE (Extended).
   Complicado, né? Com múltiplos agrupamentos! */
#define IP_REGEX "^([1-2]*[0-9]{1,2})" \
  "(\\.[1-2]*[0-9]{1,2}){0,1}" \
  "(\\.[1-2]*[0-9]{1,2}){0,1}" \
  "(\\.[1-2]*[0-9]{1,2}){0,1}" \
  "(/[0-9]{1,2}){0,1}$"

/* Obtem IP e CIDR a partir de uma string.
   Retorna 0, em caso de falha e 1, se tudo ok. */
int str_to_ip_and_cidr(char const *const addr, CIDR *cidr_ptr)
{
  regex_t re;
  regmatch_t rm[6];
  unsigned matches[5];
  int i, len;
  char *t;

  cidr_ptr->addr = cidr_ptr->cidr = 0;

  /* Tenta compilar a regex. */
  if (regcomp(&re, IP_REGEX, REG_EXTENDED))
    return 0;

  /* Executa a regex contra a string. */
  if (regexec(&re, addr, sizeof(rm), rm, 0))
  {
    regfree(&re);
    return 0;
  }

  /* Aloca espaço suficiente para as substrings. */
  if ((t = strdup(addr)) == NULL)
  {
    perror("Cannot allocate temporary string");
    abort();
  }

  /* Converte os octetos que "bateram".
     Pela regex, o primeiro sempre "bate".
   */
  len = rm[1].rm_eo - rm[1].rm_so;
  COPY_SUBSTR(t, addr + rm[1].rm_so, len);
  matches[0] = atoi(t);

  /* Converte os outros 3 octetos, se existirem.
     Note que as substrings começam com '.'. */
  for (i = 2; i <= 4; i++)
  {
    /* Pela regex, esses octetos podem nem
       ter sido informados! */
    if (MATCH(rm[i]))
    {
      len = rm[i].rm_eo - rm[i].rm_so - 1;
      COPY_SUBSTR(t, addr + rm[i].rm_so + 1, len);
      matches[i - 1] = atoi(t);
    }
    else
      matches[i - 1] = 0;
  }

  /* Converte o cidr.
     Pela regex ele pode nem ter sido informado!
     Note que a substring começa com '/'. */

  if (MATCH(rm[5]))
  {
    len = rm[5].rm_eo - rm[5].rm_so - 1;
    COPY_SUBSTR(t, addr + rm[5].rm_so + 1, len);

    /* CIDRs merecem um tratamento especial.
       Quando informados, precism ser >= 1. */
    if ((matches[4] = atoi(t)) == 0)
    {
      /* if cidr is actually '0', then it is an error! */
      free(t);
      regfree(&re);
      return 0;
    }
  }
  else
    matches[4] = 32;

  /* Não precisamos mais da string temporária. */
  free(t);

  /* Valida os octetos */
  for (i = 0; i < 4; i++)
    if (matches[i] > 255)
    {
      regfree(&re);
      return 0;
    }

  /* Valida o cidr. */
  if (matches[4] > 32)
  {
    regfree(&re);
    return 0;
  }

  /* Prepara a estrutura do CIDR. */
  cidr_ptr->cidr = matches[4];
  cidr_ptr->addr = (((matches[3] & 0xff))       |
                    ((matches[2] & 0xff) << 8)  |
                    ((matches[1] & 0xff) << 16) |
                    ((matches[0] & 0xff) << 24)) &
                   (0xffffffffUL << (32 - cidr_ptr->cidr));

  regfree(&re);
  return 1;
}

A função acima aceita as variações como verdadeiras: a; a.b; a.b.c; a.b.c.d; a/n; a.b/n; a.b.c/n e a.b.c.d/n − onde a, b, c e d podem ser valores entre 0 e 255 e n entre 1 e 32. Em qualquer outro caso a função retorna 0 (falha). A função ainda toma conta de outro detalhe: Se fornecermos a string “10.4.23.2/16”, o valor que estará no campo addr da estrutura CIDR será equivalente a 10.4.0.0/16, já que 16 bits de CIDR significa que os 16 bit superiores é que contam (netmask de 16 bits).

Uma “feature” adicional de minha rotina: Se ela falhar, a estrutura CIDR conterá { 0, 0 }!

A função parece grande, mas garanto que fazer uma lógica parecida usando strtok(), por exemplo, a faria bem mais complicada!

Uma nota de cautela

Existem vários “sabores” de regexes por ai. PERL tem o dele próprio, por exemplo… As regexes válidas na libc são aquelas que seguem o padrão POSIX.2 (tanto a básica quanto a extendida). É útil dar uma olhada na manpage sobre regex:

$ man 7 regex

Outro cuidado é quanto ao compilador… Se você usa o Visual C++ não encontrará essa implementação por lá. Ao que parece, a implementação da Microsoft só existe em C++, em forma de classe, e apenas para a variação da gramática de C++ para .NET. Então, se você quer usar regexes nos seus programas em C, no Windows, recomendo o uso do MinGW32 ou MinGW64. Isso tem uma vantagem: Ambos estão disponíveis, em forma de pacotes, inclusive para Linux (sim! vocẽ pode desenvolver aplicações para Windows no Linux!).

Fontes… De informação

Utilizo a um bom tempo algumas fontes de informação e em especial a que mais gosto é a do Linux Weekley News.
Hoje, infelizmente, eu não estou mais assinando como fazia no passado.
Mas como a característica daquele site é ser open, uma reportagem disponível para assinantes fica, depois de um certo tempo, liberada para leitura para os “pobres”.
Hoje saiu uma chamada para um assunto que tenho bastante interesse e já falei algumas vezes aqui: orientação a objetos utilizando linguagens sem suporte formal à técnica.
Esta abordagem foi feita no gtk e diversos projetos a utilizam.
Sempre me pergunto, já sabendo de antemão a resposta, o por quê de assim ser feito? As respostas podem variar, mas a minha predileta é a que atribui à melhoria de performance. Mas, existe outra que me estimula explorar melhor a questão: organização e reutilização de código (e projetos).
Sabia que o nosso venerado linux faz esta abordagem, visando performance, mas a organização, pelo menos para quem não tem familiaridade com projetos de envergaduras maiores, é um tanto caótica.
Enfim, lançaram parte 1 deste artigo: Object-oriented design patterns in kernel – bloqueado para não assinantes, até o dia 09/06/11.
Eu, que já anotei na minha agenda, começo a ler na quinta.
Juntar três assuntos – kernel, oo e design patterns – e aprender mais sobre o “core” de um projeto destes? Quem não quer?!
Enquanto isto os “peladeiros” salivam, na iminência do lançamento da “oitava porcavilha” do universo.

A porcaria do Windows não faz isso?! Não diga! (Parte 3)

Meu amigo, e co-autor deste blog, MaRZ, me pediu para pegar leve com os insultos aos maníacos pelo Windows. Só que eu não resisto. Eis mais uma coisa que a Microsoft não colocou nele!

Suponha que vocẽ tenha feito um webservice bem simples. Seu site devolve uma string de acordo com um conjunto de critérios que vocẽ passe para ele. Só que vocẽ quer criar um script em bash para ler esse resultado sem usar nenhum programinha bobo. Ou seja, só pode usar as funções built-in do bash.

Um exemplo, do excelente post do blog de outro amigo meu, o @Kl0nEz, que pode ser lido aqui, mostra como. Antes de cair de boca no código, é necessária uma explicação:

Existem 3 handlers para dispositivos do tipo character que são padrão em qualquer distribuição unix ou derivadas. Esses handlers também estão disponíveis diretamente em qualquer programa escrito em C/C++, são eles: stdin, stdout e stderr. Esses handlers são padronizados em 0 (para stdin), 1 (para stdout) e 2 (para stderr).

A entrada padrão (stdin) é o teclado e a saída padrão (stdout) é o vídeo, assim como a saída de erros padrão (stderr). No exemplo abaixo criaremos um hadle com valor 3 justamente para não sobrepor os 3 primeiros – que são definidos pelo sistema. O que acontece quando você tenta sobrepor um dos hadles do sistema? Well… faça uma experiência!!! ;)

Agora, vamos ao nosso exemplo… Vamos aqui obter o HTML da página do Google (http://www.google.com/) via bash:

$ exec 3<>/dev/tcp/www.google.com/80
$ echo -e "GET / HTTP/1.1\n\n" >&3
$ cat <&3
HTTP/1.1 302 Found
Location: http://www.google.com.br/
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Set-Cookie: ********
Set-Cookie: ********
Date: Fri, 20 May 2011 13:58:07 GMT
Server: gws
Content-Length: 222
X-XSS-Protection: 1; mode=block

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.com.br/">here</A>.
</BODY></HTML>
$ exec <&3-

Lembre-se que os 3 comandos dados (exec, echo e cat) são built-in no bash! A primeira linha cria o handle 3, bidirecional, e o associa ao dispositivo de caracter /dev/tcp/url/porta. A segunda linha envia a requisição para o handle, de acordo com a RFC 2616. Tudo o que temos que fazer, na terceira linha, é ler o handle 3 para obter a resposta do site.

No final de tudo temos que fechar o handle 3. É isso o que a última linha faz.

É claro que esse exemplo ai em cima mostra a instrução de redirecionamento do google (error code 302). Acredito que @Kl0nEz tenha escolhido justamente esta URL para manter o HTML pequeno e ser usado como exemplo no post dele. Fiz o mesmo aqui.

Com esse recurso podemos obter a resposta de um site, colocá-lo numa variável e retirar aquilo que precisamos.

Agora responda: Você consegue fazer isso num arquivo .BAT, na porcaria do Windows?

A porcaria do Windows não faz isso?! Não diga! (parte 2)

Dando uma olhada no artigo sobre Static Archives & Shared Objects você pode pensar em usar a mesma técnica dos links simbólicos para mitigar o famoso problema do DLL Hell, no Windows…. Vamos tentar algo parecido:

Primeiro, abra uma CLI (Command Line Interface) – o também famoso CMD.EXE – e crie um arquivo texto em alguma pasta do seu Windows:

D:\Temp> copy con: teste.txt
teste
^Z

Depois crie um link simbólico chamado “teste2.txt“, apontando para o recém criado “teste.txt“… Hummm… por linha de comando não dá pra fazer! Porra, Microsoft! Temos que abrir o diretório no Windows Explorer e criar a droga do link:


Clique no arquivo teste.txt com o botão direito, escolha Create Shortcut (ou Cirar Atalho) e depois renomeie o atalho para teste2.txt.

Agora, volte a CLI e tente abrir o arquivo teste.txt pelo link simbólico teste2.txt:

D:\Temp> notepad teste2.txt

Olha só o que vai acontecer:


Sabe porque isso aconteceu? É que a porcaria do Windows não cria o “atalho” com o nome que foi dado ao arquivo! Ele adiciona a extensão .lnk para identificar o arquivo como link. Ou seja, um link não é um link, é um arquivo!

D:\Temp> dir
 Volume in drive D is XXX
 Volume serial number is BABA-CA01
 Directory of D:\Temp
05/16/2011  10:43 AM    <DIR>           .
05/16/2011  10:43 AM    <DIR>           ..
05/16/2011  10:43 AM                 5  teste.txt
05/16/2011  10:43 AM               512  teste2.txt.lnk
                2 file(s)            517 bytes
                2 Dir(s)     13283298943 bytes Free

Então, eis minhas perguntas:

  1. Pra que serve um link simbólico que não é um link simbólico?
  2. Se vocẽ precisar criar um link simbólico via linha-de-comando, como faz?1
  3. Pra que serve um link simbólico que não “responde” pelo nome do arquivo dado pelo usuário?
Nota 1 – Eu sei como faz! É só criar uma porcariazinha de script em VBScript ou JScript para isso. Meu ponto é: A M$ não se preocpuou nem um cadinho em criar meios de construir “links” via linha de comando!

A porcaria do Windows não faz isso?! Não diga!

Artigo originalmente publicado no Lost in the e-Jungle

Windows é um ótimo SO para jogar no lixo!

Recentemente topei com um problema interessante. Na verdade são dois problemas envolvendo duas porcarias de software: Windows e Java.

Graças a preguiça da Red Hat, a versão para Windows do JBoss Operations Network não funciona direito quando usamos as versões x86-64 do Windows e do JDK 1.6. Isso porque os agentes requerem o uso de um wrapper que funciona como front-end para o JBoss poder rodar como serviço e esse wrapper só funciona com a versão 32 bits da JVM (Java Virtual Machine)… Existe uma versão 64 bits do wrapper, mas ela não acompanha o JBoss ON (por que não é gratuita!!). Então eu tive a idéia mais óbvia do mundo: Vou colocar o batch rhq-agent.bat como serviço!

Well… É mais fácil falar do que fazer… Segundo a documentação da própria Microsoft, existia um programinha pra fazer isso no Windows 2000. Você tem que instalar o programinha, levar uma DLL junto e o seu arquivo .BAT (e, se bobear, mexer em um monte de lugares no registro)… Uma complicação do caralho!

Olha só como isso é feito no Linux:

  1. Crie um script em /etc/init.d para iniciar, parar, reiniciar e pegar o status do serviço;
  2. Crie dois links simbólicos em /etc/rc#.d (# é o runlevel). Um dos script tem o nome do formato Snn<nome> e o outro Knn<nome>, onde <nome> é o nome do script que vocẽ colocou em /etc/init.d. O valor de “nn” é a ordem com que o serviço vai ser executado.

Assim, um link S99abcd é executado antes de S99zebra. E S98zebra é executado antes de S99abcd. A mesma ordem é obsevada nos arquivos com nome começados com K.

Para sua informação: “S” significa “Startup” e “K” significa “Kill”.

Voilà! Serviço instalado e pronto para rodar. Para fazer com que um executável ou script seja executado sob um usuário específico (já que o script sempre será executado sob as credenciais do root), altere o seu script em /etc/init.d colocando as suas chamadas assim:

su -l usuario -c "comando"

No Windows, depois do 2000, rodar batches ou scripts como serviços parece ser algo impossível de fazer.

Ô ambientezinho de merda, sô!

Update:

Estou pensando em construir meu próprio wrapper