博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

update_engine-整体结构(三)

Posted on 2019-03-19 11:43  不上班行不行  阅读(2142)  评论(0编辑  收藏  举报

在update_engine-整体结构(二)中分析到了Action,那么我们接着继续分析.

 首先来看一下BuildUpdateActons(...)这个方法。

src/system/update_engine/update_attempter_android.cc

 1 void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
 2   CHECK(!processor_->IsRunning());
 3   processor_->set_delegate(this);
 4 
 5   // Actions:
 6   shared_ptr<InstallPlanAction> install_plan_action(
 7       new InstallPlanAction(install_plan_));
 8 
 9   HttpFetcher* download_fetcher = nullptr;
10   if (FileFetcher::SupportedUrl(url)) {
11     DLOG(INFO) << "Using FileFetcher for file URL.";
12     download_fetcher = new FileFetcher();
13   } else {
14 #ifdef _UE_SIDELOAD
15     LOG(FATAL) << "Unsupported sideload URI: " << url;
16 #else
17     LibcurlHttpFetcher* libcurl_fetcher =
18         new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
19     libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
20     download_fetcher = libcurl_fetcher;
21 #endif  // _UE_SIDELOAD
22   }
23   shared_ptr<DownloadAction> download_action(
24       new DownloadAction(prefs_,
25                          boot_control_,
26                          hardware_,
27                          nullptr,             // system_state, not used.
28                          download_fetcher));  // passes ownership
29   shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
30       new FilesystemVerifierAction());
31 
32   shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
33       new PostinstallRunnerAction(boot_control_, hardware_));
34 
35   download_action->set_delegate(this);
36   download_action->set_base_offset(base_offset_);
37   download_action_ = download_action;
38   postinstall_runner_action->set_delegate(this);
39 
40   actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
41   actions_.push_back(shared_ptr<AbstractAction>(download_action));
42   actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
43   actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
44 
45   // Bond them together. We have to use the leaf-types when calling
46   // BondActions().
47   BondActions(install_plan_action.get(), download_action.get());
48   BondActions(download_action.get(), filesystem_verifier_action.get());
49   BondActions(filesystem_verifier_action.get(),
50               postinstall_runner_action.get());
51 
52   // Enqueue the actions.
53   for (const shared_ptr<AbstractAction>& action : actions_)
54     processor_->EnqueueAction(action.get());
55 }

 我们会发现processor_,InstallPlanAction,DownloadAction,FilesystemVerifierAction,PostinstallRunnerAction。首先分析processor_,它是在UpdateAttempterAndroid的构造方法中被赋值

 1 UpdateAttempterAndroid::UpdateAttempterAndroid(
 2     DaemonStateInterface* daemon_state,
 3     PrefsInterface* prefs,
 4     BootControlInterface* boot_control,
 5     HardwareInterface* hardware)
 6     : daemon_state_(daemon_state),
 7       prefs_(prefs),
 8       boot_control_(boot_control),
 9       hardware_(hardware),
