quinta-feira, 15 de setembro de 2016

Melhorias ao SQL

Lendo um relato sobre um pobre programador que apagou uma tabela inteira, porque esqueceu o where num delete, fiquei a pensar que o SQL deveria ter sintaxe diferente para alguns casos.

Os updates e os deletes de tabelas inteiras deveriam indicar a intenção explicitamente:

update my_table set my_column=0 all rows;
delete other_table all rows;

E se alguém esquecesse do all rows ou do where, seria um erro de sintaxe. Aposto que essa mudança evitaria muito sofrimento.

quarta-feira, 14 de setembro de 2016

Recuperando um roteador WR1043ND com um Raspberry Pi

Um amigo apresentou-me um desafio: recuperar um roteador cuja atualização do firmware dera errado. O roteador, um TP-Link WR1043ND, não funcionava mais. Esse equipamento tem uma interface serial na placa. É uma interface de 3,3V que é compatível com a interface serial do Raspberry Pi.

Infelizmente, não basta abrir a caixa: a interface serial não tem os pinos instalados. Comprei os pinos, um pouco de fio e conectores para construir um cabinho. Depois comecei a perceber que cabinhos com os conectores adequados estão por toda parte: os mouse Genius antigos os tem, assim como os cabos de som dos drives antigos de CD. Até mesmo o cabo de um drive de disquetes ou de HD IDE serviria com algumas modificações. Também aprendi que montar um cabo sem um crimpador é difícil.

No Raspberry Pi, os pinos 6, 8, e 10 servem a porta serial, sendo, respectivamente, o terra (G), a transmissão (Tx) e a recepção (Rx). Eles são vizinhos de dois pinos com 5V (o 2 e o 4), então é preciso ter cuidado. No roteador, para minha sorte, Rx e Tx estão trocados. Percebi isso apenas depois de ter montado o cabo com pinagem idêntica nas duas pontas. Sorte de principiante.

Como os pinos são muito pequenos e não tenho nenhuma prática com soldagem, pedi ajuda à esposa amada, que, embora também não soubesse soldar, tem mais habilidade. O resultado ficou além do esperado.



Do outro lado, os pinos:


São quatro pinos, porque não havia um conector de três disponível. O quarto pino é de energia (3,3V) e é desnecessário para este projeto.

As instruções para atualizar o firmware via a interface serial estão disponíveis na wiki do OpenWRT. Para esse modelo, há duas opções para carregar o arquivo: via tftp e via a própria interface serial. Como eu estava longe do roteador e usando um Raspberry Pi que não tem uma porta ethernet, resolvi executar tudo pela interface serial.

O primeiro passo é instalar o kermit (para enviar o arquivo) e o minicom (para interagir com o roteador):

sudo apt-get install kermit minicom

A porta serial aparece como o device /dev/serial0 (que é apenas um link para /dev/ttyAMA0). Então, para iniciar a comunicação, o comando é:

minicom -b 115200 -o -D /dev/serial0

E só depois liguei o roteador. Ele não dá muito tempo para interromper a sequência de boot. Assim que aparecer o texto "Autobooting in 3 seconds" é preciso digitar tpl para interromer o processo normal. Pode ser preciso desligar e religar o roteador várias vezes.

Pelo minicom, digitei um comando para limpar um bloco de memória e outro para carregar o arquivo novo:

erase 0xbf020000 +7c0000 
loadb 0x81000000

Depois, noutro terminal, iniciei o kermit sem parâmetros e digitei os seguintes comandos:

set line /dev/serial0
set speed 115200
set carrier-watch off
set handshake none
set flow-control none
robust
set file type bin
set file name lit
set rec pack 1000
set send pack 1000
set window 5
send wr1043nd.bin 

Isso deveria durar uns 20 minutos, mas houve muitos erros na transmissão e o envio levou duas horas. Os erros atribuo à qualidade do meu cabo.


Terminado o envio, dois comandos terminam o processo, copiando o arquivo para o flash e reiniciando o roteador:

cp.b 0x81000000 0xbf020000 0x7c0000
bootm 0xbf020000

