Caminhada por Paris

Segundo dia em Paris, chove, faz frio, depois do veranico de ontem parece que estamos no inverno novamente! Como estavamos em efeito pós viagem, cansados e semi-moídos da viagem, deixamos a chuva embalar nossa preguiça por um tempo. Tomamos o café da manhã francês do hotel, um monte de baguetes (absurdamente boas), manteiga, geléia, suco de laranja junto com a bebida de preferência, chá, café e chocolate quente. A Nanda preferiu o chocolate e eu o café, fiquei com inveja do chocolate, tava muito bom.

Primeira parada do dia foi no paraíso feminino: Sephora. Loja na Champs-Elysés lotada, em pleno domingo de manhã, cheia de mulheres comprando tudo quanto é tipo de maquiagem, perfumes, pincéis, cremes, acessórios, etc… Obviamente não saimos de mãos vazias da loja, compramos nossos perfumes favoritos e mais algumas coisas. Saímos de lá e entramos na FNAC logo ao lado, hora de Nanda virar fotografa! Depois de estudarmos as opções optamos por uma Panasonic Lumix FS35, compacta, zoom bom e mega pixels mais que suficientes!

Tivemos nosso primeiro “encontro” com a grosseria francesa, estavamos meio atrapalhado sobre qual era o local correto para fazer o tax-refund. Esperamos no balcão, mas ficamos na dúvida se não era em um outro local, fomos até lá, mas não era, quando voltamos (questão de segundos) fomos prontamente atendidos, mas o cliente grosseiro que havia acabado de chegar achou que estavamos furando a fila e falou um alto e sonoro “Merde!”. A funcionária que nos atendia ficou possesa com ele e colocou ele no seu lugar!

Aproveitamos que estavamos ao lado do Arco do Triunfo e passeamos pelo entorno dele. Como o tempo estava feio, optamos por não gastar para subir no arco. Obviamente começou a chover enquantos estamos passeando por ali. Hora de mudar de programa, próxima parada: Centre Pompidou!

Chegamos e já tinha uma fila pequena na porta do Pompidou, quando passamos pela segurança, ela havia triplicado de tamanho! Acho que todos os turistas, franceses, etc resolveram ir lá. Visitamos a exposição do François Morellet, que trabalha com luzes e a geometria. Muito legal a exposição, na outra viagem já havia visto um dos trabalhos que compunham a amostra em Nuremberg. A grande pena foi que a parte dos anos 60 até hoje em dia estava fechada, mas podemos aproveitar a grande aula de arte do período entre 1900 e 1960.

Às cinco da tarde, resolvemos que era hora de almoçar, fomos até a cafeteria do centro e deliciamos um croq mousier e uma salada do chefe. Nada como estar em Paris, qualquer local serve boas comidas. Após o “almoço”, saímos para caminhar pelo Marais, até chegarmos a Place des Voges. Incrível ver a praça com folhas nas árvores, mais incrível ainda pelo formato retangular das copas! Terminamos o passeio caminhando pelo canal que existe ao lado da Bastille.

Terminamos o dia jantando numa creperie que tem perto do hotel. O crepe de sobremesa estava muito bom!

Paris!

Depois de três anos, estou de volta a Paris. Dessa vez acompanhado pela Fernanda, minha namorada! Como é bom estar em Paris, dessa vez, o clima está diferente, não é inverno, faz calor, muito calor, as plantas estão verdes, tem flores em todos os canteiros. Mas isso é a parte boa do primeiro trecho da viagem.

Tivemos uma grande surpresa quando chegamos ao aeroporto Salgado Filho! Descobrimos que nossa passagem para o Rio, onde pegaríamos o voo da Air France, não existiam mais! Com sorte, chegamos 1h30 antes do voo. Passamos mais ou menos todo esse tempo no telefone com a agência que vendeu a passagem para resolver a situação. Segundo a funcionária da Gol, que tomou as nossas dores e realmente nos ajudou, a reserva do voo Porto Alegre – Rio tinha deixado de existir no dia 19 de março. Tempo mais que suficiente para a agência nos informar e realocar nosso voo. Mas, como se pode ver, resolvemos todos os problemas a tempo, apesar do enorme stress.

