From 4cdc0437fdc2aca2ca6fedf422a05fbbaca0f2e0 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 8 Jan 2009 13:33:34 -0800 Subject: [PATCH] mknods: merge program to create directories, symlinks, nodes and fifos off a table When booting up, a great amount of time is spent in calling programs to create files, directories, symlinks and device nodes, specially when bringin up a udev-based system. mknods aims to shorten that time by not having to call a myriad small programs (with all the forks and glibc initialization overhead) for doing said task. Takes an ASCII table as input in stdin (or files in the command line) and creates all the files/directories/nodes in a simple go, the overhead being as single system call for each plus another one to adjust ownerships. Signed-off-by: Inaky Perez-Gonzalez --- diff -r cfb91818c50e configure.ac --- a/configure.ac Mon Feb 09 13:21:03 2009 -0800 +++ b/configure.ac Mon Feb 09 15:20:55 2009 -0800 @@ -94,6 +94,7 @@ extras/collect/Makefile extras/floppy/Makefile extras/fstab_import/Makefile + extras/mknods/Makefile extras/rule_generator/Makefile extras/scsi_id/Makefile extras/usb_id/Makefile diff -r cfb91818c50e extras/Makefile.am --- a/extras/Makefile.am Mon Feb 09 13:21:03 2009 -0800 +++ b/extras/Makefile.am Mon Feb 09 15:20:55 2009 -0800 @@ -9,6 +9,7 @@ collect \ floppy \ fstab_import \ + mknods \ rule_generator \ scsi_id \ usb_id \ diff -r cfb91818c50e extras/mknods/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extras/mknods/Makefile.am Mon Feb 09 15:20:55 2009 -0800 @@ -0,0 +1,6 @@ +include $(top_srcdir)/Makefile.am.inc + +udevhomedir = $(udev_prefix)/lib/udev +udevhome_PROGRAMS = \ + mknods + diff -r cfb91818c50e extras/mknods/mknods.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extras/mknods/mknods.c Mon Feb 09 15:20:55 2009 -0800 @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2008 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * See help() for doc on usage + * + * Optimized for speed as much as we can without making it unreadable + * (and without really trying that hard). + * + * Error recovery strategy: pass and keep going, return error at the + * end. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Ugly, but easy to have them as global :( */ +static int line; +static int silent_eexist = 0; +static char *file; +static int my_errno = 0; + +static +void maybe_set_errno(void) +{ + if (my_errno == 0) + my_errno = errno; +} + + +static +void help(FILE *f) +{ + fprintf(f, + "mknods [OPTIONS] [FILES]\n" + "\n" + "Creates a bunch of files or nodes from specifications in files/stdin\n" + "\n" + "(C) 2008 Intel Corporation - Distributed under the terms of the GPLv2 license\n" + "\n" + "The input is line oriented. Either a list of files specified in the command\n" + "line or stdin (if none given). Empty/whitespace lines and lines \n" + "starting with # are ignored. No line continuation with a \\ can be used.\n" + "\n" + "The format is:\n" + "\n" + "- symlinks: sl DESTINATION SOURCE\n" + "- char device: c NAME PERMS USERID GROUPID MAJOR MINOR\n" + "- block device: b NAME PERMS USERID GROUPID MAJOR MINOR\n" + "- directory: d NAME PERMS USERID GROUPID\n" + "\n" + "USERID and GROUPID have to be names, not numeric. Permissions have to\n" + "be in octal.\n" + "\n" + "Command line options:\n" + "\n" + " -h Show this help and exit\n" + " -e Be silent about errors creating files that already exist\n" + ); +} + +static +void error_create(const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + if (errno != EEXIST || (errno == EEXIST && !silent_eexist)) + vfprintf(stderr, fmt, vargs); + va_end(vargs); +} + +static +int mchown(const char *name, int uid, int gid) +{ + if (chown(name, uid, gid) == -1) { + fprintf(stderr, "mknodes: %s: %d: %s: can't chown to %d:%d: %m\n", + file, line, name, uid, gid); + maybe_set_errno(); + return -1; + } + return 0; +} + + +static +int mknodbc(const char *name, int mode, const char *major_str, const char *minor_str) +{ + int major , minor; + major = strtol(major_str, NULL, 0); + if (errno) { + fprintf(stderr, "mknods: %s: %d: %s: can't parse %s as a major number\n", + file, line, name, major_str); + maybe_set_errno(); + return -1; + } + minor = strtol(minor_str, NULL, 0); + if (errno) { + fprintf(stderr, "mknods: %s: %d: %s: can't parse %s as a minor number\n", + file, line, name, minor_str); + maybe_set_errno(); + return -1; + } + if (mknod(name, mode, major << 8 | minor) == -1) { + error_create("mknods: %s: %d: %s: can't create " + "block device: %m\n", file, line, name); + maybe_set_errno(); + return -1; + } + return 0; +} + +static +int parse_perms(const char *name, const char *str) +{ + int perms; + char *tail; + errno = 0; + perms = strtol(str, &tail, 8); + if (errno || *tail != 0) { + fprintf(stderr, "mknods: %s: %d: %s: can't parse '%s' as octal permissions\n", + file, line, name, str); + maybe_set_errno(); + return -1; + } + return perms; +} + + +static +void not_enough_args(int line, int needed) +{ + fprintf(stderr, "mknods: %d: bad number of arguments, need %d\n", + line, needed); +} + + +static +void do_file(FILE *fd, char *file_name) +{ + ssize_t r; + int perm; + + char *buf = NULL; + size_t buf_size = 0; + const char *mode, *name, *perms, *uids, *gids, *major, *minor; + char last_uids[32] = "", last_gids[32] = ""; + uid_t uid = 0; + gid_t gid = 0; + + line = 0; + file = file_name; + while(!feof(fd)) { + r = getline(&buf, &buf_size, fd); + if (r == -1) { + if (feof(fd)) + break; + fprintf(stderr, "mknods: %s: error reading: %m\n", file); + maybe_set_errno(); + return; /* skip the whole file */ + } + line++; + mode = strtok(buf, " \t\n"); + name = strtok(NULL, " \t\n"); + perms = strtok(NULL, " \t\n"); + uids = strtok(NULL, " \t\n"); + gids = strtok(NULL, " \t\n"); + major = strtok(NULL, " \t\n"); + minor = strtok(NULL, " \t\n"); + + if (mode == NULL || mode[0] == '#') /* skip blanks and comments*/ + continue; + + /* Lookup users/gids, cache a little bit */ + if (gids != NULL) { + if (strcmp(last_gids, gids)) { /* cache miss, lookup */ + struct group *g; + g = getgrnam(gids); + if (g == NULL) { + fprintf(stderr, "mknods: %s: %d: can't find group '%s'\n", + file, line, gids); + maybe_set_errno(); + continue; + } + gid = g->gr_gid; + strncpy(last_gids, gids, sizeof(last_gids)); + } + /* else nothing to do, we reuse the gid set last time */ + } + + if (uids != NULL) { + if (strcmp(last_uids, uids)) { /* cache miss, lookup */ + struct passwd *p; + p = getpwnam(uids); + if (p == NULL) { + fprintf(stderr, "mknods: %s: %d: can't find user '%s'\n", + file, line, uids); + maybe_set_errno(); + continue; + } + uid = p->pw_uid; + strncpy(last_uids, uids, sizeof(last_uids)); + } + /* else nothing to do, we reuse the gid set last time */ + } + + /* Do nodes */ + if (!strcmp(mode, "sl")) { + /* for symlinks, the perms string is the destination */ + if (perms == NULL) { + not_enough_args(line, 3); + continue; + } + if (symlink(perms, name) == -1) { + error_create("mknods: %s: %d: can't create " + "symlink from %s to %s: %m\n", + file, line, perms, name); + maybe_set_errno(); + continue; + } + } + else if (!strcmp(mode, "c")) { + if (minor == NULL) { + not_enough_args(line, 7); + continue; + } + perm = parse_perms(name, perms); + if (perm < 0) + continue; + perm = S_IFCHR | perm; + r = mknodbc(name, perm, major, minor); + if (r < 0) + continue; + r = mchown(name, uid, gid); + if (r < 0) + continue; + } + else if (!strcmp(mode, "b")) { + if (minor == NULL) { + not_enough_args(line, 7); + continue; + } + perm = parse_perms(name, perms); + if (perm < 0) + continue; + perm = S_IFBLK | perm; + r = mknodbc(name, perm, major, minor); + if (r < 0) + continue; + r = mchown(name, uid, gid); + if (r < 0) + continue; + } + else if (!strcmp(mode, "d")) { + if (gids == NULL) { + not_enough_args(line, 5); + continue; + } + if (mkdir(name, parse_perms(name, perms)) == -1) { + error_create("mknods: %s: %d: %s: can't create " + "directory: %m\n", file, line, name); + maybe_set_errno(); + continue; + } + r = mchown(name, uid, gid); + if (r < 0) + continue; + } else { + fprintf(stderr, "mknods: %s: %d: unknown mode %s\n", + file, line, mode); + maybe_set_errno(); + continue; + } + } + return; +} + + +int main (int argc, char **argv) +{ + unsigned itr, files = 0; + FILE *fd; + + umask(0); /* no interference, please */ + itr = 1; + while (itr < argc) { + if (!strcmp(argv[itr], "-h")) { + help(stdout); + exit(0); + } else if (!strcmp(argv[itr], "-e")) { + silent_eexist = 1; + itr++; + continue; + } + fd = fopen(argv[itr], "r"); + if (fd != NULL) { + do_file(fd, argv[itr]); + fclose(fd); + } else + fprintf(stderr, "mknods: cannot open %s: %m -- try -h\n", + argv[itr]); + files++; + itr++; + } + if (files == 0) /* no file on command line, do stdin */ + do_file(stdin, "standard input"); + if (my_errno != 0) { + fprintf(stderr, "mknods: delayed error exit: %d\n", my_errno); + return 1; + } + else + return 0; +}