Add an option to shutdown to allow for receiving the control socket file
[oweals/dinit.git] / src / shutdown.cc
1 #include <cstddef>
2 #include <cstdio>
3 #include <csignal>
4 #include <unistd.h>
5 #include <cstring>
6 #include <string>
7 #include <iostream>
8
9 #include <sys/reboot.h>
10 #include <sys/types.h>
11 #include <sys/socket.h>
12 #include <sys/un.h>
13 #include <sys/wait.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16
17 #include "cpbuffer.h"
18 #include "control-cmds.h"
19 #include "service-constants.h"
20
21 // shutdown:  shut down the system
22 // This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
23
24 void do_system_shutdown(ShutdownType shutdown_type);
25 static void unmount_disks();
26 static void swap_off();
27 static void wait_for_reply(CPBuffer<1024> &rbuffer, int fd);
28
29
30 class ReadCPException
31 {
32     public:
33     int errcode;
34     ReadCPException(int err) : errcode(err) { }
35 };
36
37
38 int main(int argc, char **argv)
39 {
40     using namespace std;
41     
42     bool show_help = false;
43     bool sys_shutdown = false;
44     bool use_passed_cfd = false;
45     
46     auto shutdown_type = ShutdownType::POWEROFF;
47         
48     for (int i = 1; i < argc; i++) {
49         if (argv[i][0] == '-') {
50             if (strcmp(argv[i], "--help") == 0) {
51                 show_help = true;
52                 break;
53             }
54             
55             if (strcmp(argv[i], "--system") == 0) {
56                 sys_shutdown = true;
57             }
58             else if (strcmp(argv[i], "-r") == 0) {
59                 shutdown_type = ShutdownType::REBOOT;
60             }
61             else if (strcmp(argv[i], "-h") == 0) {
62                 shutdown_type = ShutdownType::HALT;
63             }
64             else if (strcmp(argv[i], "-p") == 0) {
65                 shutdown_type = ShutdownType::POWEROFF;
66             }
67             else if (strcmp(argv[i], "--use-passed-cfd") == 0) {
68                 use_passed_cfd = true;
69             }
70             else {
71                 cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
72                 return 1;
73             }
74         }
75         else {
76             // time argument? TODO
77             show_help = true;
78         }
79     }
80
81     if (show_help) {
82         cout << "dinit-shutdown :   shutdown the system" << endl;
83         cout << "  --help           : show this help" << endl;
84         cout << "  -r               : reboot" << endl;
85         cout << "  -h               : halt system" << endl;
86         cout << "  -p               : power down (default)" << endl;
87         cout << "  --use-passed-cfd : use the socket file descriptor identified by the DINIT_CS_FD" << endl;
88         cout << "                     environment variable to communicate with the init daemon." << endl;
89         cout << "  --system         : perform shutdown immediately, instead of issuing shutdown" << endl;
90         cout << "                     command to the init program. Not recommended for use" << endl;
91         cout << "                     by users." << endl;
92         return 1;
93     }
94     
95     if (sys_shutdown) {
96         do_system_shutdown(shutdown_type);
97         return 0;
98     }
99
100     signal(SIGPIPE, SIG_IGN);
101     
102     int socknum = 0;
103     
104     if (use_passed_cfd) {
105         char * dinit_cs_fd_env = getenv("DINIT_CS_FD");
106         if (dinit_cs_fd_env != nullptr) {
107             char * endptr;
108             long int cfdnum = strtol(dinit_cs_fd_env, &endptr, 10);
109             if (endptr != dinit_cs_fd_env) {
110                 socknum = (int) cfdnum;
111             }
112             else {
113                 use_passed_cfd = false;
114             }
115         }
116         else {
117             use_passed_cfd = false;
118         }
119     }
120     
121     if (! use_passed_cfd) {
122         socknum = socket(AF_UNIX, SOCK_STREAM, 0);
123         if (socknum == -1) {
124             perror("socket");
125             return 1;
126         }
127         
128         const char *naddr = "/dev/dinitctl";
129         
130         struct sockaddr_un name;
131         name.sun_family = AF_UNIX;
132         strcpy(name.sun_path, naddr);
133         int sunlen = offsetof(struct sockaddr_un, sun_path) + strlen(naddr) + 1; // family, (string), nul
134         
135         int connr = connect(socknum, (struct sockaddr *) &name, sunlen);
136         if (connr == -1) {
137             perror("connect");
138             return 1;
139         }
140     }
141
142     // Build buffer;
143     //uint16_t sname_len = strlen(service_name);
144     int bufsize = 2;
145     char * buf = new char[bufsize];
146     
147     buf[0] = DINIT_CP_SHUTDOWN;
148     buf[1] = static_cast<char>(shutdown_type);
149     
150     cout << "Issuing shutdown command..." << endl;
151     
152     // TODO make sure to write the whole buffer
153     int r = write(socknum, buf, bufsize);
154     if (r == -1) {
155         perror("write");
156         return 1;
157     }
158     
159     // Wait for ACK/NACK
160     // r = read(socknum, buf, 1);
161     //if (r > 0) {
162     //    cout << "Received acknowledgement. System should now shut down." << endl;
163     //}
164     
165     CPBuffer<1024> rbuffer;
166     try {
167         wait_for_reply(rbuffer, socknum);
168         
169         if (rbuffer[0] != DINIT_RP_ACK) {
170             cerr << "shutdown: control socket protocol error" << endl;
171             return 1;
172         }
173     }
174     catch (ReadCPException &exc)
175     {
176         cerr << "shutdown: control socket read failure or protocol error" << endl;    
177         return 1;
178     }
179     
180     while (true) {
181         pause();
182     }
183     
184     return 0;
185 }
186
187 // Fill a circular buffer from a file descriptor, reading at least _rlength_ bytes.
188 // Throws ReadException if the requested number of bytes cannot be read, with:
189 //     errcode = 0   if end of stream (remote end closed)
190 //     errcode = errno   if another error occurred
191 // Note that EINTR is ignored (i.e. the read will be re-tried).
192 static void fillBufferTo(CPBuffer<1024> *buf, int fd, int rlength)
193 {
194     do {
195         int r = buf->fill_to(fd, rlength);
196         if (r == -1) {
197             if (errno != EINTR) {
198                 throw ReadCPException(errno);
199             }
200         }
201         else if (r == 0) {
202             throw ReadCPException(0);
203         }
204         else {
205             return;
206         }
207     }
208     while (true);
209 }
210
211 // Wait for a reply packet, skipping over any information packets
212 // that are received in the meantime.
213 static void wait_for_reply(CPBuffer<1024> &rbuffer, int fd)
214 {
215     fillBufferTo(&rbuffer, fd, 1);
216     
217     while (rbuffer[0] >= 100) {
218         // Information packet; discard.
219         fillBufferTo(&rbuffer, fd, 1);
220         int pktlen = (unsigned char) rbuffer[1];
221         
222         rbuffer.consume(1);  // Consume one byte so we'll read one byte of the next packet
223         fillBufferTo(&rbuffer, fd, pktlen);
224         rbuffer.consume(pktlen - 1);
225     }
226 }
227
228 // Actually shut down the system.
229 void do_system_shutdown(ShutdownType shutdown_type)
230 {
231     using namespace std;
232     
233     // Mask all signals to prevent death of our parent etc from terminating us
234     sigset_t allsigs;
235     sigfillset(&allsigs);
236     sigprocmask(SIG_SETMASK, &allsigs, nullptr);
237     
238     int reboot_type = 0;
239     if (shutdown_type == ShutdownType::REBOOT) reboot_type = RB_AUTOBOOT;
240     else if (shutdown_type == ShutdownType::POWEROFF) reboot_type = RB_POWER_OFF;
241     else reboot_type = RB_HALT_SYSTEM;
242     
243     // Write to console rather than any terminal, since we lose the terminal it seems:
244     close(STDOUT_FILENO);
245     int consfd = open("/dev/console", O_WRONLY);
246     if (consfd != STDOUT_FILENO) {
247         dup2(consfd, STDOUT_FILENO);
248     }
249     
250     cout << "Sending TERM/KILL to all processes..." << endl; // DAV
251     
252     // Send TERM/KILL to all (remaining) processes
253     kill(-1, SIGTERM);
254     sleep(1);
255     kill(-1, SIGKILL);
256     
257     // perform shutdown
258     cout << "Turning off swap..." << endl;
259     swap_off();
260     cout << "Unmounting disks..." << endl;
261     unmount_disks();
262     sync();
263     
264     cout << "Issuing shutdown via kernel..." << endl;
265     reboot(reboot_type);
266 }
267
268 static void unmount_disks()
269 {
270     pid_t chpid = fork();
271     if (chpid == 0) {
272         // umount -a -r
273         //  -a : all filesystems (except proc)
274         //  -r : mount readonly if can't unmount
275         execl("/bin/umount", "/bin/umount", "-a", "-r", nullptr);
276     }
277     else if (chpid > 0) {
278         int status;
279         waitpid(chpid, &status, 0);
280     }
281 }
282
283 static void swap_off()
284 {
285     pid_t chpid = fork();
286     if (chpid == 0) {
287         // swapoff -a
288         execl("/sbin/swapoff", "/sbin/swapoff", "-a", nullptr);
289     }
290     else if (chpid > 0) {
291         int status;
292         waitpid(chpid, &status, 0);
293     }
294 }