10       processor_(new ActionProcessor()) {
11   network_selector_ = network::CreateNetworkSelector();
12 }

 ActionProcessor的数据结构为:

  1 // An ActionProcessor keeps a queue of Actions and processes them in order.
  2 
  3 namespace chromeos_update_engine {
  4 
  5 class AbstractAction;
  6 class ActionProcessorDelegate;
  7 
  8 class ActionProcessor {
  9  public:
 10   ActionProcessor() = default;
 11 
 12   virtual ~ActionProcessor();
 13 
 14   // Starts processing the first Action in the queue. If there's a delegate,
 15   // when all processing is complete, ProcessingDone() will be called on the
 16   // delegate.
 17   virtual void StartProcessing();
 18 
 19   // Aborts processing. If an Action is running, it will have
 20   // TerminateProcessing() called on it. The Action that was running and all the
 21   // remaining actions will be lost and must be re-enqueued if this Processor is
 22   // to use it.
 23   void StopProcessing();
 24 
 25   // Suspend the processing. If an Action is running, it will have the
 26   // SuspendProcessing() called on it, and it should suspend operations until
 27   // ResumeProcessing() is called on this class to continue. While suspended,
 28   // no new actions will be started. Calling SuspendProcessing while the
 29   // processing is suspended or not running this method performs no action.
 30   void SuspendProcessing();
 31 
 32   // Resume the suspended processing. If the ActionProcessor is not suspended
 33   // or not running in the first place this method performs no action.
 34   void ResumeProcessing();
 35 
 36   // Returns true iff the processing was started but not yet completed nor
 37   // stopped.
 38   bool IsRunning() const { return current_action_ != nullptr || suspended_; }
 39 
 40   // Adds another Action to the end of the queue.
 41   virtual void EnqueueAction(AbstractAction* action);
 42 
 43   // Sets/gets the current delegate. Set to null to remove a delegate.
 44   ActionProcessorDelegate* delegate() const { return delegate_; }
 45   void set_delegate(ActionProcessorDelegate *delegate) {
 46     delegate_ = delegate;
 47   }
 48 
 49   // Returns a pointer to the current Action that's processing.
 50   AbstractAction* current_action() const {
 51     return current_action_;
 52   }
 53 
 54   // Called by an action to notify processor that it's done. Caller passes self.
 55   void ActionComplete(AbstractAction* actionptr, ErrorCode code);
 56 
 57  private:
 58   // Continue processing actions (if any) after the last action terminated with
 59   // the passed error code. If there are no more actions to process, the
 60   // processing will terminate.
 61   void StartNextActionOrFinish(ErrorCode code);
 62 
 63   // Actions that have not yet begun processing, in the order in which
 64   // they'll be processed.
 65   std::deque<AbstractAction*> actions_;
 66 
 67   // A pointer to the currently processing Action, if any.
 68   AbstractAction* current_action_{nullptr};
 69 
 70   // The ErrorCode reported by an action that was suspended but finished while
 71   // being suspended. This error code is stored here to be reported back to the
 72   // delegate once the processor is resumed.
 73   ErrorCode suspended_error_code_{ErrorCode::kSuccess};
 74 
 75   // Whether the action processor is or should be suspended.
 76   bool suspended_{false};
 77 
 78   // A pointer to the delegate, or null if none.
 79   ActionProcessorDelegate* delegate_{nullptr};
 80 
 81   DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
 82 };
 83 
 84 // A delegate object can be used to be notified of events that happen
 85 // in an ActionProcessor. An instance of this class can be passed to an
 86 // ActionProcessor to register itself.
 87 class ActionProcessorDelegate {
 88  public:
 89   virtual ~ActionProcessorDelegate() = default;
 90 
 91   // Called when all processing in an ActionProcessor has completed. A pointer
 92   // to the ActionProcessor is passed. |code| is set to the exit code of the
 93   // last completed action.
 94   virtual void ProcessingDone(const ActionProcessor* processor,
 95                               ErrorCode code) {}
 96 
 97   // Called when processing has stopped. Does not mean that all Actions have
 98   // completed. If/when all Actions complete, ProcessingDone() will be called.
 99   virtual void ProcessingStopped(const ActionProcessor* processor) {}
100 
101   // Called whenever an action has finished processing, either successfully
102   // or otherwise.
103   virtual void ActionCompleted(ActionProcessor* processor,
104                                AbstractAction* action,
105                                ErrorCode code) {}
106 };
107 
108 }  

 从中可以看到ActionProcessor其实就是用来管理Action的,它的方法都比较简单,根据注释我们大体就能够明白每个方法的意思,在遇到的时候某一个方法再具体分析。接下来再看Action它所存在的继承关系如下

