Re-vamp shutdown
[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     
45     auto shutdown_type = ShutdownType::POWEROFF;
46         
47     for (int i = 1; i < argc; i++) {
48         if (argv[i][0] == '-') {
49             if (strcmp(argv[i], "--help") == 0) {
50                 show_help = true;
51                 break;
52             }
53             
54             if (strcmp(argv[i], "--system") == 0) {
55                 sys_shutdown = true;
56             }
57             else if (strcmp(argv[i], "-r") == 0) {
58                 shutdown_type = ShutdownType::REBOOT;
59             }
60             else if (strcmp(argv[i], "-h") == 0) {
61                 shutdown_type = ShutdownType::HALT;
62             }
63             else if (strcmp(argv[i], "-p") == 0) {
64                 shutdown_type = ShutdownType::POWEROFF;
65             }
66             else {
67                 cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
68                 return 1;
69             }
70         }
71         else {
72             // time argument? TODO
73             show_help = true;
74         }
75     }
76
77     if (show_help) {
78         cout << "dinit-shutdown :   shutdown the system" << endl;
79         cout << "  --help           : show this help" << endl;
80         cout << "  -r               : reboot" << endl;
81         cout << "  -h               : halt system" << endl;
82         cout << "  -p               : power down (default)" << endl;
83         cout << "  --system         : perform shutdown immediately, instead of issuing shutdown" << endl;
84         cout << "                     command to the init program. Not recommended for use" << endl;
85         cout << "                     by users." << endl;
86         return 1;
87     }
88     
89     if (sys_shutdown) {
90         do_system_shutdown(shutdown_type);
91         return 0;
92     }
93     
94     int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
95     if (socknum == -1) {
96         perror("socket");
97         return 1;
98     }
99
100     signal(SIGPIPE, SIG_IGN);
101     
102     const char *naddr = "/dev/dinitctl";
103     
104     struct sockaddr_un name;
105     name.sun_family = AF_UNIX;
106     strcpy(name.sun_path, naddr);
107     int sunlen = offsetof(struct sockaddr_un, sun_path) + strlen(naddr) + 1; // family, (string), nul
108     
109     int connr = connect(socknum, (struct sockaddr *) &name, sunlen);
110     if (connr == -1) {
111         perror("connect");
112         return 1;
113     }
114     
115     // Build buffer;
116     //uint16_t sname_len = strlen(service_name);
117     int bufsize = 2;
118     char * buf = new char[bufsize];
119     
120     buf[0] = DINIT_CP_SHUTDOWN;
121     buf[1] = static_cast<char>(shutdown_type);
122     
123     cout << "Issuing shutdown command..." << endl;
124     
125     // TODO make sure to write the whole buffer
126     int r = write(socknum, buf, bufsize);
127     if (r == -1) {
128         perror("write");
129         return 1;
130     }
131     
132     // Wait for ACK/NACK
133     // r = read(socknum, buf, 1);
134     //if (r > 0) {
135     //    cout << "Received acknowledgement. System should now shut down." << endl;
136     //}
137     
138     CPBuffer<1024> rbuffer;
139     try {
140         wait_for_reply(rbuffer, socknum);
141         
142         if (rbuffer[0] != DINIT_RP_ACK) {
143             cerr << "shutdown: control socket protocol error" << endl;
144             return 1;
145         }
146     }
147     catch (ReadCPException &exc)
148     {
149         cerr << "shutdown: control socket read failure or protocol error" << endl;    
150         return 1;
151     }
152     
153     while (true) {
154         pause();
155     }
156     
157     return 0;
158 }
159
160 // Fill a circular buffer from a file descriptor, reading at least _rlength_ bytes.
161 // Throws ReadException if the requested number of bytes cannot be read, with:
162 //     errcode = 0   if end of stream (remote end closed)
163 //     errcode = errno   if another error occurred
164 // Note that EINTR is ignored (i.e. the read will be re-tried).
165 static void fillBufferTo(CPBuffer<1024> *buf, int fd, int rlength)
166 {
167     do {
168         int r = buf->fill_to(fd, rlength);
169         if (r == -1) {
170             if (errno != EINTR) {
171                 throw ReadCPException(errno);
172             }
173         }
174         else if (r == 0) {
175             throw ReadCPException(0);
176         }
177         else {
178             return;
179         }
180     }
181     while (true);
182 }
183
184 // Wait for a reply packet, skipping over any information packets
185 // that are received in the meantime.
186 static void wait_for_reply(CPBuffer<1024> &rbuffer, int fd)
187 {
188     fillBufferTo(&rbuffer, fd, 1);
189     
190     while (rbuffer[0] >= 100) {
191         // Information packet; discard.
192         fillBufferTo(&rbuffer, fd, 1);
193         int pktlen = (unsigned char) rbuffer[1];
194         
195         rbuffer.consume(1);  // Consume one byte so we'll read one byte of the next packet
196         fillBufferTo(&rbuffer, fd, pktlen);
197         rbuffer.consume(pktlen - 1);
198     }
199 }
200
201 // Actually shut down the system.
202 void do_system_shutdown(ShutdownType shutdown_type)
203 {
204     using namespace std;
205     
206     int reboot_type = 0;
207     if (shutdown_type == ShutdownType::REBOOT) reboot_type = RB_AUTOBOOT;
208     else if (shutdown_type == ShutdownType::POWEROFF) reboot_type = RB_POWER_OFF;
209     else reboot_type = RB_HALT_SYSTEM;
210     
211     // Write to console rather than any terminal, since we lose the terminal it seems:
212     close(STDOUT_FILENO);
213     int consfd = open("/dev/console", O_WRONLY);
214     if (consfd != STDOUT_FILENO) {
215         dup2(consfd, STDOUT_FILENO);
216     }
217     
218     cout << "Sending TERM/KILL to all processes..." << endl; // DAV
219     
220     // Send TERM/KILL to all (remaining) processes
221     kill(-1, SIGTERM);
222     sleep(1);
223     kill(-1, SIGKILL);
224     
225     // perform shutdown
226     cout << "Turning off swap..." << endl;
227     swap_off();
228     cout << "Unmounting disks..." << endl;
229     unmount_disks();
230     sync();
231     
232     cout << "Issuing shutdown via kernel..." << endl;
233     reboot(reboot_type);
234 }
235
236 static void unmount_disks()
237 {
238     pid_t chpid = fork();
239     if (chpid == 0) {
240         // umount -a -r
241         //  -a : all filesystems (except proc)
242         //  -r : mount readonly if can't unmount
243         execl("/bin/umount", "/bin/umount", "-a", "-r", nullptr);
244     }
245     else if (chpid > 0) {
246         int status;
247         waitpid(chpid, &status, 0);
248     }
249 }
250
251 static void swap_off()
252 {
253     pid_t chpid = fork();
254     if (chpid == 0) {
255         // swapoff -a
256         execl("/sbin/swapoff", "/sbin/swapoff", "-a", nullptr);
257     }
258     else if (chpid > 0) {
259         int status;
260         waitpid(chpid, &status, 0);
261     }
262 }