Factor out some Dinit client functions tp a new header (dinit-client.h).
[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 #include "dinit-client.h"
21
22 // shutdown:  shut down the system
23 // This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
24
25 void do_system_shutdown(shutdown_type_t shutdown_type);
26 static void unmount_disks();
27 static void swap_off();
28
29
30 int main(int argc, char **argv)
31 {
32     using namespace std;
33     
34     bool show_help = false;
35     bool sys_shutdown = false;
36     bool use_passed_cfd = false;
37     
38     auto shutdown_type = shutdown_type_t::POWEROFF;
39         
40     for (int i = 1; i < argc; i++) {
41         if (argv[i][0] == '-') {
42             if (strcmp(argv[i], "--help") == 0) {
43                 show_help = true;
44                 break;
45             }
46             
47             if (strcmp(argv[i], "--system") == 0) {
48                 sys_shutdown = true;
49             }
50             else if (strcmp(argv[i], "-r") == 0) {
51                 shutdown_type = shutdown_type_t::REBOOT;
52             }
53             else if (strcmp(argv[i], "-h") == 0) {
54                 shutdown_type = shutdown_type_t::HALT;
55             }
56             else if (strcmp(argv[i], "-p") == 0) {
57                 shutdown_type = shutdown_type_t::POWEROFF;
58             }
59             else if (strcmp(argv[i], "--use-passed-cfd") == 0) {
60                 use_passed_cfd = true;
61             }
62             else {
63                 cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
64                 return 1;
65             }
66         }
67         else {
68             // time argument? TODO
69             show_help = true;
70         }
71     }
72
73     if (show_help) {
74         cout << "dinit-shutdown :   shutdown the system" << endl;
75         cout << "  --help           : show this help" << endl;
76         cout << "  -r               : reboot" << endl;
77         cout << "  -h               : halt system" << endl;
78         cout << "  -p               : power down (default)" << endl;
79         cout << "  --use-passed-cfd : use the socket file descriptor identified by the DINIT_CS_FD" << endl;
80         cout << "                     environment variable to communicate with the init daemon." << endl;
81         cout << "  --system         : perform shutdown immediately, instead of issuing shutdown" << endl;
82         cout << "                     command to the init program. Not recommended for use" << endl;
83         cout << "                     by users." << endl;
84         return 1;
85     }
86     
87     if (sys_shutdown) {
88         do_system_shutdown(shutdown_type);
89         return 0;
90     }
91
92     signal(SIGPIPE, SIG_IGN);
93     
94     int socknum = 0;
95     
96     if (use_passed_cfd) {
97         char * dinit_cs_fd_env = getenv("DINIT_CS_FD");
98         if (dinit_cs_fd_env != nullptr) {
99             char * endptr;
100             long int cfdnum = strtol(dinit_cs_fd_env, &endptr, 10);
101             if (endptr != dinit_cs_fd_env) {
102                 socknum = (int) cfdnum;
103             }
104             else {
105                 use_passed_cfd = false;
106             }
107         }
108         else {
109             use_passed_cfd = false;
110         }
111     }
112     
113     if (! use_passed_cfd) {
114         socknum = socket(AF_UNIX, SOCK_STREAM, 0);
115         if (socknum == -1) {
116             perror("socket");
117             return 1;
118         }
119         
120         const char *naddr = "/dev/dinitctl";
121         
122         struct sockaddr_un name;
123         name.sun_family = AF_UNIX;
124         strcpy(name.sun_path, naddr);
125         int sunlen = offsetof(struct sockaddr_un, sun_path) + strlen(naddr) + 1; // family, (string), nul
126         
127         int connr = connect(socknum, (struct sockaddr *) &name, sunlen);
128         if (connr == -1) {
129             perror("connect");
130             return 1;
131         }
132     }
133
134     // Build buffer;
135     constexpr int bufsize = 2;
136     char buf[bufsize];
137     
138     buf[0] = DINIT_CP_SHUTDOWN;
139     buf[1] = static_cast<char>(shutdown_type);
140     
141     cout << "Issuing shutdown command..." << endl;
142     
143     int r = write_all(socknum, buf, bufsize);
144     if (r == -1) {
145         perror("write");
146         return 1;
147     }
148     
149     // Wait for ACK/NACK
150     // r = read(socknum, buf, 1);
151     //if (r > 0) {
152     //    cout << "Received acknowledgement. System should now shut down." << endl;
153     //}
154     
155     cpbuffer<1024> rbuffer;
156     try {
157         wait_for_reply(rbuffer, socknum);
158         
159         if (rbuffer[0] != DINIT_RP_ACK) {
160             cerr << "shutdown: control socket protocol error" << endl;
161             return 1;
162         }
163     }
164     catch (read_cp_exception &exc)
165     {
166         cerr << "shutdown: control socket read failure or protocol error" << endl;    
167         return 1;
168     }
169     
170     while (true) {
171         pause();
172     }
173     
174     return 0;
175 }
176
177 // Actually shut down the system.
178 void do_system_shutdown(shutdown_type_t shutdown_type)
179 {
180     using namespace std;
181     
182     // Mask all signals to prevent death of our parent etc from terminating us
183     sigset_t allsigs;
184     sigfillset(&allsigs);
185     sigprocmask(SIG_SETMASK, &allsigs, nullptr);
186     
187     int reboot_type = 0;
188     if (shutdown_type == shutdown_type_t::REBOOT) reboot_type = RB_AUTOBOOT;
189     else if (shutdown_type == shutdown_type_t::POWEROFF) reboot_type = RB_POWER_OFF;
190     else reboot_type = RB_HALT_SYSTEM;
191     
192     // Write to console rather than any terminal, since we lose the terminal it seems:
193     close(STDOUT_FILENO);
194     int consfd = open("/dev/console", O_WRONLY);
195     if (consfd != STDOUT_FILENO) {
196         dup2(consfd, STDOUT_FILENO);
197     }
198     
199     cout << "Sending TERM/KILL to all processes..." << endl;
200     
201     // Send TERM/KILL to all (remaining) processes
202     kill(-1, SIGTERM);
203     sleep(1);
204     kill(-1, SIGKILL);
205     
206     // perform shutdown
207     cout << "Turning off swap..." << endl;
208     swap_off();
209     cout << "Unmounting disks..." << endl;
210     unmount_disks();
211     sync();
212     
213     cout << "Issuing shutdown via kernel..." << endl;
214     reboot(reboot_type);
215 }
216
217 static void unmount_disks()
218 {
219     pid_t chpid = fork();
220     if (chpid == 0) {
221         // umount -a -r
222         //  -a : all filesystems (except proc)
223         //  -r : mount readonly if can't unmount
224         execl("/bin/umount", "/bin/umount", "-a", "-r", nullptr);
225     }
226     else if (chpid > 0) {
227         int status;
228         waitpid(chpid, &status, 0);
229     }
230 }
231
232 static void swap_off()
233 {
234     pid_t chpid = fork();
235     if (chpid == 0) {
236         // swapoff -a
237         execl("/sbin/swapoff", "/sbin/swapoff", "-a", nullptr);
238     }
239     else if (chpid > 0) {
240         int status;
241         waitpid(chpid, &status, 0);
242     }
243 }