Aciton继承关系

 

 FilesystemVerifierAction,PostinstallRunnerAction,DownloadAction都继承了InstallPlanAction,根据继承关系可以看出他们都会有PerformAction,ActionCompleted等方法。PerformAction()是在Action开始执行前进行调用,而ActionCompleted是在执行完成后进行调用。先来看看InstallPlanAction中的内容

 src/system/update_engine/payload_consumer/install_plan.h

 1 class InstallPlanAction : public Action<InstallPlanAction> {
 2  public:
 3   InstallPlanAction() {}
 4   explicit InstallPlanAction(const InstallPlan& install_plan):
 5     install_plan_(install_plan) {}
 6 
 7   void PerformAction() override {
 8     if (HasOutputPipe()) {
 9       SetOutputObject(install_plan_);
10     }
11     processor_->ActionComplete(this, ErrorCode::kSuccess);
12   }
13 
14   InstallPlan* install_plan() { return &install_plan_; }
15 
16   static std::string StaticType() { return "InstallPlanAction"; }
17   std::string Type() const override { return StaticType(); }
18 
19   typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
20   typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType;
21 
22  private:
23   InstallPlan install_plan_;
24 
25   DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
26 };

 可以看到InstallAction比较简单,仅仅是将install_plan_设置为了输出对象,传递给了下一个Action,这是Action之间的一个通信方式,这个方式可以称之为pipe方式,下面来分析一下这种通信方式。先来看在Action这个类里面提到的ActionPipe

 src/system/update_engine/common/action_pipe.h

 1 namespace chromeos_update_engine {
 2 
 3 // Used by Actions an InputObjectType or OutputObjectType to specify that
 4 // for that type, no object is taken/given.
 5 class NoneType {};
 6 
 7 template<typename T>
 8 class Action;
 9 
10 template<typename ObjectType>
11 class ActionPipe {
12  public:
13   virtual ~ActionPipe() {}
14 
15   // This should be called by an Action on its input pipe.
16   // Returns a reference to the stored object.
17   const ObjectType& contents() const { return contents_; }                //获取管道中的内容
18  
19   // This should be called by an Action on its output pipe.
20   // Stores a copy of the passed object in this pipe.
21   void set_contents(const ObjectType& contents) { contents_ = contents; }  //设置管道中的内容
22 
23   // Bonds two Actions together with a new ActionPipe. The ActionPipe is
24   // jointly owned by the two Actions and will be automatically destroyed
25   // when the last Action is destroyed.
26   template<typename FromAction, typename ToAction>
27   static void Bond(FromAction* from, ToAction* to) {                               //将两个Action连接通过pipe连接在一起
28     std::shared_ptr<ActionPipe<ObjectType>> pipe(new ActionPipe<ObjectType>);
29     from->set_out_pipe(pipe);
30 
31     to->set_in_pipe(pipe);  // If you get an error on this line, then
32     // it most likely means that the From object's OutputObjectType is
33     // different from the To object's InputObjectType.
34   }
35 
36  private:
37   ObjectType contents_;
38 
39   // The ctor is private. This is because this class should construct itself
40   // via the static Bond() method.
41   ActionPipe() {}
42   DISALLOW_COPY_AND_ASSIGN(ActionPipe);
43 };
44 
45 // Utility function
46 template<typename FromAction, typename ToAction>
47 void BondActions(FromAction* from, ToAction* to) {
48   static_assert(
49       std::is_same<typename FromAction::OutputObjectType,
50                    typename ToAction::InputObjectType>::value,
51       "FromAction::OutputObjectType doesn't match ToAction::InputObjectType");
52   ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to);
53 }
54 
55 } 

  可以看到ActionPipe主要就是将两个Action连接在一起。为什么说就会连接在一起呢?再来看Action中相关的方法

