1// SPDX-License-Identifier: GPL-2.0-only
2/* Disk protection for HP/DELL machines.
3 *
4 * Copyright 2008 Eric Piel
5 * Copyright 2009 Pavel Machek <pavel@ucw.cz>
6 * Copyright 2012 Sonal Santan
7 * Copyright 2014 Pali Roh��r <pali@kernel.org>
8 */
9
10#include <stdio.h>
11#include <stdlib.h>
12#include <unistd.h>
13#include <fcntl.h>
14#include <sys/stat.h>
15#include <sys/types.h>
16#include <string.h>
17#include <stdint.h>
18#include <errno.h>
19#include <signal.h>
20#include <sys/mman.h>
21#include <sched.h>
22#include <syslog.h>
23
24static int noled;
25static char unload_heads_path[64];
26static char device_path[32];
27static const char app_name[] = "FREE FALL";
28
29static int set_unload_heads_path(char *device)
30{
31	if (strlen(device) <= 5 || strncmp(device, "/dev/", 5) != 0)
32		return -EINVAL;
33	strncpy(device_path, device, sizeof(device_path) - 1);
34
35	snprintf(unload_heads_path, sizeof(unload_heads_path) - 1,
36				"/sys/block/%s/device/unload_heads", device+5);
37	return 0;
38}
39
40static int valid_disk(void)
41{
42	int fd = open(unload_heads_path, O_RDONLY);
43
44	if (fd < 0) {
45		perror(unload_heads_path);
46		return 0;
47	}
48
49	close(fd);
50	return 1;
51}
52
53static void write_int(char *path, int i)
54{
55	char buf[1024];
56	int fd = open(path, O_RDWR);
57
58	if (fd < 0) {
59		perror("open");
60		exit(1);
61	}
62
63	sprintf(buf, "%d", i);
64
65	if (write(fd, buf, strlen(buf)) != strlen(buf)) {
66		perror("write");
67		exit(1);
68	}
69
70	close(fd);
71}
72
73static void set_led(int on)
74{
75	if (noled)
76		return;
77	write_int("/sys/class/leds/hp::hddprotect/brightness", on);
78}
79
80static void protect(int seconds)
81{
82	const char *str = (seconds == 0) ? "Unparked" : "Parked";
83
84	write_int(unload_heads_path, seconds*1000);
85	syslog(LOG_INFO, "%s %s disk head\n", str, device_path);
86}
87
88static int on_ac(void)
89{
90	/* /sys/class/power_supply/AC0/online */
91	return 1;
92}
93
94static int lid_open(void)
95{
96	/* /proc/acpi/button/lid/LID/state */
97	return 1;
98}
99
100static void ignore_me(int signum)
101{
102	protect(0);
103	set_led(0);
104}
105
106int main(int argc, char **argv)
107{
108	int fd, ret;
109	struct stat st;
110	struct sched_param param;
111
112	if (argc == 1)
113		ret = set_unload_heads_path("/dev/sda");
114	else if (argc == 2)
115		ret = set_unload_heads_path(argv[1]);
116	else
117		ret = -EINVAL;
118
119	if (ret || !valid_disk()) {
120		fprintf(stderr, "usage: %s <device> (default: /dev/sda)\n",
121				argv[0]);
122		exit(1);
123	}
124
125	fd = open("/dev/freefall", O_RDONLY);
126	if (fd < 0) {
127		perror("/dev/freefall");
128		return EXIT_FAILURE;
129	}
130
131	if (stat("/sys/class/leds/hp::hddprotect/brightness", &st))
132		noled = 1;
133
134	if (daemon(0, 0) != 0) {
135		perror("daemon");
136		return EXIT_FAILURE;
137	}
138
139	openlog(app_name, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
140
141	param.sched_priority = sched_get_priority_max(SCHED_FIFO);
142	sched_setscheduler(0, SCHED_FIFO, &param);
143	mlockall(MCL_CURRENT|MCL_FUTURE);
144
145	signal(SIGALRM, ignore_me);
146
147	for (;;) {
148		unsigned char count;
149
150		ret = read(fd, &count, sizeof(count));
151		alarm(0);
152		if ((ret == -1) && (errno == EINTR)) {
153			/* Alarm expired, time to unpark the heads */
154			continue;
155		}
156
157		if (ret != sizeof(count)) {
158			perror("read");
159			break;
160		}
161
162		protect(21);
163		set_led(1);
164		if (1 || on_ac() || lid_open())
165			alarm(2);
166		else
167			alarm(20);
168	}
169
170	closelog();
171	close(fd);
172	return EXIT_SUCCESS;
173}
174