1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.os;
18
19import static android.system.OsConstants.POLLIN;
20
21import android.net.LocalServerSocket;
22import android.net.LocalSocket;
23import android.system.Os;
24import android.system.ErrnoException;
25import android.system.StructPollfd;
26import android.util.Log;
27
28import android.util.Slog;
29import java.io.IOException;
30import java.io.FileDescriptor;
31import java.util.ArrayList;
32
33/**
34 * Server socket class for zygote processes.
35 *
36 * Provides functions to wait for commands on a UNIX domain socket, and fork
37 * off child processes that inherit the initial state of the VM.%
38 *
39 * Please see {@link ZygoteConnection.Arguments} for documentation on the
40 * client protocol.
41 */
42class ZygoteServer {
43    public static final String TAG = "ZygoteServer";
44
45    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
46
47    /**
48     * Listening socket that accepts new server connections.
49     */
50    private LocalServerSocket mServerSocket;
51
52    /**
53     * Whether or not mServerSocket's underlying FD should be closed directly.
54     * If mServerSocket is created with an existing FD, closing the socket does
55     * not close the FD and it must be closed explicitly. If the socket is created
56     * with a name instead, then closing the socket will close the underlying FD
57     * and it should not be double-closed.
58     */
59    private boolean mCloseSocketFd;
60
61    /**
62     * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
63     */
64    private boolean mIsForkChild;
65
66    ZygoteServer() {
67    }
68
69    void setForkChild() {
70        mIsForkChild = true;
71    }
72
73    /**
74     * Registers a server socket for zygote command connections. This locates the server socket
75     * file descriptor through an ANDROID_SOCKET_ environment variable.
76     *
77     * @throws RuntimeException when open fails
78     */
79    void registerServerSocketFromEnv(String socketName) {
80        if (mServerSocket == null) {
81            int fileDesc;
82            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
83            try {
84                String env = System.getenv(fullSocketName);
85                fileDesc = Integer.parseInt(env);
86            } catch (RuntimeException ex) {
87                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
88            }
89
90            try {
91                FileDescriptor fd = new FileDescriptor();
92                fd.setInt$(fileDesc);
93                mServerSocket = new LocalServerSocket(fd);
94                mCloseSocketFd = true;
95            } catch (IOException ex) {
96                throw new RuntimeException(
97                        "Error binding to local socket '" + fileDesc + "'", ex);
98            }
99        }
100    }
101
102    /**
103     * Registers a server socket for zygote command connections. This opens the server socket
104     * at the specified name in the abstract socket namespace.
105     */
106    void registerServerSocketAtAbstractName(String socketName) {
107        if (mServerSocket == null) {
108            try {
109                mServerSocket = new LocalServerSocket(socketName);
110                mCloseSocketFd = false;
111            } catch (IOException ex) {
112                throw new RuntimeException(
113                        "Error binding to abstract socket '" + socketName + "'", ex);
114            }
115        }
116    }
117
118    /**
119     * Waits for and accepts a single command connection. Throws
120     * RuntimeException on failure.
121     */
122    private ZygoteConnection acceptCommandPeer(String abiList) {
123        try {
124            return createNewConnection(mServerSocket.accept(), abiList);
125        } catch (IOException ex) {
126            throw new RuntimeException(
127                    "IOException during accept()", ex);
128        }
129    }
130
131    protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
132            throws IOException {
133        return new ZygoteConnection(socket, abiList);
134    }
135
136    /**
137     * Close and clean up zygote sockets. Called on shutdown and on the
138     * child's exit path.
139     */
140    void closeServerSocket() {
141        try {
142            if (mServerSocket != null) {
143                FileDescriptor fd = mServerSocket.getFileDescriptor();
144                mServerSocket.close();
145                if (fd != null && mCloseSocketFd) {
146                    Os.close(fd);
147                }
148            }
149        } catch (IOException ex) {
150            Log.e(TAG, "Zygote:  error closing sockets", ex);
151        } catch (ErrnoException ex) {
152            Log.e(TAG, "Zygote:  error closing descriptor", ex);
153        }
154
155        mServerSocket = null;
156    }
157
158    /**
159     * Return the server socket's underlying file descriptor, so that
160     * ZygoteConnection can pass it to the native code for proper
161     * closure after a child process is forked off.
162     */
163
164    FileDescriptor getServerSocketFileDescriptor() {
165        return mServerSocket.getFileDescriptor();
166    }
167
168    /**
169     * Runs the zygote process's select loop. Accepts new connections as
170     * they happen, and reads commands from connections one spawn-request's
171     * worth at a time.
172     */
173    Runnable runSelectLoop(String abiList) {
174        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
175        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
176
177        fds.add(mServerSocket.getFileDescriptor());
178        peers.add(null);
179
180        while (true) {
181            StructPollfd[] pollFds = new StructPollfd[fds.size()];
182            for (int i = 0; i < pollFds.length; ++i) {
183                pollFds[i] = new StructPollfd();
184                pollFds[i].fd = fds.get(i);
185                pollFds[i].events = (short) POLLIN;
186            }
187            try {
188                Os.poll(pollFds, -1);
189            } catch (ErrnoException ex) {
190                throw new RuntimeException("poll failed", ex);
191            }
192            for (int i = pollFds.length - 1; i >= 0; --i) {
193                if ((pollFds[i].revents & POLLIN) == 0) {
194                    continue;
195                }
196
197                if (i == 0) {
198                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
199                    peers.add(newPeer);
200                    fds.add(newPeer.getFileDesciptor());
201                } else {
202                    try {
203                        ZygoteConnection connection = peers.get(i);
204                        final Runnable command = connection.processOneCommand(this);
205
206                        if (mIsForkChild) {
207                            // We're in the child. We should always have a command to run at this
208                            // stage if processOneCommand hasn't called "exec".
209                            if (command == null) {
210                                throw new IllegalStateException("command == null");
211                            }
212
213                            return command;
214                        } else {
215                            // We're in the server - we should never have any commands to run.
216                            if (command != null) {
217                                throw new IllegalStateException("command != null");
218                            }
219
220                            // We don't know whether the remote side of the socket was closed or
221                            // not until we attempt to read from it from processOneCommand. This shows up as
222                            // a regular POLLIN event in our regular processing loop.
223                            if (connection.isClosedByPeer()) {
224                                connection.closeSocket();
225                                peers.remove(i);
226                                fds.remove(i);
227                            }
228                        }
229                    } catch (Exception e) {
230                        if (!mIsForkChild) {
231                            // We're in the server so any exception here is one that has taken place
232                            // pre-fork while processing commands or reading / writing from the
233                            // control socket. Make a loud noise about any such exceptions so that
234                            // we know exactly what failed and why.
235
236                            Slog.e(TAG, "Exception executing zygote command: ", e);
237
238                            // Make sure the socket is closed so that the other end knows immediately
239                            // that something has gone wrong and doesn't time out waiting for a
240                            // response.
241                            ZygoteConnection conn = peers.remove(i);
242                            conn.closeSocket();
243
244                            fds.remove(i);
245                        } else {
246                            // We're in the child so any exception caught here has happened post
247                            // fork and before we execute ActivityThread.main (or any other main()
248                            // method). Log the details of the exception and bring down the process.
249                            Log.e(TAG, "Caught post-fork exception in child process.", e);
250                            throw e;
251                        }
252                    } finally {
253                        // Reset the child flag, in the event that the child process is a child-
254                        // zygote. The flag will not be consulted this loop pass after the Runnable
255                        // is returned.
256                        mIsForkChild = false;
257                    }
258                }
259            }
260        }
261    }
262}
263