|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
Copyright © 2002 Avi Alkalay 2.1 :: 2002-08-24
Índice
O Linux está cada vez mais popular, e muitos fabricantes de Software estão portando seus produtos de outras plataformas. Este documento (artigo) tenta esclarecer algumas dúvidas e dar dicas de como criar aplicações para Linux, visando uma ótima integração com o Sistema Operacional, facilidade ao usuário e segurança. Os exemplos foram testados em Red Hat Linux, e devem ser compatíveis com outras distribuições baseadas em Red Hat (Conectiva, Turbolinux, Caldera, PLD, Mandrake, etc). O conceito de user-friendly é erradamente associado a uma boa interface gráfica com o usuário. Na verdade vai muito além disso. Em sistemas como o Linux (que tem características mais fortes de servidor), o usuário mede a facilidade principalmente na instalação e configração inicial de um software. Ele pode até esquecer como foi fácil instalar e usar um certo produto, mas nunca vai esquecer que um software tem um processo de instalação complexo e demorado. Uma migração ou nova instalação será sempre um pesadelo, fazendo o usuário evita-la. Para piorar, uma má experiência de usuário com seu produto poderá queimar o filme de sua empresa. Imagine que você vai instalar aquele produto caríssimo que sua empresa comprou da ACME, e descobre que terá de fazer o seguinte:
Soa familiar? Quem nunca passou por essa triste situação, que induz o usuário a cometer erros? Se o processo de instalação de seu produto é Descompactar-Copiar-Configurar-ConfigurarMais-Usar, como o acima, você tem um problema, e o usuário não vai gostar. Usuários gostam de sentir que seu Produto se integra bem ao SO. Não deve exigir que o SO se adapte ao seu Produto (alterando variáveis de ambiente, etc). Deve permitir o usuário Instalar-e-Usar. A gloria do Instalar-e-Usar é facilmente atingida usando uma receita de 3 ingredientes:
Vamos discutir aqui o que são estes ingredientes, e como implementa-los O conjunto de arquivos de qualquer Software, seja ele gráfico, de servidor, comercial, aberto/livre, monolítico etc, tem sempre 4 partes universais: 1a :: O Software em Sí: o corpoSão os executáveis, bibliotecas, arquivos de dados estáticos, manuais e documentação, exemplos etc. O usuário comum poder ter somente acesso de leitura a estes arquivos. Eles são alterados somente quando administrador do sistema faz um upgrade neste Software. 2a :: Arquivos de Configuração: a almaSão arquivos que definem como o Software vai rodar, como usar o Conteúdo, parametrizam a segurança, performance etc. Sem eles, o Software em Sí geralmente não é usável. Dependendo do seu Software, usuários com permissões específicas devem poder alterar estes arquivos, para faze-lo funcionar da forma que lhes convier. é importante fornecer manuais sobre as possíveis configurações. 3a :: Conteúdoé o que recebe toda atenção de seu usuário. é o que o usuário confiou ao seu Produto gerenciar. é o que fará, se for danificado, o usuário se livrar de seu produto, e procurar um concorrente seu. São as tabelas para um banco de dados, os documentos de um editor de textos, as páginas HTML e imagens de um web-server, os servlets e EJBs de um Application Server etc. 4a :: Logs, Dumps etcGeralmente os Softwares de servidor geram logs de acesso, arquivos de trace para identificar problemas, arquivos temporários, etc. Outros Softwares também lançam mão destes tipos de arquivos, talvez com menos freqüencia. é a última das classes de arquivos, mas muitas vezes é a que mais causa problemas para um administrador de sistemas, pois seu volume pode ultrapassar o do próprio conteúdo. Por isso é importante pensar em alguma metodologia ou facilidade neste ponto enquanto você ainda está criando seu produto. Vamos ver a universalidade do conceito acima analizando alguns tipos aleatórios de softwares: Tabela 1. Universalidade das 4 Partes
Percebam como o Software em Sí contém toda a lógica de negócio de seu produto, que seria inútil se não houvesse uma Configuração para definir como trabalhar com uma massa de dados, provida pelo usuário. Ou seja, as Configurações são o que ligam seu produto ao usuário. Podemos pensar numa metáfora sobre um Escultor (software em sí), que precisa receber Bronze (conteúdo) e um Tema ou Inpiração (configuração) de um Mecenas (usuário), para produzir uma bela Obra (conteúdo). Ele anota em seu Diário (logs) as atividades de cada dia para relatar ao seu Mecenas (usuário) que está produzindo. OK, então vamos ser mais práticos. O fato é que se aplicarmos corretamente o conceito das partes universais, aumentamos enormemente a qualidade de nosso produto. Faremos isso simplesmente separando, encapsulando, cada uma dessas partes em diretórios diferentes (separar em arquivos não é suficiente). Existe um padrão chamado Filesystem Hierarchy Standard que define os diretórios do Linux para cada parte, e vamos discutir isso mais tarde na Secção 4. Por enquanto vamos ver o valor disso para o usuário:
Vamos exercitar a separação com um exemplo de um sistema chamado MySoftware, em que a lógica de negócio está puramente no Exemplo 1. Programa em Shell fazendo referência a configurações externas e a configuração está em Exemplo 2. Arquivo contendo somente as configurações para MySoftware. Exemplo 1. Programa em Shell fazendo referência a configurações externas Exemplo 2. Arquivo contendo somente as configurações para MySoftware Quando eu era o administrador de sistemas para IBM e-business Hosting Services, eu era fascinado pela flexibilidade do Apache permitindo fazer coisas deste tipo:
Se não passassemos nenhum parâmetro (como no primeiro exemplo), o Apache carregava seu arquivo de configuração default, hardcoded, de /etc/httpd/conf/httpd.conf. Nós criamos outras configurações, uma para cada cliente, com uma estrutura completamente differente, endereço IP, módulos carregados, diretório de conteúdo, senhas, domínios, estratégia de log etc. Este mesmo conceito é usado por um editor de texto de um desktop multiusuário (como o Linux). Quando o código é carregado, ele procura por um arquivo de configuração no $HOME do usuário, e dependendo quem o invocou (usuário A ou B), aparecerá diferente porque cada um tem sua própria configuração pessoal. A conclusão óbvia é que o corpo (lógica de negócio) do Software é puro e completamente orientado pela alma (configuração) de quem o manipula. Mas a vantagem competitiva está na facilidade de como trocamos uma alma pela outra, como no caso do Apache. é muito saudável você promover isto ao seu usuário, pois lhe permite criar intimidade, conforto, segurança com seu Produto. Usamos essa abordagem com muito diferentes Software naqueles tempos de e-business Hosting, e era extremamente prático para manutenções etc. Numa migração de versão, tinhamos controle total de onde estava cada uma de suas partes, e faziamos upgrades e downgrades sem perder tempo, com sucesso óbvio. Mas haviam uns Softwares que se recusavam a trabalhar desta forma. Tinham tantos parâmetros hardcoded, que não conseguiamos enchergar com clareza o que separava o corpo de sua alma (ou outras partes). Marcavamos estes Produtos como os de sangue ruim, e os descartávamos/trocávamos assim que possível. Concluimos que os Softwares de sangue bom foram intuitivamente abençoados pela visão das quatro partes de seus desenvolvedores. E eles fizeram nossa vida mais fácil. De fato, foi nessa época que formulamos essa teoria, e ela continua se comprovando. Você quer criar Software sangue ruim ou sangue bom? Toda a discussão até aqui era independente de sistema operacional. No Linux, a lei das Quatro Partes do Software se expressa em sua hierarquia de diretórios, que é classificada e documentada no Filesystem Hierarchy Standard. O FHS faz parte do LSB (Linux Standard Base), o que faz dele uma coisa boa pois toda a indústria está se movendo em sua direção, e é uma preocupação constante de todas as distribuições. é o FHS que define em que diretórios cada pedaço do Apache, do Samba, Mozilla, KDE e de seu Software será colocado, e você não tem mais nenhum motivo para não usa-lo quando pensar em desenvolver seu software, mas vou dar mais alguns:
Só este último já justifica sua adoção, então use sempre o FHS !!! Mais sobre a importância do FHS e sobre compartilhar a mesma estrutura de diretórios pode ser encontrado no site da Red Hat Então vamos resumir o que o FHS diz sobre as pastas (diretórios) do Linux: As Pastas do Linux
Pode parecer uma má idéia quebrar seu Software (como um todo) em vários pedaços, ao invés de mante-lo todo em uma pasta self-contained. Mas um sistema de pacotes (RPM) tem uma base de dados que faz tudo isso para você de uma forma muito profissional, mantendo controle de arquivos de configuração, diretórios etc. E se você espalhar seu Software usando o FHS, além da facilidade ao usuários, você propiciará uma forma intuitiva ao administrador configura-lo, e trabalhar melhor com performance e segurança. Agora que sabemos onde cada parte de nosso Software deve ser instalada, vamos rever a Tabela das Partes Universais cruzada com o FHS. Tabela 2. Mesmos Softwares, aplicando o FHS
Se você é um administrador de sistemas, saiba que esta seção não é para você. Esse é um assunto para desenvolvedores e empacotadores, para facilitar a vida do administrador de sistemas. Os diretórios /opt e /usr/local são usados pelos administradores para instalar manualmente softwares com arquivos soltos (sem RPM), justamente para ele não perder o controle sobre eles. Repare como esses diretórios ficam separados do resto do sistema. Um processo de instalação manual (sem RPM, ou baseado em simples cópia de arquivos) fica num documento esquecido dentro do armário (se foi documentado), e na cabeça de quem instalou. Se ele mudar de emprego, aquela instalação fica obscura para o resto da equipe, e é uma bomba relógio. Com RPM é diferente. RPM (ou qualquer outro sistema de pacotes) é um "processo" de instalação em sí. Está auto documentado na forma de sua base de dados e ações de pré e pós-instalação, o que permite controle total. Torna as instalações independentes de quem instalou, e por isso é processo de negócio. Instalações baseadas em copia de arquivos para /opt ou /usr/local nem de longe provê a organização, visibilidade de sistema e controle do RPM. Diria até que /opt e /usr/local seriam obsoletos se todos os softwares fossem distribuidos como pacotes RPM. É muito importante para a evolução e popularização do Linux (sobretudo no campo de batalha do desktop) que desenvolvedores parem de usar /opt e /usr/local, e comecem a integrar a instalação de seus produtos à árvore principal do sistema. Se depois de ler esta seção você (como desenvolvedor de software) ainda acreditar que estes diretórios são um bom negócio, por favor mande-me um e-mail. Produtos que são inteiramente instalados sob um diretório (a rasão da existêcia de /opt e /usr/local), usam a abordagem self-contained, que tem os seguintes problemas:
Muitos desenvolvedores acreditam que a abordagem "self-contained" lhes permite trabalhar com várias versões de um mesmo produto simultaneamente, parar testes, ou o que seja. Sim, concordo, com este ou qualquer outro bom motivo do planeta. Mas lembre que um Software de Alta Qualidade (ou Comercial) tem como objetivo a praticidade ao usuário final, e não a facilidade para quem o desenvolve ou testa. Visite um usuário inexperiente (mas potencial cliente) e veja-o instalando seu produto. Desenvolvedor, não tenha medo de espalhar seus arquivos de acordo com o FHS (livrando-se de /opt e /usr/local), porque o RPM estará de olho neles. Se você tem uma necessidade de negócio de permitir o usuário trabalhar com várias versões de seu Produto simultaneamente (ou qualuqer outro motivo), faça um pacote relocalizável (relocatable package), cuja descrição está no livro Maximum RPM. Esteja a par também das implicações de usar este recurso, descritos no mesmo livro. Não é a toa que distribuições como Red Hat e seus derivados usam sempre o padrão de diretórios, ao invés de /opt ou /usr/local. Veja o que a Red Hat fala sobre este assunto, e pense nisso.
Você provavelmente vai querer permitir que outros fabricantes possam plugar extensões ao seu produto. Como você é o autor do Software inicial, cabe-lhe organiza-lo de tal forma que o usuário possa instalar o RPM da extensão e sair usando, sem força-lo a mexer em nenhum arquivo de configuração. é novamente o famoso Instalar-e-Usar que garante facilidade ao usuário. Ora, uma extensão nada mais é do que alguns arquivos em um formato correto (DLLs que implementam a API que seu Software definiu), colocados em diretórios corretos (pastas em que seu Software procura por extensões). Tem-se visto muitas aplicações em que além da instalação dos arquivos é necessário alterar arquivos de configuração para "declarar" a presença do novo plugin. Isto deve ser evitado por dificultar a vida do usuário ou do fornecedor da extensão, além de ser desnecessário. Uma boa arquitetura para extensões tem a ver com documentar um diretório qualquer e tudo que se colocar alí será tratado como um plug-in. Um bom candidato para isso é /usr/lib/meuproduto/plugins ou /usr/share/meuproduto/plugins (se as extensões forem independentes de plataforma). Se algum RPM instalar algo que não atenda sua definição de plug-in, cabe a robustez de seu Software detectar e ignora-lo. Gostaria de fechar este assunto convidando o leitor a se abstrair e pensar em que qualquer Software pode ser tratado como uma extensão ao Software de nível superior. Então da mesma forma que um plugin de terceiros é uma extensão ao seu Software, seu Software acaba sendo uma extensão ao SO (nível superior). Podemos então aplicar todos os conceitos de facilidade ao usuário etc que discutimos anteriormente, para o desenho da arquitetura de plugins de seu Software. Isso é extremamente importante por vários motivos:
Mas um bom pacote não é simplesmente empacotar todos seus arquivos num RPM. O FHS deve ser respeitado, arquivos de configuração e documentação devem estar marcados como tal, e scripts de pré- e pós-instalação devem ter qualidade, para não danificar o sistema. Conheça bem o RPM pois ele pode trazer muito poder e facilidade para você a ao usuário. Há muita documentação sobre ele na Internet e em livros. Eis algumas referências:
De ao usuário a opção de instalar somente a parte de seu Software que ele precisa. Imagine que seu Software tenha a parte cliente e servidor, e ambos usam alguns arquivos ou bibliotecas em comum. Quebre então em 3 RPMs. Como exemplo, imagine que o nome de seu produto é MyDB, então você fornecerá os pacotes:
sendo que os dois últimos dependem do primeiro. Se o usuário está instalando o sistema com perfil de cliente, usará:
E se o perfil for de servidor:
Esta abordagem ajuda o usuário a economizar espaço em disco, e ficar ciente de como seu Software está organizado. Em linhas muito gerais, segurança é sinônimo de ordem, de consciência. E inseguro é tudo aquilo que faz um sistema parar sem o desejo do usuário. Então além de portas de rede abertas, ou criptografia fraca (que estão fora do escopo deste documento), uma aplicação que induz o usuário a usa-la somente como root, ou faze-lo alterar arquivos em lugares indevidos, é considerada insegura. O mesmo se diz para outra que lota uma partição de disco que é vital ao sistema. Muitos padrões nasceram de boas práticas discutidas e desenvolvidas em conjunto por muito tempo. Portanto conheça e use-os quando for empacotar seu Software, porque eles são uma peça chave para que você atinja um bom nível de organização (segurança). Todos nós gostamos de interfaces gráficas. Muitas vezes facilitam nossa vida e por isso ajudam a popularizar um software, pois diminuem a curva de aprendizado. Mas para o dia-a-dia, um comando com muitas opções e um bom manual acaba sendo muito mais prático, facilitando automações, acesso remoto etc. Então a sugestão é que sempre que for possível, forneça ambas as interfaces: a gráfica para o principiante, e a linha de comando para o experiente. Melhor do que uma simples interface gráfica, é um desktop integrado consistente. Portanto desenvolvedor, não reinvente a roda. O que já existe de desktop no Linux é completo, com APIs que facilitam muito sua vida. Os desktops que hoje reinam na Linuxlândia são o KDE e o GNOME. Tente sempre usar um dos dois, ou ambos. O KDE é o que mais tem se destacado, oferecendo um verdadeiro desktop consistênte, flexível, e de arquitetura extremamente elegante, incorporando componentes (como COM e COM+ da Microsoft), intercomunicação, performance etc. Está constantemente se aprimorando, e é desenvolvido em C++. Suas aplicações tem um "look-and-feel" integrado e familiar, é leve e maduro. Dizem que o KDE 3 é um diamante lapidado pronto para ser usado, e é a minha principal sugestão para você. O GNOME também traz a proposta de desktop integrado, mas está longe da maturidade e facilidade do KDE. Por outro lado, é muito bem suportado pela comunidade, e ótimas melhorias vem surgindo. O Motif não é um desktop integrado. É uma bilioteca de widgets (botão, scrollbar etc), mais um gerenciador de janelas. Ele nasceu comercial, é maduro e bastante usado em aplicações legado. Mas é considerado obsoleto perante o KDE e GNOME, que integram o desktop. O código fonte do Motif foi aberto pelo OpenGroup e por isso também renomeado para OpenMotif. Java tem sido muito usado para interfaces gráficas, principalmente em Softwares de servidor, onde a interação gráfica é somente um auxílio para a configuração e administração. A Abstract Windowing Toolkit (AWT) do Java, como o próprio nome diz, é independente de desktop. Use interfaces Java para aplicações de usuários específicos, que não necessitem integração com o desktop, ou que não as usarão todos os dias. Hoje todo desktop tem um browser, e se seu Software for uma aplicação de servidor, a Interface Web é a escolha certa, por permitir que seja administrado de qualquer lugar. Mas tenha sempre em mente a segurança e organização de seus CGIs, pois eles costumam ser a porta de entrada para crackers. Interface web (CGI) é um paradigma completamente diferente de programação. Tente entende-lo conceitualmente primeiro, passando por como um web-server funciona, o que é uma URL, etc, para se aventurar nisso sem comprometer a segurança de seu produto. Principalmente se for um produto comercial, seu Software deve fornecer um instalador gráfico. Acredite, eles impressionam numa demonstração, e CIOs gostam deles. Mais que instalação, um wizard auxilia na configuração inicial de um produto, além de coletar dados como chave de ativação etc, e também mostrar a licensa do fabricante. Um wizard não deve fazer mais do que isso:
Ou seja, o wizard facilita a instalação de RPMs e cria alguma personalização. A responsabilidade de colocar todos os arquivos de seu Software nos lugares corretos é ainda do RPM. Seu instalador nunca deve tomar esta responsabilidade para sí. Pense que um administrador de sistemas experiente (há muitos deles no mundo Linux) deve poder reproduzir a instalação de seu Software sem seu instalador gráfico, usando somente comandos do RPM. De fato, em grandes CPDs, onde se instala software em massa, um instalador gráfico só atrapalha. O RPM traz ferramentas que auxiliam seu instalador gráfico interagir com ele, tais como um medidor de porcentagem de instalação. Documentação para seu uso estão no manual (man rpm) e no livro Maximum RPM. A forma como o Linux inicia (e para) todos os seus subsistemas é muito simples e modular. Permite definir ordem de inicialização, níveis (runlevels) etc. Vamos repassar brevemente o que acontece quando bootamos o Linux.
O mecanismo de runlevels permite fazer o Linux se iniciar de formas diferentes. E permitem também mudarmos de um perfil (runlevel) para outro sem rebootarmos. O runlevel default é definido na /etc/inittab com uma linha do tipo Exemplo 3. Linha do runlevel default em /etc/inittab
Os runlevels são números de 0 a 6, e cada um deles é usado de acordo com o seguinte padrão:
Pode-se mudar de um runlevel para outro com o comando telinit. E pode-se ver o runlevel corrente e o anterior com o comando runlevel. Veja abaixo como mudamos do runlevel 3 para o 5.
Podemos citar como exemplo de subsistemas um web-server, um banco de dados, ou ainda a camada de rede do SO. Não consideraremos como sendo subsistema uma aplicação totalmente voltada ao usuário (como um editor de textos). O Linux provê uma forma elegante e modular para organizar a inicialização dos subistemas. Um fator importante é pensar nas dependências entre eles. Por exemplo, não faz sentido iniciar um web-server antes do subsistema de rede estar ativo. Os subsistemas se organizam sob os diretórios /etc/init.d e /etc/rc.d/rcN.d:
Portanto, para fazer seu Software iniciar automaticamente no boot, ele deve ser um subsistema, e veremos como fazer isso em seguinda. Os arquivos de seu Software se espalharão pelo SO, mas você vai querer prover uma interface simples e consistente para que o usuário possa principalmente inicia-lo e para-lo. A arquitetura de Subsistemas promove esta facilidade permitindo-lhe também (não obrigatoriamente) ser automático no boot. Basta colocar um script em /etc/init.d que siga um padrão para que seja funcional e prático. Exemplo 6. Esqueleto de um script de Subsistema em /etc/init.d
Os metodos que você implementou no subsistema mysystem poderão ser invocados pelos usuários com o comando service, como no exemplo: Exemplo 7. Usando o comando service
Você não precisa se preocupar em gerenciar os links simbólicos em /etc/rc.d/rcN.d. O commando chkconfig faz isso para você, baseado nos comentários de controle definidos no começo de sue script. Exemplo 8. Usando o comando chkconfig
Leia o manual do chkconfig para ver o que mais ele pode fazer por você. Quando criar o RPM, coloque seu script de Subsistema em /etc/init.d e não inclua nenhum link nos diretórios /etc/rc.d/rcN.d, porque é uma decisão do usuário permitir seu Subsistema ser automático ou não. Se os links forem incluidos no pacote, e o usuário faz alguma mudança, o inventário de arquivos do RPM se tornará incosistente. Os links simbólicos devem ser criados e removidos dinamicamente pelo processo de pós instalação e pré-desintalação de seu pacote, usando o comando chkconfig. Esta abordagem garente 100% de consistência para o pacote e sistema de arquivos. A. Red Hat, Sobre a Estrutura do FilesystemEste texto foi tirado de The Official Red Hat Linux Reference Guide Por que Compartilhar uma Estrutura Comum?A estrutura do filesystem de um SO é o nível mais básico de organização. Quase todas as formas em que o SO interage com seus usuários, aplicações, e modelo de segurança são dependentes na forma ele armazena seus arquivos num dispositivo primário de armazenamento (geralmente um HD). é crucial, por uma variedade de razões que usuários, como também programas no momento da instalação e além, sejam capazes de se referir a uma diretiva comum para saber onde ler e escrever seus binários, configurações, logs, e outros arquivos necessários. Um organização do filesystem pode ser vista em termos de duas diferentes categorias lógicas de arquivos:
Arquivos compartilhaveis são aqueles que podem ser acessados por várias máquinas; não-compartilhaveis não estão disponíveis para nenhuma outra máquina. Arquivos variáveis podem mudar a qualquer momento sem a intervenção (ativa ou passiva) do administrador do sistema; arquivos estáticos, como documentação e binários, não mudam sem uma ação do administrador do sistema, ou um agente que o administrador ativou para efetuar esta tarefa. A razão para olhar para os arquivos desta maneira tem a ver com o tipo de permissão dado ao diretório que os contém. O modo como o SO e seus usuários precisam utilizar os arquivos determina o diretório onde esses arquivos devem ser colocados, tanto para diretórios que foram montados somente-para-leitura ou leitura-e-escrita, e o nível de acesso permitido em cada arquivo. O nível mais externo desta organização (diretório /) é crucial, como o accesso aos seus diretórios mais internos podem ser restringidos, ou problemas de segurança podem se manifestar, se o alto nível é deixado desorganizado (segurança=organização), ou sem uma estrutura amplamente utilizada. Contudo, simplesmente tendo uma estrutura não significa muito, a não ser que seja um padrão. Estruturas concorrentes podem na verdade causar mais problemas do que soluções. Por isso, a Red Hat escolheu a estrutura de filesystem mais amplamente usada, e a extendeu ligeiramente para acomodar arquivos especiais usados no Red Hat Linux. B. Sobre este DocumentoEste documento deve ser distribuido sob a GNU Free Documentation License, o que o torna suficientemente livre. Todos estão convidados a contribuir com seu conteúdo e idéias. Copyright 2002, de Avi Alkalay. Este documento está publicado nos seguintes lugares:
Foi escrito originalmente em português do Brasil, e depois traduzido para o inglês. Foi usado SGML e o mais-que-incrivel DocBook, que permitiu disponibiliza-lo em outros formatos, encontrados no site. Ficou pronto (português+inglês) em meados de março de 2002. Qualquer mudança após esta época, é perfumaria. Eu o escrevi para ajudar companhias comercias e desenvolvedores OpenSource a fazer software plug-and-play, fácil de usar para Linux, e assim melhorar sua usabilidade e popularidade. Todos os conceitos (de uma forma abrangente) descritos aqui podem ser aplicados em qualquer outro UNIX, ou outro sistema opracional também, inclusive Windows. Talvez um dia eu escreva um desses para Windows...ou Mac.... | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
Home :: Copyright :: Privacy :: Credits :: Get a free Linuxinfor Email Account Document on this page is part of "Criando Aplicações Integradas de Alta Qualidade para Linux". See Index Page for more info about Authorship and Copyright. Counter: 1999-2008 Linuxinfor.com. No rights reserved. |