src/system/update_engine/common/action.h 

 1 template<typename SubClass>
 2 class Action : public AbstractAction {
 3  public:
 4   ~Action() override {}
 5 
 6   // Attaches an input pipe to this Action. This is optional; an Action
 7   // doesn't need to have an input pipe. The input pipe must be of the type
 8   // of object that this class expects.
 9   // This is generally called by ActionPipe::Bond()
10   void set_in_pipe(                                                  //设置输入管道
11       // this type is a fancy way of saying: a shared_ptr to an
12       // ActionPipe<InputObjectType>.
13       const std::shared_ptr<ActionPipe<
14           typename ActionTraits<SubClass>::InputObjectType>>& in_pipe) {
15     in_pipe_ = in_pipe;
16   }
17 
18   // Attaches an output pipe to this Action. This is optional; an Action
19   // doesn't need to have an output pipe. The output pipe must be of the type
20   // of object that this class expects.
21   // This is generally called by ActionPipe::Bond()
22   void set_out_pipe(                                                 //设置输出管道
23       // this type is a fancy way of saying: a shared_ptr to an
24       // ActionPipe<OutputObjectType>.
25       const std::shared_ptr<ActionPipe<
26           typename ActionTraits<SubClass>::OutputObjectType>>& out_pipe) {
27     out_pipe_ = out_pipe;
28   }
29 
30   // Returns true iff there is an associated input pipe. If there's an input
31   // pipe, there's an input object, but it may have been constructed with the
32   // default ctor if the previous action didn't call SetOutputObject().
33   bool HasInputObject() const { return in_pipe_.get(); }                    //是否有输入管道
34 
35   // returns a const reference to the object in the input pipe.
36   const typename ActionTraits<SubClass>::InputObjectType& GetInputObject()         //获取输入的内容
37       const {
38     CHECK(HasInputObject());
39     return in_pipe_->contents();
40   }
41 
42   // Returns true iff there's an output pipe.
43   bool HasOutputPipe() const {                                                  //是否有输出管道
44     return out_pipe_.get();
45   }
46 
47   // Copies the object passed into the output pipe. It will be accessible to
48   // the next Action via that action's input pipe (which is the same as this
49   // Action's output pipe).
50   void SetOutputObject(                                                          //设置输出的内容
51       const typename ActionTraits<SubClass>::OutputObjectType& out_obj) {
52     CHECK(HasOutputPipe());
53     out_pipe_->set_contents(out_obj);
54   }
55 
56   // Returns a reference to the object sitting in the output pipe.
57   const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() {          //获取输出的内容
58     CHECK(HasOutputPipe());
59     return out_pipe_->contents();
60   }
61 
62  protected:
63   // We use a shared_ptr to the pipe. shared_ptr objects destroy what they
64   // point to when the last such shared_ptr object dies. We consider the
65   // Actions on either end of a pipe to "own" the pipe. When the last Action
66   // of the two dies, the ActionPipe will die, too.
67   std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType>>
68       in_pipe_;
69   std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::OutputObjectType>>
70       out_pipe_;
71 };

从这里我们就能够看出每个Action其实有两个ActionPipe,一个是输入ActionPipe,一个是输出ActionPipe,输入ActionPipe和前一个Action的输出ActionPipe其实是一个ActionPipe,输出Actionpipe和下一个Action的输出ActionPipe是一个ActionPipe.

ActionTraits在这个类里仅仅是为InstallPlan这个类型定义了一个新的类型

src/system/update_engine/payload_consumer/install_plan.h

1 template<>
2 class ActionTraits<InstallPlanAction> {
3  public:
4   // Takes the install plan as input
5   typedef InstallPlan InputObjectType;
6   // Passes the install plan as output
7   typedef InstallPlan OutputObjectType;
8 };

 到这里Action机制也分析的差不多了,我们可以回到BuildUpdateActions中继续进行分析了。

 1 void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
 2   CHECK(!processor_->IsRunning());
 3   processor_->set_delegate(this);
 4 
 5   // Actions:
 6   shared_ptr<InstallPlanAction> install_plan_action(
 7       new InstallPlanAction(install_plan_));
 8 
 9   HttpFetcher* download_fetcher = nullptr;