Eu entendo que a interface serial esteja escondida, porque seria fácil usar uma interface de 5V (ou 12V) e queimar o equipamento, mas acho que foi economia demais não colocar os pinos (que custam centavos). De qualquer forma, o experimento foi um sucesso e várias habilidades novas foram adquiridas.

terça-feira, 6 de setembro de 2016

A linha mais comprida de um arquivo II (O Desafio)

Em janeiro, achei a linha mais comprida de um arquivo usando comandos do shell e usando perl (que resultou ser muito mais rápido).

O Linux pediu revanche, porque eu havia ignorado o awk. Perl provou mesmo ser eficiente.


$time perl -MData::Dumper -ne '$n{length($_)}++; END {print Dumper(%n)}' arquivao.txt
$VAR1 = '1088';
$VAR2 = 349647;

real    0m1.326s
user    0m0.814s
sys     0m0.371s

$time awk 'length($0) > max { max=length($0) } END { print max }' arquivao.txt
1087

real    0m21.400s
user    0m18.596s
sys     0m0.455s

Perl deixou o awk comendo poeira. A diferença nos números deve-se ao fato do awk já ignorar o caractere de fim de linha.

segunda-feira, 5 de setembro de 2016

Múltiplos dos primos

Há anos eu havia escrito um programa para encontrar inteiros candidatos a primos, pulando todos os que eu já sabia serem múltiplos de alguns poucos primos.

Resolvi brincar com o contrário: achar sequências de pulos que levariam a múltiplos dos primeiros primos.


#!/usr/bin/perl
my @primes=qw(
      2      3      5      7     11     13     17     19     23     29 
     31     37     41     43     47     53     59     61     67     71 
     73     79     83     89     97    101    103    107    109    113 
    127    131    137    139    149    151    157    163    167    173 
    179    181    191    193    197    199    211    223    227    229 
    233    239    241    251    257    263    269    271    277    281 
    283    293    307    311    313    317    331    337    347    349 
    353    359    367    373    379    383    389    397    401    409 
    419    421    431    433    439    443    449    457    461    463 
    467    479    487    491    499    503    509    521    523    541 
    547    557    563    569    571    577    587    593    599    601 
    607    613    617    619    631    641    643    647    653    659 
    661    673    677    683    691    701    709    719    727    733 
    739    743    751    757    761    769    773    787    797    809 
    811    821    823    827    829    839    853    857    859    863);

my @steps=(2);
my $product=1;
my @sub=@primes[0..5];
map { $product*=$_ } @sub;
for my $i (2..$product) {
  $found=0;
  for (@sub) {
    if ($i%$_==0) {
      $found=1;
      last;
    }
  }
  if($found) {
    push @steps, $s;
    $s=1;
  } else {
    $s++;
  }
}
print "@steps\n";

A série precisa ir até o produtório dos n primos. Para n=2 (2 e 3), ela vai até 6. Para n=3 (2, 3, e 5), vai até 30, e assim por diante. Os passos são sempre de 1 ou 2, porque um passo maior que 2, pularia um par.

Os primeiros resultados são:

n=1 
2
n=2 
2 1 1 2
n=3 
2 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 2
n=4 
2 1 1 1 1 1 1 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 2 2 1 1 2 1 1 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 
2 2 1 1 1 1 2 1 1 2 1 1 1 1 2 1 1 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 1 1 2 1 1 1 1 2 1 1 2 1 1 1 1 2 2 
1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 1 1 2 1 1 2 2 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 1 1 1 1 1 1 2

A soma é igual ao produto, obviamente. Ademais, os inversos das médias são as sequência de frações da última investigação sobre primos. Isto é, para n=2, a média é 6/4 e os dois primos são fatores de 2/3 dos números inteiros. Além disso, as duas metades das sequências são espelhadas, o que podemos usar para economizar espaço e processamento.


quinta-feira, 1 de setembro de 2016

Renovação da habilitação

Parecia mais um dia normal de 2050. Zveg entrou em seu veículo elétrico multipassageiro e iniciou o sistema. Após a breve rotina de inicialização e verificação, o veículo indagou:

-Bom dia, Zveg. Sua habilitação expirará em 15 dias. Deseja executar o teste de renovação?

Zveg irritou-se, mas conformou-se a finalmente a executar o teste, ao menos para deixar de ter que responder a essa pergunta.

