1/*
2 * Access helpers for sysfs.
3 * Copyright (c) Mark Lord 2008
4 *
5 * You may use/distribute this freely, under the terms of either
6 * (your choice) the GNU General Public License version 2,
7 * or a BSD style license.
8 */
9#include <stdlib.h>
10#include <unistd.h>
11#include <string.h>
12#include <stdio.h>
13#include <fcntl.h>
14#include <errno.h>
15#include <dirent.h>
16#include <sys/stat.h>
17#include <linux/types.h>
18
19#include "hdparm.h"
20
21static char *path_append (char *path, const char *new)
22{
23	char *pathtail = path + strlen(path);
24
25	*pathtail = '/';
26	strcpy(pathtail+1, new);
27	return pathtail;
28}
29
30static int sysfs_write_attr (char *path, const char *attr, const char *fmt, void *val, int verbose)
31{
32	FILE *fp;
33	int count = -1, err = 0;
34	char *pathtail = path_append(path, attr);
35
36	fp = fopen(path, "w");
37	if (!fp) {
38		err = errno;
39	} else if (fmt[0] != '%') {
40		err = EINVAL;
41	} else {
42		switch (fmt[1]) {
43			case 's':
44				count = fprintf(fp, fmt, val);
45				break;
46			case 'd':
47			case 'u':
48				count = fprintf(fp, fmt, *(unsigned int *)val);
49				break;
50			case 'l':
51				if (fmt[2] == 'l')
52					count = fprintf(fp, fmt, *(unsigned long long *)val);
53				else
54					count = fprintf(fp, fmt, *(unsigned long *)val);
55				break;
56			default:
57				errno = EINVAL;
58		}
59		if (count < 0)
60			err = errno;
61		fclose(fp);
62	}
63	if (err && verbose) perror(path);
64	*pathtail = '\0';
65	return err;
66}
67
68static int sysfs_read_attr (char *path, const char *attr, const char *fmt, void *val1, void *val2, int verbose)
69{
70	FILE *fp;
71	int count, err = 0;
72	char *pathtail = path_append(path, attr);
73
74	fp = fopen(path, "r");
75	if (!fp) {
76		err = errno;
77	} else {
78		count = fscanf(fp, fmt, val1, val2);
79		if (count != (val2 ? 2 : 1))
80			err = (count == EOF) ? errno : EINVAL;
81		fclose(fp);
82	}
83	if (err && verbose) perror(path);
84	*pathtail = '\0';
85	return err;
86}
87
88static int sysfs_find_dev2 (char *path, dev_t dev, int recurse, int verbose)
89{
90	DIR *dp;
91	struct dirent *entry;
92	char *pathtail;
93
94	if (!(dp = opendir(path))) {
95		int err = errno;
96		if (verbose) perror(path);
97		return err;
98	}
99	pathtail = path + strlen(path);
100	while ((entry = readdir(dp)) != NULL) {
101		if ((entry->d_type == DT_DIR || entry->d_type == DT_LNK) && entry->d_name[0] != '.') {
102			unsigned int maj, min;
103			sprintf(pathtail, "/%s", entry->d_name);
104			if (sysfs_read_attr(path, "/dev", "%u:%u", &maj, &min, verbose))
105				min = ~minor(dev);
106			else if (maj != (unsigned)major(dev))
107				continue;
108			if (min == (unsigned)minor(dev)
109			 || (recurse && sysfs_find_dev2(path, dev, recurse - 1, verbose) == 0)) {
110				closedir(dp);
111				return 0;
112			}
113		}
114	}
115	closedir(dp);
116	*pathtail = '\0';
117	if (verbose)
118		fprintf(stderr, "%u,%u: device not found in /sys\n", major(dev), minor(dev));
119	return ENOENT;
120}
121
122static int sysfs_find_dev (dev_t dev, char *path, int verbose)
123{
124	int err, recurse = 1;
125
126	strcpy(path, "/sys/block");
127	err = sysfs_find_dev2(path, dev, recurse, 0);
128	if (err && verbose)
129		fprintf(stderr, "%s(%u:%u): %s\n", __func__,
130			major(dev), minor(dev), strerror(err));
131	return err;
132}
133
134static int get_dev_from_fd (int fd, dev_t *dev, int verbose)
135{
136	struct stat st;
137
138	if (0 != fstat(fd, &st)) {
139		int err = errno;
140		if (verbose) perror(" fstat() failed");
141		return err;
142	}
143	if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))
144		*dev = st.st_rdev;
145	else
146		*dev = st.st_dev;
147	return 0;
148}
149
150static int sysfs_find_fd (int fd, char **path_p, int verbose)
151{
152	static int have_prev = 0;
153	static dev_t prev;
154	static char path[PATH_MAX];
155	dev_t dev;
156	int err;
157
158	memset(&dev, 0, sizeof(dev));
159	err = get_dev_from_fd(fd, &dev, verbose);
160	if (!err) {
161		if (have_prev && 0 == memcmp(&dev, &prev, sizeof(dev))) {
162			/*re-use previous path, since dev was unchanged from before */
163		} else {
164			prev = dev;
165			have_prev = 1;
166			err = sysfs_find_dev(dev, path, verbose);
167		}
168	}
169	if (err)
170		have_prev = 0;
171	else
172		*path_p = path;
173	return err;
174}
175
176int sysfs_get_attr (int fd, const char *attr, const char *fmt, void *val1, void *val2, int verbose)
177{
178	char *path;
179	int err;
180
181	err = sysfs_find_fd(fd, &path, verbose);
182	if (!err)
183		err = sysfs_read_attr(path, attr, fmt, val1, val2, verbose);
184	return err;
185}
186
187int sysfs_set_attr (int fd, const char *attr, const char *fmt, void *val_p, int verbose)
188{
189	char *path;
190	int err;
191
192	err = sysfs_find_fd(fd, &path, verbose);
193	if (!err)
194		err = sysfs_write_attr(path, attr, fmt, val_p, verbose);
195	return err;
196}
197