Trocamos de voo para o Rio, e com uma sorte absurda, um voo da Air France das 16h havia sido adiado para às 21h! Trocamos nosso voo das 19h, para o das 21h. Acho que a troca foi benéfica, fez um nerd muito feliz por estar voando pela primeira vez em um 747-400 (um dos meus sonhos desde criança). O voo, como é de se esperar da Air France, foi ótimo! A comida a bordo era boa, serviram Camembert da Président, nhami! Única queixa foi só servirem picolés da Kibon sabor Uva, na caixa térmica da Hägen-Däsz, baita propaganda enganosa – Nada contra a Kibon, mas…

Chegamos em Paris no tempo previsto do novo voo. O terminal novo do Charles de Gaule é bem bacana. Não sei quem é o arquiteto, mas ele usa bastante madeira, pintada com uma cor cimento, de forma que diversas vezes parece que a parede é de concreto. Resultado fica legal, e a acústica do ambiente fica excelente. Depois de fazer a imigração e já com as bagagens, sofremos para descobrir como pegar o RER que passa no aeroporto – que não está funcionando justamente neste final de semana. Quando descobrimos isso, ficamos surpresos com a eficiência da sinalização e estrutura montada para fechar uma estação de metrô por alguns dias. Haviam panfletos, banners, diversos ônibus fazendo a conexão para a estação mais próxima.

Depois de nos instalarmos no hotel, resolvemos fazer um pequeno tour a pé. Caminhamos até o Centre Pompidou, que fica à uns 10min do hotel. Depois seguimos até a Notre Dame, passando pelo Hôtel de Ville. Fomos até a ponta da Île de la Cité, haviam diversas pessoas fazendo piquenique. Uns super sofisticados, com camarões, baguetes, queijos e vinhos, já outros, eram só salgadinhos prontos e cerveja quente. Fizemos uma parada, quase que obrigatória, na Apple Store do Louvre. Aproveitamos para tomar um Caramel Machiato na Starbucks e descansar os pés um pouco. Terminamos a caminhada na L’orangerie.

Centre Pompidou

Centre Pompidou

Notre Dame

Notre Dame

Nanda na ponta da Île de la Cité

Nanda na ponta da Île de la Cité

Agora é hora de jantar!

From CVS to Git to Gitorious!

Migrating from CVS to Git

Last week I’ve offered myself to migrate some ~300 repositories to git. Not an easy task at first, but with the right tools at hand the task becomes manageable. Installing cvs2git, and following its documentation will get you started. In Ubuntu that is as simples as:

sudo apt-get install cvs2svn

I know it’s weird, but cvs2git is bundled in cvs2svn… go figure.

But migrating hundreds of repositories isn’t a task to do manually, so I created a script for automating the process. As I had access to the server files, migrating was easier then I expected. My directory structure was something like:

  • cvs_project_1
    • repo_1
    • repo_2
    • repo_3
  • cvs_project_2

I’ve decided to migrate one project at a time, making it straightforward to verify each repo. My script is the following, bare in mind that it my have some flaws, it worked for me. Test it before erasing your old CVS data.

#!/bin/bash
# Copyright (C) Pedro Kiefer

