2011-12-25 21:10:07 +04:00
/*
* firmware - Kernel firmware loader
*
* Copyright ( C ) 2009 Piter Punk < piterpunk @ slackware . com >
2012-11-12 22:36:23 +04:00
* Copyright ( C ) 2009 - 2011 Kay Sievers < kay @ vrfy . org >
2011-12-25 21:10:07 +04:00
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation ; either version 2 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details : *
*/
# include <unistd.h>
# include <stdlib.h>
# include <string.h>
# include <stdio.h>
# include <getopt.h>
# include <errno.h>
# include <stdbool.h>
# include <sys/utsname.h>
# include <sys/stat.h>
# include "udev.h"
static bool set_loading ( struct udev * udev , char * loadpath , const char * state )
{
2012-01-10 04:34:15 +04:00
FILE * ldfile ;
ldfile = fopen ( loadpath , " we " ) ;
if ( ldfile = = NULL ) {
2012-04-08 18:06:20 +04:00
log_error ( " error: can not open '%s' \n " , loadpath ) ;
2012-01-10 04:34:15 +04:00
return false ;
} ;
fprintf ( ldfile , " %s \n " , state ) ;
fclose ( ldfile ) ;
return true ;
2011-12-25 21:10:07 +04:00
}
static bool copy_firmware ( struct udev * udev , const char * source , const char * target , size_t size )
{
2012-01-10 04:34:15 +04:00
char * buf ;
FILE * fsource = NULL , * ftarget = NULL ;
bool ret = false ;
buf = malloc ( size ) ;
if ( buf = = NULL ) {
2012-04-08 18:06:20 +04:00
log_error ( " No memory available to load firmware file " ) ;
2012-01-10 04:34:15 +04:00
return false ;
}
2012-04-08 18:06:20 +04:00
log_debug ( " writing '%s' (%zi) to '%s' \n " , source , size , target ) ;
2012-01-10 04:34:15 +04:00
fsource = fopen ( source , " re " ) ;
if ( fsource = = NULL )
goto exit ;
ftarget = fopen ( target , " we " ) ;
if ( ftarget = = NULL )
goto exit ;
if ( fread ( buf , size , 1 , fsource ) ! = 1 )
goto exit ;
if ( fwrite ( buf , size , 1 , ftarget ) = = 1 )
ret = true ;
2011-12-25 21:10:07 +04:00
exit :
2012-01-10 04:34:15 +04:00
if ( ftarget ! = NULL )
fclose ( ftarget ) ;
if ( fsource ! = NULL )
fclose ( fsource ) ;
free ( buf ) ;
return ret ;
2011-12-25 21:10:07 +04:00
}
static int builtin_firmware ( struct udev_device * dev , int argc , char * argv [ ] , bool test )
{
2012-01-10 04:34:15 +04:00
struct udev * udev = udev_device_get_udev ( dev ) ;
static const char * searchpath [ ] = { FIRMWARE_PATH } ;
char fwencpath [ UTIL_PATH_SIZE ] ;
char misspath [ UTIL_PATH_SIZE ] ;
char loadpath [ UTIL_PATH_SIZE ] ;
char datapath [ UTIL_PATH_SIZE ] ;
char fwpath [ UTIL_PATH_SIZE ] ;
const char * firmware ;
2012-04-13 17:08:55 +04:00
FILE * fwfile = NULL ;
2012-01-10 04:34:15 +04:00
struct utsname kernel ;
struct stat statbuf ;
unsigned int i ;
int rc = EXIT_SUCCESS ;
firmware = udev_device_get_property_value ( dev , " FIRMWARE " ) ;
if ( firmware = = NULL ) {
2012-04-08 18:06:20 +04:00
log_error ( " firmware parameter missing \n \n " ) ;
2012-01-10 04:34:15 +04:00
rc = EXIT_FAILURE ;
goto exit ;
}
/* lookup firmware file */
uname ( & kernel ) ;
2012-04-16 05:13:22 +04:00
for ( i = 0 ; i < ELEMENTSOF ( searchpath ) ; i + + ) {
2012-01-10 04:34:15 +04:00
util_strscpyl ( fwpath , sizeof ( fwpath ) , searchpath [ i ] , kernel . release , " / " , firmware , NULL ) ;
fwfile = fopen ( fwpath , " re " ) ;
if ( fwfile ! = NULL )
break ;
util_strscpyl ( fwpath , sizeof ( fwpath ) , searchpath [ i ] , firmware , NULL ) ;
fwfile = fopen ( fwpath , " re " ) ;
if ( fwfile ! = NULL )
break ;
}
util_path_encode ( firmware , fwencpath , sizeof ( fwencpath ) ) ;
2012-04-16 19:21:22 +04:00
util_strscpyl ( misspath , sizeof ( misspath ) , " /run/udev/firmware-missing/ " , fwencpath , NULL ) ;
2012-01-10 04:34:15 +04:00
util_strscpyl ( loadpath , sizeof ( loadpath ) , udev_device_get_syspath ( dev ) , " /loading " , NULL ) ;
if ( fwfile = = NULL ) {
int err ;
/* This link indicates the missing firmware file and the associated device */
2012-04-08 18:06:20 +04:00
log_debug ( " did not find firmware file '%s' \n " , firmware ) ;
2012-01-10 04:34:15 +04:00
do {
2012-05-31 15:20:06 +04:00
err = mkdir_parents ( misspath , 0755 ) ;
2012-01-10 04:34:15 +04:00
if ( err ! = 0 & & err ! = - ENOENT )
break ;
err = symlink ( udev_device_get_devpath ( dev ) , misspath ) ;
if ( err ! = 0 )
err = - errno ;
} while ( err = = - ENOENT ) ;
rc = EXIT_FAILURE ;
2012-07-19 14:32:24 +04:00
/*
* Do not cancel the request in the initrd , the real root might have
* the firmware file and the ' coldplug ' run in the real root will find
* this pending request and fulfill or cancel it .
* */
if ( ! in_initrd ( ) )
set_loading ( udev , loadpath , " -1 " ) ;
2012-01-10 04:34:15 +04:00
goto exit ;
}
if ( stat ( fwpath , & statbuf ) < 0 | | statbuf . st_size = = 0 ) {
2012-11-16 20:07:19 +04:00
if ( ! in_initrd ( ) )
set_loading ( udev , loadpath , " -1 " ) ;
2012-01-10 04:34:15 +04:00
rc = EXIT_FAILURE ;
goto exit ;
}
2012-11-16 20:07:19 +04:00
2012-01-10 04:34:15 +04:00
if ( unlink ( misspath ) = = 0 )
util_delete_path ( udev , misspath ) ;
if ( ! set_loading ( udev , loadpath , " 1 " ) )
goto exit ;
util_strscpyl ( datapath , sizeof ( datapath ) , udev_device_get_syspath ( dev ) , " /data " , NULL ) ;
if ( ! copy_firmware ( udev , fwpath , datapath , statbuf . st_size ) ) {
2012-04-08 18:06:20 +04:00
log_error ( " error sending firmware '%s' to device \n " , firmware ) ;
2012-01-10 04:34:15 +04:00
set_loading ( udev , loadpath , " -1 " ) ;
rc = EXIT_FAILURE ;
goto exit ;
} ;
set_loading ( udev , loadpath , " 0 " ) ;
2011-12-25 21:10:07 +04:00
exit :
2012-01-10 04:34:15 +04:00
if ( fwfile )
fclose ( fwfile ) ;
return rc ;
2011-12-25 21:10:07 +04:00
}
const struct udev_builtin udev_builtin_firmware = {
2012-01-10 04:34:15 +04:00
. name = " firmware " ,
. cmd = builtin_firmware ,
. help = " kernel firmware loader " ,
. run_once = true ,
2011-12-25 21:10:07 +04:00
} ;