1// Copyright 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 "base/message_loop/message_pump_io_ios.h"
6
7namespace base {
8
9MessagePumpIOSForIO::FileDescriptorWatcher::FileDescriptorWatcher()
10    : is_persistent_(false),
11      fdref_(NULL),
12      callback_types_(0),
13      fd_source_(NULL),
14      watcher_(NULL) {
15}
16
17MessagePumpIOSForIO::FileDescriptorWatcher::~FileDescriptorWatcher() {
18  StopWatchingFileDescriptor();
19}
20
21bool MessagePumpIOSForIO::FileDescriptorWatcher::StopWatchingFileDescriptor() {
22  if (fdref_ == NULL)
23    return true;
24
25  CFFileDescriptorDisableCallBacks(fdref_, callback_types_);
26  if (pump_)
27    pump_->RemoveRunLoopSource(fd_source_);
28  fd_source_.reset();
29  fdref_.reset();
30  callback_types_ = 0;
31  pump_.reset();
32  watcher_ = NULL;
33  return true;
34}
35
36void MessagePumpIOSForIO::FileDescriptorWatcher::Init(
37    CFFileDescriptorRef fdref,
38    CFOptionFlags callback_types,
39    CFRunLoopSourceRef fd_source,
40    bool is_persistent) {
41  DCHECK(fdref);
42  DCHECK(!fdref_);
43
44  is_persistent_ = is_persistent;
45  fdref_.reset(fdref);
46  callback_types_ = callback_types;
47  fd_source_.reset(fd_source);
48}
49
50void MessagePumpIOSForIO::FileDescriptorWatcher::OnFileCanReadWithoutBlocking(
51    int fd,
52    MessagePumpIOSForIO* pump) {
53  DCHECK(callback_types_ & kCFFileDescriptorReadCallBack);
54  pump->WillProcessIOEvent();
55  watcher_->OnFileCanReadWithoutBlocking(fd);
56  pump->DidProcessIOEvent();
57}
58
59void MessagePumpIOSForIO::FileDescriptorWatcher::OnFileCanWriteWithoutBlocking(
60    int fd,
61    MessagePumpIOSForIO* pump) {
62  DCHECK(callback_types_ & kCFFileDescriptorWriteCallBack);
63  pump->WillProcessIOEvent();
64  watcher_->OnFileCanWriteWithoutBlocking(fd);
65  pump->DidProcessIOEvent();
66}
67
68MessagePumpIOSForIO::MessagePumpIOSForIO() : weak_factory_(this) {
69}
70
71MessagePumpIOSForIO::~MessagePumpIOSForIO() {
72}
73
74bool MessagePumpIOSForIO::WatchFileDescriptor(
75    int fd,
76    bool persistent,
77    int mode,
78    FileDescriptorWatcher *controller,
79    Watcher *delegate) {
80  DCHECK_GE(fd, 0);
81  DCHECK(controller);
82  DCHECK(delegate);
83  DCHECK(mode == WATCH_READ || mode == WATCH_WRITE || mode == WATCH_READ_WRITE);
84
85  // WatchFileDescriptor should be called on the pump thread. It is not
86  // threadsafe, and your watcher may never be registered.
87  DCHECK(watch_file_descriptor_caller_checker_.CalledOnValidThread());
88
89  CFFileDescriptorContext source_context = {0};
90  source_context.info = controller;
91
92  CFOptionFlags callback_types = 0;
93  if (mode & WATCH_READ) {
94    callback_types |= kCFFileDescriptorReadCallBack;
95  }
96  if (mode & WATCH_WRITE) {
97    callback_types |= kCFFileDescriptorWriteCallBack;
98  }
99
100  CFFileDescriptorRef fdref = controller->fdref_;
101  if (fdref == NULL) {
102    base::ScopedCFTypeRef<CFFileDescriptorRef> scoped_fdref(
103        CFFileDescriptorCreate(
104            kCFAllocatorDefault, fd, false, HandleFdIOEvent, &source_context));
105    if (scoped_fdref == NULL) {
106      NOTREACHED() << "CFFileDescriptorCreate failed";
107      return false;
108    }
109
110    CFFileDescriptorEnableCallBacks(scoped_fdref, callback_types);
111
112    // TODO(wtc): what should the 'order' argument be?
113    base::ScopedCFTypeRef<CFRunLoopSourceRef> scoped_fd_source(
114        CFFileDescriptorCreateRunLoopSource(
115            kCFAllocatorDefault, scoped_fdref, 0));
116    if (scoped_fd_source == NULL) {
117      NOTREACHED() << "CFFileDescriptorCreateRunLoopSource failed";
118      return false;
119    }
120    CFRunLoopAddSource(run_loop(), scoped_fd_source, kCFRunLoopCommonModes);
121
122    // Transfer ownership of scoped_fdref and fd_source to controller.
123    controller->Init(scoped_fdref.release(), callback_types,
124                     scoped_fd_source.release(), persistent);
125  } else {
126    // It's illegal to use this function to listen on 2 separate fds with the
127    // same |controller|.
128    if (CFFileDescriptorGetNativeDescriptor(fdref) != fd) {
129      NOTREACHED() << "FDs don't match: "
130                   << CFFileDescriptorGetNativeDescriptor(fdref)
131                   << " != " << fd;
132      return false;
133    }
134    if (persistent != controller->is_persistent_) {
135      NOTREACHED() << "persistent doesn't match";
136      return false;
137    }
138
139    // Combine old/new event masks.
140    CFFileDescriptorDisableCallBacks(fdref, controller->callback_types_);
141    controller->callback_types_ |= callback_types;
142    CFFileDescriptorEnableCallBacks(fdref, controller->callback_types_);
143  }
144
145  controller->set_watcher(delegate);
146  controller->set_pump(weak_factory_.GetWeakPtr());
147
148  return true;
149}
150
151void MessagePumpIOSForIO::RemoveRunLoopSource(CFRunLoopSourceRef source) {
152  CFRunLoopRemoveSource(run_loop(), source, kCFRunLoopCommonModes);
153}
154
155void MessagePumpIOSForIO::AddIOObserver(IOObserver *obs) {
156  io_observers_.AddObserver(obs);
157}
158
159void MessagePumpIOSForIO::RemoveIOObserver(IOObserver *obs) {
160  io_observers_.RemoveObserver(obs);
161}
162
163void MessagePumpIOSForIO::WillProcessIOEvent() {
164  FOR_EACH_OBSERVER(IOObserver, io_observers_, WillProcessIOEvent());
165}
166
167void MessagePumpIOSForIO::DidProcessIOEvent() {
168  FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent());
169}
170
171// static
172void MessagePumpIOSForIO::HandleFdIOEvent(CFFileDescriptorRef fdref,
173                                          CFOptionFlags callback_types,
174                                          void* context) {
175  FileDescriptorWatcher* controller =
176      static_cast<FileDescriptorWatcher*>(context);
177  DCHECK_EQ(fdref, controller->fdref_);
178
179  // Ensure that |fdref| will remain live for the duration of this function
180  // call even if |controller| is deleted or |StopWatchingFileDescriptor()| is
181  // called, either of which will cause |fdref| to be released.
182  ScopedCFTypeRef<CFFileDescriptorRef> scoped_fdref(
183      fdref, base::scoped_policy::RETAIN);
184
185  int fd = CFFileDescriptorGetNativeDescriptor(fdref);
186  MessagePumpIOSForIO* pump = controller->pump().get();
187  DCHECK(pump);
188  if (callback_types & kCFFileDescriptorWriteCallBack)
189    controller->OnFileCanWriteWithoutBlocking(fd, pump);
190
191  // Perform the read callback only if the file descriptor has not been
192  // invalidated in the write callback. As |FileDescriptorWatcher| invalidates
193  // its file descriptor on destruction, the file descriptor being valid also
194  // guarantees that |controller| has not been deleted.
195  if (callback_types & kCFFileDescriptorReadCallBack &&
196      CFFileDescriptorIsValid(fdref)) {
197    DCHECK_EQ(fdref, controller->fdref_);
198    controller->OnFileCanReadWithoutBlocking(fd, pump);
199  }
200
201  // Re-enable callbacks after the read/write if the file descriptor is still
202  // valid and the controller is persistent.
203  if (CFFileDescriptorIsValid(fdref) && controller->is_persistent_) {
204    DCHECK_EQ(fdref, controller->fdref_);
205    CFFileDescriptorEnableCallBacks(fdref, callback_types);
206  }
207}
208
209}  // namespace base
210