Está faltando isso em sua definição de formulário:
$form['#attributes']['enctype'] = 'multipart/form-data'; // If this is not here, upload will fail on submit
Aqui está a lógica que eu uso para criar um widget de upload de arquivo em um formulário:
// these give us the file upload widget:
$form['#attributes']['enctype'] = 'multipart/form-data'; // If this is not here, upload will fail on submit
$form['fid'] = array( '#title' => t('Upload image'),
'#type' => 'file',
'#description' => t('Images must be one of jpg, bmp, gif or png formats.'),
);
E aqui está a contrapartida dessa lógica, que eu tenho no retorno de chamada válido do meu formulário, porque tenho restrições de nome de arquivo de imagem na minha lógica, mas você pode colocá-lo no envio de retorno de chamada, se desejar:
// @see: http://api.drupal.org/api/function/file_save_upload/6
// $file will become 0 if the upload doesn't exist, or an object describing the uploaded file
$file = file_save_upload( 'fid' );
error_log( 'file is "'.print_r( $file, true ).'"' );
if (!$file) {
form_set_error('fid', t('Unable to access file or file is missing.'));
}
é isso aí.
$form['#attributes']['enctype']
no Drupal 7. É feito automaticamente