10   if (FileFetcher::SupportedUrl(url)) {
11     DLOG(INFO) << "Using FileFetcher for file URL.";
12     download_fetcher = new FileFetcher();
13   } else {
14 #ifdef _UE_SIDELOAD
15     LOG(FATAL) << "Unsupported sideload URI: " << url;
16 #else
17     LibcurlHttpFetcher* libcurl_fetcher =
18         new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
19     libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
20     download_fetcher = libcurl_fetcher;
21 #endif  // _UE_SIDELOAD
22   }
23   shared_ptr<DownloadAction> download_action(
24       new DownloadAction(prefs_,
25                          boot_control_,
26                          hardware_,
27                          nullptr,             // system_state, not used.
28                          download_fetcher));  // passes ownership
29   shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
30       new FilesystemVerifierAction());
31 
32   shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
33       new PostinstallRunnerAction(boot_control_, hardware_));
34 
35   download_action->set_delegate(this);
36   download_action->set_base_offset(base_offset_);
37   download_action_ = download_action;
38   postinstall_runner_action->set_delegate(this);
39 
40   actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
41   actions_.push_back(shared_ptr<AbstractAction>(download_action));
42   actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
43   actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
44 
45   // Bond them together. We have to use the leaf-types when calling
46   // BondActions().
47   BondActions(install_plan_action.get(), download_action.get());
48   BondActions(download_action.get(), filesystem_verifier_action.get());
49   BondActions(filesystem_verifier_action.get(),
50               postinstall_runner_action.get());
51 
52   // Enqueue the actions.
53   for (const shared_ptr<AbstractAction>& action : actions_)
54     processor_->EnqueueAction(action.get());
55 }
View Code

在这个方法里主要做了:

1.为processor_设置delegate,其实也就是注册了回调方法,UpdateAttempterAndroid实现了ActionProcessDelegate中的方法.

2. 创建了InstallPlanAction

3.创建了download_fetcher,我们这里假定用的是本地的文件既使用file:///协议,所以download_fetcher即为FileFetcher,从这一部分的代码可以看HtppFetcher,FileFetcher,LibcurlHttpFetcher之间具有继承或实现的关系。

4.创建DownloadAction,注意在创建的时候传入了download_fetcher为FileFetcher类型

5.创建FilesystemVerifierAction,PostinstallRunnerAction.从这里可以看出升级流程的精华应该就是这三个Action了

6.为download_action设置delegate,设置开始下载的offfset等,因为代码中设置delegate的操作比较多,如果不注意很有可能记混乱了。

7.为postinstall_runner_action设置delegate

8.将Action加入到Action的集合中

9.使用BondActions方法为Action之间建立管道。

10.将action遍历放入到processor_的队列中,并且设置action的管理者为processor_。

在分析完这个方法所干的事情之后,再分析一下HtppFetcher,FileFetcher,LibcurlHttpFetcher这三者之间的关系

 HtppFetcher,FileFetcher,LibcurlHttpFetcher这三者之间的关系

 

现在继续分析ApplyPayload中的最后一个方法UpdateBootFlags()

 1 void UpdateAttempterAndroid::UpdateBootFlags() {
 2   if (updated_boot_flags_) {
 3     LOG(INFO) << "Already updated boot flags. Skipping.";
 4     CompleteUpdateBootFlags(true);
 5     return;
 6   }
 7   // This is purely best effort.
 8   LOG(INFO) << "Marking booted slot as good.";
 9   if (!boot_control_->MarkBootSuccessfulAsync(
10           Bind(&UpdateAttempterAndroid::CompleteUpdateBootFlags,
11                base::Unretained(this)))) {
12     LOG(ERROR) << "Failed to mark current boot as successful.";
13     CompleteUpdateBootFlags(false);
14   }
15 }

首先检查当前运行的slot是否已经被标记为successful状态,如果是则调用CompleteUpdateBootFlags方法,否则的就调用MarkBootSuccessfulAsync将当前的slot标记为successful。标记完成后调用CompleteUpdateBootFlags方法

