Clonando partições teimosamente (1)

Há alguns dias comprei um disco rígido novo para acrescentar ao meu computador. Inicialmente iria usá-lo apenas para dados (e programas, mas sem sistema operacional). Mas pensei: não é nenhum SSD com o qual eu vá ganhar desempenho movendo o sistema operacional para lá, mas pode ser um aprendizado interessante e pode simplificar a tabela de partições do meu disco existente que tem 5 partições e 2 sistemas operacionais (já teve 3 sistemas: WinXP, Win7, Ubuntu Linux 12.04 e agora tem Win10 e Ubuntu 14.04).

Fui ver na internet quais eram os métodos mais recomendados e os problemas possíveis ao mover um Windows 10 de um disco para o outro. Assim eu poderia tirá-lo da partição do meio do disco e ele ficaria apenas para dados e para o Linux, que na verdade é o sistema principal da máquina (portanto se eu estragasse a instalação do Windows o prejuízo seria mínimo). Tudo sugeria baixar algum programa para Windows (às vezes livre, às vezes em versão grátis com limitações) e deixar que ele fizesse tudo.

Aí que entra a parte “teimosamente” do título. Teimei que queria fazer o serviço com o mínimo de programas adicionais aos que eu já tivesse no Linux ou no Windows (instalado ou no DVD de instalação). Também teimei em não seguir as dicas de “não iniciar o sistema novo com o disco antigo ainda instalado”. O motivo de evitar programas novos era primeiro por imaginar que eles seriam muito opacos e inflexíveis (ex.: só copiar para novos discos vazios; ignorar ou atrapalhar-se com a existência de outros sistemas operacionais, ou até excluí-los; etc.), segundo por não querer arriscar encontrar neles uma fonte de adware ou spyware, e terceiro para aprender os detalhes do que estou fazendo.

O que aconteceu foi o seguinte:

  • Sim, é possível obter uma instalação funcional do Windows 10 em outro disco apenas copiando arquivos com o comando robocopy.
    • Desde que você use as opções certas e execute a cópia fora do Windows que está rodando (fiz no ambiente de recuperação presente no instalador).
    • Ainda assim, directory junctions, symbolic links, e hard links precisarão de muito cuidado especial para ficarem iguais. Fiquei com uma instalação funcional mas onde os hard links não eram mais hard links (os arquivos ficaram duplicados) e mesmo depois de muitos scripts escritos à mão desisti de consertar.
    • Um pouco de edição offline do registro da nova instalação pode ser útil para definir qual será o novo drive C:
    • É ineficiente, mas eu queria testar. Queria saber o quanto de “mágica” existe no sistema de arquivos do Windows. A conclusão é que ele é bem mais chato com permissões de arquivos e você pode ser surpreendido com alguns recursos obscuros (como as junctions e links, que normalmente não são usados no Windows), mas uma cópia arquivo-por-arquivo pode funcionar, sim.
    • Como não altera os setores de boot da partição, evita o problema de boot que menciono abaixo.
  • Fazer clone da partição com um editor de partições normal do Linux funciona, mas tem uma grande chance de deixar a partição não-inicializável.
    • Ao contrário do que eu imaginava, não havia nenhuma santa ferramenta do Windows que consertasse o boot da partição no novo disco. Perdi as contas da quantidade de vezes que tentei isso. Eu achava que elas reescreveriam todo o setor de boot, mas parece não ser o caso.
    • O Grub pode ser configurado para não carregar o Windows a partir do setor de boot, e sim a partir do bootmgr. Isso desvia do problema, mas não é exatamente uma correção.
  • Resolvi relaxar minha regra de não baixar programas novos e usar o Clonezilla. Software livre, baseado em Linux, com diversas opções. Deve resolver.
    • Uma omissão é que não clona para partições menores (que é útil quando se move para um SSD), ainda bem que eu não estava precisando disso.
    • Acabou resultando no mesmo problema de partição não incializável do caso anterior. Mas pode ser que eu tenha simplesmente esquecido de marcar alguma opção essencial para o tipo de cópia que eu queria fazer.
    • Já estava pensando em desistir totalmente ou usar um software específico para mover instalações do Windows para SSDs, que (tomara) seriam capazes de tratar todos os casos corretamente em vez de me deixar com uma cópia que não inicializa.
    • Depois de mais alguns dias descobri que no próprio sistema inicializado pelo drive USB do Clonezilla há um utilitário chamado partclone.ntfsfixboot (que na verdade é um software separado que eu estava pensando em baixar, mas tê-lo ali já é um incentivo a testá-lo), e ele finalmente foi capaz de consertar o setor de boot (obs.: estou falando do setor de boot que fica dentro da partição, não a MBR).