for f in `cat repo_list`;
do
	FOP=${f/\//\-}
	echo "===== Creating git repository for ${f/\//\-/}/";
	sed -e "s/__REPO__/${f/\//\\/}/g" my-default.options > $FOP.options;
	cvs2git --options=$FOP.options
	rm $FOP.options
	mkdir $FOP.git
	cd $FOP.git
	git init --bare
	cat ../cvs2svn-tmp/git-blob.dat ../cvs2svn-tmp/git-dump.dat | git fast-import
	cd ..
done

The script takes a repo_list file with a list of paths to the CVS repositories. Creating this list is quite easy, something like this should work. Be sure to remove CVSROOT and the root directory.

find cvs_project_1/ -maxdepth 1 -type d | sort > repo_list
vim repo_list

The other file the script need is my-default.options, which is the configuration file used by cvs2git. Most of the default values are good, but you really want to add a list of cvs commiters – so you can map the cvs login to a name + email. The other change need is on the line that sets the repository path. For the script to work you need to have it set as __REPO__. Like this:

run_options.set_project(
    # The filesystem path to the part of the CVS repository (*not* a
    # CVS working copy) that should be converted.  This may be a
    # subdirectory (i.e., a module) within a larger CVS repository.
    r'__REPO__',

That’s it, just run the script, and voilà, git repositories for all your cvs modules.

From Git to Gitorious

The second part of my task was importing all of those git repositories to my local Gitorious install. Again, doing it manually is not the right way to do it. After asking about it on gitorious mailing list and learning some ruby, I’ve created this little script. It creates all the repositories for a given project. The projects were created manually on gitorious, as I had only 6 projects – extending the tool to support the creating of projects should be easy.

After using the script above, I had the following directory structure:

  • project_1/
    • repo_1.git
    • repo_2.git
    • repo_3.git

      The scripts takes as argument the project name, which should be equal to the one you created on gitorious web interface. The script scan the project directory and creates the matching gitorious repositories, copying the data to the newly created repository. Some magic regexp was added to remove version numbers and set uniform names to the new repositories. You might want to edit this to your taste.

      By the way, this is my very first ruby programming, don’t expect it to be pretty!

      #!/usr/bin/env ruby
      # encoding: utf-8
      #--
      # Copyright (C) Pedro Kiefer
      #
      # Mass migrate git repositories to gitorious
      #
      #++
      
      require "/path/to/gitorious/config/environment.rb"
      require "optparse"
      
      def new_repos(opts={})
        Repository.new({
          :name => "foo"
          }.merge(opts))
      end
      
      current_proj = ARGV[0]
      
      @project = Project.find_by_slug(current_proj)
      
      Dir.chdir(current_proj)
      puts Dir.pwd
      files = Dir.glob("*.git")
      
      files.each do |f|
        orig_repo = f
        f = f.gsub(/\.git$/, "")
        f = f.gsub(/_/,"-")
        
        # has version?
        version = f.match(/-([0-9](.[0-9][0-9]*)+)(-)?/)
        f = f.gsub(/-([0-9](.[0-9][0-9]*)+)(-)?/, "")
        
        desc = "Repository for package #{f.downcase}\n"
        desc << "Package version #{version&#91;1&#93;}\n" if version
        
        print "Creating repository for package #{f} ... " 
        
        @repo = new_repos(:name => f.downcase, :project => @project, :owner => @project.owner, :user => @project.user, :description => desc)
        @repo.save
        path = @repo.full_repository_path
        Repository.git_backend.create(path)
        Repository.create_git_repository(@repo.real_gitdir)
        @repo.ready = true
        @repo.save 
      
        FileUtils.cp_r(["#{orig_repo}/branches", "#{orig_repo}/info", "#{orig_repo}/objects", "#{orig_repo}/refs"], @repo.full_repository_path)
        puts "Ok!"
      end 
      

Asterisk and FreePBX on Ubuntu Server 10.10

This is just a small gathering of commands and best practices for installing Asterisk and FreePBX on Ubuntu. This worked for me, it has some shortcomings but should work on most of the cases. Feel free to add some comments on better ways of installing it.

The following packages will be installed:

  • Asterisk 1.6.2.7
  • FreePBX 2.8.1

I started with a fresh install of Ubuntu Server 10.10, but if you already have it installed, results should be similar. While installing I selected the LAMP and SSH services, those are pretty basic services which you will need. If you have finished a fresh install, or haven’t updated your system in a while, I suggest running the following lines before continuing with this guide.

sudo apt-get update
sudo apt-get upgrade

Postfix

Although not necessary for running Asterisk and FreePBX, I suggest that you install a MTA agent. If you think this is unnecessary on your setup skip to the next section. Postfix is my MTA of choice, so we are going to install it. When prompt about which configuration should be done to it, select Internet with smarthost, just confirm the other options.

sudo apt-get install postfix

Okey, postfix installed, time to edit the basic configuration, add or change the following lines to /etc/postfix/main.cf:

relayhost = [smtp.gmail.com]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_CAfile = /etc/postfix/cacert.pem
smtp_use_tls = yes

The password for accessing your external relay must be saved to
/etc/postfix/sasl_passwd, add the following this file:

[smtp.gmail.com]:587    user.name@gmail.com:password

Fix the permissions on this file:

sudo chmod 400 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd

Add the appropriate ca-certificate to /etc/postfix/cacert.pem. For gmail, that’s Thawte Consulting, so add their ca-certificate.

cat /etc/ssl/certs/Thawte_Premium_Server_CA.pem | sudo tee -a /etc/postfix/cacert.pem

Restart postfix:

sudo /etc/init.d/postfix restart

Avoid sending mail as root

Edit /etc/aliases, add the following:

root: server@domain.tld

Run the new alias command:

newaliases

Create a /etc/postfix/sender_canonical file mapping user -> email such as:

root            server@domain.tld

Run the following lines:

sudo postmap hash:/etc/postfix/sender_canonical

Add the following line to /etc/postfix/main.cf:

sender_canonical_maps=hash:/etc/postfix/sender_canonical

Restart postfix:

sudo /etc/init.d/postfix restart

PHP

When you selected the LAMP service on your Ubuntu install, you automatically got PHP5 installed. Now you just have to install some additional packages that didn’t get installed. So run the following line to install them.

sudo apt-get install php5-gd php-pear php-db sox curl

phpMyAdmin

One might find useful to have phpMyAdmin installed for managing the MySQL database used by FrePBX and Asterisk. If you don’t know what phpMyAdmin is, you can skip to the next section.

Asterisk

Ubuntu 10.10 provides pre-compiled asterisk packages, using that is way more easier than backing your own asterisk. Run the following to install it, and all of its dependencies.

sudo apt-get install asterisk asterisk-mysql asterisk-mp3 asterisk-sounds-extra

Dahdi

This is a really short how-to for configuring Dahdi, it just covers the bare minimum, but it works ok. First of all, load the necessary kernel modules, in my case for a TDM400P it was the following line:

sudo modprobe wctdm

You might wanna check if the module was loaded and configured your hardware properly, so run a dmesg. If everything is alright, you have to create the dadhi configuration file. That’s really easy, just run:

sudo dahdi_genconf -vvvv

Warning: Be careful when you run this on a production system, it will override the current dahdi configuration file.

Edit /etc/dahdi/system.conf and set the correct loadzone and defaultzone for your country code. I like to use vim to edit configuration files, but you can use any text editor.

sudo vim /etc/dahdi/system.conf

Now check if channels are up an running, run dahdi_cfg:

sudo dahdi_cfg -vvv

Next you have to edit /etc/asterisk/chan_dahdi.conf to configure the channels, this is what asterisk will see and use to send and receive calls.

Apache

Before running the install command, you have to configure your apache server. I prefer to use virtual host, and as of lately I have adopted the following layout for my server:

  • /var/www/address/conf
  • /var/www/address/public
  • /var/www/address/log

In the conf I store the necessary vhost configuration, in public lives the public accessible files, and log hosts the logging files. Feel free to use your own personal taste on installing webapps. For those who want to stick with the how-to, create the needed directories:

sudo mkdir /var/www/pabx.domain/
sudo mkdir /var/www/pabx.domain/conf
sudo mkdir /var/www/pabx.domain/log
sudo mkdir /var/www/pabx.domain/public

Now create a /var/www/pabx.domain/conf/vhost.conf file:

sudo vim /var/www/pabx.domain/conf/vhost.conf

And paste the following lines, change it accordingly to your domain.

<VirtualHost *:80>
   ServerName pabx.domain
   ServerAlias pabx.domain

   ServerAdmin admin@domain.tld
   ErrorLog /var/www/pabx.domain/log/error.log
   CustomLog /var/www/pabx.domain/log/access.log combined

   DocumentRoot /var/www/pabx.domain/public
   <Directory /var/www/pabx.domain/public>
       Options Indexes FollowSymLinks MultiViews
       Order allow,deny
       AllowOverride All
       Allow from all
   </Directory>

   <Directory /var/www/pabx.domain/public/admin>
       AuthType Basic
       AuthName "Restricted Area"
       AuthUserFile freepbx-passwd
       Require user admin
   </Directory>
</VirtualHost>

With the file created, add the vhost to the sites-enabled directory, with:

sudo ln -s /var/www/pabx.domain/conf/vhost.conf /etc/apache2/sites-available/pabx.domain
cd /etc/apache2/sites-enabled/
sudo ln -s ../sites-available/pabx.domain

For now, create an htpasswd file to protect the access to freepbx.

sudo htpasswd -c /etc/apache2/freepbx-passwd admin

And finally, restart apache.

sudo /etc/init.d/apache2 restart

FreePBX

Your Asterisk install should be working by now, so it’s time to install a nice web user interface. Ubuntu doesn’t provide a package for FreePBX, so grab the latest stable source code from FreePBX site.

cd /tmp
wget http://mirror.freepbx.org/freepbx-2.8.1.tar.gz
cd /usr/src
sudo tar xvzf /tmp/freepbx-2.8.1.tar.gz
cd freepbx-2.8.1/

You can equally extract the tarball on your home directory. It doesn’t make any difference. Now it’s time to create the database, the user used to access it, and populate the basic tables. This will create and import the basic tables to asterisk and asterisk cdr database, run this from the recently extracted directory.

mysqladmin create asterisk -u root -p
mysqladmin create asteriskcdrdb -u root -p
mysql -u root -p asterisk < SQL/newinstall.sql
mysql -u root -p asteriskcdrdb < SQL/cdr_mysql_table.sql

With the tables in-place, it's time to create the user with privileges to access and edit those tables. Open a mysql prompt with:

mysql -u root -p

On the prompt run the following queries:

GRANT ALL PRIVILEGES ON asterisk.* TO asterisk@localhost IDENTIFIED BY 'badasspassword';
GRANT ALL PRIVILEGES ON asteriskcdrdb.* TO  asterisk@localhost IDENTIFIED BY 'badasspassword';
flush privileges;
quit;

Don't forget to change the password!

Before running the install command, make a copy of /etc/asterisk/modules.conf. FreePBX rewrites the file and trashes Asterisk installation. If you restart Asterisk after installing FreePBX Asterisk dies with no message.

sudo cp /etc/asterisk/modules.conf ~/asterisk-modules.conf

Ok, we are ready to install freepbx to /var/www/pabx.domain/public:

sudo ./install_amp

The install script will ask for some configuration data, eg. were to install freepbx (/var/www/pabx.domain/public), sql password, asterisk password, etc. Take note of the passwords you used, you might need them later.

The output from the install script is somewhat like this:

...
Enter your USERNAME to connect to the 'asterisk' database:
 [asteriskuser] asterisk
Enter your PASSWORD to connect to the 'asterisk' database:
 [amp109] badasspassword
Enter the hostname of the 'asterisk' database:
 [localhost] 
Enter a USERNAME to connect to the Asterisk Manager interface:
 [admin] 
Enter a PASSWORD to connect to the Asterisk Manager interface:
 [amp111] 
Enter the path to use for your AMP web root:
 [/var/www/html] 
/var/www/pabx.domain/public 
Enter the IP ADDRESS or hostname used to access the AMP web-admin:
 [xx.xx.xx.xx] pabx.domain
Enter a PASSWORD to perform call transfers with the Flash Operator Panel:
 [passw0rd] password
Use simple Extensions [extensions] admin or separate Devices and Users [deviceanduser]?
 [extensions] 
Enter directory in which to store AMP executable scripts:
 [/var/lib/asterisk/bin] 
...

Restore asterisk-modules.conf file, which you backed up before installing FreePBX:

sudo cp ~/asterisk-modules.conf /etc/asterisk/modules.conf

Apache runs as www-data, Asterisk as user asterisk, so we have to change some permission to make both programs work together. First, add www-data to asterisk group:

sudo adduser www-data asterisk

Fix the permissions from amportal, add these lines to the end of /etc/amportal.conf:

AMPASTERISKUSER=www-data
AMPASTERISKGROUP=asterisk
AMPASTERISKWEBUSER=www-data
AMPASTERISKWEBGROUP=asterisk

Everything in place, time to start amportal:

sudo amportal start

Open your web browser and go to http://pabx.domain/ and you will be greeted with FreePBX site. I strongly suggest you to upgrade and install the FreePBX modules you will need, so go to Modules Admin and click on Check for online updates.

Start asterisk with amportal

Before we finish, lets make amportal script to manage asterisk and run it through the safe_asterisk script, for that, we have to remove asterisk from rc.d:

sudo update-rc.d -f asterisk remove

Now edit safe_asterisk, to make sure it runs on background, edit the variable BACKGROUND to zero:

sudo sed -e s/BACKGROUND=0/BACKGROUND=1/ -i /usr/sbin/safe_asterisk

We have to start amportal after booting, so call amportal start in /etc/rc.local. Edit your /etc/rc.local and add the following line before the exit 0 line.

/usr/local/sbin/amportal start

Reboot your machine, and check that everything is still working. Have fun!

Ponteiros!

Outro dia no trabalho resolvemos brincar com ponteiros, não que o código que resultou da brincadeira seja algo bom, mas foi divertido. Basicamente tinhamos uma função que traduzia uma string interna passada para a string que utilizariamos em tela, software foi crescendo e mais traduções eram necessárias para a mesma string.

Então aos exemplos, do que tinhamos, da brincadeira que fizemos com os ponteiros e o que seria a melhor solução para o código. Antes algumas definições, a estrutura que contém as strings é a seguinte, basicamente um mapa 1 pra n:

enum {
	KERNEL = 1,
	WEB,
	CLI
};

struct test {
	int idx;
	char *kernel;
	char *web;
	char *cli;
};

struct test example[] = {
	{.idx = 0, .kernel = "eth0", .web = "Ethernet 0", .cli = "ethernet0" },
	{.idx = 1, .kernel = "eth1", .web = "Ethernet 1", .cli = "ethernet1" },
	{.idx = 2, .kernel = "eth2", .web = "Ethernet 2", .cli = "ethernet2" },
	{.idx = 3, .kernel = NULL, .web = NULL, .cli = NULL}
};

A versão original da função que procurava pela string correta era algo parecido com:

char *get_str_for_v1(const char *eth, enum str_type type)
{
	char *rtn = NULL;
	int i;
	
	switch (type) {
	case KERNEL:
		for (i = 0; i < sizeof (example); i++) {
			if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
				rtn = example&#91;i&#93;.kernel;
				break;
			}
		}
		break;
	case WEB:
		for (i = 0; i < sizeof (example); i++) {
			if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
				rtn = example&#91;i&#93;.web;
				break;
			}
		}
		break;
	case CLI:
		for (i = 0; i < sizeof (example); i++) {
			if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
				rtn = example&#91;i&#93;.cli;
				break;
			}
		}
		break;
	}
	
	return rtn;
}
&#91;/sourcecode&#93;