1 void UpdateAttempterAndroid::CompleteUpdateBootFlags(bool successful) {
2   updated_boot_flags_ = true;
3   ScheduleProcessingStart();
4 }

 从这里看出即使标记失败了仍然调用 ScheduleProcessingStart(),这个方法主要就是开始执行Action

1 void UpdateAttempterAndroid::ScheduleProcessingStart() {
2   LOG(INFO) << "Scheduling an action processor start.";
3   brillo::MessageLoop::current()->PostTask(
4       FROM_HERE,
5       Bind([](ActionProcessor* processor) { processor->StartProcessing(); },
6            base::Unretained(processor_.get())));
7 }

 在来看看StartProcessing()方法的实现,首先是获取对列中的第一个action,打印action的类型,之后将action移出队列,并且调用PerformAction。

src/system/update_engine/common/action_processor.cc

1 void ActionProcessor::StartProcessing() {
2   CHECK(!IsRunning());
3   if (!actions_.empty()) {
4     current_action_ = actions_.front();    
5     LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
6     actions_.pop_front();
7     current_action_->PerformAction();
8   }
9 }

分析到了这里就对整体的update_engine有了一定的了解,接下来只需要对各个Action逐个击破就好了。在之前已经看过了InstallPlanAction,它的内容很简单,仅仅是在输出管道中设置了install_plan_,接下来就调用了processor_->ActionComplete(this, ErrorCode::kSuccess),看一下ActionComplete的内容,它是如何让下一个action开始执行的。

 1 void ActionProcessor::ActionComplete(AbstractAction* actionptr,
 2                                      ErrorCode code) {
 3   CHECK_EQ(actionptr, current_action_);
 4   if (delegate_)
 5     delegate_->ActionCompleted(this, actionptr, code);
 6   string old_type = current_action_->Type();
 7   current_action_->ActionCompleted(code);
 8   current_action_->SetProcessor(nullptr);
 9   current_action_ = nullptr;
10   LOG(INFO) << "ActionProcessor: finished "
11             << (actions_.empty() ? "last action " : "") << old_type
12             << (suspended_ ? " while suspended" : "")
13             << " with code " << utils::ErrorCodeToString(code);
14   if (!actions_.empty() && code != ErrorCode::kSuccess) {
15     LOG(INFO) << "ActionProcessor: Aborting processing due to failure.";
16     actions_.clear();
17   }
18   if (suspended_) {
19     // If an action finished while suspended we don't start the next action (or
20     // terminate the processing) until the processor is resumed. This condition
21     // will be flagged by a nullptr current_action_ while suspended_ is true.
22     suspended_error_code_ = code;
23     return;
24   }
25   StartNextActionOrFinish(code);
26 }

 其实这个方法中也就进行了善后和开始下一个Action的工作。包括:

1.判断是否注册了回调方法。这里的delegate_的类型为UpdateAttempterAndroid。如果注册了就回调ActionCompleted方法,在UpdateAttempterAndroid中它的内容为

 1 void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor,
 2                                              AbstractAction* action,
 3                                              ErrorCode code) {
 4   // Reset download progress regardless of whether or not the download
 5   // action succeeded.
 6   const string type = action->Type();
 7   if (type == DownloadAction::StaticType()) {
 8     download_progress_ = 0;
 9   }
10   if (code != ErrorCode::kSuccess) {
11     // If an action failed, the ActionProcessor will cancel the whole thing.
12     return;
13   }
14   if (type == DownloadAction::StaticType()) {
15     SetStatusAndNotify(UpdateStatus::FINALIZING);
16   }
17 }

可以看到这个方法主要就是为重置下载的进度。

2.调用当前的Action的ActionCompleted,将Processor和当前Action置空等。

3.如果执行到某人Action的时候出了错,则停止执行其他的Action

4. processor如果被挂起,则暂停执行下一个Action

5.执行下一个Action或者是否完成了所有的Action,StartNextActionOrFinish(code),该方法比较简单,就不进行分析了。

到这里整体的Action的执行流程也就通了,下一篇开始会分析其他三个Action

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

.