-Certo, vamos logo com isso.
-Iniciando teste de habilitação. Fase 1, teste de visão. Quais são as letras exibidas no parabrisas?
-T, P, E...
-Por favor, mantenha sua cabeça contra o encosto.
-Certo, então, T, P, E, V, e X.
-Resposta satisfatória. Tendo em vista o número de pontos acumulados, Zveg, é preciso executar o teste de simulação e sinalização e o escore mínimo é 73.

O veículo escureceu o ambiente e iniciou a simulação. Zveg estava um pouco preocupado; andava dirigindo com desatenção e agora precisava de um desempenho melhor para compensar as faltas.

Durante 20 minutos, o teste de simulação e sinalização correu. O veículo apresentava distintas situações de tráfego e expedia ordens. Em alguns casos, as ordens levariam a infrações e Zveg devia indicar a ação correta a ser tomada:

-Este local não é adequado para estacionar e tampouco posso fazer o retorno, então devo procurar outra rua.

Ao fim do teste, o veredito foi informado prontamente e sem rodeios:

-O resultado é 76. A próxima renovação será em 180 dias. Deseja repetir o teste de simulação e sinalização? Com 80 pontos, a renovação só será necessária em 365 dias.

Zveg já estava farto.

-Não!
-Enviarei os dados do teste para o registro central. Aguarde um insta..zuuuup.

O veículo escureceu e então as luzes piscaram violentamente por alguns segundos.

-Bom dia, Zveg. Sua habilitação expirará em 15 dias. Deseja executar o teste de renovação?



quarta-feira, 31 de agosto de 2016

Cobertura dos n primeiros primos

Uma questão bastante simples serviu para eu revisar e combinar duas ferramentas matemáticas: o princípio da inclusão-exclusão e as combinações.

A dúvida era: dados n primeiros primos, qual porcentagem dos inteiros é de múltiplos de ao menos um deles.

Se n=1, o único primo a ser considerado é o 2. Obviamente, metade dos inteiros é múltiplo de 2.

Com n=2, temos os primos 2 e 3. É preciso somar 1/2 e 1/3, mas também subtrair 1/6, porque esses 1/6 já foram contabilizados num caso ou noutro. Aí já vemos que precisamos das combinações e logo depois do princípio da inclusão-exclusão: somam-se as porcentagens de cada primo, depois subtraem-se as combinações dois a dois.

Então, preciso de duas funções e uma lista de primos. A função de combinações (choose) está descrita em detalhes noutro artigo.


#!/usr/bin/perl

my @primes=qw(2 3 5 7 11 13 17 19 23 29);

sub choose {
  my $n=shift;
  my $k=shift;
  my $callback=shift;
  my @rest=@_;
  
  if($k>0) {
    for my $m ($k..$n) {
      choose($m-1, $k-1, $callback, $m, @rest); 
      &$callback($m, @rest) if $k==1;
    }
  } 
}

for my $n (1..50) {
  my @g=@primes[0..$n-1];
  my $sum=0;
  my $op=1;
  for my $k (1..$n) {
    choose($n, $k, sub {
      my $product=1;
      map { $product*=$g[$_-1] } @_;
      $sum+=($op*(1/$product));
      print "$sum\r"; 
    });
    $op*=-1;
  }    
  print "$sum\n";
}

Esse código imprime a porcentagem dos inteiros que são múltiplos dos n primeiros primos, até o n=50. Eu reduzi a lista dos primos para encurtar o código, mas é fácil obter a lista dos primeiros 1000 primos.

O cálculo começa a ficar lento com n=15 ou nessa redondeza, conforme o computador.

O gráfico abaixo mostra a evolução. Ela parece convergir para algo perto de 0,9, mas na verdade ela vai muito lentamente em direção ao 1.


Agora, o que me interessa são as frações que esses números representam. Os primeiros são 1/2, 2/3, 11/15, e 27/35.

terça-feira, 23 de agosto de 2016

Telegram bot no OpenWRT

O roteador TP-Link WR842ND, além de ser relativamente barato, possui uma porta USB e permite rodar o OpenWRT. O desempenho dele (266,64 Bogomips) é semelhante ao de um Pentium 133. Ele possui 8MB de Flash e 32MB de RAM. Então, é uma máquina com potencial. Seria uma pena que fosse usada apenas para oferecer wifi.