O que é obviamente muito feio! Com excesso de código repetido, péssimo de ler e dar manutenção. Vendo esse código propus o desafio de escrever a mesma função com um <code>for</code> só. Meu colega de trabalho resolveu usar ponteiros pra resolver isso, calculando o offset de cada um dos membros da estrutura e recuperando o ponteiro da string a partir do offset. Claro, isso parece muito simples de se fazer... mas na hora de escrever o código, demorou bem mais do que imaginavamos, mas foi bastante divertido entender como funcionam os casts em C.

O resultado foi o seguinte código, as macros eu adicionei para facilitar a leitura do código, mais sobre elas daqui um pouco...

#define member_offset(type, member) \
			((unsigned long)(&((type *)0)->member))
#define get_member_by_offset(ptr, type, offset) \
			((type *)((void *)(ptr) + offset))

char *get_str_for_pointer_fun(const char *eth, enum str_type type)
{
	int i;
	unsigned long offset;
	
	switch(type) {
	case KERNEL:
		offset = member_offset(struct test, kernel);
		break;
	case WEB:
		offset = member_offset(struct test, web);
		break;
	case CLI:
		offset = member_offset(struct test, cli);
		break;
	}
	
	for (i = 0; i < sizeof (example); i++) {
		if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
			return *get_member_by_offset(&example&#91;i&#93;, char *, offset);
		}
	}
	
	return NULL;
}
&#91;/sourcecode&#93;

