blob: 76a029ce40ac73e16d12e9e951562eee0b0b1f40 [file] [log] [blame]
Paul Stewart75897df2011-04-27 09:05:53 -07001// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Chris Masone8fe2c7e2011-06-09 15:51:19 -07005#include "shill/manager.h"
6
Paul Stewart75897df2011-04-27 09:05:53 -07007#include <time.h>
Paul Stewart75897df2011-04-27 09:05:53 -07008#include <stdio.h>
Chris Masoneee929b72011-05-10 10:02:18 -07009
Paul Stewart22aa71b2011-09-16 12:15:11 -070010#include <algorithm>
mukesh agrawal7a4e4002011-09-06 11:26:05 -070011#include <map>
Paul Stewart75897df2011-04-27 09:05:53 -070012#include <string>
Chris Masone52cd19b2011-06-29 17:23:04 -070013#include <vector>
Paul Stewart75897df2011-04-27 09:05:53 -070014
Paul Stewarte6132022011-08-16 09:11:02 -070015#include <base/file_util.h>
Chris Masoneee929b72011-05-10 10:02:18 -070016#include <base/logging.h>
Chris Masone9be4a9d2011-05-16 15:44:09 -070017#include <base/memory/ref_counted.h>
Paul Stewart22aa71b2011-09-16 12:15:11 -070018#include <base/string_split.h>
19#include <base/string_util.h>
Chris Masone3bd3c8c2011-06-13 08:20:26 -070020#include <chromeos/dbus/service_constants.h>
Chris Masoneee929b72011-05-10 10:02:18 -070021
Chris Masoned0ceb8c2011-06-02 10:05:39 -070022#include "shill/adaptor_interfaces.h"
Paul Stewart75897df2011-04-27 09:05:53 -070023#include "shill/control_interface.h"
Chris Masoned0ceb8c2011-06-02 10:05:39 -070024#include "shill/dbus_adaptor.h"
Chris Masone7aa5f902011-07-11 11:13:35 -070025#include "shill/default_profile.h"
Chris Masone9be4a9d2011-05-16 15:44:09 -070026#include "shill/device.h"
27#include "shill/device_info.h"
Chris Masone6791a432011-07-12 13:23:19 -070028#include "shill/ephemeral_profile.h"
Chris Masone8fe2c7e2011-06-09 15:51:19 -070029#include "shill/error.h"
Chris Masone9d779932011-08-25 16:33:41 -070030#include "shill/key_file_store.h"
Paul Stewart22aa71b2011-09-16 12:15:11 -070031#include "shill/service_sorter.h"
Chris Masone7aa5f902011-07-11 11:13:35 -070032#include "shill/profile.h"
Chris Masoneb925cc82011-06-22 15:39:57 -070033#include "shill/property_accessor.h"
Paul Stewarte6132022011-08-16 09:11:02 -070034#include "shill/resolver.h"
Chris Masone9be4a9d2011-05-16 15:44:09 -070035#include "shill/shill_event.h"
36#include "shill/service.h"
mukesh agrawal7a4e4002011-09-06 11:26:05 -070037#include "shill/wifi.h"
38#include "shill/wifi_service.h"
Paul Stewart75897df2011-04-27 09:05:53 -070039
Paul Stewart22aa71b2011-09-16 12:15:11 -070040using std::map;
Paul Stewart75897df2011-04-27 09:05:53 -070041using std::string;
Chris Masone9be4a9d2011-05-16 15:44:09 -070042using std::vector;
Paul Stewart75897df2011-04-27 09:05:53 -070043
44namespace shill {
Paul Stewarte6132022011-08-16 09:11:02 -070045
mukesh agrawal7a4e4002011-09-06 11:26:05 -070046// static
47const char Manager::kManagerErrorNoDevice[] = "no wifi devices available";
48
Paul Stewart75897df2011-04-27 09:05:53 -070049Manager::Manager(ControlInterface *control_interface,
Darin Petkov887f2982011-07-14 16:10:17 -070050 EventDispatcher *dispatcher,
Chris Masone2ae797d2011-08-23 20:41:00 -070051 GLib *glib,
52 const string &run_directory,
53 const string &storage_directory,
54 const string &user_storage_format)
55 : run_path_(FilePath(run_directory)),
56 storage_path_(FilePath(storage_directory)),
57 user_storage_format_(user_storage_format),
Paul Stewarte6132022011-08-16 09:11:02 -070058 adaptor_(control_interface->CreateManagerAdaptor(this)),
Paul Stewartb50f0b92011-05-16 16:31:42 -070059 device_info_(control_interface, dispatcher, this),
Darin Petkov887f2982011-07-14 16:10:17 -070060 modem_info_(control_interface, dispatcher, this, glib),
Chris Masone6791a432011-07-12 13:23:19 -070061 running_(false),
Chris Masone9d779932011-08-25 16:33:41 -070062 ephemeral_profile_(new EphemeralProfile(control_interface, this)),
Chris Masone2ae797d2011-08-23 20:41:00 -070063 control_interface_(control_interface),
64 glib_(glib) {
Chris Masone7aa5f902011-07-11 11:13:35 -070065 HelpRegisterDerivedString(flimflam::kActiveProfileProperty,
66 &Manager::GetActiveProfileName,
67 NULL);
Chris Masone27c4aa52011-07-02 13:10:14 -070068 HelpRegisterDerivedStrings(flimflam::kAvailableTechnologiesProperty,
69 &Manager::AvailableTechnologies,
70 NULL);
Chris Masone88cbd5f2011-07-03 14:30:04 -070071 store_.RegisterString(flimflam::kCheckPortalListProperty,
72 &props_.check_portal_list);
Chris Masone27c4aa52011-07-02 13:10:14 -070073 HelpRegisterDerivedStrings(flimflam::kConnectedTechnologiesProperty,
74 &Manager::ConnectedTechnologies,
75 NULL);
Chris Masone88cbd5f2011-07-03 14:30:04 -070076 store_.RegisterString(flimflam::kCountryProperty, &props_.country);
Chris Masone27c4aa52011-07-02 13:10:14 -070077 HelpRegisterDerivedString(flimflam::kDefaultTechnologyProperty,
78 &Manager::DefaultTechnology,
79 NULL);
Chris Masone27c4aa52011-07-02 13:10:14 -070080 HelpRegisterDerivedStrings(flimflam::kDevicesProperty,
81 &Manager::EnumerateDevices,
82 NULL);
Chris Masone88cbd5f2011-07-03 14:30:04 -070083 HelpRegisterDerivedStrings(flimflam::kEnabledTechnologiesProperty,
84 &Manager::EnabledTechnologies,
85 NULL);
86 store_.RegisterBool(flimflam::kOfflineModeProperty, &props_.offline_mode);
87 store_.RegisterString(flimflam::kPortalURLProperty, &props_.portal_url);
88 HelpRegisterDerivedString(flimflam::kStateProperty,
89 &Manager::CalculateState,
90 NULL);
Chris Masone27c4aa52011-07-02 13:10:14 -070091 HelpRegisterDerivedStrings(flimflam::kServicesProperty,
92 &Manager::EnumerateAvailableServices,
93 NULL);
94 HelpRegisterDerivedStrings(flimflam::kServiceWatchListProperty,
95 &Manager::EnumerateWatchedServices,
96 NULL);
Chris Masone3c3f6a12011-07-01 10:01:41 -070097
Chris Masone4d42df82011-07-02 17:09:39 -070098 // TODO(cmasone): Wire these up once we actually put in profile support.
Chris Masoneb925cc82011-06-22 15:39:57 -070099 // known_properties_.push_back(flimflam::kProfilesProperty);
Chris Masoneb07006b2011-05-14 16:10:04 -0700100 VLOG(2) << "Manager initialized.";
Paul Stewart75897df2011-04-27 09:05:53 -0700101}
102
Chris Masone6791a432011-07-12 13:23:19 -0700103Manager::~Manager() {
Chris Masone9d779932011-08-25 16:33:41 -0700104 profiles_.clear();
Chris Masone6791a432011-07-12 13:23:19 -0700105}
Paul Stewart75897df2011-04-27 09:05:53 -0700106
mukesh agrawal8f317b62011-07-15 11:53:23 -0700107void Manager::AddDeviceToBlackList(const string &device_name) {
108 device_info_.AddDeviceToBlackList(device_name);
109}
110
Paul Stewart75897df2011-04-27 09:05:53 -0700111void Manager::Start() {
Paul Stewart0af98bf2011-05-10 17:38:08 -0700112 LOG(INFO) << "Manager started.";
Paul Stewarte6132022011-08-16 09:11:02 -0700113
Chris Masone2ae797d2011-08-23 20:41:00 -0700114 CHECK(file_util::CreateDirectory(run_path_)) << run_path_.value();
Paul Stewarte6132022011-08-16 09:11:02 -0700115 Resolver::GetInstance()->set_path(run_path_.Append("resolv.conf"));
Chris Masone2ae797d2011-08-23 20:41:00 -0700116
117 CHECK(file_util::CreateDirectory(storage_path_)) << storage_path_.value();
118
Chris Masoneb9c00592011-10-06 13:10:39 -0700119 profiles_.push_back(new DefaultProfile(control_interface_,
120 this,
121 storage_path_,
122 props_));
123 CHECK(profiles_[0]->InitStorage(glib_));
124
Paul Stewart75897df2011-04-27 09:05:53 -0700125 running_ = true;
Chris Masone413a3192011-05-09 17:10:05 -0700126 adaptor_->UpdateRunning();
Paul Stewart0af98bf2011-05-10 17:38:08 -0700127 device_info_.Start();
Darin Petkov887f2982011-07-14 16:10:17 -0700128 modem_info_.Start();
Paul Stewart75897df2011-04-27 09:05:53 -0700129}
130
131void Manager::Stop() {
132 running_ = false;
Chris Masone9d779932011-08-25 16:33:41 -0700133 // Persist profile, device, service information to disk.
134 vector<ProfileRefPtr>::iterator it;
135 for (it = profiles_.begin(); it != profiles_.end(); ++it) {
Chris Masoneb9c00592011-10-06 13:10:39 -0700136 (*it)->Finalize();
Chris Masone9d779932011-08-25 16:33:41 -0700137 }
Chris Masoneb9c00592011-10-06 13:10:39 -0700138 ephemeral_profile_->Finalize();
Chris Masone9d779932011-08-25 16:33:41 -0700139
Chris Masone413a3192011-05-09 17:10:05 -0700140 adaptor_->UpdateRunning();
Darin Petkov887f2982011-07-14 16:10:17 -0700141 modem_info_.Stop();
142 device_info_.Stop();
Paul Stewart75897df2011-04-27 09:05:53 -0700143}
144
Chris Masoneb9c00592011-10-06 13:10:39 -0700145void Manager::AdoptProfile(const ProfileRefPtr &profile) {
146 profiles_.push_back(profile);
147}
148
Chris Masone7aa5f902011-07-11 11:13:35 -0700149const ProfileRefPtr &Manager::ActiveProfile() {
Chris Masoneb9c00592011-10-06 13:10:39 -0700150 DCHECK_NE(profiles_.size(), 0);
Chris Masone7aa5f902011-07-11 11:13:35 -0700151 return profiles_.back();
152}
153
Chris Masone6791a432011-07-12 13:23:19 -0700154bool Manager::MoveToActiveProfile(const ProfileRefPtr &from,
155 const ServiceRefPtr &to_move) {
156 return ActiveProfile()->AdoptService(to_move) &&
157 from->AbandonService(to_move->UniqueName());
158}
159
Chris Masone2b105542011-06-22 10:58:09 -0700160void Manager::RegisterDevice(const DeviceRefPtr &to_manage) {
Chris Masone157aa0c2011-10-03 09:24:31 -0700161 // TODO(pstew): Should DefaultProfile have a list of devices, analogous to
162 // the list of services that it manages? If so, we should do a similar merge
163 // thing here.
164
Chris Masonec1e50412011-06-07 13:04:53 -0700165 vector<DeviceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700166 for (it = devices_.begin(); it != devices_.end(); ++it) {
Chris Masonec1e50412011-06-07 13:04:53 -0700167 if (to_manage.get() == it->get())
Chris Masone9be4a9d2011-05-16 15:44:09 -0700168 return;
169 }
Chris Masonec1e50412011-06-07 13:04:53 -0700170 devices_.push_back(to_manage);
Paul Stewartf1ce5d22011-05-19 13:10:20 -0700171
172 // TODO(pstew): Should check configuration
173 if (running_)
174 to_manage->Start();
Chris Masone9be4a9d2011-05-16 15:44:09 -0700175}
176
mukesh agrawal5029c6c2011-08-25 11:12:40 -0700177void Manager::DeregisterDevice(const DeviceRefPtr &to_forget) {
Chris Masonec1e50412011-06-07 13:04:53 -0700178 vector<DeviceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700179 for (it = devices_.begin(); it != devices_.end(); ++it) {
Chris Masonec1e50412011-06-07 13:04:53 -0700180 if (to_forget.get() == it->get()) {
mukesh agrawal5029c6c2011-08-25 11:12:40 -0700181 VLOG(2) << "Deregistered device: " << to_forget->UniqueName();
182 to_forget->Stop();
Chris Masone9be4a9d2011-05-16 15:44:09 -0700183 devices_.erase(it);
184 return;
185 }
186 }
mukesh agrawal5029c6c2011-08-25 11:12:40 -0700187 VLOG(2) << __func__ << " unknown device: " << to_forget->UniqueName();
Chris Masone9be4a9d2011-05-16 15:44:09 -0700188}
189
Chris Masone2b105542011-06-22 10:58:09 -0700190void Manager::RegisterService(const ServiceRefPtr &to_manage) {
mukesh agrawald835b202011-10-07 15:26:47 -0700191 VLOG(2) << __func__ << to_manage->UniqueName();
192
Chris Masone157aa0c2011-10-03 09:24:31 -0700193 for (vector<ProfileRefPtr>::iterator it = profiles_.begin();
194 it != profiles_.end();
195 ++it) {
196 if ((*it)->MergeService(to_manage)) // this will merge, if possible.
197 break;
198 }
Chris Masone6791a432011-07-12 13:23:19 -0700199
200 // If not found, add it to the ephemeral profile
201 ephemeral_profile_->AdoptService(to_manage);
202
203 // Now add to OUR list.
Chris Masonec1e50412011-06-07 13:04:53 -0700204 vector<ServiceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700205 for (it = services_.begin(); it != services_.end(); ++it) {
mukesh agrawald835b202011-10-07 15:26:47 -0700206 CHECK(to_manage->UniqueName() != (*it)->UniqueName());
Chris Masone9be4a9d2011-05-16 15:44:09 -0700207 }
Chris Masonec1e50412011-06-07 13:04:53 -0700208 services_.push_back(to_manage);
Paul Stewart22aa71b2011-09-16 12:15:11 -0700209 SortServices();
mukesh agrawal32399322011-09-01 10:53:43 -0700210
211 vector<string> service_paths;
212 for (it = services_.begin(); it != services_.end(); ++it) {
213 service_paths.push_back((*it)->GetRpcIdentifier());
214 }
215 adaptor_->EmitRpcIdentifierArrayChanged(flimflam::kServicesProperty,
216 service_paths);
Chris Masone9be4a9d2011-05-16 15:44:09 -0700217}
218
Chris Masone2b105542011-06-22 10:58:09 -0700219void Manager::DeregisterService(const ServiceConstRefPtr &to_forget) {
Chris Masone6791a432011-07-12 13:23:19 -0700220 // If the service is in the ephemeral profile, destroy it.
221 if (!ephemeral_profile_->AbandonService(to_forget->UniqueName())) {
222 // if it's in one of the real profiles...um...I guess mark it unconnectable?
223 }
Chris Masonec1e50412011-06-07 13:04:53 -0700224 vector<ServiceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700225 for (it = services_.begin(); it != services_.end(); ++it) {
Chris Masone6791a432011-07-12 13:23:19 -0700226 if (to_forget->UniqueName() == (*it)->UniqueName()) {
Chris Masone9be4a9d2011-05-16 15:44:09 -0700227 services_.erase(it);
Paul Stewart22aa71b2011-09-16 12:15:11 -0700228 SortServices();
Chris Masone9be4a9d2011-05-16 15:44:09 -0700229 return;
230 }
231 }
232}
233
Paul Stewart03dba0b2011-08-22 16:32:45 -0700234void Manager::UpdateService(const ServiceConstRefPtr &to_update) {
235 LOG(INFO) << "Service " << to_update->UniqueName() << " updated;"
236 << " state: " << to_update->state() << " failure: "
237 << to_update->failure();
Paul Stewart22aa71b2011-09-16 12:15:11 -0700238 SortServices();
Paul Stewart03dba0b2011-08-22 16:32:45 -0700239}
240
Paul Stewartfdd16072011-09-16 12:41:35 -0700241void Manager::FilterByTechnology(Technology::Identifier tech,
Chris Masonec1e50412011-06-07 13:04:53 -0700242 vector<DeviceRefPtr> *found) {
Chris Masone9be4a9d2011-05-16 15:44:09 -0700243 CHECK(found);
Chris Masonec1e50412011-06-07 13:04:53 -0700244 vector<DeviceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700245 for (it = devices_.begin(); it != devices_.end(); ++it) {
246 if ((*it)->TechnologyIs(tech))
247 found->push_back(*it);
248 }
249}
250
Paul Stewart22aa71b2011-09-16 12:15:11 -0700251ServiceRefPtr Manager::FindService(const string& name) {
Chris Masonec1e50412011-06-07 13:04:53 -0700252 vector<ServiceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700253 for (it = services_.begin(); it != services_.end(); ++it) {
Chris Masone6791a432011-07-12 13:23:19 -0700254 if (name == (*it)->UniqueName())
Chris Masonee0dea762011-06-09 09:06:03 -0700255 return *it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700256 }
Chris Masonee0dea762011-06-09 09:06:03 -0700257 return NULL;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700258}
259
mukesh agrawalffa3d042011-10-06 15:26:10 -0700260void Manager::HelpRegisterDerivedString(
261 const string &name,
262 string(Manager::*get)(void),
263 void(Manager::*set)(const string&, Error *)) {
Chris Masone27c4aa52011-07-02 13:10:14 -0700264 store_.RegisterDerivedString(
265 name,
266 StringAccessor(new CustomAccessor<Manager, string>(this, get, set)));
Chris Masoneb925cc82011-06-22 15:39:57 -0700267}
268
mukesh agrawalffa3d042011-10-06 15:26:10 -0700269void Manager::HelpRegisterDerivedStrings(
270 const string &name,
271 Strings(Manager::*get)(void),
272 void(Manager::*set)(const Strings &, Error *)) {
Chris Masone27c4aa52011-07-02 13:10:14 -0700273 store_.RegisterDerivedStrings(
274 name,
275 StringsAccessor(new CustomAccessor<Manager, Strings>(this, get, set)));
Chris Masoneb925cc82011-06-22 15:39:57 -0700276}
277
Paul Stewart22aa71b2011-09-16 12:15:11 -0700278void Manager::SortServices() {
279 sort(services_.begin(), services_.end(), ServiceSorter(technology_order_));
280}
281
Chris Masoneb925cc82011-06-22 15:39:57 -0700282string Manager::CalculateState() {
283 return flimflam::kStateOffline;
284}
285
286vector<string> Manager::AvailableTechnologies() {
287 return vector<string>();
288}
289
290vector<string> Manager::ConnectedTechnologies() {
291 return vector<string>();
292}
293
294string Manager::DefaultTechnology() {
295 return "";
296}
297
298vector<string> Manager::EnabledTechnologies() {
299 return vector<string>();
300}
301
Chris Masone3c3f6a12011-07-01 10:01:41 -0700302vector<string> Manager::EnumerateDevices() {
303 vector<string> device_rpc_ids;
304 for (vector<DeviceRefPtr>::const_iterator it = devices_.begin();
305 it != devices_.end();
306 ++it) {
307 device_rpc_ids.push_back((*it)->GetRpcIdentifier());
308 }
309 return device_rpc_ids;
310}
311
312vector<string> Manager::EnumerateAvailableServices() {
Chris Masone3c3f6a12011-07-01 10:01:41 -0700313 vector<string> service_rpc_ids;
314 for (vector<ServiceRefPtr>::const_iterator it = services_.begin();
315 it != services_.end();
316 ++it) {
317 service_rpc_ids.push_back((*it)->GetRpcIdentifier());
318 }
319 return service_rpc_ids;
320}
321
322vector<string> Manager::EnumerateWatchedServices() {
Chris Masone6791a432011-07-12 13:23:19 -0700323 // TODO(cmasone): Filter this list for services in appropriate states.
Chris Masone3c3f6a12011-07-01 10:01:41 -0700324 return EnumerateAvailableServices();
325}
326
Chris Masone7aa5f902011-07-11 11:13:35 -0700327string Manager::GetActiveProfileName() {
Chris Masone7df0c672011-07-15 10:24:54 -0700328 return ActiveProfile()->GetFriendlyName();
Chris Masone7aa5f902011-07-11 11:13:35 -0700329}
330
mukesh agrawal32399322011-09-01 10:53:43 -0700331// called via RPC (e.g., from ManagerDBusAdaptor)
mukesh agrawal7a4e4002011-09-06 11:26:05 -0700332WiFiServiceRefPtr Manager::GetWifiService(const KeyValueStore &args,
333 Error *error) {
334 std::vector<DeviceRefPtr> wifi_devices;
Paul Stewart22aa71b2011-09-16 12:15:11 -0700335 FilterByTechnology(Technology::kWifi, &wifi_devices);
mukesh agrawal7a4e4002011-09-06 11:26:05 -0700336 if (wifi_devices.empty()) {
337 error->Populate(Error::kInvalidArguments, kManagerErrorNoDevice);
338 return NULL;
339 } else {
340 WiFi *wifi = dynamic_cast<WiFi *>(wifi_devices.front().get());
341 CHECK(wifi);
342 return wifi->GetService(args, error);
343 }
344}
345
346// called via RPC (e.g., from ManagerDBusAdaptor)
Paul Stewart22aa71b2011-09-16 12:15:11 -0700347void Manager::RequestScan(const string &technology, Error *error) {
mukesh agrawal32399322011-09-01 10:53:43 -0700348 if (technology == flimflam::kTypeWifi || technology == "") {
349 vector<DeviceRefPtr> wifi_devices;
Paul Stewartfdd16072011-09-16 12:41:35 -0700350 FilterByTechnology(Technology::kWifi, &wifi_devices);
mukesh agrawal32399322011-09-01 10:53:43 -0700351
352 for (vector<DeviceRefPtr>::iterator it = wifi_devices.begin();
353 it != wifi_devices.end();
354 ++it) {
Darin Petkovc0865312011-09-16 15:31:20 -0700355 (*it)->Scan(error);
mukesh agrawal32399322011-09-01 10:53:43 -0700356 }
357 } else {
358 // TODO(quiche): support scanning for other technologies?
359 const string kMessage = "Unrecognized technology " + technology;
360 LOG(ERROR) << kMessage;
361 CHECK(error);
362 error->Populate(Error::kInvalidArguments, kMessage);
363 }
364}
365
Paul Stewart22aa71b2011-09-16 12:15:11 -0700366string Manager::GetTechnologyOrder() {
367 vector<string> technology_names;
368 for (vector<Technology::Identifier>::iterator it = technology_order_.begin();
369 it != technology_order_.end();
370 ++it) {
371 technology_names.push_back(Technology::NameFromIdentifier(*it));
372 }
373
374 return JoinString(technology_names, ',');
375}
376
377void Manager::SetTechnologyOrder(const string &order, Error *error) {
378 vector<Technology::Identifier> new_order;
379 map<Technology::Identifier, bool> seen;
380
381 vector<string> order_parts;
382 base::SplitString(order, ',', &order_parts);
383
384 for (vector<string>::iterator it = order_parts.begin();
385 it != order_parts.end();
386 ++it) {
387 Technology::Identifier identifier = Technology::IdentifierFromName(*it);
388
389 if (identifier == Technology::kUnknown) {
390 error->Populate(Error::kInvalidArguments, *it +
391 " is an unknown technology name");
392 return;
393 }
394
395 if (ContainsKey(seen, identifier)) {
396 error->Populate(Error::kInvalidArguments, *it +
397 " is duplicated in the list");
398 return;
399 }
400 seen[identifier] = true;
401 new_order.push_back(identifier);
402 }
403
404 technology_order_ = new_order;
405 SortServices();
406}
407
Paul Stewart75897df2011-04-27 09:05:53 -0700408} // namespace shill