aboutsummaryrefslogtreecommitdiffstats
path: root/init/do_mounts_devfs.c
blob: cc526474690a68a155282e75154d25d0dff8f423 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <linux/kernel.h>
#include <linux/dirent.h>
#include <linux/string.h>

#include "do_mounts.h"

void __init mount_devfs(void)
{
	sys_mount("devfs", "/dev", "devfs", 0, NULL);
}

void __init umount_devfs(char *path)
{
	sys_umount(path, 0);
}

/*
 * If the dir will fit in *buf, return its length.  If it won't fit, return
 * zero.  Return -ve on error.
 */
static int __init do_read_dir(int fd, void *buf, int len)
{
	long bytes, n;
	char *p = buf;
	sys_lseek(fd, 0, 0);

	for (bytes = 0; bytes < len; bytes += n) {
		n = sys_getdents64(fd, (struct linux_dirent64 *)(p + bytes),
					len - bytes);
		if (n < 0)
			return n;
		if (n == 0)
			return bytes;
	}
	return 0;
}

/*
 * Try to read all of a directory.  Returns the contents at *p, which
 * is kmalloced memory.  Returns the number of bytes read at *len.  Returns
 * NULL on error.
 */
static void * __init read_dir(char *path, int *len)
{
	int size;
	int fd = sys_open(path, 0, 0);

	*len = 0;
	if (fd < 0)
		return NULL;

	for (size = 1 << 9; size <= (PAGE_SIZE << MAX_ORDER); size <<= 1) {
		void *p = kmalloc(size, GFP_KERNEL);
		int n;
		if (!p)
			break;
		n = do_read_dir(fd, p, size);
		if (n > 0) {
			sys_close(fd);
			*len = n;
			return p;
		}
		kfree(p);
		if (n == -EINVAL)
			continue;	/* Try a larger buffer */
		if (n < 0)
			break;
	}
	sys_close(fd);
	return NULL;
}

/*
 * recursively scan <path>, looking for a device node of type <dev>
 */
static int __init find_in_devfs(char *path, unsigned dev)
{
	char *end = path + strlen(path);
	int rest = path + 64 - end;
	int size;
	char *p = read_dir(path, &size);
	char *s;

	if (!p)
		return -1;
	for (s = p; s < p + size; s += ((struct linux_dirent64 *)s)->d_reclen) {
		struct linux_dirent64 *d = (struct linux_dirent64 *)s;
		if (strlen(d->d_name) + 2 > rest)
			continue;
		switch (d->d_type) {
			case DT_BLK:
				sprintf(end, "/%s", d->d_name);
				if (bstat(path) != dev)
					break;
				kfree(p);
				return 0;
			case DT_DIR:
				if (strcmp(d->d_name, ".") == 0)
					break;
				if (strcmp(d->d_name, "..") == 0)
					break;
				sprintf(end, "/%s", d->d_name);
				if (find_in_devfs(path, dev) < 0)
					break;
				kfree(p);
				return 0;
		}
	}
	kfree(p);
	return -1;
}

/*
 * create a device node called <name> which points to
 * <devfs_name> if possible, otherwise find a device node
 * which matches <dev> and make <name> a symlink pointing to it.
 */
int __init create_dev(char *name, dev_t dev, char *devfs_name)
{
	char path[64];

	sys_unlink(name);
	if (devfs_name && devfs_name[0]) {
		if (strncmp(devfs_name, "/dev/", 5) == 0)
			devfs_name += 5;
		sprintf(path, "/dev/%s", devfs_name);
		if (sys_access(path, 0) == 0)
			return sys_symlink(devfs_name, name);
	}
	if (!dev)
		return -1;
	strcpy(path, "/dev");
	if (find_in_devfs(path, new_encode_dev(dev)) < 0)
		return -1;
	return sys_symlink(path + 5, name);
}