Com certeza não é a melhor implementação possível, talvez seja a mais complexa de se entender, especialmente se não for utilizada uma macro para esconder a implementação. Mas é uma implementação divertida, ou pelo menos foi divertido tentar achar a combinação correta de ponteiros para gerá-la.

E finalmente o código que eu julgo o mais aceitável:
&#91;sourcecode lang="c"&#93;
char *get_str_for_v2(const char *eth, enum str_type type)
{
	int i;

	for (i = 0; i < sizeof (example); i++) {
		if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
			switch (type) {
			case KERNEL:
				return example&#91;i&#93;.kernel;
			case WEB:
				return example&#91;i&#93;.web;
			case CLI:
				return example&#91;i&#93;.cli;
			}
		}
	}
	
	return NULL;
}
&#91;/sourcecode&#93;

<h2>Macros</h2>
Bom, disse que ia explicar o funcionamento das duas macros utilizadas, então recapitulando elas:

#define member_offset(type, member) \
			((unsigned long)(&((type *)0)->member))

#define get_member_by_offset(ptr, type, offset) \
			((type *)((void *)(ptr) + offset))

A macro member_offset calcula o offset de um membro qualquer de um estrutura. Para fazer isso, ela dá um cast para um ponteiro do tipo da estrutura requerida ao valor 0. O que parece estranho a princípio, mas faz todo o sentido! Do ponteiro para zero até o membro requerido da estrutura existem os n bytes que representam o offset em memória do início da estrutura até o membro.