Entretanto, nesse momento eu já tinha meio que desistido e tinha até atualizado o Windows 10 na partição original para o Anniversary Edition (aquele que permite instalar o Ubuntu dentro do Windows, haha). De certa forma a cópia da partição que fiz antes serviu como backup. No fim não achei bom o desempenho de boot no novo disco. Talvez se rodasse um desfragmentador ou usasse por mais tempo daria chance do Windows otimizar o boot, mas nem sei se vale a pena. A esta altura já aprendi o que queria aprender: editei comandos do Grub durante o boot para testar diversas possibilidades e até VBScript eu usei para tentar consertar os hard links.

Gostaria de criar um novo post ou atualizar este aqui com mais detalhes do que tentei como programas, opções usadas, e links úteis. Quem sabe consigo fazer isso em breve.

Anúncios

Descobri o que me incomoda com Lisp-like languages

Não são só os parênteses. É a falta de pistas sobre o significado dos elementos (fora o primeiro de todos) das listas; e também porque tudo “cresce para dentro”, criando mais níveis de aninhamento. Quer uma variável? Põe mais um let ou um destructuring-bind e indente todo o resto do código mais um nível! (estou pensando principalmente em Common Lisp porque foi um dos Lisps que eu “tentei gostar”, mesmo sabendo que não seria o mais elegante de todos).

A falta de pistas é um caso interessante. À primeira vista, a linguagem parece incrivelmente regular: tudo é uma chamada com parâmetros, assim: (chamada param1 param2 param3). Funções, macros, formas especiais (*), tudo igual.

Mas daí você vê que num (if «condição» «código if true» «código if false»), nem tudo tem o mesmo valor. Não dá para começar a ler do meio, porque o terceiro parâmetro do if só executa se a condição for falsa, e não há separador entre as três partes (uma palavrinha “else” seria bem útil). Você vê (a 1) e isso pode ser a chamada da função “a”, ou pode ser a inicialização de uma nova variável, se estiver na posição correta dentro de um “let”, “do”, etc. Isso da “posição correta” é o que complica. Em linguagens como-C (C-like), é mais fácil começar a “ler do meio”. Tem uma palavra “else”? Você está no meio de um “if”, procure-o mais acima. Abriu/fechou {chaves}? Então você está dentro de um bloco, o que em Common Lisp seria um mais ou menos um “progn”. Encontrou um “=”? É uma atribuição a uma variável existente ou a uma variável nova. Você já sabe que o lado esquerdo não terá seu valor calculado, e sim alterado. Não há como surgir uma nova variável numa chamada(normal, com, parâmetros). Encontrou um ponto-e-vírgula? Descanse e comece de novo na próxima linha (descanse = faça um flush mental do seu parser de expressões).

Para um iniciante, no caso do let e suas inúmeras variantes (let*, letrec, aliás, para que tantas?), até dá para administrar, mas em construções usadas com menos freqüência como “do” ou “handler-case” dá um desânimo: quais parênteses são agrupadores da “sintaxe” e quais são parênteses normais?

