7deb1dbe99eda06fd6033956e153fcf77e71eb5b
[libvirt.git] / src / xen / xen_inotify.c
1 /*
2  * xen_inofify.c: Xen notification of xml file activity in the
3  *                following dirs:
4  *                /etc/xen
5  *                /var/lib/xend/domains
6  *
7  * Copyright (C) 2008 VirtualIron
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
22  *
23  * Author: Ben Guthro
24  */
25 #include <config.h>
26 #include <dirent.h>
27 #include <sys/inotify.h>
28
29 #include "virterror_internal.h"
30 #include "datatypes.h"
31 #include "driver.h"
32 #include "memory.h"
33 #include "event.h"
34 #include "xen_driver.h"
35 #include "conf.h"
36 #include "domain_conf.h"
37 #include "xen_inotify.h"
38 #include "xend_internal.h"
39 #include "logging.h"
40 #include "uuid.h"
41
42 #include "xm_internal.h" /* for xenXMDomainConfigParse */
43
44 #define VIR_FROM_THIS VIR_FROM_XEN_INOTIFY
45
46 #define virXenInotifyError(conn, code, fmt...)                                 \
47         virReportErrorHelper(NULL, VIR_FROM_XEN_INOTIFY, code, __FILE__,      \
48                                __FUNCTION__, __LINE__, fmt)
49
50 #define LIBVIRTD_DOMAINS_DIR "/var/lib/xend/domains"
51
52 struct xenUnifiedDriver xenInotifyDriver = {
53     xenInotifyOpen, /* open */
54     xenInotifyClose, /* close */
55     NULL, /* version */
56     NULL, /* hostname */
57     NULL, /* nodeGetInfo */
58     NULL, /* getCapabilities */
59     NULL, /* listDomains */
60     NULL, /* numOfDomains */
61     NULL, /* domainCreateLinux */
62     NULL, /* domainSuspend */
63     NULL, /* domainResume */
64     NULL, /* domainShutdown */
65     NULL, /* domainReboot */
66     NULL, /* domainDestroy */
67     NULL, /* domainGetOSType */
68     NULL, /* domainGetMaxMemory */
69     NULL, /* domainSetMaxMemory */
70     NULL, /* domainSetMemory */
71     NULL, /* domainGetInfo */
72     NULL, /* domainSave */
73     NULL, /* domainRestore */
74     NULL, /* domainCoreDump */
75     NULL, /* domainSetVcpus */
76     NULL, /* domainPinVcpu */
77     NULL, /* domainGetVcpus */
78     NULL, /* domainGetMaxVcpus */
79     NULL, /* listDefinedDomains */
80     NULL, /* numOfDefinedDomains */
81     NULL, /* domainCreate */
82     NULL, /* domainDefineXML */
83     NULL, /* domainUndefine */
84     NULL, /* domainAttachDevice */
85     NULL, /* domainDetachDevice */
86     NULL, /* domainGetAutostart */
87     NULL, /* domainSetAutostart */
88     NULL, /* domainGetSchedulerType */
89     NULL, /* domainGetSchedulerParameters */
90     NULL, /* domainSetSchedulerParameters */
91 };
92
93 static int
94 xenInotifyXenCacheLookup(virConnectPtr conn,
95                          const char *filename,
96                          char **name, unsigned char *uuid) {
97     xenUnifiedPrivatePtr priv = conn->privateData;
98     xenXMConfCachePtr entry;
99
100     if (!(entry = virHashLookup(priv->configCache, filename))) {
101         DEBUG("No config found for %s", filename);
102         return -1;
103     }
104
105     *name = strdup(entry->def->name);
106     memcpy(uuid, entry->def->uuid, VIR_UUID_BUFLEN);
107
108     if (!*name) {
109         DEBUG0("Error getting dom from def");
110         return -1;
111     }
112     return 0;
113 }
114
115 static int
116 xenInotifyXendDomainsDirLookup(virConnectPtr conn, const char *filename,
117                                char **name, unsigned char *uuid) {
118     int i;
119     virDomainPtr dom;
120     const char *uuid_str;
121     unsigned char rawuuid[VIR_UUID_BUFLEN];
122     xenUnifiedPrivatePtr priv = conn->privateData;
123
124     /* xend is managing domains. we will get
125     * a filename in the manner:
126     * /var/lib/xend/domains/<uuid>/
127     */
128     uuid_str = filename + strlen(LIBVIRTD_DOMAINS_DIR) + 1;
129
130     if (virUUIDParse(uuid_str, rawuuid) < 0) {
131         virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
132                            _("parsing uuid %s"), uuid_str);
133         return -1;
134     }
135     /* call directly into xend here, as driver may not yet
136        be set during open while we are building our
137        initial list of domains */
138     DEBUG("Looking for dom with uuid: %s", uuid_str);
139     /* XXX Should not have to go via a virDomainPtr obj instance */
140     if(!(dom = xenDaemonLookupByUUID(conn, rawuuid))) {
141         /* If we are here, the domain has gone away.
142            search for, and create a domain from the stored
143            list info */
144         for (i = 0 ; i < priv->configInfoList->count ; i++) {
145             if (!memcmp(uuid, priv->configInfoList->doms[i]->uuid, VIR_UUID_BUFLEN)) {
146                 *name = strdup(priv->configInfoList->doms[i]->name);
147                 if (!*name) {
148                     virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
149                                        _("finding dom for %s"), uuid_str);
150                     return -1;
151                 }
152                 memcpy(uuid, priv->configInfoList->doms[i]->uuid, VIR_UUID_BUFLEN);
153                 DEBUG0("Found dom on list");
154                 return 0;
155             }
156         }
157         virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
158                            "%s", _("finding dom on config list"));
159         return -1;
160     }
161
162     if (!(*name = strdup(dom->name)))
163         return -1;
164     memcpy(uuid, dom->uuid, VIR_UUID_BUFLEN);
165     virDomainFree(dom);
166     /* succeeded too find domain by uuid */
167     return 0;
168 }
169
170 static int
171 xenInotifyDomainLookup(virConnectPtr conn,
172                        const char *filename,
173                        char **name, unsigned char *uuid) {
174     xenUnifiedPrivatePtr priv = conn->privateData;
175     if (priv->useXenConfigCache)
176         return xenInotifyXenCacheLookup(conn, filename, name, uuid);
177     else
178         return xenInotifyXendDomainsDirLookup(conn, filename, name, uuid);
179 }
180
181 static virDomainEventPtr
182 xenInotifyDomainEventFromFile(virConnectPtr conn,
183                               const char *filename,
184                               int type, int detail) {
185     virDomainEventPtr event;
186     char *name = NULL;
187     unsigned char uuid[VIR_UUID_BUFLEN];
188
189     if (xenInotifyDomainLookup(conn, filename, &name, uuid) < 0)
190         return NULL;
191
192     event = virDomainEventNew(-1, name, uuid, type, detail);
193     VIR_FREE(name);
194     return event;
195 }
196
197 static int
198 xenInotifyXendDomainsDirRemoveEntry(virConnectPtr conn,
199                                     const char *fname) {
200     xenUnifiedPrivatePtr priv = conn->privateData;
201     const char *uuidstr = fname + strlen(LIBVIRTD_DOMAINS_DIR) + 1;
202     unsigned char uuid[VIR_UUID_BUFLEN];
203     int i;
204
205     if (virUUIDParse(uuidstr, uuid) < 0) {
206         virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
207                            _("parsing uuid %s"), uuidstr);
208         return -1;
209     }
210
211     /* match and remove on uuid */
212     for (i = 0 ; i < priv->configInfoList->count ; i++) {
213         if (!memcmp(uuid, priv->configInfoList->doms[i]->uuid, VIR_UUID_BUFLEN)) {
214             VIR_FREE(priv->configInfoList->doms[i]->name);
215             VIR_FREE(priv->configInfoList->doms[i]);
216
217             if (i < (priv->configInfoList->count - 1))
218                 memmove(priv->configInfoList->doms + i,
219                         priv->configInfoList->doms + i + 1,
220                         sizeof(*(priv->configInfoList->doms)) *
221                                 (priv->configInfoList->count - (i + 1)));
222
223             if (VIR_REALLOC_N(priv->configInfoList->doms,
224                               priv->configInfoList->count - 1) < 0) {
225                 ; /* Failure to reduce memory allocation isn't fatal */
226             }
227             priv->configInfoList->count--;
228             return 0;
229         }
230     }
231     return -1;
232 }
233
234 static int
235 xenInotifyXendDomainsDirAddEntry(virConnectPtr conn,
236                                  const char *fname) {
237     char *name = NULL;
238     unsigned char uuid[VIR_UUID_BUFLEN];
239     xenUnifiedPrivatePtr priv = conn->privateData;
240
241     if (xenInotifyDomainLookup(conn, fname, &name, uuid) < 0) {
242         virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
243                            "%s", _("Error looking up domain"));
244         return -1;
245     }
246
247     if (xenUnifiedAddDomainInfo(priv->configInfoList,
248                                 -1, name, uuid) < 0) {
249         virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
250                         "%s", _("Error adding file to config cache"));
251         VIR_FREE(name);
252         return -1;
253     }
254     VIR_FREE(name);
255     return 0;
256 }
257
258 static int
259 xenInotifyRemoveDomainConfigInfo(virConnectPtr conn,
260                                  const char *fname) {
261     xenUnifiedPrivatePtr priv = conn->privateData;
262     return priv->useXenConfigCache ?
263         xenXMConfigCacheRemoveFile(conn, fname) :
264         xenInotifyXendDomainsDirRemoveEntry(conn, fname);
265 }
266
267 static int
268 xenInotifyAddDomainConfigInfo(virConnectPtr conn,
269                               const char *fname) {
270     xenUnifiedPrivatePtr priv = conn->privateData;
271     return priv->useXenConfigCache ?
272         xenXMConfigCacheAddFile(conn, fname) :
273         xenInotifyXendDomainsDirAddEntry(conn, fname);
274 }
275
276 static void
277 xenInotifyEvent(int watch ATTRIBUTE_UNUSED,
278                 int fd,
279                 int events ATTRIBUTE_UNUSED,
280                 void *data)
281 {
282     char buf[1024];
283     char fname[1024];
284     struct inotify_event *e;
285     int got;
286     char *tmp, *name;
287     virConnectPtr conn = data;
288     xenUnifiedPrivatePtr priv = NULL;
289
290     DEBUG0("got inotify event");
291
292     if( conn && conn->privateData ) {
293         priv = conn->privateData;
294     } else {
295         virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
296                            "%s", _("conn, or private data is NULL"));
297         return;
298     }
299
300     xenUnifiedLock(priv);
301
302 reread:
303     got = read(fd, buf, sizeof(buf));
304     if (got == -1) {
305         if (errno == EINTR)
306             goto reread;
307         goto cleanup;
308     }
309
310     tmp = buf;
311     while (got) {
312         if (got < sizeof(struct inotify_event))
313             goto cleanup; /* bad */
314
315         e = (struct inotify_event *)tmp;
316         tmp += sizeof(struct inotify_event);
317         got -= sizeof(struct inotify_event);
318
319         if (got < e->len)
320             goto cleanup;
321
322         tmp += e->len;
323         got -= e->len;
324
325         name = (char *)&(e->name);
326
327         snprintf(fname, 1024, "%s/%s",
328                  priv->configDir, name);
329
330         if (e->mask & (IN_DELETE | IN_MOVED_FROM)) {
331             virDomainEventPtr event =
332                 xenInotifyDomainEventFromFile(conn, fname,
333                                               VIR_DOMAIN_EVENT_UNDEFINED,
334                                               VIR_DOMAIN_EVENT_UNDEFINED_REMOVED);
335             if (!event)
336                 xenUnifiedDomainEventDispatch(conn->privateData, event);
337             else
338                 virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
339                                    "%s", _("looking up dom"));
340
341             if (xenInotifyRemoveDomainConfigInfo(conn, fname) < 0 ) {
342                 virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
343                                    "%s", _("Error adding file to config cache"));
344                 goto cleanup;
345             }
346         } else if (e->mask & ( IN_CREATE | IN_CLOSE_WRITE | IN_MOVED_TO) ) {
347             virDomainEventPtr event;
348             if (xenInotifyAddDomainConfigInfo(conn, fname) < 0 ) {
349                 virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
350                                    "%s", _("Error adding file to config cache"));
351                 goto cleanup;
352             }
353
354             event = xenInotifyDomainEventFromFile(conn, fname,
355                                                   VIR_DOMAIN_EVENT_DEFINED,
356                                                   VIR_DOMAIN_EVENT_DEFINED_ADDED);
357
358             if (event)
359                 xenUnifiedDomainEventDispatch(conn->privateData, event);
360             else
361                 virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
362                                    "%s", _("looking up dom"));
363
364         }
365
366     }
367
368 cleanup:
369     xenUnifiedUnlock(priv);
370 }
371
372 /**
373  * xenInotifyOpen:
374  * @conn: pointer to the connection block
375  * @name: URL for the target, NULL for local
376  * @flags: combination of virDrvOpenFlag(s)
377  *
378  * Connects and starts listening for inotify events
379  *
380  * Returns 0 or -1 in case of error.
381  */
382 virDrvOpenStatus
383 xenInotifyOpen(virConnectPtr conn ATTRIBUTE_UNUSED,
384              virConnectAuthPtr auth ATTRIBUTE_UNUSED,
385              int flags ATTRIBUTE_UNUSED)
386 {
387     DIR *dh;
388     struct dirent *ent;
389     char path[PATH_MAX];
390     xenUnifiedPrivatePtr priv = (xenUnifiedPrivatePtr) conn->privateData;
391
392     if (priv->configDir) {
393         priv->useXenConfigCache = 1;
394     } else {
395         /* /var/lib/xend/domains/<uuid>/config.sxp */
396         priv->configDir = LIBVIRTD_DOMAINS_DIR;
397         priv->useXenConfigCache = 0;
398
399         if (VIR_ALLOC(priv->configInfoList) < 0) {
400             virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
401                                "%s", _("failed to allocate configInfoList"));
402             return -1;
403         }
404
405         /* populate initial list */
406         if (!(dh = opendir(priv->configDir))) {
407             virReportSystemError(NULL, errno,
408                                  _("cannot open directory: %s"),
409                                  priv->configDir);
410             return -1;
411         }
412         while ((ent = readdir(dh))) {
413             if (STRPREFIX(ent->d_name, "."))
414                 continue;
415
416             /* Build the full file path */
417             if ((strlen(priv->configDir) + 1 +
418                  strlen(ent->d_name) + 1) > PATH_MAX)
419                 continue;
420             strcpy(path, priv->configDir);
421             strcat(path, "/");
422             strcat(path, ent->d_name);
423
424             if (xenInotifyAddDomainConfigInfo(conn, path) < 0 ) {
425                 virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
426                                    "%s", _("Error adding file to config list"));
427                 closedir(dh);
428                 return -1;
429             }
430         }
431         closedir(dh);
432     }
433
434     if ((priv->inotifyFD = inotify_init()) < 0) {
435         virReportSystemError(NULL, errno,
436                              "%s", _("initializing inotify"));
437         return -1;
438     }
439
440     DEBUG("Adding a watch on %s", priv->configDir);
441     if (inotify_add_watch(priv->inotifyFD,
442                           priv->configDir,
443                           IN_CREATE |
444                           IN_CLOSE_WRITE | IN_DELETE |
445                           IN_MOVED_TO | IN_MOVED_FROM) < 0) {
446         virReportSystemError(NULL, errno,
447                              _("adding watch on %s"),
448                              priv->configDir);
449         return -1;
450     }
451
452     DEBUG0("Building initial config cache");
453     if (priv->useXenConfigCache &&
454         xenXMConfigCacheRefresh (conn) < 0) {
455         DEBUG("Failed to enable XM config cache %s", conn->err.message);
456         return -1;
457     }
458
459     DEBUG0("Registering with event loop");
460     /* Add the handle for monitoring */
461     if ((priv->inotifyWatch = virEventAddHandle(priv->inotifyFD, VIR_EVENT_HANDLE_READABLE,
462                                                 xenInotifyEvent, conn, NULL)) < 0) {
463         DEBUG0("Failed to add inotify handle, disabling events");
464     }
465
466     virConnectRef(conn);
467     return 0;
468 }
469
470 /**
471  * xenInotifyClose:
472  * @conn: pointer to the connection block
473  *
474  * Close and stop listening for inotify events
475  *
476  * Returns 0 in case of success or -1 in case of error.
477  */
478 int
479 xenInotifyClose(virConnectPtr conn)
480 {
481     xenUnifiedPrivatePtr priv = conn->privateData;
482
483     if (priv->configInfoList)
484         xenUnifiedDomainInfoListFree(priv->configInfoList);
485
486     if (priv->inotifyWatch != -1)
487         virEventRemoveHandle(priv->inotifyWatch);
488     close(priv->inotifyFD);
489     virUnrefConnect(conn);
490
491     return 0;
492 }