1/* Copyright (C) 2002 The gtkmm Development Team 2 * 3 * This library is free software; you can redistribute it and/or 4 * modify it under the terms of the GNU Lesser General Public 5 * License as published by the Free Software Foundation; either 6 * version 2.1 of the License, or (at your option) any later version. 7 * 8 * This library is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 * Lesser General Public License for more details. 12 * 13 * You should have received a copy of the GNU Lesser General Public 14 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 15 */ 16 17#include <glibmm/exceptionhandler.h> 18#include <glibmm/iochannel.h> 19#include <glibmm/utility.h> 20#include <glibmm/main.h> 21 22namespace 23{ 24 25// Glib::IOChannel reference counting issues: 26// 27// Normally, you'd expect that the C++ object stays around as long as the 28// C instance does. Also Glib::wrap() usually returns always the same C++ 29// wrapper object for a single C instance. 30// 31// Unfortunately it isn't possible to implement these features if we didn't 32// create the underlying GIOChannel. That is, when wrapping existing 33// GIOChannel instances such as returned by e.g. g_io_channel_unix_new() or 34// g_io_channel_new_file(). Neither is there a way to hook up a wrapper 35// object in an existing GIOChannel, nor exists any destroy notification. 36// 37// So that means: If the IOChannel is implemented in C++ -- that is, our 38// GlibmmIOChannel backend is used -- we use the GIOChannel reference 39// counting mechanism. If the IOChannel backend is unknown, then the 40// wrapper instance holds always exactly one reference to the GIOChannel. 41// The wrapper object itself is then managed via our own refcounting 42// mechanism. To do that a utility class ForeignIOChannel is introduced to 43// override reference() and unreference(). 44 45class ForeignIOChannel : public Glib::IOChannel 46{ 47public: 48 ForeignIOChannel(GIOChannel* gobject, bool take_copy) 49 : Glib::IOChannel(gobject, take_copy), ref_count_(0) 50 { 51 } 52 53 void reference() const override; 54 void unreference() const override; 55 56private: 57 mutable int ref_count_; 58}; 59 60void 61ForeignIOChannel::reference() const 62{ 63 ++ref_count_; 64} 65 66void 67ForeignIOChannel::unreference() const 68{ 69 if (!(--ref_count_)) 70 delete this; 71} 72 73} // anonymous namespace 74 75namespace Glib 76{ 77 78class GlibmmIOChannel 79{ 80public: 81 GIOChannel base; 82 Glib::IOChannel* wrapper; 83 84 static const GIOFuncs vfunc_table; 85 86 static GIOStatus io_read( 87 GIOChannel* channel, char* buf, gsize count, gsize* bytes_read, GError** err); 88 89 static GIOStatus io_write( 90 GIOChannel* channel, const char* buf, gsize count, gsize* bytes_written, GError** err); 91 92 static GIOStatus io_seek(GIOChannel* channel, gint64 offset, GSeekType type, GError** err); 93 static GIOStatus io_close(GIOChannel* channel, GError** err); 94 95 static GSource* io_create_watch(GIOChannel* channel, GIOCondition condition); 96 static void io_free(GIOChannel* channel); 97 98 static GIOStatus io_set_flags(GIOChannel* channel, GIOFlags flags, GError** err); 99 static GIOFlags io_get_flags(GIOChannel* channel); 100}; 101 102// static 103const GIOFuncs GlibmmIOChannel::vfunc_table = { 104 &GlibmmIOChannel::io_read, &GlibmmIOChannel::io_write, &GlibmmIOChannel::io_seek, 105 &GlibmmIOChannel::io_close, &GlibmmIOChannel::io_create_watch, &GlibmmIOChannel::io_free, 106 &GlibmmIOChannel::io_set_flags, &GlibmmIOChannel::io_get_flags, 107}; 108 109/**** GLib::IOChannel ******************************************************/ 110 111/* Construct a custom C++-implemented IOChannel. GlibmmIOChannel is an 112 * extended GIOChannel struct which allows us to hook up a pointer to this 113 * persistent wrapper instance. 114 */ 115IOChannel::IOChannel() : gobject_(static_cast<GIOChannel*>(g_malloc(sizeof(GlibmmIOChannel)))) 116{ 117 g_io_channel_init(gobject_); 118 gobject_->funcs = const_cast<GIOFuncs*>(&GlibmmIOChannel::vfunc_table); 119 120 reinterpret_cast<GlibmmIOChannel*>(gobject_)->wrapper = this; 121} 122 123IOChannel::IOChannel(IOChannel&& other) noexcept : sigc::trackable(std::move(other)), 124 gobject_(std::move(other.gobject_)) 125{ 126 other.gobject_ = nullptr; 127} 128 129IOChannel& 130IOChannel::operator=(IOChannel&& other) noexcept 131{ 132 sigc::trackable::operator=(std::move(other)); 133 134 release_gobject(); 135 136 gobject_ = std::move(other.gobject_); 137 other.gobject_ = nullptr; 138 139 return *this; 140} 141 142/* Construct an IOChannel wrapper for an already created GIOChannel. 143 * See the comment at the top of this file for an explanation of the 144 * problems with this approach. 145 */ 146IOChannel::IOChannel(GIOChannel* gobject, bool take_copy) : gobject_(gobject) 147{ 148 // This ctor should never be called for GlibmmIOChannel instances. 149 g_assert(gobject != nullptr); 150 g_assert(gobject->funcs != &GlibmmIOChannel::vfunc_table); 151 152 if (take_copy) 153 g_io_channel_ref(gobject_); 154} 155 156void 157IOChannel::release_gobject() 158{ 159 if (gobject_) 160 { 161 // Check whether this IOChannel is implemented in C++, i.e. whether it 162 // uses our GlibmmIOChannel forwarding backend. Normally, this will never 163 // be true because the wrapper should only be deleted in the io_free() 164 // callback, which clears gobject_ before deleting. But in case the ctor 165 // of a derived class threw an exception the GIOChannel must be destroyed 166 // prematurely. 167 // 168 if (gobject_->funcs == &GlibmmIOChannel::vfunc_table) 169 { 170 // Disconnect the wrapper object so that it won't be deleted twice. 171 reinterpret_cast<GlibmmIOChannel*>(gobject_)->wrapper = nullptr; 172 } 173 174 const auto tmp_gobject = gobject_; 175 gobject_ = nullptr; 176 177 g_io_channel_unref(tmp_gobject); 178 } 179} 180 181IOChannel::~IOChannel() 182{ 183 release_gobject(); 184} 185 186Glib::RefPtr<IOChannel> 187IOChannel::create_from_file(const std::string& filename, const std::string& mode) 188{ 189 GError* gerror = nullptr; 190 const auto channel = g_io_channel_new_file(filename.c_str(), mode.c_str(), &gerror); 191 192 if (gerror) 193 { 194 Glib::Error::throw_exception(gerror); 195 } 196 197 return Glib::wrap(channel, false); 198} 199 200Glib::RefPtr<IOChannel> 201IOChannel::create_from_fd(int fd) 202{ 203 return Glib::wrap(g_io_channel_unix_new(fd), false); 204} 205 206#ifdef G_OS_WIN32 207 208Glib::RefPtr<IOChannel> 209IOChannel::create_from_win32_fd(int fd) 210{ 211 return Glib::wrap(g_io_channel_win32_new_fd(fd), false); 212} 213 214Glib::RefPtr<IOChannel> 215IOChannel::create_from_win32_socket(int socket) 216{ 217 return Glib::wrap(g_io_channel_win32_new_socket(socket), false); 218} 219 220#endif /* G_OS_WIN32 */ 221 222IOStatus 223IOChannel::write(const Glib::ustring& str) 224{ 225 gsize bytes_written = 0; 226 return write(str.data(), str.bytes(), bytes_written); 227} 228 229IOStatus 230IOChannel::read_line(Glib::ustring& line) 231{ 232 GError* gerror = nullptr; 233 gsize bytes = 0; 234 char* pch_buf = nullptr; 235 236 const auto status = g_io_channel_read_line(gobj(), &pch_buf, &bytes, nullptr, &gerror); 237 auto buf = make_unique_ptr_gfree(pch_buf); 238 if (gerror) 239 { 240 Glib::Error::throw_exception(gerror); 241 } 242 243 if (buf.get()) 244 line.assign(buf.get(), buf.get() + bytes); 245 else 246 line.erase(); 247 248 return (IOStatus)status; 249} 250 251IOStatus 252IOChannel::read_to_end(Glib::ustring& str) 253{ 254 GError* gerror = nullptr; 255 gsize bytes = 0; 256 char* pch_buf = nullptr; 257 258 const auto status = g_io_channel_read_to_end(gobj(), &pch_buf, &bytes, &gerror); 259 auto buf = make_unique_ptr_gfree(pch_buf); 260 if (gerror) 261 { 262 Glib::Error::throw_exception(gerror); 263 } 264 265 if (buf.get()) 266 str.assign(buf.get(), buf.get() + bytes); 267 else 268 str.erase(); 269 270 return (IOStatus)status; 271} 272 273IOStatus 274IOChannel::read(Glib::ustring& str, gsize count) 275{ 276 auto buf = make_unique_ptr_gfree(g_new(char, count)); 277 GError* gerror = nullptr; 278 gsize bytes = 0; 279 280 const auto status = g_io_channel_read_chars(gobj(), buf.get(), count, &bytes, &gerror); 281 282 if (gerror) 283 { 284 Glib::Error::throw_exception(gerror); 285 } 286 287 if (buf.get()) 288 str.assign(buf.get(), buf.get() + bytes); 289 else 290 str.erase(); 291 292 return (IOStatus)status; 293} 294 295IOStatus 296IOChannel::set_encoding(const std::string& encoding) 297{ 298 GError* gerror = nullptr; 299 300 const auto status = g_io_channel_set_encoding(gobj(), Glib::c_str_or_nullptr(encoding), &gerror); 301 302 if (gerror) 303 { 304 Glib::Error::throw_exception(gerror); 305 } 306 307 return (IOStatus)status; 308} 309 310std::string 311IOChannel::get_encoding() const 312{ 313 const char* const encoding = g_io_channel_get_encoding(gobject_); 314 return convert_const_gchar_ptr_to_stdstring(encoding); 315} 316 317void 318IOChannel::set_line_term(const std::string& term) 319{ 320 if (term.empty()) 321 g_io_channel_set_line_term(gobj(), nullptr, 0); 322 else 323 g_io_channel_set_line_term(gobj(), term.data(), term.size()); 324} 325 326std::string 327IOChannel::get_line_term() const 328{ 329 int len = 0; 330 const char* const term = g_io_channel_get_line_term(gobject_, &len); 331 332 return (term) ? std::string(term, len) : std::string(); 333} 334 335Glib::RefPtr<IOSource> 336IOChannel::create_watch(IOCondition condition) 337{ 338 // The corresponding unreference() takes place in the dtor 339 // of the Glib::RefPtr<IOChannel> object below. 340 reference(); 341 return IOSource::create(Glib::RefPtr<IOChannel>(this), condition); 342} 343 344IOStatus 345IOChannel::read_vfunc(char*, gsize, gsize&) 346{ 347 g_assert_not_reached(); 348 return IO_STATUS_ERROR; 349} 350 351IOStatus 352IOChannel::write_vfunc(const char*, gsize, gsize&) 353{ 354 g_assert_not_reached(); 355 return IO_STATUS_ERROR; 356} 357 358IOStatus IOChannel::seek_vfunc(gint64, SeekType) 359{ 360 g_assert_not_reached(); 361 return IO_STATUS_ERROR; 362} 363 364IOStatus 365IOChannel::close_vfunc() 366{ 367 g_assert_not_reached(); 368 return IO_STATUS_ERROR; 369} 370 371Glib::RefPtr<Glib::Source> IOChannel::create_watch_vfunc(IOCondition) 372{ 373 g_assert_not_reached(); 374 return Glib::RefPtr<Glib::Source>(); 375} 376 377IOStatus IOChannel::set_flags_vfunc(IOFlags) 378{ 379 g_assert_not_reached(); 380 return IO_STATUS_ERROR; 381} 382 383IOFlags 384IOChannel::get_flags_vfunc() 385{ 386 g_assert_not_reached(); 387 return IOFlags(0); 388} 389 390void 391IOChannel::reference() const 392{ 393 g_io_channel_ref(gobject_); 394} 395 396void 397IOChannel::unreference() const 398{ 399 g_io_channel_unref(gobject_); 400} 401 402Glib::RefPtr<IOChannel> 403wrap(GIOChannel* gobject, bool take_copy) 404{ 405 IOChannel* cpp_object = nullptr; 406 407 if (gobject) 408 { 409 if (gobject->funcs == &GlibmmIOChannel::vfunc_table) 410 { 411 cpp_object = reinterpret_cast<GlibmmIOChannel*>(gobject)->wrapper; 412 413 if (take_copy && cpp_object) 414 cpp_object->reference(); 415 } 416 else 417 { 418 cpp_object = new ForeignIOChannel(gobject, take_copy); 419 cpp_object->reference(); // the refcount is initially 0 420 } 421 } 422 423 return Glib::RefPtr<IOChannel>(cpp_object); 424} 425 426/**** Glib::GlibmmIOChannel ************************************************/ 427 428GIOStatus 429GlibmmIOChannel::io_read( 430 GIOChannel* channel, char* buf, gsize count, gsize* bytes_read, GError** err) 431{ 432 const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper; 433 434 try 435 { 436 return (GIOStatus)wrapper->read_vfunc(buf, count, *bytes_read); 437 } 438 catch (Glib::Error& error) 439 { 440 error.propagate(err); 441 } 442 catch (...) 443 { 444 Glib::exception_handlers_invoke(); 445 } 446 447 return G_IO_STATUS_ERROR; 448} 449 450GIOStatus 451GlibmmIOChannel::io_write( 452 GIOChannel* channel, const char* buf, gsize count, gsize* bytes_written, GError** err) 453{ 454 const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper; 455 456 try 457 { 458 return (GIOStatus)wrapper->write_vfunc(buf, count, *bytes_written); 459 } 460 catch (Glib::Error& error) 461 { 462 error.propagate(err); 463 } 464 catch (...) 465 { 466 Glib::exception_handlers_invoke(); 467 } 468 469 return G_IO_STATUS_ERROR; 470} 471 472GIOStatus 473GlibmmIOChannel::io_seek(GIOChannel* channel, gint64 offset, GSeekType type, GError** err) 474{ 475 const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper; 476 477 try 478 { 479 return (GIOStatus)wrapper->seek_vfunc(offset, (SeekType)type); 480 } 481 catch (Glib::Error& error) 482 { 483 error.propagate(err); 484 } 485 catch (...) 486 { 487 Glib::exception_handlers_invoke(); 488 } 489 490 return G_IO_STATUS_ERROR; 491} 492 493GIOStatus 494GlibmmIOChannel::io_close(GIOChannel* channel, GError** err) 495{ 496 const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper; 497 498 try 499 { 500 return (GIOStatus)wrapper->close_vfunc(); 501 } 502 catch (Glib::Error& error) 503 { 504 error.propagate(err); 505 } 506 catch (...) 507 { 508 Glib::exception_handlers_invoke(); 509 } 510 511 return G_IO_STATUS_ERROR; 512} 513 514// static 515GSource* 516GlibmmIOChannel::io_create_watch(GIOChannel* channel, GIOCondition condition) 517{ 518 const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper; 519 520 try 521 { 522 const auto source = wrapper->create_watch_vfunc((IOCondition)condition); 523 return (source) ? source->gobj_copy() : nullptr; 524 } 525 catch (...) 526 { 527 Glib::exception_handlers_invoke(); 528 } 529 530 return nullptr; 531} 532 533// static 534void 535GlibmmIOChannel::io_free(GIOChannel* channel) 536{ 537 if (IOChannel* const wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper) 538 { 539 wrapper->gobject_ = nullptr; 540 delete wrapper; 541 } 542 543 g_free(channel); 544} 545 546GIOStatus 547GlibmmIOChannel::io_set_flags(GIOChannel* channel, GIOFlags flags, GError** err) 548{ 549 const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper; 550 551 try 552 { 553 return (GIOStatus)wrapper->set_flags_vfunc((IOFlags)flags); 554 } 555 catch (Glib::Error& error) 556 { 557 error.propagate(err); 558 } 559 catch (...) 560 { 561 Glib::exception_handlers_invoke(); 562 } 563 564 return G_IO_STATUS_ERROR; 565} 566 567// static 568GIOFlags 569GlibmmIOChannel::io_get_flags(GIOChannel* channel) 570{ 571 const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper; 572 573 try 574 { 575 return (GIOFlags)wrapper->get_flags_vfunc(); 576 } 577 catch (...) 578 { 579 Glib::exception_handlers_invoke(); 580 } 581 582 return GIOFlags(0); 583} 584 585} // namespace Glib 586