Eu preferiria que houvesse algum indicativo mais claro para elementos como:

  • Nova variável. Clojure faz isso com colchetes.
  • Código “normal” de tamanho indeterminado. É o chamado “progn implícito” do Common Lisp, que pode surgir a qualquer momento.
  • Decisões. Num cond os blocos de código ficam muito misturados com as condições, bastaria algo como when/then para melhorar a visualização.
  • Símbolos. Em Lisp existe o quote (como em: ‘a), mas como as macros têm liberdade de fazer o que quiserem, e como colocar «’» em tudo ficaria feio de qualquer jeito, o uso do quote é inconsistente. Make-instance usa nomes ‘quotados, diversas outras construções (let, setf, handler-case, destructuring-bind) não usam.
  • Tipos, anotações, valores default e qualquer coisa opcional. Lisps têm a mania de usar mais um nível de parênteses sempre que for necessário adicionar algo que era opcional.
    • Variável num let? Você pode usar simplesmente o nome da sua variável. Ah, você queria inicializá-la, como normalmente é o caso? Então coloque-a entre parênteses junto com o valor.
    • Parâmetro de função? Basta o nome. Ah, você quer colocar um valor default? De novo: parênteses adicionais. Quer colocar um tipo num parâmetro de defmethod? Parênteses.
    • Slot dentro de um defclass? Lista de nomes entre parênteses. Atributos adicionais? Então agrupe cada slot com seus atributos em mais um nível de parênteses.

E não há necessariamente nenhuma lógica na escolha do que vai ser agrupado ou não. As variáveis de um let ficam entre parênteses no primeiro argumento, e o resto é código. Nada impediria que as variáveis e o código fossem separadas por um símbolo, como “in” (ML-like). Isso evitaria vários parênteses duplos. Ou o bloco de código (o progn implícito) é que poderia ter um delimitador (como parênteses ou chaves). Os slots de uma classe ficam entre parênteses, mas poderiam ser separados do resto com uma palavra, como “slots”.

A forma handler-case ilustra diversos pontos que incomodam: o primeiro parâmetro é o código protegido (o “try” de outras linguagens). Ele não é um progn implícito (mas poderia ser, por que não?), então se quiser fazer mais de uma coisa, você precisa colocar seu próprio progn. Depois vêm os tratadores de exceção (os “catch”), cada um entre parênteses. O primeiro item de cada é o nome da condition (sem nenhum indicativo especial, é tudo posicional). Depois, entre parênteses (precisavam de um delimitador, adivinha qual escolheram?), fica o nome da variável que receberá a condition. E então você tem um espaço livre para escrever seu código tratador de exceções usando os parênteses no seu significado normal.

É assim:

(handler-case
    (progn
      (do-stuff)
      (do-more-stuff))
  (some-exception (e)
      (recover se)))

Quando poderia ser:

(handler-case
    :try
      (do-stuff)
      (do-more-stuff)
    :catch some-exception :as e :do
      (recover se))

No exemplo hipotético acima, os parênteses são usados apenas para chamar funções/macros, enquanto que a estrutura dentro do handler-case é marcada de outras formas. Assim some-exception não fica parecendo uma chamada de função, a variável “e” é marcada com :as (que sugere ao leitor a ideia de um nome novo) e os blocos de código são marcados com :try e :catch/:do conforme seu propósito.

Dá para fazer isso com macros, claro que dá. Mas aí fica tão diferente da linguagem, que cada programador acaba tendo seu próprio dialeto. Se eu queria uma linguagem, acabei ganhando um kit de montagem em vez de uma linguagem usável.

O único lugar onde Common Lisp se permite usar algumas palavras a mais é no loop… Na verdade todas as ideias acima foram inspiradas no loop.

(*) Peraí, formas especiais? Pois é, nem Lisp escapa da necessidade de dar valor especial a certas formas. Fico agora pensando se TCL não acaba sendo mais regular…