Munido do valor do offset, podemos então recuperar a estrutura que se encontra naquele offset utilizando a macro get_member_by_offset. Obviamente é necessário sabermos qual o tipo de dado que se encontra naquele offset! Então a partir do ponteiro (endereço) da estrura que queremos acessar podemos somar o offset do membro requerido. Mas só somar o offset não resolve, é necessário dar os casts apropriados.

Como queremos um que o endereço calculado (ponteiro original + offset) seja um ponteiro, é necessário indicar isto ao compilador, colocando um * no cast do cálculo. Além disso, é necessário indicar que tipo de ponteiro estamos recuperando naquele endereço, portanto o cast vira (type *). No exemplo, o cast é (char **) indicando corretamente que naquele endereço encontra-se uma string.

Esse mesmo método de recuperar um membro de uma estrutura é utilizado na implementação de listas no kernel do linux. A macro definida é um pouco diferente, uma vez que ela realiza estas duas operações de uma só vez, mas a explicação do seu funcionamento é a mesma! Segue a sua implementação:

#define list_entry(ptr, type, member) \
	((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

Basicamente o que este código faz é recuperar o ponteiro para a estrutura a partir de um membro qualquer dela. No caso das listas, o membro utilizado para recuperar a estrutura original é o struct list_head. Com isso, é possível utilizar diversos struct list_head numa mesma estrutura, adicionando ou não a estrutura alocada a uma das listas.