| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium 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 | |
| 5 #include "chrome/browser/policy/cloud/device_management_service.h" | |
| 6 | |
| 7 #include <utility> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/compiler_specific.h" | |
| 11 #include "base/message_loop/message_loop.h" | |
| 12 #include "base/message_loop/message_loop_proxy.h" | |
| 13 #include "net/base/escape.h" | |
| 14 #include "net/base/load_flags.h" | |
| 15 #include "net/base/net_errors.h" | |
| 16 #include "net/http/http_response_headers.h" | |
| 17 #include "net/url_request/url_fetcher.h" | |
| 18 #include "net/url_request/url_request_status.h" | |
| 19 #include "url/gurl.h" | |
| 20 | |
| 21 namespace em = enterprise_management; | |
| 22 | |
| 23 namespace policy { | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 const char kPostContentType[] = "application/protobuf"; | |
| 28 | |
| 29 const char kServiceTokenAuthHeader[] = "Authorization: GoogleLogin auth="; | |
| 30 const char kDMTokenAuthHeader[] = "Authorization: GoogleDMToken token="; | |
| 31 | |
| 32 // Number of times to retry on ERR_NETWORK_CHANGED errors. | |
| 33 const int kMaxNetworkChangedRetries = 3; | |
| 34 | |
| 35 // HTTP Error Codes of the DM Server with their concrete meanings in the context | |
| 36 // of the DM Server communication. | |
| 37 const int kSuccess = 200; | |
| 38 const int kInvalidArgument = 400; | |
| 39 const int kInvalidAuthCookieOrDMToken = 401; | |
| 40 const int kMissingLicenses = 402; | |
| 41 const int kDeviceManagementNotAllowed = 403; | |
| 42 const int kInvalidURL = 404; // This error is not coming from the GFE. | |
| 43 const int kInvalidSerialNumber = 405; | |
| 44 const int kDeviceIdConflict = 409; | |
| 45 const int kDeviceNotFound = 410; | |
| 46 const int kPendingApproval = 412; | |
| 47 const int kInternalServerError = 500; | |
| 48 const int kServiceUnavailable = 503; | |
| 49 const int kPolicyNotFound = 902; // This error is not sent as HTTP status code. | |
| 50 | |
| 51 bool IsProxyError(const net::URLRequestStatus status) { | |
| 52 switch (status.error()) { | |
| 53 case net::ERR_PROXY_CONNECTION_FAILED: | |
| 54 case net::ERR_TUNNEL_CONNECTION_FAILED: | |
| 55 case net::ERR_PROXY_AUTH_UNSUPPORTED: | |
| 56 case net::ERR_HTTPS_PROXY_TUNNEL_RESPONSE: | |
| 57 case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED: | |
| 58 case net::ERR_PROXY_CERTIFICATE_INVALID: | |
| 59 case net::ERR_SOCKS_CONNECTION_FAILED: | |
| 60 case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE: | |
| 61 return true; | |
| 62 } | |
| 63 return false; | |
| 64 } | |
| 65 | |
| 66 bool IsProtobufMimeType(const net::URLFetcher* fetcher) { | |
| 67 return fetcher->GetResponseHeaders()->HasHeaderValue( | |
| 68 "content-type", "application/x-protobuffer"); | |
| 69 } | |
| 70 | |
| 71 bool FailedWithProxy(const net::URLFetcher* fetcher) { | |
| 72 if ((fetcher->GetLoadFlags() & net::LOAD_BYPASS_PROXY) != 0) { | |
| 73 // The request didn't use a proxy. | |
| 74 return false; | |
| 75 } | |
| 76 | |
| 77 if (!fetcher->GetStatus().is_success() && | |
| 78 IsProxyError(fetcher->GetStatus())) { | |
| 79 LOG(WARNING) << "Proxy failed while contacting dmserver."; | |
| 80 return true; | |
| 81 } | |
| 82 | |
| 83 if (fetcher->GetStatus().is_success() && | |
| 84 fetcher->GetResponseCode() == kSuccess && | |
| 85 fetcher->WasFetchedViaProxy() && | |
| 86 !IsProtobufMimeType(fetcher)) { | |
| 87 // The proxy server can be misconfigured but pointing to an existing | |
| 88 // server that replies to requests. Try to recover if a successful | |
| 89 // request that went through a proxy returns an unexpected mime type. | |
| 90 LOG(WARNING) << "Got bad mime-type in response from dmserver that was " | |
| 91 << "fetched via a proxy."; | |
| 92 return true; | |
| 93 } | |
| 94 | |
| 95 return false; | |
| 96 } | |
| 97 | |
| 98 const char* UserAffiliationToString(UserAffiliation affiliation) { | |
| 99 switch (affiliation) { | |
| 100 case USER_AFFILIATION_MANAGED: | |
| 101 return dm_protocol::kValueUserAffiliationManaged; | |
| 102 case USER_AFFILIATION_NONE: | |
| 103 return dm_protocol::kValueUserAffiliationNone; | |
| 104 } | |
| 105 NOTREACHED() << "Invalid user affiliation " << affiliation; | |
| 106 return dm_protocol::kValueUserAffiliationNone; | |
| 107 } | |
| 108 | |
| 109 const char* JobTypeToRequestType(DeviceManagementRequestJob::JobType type) { | |
| 110 switch (type) { | |
| 111 case DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT: | |
| 112 return dm_protocol::kValueRequestAutoEnrollment; | |
| 113 case DeviceManagementRequestJob::TYPE_REGISTRATION: | |
| 114 return dm_protocol::kValueRequestRegister; | |
| 115 case DeviceManagementRequestJob::TYPE_POLICY_FETCH: | |
| 116 return dm_protocol::kValueRequestPolicy; | |
| 117 case DeviceManagementRequestJob::TYPE_API_AUTH_CODE_FETCH: | |
| 118 return dm_protocol::kValueRequestApiAuthorization; | |
| 119 case DeviceManagementRequestJob::TYPE_UNREGISTRATION: | |
| 120 return dm_protocol::kValueRequestUnregister; | |
| 121 case DeviceManagementRequestJob::TYPE_UPLOAD_CERTIFICATE: | |
| 122 return dm_protocol::kValueRequestUploadCertificate; | |
| 123 } | |
| 124 NOTREACHED() << "Invalid job type " << type; | |
| 125 return ""; | |
| 126 } | |
| 127 | |
| 128 } // namespace | |
| 129 | |
| 130 // Request job implementation used with DeviceManagementService. | |
| 131 class DeviceManagementRequestJobImpl : public DeviceManagementRequestJob { | |
| 132 public: | |
| 133 DeviceManagementRequestJobImpl( | |
| 134 JobType type, | |
| 135 const std::string& agent_parameter, | |
| 136 const std::string& platform_parameter, | |
| 137 DeviceManagementService* service, | |
| 138 net::URLRequestContextGetter* request_context); | |
| 139 virtual ~DeviceManagementRequestJobImpl(); | |
| 140 | |
| 141 // Handles the URL request response. | |
| 142 void HandleResponse(const net::URLRequestStatus& status, | |
| 143 int response_code, | |
| 144 const net::ResponseCookies& cookies, | |
| 145 const std::string& data); | |
| 146 | |
| 147 // Gets the URL to contact. | |
| 148 GURL GetURL(const std::string& server_url); | |
| 149 | |
| 150 // Configures the fetcher, setting up payload and headers. | |
| 151 void ConfigureRequest(net::URLFetcher* fetcher); | |
| 152 | |
| 153 // Returns true if this job should be retried. |fetcher| has just completed, | |
| 154 // and can be inspected to determine if the request failed and should be | |
| 155 // retried. | |
| 156 bool ShouldRetry(const net::URLFetcher* fetcher); | |
| 157 | |
| 158 // Invoked right before retrying this job. | |
| 159 void PrepareRetry(); | |
| 160 | |
| 161 protected: | |
| 162 // DeviceManagementRequestJob: | |
| 163 virtual void Run() OVERRIDE; | |
| 164 | |
| 165 private: | |
| 166 // Invokes the callback with the given error code. | |
| 167 void ReportError(DeviceManagementStatus code); | |
| 168 | |
| 169 // Pointer to the service this job is associated with. | |
| 170 DeviceManagementService* service_; | |
| 171 | |
| 172 // Whether the BYPASS_PROXY flag should be set by ConfigureRequest(). | |
| 173 bool bypass_proxy_; | |
| 174 | |
| 175 // Number of times that this job has been retried due to ERR_NETWORK_CHANGED. | |
| 176 int retries_count_; | |
| 177 | |
| 178 // The request context to use for this job. | |
| 179 net::URLRequestContextGetter* request_context_; | |
| 180 | |
| 181 DISALLOW_COPY_AND_ASSIGN(DeviceManagementRequestJobImpl); | |
| 182 }; | |
| 183 | |
| 184 DeviceManagementRequestJobImpl::DeviceManagementRequestJobImpl( | |
| 185 JobType type, | |
| 186 const std::string& agent_parameter, | |
| 187 const std::string& platform_parameter, | |
| 188 DeviceManagementService* service, | |
| 189 net::URLRequestContextGetter* request_context) | |
| 190 : DeviceManagementRequestJob(type, agent_parameter, platform_parameter), | |
| 191 service_(service), | |
| 192 bypass_proxy_(false), | |
| 193 retries_count_(0), | |
| 194 request_context_(request_context) {} | |
| 195 | |
| 196 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() { | |
| 197 service_->RemoveJob(this); | |
| 198 } | |
| 199 | |
| 200 void DeviceManagementRequestJobImpl::Run() { | |
| 201 service_->AddJob(this); | |
| 202 } | |
| 203 | |
| 204 void DeviceManagementRequestJobImpl::HandleResponse( | |
| 205 const net::URLRequestStatus& status, | |
| 206 int response_code, | |
| 207 const net::ResponseCookies& cookies, | |
| 208 const std::string& data) { | |
| 209 if (status.status() != net::URLRequestStatus::SUCCESS) { | |
| 210 LOG(WARNING) << "DMServer request failed, status: " << status.status() | |
| 211 << ", error: " << status.error(); | |
| 212 em::DeviceManagementResponse dummy_response; | |
| 213 callback_.Run(DM_STATUS_REQUEST_FAILED, status.error(), dummy_response); | |
| 214 return; | |
| 215 } | |
| 216 | |
| 217 if (response_code != kSuccess) | |
| 218 LOG(WARNING) << "DMServer sent an error response: " << response_code; | |
| 219 | |
| 220 switch (response_code) { | |
| 221 case kSuccess: { | |
| 222 em::DeviceManagementResponse response; | |
| 223 if (!response.ParseFromString(data)) { | |
| 224 ReportError(DM_STATUS_RESPONSE_DECODING_ERROR); | |
| 225 return; | |
| 226 } | |
| 227 callback_.Run(DM_STATUS_SUCCESS, net::OK, response); | |
| 228 return; | |
| 229 } | |
| 230 case kInvalidArgument: | |
| 231 ReportError(DM_STATUS_REQUEST_INVALID); | |
| 232 return; | |
| 233 case kInvalidAuthCookieOrDMToken: | |
| 234 ReportError(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID); | |
| 235 return; | |
| 236 case kMissingLicenses: | |
| 237 ReportError(DM_STATUS_SERVICE_MISSING_LICENSES); | |
| 238 return; | |
| 239 case kDeviceManagementNotAllowed: | |
| 240 ReportError(DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED); | |
| 241 return; | |
| 242 case kPendingApproval: | |
| 243 ReportError(DM_STATUS_SERVICE_ACTIVATION_PENDING); | |
| 244 return; | |
| 245 case kInvalidURL: | |
| 246 case kInternalServerError: | |
| 247 case kServiceUnavailable: | |
| 248 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE); | |
| 249 return; | |
| 250 case kDeviceNotFound: | |
| 251 ReportError(DM_STATUS_SERVICE_DEVICE_NOT_FOUND); | |
| 252 return; | |
| 253 case kPolicyNotFound: | |
| 254 ReportError(DM_STATUS_SERVICE_POLICY_NOT_FOUND); | |
| 255 return; | |
| 256 case kInvalidSerialNumber: | |
| 257 ReportError(DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER); | |
| 258 return; | |
| 259 case kDeviceIdConflict: | |
| 260 ReportError(DM_STATUS_SERVICE_DEVICE_ID_CONFLICT); | |
| 261 return; | |
| 262 default: | |
| 263 // Handle all unknown 5xx HTTP error codes as temporary and any other | |
| 264 // unknown error as one that needs more time to recover. | |
| 265 if (response_code >= 500 && response_code <= 599) | |
| 266 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE); | |
| 267 else | |
| 268 ReportError(DM_STATUS_HTTP_STATUS_ERROR); | |
| 269 return; | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 GURL DeviceManagementRequestJobImpl::GetURL( | |
| 274 const std::string& server_url) { | |
| 275 std::string result(server_url); | |
| 276 result += '?'; | |
| 277 for (ParameterMap::const_iterator entry(query_params_.begin()); | |
| 278 entry != query_params_.end(); | |
| 279 ++entry) { | |
| 280 if (entry != query_params_.begin()) | |
| 281 result += '&'; | |
| 282 result += net::EscapeQueryParamValue(entry->first, true); | |
| 283 result += '='; | |
| 284 result += net::EscapeQueryParamValue(entry->second, true); | |
| 285 } | |
| 286 return GURL(result); | |
| 287 } | |
| 288 | |
| 289 void DeviceManagementRequestJobImpl::ConfigureRequest( | |
| 290 net::URLFetcher* fetcher) { | |
| 291 fetcher->SetRequestContext(request_context_); | |
| 292 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | | |
| 293 net::LOAD_DO_NOT_SAVE_COOKIES | | |
| 294 net::LOAD_DISABLE_CACHE | | |
| 295 (bypass_proxy_ ? net::LOAD_BYPASS_PROXY : 0)); | |
| 296 std::string payload; | |
| 297 CHECK(request_.SerializeToString(&payload)); | |
| 298 fetcher->SetUploadData(kPostContentType, payload); | |
| 299 std::string extra_headers; | |
| 300 if (!gaia_token_.empty()) | |
| 301 extra_headers += kServiceTokenAuthHeader + gaia_token_ + "\n"; | |
| 302 if (!dm_token_.empty()) | |
| 303 extra_headers += kDMTokenAuthHeader + dm_token_ + "\n"; | |
| 304 fetcher->SetExtraRequestHeaders(extra_headers); | |
| 305 } | |
| 306 | |
| 307 bool DeviceManagementRequestJobImpl::ShouldRetry( | |
| 308 const net::URLFetcher* fetcher) { | |
| 309 if (FailedWithProxy(fetcher) && !bypass_proxy_) { | |
| 310 // Retry the job if it failed due to a broken proxy, by bypassing the | |
| 311 // proxy on the next try. | |
| 312 bypass_proxy_ = true; | |
| 313 return true; | |
| 314 } | |
| 315 | |
| 316 // Early device policy fetches on ChromeOS and Auto-Enrollment checks are | |
| 317 // often interrupted during ChromeOS startup when network change notifications | |
| 318 // are sent. Allowing the fetcher to retry once after that is enough to | |
| 319 // recover; allow it to retry up to 3 times just in case. | |
| 320 if (fetcher->GetStatus().error() == net::ERR_NETWORK_CHANGED && | |
| 321 retries_count_ < kMaxNetworkChangedRetries) { | |
| 322 ++retries_count_; | |
| 323 return true; | |
| 324 } | |
| 325 | |
| 326 // The request didn't fail, or the limit of retry attempts has been reached; | |
| 327 // forward the result to the job owner. | |
| 328 return false; | |
| 329 } | |
| 330 | |
| 331 void DeviceManagementRequestJobImpl::PrepareRetry() { | |
| 332 if (!retry_callback_.is_null()) | |
| 333 retry_callback_.Run(this); | |
| 334 } | |
| 335 | |
| 336 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) { | |
| 337 em::DeviceManagementResponse dummy_response; | |
| 338 callback_.Run(code, net::OK, dummy_response); | |
| 339 } | |
| 340 | |
| 341 DeviceManagementRequestJob::~DeviceManagementRequestJob() {} | |
| 342 | |
| 343 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) { | |
| 344 gaia_token_ = gaia_token; | |
| 345 } | |
| 346 | |
| 347 void DeviceManagementRequestJob::SetOAuthToken(const std::string& oauth_token) { | |
| 348 AddParameter(dm_protocol::kParamOAuthToken, oauth_token); | |
| 349 } | |
| 350 | |
| 351 void DeviceManagementRequestJob::SetUserAffiliation( | |
| 352 UserAffiliation user_affiliation) { | |
| 353 AddParameter(dm_protocol::kParamUserAffiliation, | |
| 354 UserAffiliationToString(user_affiliation)); | |
| 355 } | |
| 356 | |
| 357 void DeviceManagementRequestJob::SetDMToken(const std::string& dm_token) { | |
| 358 dm_token_ = dm_token; | |
| 359 } | |
| 360 | |
| 361 void DeviceManagementRequestJob::SetClientID(const std::string& client_id) { | |
| 362 AddParameter(dm_protocol::kParamDeviceID, client_id); | |
| 363 } | |
| 364 | |
| 365 em::DeviceManagementRequest* DeviceManagementRequestJob::GetRequest() { | |
| 366 return &request_; | |
| 367 } | |
| 368 | |
| 369 DeviceManagementRequestJob::DeviceManagementRequestJob( | |
| 370 JobType type, | |
| 371 const std::string& agent_parameter, | |
| 372 const std::string& platform_parameter) { | |
| 373 AddParameter(dm_protocol::kParamRequest, JobTypeToRequestType(type)); | |
| 374 AddParameter(dm_protocol::kParamDeviceType, dm_protocol::kValueDeviceType); | |
| 375 AddParameter(dm_protocol::kParamAppType, dm_protocol::kValueAppType); | |
| 376 AddParameter(dm_protocol::kParamAgent, agent_parameter); | |
| 377 AddParameter(dm_protocol::kParamPlatform, platform_parameter); | |
| 378 } | |
| 379 | |
| 380 void DeviceManagementRequestJob::SetRetryCallback( | |
| 381 const RetryCallback& retry_callback) { | |
| 382 retry_callback_ = retry_callback; | |
| 383 } | |
| 384 | |
| 385 void DeviceManagementRequestJob::Start(const Callback& callback) { | |
| 386 callback_ = callback; | |
| 387 Run(); | |
| 388 } | |
| 389 | |
| 390 void DeviceManagementRequestJob::AddParameter(const std::string& name, | |
| 391 const std::string& value) { | |
| 392 query_params_.push_back(std::make_pair(name, value)); | |
| 393 } | |
| 394 | |
| 395 // A random value that other fetchers won't likely use. | |
| 396 const int DeviceManagementService::kURLFetcherID = 0xde71ce1d; | |
| 397 | |
| 398 DeviceManagementService::~DeviceManagementService() { | |
| 399 // All running jobs should have been cancelled by now. | |
| 400 DCHECK(pending_jobs_.empty()); | |
| 401 DCHECK(queued_jobs_.empty()); | |
| 402 } | |
| 403 | |
| 404 DeviceManagementRequestJob* DeviceManagementService::CreateJob( | |
| 405 DeviceManagementRequestJob::JobType type, | |
| 406 net::URLRequestContextGetter* request_context) { | |
| 407 return new DeviceManagementRequestJobImpl( | |
| 408 type, | |
| 409 configuration_->GetAgentParameter(), | |
| 410 configuration_->GetPlatformParameter(), | |
| 411 this, | |
| 412 request_context); | |
| 413 } | |
| 414 | |
| 415 void DeviceManagementService::ScheduleInitialization(int64 delay_milliseconds) { | |
| 416 if (initialized_) | |
| 417 return; | |
| 418 base::MessageLoop::current()->PostDelayedTask( | |
| 419 FROM_HERE, | |
| 420 base::Bind(&DeviceManagementService::Initialize, | |
| 421 weak_ptr_factory_.GetWeakPtr()), | |
| 422 base::TimeDelta::FromMilliseconds(delay_milliseconds)); | |
| 423 } | |
| 424 | |
| 425 void DeviceManagementService::Initialize() { | |
| 426 if (initialized_) | |
| 427 return; | |
| 428 initialized_ = true; | |
| 429 | |
| 430 while (!queued_jobs_.empty()) { | |
| 431 StartJob(queued_jobs_.front()); | |
| 432 queued_jobs_.pop_front(); | |
| 433 } | |
| 434 } | |
| 435 | |
| 436 void DeviceManagementService::Shutdown() { | |
| 437 for (JobFetcherMap::iterator job(pending_jobs_.begin()); | |
| 438 job != pending_jobs_.end(); | |
| 439 ++job) { | |
| 440 delete job->first; | |
| 441 queued_jobs_.push_back(job->second); | |
| 442 } | |
| 443 pending_jobs_.clear(); | |
| 444 } | |
| 445 | |
| 446 DeviceManagementService::DeviceManagementService( | |
| 447 scoped_ptr<Configuration> configuration) | |
| 448 : configuration_(configuration.Pass()), | |
| 449 initialized_(false), | |
| 450 weak_ptr_factory_(this) { | |
| 451 DCHECK(configuration_); | |
| 452 } | |
| 453 | |
| 454 void DeviceManagementService::StartJob(DeviceManagementRequestJobImpl* job) { | |
| 455 std::string server_url = GetServerURL(); | |
| 456 net::URLFetcher* fetcher = net::URLFetcher::Create( | |
| 457 kURLFetcherID, job->GetURL(server_url), net::URLFetcher::POST, this); | |
| 458 job->ConfigureRequest(fetcher); | |
| 459 pending_jobs_[fetcher] = job; | |
| 460 fetcher->Start(); | |
| 461 } | |
| 462 | |
| 463 std::string DeviceManagementService::GetServerURL() { | |
| 464 return configuration_->GetServerUrl(); | |
| 465 } | |
| 466 | |
| 467 void DeviceManagementService::OnURLFetchComplete( | |
| 468 const net::URLFetcher* source) { | |
| 469 JobFetcherMap::iterator entry(pending_jobs_.find(source)); | |
| 470 if (entry == pending_jobs_.end()) { | |
| 471 NOTREACHED() << "Callback from foreign URL fetcher"; | |
| 472 return; | |
| 473 } | |
| 474 | |
| 475 DeviceManagementRequestJobImpl* job = entry->second; | |
| 476 pending_jobs_.erase(entry); | |
| 477 | |
| 478 if (job->ShouldRetry(source)) { | |
| 479 VLOG(1) << "Retrying dmserver request."; | |
| 480 job->PrepareRetry(); | |
| 481 StartJob(job); | |
| 482 } else { | |
| 483 std::string data; | |
| 484 source->GetResponseAsString(&data); | |
| 485 job->HandleResponse(source->GetStatus(), source->GetResponseCode(), | |
| 486 source->GetCookies(), data); | |
| 487 } | |
| 488 delete source; | |
| 489 } | |
| 490 | |
| 491 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) { | |
| 492 if (initialized_) | |
| 493 StartJob(job); | |
| 494 else | |
| 495 queued_jobs_.push_back(job); | |
| 496 } | |
| 497 | |
| 498 void DeviceManagementService::RemoveJob(DeviceManagementRequestJobImpl* job) { | |
| 499 for (JobFetcherMap::iterator entry(pending_jobs_.begin()); | |
| 500 entry != pending_jobs_.end(); | |
| 501 ++entry) { | |
| 502 if (entry->second == job) { | |
| 503 delete entry->first; | |
| 504 pending_jobs_.erase(entry); | |
| 505 return; | |
| 506 } | |
| 507 } | |
| 508 | |
| 509 const JobQueue::iterator elem = | |
| 510 std::find(queued_jobs_.begin(), queued_jobs_.end(), job); | |
| 511 if (elem != queued_jobs_.end()) | |
| 512 queued_jobs_.erase(elem); | |
| 513 } | |
| 514 | |
| 515 } // namespace policy | |
| OLD | NEW |