1 /*
2  *  ser2net - A program for allowing telnet connection to serial ports
3  *  Copyright (C) 2001-2016  Corey Minyard <minyard@acm.org>
4  *  Copyright (C) 2015 I2SE GmbH <info@i2se.com>
5  *  Copyright (C) 2016 Michael Heimpold <mhei@heimpold.de>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 
22 /* This file contains a LED driver for Linux's sysfs based LEDs. */
23 
24 #ifdef USE_SYSFS_LED_FEATURE
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <sys/types.h>
31 #include <syslog.h>
32 #include "led.h"
33 
34 #define SYSFS_LED_BASE "/sys/class/leds"
35 
36 #define BUFSIZE 4096
37 
38 struct led_sysfs_s
39 {
40     char *device;
41     int state;
42     int duration;
43 };
44 
45 static int
led_is_trigger_missing(const char * led)46 led_is_trigger_missing(const char *led)
47 {
48     char *buffer, *trigger;
49     int fd, c;
50 
51     buffer = malloc(BUFSIZE);
52     if (!buffer)
53 	return -1;
54 
55     snprintf(buffer, BUFSIZE, "%s/%s/trigger", SYSFS_LED_BASE, led);
56 
57     if ((fd = open(buffer, O_RDONLY)) == -1) {
58 	free(buffer);
59 	return -1;
60     }
61 
62     if ((c = read(fd, buffer, BUFSIZE)) <= 0) {
63 	free(buffer);
64 	close(fd);
65 	return -1;
66     }
67 
68     if (close(fd) < 0) {
69 	free(buffer);
70 	return -1;
71     }
72 
73     buffer[c] = '\0';
74     trigger = strstr(buffer, "transient");
75     free(buffer);
76     return trigger == NULL;
77 }
78 
79 static int
led_write(const char * led,const char * property,const char * buf)80 led_write(const char *led, const char *property, const char *buf)
81 {
82     char filename[255];
83     int fd;
84 
85     snprintf(filename, sizeof(filename), "%s/%s/%s", SYSFS_LED_BASE, led, property);
86 
87     if ((fd = open(filename, O_WRONLY | O_TRUNC)) == -1)
88 	return -1;
89 
90     if (write(fd, buf, strlen(buf)) != strlen(buf)) {
91 	close(fd);
92 	return -1;
93     }
94 
95     return close(fd);
96 }
97 
98 static int
led_sysfs_init(struct led_s * led,char * parameters,int lineno)99 led_sysfs_init(struct led_s *led, char *parameters, int lineno)
100 {
101     struct led_sysfs_s *drv_data = NULL;
102     char *str1, *str2, *token, *subtoken;
103     char *saveptr1, *saveptr2;
104     char *key, *value;
105     int i;
106 
107     drv_data = calloc(1, sizeof(*drv_data));
108     if (!drv_data) {
109 	syslog(LOG_ERR,
110 	       "Out of memory handling LED %s on line %d.",
111 	       led->name, lineno);
112 	return -1;
113     }
114 
115     /* preset to detect default and/or wrong user input */
116     drv_data->state = -1;
117 
118     /* parse parameter key=value pairs - seperated by whitespace */
119     for (str1 = parameters; ; str1 = NULL) {
120 	token = strtok_r(str1, " \t", &saveptr1);
121 	if (!token)
122 	    break;
123 
124 	/* parse single key=value pair */
125 	for (i = 0, str2 = token; ; i++, str2 = NULL) {
126 	    subtoken = strtok_r(str2, "=", &saveptr2);
127 	    if (!subtoken)
128 		break;
129 
130 	    if (i == 0)
131 		key = subtoken;
132 
133 	    if (i == 1) {
134 		value = subtoken;
135 
136 		if (strcasecmp(key, "device") == 0) {
137 		    /* if 'device' is given more than once, last wins */
138 		    if (drv_data->device)
139 			free(drv_data->device);
140 
141 		    drv_data->device = strdup(value);
142 		    if (!drv_data->device) {
143 			syslog(LOG_ERR,
144 			       "Out of memory handling LED '%s' on line %d.",
145 			       led->name, lineno);
146 			return -1;
147 		    }
148 		}
149 
150 		if (strcasecmp(key, "duration") == 0)
151 		    drv_data->duration = atoi(value);
152 
153 		if (strcasecmp(key, "state") == 0)
154 		    drv_data->state = atoi(value);
155 	    }
156 	}
157     }
158 
159     if (!drv_data->device) {
160 	syslog(LOG_ERR,
161 	       "LED '%s': parameter 'device' required, but missing on line %d.",
162 	       led->name, lineno);
163 	free(drv_data);
164 	return -1;
165     }
166 
167     if (drv_data->duration < 0) {
168 	syslog(LOG_ERR,
169 	       "LED '%s': invalid duration, using default on line %d.",
170 	       led->name, lineno);
171 	drv_data->duration = 10;
172     }
173     if (drv_data->duration == 0)
174 	drv_data->duration = 10;
175 
176 
177     if (drv_data->state == -1)
178 	drv_data->state = 1;
179     if (drv_data->state < 0 || drv_data->state > 1) {
180 	syslog(LOG_ERR,
181 	       "LED '%s': invalid state, using default on line %d.",
182 	       led->name, lineno);
183 	drv_data->state = 1;
184     }
185 
186     led->drv_data = (void *)drv_data;
187 
188     return 0;
189 }
190 
191 static int
led_sysfs_free(struct led_s * led)192 led_sysfs_free(struct led_s *led)
193 {
194     struct led_sysfs_s *ctx = (struct led_sysfs_s *)led->drv_data;
195 
196     free(ctx->device);
197     free(ctx);
198 
199     led->drv_data = NULL;
200     return 0;
201 }
202 
203 static int
led_sysfs_configure(void * led_driver_data)204 led_sysfs_configure(void *led_driver_data)
205 {
206     struct led_sysfs_s *ctx = (struct led_sysfs_s *)led_driver_data;
207     char buffer[255];
208     int rv = 0;
209 
210     /* check whether we can enable the transient trigger for this led */
211     rv = led_is_trigger_missing(ctx->device);
212     if (rv != 0)
213 	return rv;
214 
215     /*
216      * switch to transient trigger, this will kick creation of additional
217      * property file in sysfs
218      */
219     rv = led_write(ctx->device, "trigger", "transient");
220     if (rv)
221 	return rv;
222 
223     /* pre-configure the trigger for our needs */
224     snprintf(buffer, sizeof(buffer), "%d", ctx->duration);
225     rv |= led_write(ctx->device, "duration", buffer);
226 
227     snprintf(buffer, sizeof(buffer), "%d", ctx->state);
228     rv |= led_write(ctx->device, "state", buffer);
229 
230     return rv;
231 }
232 
233 static int
led_sysfs_flash(void * led_driver_data)234 led_sysfs_flash(void *led_driver_data)
235 {
236     struct led_sysfs_s *ctx = (struct led_sysfs_s *)led_driver_data;
237 
238     return led_write(ctx->device, "activate", "1");
239 }
240 
241 static int
led_sysfs_deconfigure(void * led_driver_data)242 led_sysfs_deconfigure(void *led_driver_data)
243 {
244     struct led_sysfs_s *ctx = (struct led_sysfs_s *)led_driver_data;
245     int rv = 0;
246 
247 
248     rv |= led_write(ctx->device, "trigger", "none");
249     rv |= led_write(ctx->device, "brightness", "0");
250 
251     return rv;
252 }
253 
254 static struct led_driver_s led_sysfs_driver = {
255     .name        = "sysfs",
256 
257     .init        = led_sysfs_init,
258     .free        = led_sysfs_free,
259 
260     .configure   = led_sysfs_configure,
261     .flash       = led_sysfs_flash,
262     .deconfigure = led_sysfs_deconfigure,
263 };
264 
265 int
led_sysfs_register(void)266 led_sysfs_register(void)
267 {
268     return led_driver_register(&led_sysfs_driver);
269 }
270 #else
271 int
led_sysfs_register(void)272 led_sysfs_register(void)
273 {
274     return 0;
275 }
276 #endif
277