1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2024-10-30 14:55:26 +03:00
systemd-stable/udev_add.c
kay.sievers@vrfy.org 7a947ce515 [PATCH] big cleanup of internal udev api
Here is the first patch to cleanup the internal processing of the
various stages of an udev event. It should not change any behavior,
but if your system depends on udev, please always test it before reboot :)

We pass only one generic structure around between add, remove,
namedev, db and dev_d handling and make all relevant data available
to all internal stages. All udev structures are renamed to "udev".

We replace the fake parameter by a flag in the udev structure.

We open the class device in the main binaries and not in udev_add, to
make it possible to use libsysfs for udevstart directory crawling.

The last sleep parameters are removed.
2005-04-26 22:02:46 -07:00

410 lines
9.2 KiB
C

/*
* udev-add.c
*
* Userspace devfs
*
* Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
*
*
* 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 version 2 of the License.
*
* 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <grp.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#ifndef __KLIBC__
#include <pwd.h>
#include <utmp.h>
#endif
#include "libsysfs/sysfs/libsysfs.h"
#include "udev.h"
#include "udev_lib.h"
#include "udev_version.h"
#include "logging.h"
#include "namedev.h"
#include "udevdb.h"
#include "klibc_fixups.h"
#define LOCAL_USER "$local"
#include "selinux.h"
/*
* the major/minor of a device is stored in a file called "dev"
* The number is stored in decimal values in the format: M:m
*/
static int get_major_minor(struct sysfs_class_device *class_dev, struct udevice *udev)
{
struct sysfs_attribute *attr = NULL;
attr = sysfs_get_classdev_attr(class_dev, "dev");
if (attr == NULL)
goto error;
dbg("dev='%s'", attr->value);
if (sscanf(attr->value, "%u:%u", &udev->major, &udev->minor) != 2)
goto error;
dbg("found major=%d, minor=%d", udev->major, udev->minor);
return 0;
error:
return -1;
}
static int create_path(char *file)
{
char p[NAME_SIZE];
char *pos;
int retval;
struct stat stats;
strfieldcpy(p, file);
pos = strchr(p+1, '/');
while (1) {
pos = strchr(pos+1, '/');
if (pos == NULL)
break;
*pos = 0x00;
if (stat(p, &stats)) {
selinux_setfscreatecon(p, S_IFDIR);
retval = mkdir(p, 0755);
if (retval != 0) {
dbg("mkdir(%s) failed with error '%s'",
p, strerror(errno));
return retval;
}
dbg("created '%s'", p);
} else {
selinux_setfilecon(p, S_IFDIR);
}
*pos = '/';
}
return 0;
}
static int make_node(char *file, int major, int minor, unsigned int mode, uid_t uid, gid_t gid)
{
struct stat stats;
int retval = 0;
if (stat(file, &stats) != 0)
goto create;
/* preserve node with already correct numbers, to not change the inode number */
if (((stats.st_mode & S_IFMT) == S_IFBLK || (stats.st_mode & S_IFMT) == S_IFCHR) &&
(stats.st_rdev == makedev(major, minor))) {
dbg("preserve file '%s', cause it has correct dev_t", file);
selinux_setfilecon(file,stats.st_mode);
goto perms;
}
if (unlink(file) != 0)
dbg("unlink(%s) failed with error '%s'", file, strerror(errno));
else
dbg("already present file '%s' unlinked", file);
create:
selinux_setfscreatecon(file, mode);
retval = mknod(file, mode, makedev(major, minor));
if (retval != 0) {
dbg("mknod(%s, %#o, %u, %u) failed with error '%s'",
file, mode, major, minor, strerror(errno));
goto exit;
}
perms:
dbg("chmod(%s, %#o)", file, mode);
if (chmod(file, mode) != 0) {
dbg("chmod(%s, %#o) failed with error '%s'", file, mode, strerror(errno));
goto exit;
}
if (uid != 0 || gid != 0) {
dbg("chown(%s, %u, %u)", file, uid, gid);
if (chown(file, uid, gid) != 0) {
dbg("chown(%s, %u, %u) failed with error '%s'",
file, uid, gid, strerror(errno));
goto exit;
}
}
exit:
return retval;
}
/* get the local logged in user */
static void set_to_local_user(char *user)
{
struct utmp *u;
time_t recent = 0;
strfieldcpymax(user, default_owner_str, OWNER_SIZE);
setutent();
while (1) {
u = getutent();
if (u == NULL)
break;
/* is this a user login ? */
if (u->ut_type != USER_PROCESS)
continue;
/* is this a local login ? */
if (strcmp(u->ut_host, ""))
continue;
if (u->ut_time > recent) {
recent = u->ut_time;
strfieldcpymax(user, u->ut_user, OWNER_SIZE);
dbg("local user is '%s'", user);
break;
}
}
endutent();
}
static int create_node(struct udevice *udev)
{
char filename[NAME_SIZE];
char linkname[NAME_SIZE];
char linktarget[NAME_SIZE];
char partitionname[NAME_SIZE];
uid_t uid = 0;
gid_t gid = 0;
int i;
int tail;
char *pos;
int len;
strfieldcpy(filename, udev_root);
strfieldcat(filename, udev->name);
switch (udev->type) {
case 'b':
udev->mode |= S_IFBLK;
break;
case 'c':
case 'u':
udev->mode |= S_IFCHR;
break;
case 'p':
udev->mode |= S_IFIFO;
break;
default:
dbg("unknown node type %c\n", udev->type);
return -EINVAL;
}
/* create parent directories if needed */
if (strrchr(udev->name, '/'))
create_path(filename);
if (udev->owner[0] != '\0') {
char *endptr;
unsigned long id = strtoul(udev->owner, &endptr, 10);
if (endptr[0] == '\0')
uid = (uid_t) id;
else {
struct passwd *pw;
if (strncmp(udev->owner, LOCAL_USER, sizeof(LOCAL_USER)) == 0)
set_to_local_user(udev->owner);
pw = getpwnam(udev->owner);
if (pw == NULL)
dbg("specified user unknown '%s'", udev->owner);
else
uid = pw->pw_uid;
}
}
if (udev->group[0] != '\0') {
char *endptr;
unsigned long id = strtoul(udev->group, &endptr, 10);
if (endptr[0] == '\0')
gid = (gid_t) id;
else {
struct group *gr = getgrnam(udev->group);
if (gr == NULL)
dbg("specified group unknown '%s'", udev->group);
else
gid = gr->gr_gid;
}
}
if (!udev->test_run) {
info("creating device node '%s'", filename);
if (make_node(filename, udev->major, udev->minor, udev->mode, uid, gid) != 0)
goto error;
} else {
info("creating device node '%s', major = '%d', minor = '%d', "
"mode = '%#o', uid = '%d', gid = '%d'", filename,
udev->major, udev->minor, (mode_t)udev->mode, uid, gid);
}
/* create all_partitions if requested */
if (udev->partitions > 0) {
info("creating device partition nodes '%s[1-%i]'", filename, udev->partitions);
if (!udev->test_run) {
for (i = 1; i <= udev->partitions; i++) {
strfieldcpy(partitionname, filename);
strintcat(partitionname, i);
make_node(partitionname, udev->major, udev->minor + i, udev->mode, uid, gid);
}
}
}
/* create symlink(s) if requested */
foreach_strpart(udev->symlink, " ", pos, len) {
strfieldcpymax(linkname, pos, len+1);
strfieldcpy(filename, udev_root);
strfieldcat(filename, linkname);
dbg("symlink '%s' to node '%s' requested", filename, udev->name);
if (!udev->test_run)
if (strrchr(linkname, '/'))
create_path(filename);
/* optimize relative link */
linktarget[0] = '\0';
i = 0;
tail = 0;
while ((udev->name[i] == linkname[i]) && udev->name[i]) {
if (udev->name[i] == '/')
tail = i+1;
i++;
}
while (linkname[i] != '\0') {
if (linkname[i] == '/')
strfieldcat(linktarget, "../");
i++;
}
strfieldcat(linktarget, &udev->name[tail]);
dbg("symlink(%s, %s)", linktarget, filename);
if (!udev->test_run) {
selinux_setfscreatecon(filename, S_IFLNK);
unlink(filename);
if (symlink(linktarget, filename) != 0)
dbg("symlink(%s, %s) failed with error '%s'",
linktarget, filename, strerror(errno));
}
}
return 0;
error:
return -1;
}
static int rename_net_if(struct udevice *udev)
{
int sk;
struct ifreq ifr;
int retval;
dbg("changing net interface name from '%s' to '%s'", udev->kernel_name, udev->name);
if (udev->test_run)
return 0;
sk = socket(PF_INET, SOCK_DGRAM, 0);
if (sk < 0) {
dbg("error opening socket");
return -1;
}
memset(&ifr, 0x00, sizeof(struct ifreq));
strfieldcpy(ifr.ifr_name, udev->kernel_name);
strfieldcpy(ifr.ifr_newname, udev->name);
retval = ioctl(sk, SIOCSIFNAME, &ifr);
if (retval != 0)
dbg("error changing net interface name");
close(sk);
return retval;
}
int udev_add_device(struct udevice *udev, struct sysfs_class_device *class_dev)
{
char *pos;
int retval = 0;
if (udev->type == 'b' || udev->type == 'c') {
retval = get_major_minor(class_dev, udev);
if (retval != 0) {
dbg("no dev-file found, do nothing");
goto close;
}
}
if (namedev_name_device(udev, class_dev) != 0)
goto exit;
dbg("adding name='%s'", udev->name);
selinux_init();
switch (udev->type) {
case 'b':
case 'c':
retval = create_node(udev);
if (retval != 0)
goto exit;
if ((!udev->test_run) && (udevdb_add_dev(udev) != 0))
dbg("udevdb_add_dev failed, but we are going to try "
"to create the node anyway. But remove might not "
"work properly for this device.");
dev_d_send(udev);
break;
case 'n':
if (strcmp(udev->name, udev->kernel_name) != 0) {
retval = rename_net_if(udev);
if (retval != 0)
goto exit;
/* netif's are keyed with the configured name, cause
* the original kernel name sleeps with the fishes
*/
pos = strrchr(udev->devpath, '/');
if (pos != NULL) {
pos[1] = '\0';
strfieldcat(udev->devpath, udev->name);
}
}
if ((!udev->test_run) && (udevdb_add_dev(udev) != 0))
dbg("udevdb_add_dev failed");
dev_d_send(udev);
break;
}
exit:
selinux_restore();
close:
sysfs_close_class_device(class_dev);
return retval;
}