Adicionei um pendrive de 16GB e um amigo gentilmente instalou e configurou o OpenWRT (Gargoyle, para ser mais preciso). Além de roteador, com muitos gigas livres, ele pode virar um servidor de arquivos e posso deixar um download grande rodando nele durante a noite, por exemplo.

Ele não oferece o X, obviamente, mas tem uma linha de comando completa via o BusyBox. Já que tenho mais memória via o pendrive, instalei o Perl para facilitar as tarefas.

Para gerenciá-lo remotamente, resolvi escrever um bot do Telegram. E para dar mais emoção, decidi usar o que estivesse já instalado. Só abri exceção para o curl, porque não sou tão masoquista. O shell disponível é o ash: limitado, mas pequeno e eficiente.

O Telegram oferece uma interface sobre HTTP com mensagens em JSON. Por sorte, o OpenWRT já oferece uma ferramenta para processar JSON: jsonfilter. Por azar, não tem nenhuma documentação. Só encontrei o modo de usar no código-fonte.

Então, a única peça que faltava era o curl.

opkg install curl


E esse comando instala um executável de apenas 69KB.

Para enviar uma mensagem com o IP do roteador, um script simples basta:

#!/bin/ash

ip=$(ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2}')

api=https://api.telegram.org/bot<key>
curl -k -s -X POST $api/sendMessage?chat_id=<id>&text=$ip


A opção -k indica que o curl pode ignorar os erros de certificado, o -s suprime as mensagens e o -X precede o POST.

Resolvi colocar cada comando em um arquivo distinto. Para retornar o resultado ao script de controle, duas variáveis são necessárias: telegram_reply para indicar se houve sucesso no processamento e telegram_message com o texto da resposta. Talvez no futuro seja preciso adicionar telegram_type, se eu quiser enviar fotos, por exemplo. O script para tratar o comando /ip fica assim:

#!/bin/ash

ip=$(ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2}')

telegram_message=$ip
telegram_reply=true

O método getUpdates (que informa os últimos comandos) pode receber um parâmetro para indicar quais comandos já foram respondidos. Para manter registro desse parâmetro, criei um arquivo /tmp/bot_vars.sh. Ele conterá apenas uma linha na forma offset=123.

Então, o script que solicita os comandos e os encaminha para os scripts específicos precisa:
  1. Solicitar os comandos;
  2. Extrair os ids das solicitações, os ids dos solicitantes, e os comandos propriamente ditos;
  3. Para cada comando, separar os parâmetros e invocar o respectivo script;
  4. Se o script retornou com sucesso, enviar a mensagem produzida;
  5. atualizar o arquivo /tmp/bot_vars.sh com o id do último comando.

#!/bin/ash

key='segredo';
api="https://api.telegram.org/bot$key"
source /tmp/bot_vars.sh
offset=$(($offset+1))
status=0
updates=$(curl -s -k -X GET $api/getUpdates?offset=$offset)
status=$(jsonfilter -s "$updates" -e $.ok)
if [ $status = 'true' ]; then
  update_ids=$(jsonfilter -s "$updates" -e $.result[*].update_id)

  for update_id in $update_ids
  do
    sender=$(jsonfilter -s "$updates" \
                        -e "$.result[@.update_id=$update_id].message.from.id")
    command=$(jsonfilter -s "$updates" \
                         -e "$.result[@.update_id=$update_id].message.text")
    cmd=$(echo $command |  awk '{print $1}')
    parms=$(echo $command | awk '{$1=""; print $0}')

    telegram_reply=false
    case $cmd in
      '/ip') source ip.sh $parms ;;
    esac

    if [ $telegram_reply = 'true' ]; then
      curl -k -s -X POST \
        $api/sendMessage -d chat_id=$sender -d text="$telegram_message"
    fi
    
  done

  if [ -n "$update_ids" ]; then
    echo "offset=$update_id" > /tmp/bot_vars.sh
  fi
fi

Não é o quadro mais lindo do museu, mas, sendo shell, não se poderia esperar muito mais. Agora, tenho que me preocupar com os problemas de segurança que isso deve ter.