ifcfg-rh: don't chain up after failure of replace_and_commit()
[NetworkManager.git] / src / settings / plugins / ifcfg-rh / nm-ifcfg-connection.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager system settings service
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * Copyright (C) 2008 - 2011 Red Hat, Inc.
19  */
20
21 #include "nm-default.h"
22
23 #include <string.h>
24
25 #include <glib/gstdio.h>
26
27 #include "nm-dbus-interface.h"
28 #include "nm-setting-connection.h"
29 #include "nm-setting-wired.h"
30 #include "nm-setting-wireless.h"
31 #include "nm-setting-gsm.h"
32 #include "nm-setting-cdma.h"
33 #include "nm-setting-pppoe.h"
34 #include "nm-setting-wireless-security.h"
35 #include "nm-setting-8021x.h"
36 #include "nm-platform.h"
37
38 #include "common.h"
39 #include "nm-config.h"
40 #include "nm-ifcfg-connection.h"
41 #include "reader.h"
42 #include "writer.h"
43 #include "nm-inotify-helper.h"
44 #include "utils.h"
45
46 G_DEFINE_TYPE (NMIfcfgConnection, nm_ifcfg_connection, NM_TYPE_SETTINGS_CONNECTION)
47
48 #define NM_IFCFG_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionPrivate))
49
50 typedef struct {
51         gulong ih_event_id;
52
53         int file_wd;
54
55         char *keyfile;
56         int keyfile_wd;
57
58         char *routefile;
59         int routefile_wd;
60
61         char *route6file;
62         int route6file_wd;
63
64         char *unmanaged_spec;
65         char *unrecognized_spec;
66
67         gulong devtimeout_link_changed_handler;
68         guint devtimeout_timeout_id;
69
70         NMInotifyHelper *inotify_helper;
71 } NMIfcfgConnectionPrivate;
72
73 enum {
74         PROP_0,
75         PROP_UNMANAGED_SPEC,
76         PROP_UNRECOGNIZED_SPEC,
77         LAST_PROP
78 };
79
80 /* Signals */
81 enum {
82         IFCFG_CHANGED,
83         LAST_SIGNAL
84 };
85
86 static guint signals[LAST_SIGNAL] = { 0 };
87
88 static NMInotifyHelper *
89 _get_inotify_helper (NMIfcfgConnectionPrivate *priv)
90 {
91         if (!priv->inotify_helper)
92                 priv->inotify_helper = g_object_ref (nm_inotify_helper_get ());
93         return priv->inotify_helper;
94 }
95
96 static gboolean
97 devtimeout_ready (gpointer user_data)
98 {
99         NMIfcfgConnection *self = user_data;
100         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
101
102         priv->devtimeout_timeout_id = 0;
103         nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), TRUE);
104         return FALSE;
105 }
106
107 static void
108 link_changed (NMPlatform *platform, NMPObjectType *obj_type, int ifindex, const NMPlatformLink *link,
109               NMPlatformSignalChangeType change_type,
110               NMConnection *self)
111 {
112         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
113         const char *ifname;
114
115         ifname = nm_connection_get_interface_name (self);
116         if (g_strcmp0 (link->name, ifname) != 0)
117                 return;
118
119         /* Shouldn't happen, but... */
120         if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
121                 return;
122
123         nm_log_info (LOGD_SETTINGS, "Device %s appeared; connection '%s' now ready",
124                      ifname, nm_connection_get_id (self));
125
126         g_signal_handler_disconnect (platform, priv->devtimeout_link_changed_handler);
127         priv->devtimeout_link_changed_handler = 0;
128         g_source_remove (priv->devtimeout_timeout_id);
129
130         /* Don't declare the connection ready right away, since NMManager may not have
131          * started processing the device yet.
132          */
133         priv->devtimeout_timeout_id = g_idle_add (devtimeout_ready, self);
134 }
135
136 static gboolean
137 devtimeout_expired (gpointer user_data)
138 {
139         NMIfcfgConnection *self = user_data;
140         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
141
142         nm_log_info (LOGD_SETTINGS, "Device for connection '%s' did not appear before timeout",
143                      nm_connection_get_id (NM_CONNECTION (self)));
144
145         g_signal_handler_disconnect (NM_PLATFORM_GET, priv->devtimeout_link_changed_handler);
146         priv->devtimeout_link_changed_handler = 0;
147         priv->devtimeout_timeout_id = 0;
148
149         nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), TRUE);
150         return FALSE;
151 }
152
153 static void
154 nm_ifcfg_connection_check_devtimeout (NMIfcfgConnection *self)
155 {
156         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
157         NMSettingConnection *s_con;
158         const char *ifname;
159         const char *filename;
160         guint devtimeout;
161         const NMPlatformLink *pllink;
162
163         s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
164
165         if (!nm_setting_connection_get_autoconnect (s_con))
166                 return;
167         ifname = nm_setting_connection_get_interface_name (s_con);
168         if (!ifname)
169                 return;
170         filename = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (self));
171         if (!filename)
172                 return;
173
174         pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname);
175         if (pllink && pllink->initialized)
176                 return;
177
178         devtimeout = devtimeout_from_file (filename);
179         if (!devtimeout)
180                 return;
181
182         /* ONBOOT=yes, DEVICE and DEVTIMEOUT are set, but device is not present */
183         nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), FALSE);
184
185         nm_log_info (LOGD_SETTINGS, "Waiting %u seconds for %s to appear for connection '%s'",
186                      devtimeout, ifname, nm_connection_get_id (NM_CONNECTION (self)));
187
188         priv->devtimeout_link_changed_handler =
189                 g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED,
190                                   G_CALLBACK (link_changed), self);
191         priv->devtimeout_timeout_id = g_timeout_add_seconds (devtimeout, devtimeout_expired, self);
192 }
193
194 static void
195 files_changed_cb (NMInotifyHelper *ih,
196                   struct inotify_event *evt,
197                   const char *path,
198                   gpointer user_data)
199 {
200         NMIfcfgConnection *self = NM_IFCFG_CONNECTION (user_data);
201         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
202
203         if (   (evt->wd != priv->file_wd)
204             && (evt->wd != priv->keyfile_wd)
205             && (evt->wd != priv->routefile_wd)
206             && (evt->wd != priv->route6file_wd))
207                 return;
208
209         /* push the event up to the plugin */
210         g_signal_emit (self, signals[IFCFG_CHANGED], 0);
211 }
212
213 NMIfcfgConnection *
214 nm_ifcfg_connection_new (NMConnection *source,
215                          const char *full_path,
216                          GError **error,
217                          gboolean *out_ignore_error)
218 {
219         GObject *object;
220         NMConnection *tmp;
221         char *unhandled_spec = NULL;
222         const char *unmanaged_spec = NULL, *unrecognized_spec = NULL;
223         gboolean update_unsaved = TRUE;
224
225         g_assert (source || full_path);
226
227         if (out_ignore_error)
228                 *out_ignore_error = FALSE;
229
230         /* If we're given a connection already, prefer that instead of re-reading */
231         if (source)
232                 tmp = g_object_ref (source);
233         else {
234                 tmp = connection_from_file (full_path,
235                                             &unhandled_spec,
236                                             error,
237                                             out_ignore_error);
238                 if (!tmp)
239                         return NULL;
240
241                 /* If we just read the connection from disk, it's clearly not Unsaved */
242                 update_unsaved = FALSE;
243         }
244
245         if (unhandled_spec && g_str_has_prefix (unhandled_spec, "unmanaged:"))
246                 unmanaged_spec = unhandled_spec + strlen ("unmanaged:");
247         else if (unhandled_spec && g_str_has_prefix (unhandled_spec, "unrecognized:"))
248                 unrecognized_spec = unhandled_spec + strlen ("unrecognized:");
249
250         object = (GObject *) g_object_new (NM_TYPE_IFCFG_CONNECTION,
251                                            NM_SETTINGS_CONNECTION_FILENAME, full_path,
252                                            NM_IFCFG_CONNECTION_UNMANAGED_SPEC, unmanaged_spec,
253                                            NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, unrecognized_spec,
254                                            NULL);
255         /* Update our settings with what was read from the file */
256         if (nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object),
257                                                      tmp,
258                                                      update_unsaved,
259                                                      NULL,
260                                                      error))
261                 nm_ifcfg_connection_check_devtimeout (NM_IFCFG_CONNECTION (object));
262         else
263                 g_clear_object (&object);
264
265         g_object_unref (tmp);
266         g_free (unhandled_spec);
267         return (NMIfcfgConnection *) object;
268 }
269
270 static void
271 path_watch_stop (NMIfcfgConnection *self)
272 {
273         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
274         NMInotifyHelper *ih;
275
276         ih = _get_inotify_helper (priv);
277
278         nm_clear_g_signal_handler (ih, &priv->ih_event_id);
279
280         if (priv->file_wd >= 0) {
281                 nm_inotify_helper_remove_watch (ih, priv->file_wd);
282                 priv->file_wd = -1;
283         }
284
285         g_free (priv->keyfile);
286         priv->keyfile = NULL;
287         if (priv->keyfile_wd >= 0) {
288                 nm_inotify_helper_remove_watch (ih, priv->keyfile_wd);
289                 priv->keyfile_wd = -1;
290         }
291
292         g_free (priv->routefile);
293         priv->routefile = NULL;
294         if (priv->routefile_wd >= 0) {
295                 nm_inotify_helper_remove_watch (ih, priv->routefile_wd);
296                 priv->routefile_wd = -1;
297         }
298
299         g_free (priv->route6file);
300         priv->route6file = NULL;
301         if (priv->route6file_wd >= 0) {
302                 nm_inotify_helper_remove_watch (ih, priv->route6file_wd);
303                 priv->route6file_wd = -1;
304         }
305 }
306
307 static void
308 filename_changed (GObject *object,
309                   GParamSpec *pspec,
310                   gpointer user_data)
311 {
312         NMIfcfgConnection *self = NM_IFCFG_CONNECTION (object);
313         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
314         const char *ifcfg_path;
315
316         path_watch_stop (self);
317
318         ifcfg_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (self));
319         if (!ifcfg_path)
320                 return;
321
322         priv->keyfile = utils_get_keys_path (ifcfg_path);
323         priv->routefile = utils_get_route_path (ifcfg_path);
324         priv->route6file = utils_get_route6_path (ifcfg_path);
325
326         if (nm_config_get_monitor_connection_files (nm_config_get ())) {
327                 NMInotifyHelper *ih;
328
329                 ih = _get_inotify_helper (priv);
330
331                 priv->ih_event_id = g_signal_connect (ih, "event", G_CALLBACK (files_changed_cb), self);
332                 priv->file_wd = nm_inotify_helper_add_watch (ih, ifcfg_path);
333                 priv->keyfile_wd = nm_inotify_helper_add_watch (ih, priv->keyfile);
334                 priv->routefile_wd = nm_inotify_helper_add_watch (ih, priv->routefile);
335                 priv->route6file_wd = nm_inotify_helper_add_watch (ih, priv->route6file);
336         }
337 }
338
339 const char *
340 nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self)
341 {
342         g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL);
343
344         return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unmanaged_spec;
345 }
346
347 const char *
348 nm_ifcfg_connection_get_unrecognized_spec (NMIfcfgConnection *self)
349 {
350         g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL);
351
352         return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unrecognized_spec;
353 }
354
355 static void
356 replace_and_commit (NMSettingsConnection *connection,
357                     NMConnection *new_connection,
358                     NMSettingsConnectionCommitFunc callback,
359                     gpointer user_data)
360 {
361         const char *filename;
362         GError *error = NULL;
363
364         filename = nm_settings_connection_get_filename (connection);
365         if (filename && utils_has_complex_routes (filename)) {
366                 if (callback) {
367                         error = g_error_new_literal (NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
368                                                      "Cannot modify a connection that has an associated 'rule-' or 'rule6-' file");
369                         callback (connection, error, user_data);
370                         g_clear_error (&error);
371                 }
372                 return;
373         }
374
375         NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->replace_and_commit (connection, new_connection, callback, user_data);
376 }
377
378 static void
379 commit_changes (NMSettingsConnection *connection,
380                 NMSettingsConnectionCommitReason commit_reason,
381                 NMSettingsConnectionCommitFunc callback,
382                 gpointer user_data)
383 {
384         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (connection);
385         GError *error = NULL;
386         NMConnection *reread;
387         gboolean same = FALSE, success = FALSE;
388         char *ifcfg_path = NULL;
389         const char *filename;
390
391         /* To ensure we don't rewrite files that are only changed from other
392          * processes on-disk, read the existing connection back in and only rewrite
393          * it if it's really changed.
394          */
395         filename = nm_settings_connection_get_filename (connection);
396         if (filename) {
397                 reread = connection_from_file (filename, NULL, NULL, NULL);
398                 if (reread) {
399                         same = nm_connection_compare (NM_CONNECTION (connection),
400                                                       reread,
401                                                       NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
402                                                          NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS);
403                         g_object_unref (reread);
404
405                         /* Don't bother writing anything out if in-memory and on-disk data are the same */
406                         if (same) {
407                                 /* But chain up to parent to handle success - emits updated signal */
408                                 NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->commit_changes (connection, commit_reason, callback, user_data);
409                                 return;
410                         }
411                 }
412
413                 success = writer_update_connection (NM_CONNECTION (connection),
414                                                     IFCFG_DIR,
415                                                     filename,
416                                                     priv->keyfile,
417                                                     &error);
418         } else {
419                 success = writer_new_connection (NM_CONNECTION (connection),
420                                                  IFCFG_DIR,
421                                                  &ifcfg_path,
422                                                  &error);
423                 if (success) {
424                         nm_settings_connection_set_filename (connection, ifcfg_path);
425                         g_free (ifcfg_path);
426                 }
427         }
428
429         if (success) {
430                 /* Chain up to parent to handle success */
431                 NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->commit_changes (connection, commit_reason, callback, user_data);
432         } else {
433                 /* Otherwise immediate error */
434                 callback (connection, error, user_data);
435                 g_error_free (error);
436         }
437 }
438
439 static void
440 do_delete (NMSettingsConnection *connection,
441                NMSettingsConnectionDeleteFunc callback,
442                gpointer user_data)
443 {
444         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (connection);
445         const char *filename;
446
447         filename = nm_settings_connection_get_filename (connection);
448         if (filename) {
449                 g_unlink (filename);
450                 if (priv->keyfile)
451                         g_unlink (priv->keyfile);
452                 if (priv->routefile)
453                         g_unlink (priv->routefile);
454                 if (priv->route6file)
455                         g_unlink (priv->route6file);
456         }
457
458         NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->delete (connection, callback, user_data);
459 }
460
461 /* GObject */
462
463 static void
464 nm_ifcfg_connection_init (NMIfcfgConnection *connection)
465 {
466         g_signal_connect (connection, "notify::" NM_SETTINGS_CONNECTION_FILENAME,
467                           G_CALLBACK (filename_changed), NULL);
468 }
469
470 static void
471 set_property (GObject *object, guint prop_id,
472                     const GValue *value, GParamSpec *pspec)
473 {
474         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object);
475
476         switch (prop_id) {
477         case PROP_UNMANAGED_SPEC:
478                 priv->unmanaged_spec = g_value_dup_string (value);
479                 break;
480         case PROP_UNRECOGNIZED_SPEC:
481                 priv->unrecognized_spec = g_value_dup_string (value);
482                 break;
483         default:
484                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
485                 break;
486         }
487 }
488
489 static void
490 get_property (GObject *object, guint prop_id,
491                     GValue *value, GParamSpec *pspec)
492 {
493         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object);
494
495         switch (prop_id) {
496         case PROP_UNMANAGED_SPEC:
497                 g_value_set_string (value, priv->unmanaged_spec);
498                 break;
499         case PROP_UNRECOGNIZED_SPEC:
500                 g_value_set_string (value, priv->unrecognized_spec);
501                 break;
502         default:
503                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
504                 break;
505         }
506 }
507
508 static void
509 dispose (GObject *object)
510 {
511         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object);
512
513         path_watch_stop (NM_IFCFG_CONNECTION (object));
514
515         nm_clear_g_signal_handler (NM_PLATFORM_GET, &priv->devtimeout_link_changed_handler);
516         nm_clear_g_source (&priv->devtimeout_timeout_id);
517
518         g_clear_object (&priv->inotify_helper);
519
520         g_clear_pointer (&priv->unmanaged_spec, g_free);
521         g_clear_pointer (&priv->unrecognized_spec, g_free);
522
523         G_OBJECT_CLASS (nm_ifcfg_connection_parent_class)->dispose (object);
524 }
525
526 static void
527 nm_ifcfg_connection_class_init (NMIfcfgConnectionClass *ifcfg_connection_class)
528 {
529         GObjectClass *object_class = G_OBJECT_CLASS (ifcfg_connection_class);
530         NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (ifcfg_connection_class);
531
532         g_type_class_add_private (ifcfg_connection_class, sizeof (NMIfcfgConnectionPrivate));
533
534         /* Virtual methods */
535         object_class->set_property = set_property;
536         object_class->get_property = get_property;
537         object_class->dispose      = dispose;
538         settings_class->delete = do_delete;
539         settings_class->replace_and_commit = replace_and_commit;
540         settings_class->commit_changes = commit_changes;
541
542         /* Properties */
543         g_object_class_install_property
544                 (object_class, PROP_UNMANAGED_SPEC,
545                  g_param_spec_string (NM_IFCFG_CONNECTION_UNMANAGED_SPEC, "", "",
546                                       NULL,
547                                       G_PARAM_READWRITE |
548                                       G_PARAM_STATIC_STRINGS));
549         g_object_class_install_property
550                 (object_class, PROP_UNRECOGNIZED_SPEC,
551                  g_param_spec_string (NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, "", "",
552                                       NULL,
553                                       G_PARAM_READWRITE |
554                                       G_PARAM_STATIC_STRINGS));
555
556         signals[IFCFG_CHANGED] =
557                 g_signal_new ("ifcfg-changed",
558                               G_OBJECT_CLASS_TYPE (object_class),
559                               G_SIGNAL_RUN_LAST,
560                               0, NULL, NULL,
561                               g_cclosure_marshal_VOID__VOID,
562                               G_TYPE_NONE, 0);
563 }
564