/* * firmware - Load firmware device * * Copyright (C) 2009 Piter Punk * Copyright (C) 2009 Kay Sievers * * 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 #include #include #include #include #include #include #include #include #include "libudev-private.h" static bool set_loading(struct udev *udev, char *loadpath, const char *state) { FILE *ldfile; ldfile = fopen(loadpath, "w"); if (ldfile == NULL) { err(udev, "error: can not open '%s'\n", loadpath); return false; }; fprintf(ldfile, "%s\n", state); fclose(ldfile); return true; } static bool copy_firmware(struct udev *udev, const char *source, const char *target, size_t size) { char *buf; FILE *fsource, *ftarget; bool ret = false; buf = malloc(size); if (buf == NULL) { err(udev,"No memory available to load firmware file"); return false; } fsource = fopen(source, "r"); if (fsource == NULL) goto exit; ftarget = fopen(target, "w"); if (ftarget == NULL) goto exit; if (fread(buf, size, 1, fsource) != 1) goto exit; if (fwrite(buf, size, 1, ftarget) == 1) ret = true; exit: fclose(ftarget); fclose(fsource); free(buf); return ret; } int main(int argc, char **argv) { static const struct option options[] = { { "firmware", required_argument, NULL, 'f' }, { "devpath", required_argument, NULL, 'p' }, { "help", no_argument, NULL, 'h' }, {} }; static const char *searchpath[] = { "/lib/firmware/updates/", "/lib/firmware/" }; 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]; char *devpath = NULL; char *firmware = NULL; FILE *fwfile; struct utsname kernel; struct stat statbuf; struct udev *udev = NULL; unsigned int i; int rc = 0; udev_log_init("firmware"); for (;;) { int option; option = getopt_long(argc, argv, "f:p:h", options, NULL); if (option == -1) break; switch (option) { case 'f': firmware = optarg; break; case 'p': devpath = optarg; break; case 'h': printf("Usage: firmware --firmware= --devpath= [--help]\n\n"); default: rc = 1; goto exit; } } if (devpath == NULL || firmware == NULL) { fprintf(stderr, "firmware or devpath parameter missing\n\n"); rc = 1; goto exit; } udev = udev_new(); if (udev == NULL) { rc = 1; goto exit; }; /* lookup firmware file */ uname(&kernel); for (i = 0; i < ARRAY_SIZE(searchpath); i++) { util_strscpyl(fwpath, sizeof(fwpath), searchpath[i], kernel.release, "/", firmware, NULL); dbg(udev, "trying %s\n", fwpath); fwfile = fopen(fwpath, "r"); if (fwfile != NULL) break; util_strscpyl(fwpath, sizeof(fwpath), searchpath[i], firmware, NULL); dbg(udev, "trying %s\n", fwpath); fwfile = fopen(fwpath, "r"); if (fwfile != NULL) break; } util_path_encode(firmware, fwencpath, sizeof(fwencpath)); util_strscpyl(misspath, sizeof(misspath), udev_get_dev_path(udev), "/.udev/firmware-missing/", fwencpath, NULL); util_strscpyl(loadpath, sizeof(loadpath), udev_get_sys_path(udev), devpath, "/loading", NULL); if (fwfile == NULL) { int err; /* This link indicates the missing firmware file and the associated device */ info(udev, "did not find firmware file '%s'\n", firmware); do { err = util_create_path(udev, misspath); if (err != 0 && err != -ENOENT) break; udev_selinux_setfscreatecon(udev, misspath, S_IFLNK); err = symlink(devpath, misspath); if (err != 0) err = -errno; udev_selinux_resetfscreatecon(udev); } while (err == -ENOENT); rc = 2; set_loading(udev, loadpath, "-1"); goto exit; } if (stat(fwpath, &statbuf) < 0 || statbuf.st_size == 0) { rc = 3; goto exit; } if (unlink(misspath) == 0) util_delete_path(udev, misspath); set_loading(udev, loadpath, "1"); util_strscpyl(datapath, sizeof(datapath), udev_get_sys_path(udev), devpath, "/data", NULL); if (!copy_firmware(udev, fwpath, datapath, statbuf.st_size)) { err(udev, "error sending firmware '%s' to device\n", firmware); set_loading(udev, loadpath, "-1"); rc = 4; goto exit; }; set_loading(udev, loadpath, "0"); exit: udev_unref(udev); udev_log_close(); return rc; }