1 /*
2  * This library is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU Lesser General Public
4  * License as published by the Free Software Foundation;
5  * version 2.1 of the License.
6  *
7  * This library is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  * Lesser General Public License for more details.
11  *
12  * You should have received a copy of the GNU Lesser General Public
13  * License along with this library; If not, see <http://www.gnu.org/licenses/>.
14  */
15 
16 #include <unistd.h>
17 #include <fcntl.h>
18 
19 #include <xenevtchn.h>
20 
21 #include "xc_private.h"
22 #include "xenguest.h"
23 
24 #define SUSPEND_LOCK_FILE    XEN_RUN_DIR "/suspend-evtchn-%d.lock"
25 
26 /*
27  * locking
28  */
29 
30 #define ERR(x) do{                                                      \
31     ERROR("Can't " #x " lock file for suspend event channel %s: %s\n",  \
32           suspend_file, strerror(errno));                               \
33     goto err;                                                           \
34 }while(0)
35 
36 #define SUSPEND_FILE_BUFLEN (sizeof(SUSPEND_LOCK_FILE) + 10)
37 
get_suspend_file(char buf[],uint32_t domid)38 static void get_suspend_file(char buf[], uint32_t domid)
39 {
40     snprintf(buf, SUSPEND_FILE_BUFLEN, SUSPEND_LOCK_FILE, domid);
41 }
42 
lock_suspend_event(xc_interface * xch,uint32_t domid,int * lockfd)43 static int lock_suspend_event(xc_interface *xch, uint32_t domid, int *lockfd)
44 {
45     int fd = -1, r;
46     char suspend_file[SUSPEND_FILE_BUFLEN];
47     struct stat ours, theirs;
48     struct flock fl;
49 
50     get_suspend_file(suspend_file, domid);
51 
52     *lockfd = -1;
53 
54     for (;;) {
55         if (fd >= 0)
56             close (fd);
57 
58         fd = open(suspend_file, O_CREAT | O_RDWR, 0600);
59         if (fd < 0)
60             ERR("create");
61 
62         r = fcntl(fd, F_SETFD, FD_CLOEXEC);
63         if (r)
64             ERR("fcntl F_SETFD FD_CLOEXEC");
65 
66         memset(&fl, 0, sizeof(fl));
67         fl.l_type = F_WRLCK;
68         fl.l_whence = SEEK_SET;
69         fl.l_len = 1;
70         r = fcntl(fd, F_SETLK, &fl);
71         if (r)
72             ERR("fcntl F_SETLK");
73 
74         r = fstat(fd, &ours);
75         if (r)
76             ERR("fstat");
77 
78         r = stat(suspend_file, &theirs);
79         if (r) {
80             if (errno == ENOENT)
81                 /* try again */
82                 continue;
83             ERR("stat");
84         }
85 
86         if (ours.st_ino != theirs.st_ino)
87             /* someone else must have removed it while we were locking it */
88             continue;
89 
90         break;
91     }
92 
93     *lockfd = fd;
94     return 0;
95 
96  err:
97     if (fd >= 0)
98         close(fd);
99 
100     return -1;
101 }
102 
unlock_suspend_event(xc_interface * xch,uint32_t domid,int * lockfd)103 static int unlock_suspend_event(xc_interface *xch, uint32_t domid, int *lockfd)
104 {
105     int r;
106     char suspend_file[SUSPEND_FILE_BUFLEN];
107 
108     if (*lockfd < 0)
109         return 0;
110 
111     get_suspend_file(suspend_file, domid);
112 
113     r = unlink(suspend_file);
114     if (r)
115         ERR("unlink");
116 
117     r = close(*lockfd);
118     *lockfd = -1;
119     if (r)
120         ERR("close");
121 
122  err:
123     if (*lockfd >= 0)
124         close(*lockfd);
125 
126     return -1;
127 }
128 
xc_await_suspend(xc_interface * xch,xenevtchn_handle * xce,int suspend_evtchn)129 int xc_await_suspend(xc_interface *xch, xenevtchn_handle *xce, int suspend_evtchn)
130 {
131     int rc;
132 
133     do {
134         rc = xenevtchn_pending(xce);
135         if (rc < 0) {
136             ERROR("error polling suspend notification channel: %d", rc);
137             return -1;
138         }
139     } while (rc != suspend_evtchn);
140 
141     /* harmless for one-off suspend */
142     if (xenevtchn_unmask(xce, suspend_evtchn) < 0)
143         ERROR("failed to unmask suspend notification channel: %d", rc);
144 
145     return 0;
146 }
147 
148 /* Internal callers are allowed to call this with suspend_evtchn<0
149  * but *lockfd>0. */
xc_suspend_evtchn_release(xc_interface * xch,xenevtchn_handle * xce,uint32_t domid,int suspend_evtchn,int * lockfd)150 int xc_suspend_evtchn_release(xc_interface *xch, xenevtchn_handle *xce,
151                               uint32_t domid, int suspend_evtchn, int *lockfd)
152 {
153     if (suspend_evtchn >= 0)
154         xenevtchn_unbind(xce, suspend_evtchn);
155 
156     return unlock_suspend_event(xch, domid, lockfd);
157 }
158 
xc_suspend_evtchn_init_sane(xc_interface * xch,xenevtchn_handle * xce,uint32_t domid,int port,int * lockfd)159 int xc_suspend_evtchn_init_sane(xc_interface *xch, xenevtchn_handle *xce,
160                                 uint32_t domid, int port, int *lockfd)
161 {
162     int rc, suspend_evtchn = -1;
163 
164     if (lock_suspend_event(xch, domid, lockfd)) {
165         errno = EINVAL;
166         goto cleanup;
167     }
168 
169     suspend_evtchn = xenevtchn_bind_interdomain(xce, domid, port);
170     if (suspend_evtchn < 0) {
171         ERROR("failed to bind suspend event channel: %d", suspend_evtchn);
172         goto cleanup;
173     }
174 
175     rc = xc_domain_subscribe_for_suspend(xch, domid, port);
176     if (rc < 0) {
177         ERROR("failed to subscribe to domain: %d", rc);
178         goto cleanup;
179     }
180 
181     return suspend_evtchn;
182 
183 cleanup:
184     xc_suspend_evtchn_release(xch, xce, domid, suspend_evtchn, lockfd);
185 
186     return -1;
187 }
188 
xc_suspend_evtchn_init_exclusive(xc_interface * xch,xenevtchn_handle * xce,uint32_t domid,int port,int * lockfd)189 int xc_suspend_evtchn_init_exclusive(xc_interface *xch, xenevtchn_handle *xce,
190                                      uint32_t domid, int port, int *lockfd)
191 {
192     int suspend_evtchn;
193 
194     suspend_evtchn = xc_suspend_evtchn_init_sane(xch, xce, domid, port, lockfd);
195     if (suspend_evtchn < 0)
196         return suspend_evtchn;
197 
198     /* event channel is pending immediately after binding */
199     xc_await_suspend(xch, xce, suspend_evtchn);
200 
201     return suspend_evtchn;
202 }
203