From d9b5cb190cbe7b1d1486e0181e4d3b01e9552354 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Fri, 29 Oct 2021 10:22:04 +0900 Subject: [PATCH] Imported Upstream version 2.64.5 --- .clang-format | 11 + .gitlab-ci.yml | 212 +- .gitlab-ci/README.md | 9 +- .gitlab-ci/android-download-ndk.sh | 10 +- .gitlab-ci/android-ndk.Dockerfile | 3 + .gitlab-ci/android-setup-env.sh | 19 +- .gitlab-ci/cache-subprojects.sh | 9 + .gitlab-ci/check-todos.py | 89 + .gitlab-ci/coverage-docker.sh | 2 +- .gitlab-ci/cross_file_mingw64.txt | 1 + .gitlab-ci/debian-stable.Dockerfile | 5 + .gitlab-ci/fedora.Dockerfile | 38 +- .gitlab-ci/mingw.Dockerfile | 3 + .gitlab-ci/run-check-todos.sh | 16 + .gitlab-ci/run-docker.sh | 28 +- .gitlab-ci/run-style-check-diff.sh | 39 + .gitlab-ci/run-tests.sh | 2 +- .gitlab-ci/test-msvc.bat | 2 +- .gitlab-ci/test-msys2.sh | 17 +- NEWS | 591 +- clang-format-diff.py | 133 + docs/reference/gio/gdbus-codegen.xml | 62 + .../gio/gdbus-object-manager-example/.gitignore | 1 + .../gdbus-object-manager-example-docs.xml | 17 + .../gdbus-object-manager-example-sections.txt | 161 + .../gio/gdbus-object-manager-example/meson.build | 11 + docs/reference/gio/gio-docs.xml | 9 + docs/reference/gio/gio-sections-common.txt | 26 +- docs/reference/gio/gio.xml | 41 +- docs/reference/gio/meson.build | 50 +- docs/reference/gio/migrating-gdbus.xml | 29 +- docs/reference/gio/overview.xml | 7 +- docs/reference/glib/building.xml | 16 + docs/reference/glib/cross.xml | 1 + docs/reference/glib/glib-docs.xml | 4 + docs/reference/glib/glib-sections.txt | 45 + docs/reference/glib/meson.build | 3 +- docs/reference/glib/running.xml | 8 +- docs/reference/gobject/glib-genmarshal.xml | 61 +- docs/reference/gobject/glib-mkenums.xml | 198 +- docs/reference/gobject/meson.build | 3 +- docs/reference/gobject/tut_gobject.xml | 23 +- docs/reference/meson.build | 30 +- fuzzing/README.md | 2 +- gio/completion/gio | 23 +- gio/gactiongroup.c | 2 +- gio/gappinfo.c | 2 +- gio/gapplication.c | 18 +- gio/gcancellable.c | 36 +- gio/gcontenttype.c | 2 + gio/gcredentials.c | 5 +- gio/gdbus-2.0/codegen/codegen.py | 159 +- gio/gdbus-2.0/codegen/codegen_main.py | 55 +- gio/gdbus-2.0/codegen/config.py.in | 2 + gio/gdbus-2.0/codegen/dbustypes.py | 11 +- gio/gdbus-2.0/codegen/meson.build | 4 +- gio/gdbus-2.0/codegen/parser.py | 11 +- gio/gdbus-tool.c | 18 +- gio/gdbusaddress.c | 28 +- gio/gdbusauthmechanismsha1.c | 205 +- gio/gdbusconnection.c | 228 +- gio/gdbusconnection.h | 2 +- gio/gdbusdaemon.c | 8 +- gio/gdbusmessage.c | 60 +- gio/gdbusnameowning.c | 96 +- gio/gdbusnamewatching.c | 31 +- gio/gdbusobjectmanagerclient.c | 102 +- gio/gdbusproxy.c | 119 +- gio/gdesktopappinfo.c | 48 +- gio/gdocumentportal.c | 83 +- gio/gdtlsconnection.c | 76 +- gio/gdummytlsbackend.c | 6 +- gio/gfdonotificationbackend.c | 34 + gio/gfile.c | 72 +- gio/gfile.h | 7 +- gio/gfileinfo.c | 16 +- gio/gfileinfo.h | 6 +- gio/gio-launch-desktop.c | 52 - gio/gio-tool-copy.c | 4 + gio/gio-tool-info.c | 53 +- gio/gio-tool-list.c | 14 +- gio/gio-tool-mount.c | 49 +- gio/gio.h | 49 +- gio/gio_trace.h | 4 +- gio/gioenums.h | 29 + gio/giomodule.c | 20 +- gio/gioprivate.h | 4 + gio/giowin32-private.c | 447 ++ gio/glib-compile-resources.c | 20 +- gio/gliststore.c | 83 + gio/gliststore.h | 11 + gio/glocalfile.c | 27 +- gio/glocalfileinfo.c | 5 +- gio/glocalfileoutputstream.c | 6 +- gio/glocalvfs.c | 21 +- gio/gmemorymonitor.c | 150 + gio/gmemorymonitor.h | 62 + gio/gmemorymonitordbus.c | 171 + gio/gmemorymonitordbus.h | 31 + gio/gmemorymonitorportal.c | 152 + gio/gmemorymonitorportal.h | 31 + gio/gmenumodel.c | 4 +- gio/gnetworkmonitorbase.c | 128 +- gio/gregistrysettingsbackend.c | 6 +- gio/gresolver.c | 67 +- gio/gsettings.c | 2 +- gio/gsettingsschema.c | 13 +- gio/gsocket.c | 50 +- gio/gsocketclient.c | 461 +- gio/gsocks5proxy.c | 18 +- gio/gsubprocess.c | 17 + gio/gtask.c | 100 +- gio/gtask.h | 7 + gio/gtestdbus.c | 7 +- gio/gtlsclientconnection.c | 86 +- gio/gtlsconnection.c | 75 +- gio/gtrashportal.c | 4 +- gio/gunixmounts.c | 57 +- gio/gunixoutputstream.c | 4 +- gio/gunixsocketaddress.c | 2 +- gio/gwin32appinfo.c | 438 +- gio/gwin32registrykey.c | 11 +- gio/inotify/inotify-helper.c | 4 +- gio/kqueue/gkqueuefilemonitor.c | 7 +- gio/meson.build | 29 +- gio/org.freedesktop.portal.Documents.xml | 61 +- gio/org.freedesktop.portal.OpenURI.xml | 82 +- gio/org.freedesktop.portal.ProxyResolver.xml | 30 +- gio/tests/actions.c | 2 +- gio/tests/cancellable.c | 6 + gio/tests/codegen.py | 540 ++ gio/tests/dbus-appinfo.c | 83 + gio/tests/defaultvalue.c | 1 - gio/tests/fake-document-portal.c | 144 + gio/tests/file.c | 143 +- gio/tests/g-file-info-filesystem-readonly.c | 54 +- gio/tests/gdbus-addresses.c | 1 + gio/tests/gdbus-connection-flush.c | 3 + gio/tests/gdbus-connection-loss.c | 4 +- gio/tests/gdbus-connection.c | 4 + gio/tests/gdbus-export.c | 4 +- gio/tests/gdbus-names.c | 12 +- gio/tests/gdbus-object-manager-example/meson.build | 6 +- gio/tests/gdbus-serialization.c | 213 +- gio/tests/gdbus-server-auth.c | 539 ++ gio/tests/gdbus-sessionbus.c | 13 + gio/tests/gdbus-test-codegen.c | 130 + gio/tests/gdbus-test-fixture.c | 12 +- gio/tests/gdbus-tests.c | 105 +- gio/tests/gdbus-tests.h | 3 +- gio/tests/gdbus-threading.c | 267 +- gio/tests/glistmodel.c | 67 + gio/tests/live-g-file.c | 324 +- gio/tests/memory-monitor-dbus.py.in | 111 + gio/tests/memory-monitor-portal.py.in | 127 + gio/tests/memory-monitor.c | 88 + gio/tests/meson.build | 143 +- gio/tests/mock-resolver.c | 23 + gio/tests/network-address.c | 197 +- gio/tests/org.gtk.test.dbusappinfo.flatpak.desktop | 5 + gio/tests/services/meson.build | 30 + .../org.freedesktop.portal.Documents.service.in | 3 + gio/tests/socket.c | 13 +- gio/tests/static-link.py | 2 +- gio/tests/taptestrunner.py | 176 + gio/tests/task.c | 42 + gio/tests/test-codegen.xml | 9 + gio/tests/testfilemonitor.c | 2 + gio/tests/unix-streams.c | 8 +- gio/tests/win32-appinfo.c | 470 ++ glib.supp | 19 +- glib/deprecated/gthread-deprecated.c | 6 +- glib/garray.c | 236 +- glib/garray.h | 9 + glib/gatomic.c | 9 - glib/gatomic.h | 180 +- glib/gbacktrace.c | 56 +- glib/gbase64.c | 4 +- glib/gbitlock.c | 2 +- glib/gbookmarkfile.c | 15 +- glib/gbytes.c | 2 +- glib/gcharset.c | 13 +- glib/gchecksum.c | 1 + glib/gconstructor.h | 2 +- glib/gdate.c | 16 +- glib/gdate.h | 2 +- glib/gdatetime.c | 22 +- glib/genviron.c | 15 + glib/gerror.c | 6 +- glib/ghash.c | 27 +- glib/giochannel.c | 13 +- glib/gkeyfile.c | 1 + glib/glib-autocleanups.h | 8 + glib/glib-object.h | 5 +- glib/glib-unix.c | 121 + glib/glib-unix.h | 4 + glib/glib.h | 5 +- glib/glib_trace.h | 4 +- glib/glist.c | 45 + glib/glist.h | 21 + glib/gmacros.h | 24 +- glib/gmain.c | 202 +- glib/gmain.h | 106 +- glib/gmarkup.c | 28 +- glib/gmem.c | 5 +- glib/gmessages.c | 2 +- glib/gmessages.h | 38 + .../meson.build | 20 +- glib/gnulib/meson.build | 4 +- glib/gnulib/xsize.c | 3 + glib/goption.c | 4 +- glib/gpattern.c | 2 +- glib/gpoll.c | 2 +- glib/gprintf.c | 24 +- glib/gquark.c | 2 +- glib/grand.c | 3 +- glib/gslist.c | 45 + glib/gslist.h | 21 + glib/gspawn.c | 445 +- glib/gstdio.c | 41 +- glib/gstdio.h | 4 + glib/gstrfuncs.c | 20 +- glib/gstring.c | 4 +- glib/gtestutils.c | 13 +- glib/gtestutils.h | 8 +- glib/gthread-posix.c | 138 +- glib/gthread-win32.c | 85 +- glib/gthread.c | 51 +- glib/gthread.h | 32 +- glib/gthreadpool.c | 130 +- glib/gthreadprivate.h | 48 +- glib/gtimezone.c | 81 +- glib/gtypes.h | 6 +- glib/gunicollate.c | 2 +- glib/gunidecomp.c | 1 + glib/gutf8.c | 5 + glib/gutils.c | 484 +- glib/gutils.h | 123 + glib/guuid.c | 4 +- glib/gvariant-core.c | 18 +- glib/gvariant-core.h | 2 + glib/gvariant-parser.c | 9 +- glib/gvariant.c | 7 +- glib/gvariant.h | 3 +- glib/gvarianttype.c | 2 +- glib/gversionmacros.h | 42 + glib/gwin32.c | 23 +- glib/libcharset/localcharset.c | 6 +- glib/libcharset/make-patch.sh | 14 +- glib/libcharset/meson.build | 7 +- glib/libcharset/update.sh | 12 +- glib/meson.build | 2 +- glib/tests/array-test.c | 309 +- glib/tests/atomic.c | 151 +- glib/tests/autoptr.c | 41 + glib/tests/bookmarkfile.c | 124 +- glib/tests/fileutils.c | 103 +- glib/tests/gdatetime.c | 51 +- glib/tests/getpwuid-preload.c | 45 + glib/tests/gutils-user-database.c | 43 + glib/tests/gvariant.c | 2 +- glib/tests/io-channel.c | 79 + glib/tests/macros.c | 8 + glib/tests/mainloop.c | 271 +- glib/tests/markups/fail-54.expected | 1 + glib/tests/markups/fail-54.gmarkup | 1 + glib/tests/meson.build | 23 + glib/tests/regex.c | 2 +- glib/tests/spawn-singlethread.c | 14 +- glib/tests/testing.c | 15 + glib/tests/thread-pool.c | 156 + glib/tests/thread.c | 29 +- glib/tests/unix.c | 37 + glib/tests/utf8-validate.c | 6 + glib/tests/utils.c | 87 + glib/update-pcre/update.sh | 34 +- glib/valgrind.h | 4 +- gmodule/gmodule-ar.c | 15 +- gmodule/gmodule-dl.c | 14 +- gmodule/gmodule-dyld.c | 152 - gmodule/gmodule-win32.c | 5 +- gmodule/gmodule.c | 14 +- gmodule/gmoduleconf.h.in | 1 - gmodule/meson.build | 4 - gobject/gbinding.c | 95 +- gobject/gboxed.c | 6 +- gobject/gobject.c | 127 +- gobject/gobject.h | 19 + gobject/gobject_trace.h | 4 +- gobject/gparam.c | 81 +- gobject/gparam.h | 2 +- gobject/gparamspecs.c | 17 +- gobject/gparamspecs.h | 2 +- gobject/gsignal.c | 199 +- gobject/gsignal.h | 4 +- gobject/gtype-private.h | 4 + gobject/gtype.c | 4 +- gobject/gtype.h | 22 +- gobject/gvalue.c | 4 +- gobject/tests/autoptr.c | 30 + gobject/tests/binding.c | 152 +- gobject/tests/meson.build | 1 + gobject/tests/param.c | 97 +- gobject/tests/reference.c | 30 + gobject/tests/signals.c | 213 +- gobject/tests/testing.c | 71 + meson.build | 135 +- meson_options.txt | 4 +- po/POTFILES.in | 1 + po/ca.po | 841 +-- po/cs.po | 912 +-- po/da.po | 892 +-- po/de.po | 1073 +-- po/el.po | 2264 +++--- po/en_GB.po | 878 +-- po/es.po | 925 +-- po/eu.po | 918 +-- po/fr.po | 877 +-- po/gl.po | 1270 ++-- po/he.po | 3519 +++++---- po/hu.po | 909 ++- po/id.po | 914 +-- po/it.po | 968 +-- po/ja.po | 6172 ++++++++++------ po/kk.po | 1786 ++--- po/ko.po | 865 +-- po/lt.po | 880 +-- po/ms.po | 7531 +++++++++++++------- po/nl.po | 2762 ++++--- po/pl.po | 907 +-- po/pt_BR.po | 858 +-- po/ro.po | 1063 +-- po/sl.po | 1882 ++--- po/sr.po | 964 +-- po/sv.po | 923 +-- po/tr.po | 848 +-- po/uk.po | 5934 +++++++++------ po/zh_TW.po | 3113 ++++---- subprojects/gtk-doc.wrap | 4 + subprojects/zlib.wrap | 2 +- tests/atomic-test.c | 63 - tests/meson.build | 1 - tests/run-assert-msg-test.sh | 2 +- tests/run-collate-tests.sh | 18 +- tests/testgdate.c | 3 +- tests/testglib.c | 21 + tests/threadpool-test.c | 2 +- 347 files changed, 44575 insertions(+), 26646 deletions(-) create mode 100644 .clang-format create mode 100755 .gitlab-ci/cache-subprojects.sh create mode 100755 .gitlab-ci/check-todos.py create mode 100755 .gitlab-ci/run-check-todos.sh create mode 100755 .gitlab-ci/run-style-check-diff.sh create mode 100755 clang-format-diff.py create mode 100644 docs/reference/gio/gdbus-object-manager-example/.gitignore create mode 100644 docs/reference/gio/gdbus-object-manager-example/gdbus-object-manager-example-docs.xml create mode 100644 docs/reference/gio/gdbus-object-manager-example/gdbus-object-manager-example-sections.txt create mode 100644 docs/reference/gio/gdbus-object-manager-example/meson.build delete mode 100644 gio/gio-launch-desktop.c create mode 100644 gio/giowin32-private.c create mode 100644 gio/gmemorymonitor.c create mode 100644 gio/gmemorymonitor.h create mode 100644 gio/gmemorymonitordbus.c create mode 100644 gio/gmemorymonitordbus.h create mode 100644 gio/gmemorymonitorportal.c create mode 100644 gio/gmemorymonitorportal.h create mode 100644 gio/tests/codegen.py create mode 100644 gio/tests/fake-document-portal.c create mode 100644 gio/tests/gdbus-server-auth.c create mode 100755 gio/tests/memory-monitor-dbus.py.in create mode 100755 gio/tests/memory-monitor-portal.py.in create mode 100644 gio/tests/memory-monitor.c create mode 100644 gio/tests/org.gtk.test.dbusappinfo.flatpak.desktop create mode 100644 gio/tests/services/meson.build create mode 100644 gio/tests/services/org.freedesktop.portal.Documents.service.in create mode 100644 gio/tests/taptestrunner.py create mode 100644 gio/tests/win32-appinfo.c create mode 100644 glib/gnulib/xsize.c create mode 100644 glib/tests/getpwuid-preload.c create mode 100644 glib/tests/gutils-user-database.c create mode 100644 glib/tests/io-channel.c create mode 100644 glib/tests/markups/fail-54.expected create mode 100644 glib/tests/markups/fail-54.gmarkup create mode 100644 glib/tests/thread-pool.c delete mode 100644 gmodule/gmodule-dyld.c create mode 100644 gobject/tests/testing.c create mode 100644 subprojects/gtk-doc.wrap delete mode 100644 tests/atomic-test.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..13fd0fb --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +# See https://wiki.apertis.org/Guidelines/Coding_conventions#Code_formatting +BasedOnStyle: GNU +AlwaysBreakAfterDefinitionReturnType: All +BreakBeforeBinaryOperators: None +BinPackParameters: false +SpaceAfterCStyleCast: true +# Our column limit is actually 80, but setting that results in clang-format +# making a lot of dubious hanging-indent choices; disable it and assume the +# developer will line wrap appropriately. clang-format will still check +# existing hanging indents. +ColumnLimit: 0 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0f091e7..8318ab0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,5 @@ stages: + - style-check - build - coverage - analysis @@ -9,19 +10,53 @@ cache: - _ccache/ variables: + FEDORA_IMAGE: "registry.gitlab.gnome.org/gnome/glib/fedora:v8" + DEBIAN_IMAGE: "registry.gitlab.gnome.org/gnome/glib/debian-stable:v6" + ANDROID_IMAGE: "registry.gitlab.gnome.org/gnome/glib/android-ndk:v2" + MINGW_IMAGE: "registry.gitlab.gnome.org/gnome/glib/mingw:v2" MESON_TEST_TIMEOUT_MULTIPLIER: 2 G_MESSAGES_DEBUG: all - MESON_COMMON_OPTIONS: "--buildtype debug --fatal-meson-warnings" + MESON_COMMON_OPTIONS_NO_WARNING: "--buildtype debug --wrap-mode=nodownload" + MESON_COMMON_OPTIONS: "${MESON_COMMON_OPTIONS_NO_WARNING} --fatal-meson-warnings" -fedora-x86_64: - image: registry.gitlab.gnome.org/gnome/glib/fedora:v3 - stage: build +.only-default: + only: + - branches except: - tags + +.build: + extends: .only-default + before_script: + - cp -r $HOME/subprojects/* subprojects/ + +style-check-diff: + extends: .only-default + image: $DEBIAN_IMAGE + stage: style-check + allow_failure: true + script: + - .gitlab-ci/run-style-check-diff.sh + +check-todos: + extends: .only-default + image: $DEBIAN_IMAGE + stage: style-check + allow_failure: true + script: + - .gitlab-ci/run-check-todos.sh + +fedora-x86_64: + extends: .build + image: $FEDORA_IMAGE + stage: build variables: CFLAGS: "-coverage -ftest-coverage -fprofile-arcs" script: - - meson ${MESON_COMMON_OPTIONS} + # FIXME: Cannot use MESON_COMMON_OPTIONS here because meson warns about gtkdoc + # feature introduced in 0.52 but we only depends on 0.49. So we cannot build + # with --fatal-meson-warnings. + - meson ${MESON_COMMON_OPTIONS_NO_WARNING} --werror --default-library=both --prefix=$HOME/glib-installed @@ -30,18 +65,13 @@ fedora-x86_64: -Ddtrace=true -Dfam=true -Dinstalled_tests=true + -Dgtk_doc=true _build - ninja -C _build - mkdir -p _coverage - lcov --config-file .gitlab-ci/lcovrc --directory _build --capture --initial --output-file "_coverage/${CI_JOB_NAME}-baseline.lcov" - .gitlab-ci/run-tests.sh - lcov --config-file .gitlab-ci/lcovrc --directory _build --capture --output-file "_coverage/${CI_JOB_NAME}.lcov" - # FIXME: We should run all installed tests, but do only this one for now - # because it cannot run uninstalled. Reconfigure with dtrace disabled - # because it breaks static link. - - meson configure -Ddtrace=false _build - - ninja -C _build install - - GLIB_TEST_COMPILATION=1 $HOME/glib-installed/libexec/installed-tests/glib/static-link.py $HOME/glib-installed/lib/pkgconfig artifacts: reports: junit: "_build/${CI_JOB_NAME}-report.xml" @@ -51,14 +81,22 @@ fedora-x86_64: - "_build/config.h" - "_build/glib/glibconfig.h" - "_build/meson-logs" + - "_build/docs/reference/glib/glib-undeclared.txt" + - "_build/docs/reference/glib/glib-undocumented.txt" + - "_build/docs/reference/glib/glib-unused.txt" + - "_build/docs/reference/gobject/gobject-undeclared.txt" + - "_build/docs/reference/gobject/gobject-undocumented.txt" + - "_build/docs/reference/gobject/gobject-unused.txt" + - "_build/docs/reference/gio/gio-undeclared.txt" + - "_build/docs/reference/gio/gio-undocumented.txt" + - "_build/docs/reference/gio/gio-unused.txt" - "_build/${CI_JOB_NAME}-report.xml" - "_coverage" debian-stable-x86_64: - image: registry.gitlab.gnome.org/gnome/glib/debian-stable:v3 + extends: .build + image: $DEBIAN_IMAGE stage: build - except: - - tags script: - meson ${MESON_COMMON_OPTIONS} --werror @@ -83,11 +121,62 @@ debian-stable-x86_64: - "_build/meson-logs" - "_build/${CI_JOB_NAME}-report.xml" +installed-tests: + extends: .build + image: $FEDORA_IMAGE + stage: build + script: + # dtrace is disabled because it breaks the static-link.py test + - meson ${MESON_COMMON_OPTIONS} + --werror + --prefix=/usr --libdir=/usr/lib64 + -Dfam=true + -Dinstalled_tests=true + -Ddefault_library=both + -Ddtrace=false + _build + - ninja -C _build + - sudo ninja -C _build install + - sudo chown -R `id -un`:`id -gn` _build/ + # FIXME Install newer xdg-desktop-portal with + # GMemoryMonitor support, see: + # https://github.com/flatpak/xdg-desktop-portal/pull/365 + - git clone --single-branch https://github.com/flatpak/xdg-desktop-portal.git + - cd xdg-desktop-portal + - git reset --hard 1.6.0 + - ./autogen.sh --prefix=/usr --libdir=/usr/lib64 --disable-libportal --disable-dependency-tracking + - make + - sudo make install + - cd .. + # FIXME install newer gobject-introspection + # with GMemoryMonitor support, see: + # https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/193 + - git clone --single-branch https://gitlab.gnome.org/GNOME/gobject-introspection.git + - cd gobject-introspection + - /usr/bin/meson _build --prefix=/usr --libdir=/usr/lib64 + - ninja -C _build + - sudo ninja -C _build install + - cd .. + # Work-around https://gitlab.gnome.org/GNOME/gnome-desktop-testing/merge_requests/2 + - mkdir -p _build/installed-tests-report/logs/ + - GLIB_TEST_COMPILATION=1 gnome-desktop-testing-runner + --report-directory=_build/installed-tests-report/failed/ + --log-directory=_build/installed-tests-report/logs/ + --parallel=0 + glib + artifacts: + name: "glib-${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" + when: always + paths: + - "_build/config.h" + - "_build/glib/glibconfig.h" + - "_build/meson-logs" + - "_build/installed-tests-report/" + G_DISABLE_ASSERT: - image: registry.gitlab.gnome.org/gnome/glib/fedora:v3 + extends: .build + image: $FEDORA_IMAGE stage: build - except: - - tags variables: CPPFLAGS: "-DG_DISABLE_ASSERT" script: @@ -112,10 +201,9 @@ G_DISABLE_ASSERT: - "_build/${CI_JOB_NAME}-report.xml" valgrind: - image: registry.gitlab.gnome.org/gnome/glib/fedora:v3 + extends: .build + image: $FEDORA_IMAGE stage: analysis - except: - - tags variables: MESON_TEST_TIMEOUT_MULTIPLIER: 10 script: @@ -146,9 +234,8 @@ valgrind: - "_build/meson-logs" .cross-template: &cross-template + extends: .build stage: build - except: - - tags artifacts: name: "glib-${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" when: always @@ -157,7 +244,7 @@ valgrind: cross-android_api21_arm64: <<: *cross-template - image: registry.gitlab.gnome.org/gnome/glib/android-ndk:v1 + image: $ANDROID_IMAGE script: # FIXME: add --werror # We use -Diconv=auto to test that we successfully detect that iconv is not @@ -167,7 +254,7 @@ cross-android_api21_arm64: cross-android_api28_arm64: <<: *cross-template - image: registry.gitlab.gnome.org/gnome/glib/android-ndk:v1 + image: $ANDROID_IMAGE script: # FIXME: add --werror - meson ${MESON_COMMON_OPTIONS} --cross-file=/opt/cross_file_android_arm64_28.txt -Dinternal_pcre=true _build @@ -175,18 +262,17 @@ cross-android_api28_arm64: cross-mingw64: <<: *cross-template - image: registry.gitlab.gnome.org/gnome/glib/mingw:v1 + image: $MINGW_IMAGE script: # FIXME: Add --werror - meson ${MESON_COMMON_OPTIONS} --cross-file=/opt/cross_file_mingw64.txt _build - ninja -C _build msys2-mingw32: + extends: .only-default stage: build - except: - - tags tags: - - win32 + - win32-ps variables: MSYSTEM: "MINGW32" CHERE_INVOKING: "yes" @@ -195,30 +281,29 @@ msys2-mingw32: - C:\msys64\usr\bin\bash -lc "bash -x ./.gitlab-ci/test-msys2.sh" artifacts: reports: - junit: "_build/%CI_JOB_NAME%-report.xml" - name: "glib-%CI_JOB_NAME%-%CI_COMMIT_REF_NAME%" + junit: "_build/${env:CI_JOB_NAME}-report.xml" + name: "glib-${env:CI_JOB_NAME}-${env:CI_COMMIT_REF_NAME}" when: always paths: - _build/meson-logs - - "_build/%CI_JOB_NAME%-report.xml" + - "_build/${env:CI_JOB_NAME}-report.xml" - _coverage/ vs2017-x64: + extends: .only-default stage: build - except: - - tags tags: - - win32 + - win32-ps script: - .gitlab-ci/test-msvc.bat artifacts: reports: - junit: "_build/%CI_JOB_NAME%-report.xml" - name: "glib-%CI_JOB_NAME%-%CI_COMMIT_REF_NAME%" + junit: "_build/${env:CI_JOB_NAME}-report.xml" + name: "glib-${env:CI_JOB_NAME}-${env:CI_COMMIT_REF_NAME}" when: always paths: - _build/meson-logs - - "_build/%CI_JOB_NAME%-report.xml" + - "_build/${env:CI_JOB_NAME}-report.xml" freebsd-11-x86_64: stage: build @@ -248,8 +333,6 @@ freebsd-11-x86_64: - meson ${MESON_COMMON_OPTIONS} -Db_lundef=false -Diconv=external -Dxattr=false _build - ninja -C _build - bash -x ./.gitlab-ci/run-tests.sh - except: - - tags artifacts: reports: junit: "_build/${CI_JOB_NAME}-report.xml" @@ -288,11 +371,45 @@ freebsd-12-x86_64: - "_build/meson-logs" - "_build/${CI_JOB_NAME}-report.xml" +macos: + extends: .only-default + stage: build + only: + - branches@GNOME/glib + tags: + - macos + before_script: + - pip3 install --user meson==0.49.2 + - pip3 install --user ninja + - export PATH=/Users/gitlabrunner/Library/Python/3.7/bin:$PATH + script: + # FIXME: Add --werror + # FIXME: Use --wrap-mode=default so we download dependencies each time, + # until the macOS runner is a VM where we can use a pre-made image which + # already contains the dependencies. See: + # - https://gitlab.gnome.org/GNOME/glib/merge_requests/388 + # - https://gitlab.gnome.org/Infrastructure/Infrastructure/issues/225 + - meson ${MESON_COMMON_OPTIONS} + --wrap-mode=default + _build + - ninja -C _build + # FIXME: Multiple unit tests currently fails + - .gitlab-ci/run-tests.sh || true + artifacts: + reports: + junit: "_build/${CI_JOB_NAME}-report.xml" + name: "glib-${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" + when: always + paths: + - "_build/config.h" + - "_build/glib/glibconfig.h" + - "_build/meson-logs" + - "_build/${CI_JOB_NAME}-report.xml" + coverage: - image: registry.gitlab.gnome.org/gnome/glib/fedora:v3 + extends: .only-default + image: $FEDORA_IMAGE stage: coverage - except: - - tags artifacts: name: "glib-${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" paths: @@ -302,10 +419,9 @@ coverage: coverage: '/^\s+lines\.+:\s+([\d.]+\%)\s+/' scan-build: - image: registry.gitlab.gnome.org/gnome/glib/fedora:v3 + extends: .build + image: $FEDORA_IMAGE stage: analysis - except: - - tags script: - meson ${MESON_COMMON_OPTIONS} --werror @@ -335,12 +451,14 @@ pages: - public dist-job: - image: registry.gitlab.gnome.org/gnome/glib/fedora:v3 + image: $FEDORA_IMAGE stage: build only: - tags script: - - meson --buildtype release --fatal-meson-warnings -Dgtk_doc=true -Dman=true _build + # FIXME: Cannot use --fatal-meson-warnings here because meson warns about gtkdoc + # feature introduced in 0.52 but we only depends on 0.49. + - meson ${MESON_COMMON_OPTIONS_NO_WARNING} --buildtype release -Dgtk_doc=true -Dman=true _build - cd _build - ninja dist - ninja glib-doc gobject-doc gio-doc diff --git a/.gitlab-ci/README.md b/.gitlab-ci/README.md index dc6c821..6c12879 100644 --- a/.gitlab-ci/README.md +++ b/.gitlab-ci/README.md @@ -6,10 +6,11 @@ GitLab CI jobs run in a Docker image, defined here. To update that image (perhaps to install some more packages): 1. Edit `.gitlab-ci/Dockerfile` with the changes you want -1. Run `.gitlab-ci/run-docker.sh build --base=debian --base-version=1` to build - the new image (bump the version as needed) -1. Run `.gitlab-ci/run-docker.sh push --base=debian --base-version=1` to upload - the new image to the GNOME GitLab Docker registry +1. Run `.gitlab-ci/run-docker.sh build --base=debian-stable --base-version=1` to + build the new image (bump the version from the latest listed for that `base` + on https://gitlab.gnome.org/GNOME/glib/container_registry) +1. Run `.gitlab-ci/run-docker.sh push --base=debian-stable --base-version=1` to + upload the new image to the GNOME GitLab Docker registry * If this is the first time you're doing this, you'll need to log into the registry * If you use 2-factor authentication on your GNOME GitLab account, you'll diff --git a/.gitlab-ci/android-download-ndk.sh b/.gitlab-ci/android-download-ndk.sh index 785ee01..7739e39 100755 --- a/.gitlab-ci/android-download-ndk.sh +++ b/.gitlab-ci/android-download-ndk.sh @@ -24,8 +24,8 @@ set -e # Download Android NDK ANDROID_NDK_VERSION="r17b" ANDROID_NDK_SHA512="062fac12f747730f5563995089a8b4abab683fbbc621aa8582fdf35fe327daee5d69ed2437af257c10ec4ef54ecd3805a8f134a1400eb8f34ee76f55c8dc9ae9" -wget --quiet https://dl.google.com/android/repository/android-ndk-$ANDROID_NDK_VERSION-linux-x86_64.zip -echo "$ANDROID_NDK_SHA512 android-ndk-$ANDROID_NDK_VERSION-linux-x86_64.zip" | sha512sum -c -unzip android-ndk-$ANDROID_NDK_VERSION-linux-x86_64.zip -rm android-ndk-$ANDROID_NDK_VERSION-linux-x86_64.zip -mv android-ndk-$ANDROID_NDK_VERSION $ANDROID_NDK_PATH +wget --quiet "https://dl.google.com/android/repository/android-ndk-${ANDROID_NDK_VERSION}-linux-x86_64.zip" +echo "${ANDROID_NDK_SHA512} android-ndk-${ANDROID_NDK_VERSION}-linux-x86_64.zip" | sha512sum -c +unzip "android-ndk-${ANDROID_NDK_VERSION}-linux-x86_64.zip" +rm "android-ndk-${ANDROID_NDK_VERSION}-linux-x86_64.zip" +mv "android-ndk-${ANDROID_NDK_VERSION}" "${ANDROID_NDK_PATH}" diff --git a/.gitlab-ci/android-ndk.Dockerfile b/.gitlab-ci/android-ndk.Dockerfile index 9d85a9d..56b186c 100644 --- a/.gitlab-ci/android-ndk.Dockerfile +++ b/.gitlab-ci/android-ndk.Dockerfile @@ -72,4 +72,7 @@ RUN useradd -u $HOST_USER_ID -ms /bin/bash user USER user WORKDIR /home/user +COPY cache-subprojects.sh . +RUN ./cache-subprojects.sh + ENV LANG C.UTF-8 diff --git a/.gitlab-ci/android-setup-env.sh b/.gitlab-ci/android-setup-env.sh index 510056f..bdbb958 100755 --- a/.gitlab-ci/android-setup-env.sh +++ b/.gitlab-ci/android-setup-env.sh @@ -27,7 +27,7 @@ toolchain_path=$(pwd)/android-toolchain-$arch-$api prefix_path=$(pwd)/android-$arch-$api # Create standalone toolchains -$ANDROID_NDK_PATH/build/tools/make_standalone_toolchain.py --arch $arch --api $api --install-dir $toolchain_path +"${ANDROID_NDK_PATH}/build/tools/make_standalone_toolchain.py" --arch "${arch}" --api "${api}" --install-dir "${toolchain_path}" target_host=aarch64-linux-android export AR=$target_host-ar @@ -45,7 +45,7 @@ if [ "$api" -lt "28" ]; then echo "1233fe3ca09341b53354fd4bfe342a7589181145a1232c9919583a8c9979636855839049f3406f253a9d9829908816bb71fd6d34dd544ba290d6f04251376b1a libiconv-1.15.tar.gz" | sha512sum -c tar xzf libiconv-1.15.tar.gz pushd libiconv-1.15 - ./configure --host=$target_host --prefix=$prefix_path --libdir=$prefix_path/lib64 + ./configure --host="${target_host}" --prefix="${prefix_path}" --libdir="${prefix_path}/lib64" make make install popd @@ -58,7 +58,7 @@ wget --quiet https://github.com/libffi/libffi/releases/download/v3.3-rc0/libffi- echo "e6e695d32cd6eb7d65983f32986fccdfc786a593d2ea18af30ce741f58cfa1eb264b1a8d09df5084cb916001aea15187b005c2149a0620a44397a4453b6137d4 libffi-3.3-rc0.tar.gz" | sha512sum -c tar xzf libffi-3.3-rc0.tar.gz pushd libffi-3.3-rc0 -./configure --host=$target_host --prefix=$prefix_path --libdir=$prefix_path/lib64 +./configure --host="${target_host}" --prefix="${prefix_path}" --libdir="${prefix_path}/lib64" make make install popd @@ -66,9 +66,9 @@ rm libffi-3.3-rc0.tar.gz rm -r libffi-3.3-rc0 # Create a pkg-config wrapper that won't pick fedora libraries -mkdir -p $prefix_path/bin +mkdir -p "${prefix_path}/bin" export PKG_CONFIG=$prefix_path/bin/pkg-config -cat > $PKG_CONFIG <<- EOM +cat > "${PKG_CONFIG}" <<- EOM #!/bin/sh SYSROOT=${prefix_path} export PKG_CONFIG_DIR= @@ -76,14 +76,14 @@ export PKG_CONFIG_LIBDIR=\${SYSROOT}/lib64/pkgconfig export PKG_CONFIG_SYSROOT_DIR=\${SYSROOT} exec pkg-config "\$@" EOM -chmod +x $PKG_CONFIG +chmod +x "${PKG_CONFIG}" # Create a cross file that can be passed to meson -cat > cross_file_android_${arch}_${api}.txt <<- EOM +cat > "cross_file_android_${arch}_${api}.txt" <<- EOM [host_machine] system = 'android' -cpu_family = 'arm64' -cpu = 'arm64' +cpu_family = 'aarch64' +cpu = 'aarch64' endian = 'little' [properties] @@ -95,6 +95,7 @@ c_link_args = ['-L${prefix_path}/lib64', c = '${toolchain_path}/bin/${CC}' cpp = '${toolchain_path}/bin/${CXX}' ar = '${toolchain_path}/bin/${AR}' +ld = '${toolchain_path}/bin/${LD}' strip = '${toolchain_path}/bin/${STRIP}' pkgconfig = '${PKG_CONFIG}' EOM diff --git a/.gitlab-ci/cache-subprojects.sh b/.gitlab-ci/cache-subprojects.sh new file mode 100755 index 0000000..421631e --- /dev/null +++ b/.gitlab-ci/cache-subprojects.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +git clone https://gitlab.gnome.org/GNOME/glib.git +meson subprojects download --sourcedir glib +rm glib/subprojects/*.wrap +mv glib/subprojects/ . +rm -rf glib diff --git a/.gitlab-ci/check-todos.py b/.gitlab-ci/check-todos.py new file mode 100755 index 0000000..83b3eee --- /dev/null +++ b/.gitlab-ci/check-todos.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# +# Copyright © 2019 Endless Mobile, Inc. +# +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Original author: Philip Withnall + +""" +Checks that a merge request doesn’t add any instances of the string ‘todo’ +(in uppercase), or similar keywords. It may remove instances of that keyword, +or move them around, according to the logic of `git log -S`. +""" + +import argparse +import re +import subprocess +import sys + + +# We have to specify these keywords obscurely to avoid the script matching +# itself. The keyword ‘fixme’ (in upper case) is explicitly allowed because +# that’s conventionally used as a way of marking a workaround which needs to +# be merged for now, but is to be grepped for and reverted or reworked later. +BANNED_KEYWORDS = [ + 'TO' + 'DO', + 'X' + 'XX', + 'W' + 'IP', +] + + +def main(): + parser = argparse.ArgumentParser( + description='Check a range of commits to ensure they don’t contain ' + 'banned keywords.') + parser.add_argument('commits', + help='SHA to diff from, or range of commits to diff') + args = parser.parse_args() + + banned_words_seen = set() + seen_in_log = False + seen_in_diff = False + + # Check the log messages for banned words. + log_process = subprocess.run( + ['git', 'log', '--no-color', args.commits + '..HEAD'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', + check=True) + log_lines = log_process.stdout.strip().split('\n') + + for line in log_lines: + for keyword in BANNED_KEYWORDS: + if re.search('(^|\W+){}(\W+|$)'.format(keyword), line): + banned_words_seen.add(keyword) + seen_in_log = True + + # Check the diff for banned words. + diff_process = subprocess.run( + ['git', 'diff', '-U0', '--no-color', args.commits], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', + check=True) + diff_lines = diff_process.stdout.strip().split('\n') + + for line in diff_lines: + if not line.startswith('+ '): + continue + + for keyword in BANNED_KEYWORDS: + if re.search('(^|\W+){}(\W+|$)'.format(keyword), line): + banned_words_seen.add(keyword) + seen_in_diff = True + + if banned_words_seen: + if seen_in_log and seen_in_diff: + where = 'commit message and diff' + elif seen_in_log: + where = 'commit message' + elif seen_in_diff: + where = 'commit diff' + + print('Saw banned keywords in a {}: {}. ' + 'This indicates the branch is a work in progress and should not ' + 'be merged in its current ' + 'form.'.format(where, ', '.join(banned_words_seen))) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/.gitlab-ci/coverage-docker.sh b/.gitlab-ci/coverage-docker.sh index b6421be..bf79561 100755 --- a/.gitlab-ci/coverage-docker.sh +++ b/.gitlab-ci/coverage-docker.sh @@ -19,7 +19,7 @@ genhtml \ -o _coverage/coverage cd _coverage -rm -f *.lcov +rm -f ./*.lcov cat >index.html < diff --git a/.gitlab-ci/cross_file_mingw64.txt b/.gitlab-ci/cross_file_mingw64.txt index 1029b6d..7c9176b 100644 --- a/.gitlab-ci/cross_file_mingw64.txt +++ b/.gitlab-ci/cross_file_mingw64.txt @@ -12,6 +12,7 @@ c_link_args = [] c = 'x86_64-w64-mingw32-gcc' cpp = 'x86_64-w64-mingw32-g++' ar = 'x86_64-w64-mingw32-ar' +ld = 'x86_64-w64-mingw32-ld' objcopy = 'x86_64-w64-mingw32-objcopy' strip = 'x86_64-w64-mingw32-strip' pkgconfig = 'x86_64-w64-mingw32-pkg-config' diff --git a/.gitlab-ci/debian-stable.Dockerfile b/.gitlab-ci/debian-stable.Dockerfile index faaaae0..54d2d8c 100644 --- a/.gitlab-ci/debian-stable.Dockerfile +++ b/.gitlab-ci/debian-stable.Dockerfile @@ -4,6 +4,7 @@ RUN apt-get update -qq && apt-get install --no-install-recommends -qq -y \ bindfs \ clang \ clang-tools-7 \ + clang-format-7 \ dbus \ desktop-file-utils \ elfutils \ @@ -18,6 +19,7 @@ RUN apt-get update -qq && apt-get install --no-install-recommends -qq -y \ itstool \ lcov \ libattr1-dev \ + libdbus-1-dev \ libelf-dev \ libffi-dev \ libgamin-dev \ @@ -70,4 +72,7 @@ RUN useradd -u $HOST_USER_ID -ms /bin/bash user USER user WORKDIR /home/user +COPY cache-subprojects.sh . +RUN ./cache-subprojects.sh + ENV LANG=C.UTF-8 LANGUAGE=C.UTF-8 LC_ALL=C.UTF-8 diff --git a/.gitlab-ci/fedora.Dockerfile b/.gitlab-ci/fedora.Dockerfile index 9b01ccf..5c509fd 100644 --- a/.gitlab-ci/fedora.Dockerfile +++ b/.gitlab-ci/fedora.Dockerfile @@ -1,10 +1,12 @@ -FROM fedora:29 +FROM fedora:30 -RUN dnf -y install \ +RUN dnf -y update \ + && dnf -y install \ bindfs \ clang \ clang-analyzer \ dbus-daemon \ + dbus-devel \ desktop-file-utils \ elfutils-libelf-devel \ findutils \ @@ -30,6 +32,7 @@ RUN dnf -y install \ glibc-langpack-pl \ glibc-langpack-ru \ glibc-langpack-tr \ + "gnome-desktop-testing >= 2018.1" \ gtk-doc \ itstool \ lcov \ @@ -41,25 +44,48 @@ RUN dnf -y install \ ncurses-compat-libs \ ninja-build \ pcre-devel \ - python3 \ - python3-pip \ + "python3-dbusmock >= 0.18.3-2" \ + python3-pygments \ python3-wheel \ shared-mime-info \ systemtap-sdt-devel \ unzip \ valgrind \ wget \ + xdg-desktop-portal \ xz \ zlib-devel \ + && dnf -y install \ + meson \ + flex \ + bison \ + python3-devel \ + autoconf \ + automake \ + gettext-devel \ + libtool \ + diffutils \ + fontconfig-devel \ + json-glib-devel \ + geoclue2-devel \ + pipewire-devel \ + fuse-devel \ + make \ && dnf clean all -RUN pip3 install meson==0.49.2 +RUN pip3 install meson==0.52.1 + +# Enable sudo for wheel users +RUN sed -i -e 's/# %wheel/%wheel/' -e '0,/%wheel/{s/%wheel/# %wheel/}' /etc/sudoers ARG HOST_USER_ID=5555 ENV HOST_USER_ID ${HOST_USER_ID} -RUN useradd -u $HOST_USER_ID -ms /bin/bash user +RUN useradd -u $HOST_USER_ID -G wheel -ms /bin/bash user USER user WORKDIR /home/user +COPY cache-subprojects.sh . +RUN ./cache-subprojects.sh + ENV LANG C.UTF-8 diff --git a/.gitlab-ci/mingw.Dockerfile b/.gitlab-ci/mingw.Dockerfile index 19a061a..d89de8c 100644 --- a/.gitlab-ci/mingw.Dockerfile +++ b/.gitlab-ci/mingw.Dockerfile @@ -67,4 +67,7 @@ RUN useradd -u $HOST_USER_ID -ms /bin/bash user USER user WORKDIR /home/user +COPY cache-subprojects.sh . +RUN ./cache-subprojects.sh + ENV LANG C.UTF-8 diff --git a/.gitlab-ci/run-check-todos.sh b/.gitlab-ci/run-check-todos.sh new file mode 100755 index 0000000..97a2ff7 --- /dev/null +++ b/.gitlab-ci/run-check-todos.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +# We need to add a new remote for the upstream master, since this script could +# be running in a personal fork of the repository which has out of date branches. +git remote add upstream https://gitlab.gnome.org/GNOME/glib.git +git fetch upstream + +# Work out the newest common ancestor between the detached HEAD that this CI job +# has checked out, and the upstream target branch (which will typically be +# `upstream/master` or `upstream/glib-2-62`). +# `${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}` is only defined if we’re running in +# a merge request pipeline; fall back to `${CI_DEFAULT_BRANCH}` otherwise. +newest_common_ancestor_sha=$(diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "upstream/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_DEFAULT_BRANCH}}") <(git rev-list --first-parent HEAD) | head -1) +./.gitlab-ci/check-todos.py "${newest_common_ancestor_sha}" diff --git a/.gitlab-ci/run-docker.sh b/.gitlab-ci/run-docker.sh index 2f2693b..9b6fce4 100755 --- a/.gitlab-ci/run-docker.sh +++ b/.gitlab-ci/run-docker.sh @@ -6,17 +6,29 @@ read_arg() { # $3 = arg parameter local rematch='^[^=]*=(.*)$' if [[ $2 =~ $rematch ]]; then - read "$1" <<< "${BASH_REMATCH[1]}" + read -r "$1" <<< "${BASH_REMATCH[1]}" else - read "$1" <<< "$3" + read -r "$1" <<< "$3" # There is no way to shift our callers args, so # return 1 to indicate they should do it instead. return 1 fi } +SUDO_CMD="sudo" +if docker -v |& grep -q podman; then + # Using podman + SUDO_CMD="" + # Docker is actually implemented by podman, and its OCI output + # is incompatible with some of the dockerd instances on GitLab + # CI runners. + export BUILDAH_FORMAT=docker +fi + set -e +base="" +base_version="" build=0 run=0 push=0 @@ -69,7 +81,7 @@ if [ $list == 1 ]; then fi # All commands after this require --base to be set -if [ -z $base ]; then +if [ -z "${base}" ]; then echo "Usage: $0 " exit 1 fi @@ -79,7 +91,7 @@ if [ ! -f "$base.Dockerfile" ]; then exit 1 fi -if [ -z $base_version ]; then +if [ -z "${base_version}" ]; then base_version="latest" else base_version="v$base_version" @@ -89,7 +101,7 @@ TAG="registry.gitlab.gnome.org/gnome/glib/${base}:${base_version}" if [ $build == 1 ]; then echo -e "\e[1;32mBUILDING\e[0m: ${base} as ${TAG}" - sudo docker build \ + $SUDO_CMD docker build \ --build-arg HOST_USER_ID="$UID" \ --tag "${TAG}" \ --file "${base}.Dockerfile" . @@ -100,16 +112,16 @@ if [ $push == 1 ]; then echo -e "\e[1;32mPUSHING\e[0m: ${base} as ${TAG}" if [ $no_login == 0 ]; then - sudo docker login registry.gitlab.gnome.org + $SUDO_CMD docker login registry.gitlab.gnome.org fi - sudo docker push $TAG + $SUDO_CMD docker push $TAG exit $? fi if [ $run == 1 ]; then echo -e "\e[1;32mRUNNING\e[0m: ${base} as ${TAG}" - sudo docker run \ + $SUDO_CMD docker run \ --rm \ --volume "$(pwd)/..:/home/user/app" \ --workdir "/home/user/app" \ diff --git a/.gitlab-ci/run-style-check-diff.sh b/.gitlab-ci/run-style-check-diff.sh new file mode 100755 index 0000000..534698d --- /dev/null +++ b/.gitlab-ci/run-style-check-diff.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -e + +# Wrap everything in a subshell so we can propagate the exit status. +( + +# We need to add a new remote for the upstream master, since this script could +# be running in a personal fork of the repository which has out of date branches. +git remote add upstream https://gitlab.gnome.org/GNOME/glib.git +git fetch upstream + +# Work out the newest common ancestor between the detached HEAD that this CI job +# has checked out, and the upstream target branch (which will typically be +# `upstream/master` or `upstream/glib-2-62`). +# `${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}` is only defined if we’re running in +# a merge request pipeline; fall back to `${CI_DEFAULT_BRANCH}` otherwise. +newest_common_ancestor_sha=$(diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "upstream/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_DEFAULT_BRANCH}}") <(git rev-list --first-parent HEAD) | head -1) +git diff -U0 --no-color "${newest_common_ancestor_sha}" | ./clang-format-diff.py -binary "clang-format-7" -p1 + +) +exit_status=$? + +# The style check is not infallible. The clang-format configuration cannot +# perfectly describe GLib’s coding style: in particular, it cannot align +# function arguments. The documented coding style for GLib takes priority over +# clang-format suggestions. Hopefully we can eventually improve clang-format to +# be configurable enough for our coding style. That’s why this CI check is OK +# to fail: the idea is that people can look through the output and ignore it if +# it’s wrong. (That situation can also happen if someone touches pre-existing +# badly formatted code and it doesn’t make sense to tidy up the wider coding +# style with the changes they’re making.) +echo "" +echo "Note that clang-format output is advisory and cannot always match the GLib coding style, documented at" +echo " https://gitlab.gnome.org/GNOME/gtk/blob/master/docs/CODING-STYLE" +echo "Warnings from this tool can be ignored in favour of the documented coding style," +echo "or in favour of matching the style of existing surrounding code." + +exit ${exit_status} diff --git a/.gitlab-ci/run-tests.sh b/.gitlab-ci/run-tests.sh index 539726e..b545a6f 100755 --- a/.gitlab-ci/run-tests.sh +++ b/.gitlab-ci/run-tests.sh @@ -14,7 +14,7 @@ esac meson test \ -C _build \ - --timeout-multiplier ${MESON_TEST_TIMEOUT_MULTIPLIER} \ + --timeout-multiplier "${MESON_TEST_TIMEOUT_MULTIPLIER}" \ --no-suite flaky \ "$@" diff --git a/.gitlab-ci/test-msvc.bat b/.gitlab-ci/test-msvc.bat index ea1870e..6cc6d80 100644 --- a/.gitlab-ci/test-msvc.bat +++ b/.gitlab-ci/test-msvc.bat @@ -1,7 +1,7 @@ @echo on :: vcvarsall.bat sets various env vars like PATH, INCLUDE, LIB, LIBPATH for the :: specified build architecture -call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 +call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64 @echo on :: FIXME: make warnings fatal diff --git a/.gitlab-ci/test-msys2.sh b/.gitlab-ci/test-msys2.sh index 34953d5..9845b1a 100755 --- a/.gitlab-ci/test-msys2.sh +++ b/.gitlab-ci/test-msys2.sh @@ -27,16 +27,21 @@ pacman --noconfirm -S --needed \ curl -O -J -L "https://github.com/linux-test-project/lcov/releases/download/v1.14/lcov-1.14.tar.gz" echo "14995699187440e0ae4da57fe3a64adc0a3c5cf14feab971f8db38fb7d8f071a lcov-1.14.tar.gz" | sha256sum -c tar -xzf lcov-1.14.tar.gz -LCOV="$(pwd)/lcov-1.14/bin/lcov" +# FIXME: not currently using lcov, see below +#LCOV="$(pwd)/lcov-1.14/bin/lcov" mkdir -p _coverage mkdir -p _ccache -export CCACHE_BASEDIR="$(pwd)" -export CCACHE_DIR="${CCACHE_BASEDIR}/_ccache" +CCACHE_BASEDIR="$(pwd)" +CCACHE_DIR="${CCACHE_BASEDIR}/_ccache" +export CCACHE_BASEDIR CCACHE_DIR + pip3 install --upgrade --user meson==0.49.2 -export PATH="$HOME/.local/bin:$PATH" -export CFLAGS="-coverage -ftest-coverage -fprofile-arcs" + +PATH="$(cygpath "$USERPROFILE")/.local/bin:$HOME/.local/bin:$PATH" +CFLAGS="-coverage -ftest-coverage -fprofile-arcs" DIR="$(pwd)" +export PATH CFLAGS meson --werror --buildtype debug _build cd _build @@ -53,7 +58,7 @@ ninja # --output-file "${DIR}/_coverage/${CI_JOB_NAME}-baseline.lcov" # FIXME: fix the test suite -meson test --timeout-multiplier ${MESON_TEST_TIMEOUT_MULTIPLIER} --no-suite flaky || true +meson test --timeout-multiplier "${MESON_TEST_TIMEOUT_MULTIPLIER}" --no-suite flaky || true python3 "${DIR}"/.gitlab-ci/meson-junit-report.py \ --project-name glib \ diff --git a/NEWS b/NEWS index 44939b6..5d47efe 100644 --- a/NEWS +++ b/NEWS @@ -1,41 +1,531 @@ -Overview of changes in GLib 2.62.3 +Overview of changes in GLib 2.64.5 ================================== -* Use `poll()` in `g_spawn_sync()` rather than `select()`, which is subject to - FD limits (#954) +* Fix deadlock in `g_subprocess_communicate_async()` (work by Alexander Larsson) (#2182) -* Fix undefined behaviour with `g_utf8_find_prev_char()` (#1917) +* Fix cross-compilation on iOS (work by Nirbheek Chauhan) (#1868) * Bugs fixed: + - !1519 Backport !1468 “glib-compile-resources: Fix exporting on Visual Studio” to glib-2-64 + - !1520 Backport !1517 “GWin32RegistryKey: Move assertions” to glib-2-64 + - !1565 Backport !1563 “gdesktopappinfo: Fix unnecessarily copied and leaked URI list” to glib-2-64 + - !1608 Backport !1607 “meson: Don't use gnulib for printf on iOS” to glib-2-64 + - !1618 Backport !1617 “Ensure g_subprocess_communicate_async() never blocks” to glib-2-64 + - !1621 Backport !1620 “gvariant: Ensure GVS.depth is initialised” to glib-2-64 + + +Overview of changes in GLib 2.64.4 +================================== + +* Bugs fixed: + - #2140 calling malloc in fork child is undefined-behaviour + - !1507 Backport !1504 “win32 gpoll: Fix wait for at least one thread to return” to glib-2-64 + - !1523 Backport !1522 “meson: Fix gnulib printf checks” to glib-2-64 + - !1547 Backport !1544 “Resolve "calling malloc in fork child is undefined-behaviour"” to glib-2-64 + +* Translation updates: + - Kazakh + - Slovenian + + +Overview of changes in GLib 2.64.3 +================================== + +* Stability improvements for various unit tests + +* Bugs fixed: + - #1954 gdbus-server-auth intermittent failure + - #2094 Deprecation warnings when compiling with -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_28 -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_28 + - !1470 Backport !1440 -Wformat-nonliteral fixes to glib-2-64 + - !1471 Backport !1448 memory monitor test dependency fixes to glib-2-64 + - !1473 CI: Switch to new Windows runners (2.64) + - !1478 Backport !1477 D-Bus keyring handling fixes to glib-2-64 + - !1483 Backport !1481 “array: fix corrupt state of GPtrArray after g_ptr_array_extend_and_steal()” to glib-2-64 + - !1484 Backport !1480 “CI: Make sure we use meson 0.49.2 in MSYS2” to glib-2-64 + - !1486 Backport !1472 “gthread: ignore deprecated declarations in static inline functions” to glib-2-64 + - !1495 Backport !1493 “meson: Remove stray ], in O_DIRECTORY check” to glib-2-64 + - !1501 Backport !1439 “Fix stpcpy() detection“ to glib-2-64 + +* Translation updates: + - Chinese (Taiwan) + - German + + +Overview of changes in GLib 2.64.2 +================================== + +* Bugs fixed: + - #2067 Glib uses _Static_assert in C++17 mode + - #2081 gdbus error messages contains mixed up body and head signatures + - !1421 Backport !1420 “gmacros.h: avoid using _Static_assert in C++17 mode” to glib-2-64 + - !1438 Backport various patches to glib-2-64 + - !1424 docs: Fix configuration with gtk_doc=true and installed_tests=false + - !1428 Add missing 'extern' to the dllexport version of GLIB_VAR/GOBJECT_VAR + - !1429 Fix arch detection ifdefs in glib/valgrind.h + - !1431 glib-unix.c: fix heap corruption in g_unix_get_passwd_entry + - !1432 docs: Mention new gio tool options + - !1435 gdbusmessage: Fix swapped signatures in error messages + - !1447 Backport !1442 “gfile: Fallback to fast-content-type if content-type is not set” to glib-2-64 + +* Translation updates: + - Hebrew + - Romanian + - Ukrainian + + +Overview of changes in GLib 2.64.1 +================================== + +* Fix memory monitor tests to only be installed if installed-tests are enabled, + and to be skipped if GObject-Introspection is too old (!1407) + +* Bugs fixed: + - #1986 Socks5 Proxy: Authentication seems broken + - #1988 Socks5 Proxy: Wrong error returned when using no authentication + - !1407 Backport various patches from master to glib-2-64 + - !1412 Backport SOCKS5 fixes to glib-2-64 + +* Translation updates: + - Dutch + - Italian + - Serbian + + +Overview of changes in GLib 2.64.0 +================================== + +* Use `posix_spawn()` to speed up launching test D-Bus instances (!1388) + +* Bugs fixed: + - #1783 Document using glib-mkenums with meson + - #2049 Crash in g_array_copy + - !1384 Some minor clang warning fixes + - !1386 docs: Document generated headers caveats for genmarshal + - !1387 ci: Correctly propagate exit status in run-style-check-diff.sh + - !1388 gtestdbus: Use posix_spawn() to spawn dbus-daemon + - !1389 Update GError docs to use G_DEFINE_QUARK + +* Translation updates: + - Czech + - Danish + + +Overview of changes in GLib 2.63.6 +================================== + +* Fix potential relative read when calling g_printerr(), which could lead to a + denial of service from a setuid-root process being used to block access to the + TTY for another user (#1919) + +* Fix SOCKS proxy resolver sometimes not being used when resolving addresses + via Happy Eyeballs (CVE-2020-6750) (#1989) + +* Several other Happy Eyeballs fixes for address resolution (#1871, #1872, #1902) + +* Various race fixes in `GDBusConnection` and its unit tests (#1515) + +* Fix a race condition with D-Bus name ownership (#1517) + +* Drop `gio-launch-desktop` helper application in favour of calling `sh` directly (#1633) + +* Fix win32 exception handling with C# exceptions (#2025) + +* Fix thread safety of `GUnixMountMonitor` (#2030) + +* Additional fixes to new thread pool attribute behaviour from GLib 2.63.4 to + check if sched_setattr() is allowed by system policies before depending on it (#2039, !1356) + +* Fix memory leaks and corruption when freeing `GSource`s while freeing a `GMainContext` (!1353) + +* Drop inappropriate installation of object manager example documentation (!1359) + +* Bugs fixed: + - #938 gdbus call -a doesn't support message bus connections + - #1515 gio/gdbus-threading test sometimes fails in CI + - #1517 g_bus_own_name does not always call name_lost_handler when _REPLACE and _ALLOW_REPLACEMENT are set + - #1633 should not install gio-launch-desktop into PATH + - #1919 read from relative path in g_printerr() in 2.58.3 + - #1995 Tracker issue for Happy Eyeballs regressions + - #2002 g_io_channel_read_line does not honour the line_term symbols set + - #2025 W32 exception handling misbehaves when C# exceptions are thrown when running inside the Visual Studio debugger + - #2030 Random nautilus test suite failures involving GUnixMountMonitor + - #2039 sched_setattr() still can cause EPERM through natural causes + - #2043 Low memory monitor test failed in CI + - #2044 GApplication docs suggest invalid GVariant type + - !1185 gtimezone: Tidy up UTC timezone creation + - !1281 ci: Update Docker packages + - !1283 gmodule: change _g_module_close to only take a handle. + - !1298 tests: Speed up the GIO actions test + - !1299 gapplication: Fix a minor typo in the documentation + - !1339 gsocketclient: Refactor g_socket_client_connect_async() + - !1353 GMainContext - Fix memory leaks and memory corruption when freeing sources while freeing a context + - !1354 GThreadPool - Add test for !1340 + - !1355 glist: Add docs examples of how to combine with g_steal_pointer() + - !1356 GThread - Check if sched_setattr is allowed by the system policies before depending on it + - !1359 docs: Don’t install object manager example separately + - !1363 Make tests pass if we are euid != 0 with capabilities + - !1366 Fix oss-fuzz coverage link + - !1372 gobject: Fix strict aliasing warnings with g_set_object() + - !1376 gitlab-ci: 64-bit ARM is aarch64, not arm64 + - !1381 ghash: Document the iteration order over a hash table is not defined + - !1382 tests: Bump the refcount timeout in gdbus-threading + - !1383 ci: Enable parallelisation when running installed tests + +* Translation updates: + - Basque + - English (United Kingdom) + - French + - Galician + - German + - Greek, Modern (1453-) + - Hungarian + - Indonesian + - Japanese + - Korean + - Lithuanian + - Polish + - Portuguese (Brazil) + - Spanish + - Swedish + - Turkish + + +Overview of changes in GLib 2.63.5 +================================== + +* Fix behaviour of `g_file_move()` fallback code to not follow symlinks (#986) + +* Rename `--glib-min-version` argument of `gdbus-codegen` to `--glib-min-required` + (this is not an API break as `--glib-min-version` was added earlier in the + 2.63 cycle) (#1993) + +* Add gtk-doc checks to CI and fix a number of documentation issues + (thanks to Xavier Claessens) (!978) + +* Add `G_SIZEOF_MEMBER()` macro (!1333) + +* Add a debug message if `g_setenv()` or `g_unsetenv()` are used after any + threads have been spawned — this will be upgraded to a warning in future (!1337, #715) + +* Skip memory monitor tests if xdg-desktop-portal or dbusmock are not available (!1296, !1338) + +* Change the `libmount` configure option from a boolean to a Meson `feature` (!1344) + +* Do not return `target-uri` from `g_file_peek_path()` when called on trash/recent files (!1346) + +* Drop new TLS certificate API for PKCS #11 backed certificates, as the implementation + is not ready yet (this is not an API break as the API was added earlier in the + 2.63 cycle) (!1347) + +* Bugs fixed: + - #986 g_file_move: remove G_FILE_COPY_NOFOLLOW_SYMLINKS section + - #1551 CI: Add checks for `TODO` in MRs + - #1925 Large number of routes installed into kernel cause high cpu usage + - #1993 Rename gdbus-codegen --glib-min-version argument to --glib-min-required and add --glib-max-allowed + - #2012 spawn_thread_queue not initialised in GThreadPool + - #2020 g_network_monitor_base_add_network() improperly unrefs GInetAddressMask + - !978 Various fixes to make gtkdoc-check pass on glib + - !1018 docs: tag enclose 'all' and 'help' values + - !1170 Avoid C++20 deprecated assignment to volatile + - !1296 tests: Skip GMemoryMonitor tests if the dbusmock template is not available + - !1307 Remove global declaration of GMemoryMonitor + - !1322 gmain: Mark G_SOURCE_FUNC as available in 2.58 + - !1333 Add and use G_SIZEOF_MEMBER() macro + - !1337 genviron: Message if g_setenv()/g_unsetenv() are used after threads spawned + - !1338 tests: Skip GMemoryMonitor tests if xdg-desktop-portal is not available + - !1344 meson: libmount autodectection + - !1345 gio-tool-info: Print unix mount information where available + - !1346 gfile: Do not return target-uri from g_file_peek_path() + - !1347 Revert "gtlscertificate: Add support for PKCS #11 backed certificates" + - !1348 ghash: Clarify that g_hash_table_add() always consumes the key + - !1349 doc: Clarify that _locker_new() does not actually allocate memory + - !1351 glib.supp: update g-threaded-resolver-getaddrinfo-config + +* Translation updates: + - Japanese + - Lithuanian + - Malay + - Portuguese (Brazil) + - Swedish + + +Overview of changes in GLib 2.63.4 +================================== + +* Fix various race conditions on signal emission in GDBus (#604, #978, #1232) + +* Change thread pools so that thread attributes (in particular, priority) are + inherited from the thread which created the `GThreadPool` initially, rather + than from the thread which is pushing a new job into the pool (#1834, #2007) + +* Expand support for running Windows apps with + `g_app_info_launch_default_for_uri()` using rundll32 on Windows (#1932) + +* Support multiple directories in `GSETTINGS_SCHEMA_DIR` environment variable (#1998) + +* Support full Julian day range in `TZ` environment variable (#1999) + +* Apply recursion depth limits to variants in D-Bus messages (!1201) + +* Support adding call flags and timeouts to method calls generated by + `gdbus-codegen` through the new `--glib-min-version` option (!1286) + +* Fully deprecate TLS rehandshakes; they are now ignored due to TLS protocol + changes (!1305) + +* Bugs fixed: + - #198 g_fopen and friends: should also state how to close a stream + - #604 GDBus name watching dispatch is buggy/excessively-complicated + - #833 g_object_set: document the need to cast varargs + - #978 SIGSEGV in on_name_lost_or_acquired + - #1232 Insufficient thread safety around GDBusObjectManagerClient + - #1416 Re-add macOS CI + - #1834 Unwanted priority/etc inheritance with GThreadPool and GThread and the POSIX implementation + - #1932 Windows: Gio.AppInfo.launch_default_for_uri seems not to work for local files/folders + - #1983 glib:gio / dbus-appinfo test fails: GLib-GIO:ERROR:../../../../Projects/glib/gio/tests/dbus-appinfo.c:326:on_flatpak_open: 'g_file_equal (files[0], f)' should be TRUE + - #1997 Base64 encoding with "break_lines" claims to wrap at 72 characters but seems to wrap at 76 + - #1998 support multiple directories in GSETTINGS_SCHEMA_DIR + - #1999 GTimeZone fails to accept full Julian day range when parsing the direct $TZ string format + - #2007 Thread scheduler attributes fail under valgrind + - !388 ci: Avoid downloading subprojects for each job + - !1111 gio: test that launch_uris() exports files with the document portal when launching a flatpak + - !1201 gdbusmessage: Limit recursion of variants in D-Bus messages + - !1279 tests: Fix an error message set by foo_set_property() + - !1286 gdbus-codegen: Add a GDBusCallFlags arg to method calls + - !1291 gio-tool-list: Add an option to print display names + - !1294 GMemoryMonitor docs fixes + - !1295 gio: Fix socket test + - !1301 GThread - Inherit parent thread priority by default for new Win32 threads + - !1303 gvariant-core: Don't pass NULL second argument to memcpy + - !1305 Fully deprecate TLS rehandshakes + - !1308 gsocketclient: run timeout source on the task's main context + - !1309 Fix crash in gutils when application is prevented access to passwd file + - !1317 gfdonotificationbackend: remove notifications when bus name vanishes + - !1320 subprojects: Temporarily avoid using wrapdb while it’s down + - !1324 ci: Add some documentation to the style check CI test + - !1325 Check for SYS_sched_getattr before using it unconditionally + - !1330 W32: Correctly set st_ino when doing private stat() + - !1334 gthread: Ensure GThreadSchedulerSettings is always defined + +* Translation updates: + - Catalan + - Galician + - Hungarian + - Indonesian + - Polish + - Portuguese (Brazil) + - Spanish + + +Overview of changes in GLib 2.63.3 +================================== + +* Add a `--glib-min-version` argument to `gdbus-codegen` which controls breaks in the API of generated code (#1726) + +* Add `g_clear_list()` API to clear `GList`s to `NULL` (#1943) + +* Add a `GMemoryMonitor` API to be notified of memory pressure situations using the low-memory-monitor project (!1005) + +* Add support for dispose functions for `GSource` implementations (!1175) + +* Tighten up validation of GObject signal and property names, allowing performance improvements (!1224) + +* Fix installation path of GIO modules on MSVC to be the bindir (!1254) + +* Bugs fixed: + - #650 g_signal_lookup gives too many warnings + - #1011 GListStore, easily find if the item is already inserted + - #1130 gdbus-codegen: Add an option to strictly generate markdown in source comments + - #1687 glocalvfs.c uses non-thread-safe getpwnam() + - #1726 Warn when method/signal uses type 'h' but lacks GDBus.C.UnixFD annotation + - #1935 Assert in _kqsub_free seems to be too strict + - #1943 Consider g_clear_list() + - #1947 Documentation clarification for g_uuid_string_random() + - #1953 Documentation for g_type_init() and others missing from online gtk-doc documentation + - #1961 A typo in the comment of `g_settings_schema_get_path`: threfore -> therefore + - !1005 gio: Add GMemoryMonitor to monitor for low-memory + - !1172 gptrarray: Add an example to the g_ptr_array_steal() docs + - !1175 Implement a dispose function for GSource + - !1223 Add additional valgrind suppressions + - !1224 Signal name handling improvements + - !1230 ci: Run installed-tests on Fedora + - !1235 Add sudo to Fedora docker image + - !1239 tests: Run "timeout tests" sequentially + - !1248 ci: Update all Debian CI runners to use v5 of the Dockerfile + - !1249 gio-tool-mount: Allow mounting by the given UUID + - !1252 ci: Fix running all jobs on merge requests + - !1253 gthread: Fix "zero as null pointer" warning + - !1254 giomodule: gio modules are no longer installed in bindir on MSVC + - !1255 ci: Build Docker images rather than OCI images if using podman + - !1256 gdate: Add autoptr support + - !1258 Minor CI fixups + - !1261 gtk-doc: Ensure we have recent enough version + - !1262 tests: Add tests for the gdbus-codegen executable + - !1265 build: don't check for protected visibility + - !1267 Revert "doc: Workaround gtkdoc-scan bug leading to undocumented symbols" + - !1268 ci: Work-around successful installed tests having no logs + - !1269 gvariant: Add guard to g_variant_get() + - !1271 tests: Enable GDBus debug for a number of unreliable tests + - !1274 trash portal: Don't follow symlinks + - !1275 Small doc correction + - !1277 Various gtk-doc improvements + - !1278 Update installed tests CI + - !1280 clang-format-diff: Output diff for multiple files, not just one + - !1282 Revert "Revert "docs: remove GDBusObjectManager example"" + - !1284 Update POTFILES.in 191212 + - !1287 atomic/tests: test g_atomic_pointer_compare_and_exchange() with const pointers + - !1289 gtype: Define auto-cleanup functions for Module class + +* Translation updates: + - Spanish + + +Overview of changes in GLib 2.63.2 +================================== + +* Use `lldb` rather than `gdb` on macOS for debugging (#1004) + +* Switch the atomic builtins from `__sync_fetch_*()` to the slightly more modern + `__atomic_*()` (#1750) + +* Fix calculation of `gsize` width on various platforms (including OpenBSD) (#1777) + +* Fix undefined behaviour causing brokenness in `g_utf8_find_prev_char()` when + compiling with GCC ≥ 8 (#1917) + +* Revert UNIX mode changes in `G_FILE_ATTRIBUTE_ID_UNIX_MODE` which broke OSTree (#1934) + +* Slightly improve performance for signal emissions when no handlers are connected (!1083) + +* Add `g_task_return_value()` and `g_task_propagate_value()` APIs to allow + `GTask` to be used from language bindings more easily (!1216) + +* Fix a file monitoring crash on kqueue-based systems (BSD) (!1221) + +* Bugs fixed: + - #1004 [PATCH] Make gbacktrace use lldb on Mac OS X + - #1552 CI: Add code style checks + - #1750 Switch from __sync_fetch_*() to __atomic_*() in glib/gatomic.h + - #1777 gsize: improper typedef on (at least) OpenBSD + - #1895 Regression: glib does not compile on centos 6: "objcopy: unrecognized option '--add-symbol'" + - #1917 Test utf8-pointer fails with static build, LTO, optimisations, and new GCC + - #1930 glib/tests/bookmarkfile.c:385:test_modify: assertion failed: (stamp == now) + - #1934 ostree tests broken since bfdc5fc4fc84ef8518d2d1a328c8482cf5a38e98: File '/tmp/test-tmp-libostree_test-basic-user.sh.test-QB4SA0/diff-test2' is not empty + - #1938 GDateTime doesn't support leap seconds + - #1940 atomics test fails on FreeBSD CI since !1123 + - !1039 Improve documentation for footgun function g_tls_client_connection_copy_session_state() + - !1083 Use the GObject hole on 64bit arches for some flags to improve performance + - !1202 CI updates after !1177 + - !1208 gutils: Slightly improve docs formatting for g_get_os_info() + - !1209 Make ld executable configurable + - !1210 gdbus-server-auth test: Include gcredentialsprivate.h + - !1213 gsocket: Improve diagnostics on bind() failure + - !1214 gvariant, gbytes: Avoid memcmp (NULL, ., 0) or memcmp (., NULL, 0) + - !1216 Make GTask more binding-friendly + - !1218 gdb: Fix GHashTable pretty printer off-by-one error + - !1220 gparam: fix memory leak in g_param_value_defaults() + - !1221 Add NOTE_REVOKE to the list of the monitoring events + - !1225 gtlsconnection: clarify handshake() documentation + - !1227 Deprecate old GTlsConnection functionality even harder! + - !1231 Fix build on old libc that does not define _SC_HOST_NAME_MAX + - !1238 gstrfuncs: use gsize type internally for strv functions + - !1242 gfileinfo: Clarify the documentation for G_FILE_ATTRIBUTE_UNIX_MODE + - !1243 docs: Fix "occurred" typos in API documentation + +* Translation updates: + - Spanish + + +Overview of changes in GLib 2.63.1 +================================== + +* Several usability improvements to command line `gio` tool (!1153) + +* Add `g_array_steal()`, `g_ptr_array_steal()` and `g_byte_array_steal()` APIs (#285) + +* Add `g_get_os_info()` API (!1063, !1160) + +* Add `g_warning_once()` API (!1028) + +* Always resolve `localhost` to loopback address in `GResolver` (!616) + +* Add `GMainContextPusher` API (!983) + +* Limit recursion in `g_variant_parse()` (!1173) + +* Fix crash in `g_spawn()` with high FD numbers due to use of `select()` rather + than `poll()` (#954) + +* Allow passing empty `GValue`s to `g_param_value_set_default()` (!1186) + +* Escape header guards generated by `gdbus-codegen` better (#1379) + +* Bugs fixed: + - #285 [PATCH] add array steal and memdup functions - #954 The g_spawn_sync() function uses select() which has limitations - #1318 rare failure in gdbus-peer test: invalid uninstantiatable type '(null)' in cast to 'GDBusServer' + - #1379 gdbus-codegen generates invalid header guards when build directory contains a + character + - #1622 NULL pointer derefs on g_vasprintf() failure + - #1813 g_option_context_add_main_entries() is missing array annotation for entries parameter + - #1831 No reply on private socket due to auth problem + - #1836 gobject.c uses undefined annotation “(not optional)” + - #1858 docs/reference/gobject/tut_gobject.xml: object properties example uses deprecated API + - #1877 g_cancellable_source_new annotated with 'skip' + - #1896 Use after free when calling g_dbus_connection_flush_sync() in a dedicated thread - #1897 glib 2.62.0 fails test 'test_writev_no_vectors' wih gcc7 - #1903 use-after-free in mimeapps test causes intermittent segfault during testing + - #1906 test_os_info fails on FreeBSD - #1916 objcopy not used from cross-compilation file in GIO tests - - #1917 Test utf8-pointer fails with static build, LTO, optimisations, and new GCC - - !1174 Backport !1164 “use-after-free fix in mimeapps test” to glib-2-62 - - !1184 Backport !1173 “gvariant: Limit recursion in g_variant_parse()” to glib-2-62 - - !1194 Backport !1176, !1183, !1188, !1191 to `glib-2-62` - - !1203 Backport !1192, !1193, !1197 Fixes for gdbus-peer tests to glib-2-62 - - !1207 Backport !1206 “goption: Relax assertion to avoid being broken by kdeinit5” to glib-2-62 - - !1215 [2.62] gdbus-peer: Specifically listen on 127.0.0.1 - - !1219 Backport !1218 “gdb: Fix GHashTable pretty printer off-by-one error” - - !1222 Backport !1221 “Add NOTE_REVOKE to the list of the monitoring events” to glib-2-62 - - !1228 Backport !1199 “gunicode: Fix UB in gutf8.c and utf8-pointer test” to glib-2-62 - - -Overview of changes in GLib 2.62.2 -================================== + - #1923 Recent Versions of GLib Break Dolphin File-Manager's Thumbnailing when Using 'gtk2' Style + - !616 Always resolve localhost to loopback address + - !983 gmain: Add GMainContextPusher convenience API + - !1014 tests: Add a test for g_assert_finalize_object() + - !1028 gmessages: Add g_warning_once() + - !1035 Switching from C gnu89 to C gnu99 standard + - !1063 gutils: Add g_get_os_info() + - !1082 gdatetime: Document RFC 3339 extensions when parsing ISO 8601 + - !1105 syscall flood on every time*() function call + - !1120 Update documentation with FreeBSD build instructions + - !1135 gmain: use atomic operation instead of GMutex to access g_main_context_default() + - !1146 Solaris build fixes + - !1147 gmodule: fix typo in doc comment + - !1148 gio/gfileinfo: fix parameter references + - !1149 gio/gfile: fix parameter reference for value_p + - !1150 gio/gfile: fix parameter references to @contents + - !1151 gio/gfile: fix typo in doc comment + - !1152 gwinhttpvfs: Handle g_get_prgname() returning NULL + - !1153 Several gio-tool bash completion fixes and improvements + - !1155 Strict-aliasing fixes to new atomic built-ins + - !1157 Fix various compiler warnings on Android + - !1160 Add Windows support to g_get_os_info() + - !1161 hash: Remove an assertion from the hot path + - !1163 gcharset: Expand the documentation for g_get_locale_variants() + - !1165 Use uname as a fallback to get OS info + - !1167 Fix some minor leaks in testfilemonitor + - !1168 Fix gdatetime tests on toolbox + - !1171 Revert "gdbus-codegen: emit GUnixFDLists if an arg has type 'h'" + - !1173 gvariant: Limit recursion in g_variant_parse() + - !1177 ci: Add libdbus development files to CI Docker images + - !1179 Improve GPtrArray doc-comments + - !1180 array: Avoid use of memcpy(dest, NULL, 0) + - !1181 gmain: Clarify thread safety of some common GSource functions + - !1182 gio: Fix typo in URL + - !1186 Allow using an empty GValue with g_param_value_set_default() + - !1189 gparamspecs: Fix type class leaks on error handling paths + - !1197 Fix GDBus test failures on non-Linux (in particular FreeBSD) + - !1200 Minor fixes from a scan-build run -* Bugs fixed: - - #1896 Use after free when calling g_dbus_connection_flush_sync() in a dedicated thread - - !1154 Backport !1152 “gwinhttpvfs: Handle g_get_prgname() returning NULL” to glib-2-62 - - !1156 Backport !1146 Solaris fixes to glib-2-62 +* Translation updates: + - Catalan + - Chinese (Taiwan) + - Spanish -Overview of changes in GLib 2.62.1 +Overview of changes in GLib 2.63.0 ================================== +* Add g_fsync() API (#35) + * Fix regression in g_file_copy() when passing `G_FILE_COPY_TARGET_DEFAULT_PERMS` flag; the destination permissions would be private rather than following the process’ umask (!1142) @@ -45,22 +535,57 @@ Overview of changes in GLib 2.62.1 * Always build the tests if installed-tests are enabled, so that the tests can actually be installed (!1141) +* Rework atomic function implementations to use memory barrier in the correct + place (when compiler intrinsics aren’t providing the atomics), and fix + signedness issues (#1449, #1565) + +* Use the OS’ `fdwalk()` function (if safe) to speed up `g_spawn_*()` on BSD (#1638) + +* Remove the macOS `dyld` `GModule` implementation in favour of `dl` instead (!1093) + +* Bump Python requirement to ≥ 3.5, which we implicitly relied on anyway through + our Meson dependency (!1132) + * Bugs fixed: + - #35 add g_fsync to API - #174 g_file_copy always preserves permissions, even if G_FILE_COPY_ALL_METADATA flag is not set + - #259 docs: fix a misunderstanding in g_type_add_interface_* + - #767 g_signal_lookup fails if class is not referenced + - #1052 g_io_write_chars calls abort when given a null byte as input + - #1449 glib fallback atomic int/ptr get/set have memory barrier in wrong place + - #1565 Signedness of atomic operations + - #1638 g_spawn_*() is extremely slow under certain circumstances + - #1809 Provide API for working with PKCS11 backed certificates + - #1843 TSAN false positive with g_atomic_pointer_get/g_atomic_pointer_set under Clang - #1865 g_variant_get_data_as_bytes fails after serializing a variant - #1875 Segfault and Overflow in __gio_xdg_cache_mime_type_subclass() with Wps-Office installed - #1887 glib 2.62.0 breaks loading dylibs as modules - #1888 2.62: docs build on Windows broken - - !1021 docs: Remove priv pointers from the tutorial example - - !1094 Backport to 2.62: gmodule: use dl implementation on macOS - - !1101 Backport !1092 “Fix doc build on Windows” to glib-2-62 - - !1102 Backport !1080 “tests: Fix skipping mkdir-with-parents-permission test” to glib-2-62 - - !1103 Backport !1085 “xdgmime: Prevent infinite loops from badly-formed MIME registrations” to glib-2-62 - - !1127 Backport !1125 and !1115 GDateTime parsing fixes to glib-2-62 - - !1128 Backport !1043 “gvariant: Handle empty serialisations in get_child_value()” to glib-2-62 - - !1140 [2.62] g_file_info_get_modification_date_time: Calculate in integer domain - - !1141 [2.62] Always build tests if we enabled installed-tests - - !1142 Backport !1134 Fix for file copy permissions to glib-2-62 + - #1897 glib 2.62.0 fails test 'test_writev_no_vectors' wih gcc7 + - !1020 docs: Remove priv pointers from the tutorial example + - !1062 gtype: mark the inline functions in G_DECLARE_*_TYPE() as UNUSED + - !1080 tests: Fix skipping mkdir-with-parents-permission test + - !1088 Various small scan-build fixes + - !1090 giochannel: Clarify type of GSource callback in documentation + - !1093 gmodule: remove macOS dyld implementation + - !1095 Define G_IOV_MAX to 512 on macOS/iOS + - !1099 gmem: clarify that g_malloc always uses the system allocator + - !1109 doc: fix typo in gio/gsettings.c + - !1110 gio: Add missing "gio remove" option to bash completion script + - !1112 Add version macros for GLib 2.64 + - !1115 gdatetime: Fix error handling in g_date_time_new_ordinal() + - !1116 gmarkup: Add a limit on the number of attributes in an element + - !1119 Annotate the return value of various utility functions + - !1124 docs: Fix typo in GConverter{Input,Output}Stream section titles + - !1125 gdatetime: Fix error handling in g_date_time_new_week() + - !1126 fileinfo: Mention that usec mtimes are set + - !1129 gdate: Fix tautological comparison warnings on Android + - !1130 Improve GLIB_DEPRECATED_MACRO_FOR output + - !1131 gio/gfileinfo: fix param reference in doc comment + - !1132 build: Bump Python requirement to ≥ 3.5 + - !1137 gregistrysettings: bump key name length to 2048 + - !1138 Always build tests if we enabled installed-tests + - !1139 g_file_info_get_modification_date_time: Calculate in integer domain * Translation updates: - Danish diff --git a/clang-format-diff.py b/clang-format-diff.py new file mode 100755 index 0000000..3fb776c --- /dev/null +++ b/clang-format-diff.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# +# === clang-format-diff.py - ClangFormat Diff Reformatter ---*- python -*-=== # +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===---------------------------------------------------------------------=== # + +""" +This script reads input from a unified diff and reformats all the changed +lines. This is useful to reformat all the lines touched by a specific patch. +Example usage for git/svn users: + + git diff -U0 --no-color HEAD^ | clang-format-diff.py -p1 -i + svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i + +""" +from __future__ import absolute_import, division, print_function + +import argparse +import difflib +import re +import subprocess +import sys + +if sys.version_info.major >= 3: + from io import StringIO +else: + from io import BytesIO as StringIO + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('-i', action='store_true', default=False, + help='apply edits to files instead of displaying a ' + 'diff') + parser.add_argument('-p', metavar='NUM', default=0, + help='strip the smallest prefix containing P slashes') + parser.add_argument('-regex', metavar='PATTERN', default=None, + help='custom pattern selecting file paths to reformat ' + '(case sensitive, overrides -iregex)') + parser.add_argument('-iregex', metavar='PATTERN', + default=r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hh|hpp|m|mm|inc' + r'|js|ts|proto|protodevel|java|cs)', + help='custom pattern selecting file paths to reformat ' + '(case insensitive, overridden by -regex)') + parser.add_argument('-sort-includes', action='store_true', default=False, + help='let clang-format sort include blocks') + parser.add_argument('-v', '--verbose', action='store_true', + help='be more verbose, ineffective without -i') + parser.add_argument('-style', + help='formatting style to apply (LLVM, Google, ' + 'Chromium, Mozilla, WebKit)') + parser.add_argument('-binary', default='clang-format', + help='location of binary to use for clang-format') + args = parser.parse_args() + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + match = re.search(r'^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) + if match: + filename = match.group(2) + if filename is None: + continue + + if args.regex is not None: + if not re.match('^%s$' % args.regex, filename): + continue + else: + if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): + continue + + match = re.search(r'^@@.*\+(\d+)(,(\d+))?', line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count == 0: + continue + end_line = start_line + line_count - 1 + lines_by_file.setdefault(filename, []).extend( + ['-lines', str(start_line) + ':' + str(end_line)]) + + # Reformat files containing changes in place. + # We need to count amount of bytes generated in the output of + # clang-format-diff. If clang-format-diff doesn't generate any bytes it + # means there is nothing to format. + format_line_counter = 0 + for filename, lines in lines_by_file.items(): + if args.i and args.verbose: + print('Formatting {}'.format(filename)) + command = [args.binary, filename] + if args.i: + command.append('-i') + if args.sort_includes: + command.append('-sort-includes') + command.extend(lines) + if args.style: + command.extend(['-style', args.style]) + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=None, + stdin=subprocess.PIPE, + universal_newlines=True) + stdout, _ = p.communicate() + if p.returncode != 0: + sys.exit(p.returncode) + + if not args.i: + with open(filename) as f: + code = f.readlines() + formatted_code = StringIO(stdout).readlines() + diff = difflib.unified_diff(code, formatted_code, + filename, filename, + '(before formatting)', + '(after formatting)') + diff_string = ''.join(diff) + if diff_string: + format_line_counter += sys.stdout.write(diff_string) + + if format_line_counter > 0: + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/docs/reference/gio/gdbus-codegen.xml b/docs/reference/gio/gdbus-codegen.xml index fdd367b..9687681 100644 --- a/docs/reference/gio/gdbus-codegen.xml +++ b/docs/reference/gio/gdbus-codegen.xml @@ -50,6 +50,8 @@ VALUE + VERSION + VERSION FILE FILE @@ -420,6 +422,66 @@ gdbus-codegen --c-namespace MyApp \ + + VERSION + + + Specifies the minimum version of GLib which the code generated by + gdbus-codegen can depend on. This may be used to + make backwards-incompatible changes in the output or behaviour of + gdbus-codegen in future, which users may opt in to + by increasing the value they pass for . + If this option is not passed, the output from gdbus-codegen + is guaranteed to be compatible with all versions of GLib from 2.30 + upwards, as that is when gdbus-codegen was first + released. + + + Note that some version parameters introduce incompatible changes: all callers + of the generated code might need to be updated, and if the generated code is part of + a library's API or ABI, then increasing the version parameter can result in an API + or ABI break. + + + The version number must be of the form + MAJOR.MINOR.MICRO, + where all parts are integers. MINOR and + MICRO are optional. The version number may not be smaller + than 2.30. + + + If the version number is 2.64 or greater, the generated code will + have the following features: (1) If a method has h (file + descriptor) parameter(s), a GUnixFDList parameter will exist in the + generated code for it (whereas previously the annotation + org.gtk.GDBus.C.UnixFD was required), and (2) Method call functions will + have two additional arguments to allow the user to specify GDBusCallFlags + and a timeout value, as is possible when using g_dbus_proxy_call(). + + + + + + VERSION + + + Specifies the maximum version of GLib which the code generated by + gdbus-codegen can depend on. This may be used to + ensure that code generated by gdbus-codegen is + compilable with specific older versions of GLib that your software has + to support. + + + The version number must be of the form + MAJOR.MINOR.MICRO, + where all parts are integers. MINOR and + MICRO are optional. The version number must + be greater than or equal to that passed to . + It defaults to the version of GLib which provides this gdbus-codegen. + + + + diff --git a/docs/reference/gio/gdbus-object-manager-example/.gitignore b/docs/reference/gio/gdbus-object-manager-example/.gitignore new file mode 100644 index 0000000..cc8a11d --- /dev/null +++ b/docs/reference/gio/gdbus-object-manager-example/.gitignore @@ -0,0 +1 @@ +gdbus-object-manager-example-overrides.txt diff --git a/docs/reference/gio/gdbus-object-manager-example/gdbus-object-manager-example-docs.xml b/docs/reference/gio/gdbus-object-manager-example/gdbus-object-manager-example-docs.xml new file mode 100644 index 0000000..7b0d1ca --- /dev/null +++ b/docs/reference/gio/gdbus-object-manager-example/gdbus-object-manager-example-docs.xml @@ -0,0 +1,17 @@ + + +]> + + + foo + + bar + + + + + + + diff --git a/docs/reference/gio/gdbus-object-manager-example/gdbus-object-manager-example-sections.txt b/docs/reference/gio/gdbus-object-manager-example/gdbus-object-manager-example-sections.txt new file mode 100644 index 0000000..1e3b8b8 --- /dev/null +++ b/docs/reference/gio/gdbus-object-manager-example/gdbus-object-manager-example-sections.txt @@ -0,0 +1,161 @@ +
+ExampleAnimal +ExampleAnimal +ExampleAnimal +ExampleAnimalIface +example_animal_interface_info +example_animal_override_properties +example_animal_call_poke +example_animal_call_poke_finish +example_animal_call_poke_sync +example_animal_complete_poke +example_animal_emit_jumped +example_animal_get_mood +example_animal_get_foo +example_animal_get_bar +example_animal_dup_mood +example_animal_dup_foo +example_animal_dup_bar +example_animal_set_mood +example_animal_set_foo +example_animal_set_bar +ExampleAnimalProxy +ExampleAnimalProxyClass +example_animal_proxy_new +example_animal_proxy_new_finish +example_animal_proxy_new_sync +example_animal_proxy_new_for_bus +example_animal_proxy_new_for_bus_finish +example_animal_proxy_new_for_bus_sync +ExampleAnimalSkeleton +ExampleAnimalSkeletonClass +example_animal_skeleton_new + +example_animal_get_type +example_animal_proxy_get_type +example_animal_skeleton_get_type +ExampleAnimalSkeletonPrivate +ExampleAnimalProxyPrivate +EXAMPLE_TYPE_ANIMAL +EXAMPLE_TYPE_ANIMAL_PROXY +EXAMPLE_TYPE_ANIMAL_SKELETON +EXAMPLE_ANIMAL +EXAMPLE_ANIMAL_GET_IFACE +EXAMPLE_ANIMAL_PROXY +EXAMPLE_ANIMAL_PROXY_CLASS +EXAMPLE_ANIMAL_PROXY_GET_CLASS +EXAMPLE_ANIMAL_SKELETON +EXAMPLE_ANIMAL_SKELETON_CLASS +EXAMPLE_ANIMAL_SKELETON_GET_CLASS +EXAMPLE_IS_ANIMAL +EXAMPLE_IS_ANIMAL_PROXY +EXAMPLE_IS_ANIMAL_PROXY_CLASS +EXAMPLE_IS_ANIMAL_SKELETON +EXAMPLE_IS_ANIMAL_SKELETON_CLASS +
+ +
+ExampleCat +ExampleCat +ExampleCat +ExampleCatIface +example_cat_interface_info +example_cat_override_properties +ExampleCatProxy +ExampleCatProxyClass +example_cat_proxy_new +example_cat_proxy_new_finish +example_cat_proxy_new_sync +example_cat_proxy_new_for_bus +example_cat_proxy_new_for_bus_finish +example_cat_proxy_new_for_bus_sync +ExampleCatSkeleton +ExampleCatSkeletonClass +example_cat_skeleton_new + +example_cat_get_type +example_cat_proxy_get_type +example_cat_skeleton_get_type +ExampleCatProxyPrivate +ExampleCatSkeletonPrivate +EXAMPLE_TYPE_CAT +EXAMPLE_TYPE_CAT_PROXY +EXAMPLE_TYPE_CAT_SKELETON +EXAMPLE_CAT +EXAMPLE_CAT_GET_IFACE +EXAMPLE_CAT_PROXY +EXAMPLE_CAT_PROXY_CLASS +EXAMPLE_CAT_PROXY_GET_CLASS +EXAMPLE_CAT_SKELETON +EXAMPLE_CAT_SKELETON_CLASS +EXAMPLE_CAT_SKELETON_GET_CLASS +EXAMPLE_IS_CAT +EXAMPLE_IS_CAT_PROXY +EXAMPLE_IS_CAT_PROXY_CLASS +EXAMPLE_IS_CAT_SKELETON +EXAMPLE_IS_CAT_SKELETON_CLASS +
+ +
+ExampleObject +ExampleObject +ExampleObject +ExampleObjectIface +example_object_get_animal +example_object_get_cat +example_object_peek_animal +example_object_peek_cat +ExampleObjectProxy +ExampleObjectProxyClass +example_object_proxy_new +ExampleObjectSkeleton +ExampleObjectSkeletonClass +example_object_skeleton_new +example_object_skeleton_set_animal +example_object_skeleton_set_cat + +example_object_get_type +example_object_proxy_get_type +example_object_skeleton_get_type +ExampleObjectProxyPrivate +ExampleObjectSkeletonPrivate +EXAMPLE_IS_OBJECT +EXAMPLE_IS_OBJECT_PROXY +EXAMPLE_IS_OBJECT_PROXY_CLASS +EXAMPLE_IS_OBJECT_SKELETON +EXAMPLE_IS_OBJECT_SKELETON_CLASS +EXAMPLE_OBJECT +EXAMPLE_OBJECT_GET_IFACE +EXAMPLE_OBJECT_PROXY +EXAMPLE_OBJECT_PROXY_CLASS +EXAMPLE_OBJECT_PROXY_GET_CLASS +EXAMPLE_OBJECT_SKELETON +EXAMPLE_OBJECT_SKELETON_CLASS +EXAMPLE_OBJECT_SKELETON_GET_CLASS +EXAMPLE_TYPE_OBJECT +EXAMPLE_TYPE_OBJECT_PROXY +EXAMPLE_TYPE_OBJECT_SKELETON +
+ +
+ExampleObjectManagerClient +ExampleObjectManagerClient +ExampleObjectManagerClient +ExampleObjectManagerClientClass +example_object_manager_client_get_proxy_type +example_object_manager_client_new +example_object_manager_client_new_finish +example_object_manager_client_new_sync +example_object_manager_client_new_for_bus +example_object_manager_client_new_for_bus_finish +example_object_manager_client_new_for_bus_sync + +example_object_manager_client_get_type +EXAMPLE_IS_OBJECT_MANAGER_CLIENT +EXAMPLE_IS_OBJECT_MANAGER_CLIENT_CLASS +EXAMPLE_OBJECT_MANAGER_CLIENT +EXAMPLE_OBJECT_MANAGER_CLIENT_CLASS +EXAMPLE_OBJECT_MANAGER_CLIENT_GET_CLASS +EXAMPLE_TYPE_OBJECT_MANAGER_CLIENT +ExampleObjectManagerClientPrivate +
diff --git a/docs/reference/gio/gdbus-object-manager-example/meson.build b/docs/reference/gio/gdbus-object-manager-example/meson.build new file mode 100644 index 0000000..13cae5b --- /dev/null +++ b/docs/reference/gio/gdbus-object-manager-example/meson.build @@ -0,0 +1,11 @@ +gdbus_object_manager_example_doc = gnome.gtkdoc('gdbus-object-manager-example', + main_xml : 'gdbus-object-manager-example-docs.xml', + namespace : 'example', + dependencies : [libgdbus_example_objectmanager_dep], + src_dir : 'gio/tests/gdbus-object-manager-example', + scan_args : gtkdoc_common_scan_args + [ + '--rebuild-types', + ], + install : false, +) + diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml index 0ce0e2d..5694729 100644 --- a/docs/reference/gio/gio-docs.xml +++ b/docs/reference/gio/gio-docs.xml @@ -232,6 +232,7 @@ + @@ -372,6 +373,14 @@ Index of new symbols in 2.60 + + Index of new symbols in 2.62 + + + + Index of new symbols in 2.64 + + diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt index bb483b0..cd62b8a 100644 --- a/docs/reference/gio/gio-sections-common.txt +++ b/docs/reference/gio/gio-sections-common.txt @@ -2462,7 +2462,7 @@ g_charset_converter_get_type
gconverterinputstream -GConverterInputstream +GConverterInputStream GConverterInputStream g_converter_input_stream_new g_converter_input_stream_get_converter @@ -2481,7 +2481,7 @@ g_converter_input_stream_get_type
gconverteroutputstream -GConverterOutputstream +GConverterOutputStream GConverterOutputStream g_converter_output_stream_new g_converter_output_stream_get_converter @@ -4170,6 +4170,24 @@ GDBusObjectManagerServerPrivate
+gmemorymonitor +GMemoryMonitor +GMemoryMonitor +GMemoryMonitorInterface +GMemoryMonitorWarningLevel +G_MEMORY_MONITOR_EXTENSION_POINT_NAME +g_memory_monitor_dup_default + +g_memory_monitor_get_type +G_TYPE_MEMORY_MONITOR +G_MEMORY_MONITOR +G_IS_MEMORY_MONITOR +G_MEMORY_MONITOR_GET_INTERFACE +G_TYPE_MEMORY_MONITOR_WARNING_LEVEL +g_memory_monitor_warning_level_get_type +
+ +
gnetworkmonitor GNetworkMonitor GNetworkMonitor @@ -4458,6 +4476,7 @@ g_task_get_name g_task_return_boolean g_task_return_int g_task_return_pointer +g_task_return_value g_task_return_error g_task_return_new_error g_task_return_error_if_cancelled @@ -4465,6 +4484,7 @@ g_task_return_error_if_cancelled g_task_propagate_boolean g_task_propagate_int g_task_propagate_pointer +g_task_propagate_value g_task_had_error g_task_get_completed @@ -4654,6 +4674,8 @@ g_list_store_remove g_list_store_remove_all g_list_store_splice g_list_store_sort +g_list_store_find +g_list_store_find_with_equal_func G_TYPE_LIST_STORE diff --git a/docs/reference/gio/gio.xml b/docs/reference/gio/gio.xml index bd0c66e..641b222 100644 --- a/docs/reference/gio/gio.xml +++ b/docs/reference/gio/gio.xml @@ -226,6 +226,10 @@ , Never follow symbolic links. + + + Use the default permissions of the current process for the destination file, rather than copying the permissions of the source file. + @@ -304,6 +308,10 @@ Don’t follow symbolic links. + , + Print display names. + + , Print full URIs. @@ -421,8 +429,8 @@ Mount as mountable. - , - Mount volume with device file. + , + Mount volume with device file, or other identifier. , @@ -433,6 +441,10 @@ Eject the location. + , + Stop drive with device file. + + , Unmount all mounts with the given scheme. @@ -490,6 +502,31 @@ to DESTINATION. If more than one source is specified, the destination must be a directory. The move command is similar to the traditional mv utility. + + Options + + + , + Don’t copy into DESTINATION even if it is a directory. + + + , + Show progress. + + + , + Prompt for confirmation before overwriting files. + + + , + Create backups of existing destination files. + + + , + Don’t use copy and delete fallback. + + + diff --git a/docs/reference/gio/meson.build b/docs/reference/gio/meson.build index f8805a5..a4e67ca 100644 --- a/docs/reference/gio/meson.build +++ b/docs/reference/gio/meson.build @@ -1,4 +1,7 @@ if get_option('gtk_doc') + if installed_tests_enabled + subdir('gdbus-object-manager-example') + endif subdir('xml') ignore_headers = [ @@ -50,6 +53,8 @@ if get_option('gtk_doc') 'glocalfilemonitor.h', 'glocalfileoutputstream.h', 'glocalvfs.h', + 'gmemorymonitordbus.h', + 'gmemorymonitorportal.h', 'gmountprivate.h', 'gnativevolumemonitor.h', 'gnetworkingprivate.h', @@ -130,8 +135,6 @@ if get_option('gtk_doc') 'xdp-dbus.c', ] - # FIXME: ExampleAnimal docs aren't built - docpath = join_paths(glib_datadir, 'gtk-doc', 'html') version_conf = configuration_data() version_conf.set('VERSION', meson.project_version()) @@ -159,6 +162,30 @@ if get_option('gtk_doc') copy : true, ) + content_files = [ + 'overview.xml', + 'migrating-posix.xml', + 'migrating-gnome-vfs.xml', + 'migrating-gconf.xml', + 'migrating-gdbus.xml', + 'gio-querymodules.xml', + 'glib-compile-schemas.xml', + 'glib-compile-resources.xml', + 'gapplication.xml', + 'gsettings.xml', + 'gresource.xml', + 'gdbus.xml', + 'gdbus-codegen.xml', + ] + + if installed_tests_enabled + content_files += [ + gdbus_example_objectmanager_xml, + gdbus_example_objectmanager_sources, + gdbus_object_manager_example_doc + ] + endif + gnome.gtkdoc('gio', main_xml : 'gio-docs.xml', namespace : 'g', @@ -172,21 +199,7 @@ if get_option('gtk_doc') mkdb_args : [ '--ignore-files=' + ' '.join(ignore_sources), ], - content_files : [ - 'overview.xml', - 'migrating-posix.xml', - 'migrating-gnome-vfs.xml', - 'migrating-gconf.xml', - 'migrating-gdbus.xml', - 'gio-querymodules.xml', - 'glib-compile-schemas.xml', - 'glib-compile-resources.xml', - 'gapplication.xml', - 'gsettings.xml', - 'gresource.xml', - 'gdbus.xml', - 'gdbus-codegen.xml', - ], + content_files : content_files, expand_content_files : [ 'overview.xml', 'migrating-posix.xml', @@ -205,7 +218,8 @@ if get_option('gtk_doc') '--extra-dir=' + join_paths('gio', '..', 'glib', 'html'), '--extra-dir=' + join_paths('gio', '..', 'gobject', 'html'), ], - install: true + install: true, + check: true, ) endif diff --git a/docs/reference/gio/migrating-gdbus.xml b/docs/reference/gio/migrating-gdbus.xml index 7595aaa..5e2d464 100644 --- a/docs/reference/gio/migrating-gdbus.xml +++ b/docs/reference/gio/migrating-gdbus.xml @@ -241,6 +241,9 @@ on_name_acquired (GDBusConnection *connection,
+
+ Generating code and docs +
Using gdbus-codegen @@ -267,7 +270,12 @@ gdbus-codegen --interface-prefix org.gtk.GDBus.Example.ObjectManager. \ generated. Additionally, two XML files generated-docs-org.gtk.GDBus.Example.ObjectManager.Animal and generated-docs-org.gtk.GDBus.Example.ObjectManager.Cat - with Docbook XML are generated. + with Docbook XML are generated. For an example of what the docs look + like see the Animal D-Bus interface documentation. + and + the Cat D-Bus interface documentation. While the contents of generated-code.h and @@ -276,9 +284,26 @@ gdbus-codegen --interface-prefix org.gtk.GDBus.Example.ObjectManager. \ linkend="gdbus-codegen">gdbus-codegen manual page, brief examples of how this generated code can be used can be found in - and . + and . Additionally, since + the generated code has 100% gtk-doc coverage, see + #ExampleAnimal, #ExampleCat, #ExampleObject and + #ExampleObjectManagerClient pages for documentation. + Server-side application using generated codeFIXME: MISSING XINCLUDE CONTENT + + Client-side application using generated codeFIXME: MISSING XINCLUDE CONTENT + +
+ + + + + + + +
diff --git a/docs/reference/gio/overview.xml b/docs/reference/gio/overview.xml index 960cd2f..9d01326 100644 --- a/docs/reference/gio/overview.xml +++ b/docs/reference/gio/overview.xml @@ -466,10 +466,9 @@ Gvfs is also heavily distributed and relies on a session bus to be present. <envar>GSETTINGS_SCHEMA_DIR</envar> - This variable can be set to the name of a directory that is - considered in addition to the glib-2.0/schemas - subdirectories of the XDG system data dirs when looking - for compiled schemas for #GSettings. + This variable can be set to the names of directories to consider when looking for compiled schemas for #GSettings, + in addition to the glib-2.0/schemas + subdirectories of the XDG system data dirs. To specify multiple directories, use G_SEARCHPATH_SEPARATOR_S as a separator. diff --git a/docs/reference/glib/building.xml b/docs/reference/glib/building.xml index 79567f6..720a6ff 100644 --- a/docs/reference/glib/building.xml +++ b/docs/reference/glib/building.xml @@ -26,6 +26,12 @@ ninja -C _build ninja -C _build install + + On FreeBSD: + + env CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib -Wl,--disable-new-dtags" meson -Dxattr=false -Dinstalled_tests=true -Diconv=external -Db_lundef=false _build + ninja -C _build + @@ -121,6 +127,16 @@ + Python 3.5 or newer is required. Your system Python must + conform to PEP 394 + + For FreeBSD, this means that the + lang/python3 port must be installed. + + + + The libintl library from the GNU gettext package is needed if your system doesn't have the diff --git a/docs/reference/glib/cross.xml b/docs/reference/glib/cross.xml index 977421f..c90b30b 100644 --- a/docs/reference/glib/cross.xml +++ b/docs/reference/glib/cross.xml @@ -59,6 +59,7 @@ c_link_args = [] c = 'x86_64-w64-mingw32-gcc' cpp = 'x86_64-w64-mingw32-g++' ar = 'x86_64-w64-mingw32-ar' +ld = 'x86_64-w64-mingw32-ld' objcopy = 'x86_64-w64-mingw32-objcopy' strip = 'x86_64-w64-mingw32-strip' pkgconfig = 'x86_64-w64-mingw32-pkg-config' diff --git a/docs/reference/glib/glib-docs.xml b/docs/reference/glib/glib-docs.xml index fa43eaf..186543f 100644 --- a/docs/reference/glib/glib-docs.xml +++ b/docs/reference/glib/glib-docs.xml @@ -276,6 +276,10 @@ Index of new symbols in 2.62 + + Index of new symbols in 2.64 + + diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index a5e64a1..63f40ba 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -135,6 +135,7 @@ GLIB_VERSION_2_56 GLIB_VERSION_2_58 GLIB_VERSION_2_60 GLIB_VERSION_2_62 +GLIB_VERSION_2_64 GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_MAX_ALLOWED GLIB_DISABLE_DEPRECATION_WARNINGS @@ -160,6 +161,7 @@ GLIB_AVAILABLE_ENUMERATOR_IN_2_56 GLIB_AVAILABLE_ENUMERATOR_IN_2_58 GLIB_AVAILABLE_ENUMERATOR_IN_2_60 GLIB_AVAILABLE_ENUMERATOR_IN_2_62 +GLIB_AVAILABLE_ENUMERATOR_IN_2_64 GLIB_AVAILABLE_IN_ALL GLIB_AVAILABLE_IN_2_26 GLIB_AVAILABLE_IN_2_28 @@ -180,6 +182,7 @@ GLIB_AVAILABLE_IN_2_56 GLIB_AVAILABLE_IN_2_58 GLIB_AVAILABLE_IN_2_60 GLIB_AVAILABLE_IN_2_62 +GLIB_AVAILABLE_IN_2_64 GLIB_AVAILABLE_MACRO_IN_2_26 GLIB_AVAILABLE_MACRO_IN_2_28 GLIB_AVAILABLE_MACRO_IN_2_30 @@ -199,6 +202,7 @@ GLIB_AVAILABLE_MACRO_IN_2_56 GLIB_AVAILABLE_MACRO_IN_2_58 GLIB_AVAILABLE_MACRO_IN_2_60 GLIB_AVAILABLE_MACRO_IN_2_62 +GLIB_AVAILABLE_MACRO_IN_2_64 GLIB_AVAILABLE_TYPE_IN_2_26 GLIB_AVAILABLE_TYPE_IN_2_28 GLIB_AVAILABLE_TYPE_IN_2_30 @@ -218,6 +222,7 @@ GLIB_AVAILABLE_TYPE_IN_2_56 GLIB_AVAILABLE_TYPE_IN_2_58 GLIB_AVAILABLE_TYPE_IN_2_60 GLIB_AVAILABLE_TYPE_IN_2_62 +GLIB_AVAILABLE_TYPE_IN_2_64 GLIB_DEPRECATED_ENUMERATOR GLIB_DEPRECATED_ENUMERATOR_FOR GLIB_DEPRECATED_ENUMERATOR_IN_2_26 @@ -258,6 +263,8 @@ GLIB_DEPRECATED_ENUMERATOR_IN_2_60 GLIB_DEPRECATED_ENUMERATOR_IN_2_60_FOR GLIB_DEPRECATED_ENUMERATOR_IN_2_62 GLIB_DEPRECATED_ENUMERATOR_IN_2_62_FOR +GLIB_DEPRECATED_ENUMERATOR_IN_2_64 +GLIB_DEPRECATED_ENUMERATOR_IN_2_64_FOR GLIB_DEPRECATED_IN_2_26 GLIB_DEPRECATED_IN_2_26_FOR GLIB_DEPRECATED_IN_2_28 @@ -296,6 +303,8 @@ GLIB_DEPRECATED_IN_2_60 GLIB_DEPRECATED_IN_2_60_FOR GLIB_DEPRECATED_IN_2_62 GLIB_DEPRECATED_IN_2_62_FOR +GLIB_DEPRECATED_IN_2_64 +GLIB_DEPRECATED_IN_2_64_FOR GLIB_DEPRECATED_MACRO GLIB_DEPRECATED_MACRO_FOR GLIB_DEPRECATED_MACRO_IN_2_26 @@ -336,6 +345,8 @@ GLIB_DEPRECATED_MACRO_IN_2_60 GLIB_DEPRECATED_MACRO_IN_2_60_FOR GLIB_DEPRECATED_MACRO_IN_2_62 GLIB_DEPRECATED_MACRO_IN_2_62_FOR +GLIB_DEPRECATED_MACRO_IN_2_64 +GLIB_DEPRECATED_MACRO_IN_2_64_FOR GLIB_DEPRECATED_TYPE GLIB_DEPRECATED_TYPE_FOR GLIB_DEPRECATED_TYPE_IN_2_26 @@ -376,6 +387,8 @@ GLIB_DEPRECATED_TYPE_IN_2_60 GLIB_DEPRECATED_TYPE_IN_2_60_FOR GLIB_DEPRECATED_TYPE_IN_2_62 GLIB_DEPRECATED_TYPE_IN_2_62_FOR +GLIB_DEPRECATED_TYPE_IN_2_64 +GLIB_DEPRECATED_TYPE_IN_2_64_FOR GLIB_VERSION_CUR_STABLE GLIB_VERSION_PREV_STABLE
@@ -411,6 +424,7 @@ CLAMP G_APPROX_VALUE +G_SIZEOF_MEMBER G_STRUCT_MEMBER G_STRUCT_MEMBER_P G_STRUCT_OFFSET @@ -769,6 +783,11 @@ g_main_context_invoke g_main_context_invoke_full +GMainContextPusher +g_main_context_pusher_new +g_main_context_pusher_free + + g_main_context_get_thread_default g_main_context_ref_thread_default g_main_context_push_thread_default @@ -805,11 +824,13 @@ G_POLLFD_FORMAT GSource GSourceDummyMarshal GSourceFuncs +GSourceDisposeFunc GSourceCallbackFuncs g_source_new g_source_ref g_source_unref g_source_set_funcs +g_source_set_dispose_function g_source_attach g_source_destroy g_source_is_destroyed @@ -1398,6 +1419,7 @@ g_log g_logv g_message g_warning +g_warning_once g_critical g_error g_info @@ -1613,6 +1635,7 @@ g_remove g_rmdir g_fopen g_freopen +g_fsync g_chmod g_access g_creat @@ -2072,6 +2095,20 @@ g_get_user_special_dir g_get_system_data_dirs g_get_system_config_dirs g_reload_user_special_dirs_cache +g_get_os_info + + +G_OS_INFO_KEY_NAME +G_OS_INFO_KEY_PRETTY_NAME +G_OS_INFO_KEY_VERSION +G_OS_INFO_KEY_VERSION_CODENAME +G_OS_INFO_KEY_VERSION_ID +G_OS_INFO_KEY_ID +G_OS_INFO_KEY_HOME_URL +G_OS_INFO_KEY_DOCUMENTATION_URL +G_OS_INFO_KEY_SUPPORT_URL +G_OS_INFO_KEY_BUG_REPORT_URL +G_OS_INFO_KEY_PRIVACY_POLICY_URL g_get_host_name @@ -2421,6 +2458,9 @@ g_unix_fd_add g_unix_fd_add_full g_unix_fd_source_new + +g_unix_get_passwd_entry + g_unix_error_quark @@ -2469,6 +2509,7 @@ g_list_delete_link g_list_remove_all g_list_free g_list_free_full +g_clear_list g_list_alloc @@ -2525,6 +2566,7 @@ g_slist_free g_slist_free_full g_slist_free_1 g_slist_free1 +g_clear_slist g_slist_length @@ -2791,6 +2833,7 @@ g_string_chunk_free arrays GArray g_array_new +g_array_steal g_array_sized_new g_array_copy g_array_ref @@ -2819,6 +2862,7 @@ g_array_free arrays_pointer GPtrArray g_ptr_array_new +g_ptr_array_steal g_ptr_array_sized_new g_ptr_array_new_with_free_func g_ptr_array_copy @@ -2854,6 +2898,7 @@ g_ptr_array_find_with_equal_func GByteArray g_byte_array_new +g_byte_array_steal g_byte_array_new_take g_byte_array_sized_new g_byte_array_ref diff --git a/docs/reference/glib/meson.build b/docs/reference/glib/meson.build index bba7649..62d95f7 100644 --- a/docs/reference/glib/meson.build +++ b/docs/reference/glib/meson.build @@ -88,7 +88,8 @@ if get_option('gtk_doc') fixxref_args: [ '--html-dir=' + docpath, ], - install: true) + install: true, + check: true) endif if get_option('man') diff --git a/docs/reference/glib/running.xml b/docs/reference/glib/running.xml index 86765c8..989f5b4 100644 --- a/docs/reference/glib/running.xml +++ b/docs/reference/glib/running.xml @@ -156,8 +156,8 @@ How to run and debug your GLib application - The special value all can be used to turn on all debug options. - The special value help can be used to print all available options. + The special value all can be used to turn on all debug options. + The special value help can be used to print all available options. @@ -207,8 +207,8 @@ How to run and debug your GLib application - The special value all can be used to turn on all options. - The special value help can be used to print all available options. + The special value all can be used to turn on all options. + The special value help can be used to print all available options. diff --git a/docs/reference/gobject/glib-genmarshal.xml b/docs/reference/gobject/glib-genmarshal.xml index bf72ba7..2b3e86f 100644 --- a/docs/reference/gobject/glib-genmarshal.xml +++ b/docs/reference/gobject/glib-genmarshal.xml @@ -411,7 +411,66 @@ debugging information. This option is mutually exclusive with the -Using glib-genmarshal with Autotools +Using <command>glib-genmarshal</command> with Meson + +Meson supports generating closure marshallers using glib-genmarshal +out of the box in its "gnome" module. + + + +In your meson.build file you will typically call the +gnome.genmarshal() method with the source list of marshallers +to generate: + + +gnome = import('gnome') +marshal_files = gnome.genmarshal('marshal', + sources: 'marshal.list', + internal: true, +) + + +The marshal_files variable will contain an array of two elements +in the following order: + + + a build target for the source file + a build target for the header file + + +You should use the returned objects to provide a dependency on every other +build target that references the source or header file; for instance, if you +are using the source to build a library: + + +mainlib = library('project', + sources: project_sources + marshal_files, + ... +) + + +Additionally, if you are including the generated header file inside a build +target that depends on the library you just built, you must ensure that the +internal dependency includes the generated header as a required source file: + + +mainlib_dep = declare_dependency(sources: marshal_files[1], link_with: mainlib) + + +You should not include the generated source file as well, otherwise it will +be built separately for every target that depends on it, causing build +failures. To know more about why all this is required, please refer to the + +corresponding Meson FAQ entry. + + +For more information on how to use the method, see the +Meson +documentation for gnome.genmarshal(). + + + +Using <command>glib-genmarshal</command> with Autotools In order to use glib-genmarshal in your project when using Autotools as the build system, you will first need to modify your diff --git a/docs/reference/gobject/glib-mkenums.xml b/docs/reference/gobject/glib-mkenums.xml index 4c86416..9dac821 100644 --- a/docs/reference/gobject/glib-mkenums.xml +++ b/docs/reference/gobject/glib-mkenums.xml @@ -153,15 +153,16 @@ The same as @type@ with all letters uppercased (e.g. @filename@ -The name of the input file currently being processed (e.g. foo.h). +The full path of the input file currently being processed (e.g. /build/environment/project/src/foo.h). @basename@ -The base name of the input file currently being processed (e.g. foo.h). Typically -you want to use @basename@ in place of @filename@ in your templates, to improve the reproducibility of the build. (Since: 2.22) +The base name of the input file currently being processed (e.g. foo.h). +Typically you want to use @basename@ in place of @filename@ +in your templates, to improve the reproducibility of the build. (Since: 2.22) @@ -389,52 +390,15 @@ limit. For example, Windows has a limit of 8191 characters. -Using glib-mkenums with Autotools +Using templates -In order to use glib-mkenums in your project when using -Autotools as the build system, you will first need to modify your -configure.ac file to ensure you find the appropriate -command using pkg-config, similarly as to how you discover -the compiler and linker flags for GLib. +Instead of passing the various sections of the generated file to the command +line of glib-mkenums, it's strongly recommended to use a +template file, especially for generating C sources. - -PKG_PROG_PKG_CONFIG([0.28]) -PKG_CHECK_VAR([GLIB_MKENUMS], [glib-2.0], [glib_mkenums]) - -In your Makefile.am file you will typically use rules -like these: - - -# A list of headers to inspect -project_headers = \ - project-foo.h \ - project-bar.h \ - project-baz.h - -enum-types.h: $(project_headers) enum-types.h.in - $(AM_V_GEN)$(GLIB_MKENUMS) \ - --template=enum-types.h.in \ - --output=$@ \ - $(project_headers) - -enum-types.c: $(project_headers) enum-types.c.in enum-types.h - $(AM_V_GEN)$(GLIB_MKENUMS) \ - --template=enum-types.c.in \ - --output=$@ \ - $(project_headers) - -BUILT_SOURCES += enum-types.h enum-types.c -CLEANFILES += enum-types.h enum-types.c -EXTRA_DIST += enum-types.h.in enum-types.c.in - - -In the example above, we have a variable called project_headers -where we reference all header files we want to inspect for generating enumeration -GTypes. In the enum-types.h rule we use glib-mkenums -with a template called enum-types.h.in in order to generate the -header file; a header template file will typically look like this: +A C header template file will typically look like this: /*** BEGIN file-header ***/ @@ -448,7 +412,7 @@ G_BEGIN_DECLS /*** BEGIN file-production ***/ -/* enumerations from "@filename@" */ +/* enumerations from "@basename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ @@ -460,10 +424,9 @@ GType @enum_name@_get_type (void) G_GNUC_CONST; G_END_DECLS /*** END file-tail ***/ + -The enum-types.c rule is similar to the rule for the -header file, but will use a different enum-types.c.in template -file, similar to this: +A C source template file will typically look like this: /*** BEGIN file-header ***/ @@ -473,7 +436,7 @@ file, similar to this: /*** END file-header ***/ /*** BEGIN file-production ***/ -/* enumerations from "@filename@" */ +/* enumerations from "@basename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ @@ -505,6 +468,141 @@ GType /*** END value-tail ***/ + + +Template files are easier to modify and update, and can be used +to generate various types of outputs using the same command line +or tools during the build. + + + +Using glib-mkenums with Meson + +Meson supports generating enumeration types using glib-mkenums +out of the box in its "gnome" module. + + + +In your meson.build file you will typically call the +gnome.mkenums_simple() method to generate idiomatic enumeration +types from a list of headers to inspect: + + +project_headers = [ + 'project-foo.h', + 'project-bar.h', + 'project-baz.h', +] + +gnome = import('gnome') +enum_files = gnome.mkenums_simple('enum-types', + sources: project_headers, +) + + + +The enum_files variable will contain an array of two elements +in the following order: + + + a build target for the source file + a build target for the header file + + +You should use the returned objects to provide a dependency on every other +build target that references the source or header file; for instance, if you +are using the source to build a library: + + +mainlib = library('project', + sources: project_sources + enum_files, + ... +) + + +Additionally, if you are including the generated header file inside a build +target that depends on the library you just built, you must ensure that the +internal dependency includes the generated header as a required source file: + + +mainlib_dep = declare_dependency(sources: enum_files[1], link_with: mainlib) + + +You should not include the generated source file as well, otherwise it will +be built separately for every target that depends on it, causing build +failures. To know more about why all this is required, please refer to the + +corresponding Meson FAQ entry. + + + +If you are generating C header and source files that require special +templates, you can use gnome.mkenums() to provide those +headers, for instance: + + +enum_files = gnome.mkenums('enum-types', + sources: project_headers, + h_template: 'enum-types.h.in', + c_template: 'enum-types.c.in', + install_header: true, +) + + +For more information, see the Meson +documentation for gnome.mkenums(). + + + +Using glib-mkenums with Autotools + +In order to use glib-mkenums in your project when using +Autotools as the build system, you will first need to modify your +configure.ac file to ensure you find the appropriate +command using pkg-config, similarly as to how you discover +the compiler and linker flags for GLib. + + +PKG_PROG_PKG_CONFIG([0.28]) + +PKG_CHECK_VAR([GLIB_MKENUMS], [glib-2.0], [glib_mkenums]) + + +In your Makefile.am file you will typically use rules +like these: + + +# A list of headers to inspect +project_headers = \ + project-foo.h \ + project-bar.h \ + project-baz.h + +enum-types.h: $(project_headers) enum-types.h.in + $(AM_V_GEN)$(GLIB_MKENUMS) \ + --template=enum-types.h.in \ + --output=$@ \ + $(project_headers) + +enum-types.c: $(project_headers) enum-types.c.in enum-types.h + $(AM_V_GEN)$(GLIB_MKENUMS) \ + --template=enum-types.c.in \ + --output=$@ \ + $(project_headers) + +# Build the enum types files before every other target +BUILT_SOURCES += enum-types.h enum-types.c +CLEANFILES += enum-types.h enum-types.c +EXTRA_DIST += enum-types.h.in enum-types.c.in + + +In the example above, we have a variable called project_headers +where we reference all header files we want to inspect for generating enumeration +GTypes. In the enum-types.h rule we use glib-mkenums +with a template called enum-types.h.in in order to generate the +header file; similarly, in the enum-types.c rule we use a +template called enum-types.c.in. + See also diff --git a/docs/reference/gobject/meson.build b/docs/reference/gobject/meson.build index 096c903..a9a6543 100644 --- a/docs/reference/gobject/meson.build +++ b/docs/reference/gobject/meson.build @@ -50,7 +50,8 @@ if get_option('gtk_doc') '--html-dir=' + docpath, '--extra-dir=' + join_paths('gobject', '..', 'glib', 'html'), ], - install: true + install: true, + check: true, ) endif diff --git a/docs/reference/gobject/tut_gobject.xml b/docs/reference/gobject/tut_gobject.xml index 947f488..fc7d6fe 100644 --- a/docs/reference/gobject/tut_gobject.xml +++ b/docs/reference/gobject/tut_gobject.xml @@ -65,6 +65,8 @@ struct _ViewerFile GObject parent_instance; /* instance members */ + gchar *filename; + guint zoom_level; }; /* will create viewer_file_get_type and set viewer_file_parent_class */ @@ -81,11 +83,24 @@ viewer_file_constructed (GObject *obj) } static void +viewer_file_finalize (GObject *obj) +{ + ViewerFile *self = VIEWER_FILE (obj); + + g_free (self->filename); + + /* Always chain up to the parent finalize function to complete object + * destruction. */ + G_OBJECT_CLASS (viewer_file_parent_class)->finalize (obj); +} + +static void viewer_file_class_init (ViewerFileClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = viewer_file_constructed; + object_class->finalize = viewer_file_finalize; } static void @@ -465,12 +480,12 @@ ViewerFile *file = g_object_new (VIEWER_TYPE_FILE, NULL); /* Implementation */ /************************************************/ -enum +typedef enum { PROP_FILENAME = 1, PROP_ZOOM_LEVEL, N_PROPERTIES -}; +} ViewerFileProperty; static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; @@ -482,7 +497,7 @@ viewer_file_set_property (GObject *object, { ViewerFile *self = VIEWER_FILE (object); - switch (property_id) + switch ((ViewerFileProperty) property_id) { case PROP_FILENAME: g_free (self->filename); @@ -510,7 +525,7 @@ viewer_file_get_property (GObject *object, { ViewerFile *self = VIEWER_FILE (object); - switch (property_id) + switch ((ViewerFileProperty) property_id) { case PROP_FILENAME: g_value_set_string (value, self->filename); diff --git a/docs/reference/meson.build b/docs/reference/meson.build index 8da9665..0dd821a 100644 --- a/docs/reference/meson.build +++ b/docs/reference/meson.build @@ -7,7 +7,7 @@ stable_2_series_versions = [ '26', '28', '30', '32', '34', '36', '38', '40', '42', '44', '46', '48', '50', '52', '54', '56', '58', - '60', '62', + '60', '62', '64', ] ignore_decorators = [ @@ -19,9 +19,24 @@ ignore_decorators = [ foreach version : stable_2_series_versions ignore_decorators += [ + # Note that gtkdoc is going to use those in regex, and the longest match + # must come first. That's why '_FOR()' variant comes first. + # gtkdoc special-case '()' and replace it by a regex matching a symbol name. 'GLIB_AVAILABLE_IN_2_' + version, + 'GLIB_DEPRECATED_IN_2_' + version + '_FOR()', 'GLIB_DEPRECATED_IN_2_' + version, - 'GLIB_DEPRECATED_IN_2_' + version + '_FOR', + + 'GLIB_AVAILABLE_ENUMERATOR_IN_2_' + version, + 'GLIB_DEPRECATED_ENUMERATOR_IN_2_' + version + '_FOR()', + 'GLIB_DEPRECATED_ENUMERATOR_IN_2_' + version, + + 'GLIB_AVAILABLE_MACRO_IN_2_' + version, + 'GLIB_DEPRECATED_MACRO_IN_2_' + version + '_FOR()', + 'GLIB_DEPRECATED_MACRO_IN_2_' + version, + + 'GLIB_AVAILABLE_TYPE_IN_2_' + version, + 'GLIB_DEPRECATED_TYPE_IN_2_' + version + '_FOR()', + 'GLIB_DEPRECATED_TYPE_IN_2_' + version, ] endforeach @@ -29,6 +44,17 @@ gtkdoc_common_scan_args = [ '--ignore-decorators=' + '|'.join(ignore_decorators), ] +if get_option('gtk_doc') + if not meson.version().version_compare('>=0.52.0') + error('Building documentation requires Meson >= 0.52.0.') + endif + # Check we have the minimum gtk-doc version required. Older versions won't + # generate correct documentation. + dependency('gtk-doc', version : '>=1.32', + fallback : ['gtk-doc', 'dummy_dep'], + default_options : ['tests=false']) +endif + subdir('gio') subdir('glib') subdir('gobject') \ No newline at end of file diff --git a/fuzzing/README.md b/fuzzing/README.md index dcacef6..16f5a05 100644 --- a/fuzzing/README.md +++ b/fuzzing/README.md @@ -1,6 +1,6 @@ Fuzz targets used by [oss-fuzz](https://github.com/google/oss-fuzz/). -Useful links: [Dashboard](https://oss-fuzz.com/) _(requires access)_, [Build logs](https://oss-fuzz-build-logs.storage.googleapis.com/index.html), [Coverage](https://oss-fuzz.com/v2/coverage-report/job/libfuzzer_asan_glib/latest) +Useful links: [Dashboard](https://oss-fuzz.com/) _(requires access)_, [Build logs](https://oss-fuzz-build-logs.storage.googleapis.com/index.html), [Coverage](https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_glib/latest) ## How to add new targets diff --git a/gio/completion/gio b/gio/completion/gio index c1db985..63d3a12 100644 --- a/gio/completion/gio +++ b/gio/completion/gio @@ -43,20 +43,19 @@ __gio_location() { if [[ $cur =~ "/"$ ]]; then dir="$cur" elif [[ $cur =~ "/" ]]; then - dir="$(dirname "$cur")/" + # Subtract basename because dirname cmd doesn't work well with schemes + dir=${cur%$(basename "$cur")} fi - # List daemon mounts, just if dir is not specified, or looks like scheme - local mounts=() - if [[ $dir == "" ]] || [[ $dir =~ ":"$ && ! $dir =~ "/" ]]; then - while IFS=$'\n' read mount; do - # Do not care about local mounts - [[ "$mount" =~ ^"file:" ]] && continue + # List volumes and mounts + local mounts=( ) + while IFS=$'\n' read mount; do + # Do not care about local mounts + [[ "$mount" =~ ^"file:" ]] && continue - # Use only matching mounts - [[ "$mount" =~ ^"$cur" && "$mount" != "$cur" ]] && mounts+=("$mount") - done < <(gio mount -l | sed -n -r 's/^ *Mount\([0-9]+\): .* -> (.*)$/\1/p') - fi + # Use only matching mounts + [[ "$mount" =~ ^"$cur" && "$mount" != "$cur" ]] && mounts+=("$mount") + done < <(gio mount -li | sed -n -r 's/^ *(default_location|activation_root)=(.*)$/\2/p' | sort -u) # Workaround to unescape dir name (e.g. "\ " -> " ") declare -a tmp="( ${dir} )" @@ -106,7 +105,7 @@ __gio_location() { __gio() { # Complete subcommands if (( ${COMP_CWORD} == 1 )); then - COMPREPLY=($(compgen -W "help version cat copy info list mime mkdir monitor mount move open rename save set trash tree" -- "${COMP_WORDS[1]}")) + COMPREPLY=($(compgen -W "help version cat copy info list mime mkdir monitor mount move open rename remove save set trash tree" -- "${COMP_WORDS[1]}")) compopt +o nospace return 0 fi diff --git a/gio/gactiongroup.c b/gio/gactiongroup.c index 2345ce3..24da353 100644 --- a/gio/gactiongroup.c +++ b/gio/gactiongroup.c @@ -529,7 +529,7 @@ g_action_group_get_action_enabled (GActionGroup *action_group, * The return value (if non-%NULL) should be freed with * g_variant_unref() when it is no longer required. * - * Returns: (nullable): the current state of the action + * Returns: (nullable) (transfer full): the current state of the action * * Since: 2.28 **/ diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 1dafc78..6b00925 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -1309,7 +1309,7 @@ g_app_launch_context_get_display (GAppLaunchContext *context, * `DESKTOP_STARTUP_ID` for the launched operation, if supported. * * Startup notification IDs are defined in the - * [FreeDesktop.Org Startup Notifications standard](http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt"). + * [FreeDesktop.Org Startup Notifications standard](http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt). * * Returns: a startup notification ID for the application, or %NULL if * not supported. diff --git a/gio/gapplication.c b/gio/gapplication.c index 02ab2b0..8256639 100644 --- a/gio/gapplication.c +++ b/gio/gapplication.c @@ -129,7 +129,7 @@ * initialization for all of these in a single place. * * Regardless of which of these entry points is used to start the - * application, GApplication passes some "platform data from the + * application, GApplication passes some ‘platform data’ from the * launching instance to the primary instance, in the form of a * #GVariant dictionary mapping strings to variants. To use platform * data, override the @before_emit or @after_emit virtual functions @@ -701,14 +701,14 @@ add_packed_option (GApplication *application, * * It is important to use the proper GVariant format when retrieving * the options with g_variant_dict_lookup(): - * - for %G_OPTION_ARG_NONE, use b - * - for %G_OPTION_ARG_STRING, use &s - * - for %G_OPTION_ARG_INT, use i - * - for %G_OPTION_ARG_INT64, use x - * - for %G_OPTION_ARG_DOUBLE, use d - * - for %G_OPTION_ARG_FILENAME, use ^ay - * - for %G_OPTION_ARG_STRING_ARRAY, use &as - * - for %G_OPTION_ARG_FILENAME_ARRAY, use ^aay + * - for %G_OPTION_ARG_NONE, use `b` + * - for %G_OPTION_ARG_STRING, use `&s` + * - for %G_OPTION_ARG_INT, use `i` + * - for %G_OPTION_ARG_INT64, use `x` + * - for %G_OPTION_ARG_DOUBLE, use `d` + * - for %G_OPTION_ARG_FILENAME, use `^&ay` + * - for %G_OPTION_ARG_STRING_ARRAY, use `^a&s` + * - for %G_OPTION_ARG_FILENAME_ARRAY, use `^a&ay` * * Since: 2.40 */ diff --git a/gio/gcancellable.c b/gio/gcancellable.c index 0cfa91c..d9e58b8 100644 --- a/gio/gcancellable.c +++ b/gio/gcancellable.c @@ -642,20 +642,19 @@ typedef struct { GSource source; GCancellable *cancellable; - guint cancelled_handler; + gulong cancelled_handler; } GCancellableSource; /* - * We can't guarantee that the source still has references, so we are - * relying on the fact that g_source_set_ready_time() no longer makes - * assertions about the reference count - the source might be in the - * window between last-unref and finalize, during which its refcount - * is officially 0. However, we *can* guarantee that it's OK to - * dereference it in a limited way, because we know we haven't yet reached - * cancellable_source_finalize() - if we had, then we would have waited - * for signal emission to finish, then disconnected the signal handler - * under the lock. - * See https://bugzilla.gnome.org/show_bug.cgi?id=791754 + * The reference count of the GSource might be 0 at this point but it is not + * finalized yet and its dispose function did not run yet, or otherwise we + * would have disconnected the signal handler already and due to the signal + * emission lock it would be impossible to call the signal handler at that + * point. That is: at this point we either have a fully valid GSource, or + * it's not disposed or finalized yet and we can still resurrect it as needed. + * + * As such we first ensure that we have a strong reference to the GSource in + * here before calling any other GSource API. */ static void cancellable_source_cancelled (GCancellable *cancellable, @@ -663,7 +662,9 @@ cancellable_source_cancelled (GCancellable *cancellable, { GSource *source = user_data; + g_source_ref (source); g_source_set_ready_time (source, 0); + g_source_unref (source); } static gboolean @@ -679,15 +680,15 @@ cancellable_source_dispatch (GSource *source, } static void -cancellable_source_finalize (GSource *source) +cancellable_source_dispose (GSource *source) { GCancellableSource *cancellable_source = (GCancellableSource *)source; if (cancellable_source->cancellable) { - g_cancellable_disconnect (cancellable_source->cancellable, - cancellable_source->cancelled_handler); - g_object_unref (cancellable_source->cancellable); + g_clear_signal_handler (&cancellable_source->cancelled_handler, + cancellable_source->cancellable); + g_clear_object (&cancellable_source->cancellable); } } @@ -720,12 +721,12 @@ static GSourceFuncs cancellable_source_funcs = NULL, NULL, cancellable_source_dispatch, - cancellable_source_finalize, + NULL, (GSourceFunc)cancellable_source_closure_callback, }; /** - * g_cancellable_source_new: (skip) + * g_cancellable_source_new: * @cancellable: (nullable): a #GCancellable, or %NULL * * Creates a source that triggers if @cancellable is cancelled and @@ -750,6 +751,7 @@ g_cancellable_source_new (GCancellable *cancellable) source = g_source_new (&cancellable_source_funcs, sizeof (GCancellableSource)); g_source_set_name (source, "GCancellable"); + g_source_set_dispose_function (source, cancellable_source_dispose); cancellable_source = (GCancellableSource *)source; if (cancellable) diff --git a/gio/gcontenttype.c b/gio/gcontenttype.c index 04d278d..2716c89 100644 --- a/gio/gcontenttype.c +++ b/gio/gcontenttype.c @@ -614,6 +614,8 @@ g_content_type_get_generic_icon_name (const gchar *type) const gchar *xdg_icon_name; gchar *icon_name; + g_return_val_if_fail (type != NULL, NULL); + G_LOCK (gio_xdgmime); xdg_icon_name = xdg_mime_get_generic_icon (type); G_UNLOCK (gio_xdgmime); diff --git a/gio/gcredentials.c b/gio/gcredentials.c index ff9b7e0..c4794de 100644 --- a/gio/gcredentials.c +++ b/gio/gcredentials.c @@ -286,7 +286,7 @@ linux_ucred_check_valid (struct ucred *native, g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, - "GCredentials contains invalid data"); + _("GCredentials contains invalid data")); return FALSE; } @@ -578,13 +578,12 @@ g_credentials_set_unix_user (GCredentials *credentials, uid_t uid, GError **error) { - gboolean ret; + gboolean ret = FALSE; g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE); g_return_val_if_fail (uid != -1, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - ret = FALSE; #if G_CREDENTIALS_USE_LINUX_UCRED credentials->native.uid = uid; ret = TRUE; diff --git a/gio/gdbus-2.0/codegen/codegen.py b/gio/gdbus-2.0/codegen/codegen.py index aa1280f..d71299e 100644 --- a/gio/gdbus-2.0/codegen/codegen.py +++ b/gio/gdbus-2.0/codegen/codegen.py @@ -29,10 +29,11 @@ from . import dbustypes from .utils import print_error LICENSE_STR = '''/* - * Generated by gdbus-codegen {!s} from {!s}. DO NOT EDIT. + * This file is generated by gdbus-codegen, do not modify it. * * The license of this code is the same as for the D-Bus interface description - * it was derived from. + * it was derived from. Note that it links to GLib, so must comply with the + * LGPL linking clauses. */\n''' def generate_namespace(namespace): @@ -51,17 +52,25 @@ def generate_namespace(namespace): return (ns, ns_upper, ns_lower) +def generate_header_guard(header_name): + # There might be more characters that are safe to use than these, but lets + # stay conservative. + safe_valid_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + return ''.join(map(lambda c: c if c in safe_valid_chars else '_', + header_name.upper())) + class HeaderCodeGenerator: def __init__(self, ifaces, namespace, generate_objmanager, generate_autocleanup, header_name, input_files_basenames, - use_pragma, outfile): + use_pragma, glib_min_required, outfile): self.ifaces = ifaces self.namespace, self.ns_upper, self.ns_lower = generate_namespace(namespace) self.generate_objmanager = generate_objmanager self.generate_autocleanup = generate_autocleanup - self.header_guard = header_name.upper().replace('.', '_').replace('-', '_').replace('/', '_').replace(':', '_') + self.header_guard = generate_header_guard(header_name) self.input_files_basenames = input_files_basenames self.use_pragma = use_pragma + self.glib_min_required = glib_min_required self.outfile = outfile # ---------------------------------------------------------------------------------------------------- @@ -112,14 +121,11 @@ class HeaderCodeGenerator: if len(i.methods) > 0: self.outfile.write('\n') for m in i.methods: - unix_fd = False - if utils.lookup_annotation(m.annotations, 'org.gtk.GDBus.C.UnixFD'): - unix_fd = True key = (m.since, '_method_%s'%m.name_lower) value = ' gboolean (*handle_%s) (\n'%(m.name_lower) value += ' %s *object,\n'%(i.camel_name) value += ' GDBusMethodInvocation *invocation'%() - if unix_fd: + if m.unix_fd: value += ',\n GUnixFDList *fd_list' for a in m.in_args: value += ',\n %sarg_%s'%(a.ctype_in, a.name) @@ -176,15 +182,12 @@ class HeaderCodeGenerator: self.outfile.write('\n') self.outfile.write('/* D-Bus method call completion functions: */\n') for m in i.methods: - unix_fd = False - if utils.lookup_annotation(m.annotations, 'org.gtk.GDBus.C.UnixFD'): - unix_fd = True if m.deprecated: self.outfile.write('G_GNUC_DEPRECATED ') self.outfile.write('void %s_complete_%s (\n' ' %s *object,\n' ' GDBusMethodInvocation *invocation'%(i.name_lower, m.name_lower, i.camel_name)) - if unix_fd: + if m.unix_fd: self.outfile.write(',\n GUnixFDList *fd_list') for a in m.out_args: self.outfile.write(',\n %s%s'%(a.ctype_in, a.name)) @@ -212,9 +215,6 @@ class HeaderCodeGenerator: self.outfile.write('\n') self.outfile.write('/* D-Bus method calls: */\n') for m in i.methods: - unix_fd = False - if utils.lookup_annotation(m.annotations, 'org.gtk.GDBus.C.UnixFD'): - unix_fd = True # async begin if m.deprecated: self.outfile.write('G_GNUC_DEPRECATED ') @@ -222,7 +222,10 @@ class HeaderCodeGenerator: ' %s *proxy'%(i.name_lower, m.name_lower, i.camel_name)) for a in m.in_args: self.outfile.write(',\n %sarg_%s'%(a.ctype_in, a.name)) - if unix_fd: + if self.glib_min_required >= (2, 64): + self.outfile.write(',\n GDBusCallFlags call_flags' + ',\n gint timeout_msec') + if m.unix_fd: self.outfile.write(',\n GUnixFDList *fd_list') self.outfile.write(',\n' ' GCancellable *cancellable,\n' @@ -236,7 +239,7 @@ class HeaderCodeGenerator: ' %s *proxy'%(i.name_lower, m.name_lower, i.camel_name)) for a in m.out_args: self.outfile.write(',\n %sout_%s'%(a.ctype_out, a.name)) - if unix_fd: + if m.unix_fd: self.outfile.write(',\n GUnixFDList **out_fd_list') self.outfile.write(',\n' ' GAsyncResult *res,\n' @@ -249,11 +252,14 @@ class HeaderCodeGenerator: ' %s *proxy'%(i.name_lower, m.name_lower, i.camel_name)) for a in m.in_args: self.outfile.write(',\n %sarg_%s'%(a.ctype_in, a.name)) - if unix_fd: + if self.glib_min_required >= (2, 64): + self.outfile.write(',\n GDBusCallFlags call_flags' + ',\n gint timeout_msec') + if m.unix_fd: self.outfile.write(',\n GUnixFDList *fd_list') for a in m.out_args: self.outfile.write(',\n %sout_%s'%(a.ctype_out, a.name)) - if unix_fd: + if m.unix_fd: self.outfile.write(',\n GUnixFDList **out_fd_list') self.outfile.write(',\n' ' GCancellable *cancellable,\n' @@ -619,12 +625,13 @@ class HeaderCodeGenerator: # ---------------------------------------------------------------------------------------------------- class InterfaceInfoHeaderCodeGenerator: - def __init__(self, ifaces, namespace, header_name, input_files_basenames, use_pragma, outfile): + def __init__(self, ifaces, namespace, header_name, input_files_basenames, use_pragma, glib_min_required, outfile): self.ifaces = ifaces self.namespace, self.ns_upper, self.ns_lower = generate_namespace(namespace) - self.header_guard = header_name.upper().replace('.', '_').replace('-', '_').replace('/', '_').replace(':', '_') + self.header_guard = generate_header_guard(header_name) self.input_files_basenames = input_files_basenames self.use_pragma = use_pragma + self.glib_min_required = glib_min_required self.outfile = outfile # ---------------------------------------------------------------------------------------------------- @@ -672,11 +679,12 @@ class InterfaceInfoHeaderCodeGenerator: # ---------------------------------------------------------------------------------------------------- class InterfaceInfoBodyCodeGenerator: - def __init__(self, ifaces, namespace, header_name, input_files_basenames, outfile): + def __init__(self, ifaces, namespace, header_name, input_files_basenames, glib_min_required, outfile): self.ifaces = ifaces self.namespace, self.ns_upper, self.ns_lower = generate_namespace(namespace) self.header_name = header_name self.input_files_basenames = input_files_basenames + self.glib_min_required = glib_min_required self.outfile = outfile # ---------------------------------------------------------------------------------------------------- @@ -904,13 +912,14 @@ class InterfaceInfoBodyCodeGenerator: class CodeGenerator: def __init__(self, ifaces, namespace, generate_objmanager, header_name, - input_files_basenames, docbook_gen, outfile): + input_files_basenames, docbook_gen, glib_min_required, outfile): self.ifaces = ifaces self.namespace, self.ns_upper, self.ns_lower = generate_namespace(namespace) self.generate_objmanager = generate_objmanager self.header_name = header_name self.input_files_basenames = input_files_basenames self.docbook_gen = docbook_gen + self.glib_min_required = glib_min_required self.outfile = outfile # ---------------------------------------------------------------------------------------------------- @@ -1153,9 +1162,6 @@ class CodeGenerator: if len(i.methods) > 0: for m in i.methods: - unix_fd = False - if utils.lookup_annotation(m.annotations, 'org.gtk.GDBus.C.UnixFD'): - unix_fd = True self.generate_args('_%s_method_info_%s_IN_ARG'%(i.name_lower, m.name_lower), m.in_args) self.generate_args('_%s_method_info_%s_OUT_ARG'%(i.name_lower, m.name_lower), m.out_args) @@ -1181,7 +1187,7 @@ class CodeGenerator: self.outfile.write(' },\n' ' "handle-%s",\n' ' %s\n' - %(m.name_hyphen, 'TRUE' if unix_fd else 'FALSE')) + %(m.name_hyphen, 'TRUE' if m.unix_fd else 'FALSE')) self.outfile.write('};\n' '\n') @@ -1399,16 +1405,13 @@ class CodeGenerator: if len(i.methods) > 0: self.outfile.write(' /* GObject signals for incoming D-Bus method calls: */\n') for m in i.methods: - unix_fd = False - if utils.lookup_annotation(m.annotations, 'org.gtk.GDBus.C.UnixFD'): - unix_fd = True self.outfile.write(self.docbook_gen.expand( ' /**\n' ' * %s::handle-%s:\n' ' * @object: A #%s.\n' ' * @invocation: A #GDBusMethodInvocation.\n' %(i.camel_name, m.name_hyphen, i.camel_name), False)) - if unix_fd: + if m.unix_fd: self.outfile.write(' * @fd_list: (nullable): A #GUnixFDList or %NULL.\n') for a in m.in_args: self.outfile.write(' * @arg_%s: Argument passed by remote caller.\n'%(a.name)) @@ -1421,7 +1424,7 @@ class CodeGenerator: ' * Returns: %%TRUE if the invocation was handled, %%FALSE to let other signal handlers run.\n' %(i.name, m.name, i.name_lower, m.name_lower), False)) self.write_gtkdoc_deprecated_and_since_and_close(m, self.outfile, 2) - if unix_fd: + if m.unix_fd: extra_args = 2 else: extra_args = 1 @@ -1436,7 +1439,7 @@ class CodeGenerator: ' %d,\n' ' G_TYPE_DBUS_METHOD_INVOCATION' %(m.name_hyphen, i.camel_name, m.name_lower, len(m.in_args) + extra_args)) - if unix_fd: + if m.unix_fd: self.outfile.write(', G_TYPE_UNIX_FD_LIST') for a in m.in_args: self.outfile.write(', %s'%(a.gtype)) @@ -1565,7 +1568,7 @@ class CodeGenerator: ' *\n' %(i.name_lower, p.name_lower, i.camel_name, i.name, p.name, hint), False)) if p.arg.free_func != None: - self.outfile.write(' * The returned value is only valid until the property changes so on the client-side it is only safe to use this function on the thread where @object was constructed. Use %s_dup_%s() if on another thread.\n' + self.outfile.write(' * The returned value is only valid until the property changes so on the client-side it is only safe to use this function on the thread where @object was constructed. Use %s_dup_%s() if on another thread.\n' ' *\n' ' * Returns: (transfer none) (nullable): The property value or %%NULL if the property is not set. Do not free the returned value, it belongs to @object.\n' %(i.name_lower, p.name_lower)) @@ -1662,9 +1665,6 @@ class CodeGenerator: def generate_method_calls(self, i): for m in i.methods: - unix_fd = False - if utils.lookup_annotation(m.annotations, 'org.gtk.GDBus.C.UnixFD'): - unix_fd = True # async begin self.outfile.write('/**\n' ' * %s_call_%s:\n' @@ -1672,7 +1672,12 @@ class CodeGenerator: %(i.name_lower, m.name_lower, i.camel_name)) for a in m.in_args: self.outfile.write(' * @arg_%s: Argument to pass with the method invocation.\n'%(a.name)) - if unix_fd: + if self.glib_min_required >= (2, 64): + self.outfile.write(' * @call_flags: Flags from the #GDBusCallFlags enumeration. If you want to allow interactive\n' + ' authorization be sure to set %G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION.\n' + ' * @timeout_msec: The timeout in milliseconds (with %G_MAXINT meaning "infinite") or\n' + ' -1 to use the proxy default timeout.\n') + if m.unix_fd: self.outfile.write(' * @fd_list: (nullable): A #GUnixFDList or %NULL.\n') self.outfile.write(self.docbook_gen.expand( ' * @cancellable: (nullable): A #GCancellable or %%NULL.\n' @@ -1680,7 +1685,7 @@ class CodeGenerator: ' * @user_data: User data to pass to @callback.\n' ' *\n' ' * Asynchronously invokes the %s.%s() D-Bus method on @proxy.\n' - ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from.\n' + ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from (see g_main_context_push_thread_default()).\n' ' * You can then call %s_call_%s_finish() to get the result of the operation.\n' ' *\n' ' * See %s_call_%s_sync() for the synchronous, blocking version of this method.\n' @@ -1691,14 +1696,17 @@ class CodeGenerator: ' %s *proxy'%(i.name_lower, m.name_lower, i.camel_name)) for a in m.in_args: self.outfile.write(',\n %sarg_%s'%(a.ctype_in, a.name)) - if unix_fd: + if self.glib_min_required >= (2, 64): + self.outfile.write(',\n GDBusCallFlags call_flags' + ',\n gint timeout_msec') + if m.unix_fd: self.outfile.write(',\n GUnixFDList *fd_list') self.outfile.write(',\n' ' GCancellable *cancellable,\n' ' GAsyncReadyCallback callback,\n' ' gpointer user_data)\n' '{\n') - if unix_fd: + if m.unix_fd: self.outfile.write(' g_dbus_proxy_call_with_unix_fd_list (G_DBUS_PROXY (proxy),\n') else: self.outfile.write(' g_dbus_proxy_call (G_DBUS_PROXY (proxy),\n') @@ -1709,10 +1717,14 @@ class CodeGenerator: self.outfile.write(')"') for a in m.in_args: self.outfile.write(',\n arg_%s'%(a.name)) - self.outfile.write('),\n' - ' G_DBUS_CALL_FLAGS_NONE,\n' - ' -1,\n') - if unix_fd: + self.outfile.write('),\n') + if self.glib_min_required >= (2, 64): + self.outfile.write(' call_flags,\n' + ' timeout_msec,\n') + else: + self.outfile.write(' G_DBUS_CALL_FLAGS_NONE,\n' + ' -1,\n') + if m.unix_fd: self.outfile.write(' fd_list,\n') self.outfile.write(' cancellable,\n' ' callback,\n' @@ -1726,7 +1738,7 @@ class CodeGenerator: %(i.name_lower, m.name_lower, i.camel_name)) for a in m.out_args: self.outfile.write(' * @out_%s: (out) (optional)%s: Return location for return parameter or %%NULL to ignore.\n'%(a.name, ' ' + a.array_annotation if a.array_annotation else '')) - if unix_fd: + if m.unix_fd: self.outfile.write(' * @out_fd_list: (out) (optional): Return location for a #GUnixFDList or %NULL to ignore.\n') self.outfile.write(self.docbook_gen.expand( ' * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to %s_call_%s().\n' @@ -1742,14 +1754,14 @@ class CodeGenerator: ' %s *proxy'%(i.name_lower, m.name_lower, i.camel_name)) for a in m.out_args: self.outfile.write(',\n %sout_%s'%(a.ctype_out, a.name)) - if unix_fd: + if m.unix_fd: self.outfile.write(',\n GUnixFDList **out_fd_list') self.outfile.write(',\n' ' GAsyncResult *res,\n' ' GError **error)\n' '{\n' ' GVariant *_ret;\n') - if unix_fd: + if m.unix_fd: self.outfile.write(' _ret = g_dbus_proxy_call_with_unix_fd_list_finish (G_DBUS_PROXY (proxy), out_fd_list, res, error);\n') else: self.outfile.write(' _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error);\n') @@ -1777,11 +1789,16 @@ class CodeGenerator: %(i.name_lower, m.name_lower, i.camel_name)) for a in m.in_args: self.outfile.write(' * @arg_%s: Argument to pass with the method invocation.\n'%(a.name)) - if unix_fd: + if self.glib_min_required >= (2, 64): + self.outfile.write(' * @call_flags: Flags from the #GDBusCallFlags enumeration. If you want to allow interactive\n' + ' authorization be sure to set %G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION.\n' + ' * @timeout_msec: The timeout in milliseconds (with %G_MAXINT meaning "infinite") or\n' + ' -1 to use the proxy default timeout.\n') + if m.unix_fd: self.outfile.write(' * @fd_list: (nullable): A #GUnixFDList or %NULL.\n') for a in m.out_args: self.outfile.write(' * @out_%s: (out) (optional)%s: Return location for return parameter or %%NULL to ignore.\n'%(a.name, ' ' + a.array_annotation if a.array_annotation else '')) - if unix_fd: + if m.unix_fd: self.outfile.write(' * @out_fd_list: (out): Return location for a #GUnixFDList or %NULL.\n') self.outfile.write(self.docbook_gen.expand( ' * @cancellable: (nullable): A #GCancellable or %%NULL.\n' @@ -1799,18 +1816,21 @@ class CodeGenerator: ' %s *proxy'%(i.name_lower, m.name_lower, i.camel_name)) for a in m.in_args: self.outfile.write(',\n %sarg_%s'%(a.ctype_in, a.name)) - if unix_fd: + if self.glib_min_required >= (2, 64): + self.outfile.write(',\n GDBusCallFlags call_flags' + ',\n gint timeout_msec') + if m.unix_fd: self.outfile.write(',\n GUnixFDList *fd_list') for a in m.out_args: self.outfile.write(',\n %sout_%s'%(a.ctype_out, a.name)) - if unix_fd: + if m.unix_fd: self.outfile.write(',\n GUnixFDList **out_fd_list') self.outfile.write(',\n' ' GCancellable *cancellable,\n' ' GError **error)\n' '{\n' ' GVariant *_ret;\n') - if unix_fd: + if m.unix_fd: self.outfile.write(' _ret = g_dbus_proxy_call_with_unix_fd_list_sync (G_DBUS_PROXY (proxy),\n') else: self.outfile.write(' _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy),\n') @@ -1821,10 +1841,14 @@ class CodeGenerator: self.outfile.write(')"') for a in m.in_args: self.outfile.write(',\n arg_%s'%(a.name)) - self.outfile.write('),\n' - ' G_DBUS_CALL_FLAGS_NONE,\n' - ' -1,\n') - if unix_fd: + self.outfile.write('),\n') + if self.glib_min_required >= (2, 64): + self.outfile.write(' call_flags,\n' + ' timeout_msec,\n') + else: + self.outfile.write(' G_DBUS_CALL_FLAGS_NONE,\n' + ' -1,\n') + if m.unix_fd: self.outfile.write(' fd_list,\n' ' out_fd_list,\n') self.outfile.write(' cancellable,\n' @@ -1849,15 +1873,12 @@ class CodeGenerator: def generate_method_completers(self, i): for m in i.methods: - unix_fd = False - if utils.lookup_annotation(m.annotations, 'org.gtk.GDBus.C.UnixFD'): - unix_fd = True self.outfile.write('/**\n' ' * %s_complete_%s:\n' ' * @object: A #%s.\n' ' * @invocation: (transfer full): A #GDBusMethodInvocation.\n' %(i.name_lower, m.name_lower, i.camel_name)) - if unix_fd: + if m.unix_fd: self.outfile.write(' * @fd_list: (nullable): A #GUnixFDList or %NULL.\n') for a in m.out_args: self.outfile.write(' * @%s: Parameter to return.\n'%(a.name)) @@ -1872,14 +1893,14 @@ class CodeGenerator: '%s_complete_%s (\n' ' %s *object,\n' ' GDBusMethodInvocation *invocation'%(i.name_lower, m.name_lower, i.camel_name)) - if unix_fd: + if m.unix_fd: self.outfile.write(',\n GUnixFDList *fd_list') for a in m.out_args: self.outfile.write(',\n %s%s'%(a.ctype_in, a.name)) self.outfile.write(')\n' '{\n') - if unix_fd: + if m.unix_fd: self.outfile.write(' g_dbus_method_invocation_return_value_with_unix_fd_list (invocation,\n' ' g_variant_new ("(') else: @@ -1890,7 +1911,7 @@ class CodeGenerator: self.outfile.write(')"') for a in m.out_args: self.outfile.write(',\n %s'%(a.name)) - if unix_fd: + if m.unix_fd: self.outfile.write('),\n fd_list);\n') else: self.outfile.write('));\n') @@ -2226,7 +2247,7 @@ class CodeGenerator: ' *\n' ' * Asynchronously creates a proxy for the D-Bus interface #%s. See g_dbus_proxy_new() for more details.\n' ' *\n' - ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from.\n' + ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from (see g_main_context_push_thread_default()).\n' ' * You can then call %s_proxy_new_finish() to get the result of the operation.\n' ' *\n' ' * See %s_proxy_new_sync() for the synchronous, blocking version of this constructor.\n' @@ -2324,7 +2345,7 @@ class CodeGenerator: ' *\n' ' * Like %s_proxy_new() but takes a #GBusType instead of a #GDBusConnection.\n' ' *\n' - ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from.\n' + ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from (see g_main_context_push_thread_default()).\n' ' * You can then call %s_proxy_new_for_bus_finish() to get the result of the operation.\n' ' *\n' ' * See %s_proxy_new_for_bus_sync() for the synchronous, blocking version of this constructor.\n' @@ -3113,7 +3134,7 @@ class CodeGenerator: ' *\n' ' * Like %sobject_get_%s() but doesn\'t increase the reference count on the returned object.\n' ' *\n' - ' * It is not safe to use the returned object if you are on another thread than the one where the #GDBusObjectManagerClient or #GDBusObjectManagerServer for @object is running.\n' + ' * It is not safe to use the returned object if you are on another thread than the one where the #GDBusObjectManagerClient or #GDBusObjectManagerServer for @object is running.\n' ' *\n' ' * Returns: (transfer none) (nullable): A #%s or %%NULL if @object does not implement the interface. Do not free the returned object, it is owned by @object.\n' %(self.ns_lower, i.name_upper.lower(), self.namespace, self.ns_lower, i.name_upper.lower(), i.camel_name), False)) @@ -3532,7 +3553,7 @@ class CodeGenerator: ' *\n' ' * Asynchronously creates #GDBusObjectManagerClient using %sobject_manager_client_get_proxy_type() as the #GDBusProxyTypeFunc. See g_dbus_object_manager_client_new() for more details.\n' ' *\n' - ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from.\n' + ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from (see g_main_context_push_thread_default()).\n' ' * You can then call %sobject_manager_client_new_finish() to get the result of the operation.\n' ' *\n' ' * See %sobject_manager_client_new_sync() for the synchronous, blocking version of this constructor.\n' @@ -3630,7 +3651,7 @@ class CodeGenerator: ' *\n' ' * Like %sobject_manager_client_new() but takes a #GBusType instead of a #GDBusConnection.\n' ' *\n' - ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from.\n' + ' * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from (see g_main_context_push_thread_default()).\n' ' * You can then call %sobject_manager_client_new_for_bus_finish() to get the result of the operation.\n' ' *\n' ' * See %sobject_manager_client_new_for_bus_sync() for the synchronous, blocking version of this constructor.\n' diff --git a/gio/gdbus-2.0/codegen/codegen_main.py b/gio/gdbus-2.0/codegen/codegen_main.py index 7683f0a..75d97e3 100644 --- a/gio/gdbus-2.0/codegen/codegen_main.py +++ b/gio/gdbus-2.0/codegen/codegen_main.py @@ -149,7 +149,7 @@ def apply_annotations(iface_list, annotation_list): def codegen_main(): arg_parser = argparse.ArgumentParser(description='D-Bus code and documentation generator') - arg_parser.add_argument('files', metavar='FILE', nargs='*', + arg_parser.add_argument('files', metavar='FILE', nargs='+', help='D-Bus introspection XML file') arg_parser.add_argument('--xml-files', metavar='FILE', action='append', default=[], help=argparse.SUPPRESS) @@ -167,6 +167,10 @@ def codegen_main(): help='Use "pragma once" as the inclusion guard') arg_parser.add_argument('--annotate', nargs=3, action='append', metavar='WHAT KEY VALUE', help='Add annotation (may be used several times)') + arg_parser.add_argument('--glib-min-required', metavar='VERSION', + help='Minimum version of GLib to be supported by the outputted code (default: 2.30)') + arg_parser.add_argument('--glib-max-allowed', metavar='VERSION', + help='Maximum version of GLib to be used by the outputted code (default: current GLib version)') group = arg_parser.add_mutually_exclusive_group() group.add_argument('--generate-c-code', metavar='OUTFILES', @@ -233,12 +237,55 @@ def codegen_main(): c_file = args.output header_name = os.path.splitext(os.path.basename(c_file))[0] + '.h' + # Check the minimum GLib version. The minimum --glib-min-required is 2.30, + # because that’s when gdbus-codegen was introduced. Support 1, 2 or 3 + # component versions, but ignore the micro component if it’s present. + if args.glib_min_required: + try: + parts = args.glib_min_required.split('.', 3) + glib_min_required = (int(parts[0]), + int(parts[1] if len(parts) > 1 else 0)) + # Ignore micro component, but still validate it: + _ = int(parts[2] if len(parts) > 2 else 0) + except (ValueError, IndexError): + print_error('Unrecognized --glib-min-required string ‘{}’'.format( + args.glib_min_required)) + + if glib_min_required < (2, 30): + print_error('Invalid --glib-min-required string ‘{}’: minimum ' + 'version is 2.30'.format(args.glib_min_required)) + else: + glib_min_required = (2, 30) + + # And the maximum GLib version. + if args.glib_max_allowed: + try: + parts = args.glib_max_allowed.split('.', 3) + glib_max_allowed = (int(parts[0]), + int(parts[1] if len(parts) > 1 else 0)) + # Ignore micro component, but still validate it: + _ = int(parts[2] if len(parts) > 2 else 0) + except (ValueError, IndexError): + print_error('Unrecognized --glib-max-allowed string ‘{}’'.format( + args.glib_max_allowed)) + else: + glib_max_allowed = (config.MAJOR_VERSION, config.MINOR_VERSION) + + # Round --glib-max-allowed up to the next stable release. + glib_max_allowed = \ + (glib_max_allowed[0], glib_max_allowed[1] + (glib_max_allowed[1] % 2)) + + if glib_max_allowed < glib_min_required: + print_error('Invalid versions: --glib-min-required ({}) must be ' + 'less than or equal to --glib-max-allowed ({})'.format(glib_min_required, glib_max_allowed)) + all_ifaces = [] input_files_basenames = [] for fname in sorted(args.files + args.xml_files): with open(fname, 'rb') as f: xml_data = f.read() - parsed_ifaces = parser.parse_dbus_xml(xml_data) + parsed_ifaces = parser.parse_dbus_xml(xml_data, + h_type_implies_unix_fd=(glib_min_required >= (2, 64))) all_ifaces.extend(parsed_ifaces) input_files_basenames.append(os.path.basename(fname)) @@ -262,6 +309,7 @@ def codegen_main(): header_name, input_files_basenames, args.pragma_once, + glib_min_required, outfile) gen.generate() @@ -273,6 +321,7 @@ def codegen_main(): header_name, input_files_basenames, docbook_gen, + glib_min_required, outfile) gen.generate() @@ -283,6 +332,7 @@ def codegen_main(): header_name, input_files_basenames, args.pragma_once, + glib_min_required, outfile) gen.generate() @@ -292,6 +342,7 @@ def codegen_main(): args.c_namespace, header_name, input_files_basenames, + glib_min_required, outfile) gen.generate() diff --git a/gio/gdbus-2.0/codegen/config.py.in b/gio/gdbus-2.0/codegen/config.py.in index a91178a..4e6df5a 100644 --- a/gio/gdbus-2.0/codegen/config.py.in +++ b/gio/gdbus-2.0/codegen/config.py.in @@ -20,3 +20,5 @@ # Author: David Zeuthen VERSION = "@VERSION@" +MAJOR_VERSION = @MAJOR_VERSION@ +MINOR_VERSION = @MINOR_VERSION@ diff --git a/gio/gdbus-2.0/codegen/dbustypes.py b/gio/gdbus-2.0/codegen/dbustypes.py index 60c53c4..415a5cc 100644 --- a/gio/gdbus-2.0/codegen/dbustypes.py +++ b/gio/gdbus-2.0/codegen/dbustypes.py @@ -252,14 +252,16 @@ class Arg: a.post_process(interface_prefix, cns, cns_upper, cns_lower, self) class Method: - def __init__(self, name): + def __init__(self, name, h_type_implies_unix_fd=True): self.name = name + self.h_type_implies_unix_fd = h_type_implies_unix_fd self.in_args = [] self.out_args = [] self.annotations = [] self.doc_string = '' self.since = '' self.deprecated = False + self.unix_fd = False def post_process(self, interface_prefix, cns, cns_upper, cns_lower, containing_iface): if len(self.doc_string) == 0: @@ -283,14 +285,21 @@ class Method: for a in self.in_args: a.post_process(interface_prefix, cns, cns_upper, cns_lower, arg_count) arg_count += 1 + if self.h_type_implies_unix_fd and 'h' in a.signature: + self.unix_fd = True for a in self.out_args: a.post_process(interface_prefix, cns, cns_upper, cns_lower, arg_count) arg_count += 1 + if self.h_type_implies_unix_fd and 'h' in a.signature: + self.unix_fd = True if utils.lookup_annotation(self.annotations, 'org.freedesktop.DBus.Deprecated') == 'true': self.deprecated = True + if utils.lookup_annotation(self.annotations, 'org.gtk.GDBus.C.UnixFD'): + self.unix_fd = True + for a in self.annotations: a.post_process(interface_prefix, cns, cns_upper, cns_lower, self) diff --git a/gio/gdbus-2.0/codegen/meson.build b/gio/gdbus-2.0/codegen/meson.build index 121e9e6..c0caf0e 100644 --- a/gio/gdbus-2.0/codegen/meson.build +++ b/gio/gdbus-2.0/codegen/meson.build @@ -10,6 +10,8 @@ gdbus_codegen_files = [ gdbus_codegen_conf = configuration_data() gdbus_codegen_conf.set('VERSION', glib_version) +gdbus_codegen_conf.set('MAJOR_VERSION', major_version) +gdbus_codegen_conf.set('MINOR_VERSION', minor_version) gdbus_codegen_conf.set('PYTHON', python_name) gdbus_codegen_conf.set('DATADIR', glib_datadir) @@ -22,7 +24,7 @@ gdbus_codegen = configure_file(input : 'gdbus-codegen.in', # Provide tools for others when we're a subproject and they use the Meson GNOME module meson.override_find_program('gdbus-codegen', gdbus_codegen) -codegen_dir = join_paths(get_option('datadir'), 'glib-2.0/codegen') +codegen_dir = join_paths(glib_datadir, 'glib-2.0', 'codegen') gdbus_codegen_built_files = [] gdbus_codegen_built_files += configure_file(input : 'config.py.in', diff --git a/gio/gdbus-2.0/codegen/parser.py b/gio/gdbus-2.0/codegen/parser.py index f49136d..7dcc735 100644 --- a/gio/gdbus-2.0/codegen/parser.py +++ b/gio/gdbus-2.0/codegen/parser.py @@ -36,7 +36,7 @@ class DBusXMLParser: STATE_ANNOTATION = 'annotation' STATE_IGNORED = 'ignored' - def __init__(self, xml_data): + def __init__(self, xml_data, h_type_implies_unix_fd=True): self._parser = xml.parsers.expat.ParserCreate() self._parser.CommentHandler = self.handle_comment self._parser.CharacterDataHandler = self.handle_char_data @@ -53,6 +53,8 @@ class DBusXMLParser: self.doc_comment_last_symbol = '' + self._h_type_implies_unix_fd = h_type_implies_unix_fd + self._parser.Parse(xml_data) COMMENT_STATE_BEGIN = 'begin' @@ -163,7 +165,8 @@ class DBusXMLParser: elif self.state == DBusXMLParser.STATE_INTERFACE: if name == DBusXMLParser.STATE_METHOD: self.state = DBusXMLParser.STATE_METHOD - method = dbustypes.Method(attrs['name']) + method = dbustypes.Method(attrs['name'], + h_type_implies_unix_fd=self._h_type_implies_unix_fd) self._cur_object.methods.append(method) self._cur_object = method elif name == DBusXMLParser.STATE_SIGNAL: @@ -288,6 +291,6 @@ class DBusXMLParser: self.state = self.state_stack.pop() self._cur_object = self._cur_object_stack.pop() -def parse_dbus_xml(xml_data): - parser = DBusXMLParser(xml_data) +def parse_dbus_xml(xml_data, h_type_implies_unix_fd): + parser = DBusXMLParser(xml_data, h_type_implies_unix_fd) return parser.parsed_interfaces diff --git a/gio/gdbus-tool.c b/gio/gdbus-tool.c index 57978f1..863a5e8 100644 --- a/gio/gdbus-tool.c +++ b/gio/gdbus-tool.c @@ -414,7 +414,8 @@ connection_get_group (void) } static GDBusConnection * -connection_get_dbus_connection (GError **error) +connection_get_dbus_connection (gboolean require_message_bus, + GError **error) { GDBusConnection *c; @@ -450,8 +451,11 @@ connection_get_dbus_connection (GError **error) } else if (opt_connection_address != NULL) { + GDBusConnectionFlags flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT; + if (require_message_bus) + flags |= G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION; c = g_dbus_connection_new_for_address_sync (opt_connection_address, - G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + flags, NULL, /* GDBusAuthObserver */ NULL, /* GCancellable */ error); @@ -649,7 +653,7 @@ handle_emit (gint *argc, } error = NULL; - c = connection_get_dbus_connection (&error); + c = connection_get_dbus_connection ((opt_emit_dest != NULL), &error); if (c == NULL) { if (request_completion) @@ -956,7 +960,7 @@ handle_call (gint *argc, } error = NULL; - c = connection_get_dbus_connection (&error); + c = connection_get_dbus_connection (TRUE, &error); if (c == NULL) { if (request_completion) @@ -1750,7 +1754,7 @@ handle_introspect (gint *argc, } error = NULL; - c = connection_get_dbus_connection (&error); + c = connection_get_dbus_connection (TRUE, &error); if (c == NULL) { if (request_completion) @@ -1982,7 +1986,7 @@ handle_monitor (gint *argc, } error = NULL; - c = connection_get_dbus_connection (&error); + c = connection_get_dbus_connection (TRUE, &error); if (c == NULL) { if (request_completion) @@ -2202,7 +2206,7 @@ handle_wait (gint *argc, } error = NULL; - c = connection_get_dbus_connection (&error); + c = connection_get_dbus_connection (TRUE, &error); if (c == NULL) { if (request_completion) diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c index 3015457..6958d7c 100644 --- a/gio/gdbusaddress.c +++ b/gio/gdbusaddress.c @@ -244,8 +244,8 @@ is_valid_nonce_tcp (const gchar *address_entry, g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - _("Error in address “%s” — the port attribute is malformed"), - address_entry); + _("Error in address “%s” — the “%s” attribute is malformed"), + address_entry, "port"); goto out; } } @@ -255,8 +255,8 @@ is_valid_nonce_tcp (const gchar *address_entry, g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - _("Error in address “%s” — the family attribute is malformed"), - address_entry); + _("Error in address “%s” — the “%s” attribute is malformed"), + address_entry, "family"); goto out; } @@ -265,9 +265,17 @@ is_valid_nonce_tcp (const gchar *address_entry, /* TODO: validate host */ } - nonce_file = nonce_file; /* To avoid -Wunused-but-set-variable */ + if (nonce_file != NULL && *nonce_file == '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address “%s” — the “%s” attribute is malformed"), + address_entry, "noncefile"); + goto out; + } - ret= TRUE; + ret = TRUE; out: g_list_free (keys); @@ -325,8 +333,8 @@ is_valid_tcp (const gchar *address_entry, g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - _("Error in address “%s” — the port attribute is malformed"), - address_entry); + _("Error in address “%s” — the “%s” attribute is malformed"), + address_entry, "port"); goto out; } } @@ -336,8 +344,8 @@ is_valid_tcp (const gchar *address_entry, g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - _("Error in address “%s” — the family attribute is malformed"), - address_entry); + _("Error in address “%s” — the “%s” attribute is malformed"), + address_entry, "family"); goto out; } diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c index 2754d3c..5e3e93d 100644 --- a/gio/gdbusauthmechanismsha1.c +++ b/gio/gdbusauthmechanismsha1.c @@ -234,6 +234,9 @@ ensure_keyring_directory (GError **error) { gchar *path; const gchar *e; +#ifdef G_OS_UNIX + struct stat statbuf; +#endif g_return_val_if_fail (error == NULL || *error == NULL, NULL); @@ -249,48 +252,54 @@ ensure_keyring_directory (GError **error) NULL); } - if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) +#ifdef G_OS_UNIX + if (stat (path, &statbuf) != 0) { - if (g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION") == NULL) + int errsv = errno; + + if (errsv != ENOENT) { -#ifdef G_OS_UNIX - struct stat statbuf; - if (stat (path, &statbuf) != 0) - { - int errsv = errno; - g_set_error (error, - G_IO_ERROR, - g_io_error_from_errno (errsv), - _("Error when getting information for directory “%s”: %s"), - path, - g_strerror (errsv)); - g_free (path); - path = NULL; - goto out; - } - if ((statbuf.st_mode & 0777) != 0700) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, - _("Permissions on directory “%s” are malformed. Expected mode 0700, got 0%o"), - path, - (guint) (statbuf.st_mode & 0777)); - g_free (path); - path = NULL; - goto out; - } -#else + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error when getting information for directory “%s”: %s"), + path, + g_strerror (errsv)); + g_clear_pointer (&path, g_free); + return NULL; + } + } + else if (S_ISDIR (statbuf.st_mode)) + { + if (g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION") == NULL && + (statbuf.st_mode & 0777) != 0700) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Permissions on directory “%s” are malformed. Expected mode 0700, got 0%o"), + path, + (guint) (statbuf.st_mode & 0777)); + g_clear_pointer (&path, g_free); + return NULL; + } + + return g_steal_pointer (&path); + } +#else /* if !G_OS_UNIX */ + /* On non-Unix platforms, check that it exists as a directory, but don’t do + * permissions checks at the moment. */ + if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) + { #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic warning "-Wcpp" #warning Please implement permission checking on this non-UNIX platform #pragma GCC diagnostic pop -#endif -#endif - } - goto out; +#endif /* __GNUC__ */ + return g_steal_pointer (&path); } +#endif /* if !G_OS_UNIX */ if (g_mkdir_with_parents (path, 0700) != 0) { @@ -301,13 +310,11 @@ ensure_keyring_directory (GError **error) _("Error creating directory “%s”: %s"), path, g_strerror (errsv)); - g_free (path); - path = NULL; - goto out; + g_clear_pointer (&path, g_free); + return NULL; } -out: - return path; + return g_steal_pointer (&path); } /* ---------------------------------------------------------------------------------------------------- */ @@ -450,25 +457,52 @@ _log (const gchar *message, g_free (s); } +/* Returns FD for lock file, if it was created exclusively (didn't exist already, + * and was created successfully) */ +static gint +create_lock_exclusive (const gchar *lock_path, + GError **error) +{ + int errsv; + gint ret; + + ret = g_open (lock_path, O_CREAT | O_EXCL, 0600); + errsv = errno; + if (ret < 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error creating lock file “%s”: %s"), + lock_path, + g_strerror (errsv)); + return -1; + } + + return ret; +} + static gint keyring_acquire_lock (const gchar *path, GError **error) { - gchar *lock; + gchar *lock = NULL; gint ret; guint num_tries; -#ifdef EEXISTS - guint num_create_tries; -#endif int errsv; - g_return_val_if_fail (path != NULL, FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + /* Total possible sleep period = max_tries * timeout_usec = 0.5s */ + const guint max_tries = 50; + const guint timeout_usec = 1000 * 10; + + g_return_val_if_fail (path != NULL, -1); + g_return_val_if_fail (error == NULL || *error == NULL, -1); ret = -1; - lock = g_strdup_printf ("%s.lock", path); + lock = g_strconcat (path, ".lock", NULL); /* This is what the D-Bus spec says + * (https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha) * * Create a lockfile name by appending ".lock" to the name of the * cookie file. The server should attempt to create this file using @@ -481,66 +515,43 @@ keyring_acquire_lock (const gchar *path, * real locking implementations are still flaky on network filesystems */ -#ifdef EEXISTS - num_create_tries = 0; - again: -#endif - num_tries = 0; - while (g_file_test (lock, G_FILE_TEST_EXISTS)) + for (num_tries = 0; num_tries < max_tries; num_tries++) { + /* Ignore the error until the final call. */ + ret = create_lock_exclusive (lock, NULL); + if (ret >= 0) + break; + /* sleep 10ms, then try again */ - g_usleep (1000*10); - num_tries++; - if (num_tries == 50) - { - /* ok, we slept 50*10ms = 0.5 seconds. Conclude that the lock file must be - * stale (nuke the it from orbit) - */ - if (g_unlink (lock) != 0) - { - errsv = errno; - g_set_error (error, - G_IO_ERROR, - g_io_error_from_errno (errsv), - _("Error deleting stale lock file “%s”: %s"), - lock, - g_strerror (errsv)); - goto out; - } - _log ("Deleted stale lock file '%s'", lock); - break; - } + g_usleep (timeout_usec); } - ret = g_open (lock, O_CREAT | -#ifdef O_EXCL - O_EXCL, -#else - 0, -#endif - 0700); - errsv = errno; - if (ret == -1) + if (num_tries == max_tries) { -#ifdef EEXISTS - /* EEXIST: pathname already exists and O_CREAT and O_EXCL were used. */ - if (errsv == EEXISTS) + /* ok, we slept 50*10ms = 0.5 seconds. Conclude that the lock file must be + * stale (nuke it from orbit) + */ + if (g_unlink (lock) != 0) { - num_create_tries++; - if (num_create_tries < 5) - goto again; + errsv = errno; + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error deleting stale lock file “%s”: %s"), + lock, + g_strerror (errsv)); + goto out; } -#endif - g_set_error (error, - G_IO_ERROR, - g_io_error_from_errno (errsv), - _("Error creating lock file “%s”: %s"), - lock, - g_strerror (errsv)); - goto out; + + _log ("Deleted stale lock file '%s'", lock); + + /* Try one last time to create it, now that we've deleted the stale one */ + ret = create_lock_exclusive (lock, error); + if (ret < 0) + goto out; } - out: +out: g_free (lock); return ret; } diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c index f1f0921..1a4dae3 100644 --- a/gio/gdbusconnection.c +++ b/gio/gdbusconnection.c @@ -222,7 +222,6 @@ typedef struct { GDestroyNotify callback; gpointer user_data; - GMainContext *context; } CallDestroyNotifyData; static gboolean @@ -236,8 +235,6 @@ call_destroy_notify_data_in_idle (gpointer user_data) static void call_destroy_notify_data_free (CallDestroyNotifyData *data) { - if (data->context != NULL) - g_main_context_unref (data->context); g_free (data); } @@ -258,14 +255,11 @@ call_destroy_notify (GMainContext *context, CallDestroyNotifyData *data; if (callback == NULL) - goto out; + return; data = g_new0 (CallDestroyNotifyData, 1); data->callback = callback; data->user_data = user_data; - data->context = context; - if (data->context != NULL) - g_main_context_ref (data->context); idle_source = g_idle_source_new (); g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); @@ -274,11 +268,8 @@ call_destroy_notify (GMainContext *context, data, (GDestroyNotify) call_destroy_notify_data_free); g_source_set_name (idle_source, "[gio] call_destroy_notify_data_in_idle"); - g_source_attach (idle_source, data->context); + g_source_attach (idle_source, context); g_source_unref (idle_source); - - out: - ; } /* ---------------------------------------------------------------------------------------------------- */ @@ -3148,7 +3139,7 @@ g_dbus_connection_add_filter (GDBusConnection *connection, CONNECTION_LOCK (connection); data = g_new0 (FilterData, 1); - data->id = g_atomic_int_add (&_global_filter_id, 1); /* TODO: overflow etc. */ + data->id = (guint) g_atomic_int_add (&_global_filter_id, 1); /* TODO: overflow etc. */ data->ref_count = 1; data->filter_function = filter_function; data->user_data = user_data; @@ -3248,18 +3239,9 @@ typedef struct gchar *object_path; gchar *arg0; GDBusSignalFlags flags; - GArray *subscribers; + GPtrArray *subscribers; /* (owned) (element-type SignalSubscriber) */ } SignalData; -typedef struct -{ - GDBusSignalCallback callback; - gpointer user_data; - GDestroyNotify user_data_free_func; - guint id; - GMainContext *context; -} SignalSubscriber; - static void signal_data_free (SignalData *signal_data) { @@ -3270,10 +3252,46 @@ signal_data_free (SignalData *signal_data) g_free (signal_data->member); g_free (signal_data->object_path); g_free (signal_data->arg0); - g_array_free (signal_data->subscribers, TRUE); + g_ptr_array_unref (signal_data->subscribers); g_free (signal_data); } +typedef struct +{ + /* All fields are immutable after construction. */ + gatomicrefcount ref_count; + GDBusSignalCallback callback; + gpointer user_data; + GDestroyNotify user_data_free_func; + guint id; + GMainContext *context; +} SignalSubscriber; + +static SignalSubscriber * +signal_subscriber_ref (SignalSubscriber *subscriber) +{ + g_atomic_ref_count_inc (&subscriber->ref_count); + return subscriber; +} + +static void +signal_subscriber_unref (SignalSubscriber *subscriber) +{ + if (g_atomic_ref_count_dec (&subscriber->ref_count)) + { + /* Destroy the user data. It doesn’t matter which thread + * signal_subscriber_unref() is called in (or whether it’s called with a + * lock held), as call_destroy_notify() always defers to the next + * #GMainContext iteration. */ + call_destroy_notify (subscriber->context, + subscriber->user_data_free_func, + subscriber->user_data); + + g_main_context_unref (subscriber->context); + g_free (subscriber); + } +} + static gchar * args_to_rule (const gchar *sender, const gchar *interface_name, @@ -3441,6 +3459,24 @@ is_signal_data_for_name_lost_or_acquired (SignalData *signal_data) * signal is unsubscribed from, and may be called after @connection * has been destroyed.) * + * As @callback is potentially invoked in a different thread from where it’s + * emitted, it’s possible for this to happen after + * g_dbus_connection_signal_unsubscribe() has been called in another thread. + * Due to this, @user_data should have a strong reference which is freed with + * @user_data_free_func, rather than pointing to data whose lifecycle is tied + * to the signal subscription. For example, if a #GObject is used to store the + * subscription ID from g_dbus_connection_signal_subscribe(), a strong reference + * to that #GObject must be passed to @user_data, and g_object_unref() passed to + * @user_data_free_func. You are responsible for breaking the resulting + * reference count cycle by explicitly unsubscribing from the signal when + * dropping the last external reference to the #GObject. Alternatively, a weak + * reference may be used. + * + * It is guaranteed that if you unsubscribe from a signal using + * g_dbus_connection_signal_unsubscribe() from the same thread which made the + * corresponding g_dbus_connection_signal_subscribe() call, @callback will not + * be invoked after g_dbus_connection_signal_unsubscribe() returns. + * * The returned subscription identifier is an opaque value which is guaranteed * to never be zero. * @@ -3464,7 +3500,7 @@ g_dbus_connection_signal_subscribe (GDBusConnection *connection, { gchar *rule; SignalData *signal_data; - SignalSubscriber subscriber; + SignalSubscriber *subscriber; GPtrArray *signal_data_array; const gchar *sender_unique_name; @@ -3505,17 +3541,19 @@ g_dbus_connection_signal_subscribe (GDBusConnection *connection, else sender_unique_name = ""; - subscriber.callback = callback; - subscriber.user_data = user_data; - subscriber.user_data_free_func = user_data_free_func; - subscriber.id = g_atomic_int_add (&_global_subscriber_id, 1); /* TODO: overflow etc. */ - subscriber.context = g_main_context_ref_thread_default (); + subscriber = g_new0 (SignalSubscriber, 1); + subscriber->ref_count = 1; + subscriber->callback = callback; + subscriber->user_data = user_data; + subscriber->user_data_free_func = user_data_free_func; + subscriber->id = (guint) g_atomic_int_add (&_global_subscriber_id, 1); /* TODO: overflow etc. */ + subscriber->context = g_main_context_ref_thread_default (); /* see if we've already have this rule */ signal_data = g_hash_table_lookup (connection->map_rule_to_signal_data, rule); if (signal_data != NULL) { - g_array_append_val (signal_data->subscribers, subscriber); + g_ptr_array_add (signal_data->subscribers, subscriber); g_free (rule); goto out; } @@ -3529,8 +3567,8 @@ g_dbus_connection_signal_subscribe (GDBusConnection *connection, signal_data->object_path = g_strdup (object_path); signal_data->arg0 = g_strdup (arg0); signal_data->flags = flags; - signal_data->subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber)); - g_array_append_val (signal_data->subscribers, subscriber); + signal_data->subscribers = g_ptr_array_new_with_free_func ((GDestroyNotify) signal_subscriber_unref); + g_ptr_array_add (signal_data->subscribers, subscriber); g_hash_table_insert (connection->map_rule_to_signal_data, signal_data->rule, @@ -3560,26 +3598,27 @@ g_dbus_connection_signal_subscribe (GDBusConnection *connection, out: g_hash_table_insert (connection->map_id_to_signal_data, - GUINT_TO_POINTER (subscriber.id), + GUINT_TO_POINTER (subscriber->id), signal_data); CONNECTION_UNLOCK (connection); - return subscriber.id; + return subscriber->id; } /* ---------------------------------------------------------------------------------------------------- */ /* called in any thread */ -/* must hold lock when calling this (except if connection->finalizing is TRUE) */ -static void +/* must hold lock when calling this (except if connection->finalizing is TRUE) + * returns the number of removed subscribers */ +static guint unsubscribe_id_internal (GDBusConnection *connection, - guint subscription_id, - GArray *out_removed_subscribers) + guint subscription_id) { SignalData *signal_data; GPtrArray *signal_data_array; guint n; + guint n_removed = 0; signal_data = g_hash_table_lookup (connection->map_id_to_signal_data, GUINT_TO_POINTER (subscription_id)); @@ -3591,16 +3630,19 @@ unsubscribe_id_internal (GDBusConnection *connection, for (n = 0; n < signal_data->subscribers->len; n++) { - SignalSubscriber *subscriber; + SignalSubscriber *subscriber = signal_data->subscribers->pdata[n]; - subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, n)); if (subscriber->id != subscription_id) continue; + /* It’s OK to rearrange the array order using the ‘fast’ #GPtrArray + * removal functions, since we’re going to exit the loop below anyway — we + * never move on to the next element. Secondly, subscription IDs are + * guaranteed to be unique. */ g_warn_if_fail (g_hash_table_remove (connection->map_id_to_signal_data, GUINT_TO_POINTER (subscription_id))); - g_array_append_val (out_removed_subscribers, *subscriber); - g_array_remove_index (signal_data->subscribers, n); + n_removed++; + g_ptr_array_remove_index_fast (signal_data->subscribers, n); if (signal_data->subscribers->len == 0) { @@ -3641,7 +3683,7 @@ unsubscribe_id_internal (GDBusConnection *connection, g_assert_not_reached (); out: - ; + return n_removed; } /** @@ -3652,50 +3694,38 @@ unsubscribe_id_internal (GDBusConnection *connection, * * Unsubscribes from signals. * + * Note that there may still be D-Bus traffic to process (relating to this + * signal subscription) in the current thread-default #GMainContext after this + * function has returned. You should continue to iterate the #GMainContext + * until the #GDestroyNotify function passed to + * g_dbus_connection_signal_subscribe() is called, in order to avoid memory + * leaks through callbacks queued on the #GMainContext after it’s stopped being + * iterated. + * * Since: 2.26 */ void g_dbus_connection_signal_unsubscribe (GDBusConnection *connection, guint subscription_id) { - GArray *subscribers; - guint n; + guint n_subscribers_removed G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); g_return_if_fail (check_initialized (connection)); - subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber)); - CONNECTION_LOCK (connection); - unsubscribe_id_internal (connection, - subscription_id, - subscribers); + n_subscribers_removed = unsubscribe_id_internal (connection, subscription_id); CONNECTION_UNLOCK (connection); /* invariant */ - g_assert (subscribers->len == 0 || subscribers->len == 1); - - /* call GDestroyNotify without lock held */ - for (n = 0; n < subscribers->len; n++) - { - SignalSubscriber *subscriber; - subscriber = &(g_array_index (subscribers, SignalSubscriber, n)); - call_destroy_notify (subscriber->context, - subscriber->user_data_free_func, - subscriber->user_data); - g_main_context_unref (subscriber->context); - } - - g_array_free (subscribers, TRUE); + g_assert (n_subscribers_removed == 0 || n_subscribers_removed == 1); } /* ---------------------------------------------------------------------------------------------------- */ typedef struct { - guint subscription_id; - GDBusSignalCallback callback; - gpointer user_data; + SignalSubscriber *subscriber; /* (owned) */ GDBusMessage *message; GDBusConnection *connection; const gchar *sender; @@ -3727,7 +3757,7 @@ emit_signal_instance_in_idle_cb (gpointer data) #if 0 g_print ("in emit_signal_instance_in_idle_cb (id=%d sender=%s path=%s interface=%s member=%s params=%s)\n", - signal_instance->subscription_id, + signal_instance->subscriber->id, signal_instance->sender, signal_instance->path, signal_instance->interface, @@ -3739,18 +3769,18 @@ emit_signal_instance_in_idle_cb (gpointer data) CONNECTION_LOCK (signal_instance->connection); has_subscription = FALSE; if (g_hash_table_lookup (signal_instance->connection->map_id_to_signal_data, - GUINT_TO_POINTER (signal_instance->subscription_id)) != NULL) + GUINT_TO_POINTER (signal_instance->subscriber->id)) != NULL) has_subscription = TRUE; CONNECTION_UNLOCK (signal_instance->connection); if (has_subscription) - signal_instance->callback (signal_instance->connection, - signal_instance->sender, - signal_instance->path, - signal_instance->interface, - signal_instance->member, - parameters, - signal_instance->user_data); + signal_instance->subscriber->callback (signal_instance->connection, + signal_instance->sender, + signal_instance->path, + signal_instance->interface, + signal_instance->member, + parameters, + signal_instance->subscriber->user_data); g_variant_unref (parameters); @@ -3762,6 +3792,7 @@ signal_instance_free (SignalInstance *signal_instance) { g_object_unref (signal_instance->message); g_object_unref (signal_instance->connection); + signal_subscriber_unref (signal_instance->subscriber); g_free (signal_instance); } @@ -3876,16 +3907,12 @@ schedule_callbacks (GDBusConnection *connection, for (m = 0; m < signal_data->subscribers->len; m++) { - SignalSubscriber *subscriber; + SignalSubscriber *subscriber = signal_data->subscribers->pdata[m]; GSource *idle_source; SignalInstance *signal_instance; - subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, m)); - signal_instance = g_new0 (SignalInstance, 1); - signal_instance->subscription_id = subscriber->id; - signal_instance->callback = subscriber->callback; - signal_instance->user_data = subscriber->user_data; + signal_instance->subscriber = signal_subscriber_ref (subscriber); signal_instance->message = g_object_ref (message); signal_instance->connection = g_object_ref (connection); signal_instance->sender = sender; @@ -3954,7 +3981,6 @@ purge_all_signal_subscriptions (GDBusConnection *connection) GHashTableIter iter; gpointer key; GArray *ids; - GArray *subscribers; guint n; ids = g_array_new (FALSE, FALSE, sizeof (guint)); @@ -3965,28 +3991,12 @@ purge_all_signal_subscriptions (GDBusConnection *connection) g_array_append_val (ids, subscription_id); } - subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber)); for (n = 0; n < ids->len; n++) { guint subscription_id = g_array_index (ids, guint, n); - unsubscribe_id_internal (connection, - subscription_id, - subscribers); + unsubscribe_id_internal (connection, subscription_id); } g_array_free (ids, TRUE); - - /* call GDestroyNotify without lock held */ - for (n = 0; n < subscribers->len; n++) - { - SignalSubscriber *subscriber; - subscriber = &(g_array_index (subscribers, SignalSubscriber, n)); - call_destroy_notify (subscriber->context, - subscriber->user_data_free_func, - subscriber->user_data); - g_main_context_unref (subscriber->context); - } - - g_array_free (subscribers, TRUE); } /* ---------------------------------------------------------------------------------------------------- */ @@ -5198,7 +5208,7 @@ g_dbus_connection_register_object (GDBusConnection *connection, } ei = g_new0 (ExportedInterface, 1); - ei->id = g_atomic_int_add (&_global_registration_id, 1); /* TODO: overflow etc. */ + ei->id = (guint) g_atomic_int_add (&_global_registration_id, 1); /* TODO: overflow etc. */ ei->eo = eo; ei->user_data = user_data; ei->user_data_free_func = user_data_free_func; @@ -5726,17 +5736,19 @@ g_dbus_connection_call_done (GObject *source, _g_dbus_debug_print_lock (); g_print ("========================================================================\n" "GDBus-debug:Call:\n" - " <<<< ASYNC COMPLETE %s() (serial %d)\n" - " ", - state->method_name, - g_dbus_message_get_reply_serial (reply)); + " <<<< ASYNC COMPLETE %s()", + state->method_name); + if (reply != NULL) { - g_print ("SUCCESS\n"); + g_print (" (serial %d)\n" + " SUCCESS\n", + g_dbus_message_get_reply_serial (reply)); } else { - g_print ("FAILED: %s\n", + g_print ("\n" + " FAILED: %s\n", error->message); } _g_dbus_debug_print_unlock (); @@ -6858,7 +6870,7 @@ g_dbus_connection_register_subtree (GDBusConnection *connection, es->vtable = _g_dbus_subtree_vtable_copy (vtable); es->flags = flags; - es->id = g_atomic_int_add (&_global_subtree_registration_id, 1); /* TODO: overflow etc. */ + es->id = (guint) g_atomic_int_add (&_global_subtree_registration_id, 1); /* TODO: overflow etc. */ es->user_data = user_data; es->user_data_free_func = user_data_free_func; es->context = g_main_context_ref_thread_default (); diff --git a/gio/gdbusconnection.h b/gio/gdbusconnection.h index c37363c..f7f08a3 100644 --- a/gio/gdbusconnection.h +++ b/gio/gdbusconnection.h @@ -489,7 +489,7 @@ typedef GDBusInterfaceInfo ** (*GDBusSubtreeIntrospectFunc) (GDBusConnection * @object_path: The object path that was registered with g_dbus_connection_register_subtree(). * @interface_name: The D-Bus interface name that the method call or property access is for. * @node: A node that is a child of @object_path (relative to @object_path) or %NULL for the root of the subtree. - * @out_user_data: (nullable) (not optional): Return location for user data to pass to functions in the returned #GDBusInterfaceVTable (never %NULL). + * @out_user_data: (nullable) (not optional): Return location for user data to pass to functions in the returned #GDBusInterfaceVTable. * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree(). * * The type of the @dispatch function in #GDBusSubtreeVTable. diff --git a/gio/gdbusdaemon.c b/gio/gdbusdaemon.c index 0d5058f..7ae55ec 100644 --- a/gio/gdbusdaemon.c +++ b/gio/gdbusdaemon.c @@ -170,6 +170,7 @@ name_new (GDBusDaemon *daemon, const char *str) static Name * name_ref (Name *name) { + g_assert (name->refcount > 0); name->refcount++; return name; } @@ -177,6 +178,7 @@ name_ref (Name *name) static void name_unref (Name *name) { + g_assert (name->refcount > 0); if (--name->refcount == 0) { g_hash_table_remove (name->daemon->names, name->name); @@ -1463,10 +1465,11 @@ filter_function (GDBusConnection *connection, gpointer user_data) { Client *client = user_data; - const char *types[] = {"invalid", "method_call", "method_return", "error", "signal" }; if (0) - g_printerr ("%s%s %s %d(%d) sender: %s destination: %s %s %s.%s\n", + { + const char *types[] = {"invalid", "method_call", "method_return", "error", "signal" }; + g_printerr ("%s%s %s %d(%d) sender: %s destination: %s %s %s.%s\n", client->id, incoming? "->" : "<-", types[g_dbus_message_get_message_type (message)], @@ -1477,6 +1480,7 @@ filter_function (GDBusConnection *connection, g_dbus_message_get_path (message), g_dbus_message_get_interface (message), g_dbus_message_get_member (message)); + } if (incoming) { diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c index 852a704..4680ee7 100644 --- a/gio/gdbusmessage.c +++ b/gio/gdbusmessage.c @@ -58,6 +58,10 @@ #include "glibintl.h" +/* See https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature + * This is 64 containers plus 1 value within them. */ +#define G_DBUS_MAX_TYPE_DEPTH (64 + 1) + typedef struct _GMemoryBuffer GMemoryBuffer; struct _GMemoryBuffer { @@ -1439,17 +1443,27 @@ read_bytes (GMemoryBuffer *mbuf, static GVariant * parse_value_from_blob (GMemoryBuffer *buf, const GVariantType *type, + guint max_depth, gboolean just_align, guint indent, GError **error) { - GVariant *ret; - GError *local_error; + GVariant *ret = NULL; + GError *local_error = NULL; #ifdef DEBUG_SERIALIZER gboolean is_leaf; #endif /* DEBUG_SERIALIZER */ const gchar *type_string; + if (max_depth == 0) + { + g_set_error_literal (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Value nested too deeply")); + goto fail; + } + type_string = g_variant_type_peek_string (type); #ifdef DEBUG_SERIALIZER @@ -1465,12 +1479,9 @@ parse_value_from_blob (GMemoryBuffer *buf, } #endif /* DEBUG_SERIALIZER */ - ret = NULL; - #ifdef DEBUG_SERIALIZER is_leaf = TRUE; #endif /* DEBUG_SERIALIZER */ - local_error = NULL; switch (type_string[0]) { case 'b': /* G_VARIANT_TYPE_BOOLEAN */ @@ -1690,6 +1701,17 @@ parse_value_from_blob (GMemoryBuffer *buf, goto fail; } + if (max_depth == 1) + { + /* If we had recursed into parse_value_from_blob() again to + * parse the array values, this would have been emitted. */ + g_set_error_literal (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Value nested too deeply")); + goto fail; + } + ensure_input_padding (buf, fixed_size); array_data = read_bytes (buf, array_len, &local_error); if (array_data == NULL) @@ -1717,6 +1739,7 @@ parse_value_from_blob (GMemoryBuffer *buf, GVariant *item G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; item = parse_value_from_blob (buf, element_type, + max_depth - 1, TRUE, indent + 2, NULL); @@ -1731,6 +1754,7 @@ parse_value_from_blob (GMemoryBuffer *buf, GVariant *item; item = parse_value_from_blob (buf, element_type, + max_depth - 1, FALSE, indent + 2, &local_error); @@ -1770,6 +1794,7 @@ parse_value_from_blob (GMemoryBuffer *buf, key_type = g_variant_type_key (type); key = parse_value_from_blob (buf, key_type, + max_depth - 1, FALSE, indent + 2, &local_error); @@ -1778,6 +1803,7 @@ parse_value_from_blob (GMemoryBuffer *buf, value_type = g_variant_type_value (type); value = parse_value_from_blob (buf, value_type, + max_depth - 1, FALSE, indent + 2, &local_error); @@ -1812,6 +1838,7 @@ parse_value_from_blob (GMemoryBuffer *buf, GVariant *item; item = parse_value_from_blob (buf, element_type, + max_depth - 1, FALSE, indent + 2, &local_error); @@ -1858,9 +1885,26 @@ parse_value_from_blob (GMemoryBuffer *buf, sig); goto fail; } + + if (max_depth <= g_variant_type_string_get_depth_ (sig)) + { + /* Catch the type nesting being too deep without having to + * parse the data. We don’t have to check this for static + * container types (like arrays and tuples, above) because + * the g_variant_type_string_is_valid() check performed before + * the initial parse_value_from_blob() call should check the + * static type nesting. */ + g_set_error_literal (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Value nested too deeply")); + goto fail; + } + variant_type = g_variant_type_new (sig); value = parse_value_from_blob (buf, variant_type, + max_depth - 1, FALSE, indent + 2, &local_error); @@ -2098,6 +2142,7 @@ g_dbus_message_new_from_blob (guchar *blob, #endif /* DEBUG_SERIALIZER */ headers = parse_value_from_blob (&mbuf, G_VARIANT_TYPE ("a{yv}"), + G_DBUS_MAX_TYPE_DEPTH + 2 /* for the a{yv} */, FALSE, 2, error); @@ -2169,6 +2214,7 @@ g_dbus_message_new_from_blob (guchar *blob, #endif /* DEBUG_SERIALIZER */ message->body = parse_value_from_blob (&mbuf, variant_type, + G_DBUS_MAX_TYPE_DEPTH + 1 /* for the surrounding tuple */, FALSE, 2, error); @@ -2740,7 +2786,7 @@ g_dbus_message_to_blob (GDBusMessage *message, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Message body has signature “%s” but there is no signature header"), - signature_str); + g_variant_get_type_string (message->body)); goto out; } tupled_signature_str = g_strdup_printf ("(%s)", signature_str); @@ -2750,7 +2796,7 @@ g_dbus_message_to_blob (GDBusMessage *message, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Message body has type signature “%s” but signature in the header field is “%s”"), - tupled_signature_str, g_variant_get_type_string (message->body)); + g_variant_get_type_string (message->body), tupled_signature_str); g_free (tupled_signature_str); goto out; } diff --git a/gio/gdbusnameowning.c b/gio/gdbusnameowning.c index 9764f24..d20e6ff 100644 --- a/gio/gdbusnameowning.c +++ b/gio/gdbusnameowning.c @@ -310,7 +310,7 @@ request_name_cb (GObject *source_object, Client *client = user_data; GVariant *result; guint32 request_name_reply; - gboolean subscribe; + gboolean unsubscribe; request_name_reply = 0; result = NULL; @@ -325,22 +325,18 @@ request_name_cb (GObject *source_object, g_variant_unref (result); } - subscribe = FALSE; + unsubscribe = FALSE; switch (request_name_reply) { case 1: /* DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER */ /* We got the name - now listen for NameLost and NameAcquired */ call_acquired_handler (client); - subscribe = TRUE; - client->needs_release = TRUE; break; case 2: /* DBUS_REQUEST_NAME_REPLY_IN_QUEUE */ /* Waiting in line - listen for NameLost and NameAcquired */ call_lost_handler (client); - subscribe = TRUE; - client->needs_release = TRUE; break; default: @@ -349,48 +345,34 @@ request_name_cb (GObject *source_object, case 4: /* DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER */ /* Some other part of the process is already owning the name */ call_lost_handler (client); + unsubscribe = TRUE; + client->needs_release = FALSE; break; } - - if (subscribe) + /* If we’re not the owner and not in the queue, there’s no point in continuing + * to listen to NameAcquired or NameLost. */ + if (unsubscribe) { GDBusConnection *connection = NULL; - /* if cancelled, there is no point in subscribing to signals - if not, make sure - * we use a known good Connection object since it may be set to NULL at any point - * after being cancelled + /* make sure we use a known good Connection object since it may be set to + * NULL at any point after being cancelled */ G_LOCK (lock); if (!client->cancelled) connection = g_object_ref (client->connection); G_UNLOCK (lock); - /* start listening to NameLost and NameAcquired messages */ if (connection != NULL) { - client->name_lost_subscription_id = - g_dbus_connection_signal_subscribe (connection, - "org.freedesktop.DBus", - "org.freedesktop.DBus", - "NameLost", - "/org/freedesktop/DBus", - client->name, - G_DBUS_SIGNAL_FLAGS_NONE, - on_name_lost_or_acquired, - client, - NULL); - client->name_acquired_subscription_id = - g_dbus_connection_signal_subscribe (connection, - "org.freedesktop.DBus", - "org.freedesktop.DBus", - "NameAcquired", - "/org/freedesktop/DBus", - client->name, - G_DBUS_SIGNAL_FLAGS_NONE, - on_name_lost_or_acquired, - client, - NULL); + if (client->name_acquired_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id); + if (client->name_lost_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id); + client->name_acquired_subscription_id = 0; + client->name_lost_subscription_id = 0; + g_object_unref (connection); } } @@ -434,7 +416,42 @@ has_connection (Client *client) G_CALLBACK (on_connection_disconnected), client); + /* Start listening to NameLost and NameAcquired messages. We hold + * references to the Client in the signal closures, since it’s possible + * for a signal to be in-flight after unsubscribing the signal handler. + * This creates a reference count cycle, but that’s explicitly broken by + * disconnecting the signal handlers before calling client_unref() in + * g_bus_unown_name(). + * + * Subscribe to NameLost and NameAcquired before calling RequestName() to + * avoid the potential race of losing the name between receiving a reply to + * RequestName() and subscribing to NameLost. The #PreviousCall state will + * ensure that the user callbacks get called an appropriate number of times. */ + client->name_lost_subscription_id = + g_dbus_connection_signal_subscribe (client->connection, + "org.freedesktop.DBus", + "org.freedesktop.DBus", + "NameLost", + "/org/freedesktop/DBus", + client->name, + G_DBUS_SIGNAL_FLAGS_NONE, + on_name_lost_or_acquired, + client_ref (client), + (GDestroyNotify) client_unref); + client->name_acquired_subscription_id = + g_dbus_connection_signal_subscribe (client->connection, + "org.freedesktop.DBus", + "org.freedesktop.DBus", + "NameAcquired", + "/org/freedesktop/DBus", + client->name, + G_DBUS_SIGNAL_FLAGS_NONE, + on_name_lost_or_acquired, + client_ref (client), + (GDestroyNotify) client_unref); + /* attempt to acquire the name */ + client->needs_release = TRUE; g_dbus_connection_call (client->connection, "org.freedesktop.DBus", /* bus name */ "/org/freedesktop/DBus", /* object path */ @@ -872,6 +889,13 @@ g_bus_own_name_on_connection_with_closures (GDBusConnection *connection, * * Stops owning a name. * + * Note that there may still be D-Bus traffic to process (relating to owning + * and unowning the name) in the current thread-default #GMainContext after + * this function has returned. You should continue to iterate the #GMainContext + * until the #GDestroyNotify function passed to g_bus_own_name() is called, in + * order to avoid memory leaks through callbacks queued on the #GMainContext + * after it’s stopped being iterated. + * * Since: 2.26 */ void @@ -939,6 +963,10 @@ g_bus_unown_name (guint owner_id) { g_warning ("Unexpected reply %d when releasing name %s", release_name_reply, client->name); } + else + { + client->needs_release = FALSE; + } g_variant_unref (result); } } diff --git a/gio/gdbusnamewatching.c b/gio/gdbusnamewatching.c index 3a97c50..bc2a911 100644 --- a/gio/gdbusnamewatching.c +++ b/gio/gdbusnamewatching.c @@ -148,6 +148,11 @@ call_handler_data_free (CallHandlerData *data) static void actually_do_call (Client *client, GDBusConnection *connection, const gchar *name_owner, CallType call_type) { + /* The client might have been cancelled (g_bus_unwatch_name()) while we were + * sitting in the #GMainContext dispatch queue. */ + if (client->cancelled) + return; + switch (call_type) { case CALL_TYPE_NAME_APPEARED: @@ -234,13 +239,12 @@ call_appeared_handler (Client *client) } static void -call_vanished_handler (Client *client, - gboolean ignore_cancelled) +call_vanished_handler (Client *client) { if (client->previous_call != PREVIOUS_CALL_VANISHED) { client->previous_call = PREVIOUS_CALL_VANISHED; - if (((!client->cancelled) || ignore_cancelled) && client->name_vanished_handler != NULL) + if (!client->cancelled && client->name_vanished_handler != NULL) { do_call (client, CALL_TYPE_NAME_VANISHED); } @@ -296,7 +300,7 @@ on_connection_disconnected (GDBusConnection *connection, client->name_owner_changed_subscription_id = 0; client->connection = NULL; - call_vanished_handler (client, FALSE); + call_vanished_handler (client); client_unref (client); } @@ -345,7 +349,7 @@ on_name_owner_changed (GDBusConnection *connection, { g_free (client->name_owner); client->name_owner = NULL; - call_vanished_handler (client, FALSE); + call_vanished_handler (client); } if (new_owner != NULL && strlen (new_owner) > 0) @@ -390,7 +394,7 @@ get_name_owner_cb (GObject *source_object, } else { - call_vanished_handler (client, FALSE); + call_vanished_handler (client); } client->initialized = TRUE; @@ -450,7 +454,7 @@ start_service_by_name_cb (GObject *source_object, else { g_warning ("Unexpected reply %d from StartServiceByName() method", start_service_result); - call_vanished_handler (client, FALSE); + call_vanished_handler (client); client->initialized = TRUE; } } @@ -529,7 +533,7 @@ connection_get_cb (GObject *source_object, client->connection = g_bus_get_finish (res, NULL); if (client->connection == NULL) { - call_vanished_handler (client, FALSE); + call_vanished_handler (client); goto out; } @@ -603,7 +607,7 @@ g_bus_watch_name (GBusType bus_type, client = g_new0 (Client, 1); client->ref_count = 1; - client->id = g_atomic_int_add (&next_global_id, 1); /* TODO: uh oh, handle overflow */ + client->id = (guint) g_atomic_int_add (&next_global_id, 1); /* TODO: uh oh, handle overflow */ client->name = g_strdup (name); client->flags = flags; client->name_appeared_handler = name_appeared_handler; @@ -665,7 +669,7 @@ guint g_bus_watch_name_on_connection (GDBusConnection *connection, client = g_new0 (Client, 1); client->ref_count = 1; - client->id = g_atomic_int_add (&next_global_id, 1); /* TODO: uh oh, handle overflow */ + client->id = (guint) g_atomic_int_add (&next_global_id, 1); /* TODO: uh oh, handle overflow */ client->name = g_strdup (name); client->flags = flags; client->name_appeared_handler = name_appeared_handler; @@ -854,6 +858,13 @@ guint g_bus_watch_name_on_connection_with_closures ( * * Stops watching a name. * + * Note that there may still be D-Bus traffic to process (relating to watching + * and unwatching the name) in the current thread-default #GMainContext after + * this function has returned. You should continue to iterate the #GMainContext + * until the #GDestroyNotify function passed to g_bus_watch_name() is called, in + * order to avoid memory leaks through callbacks queued on the #GMainContext + * after it’s stopped being iterated. + * * Since: 2.26 */ void diff --git a/gio/gdbusobjectmanagerclient.c b/gio/gdbusobjectmanagerclient.c index 38e6f53..a9b94f6 100644 --- a/gio/gdbusobjectmanagerclient.c +++ b/gio/gdbusobjectmanagerclient.c @@ -141,6 +141,9 @@ struct _GDBusObjectManagerClientPrivate GDBusProxyTypeFunc get_proxy_type_func; gpointer get_proxy_type_user_data; GDestroyNotify get_proxy_type_destroy_notify; + + gulong name_owner_signal_id; + gulong signal_signal_id; }; enum @@ -197,13 +200,18 @@ g_dbus_object_manager_client_finalize (GObject *object) g_hash_table_unref (manager->priv->map_object_path_to_object_proxy); - if (manager->priv->control_proxy != NULL) - { - g_signal_handlers_disconnect_by_func (manager->priv->control_proxy, - on_control_proxy_g_signal, - manager); - g_object_unref (manager->priv->control_proxy); - } + if (manager->priv->control_proxy != NULL && manager->priv->signal_signal_id != 0) + g_signal_handler_disconnect (manager->priv->control_proxy, + manager->priv->signal_signal_id); + manager->priv->signal_signal_id = 0; + + if (manager->priv->control_proxy != NULL && manager->priv->name_owner_signal_id != 0) + g_signal_handler_disconnect (manager->priv->control_proxy, + manager->priv->name_owner_signal_id); + manager->priv->name_owner_signal_id = 0; + + g_clear_object (&manager->priv->control_proxy); + if (manager->priv->connection != NULL) g_object_unref (manager->priv->connection); g_free (manager->priv->object_path); @@ -1241,16 +1249,20 @@ on_notify_g_name_owner (GObject *object, GParamSpec *pspec, gpointer user_data) { - GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (user_data); + GWeakRef *manager_weak = user_data; + GDBusObjectManagerClient *manager = NULL; gchar *old_name_owner; gchar *new_name_owner; + manager = G_DBUS_OBJECT_MANAGER_CLIENT (g_weak_ref_get (manager_weak)); + if (manager == NULL) + return; + g_mutex_lock (&manager->priv->lock); old_name_owner = manager->priv->name_owner; new_name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy); manager->priv->name_owner = NULL; - g_object_ref (manager); if (g_strcmp0 (old_name_owner, new_name_owner) != 0) { GList *l; @@ -1330,6 +1342,21 @@ on_notify_g_name_owner (GObject *object, g_object_unref (manager); } +static GWeakRef * +weak_ref_new (GObject *object) +{ + GWeakRef *weak_ref = g_new0 (GWeakRef, 1); + g_weak_ref_init (weak_ref, object); + return g_steal_pointer (&weak_ref); +} + +static void +weak_ref_free (GWeakRef *weak_ref) +{ + g_weak_ref_clear (weak_ref); + g_free (weak_ref); +} + static gboolean initable_init (GInitable *initable, GCancellable *cancellable, @@ -1365,15 +1392,30 @@ initable_init (GInitable *initable, if (manager->priv->control_proxy == NULL) goto out; - g_signal_connect (G_OBJECT (manager->priv->control_proxy), - "notify::g-name-owner", - G_CALLBACK (on_notify_g_name_owner), - manager); - - g_signal_connect (manager->priv->control_proxy, - "g-signal", - G_CALLBACK (on_control_proxy_g_signal), - manager); + /* Use weak refs here. The @control_proxy will emit its signals in the current + * #GMainContext (since we constructed it just above). However, the user may + * drop the last external reference to this #GDBusObjectManagerClient in + * another thread between a signal being emitted and scheduled in an idle + * callback in this #GMainContext, and that idle callback being invoked. We + * can’t use a strong reference here, as there’s no + * g_dbus_object_manager_client_disconnect() (or similar) method to tell us + * when the last external reference to this object has been dropped, so we + * can’t break a strong reference count cycle. So use weak refs. */ + manager->priv->name_owner_signal_id = + g_signal_connect_data (G_OBJECT (manager->priv->control_proxy), + "notify::g-name-owner", + G_CALLBACK (on_notify_g_name_owner), + weak_ref_new (G_OBJECT (manager)), + (GClosureNotify) weak_ref_free, + 0 /* flags */); + + manager->priv->signal_signal_id = + g_signal_connect_data (manager->priv->control_proxy, + "g-signal", + G_CALLBACK (on_control_proxy_g_signal), + weak_ref_new (G_OBJECT (manager)), + (GClosureNotify) weak_ref_free, + 0 /* flags */); manager->priv->name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy); if (manager->priv->name_owner == NULL && manager->priv->name != NULL) @@ -1397,11 +1439,20 @@ initable_init (GInitable *initable, if (value == NULL) { maybe_unsubscribe_signals (manager); - g_warn_if_fail (g_signal_handlers_disconnect_by_func (manager->priv->control_proxy, - on_control_proxy_g_signal, - manager) == 1); + + g_warn_if_fail (manager->priv->signal_signal_id != 0); + g_signal_handler_disconnect (manager->priv->control_proxy, + manager->priv->signal_signal_id); + manager->priv->signal_signal_id = 0; + + g_warn_if_fail (manager->priv->name_owner_signal_id != 0); + g_signal_handler_disconnect (manager->priv->control_proxy, + manager->priv->name_owner_signal_id); + manager->priv->name_owner_signal_id = 0; + g_object_unref (manager->priv->control_proxy); manager->priv->control_proxy = NULL; + goto out; } @@ -1668,9 +1719,14 @@ on_control_proxy_g_signal (GDBusProxy *proxy, GVariant *parameters, gpointer user_data) { - GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (user_data); + GWeakRef *manager_weak = user_data; + GDBusObjectManagerClient *manager = NULL; const gchar *object_path; + manager = G_DBUS_OBJECT_MANAGER_CLIENT (g_weak_ref_get (manager_weak)); + if (manager == NULL) + return; + //g_debug ("yay, g_signal %s: %s\n", signal_name, g_variant_print (parameters, TRUE)); if (g_strcmp0 (signal_name, "InterfacesAdded") == 0) @@ -1693,6 +1749,8 @@ on_control_proxy_g_signal (GDBusProxy *proxy, remove_interfaces (manager, object_path, ifaces); g_free (ifaces); } + + g_object_unref (manager); } /* ---------------------------------------------------------------------------------------------------- */ diff --git a/gio/gdbusproxy.c b/gio/gdbusproxy.c index 39eed16..4c68297 100644 --- a/gio/gdbusproxy.c +++ b/gio/gdbusproxy.c @@ -95,28 +95,19 @@ G_LOCK_DEFINE_STATIC (properties_lock); /* ---------------------------------------------------------------------------------------------------- */ -G_LOCK_DEFINE_STATIC (signal_subscription_lock); - -typedef struct -{ - volatile gint ref_count; - GDBusProxy *proxy; -} SignalSubscriptionData; - -static SignalSubscriptionData * -signal_subscription_ref (SignalSubscriptionData *data) +static GWeakRef * +weak_ref_new (GObject *object) { - g_atomic_int_inc (&data->ref_count); - return data; + GWeakRef *weak_ref = g_new0 (GWeakRef, 1); + g_weak_ref_init (weak_ref, object); + return g_steal_pointer (&weak_ref); } static void -signal_subscription_unref (SignalSubscriptionData *data) +weak_ref_free (GWeakRef *weak_ref) { - if (g_atomic_int_dec_and_test (&data->ref_count)) - { - g_slice_free (SignalSubscriptionData, data); - } + g_weak_ref_clear (weak_ref); + g_free (weak_ref); } /* ---------------------------------------------------------------------------------------------------- */ @@ -152,8 +143,6 @@ struct _GDBusProxyPrivate /* mutable, protected by properties_lock */ GDBusObject *object; - - SignalSubscriptionData *signal_subscription_data; }; enum @@ -190,22 +179,6 @@ G_DEFINE_TYPE_WITH_CODE (GDBusProxy, g_dbus_proxy, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)) static void -g_dbus_proxy_dispose (GObject *object) -{ - GDBusProxy *proxy = G_DBUS_PROXY (object); - G_LOCK (signal_subscription_lock); - if (proxy->priv->signal_subscription_data != NULL) - { - proxy->priv->signal_subscription_data->proxy = NULL; - signal_subscription_unref (proxy->priv->signal_subscription_data); - proxy->priv->signal_subscription_data = NULL; - } - G_UNLOCK (signal_subscription_lock); - - G_OBJECT_CLASS (g_dbus_proxy_parent_class)->dispose (object); -} - -static void g_dbus_proxy_finalize (GObject *object) { GDBusProxy *proxy = G_DBUS_PROXY (object); @@ -346,7 +319,6 @@ g_dbus_proxy_class_init (GDBusProxyClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - gobject_class->dispose = g_dbus_proxy_dispose; gobject_class->finalize = g_dbus_proxy_finalize; gobject_class->set_property = g_dbus_proxy_set_property; gobject_class->get_property = g_dbus_proxy_get_property; @@ -638,9 +610,6 @@ static void g_dbus_proxy_init (GDBusProxy *proxy) { proxy->priv = g_dbus_proxy_get_instance_private (proxy); - proxy->priv->signal_subscription_data = g_slice_new0 (SignalSubscriptionData); - proxy->priv->signal_subscription_data->ref_count = 1; - proxy->priv->signal_subscription_data->proxy = proxy; proxy->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, @@ -868,21 +837,12 @@ on_signal_received (GDBusConnection *connection, GVariant *parameters, gpointer user_data) { - SignalSubscriptionData *data = user_data; + GWeakRef *proxy_weak = user_data; GDBusProxy *proxy; - G_LOCK (signal_subscription_lock); - proxy = data->proxy; + proxy = G_DBUS_PROXY (g_weak_ref_get (proxy_weak)); if (proxy == NULL) - { - G_UNLOCK (signal_subscription_lock); - return; - } - else - { - g_object_ref (proxy); - G_UNLOCK (signal_subscription_lock); - } + return; if (!proxy->priv->initialized) goto out; @@ -929,8 +889,7 @@ on_signal_received (GDBusConnection *connection, parameters); out: - if (proxy != NULL) - g_object_unref (proxy); + g_clear_object (&proxy); } /* ---------------------------------------------------------------------------------------------------- */ @@ -1038,7 +997,7 @@ on_properties_changed (GDBusConnection *connection, GVariant *parameters, gpointer user_data) { - SignalSubscriptionData *data = user_data; + GWeakRef *proxy_weak = user_data; gboolean emit_g_signal = FALSE; GDBusProxy *proxy; const gchar *interface_name_for_signal; @@ -1052,18 +1011,9 @@ on_properties_changed (GDBusConnection *connection, changed_properties = NULL; invalidated_properties = NULL; - G_LOCK (signal_subscription_lock); - proxy = data->proxy; + proxy = G_DBUS_PROXY (g_weak_ref_get (proxy_weak)); if (proxy == NULL) - { - G_UNLOCK (signal_subscription_lock); - goto out; - } - else - { - g_object_ref (proxy); - G_UNLOCK (signal_subscription_lock); - } + return; if (!proxy->priv->initialized) goto out; @@ -1150,11 +1100,9 @@ on_properties_changed (GDBusConnection *connection, } out: - if (changed_properties != NULL) - g_variant_unref (changed_properties); + g_clear_pointer (&changed_properties, g_variant_unref); g_free (invalidated_properties); - if (proxy != NULL) - g_object_unref (proxy); + g_clear_object (&proxy); } /* ---------------------------------------------------------------------------------------------------- */ @@ -1258,8 +1206,7 @@ on_name_owner_changed_get_all_cb (GDBusConnection *connection, { G_LOCK (properties_lock); g_free (data->proxy->priv->name_owner); - data->proxy->priv->name_owner = data->name_owner; - data->name_owner = NULL; /* to avoid an extra copy, we steal the string */ + data->proxy->priv->name_owner = g_steal_pointer (&data->name_owner); g_hash_table_remove_all (data->proxy->priv->properties); G_UNLOCK (properties_lock); if (result != NULL) @@ -1289,23 +1236,14 @@ on_name_owner_changed (GDBusConnection *connection, GVariant *parameters, gpointer user_data) { - SignalSubscriptionData *data = user_data; + GWeakRef *proxy_weak = user_data; GDBusProxy *proxy; const gchar *old_owner; const gchar *new_owner; - G_LOCK (signal_subscription_lock); - proxy = data->proxy; + proxy = G_DBUS_PROXY (g_weak_ref_get (proxy_weak)); if (proxy == NULL) - { - G_UNLOCK (signal_subscription_lock); - goto out; - } - else - { - g_object_ref (proxy); - G_UNLOCK (signal_subscription_lock); - } + return; /* if we are already trying to load properties, cancel that */ if (proxy->priv->get_all_cancellable != NULL) @@ -1415,8 +1353,7 @@ on_name_owner_changed (GDBusConnection *connection, } out: - if (proxy != NULL) - g_object_unref (proxy); + g_clear_object (&proxy); } /* ---------------------------------------------------------------------------------------------------- */ @@ -1762,8 +1699,8 @@ async_initable_init_first (GAsyncInitable *initable) proxy->priv->interface_name, G_DBUS_SIGNAL_FLAGS_NONE, on_properties_changed, - signal_subscription_ref (proxy->priv->signal_subscription_data), - (GDestroyNotify) signal_subscription_unref); + weak_ref_new (G_OBJECT (proxy)), + (GDestroyNotify) weak_ref_free); } if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS)) @@ -1778,8 +1715,8 @@ async_initable_init_first (GAsyncInitable *initable) NULL, /* arg0 */ G_DBUS_SIGNAL_FLAGS_NONE, on_signal_received, - signal_subscription_ref (proxy->priv->signal_subscription_data), - (GDestroyNotify) signal_subscription_unref); + weak_ref_new (G_OBJECT (proxy)), + (GDestroyNotify) weak_ref_free); } if (proxy->priv->name != NULL && @@ -1794,8 +1731,8 @@ async_initable_init_first (GAsyncInitable *initable) proxy->priv->name, /* arg0 */ G_DBUS_SIGNAL_FLAGS_NONE, on_name_owner_changed, - signal_subscription_ref (proxy->priv->signal_subscription_data), - (GDestroyNotify) signal_subscription_unref); + weak_ref_new (G_OBJECT (proxy)), + (GDestroyNotify) weak_ref_free); } } diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index f1e2fdd..fe3ebe2 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -160,7 +160,6 @@ static const gchar *desktop_file_dirs_config_dir = NULL; static DesktopFileDir *desktop_file_dir_user_config = NULL; /* (owned) */ static DesktopFileDir *desktop_file_dir_user_data = NULL; /* (owned) */ static GMutex desktop_file_dir_lock; -static const gchar *gio_launch_desktop_path = NULL; /* Monitor 'changed' signal handler {{{2 */ static void desktop_file_dir_reset (DesktopFileDir *dir); @@ -2728,7 +2727,7 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, * internally by expand_macro(), so we need to pass a copy of it instead, * and also use that copy to control the exit condition of the loop below. */ - dup_uris = g_list_copy (uris); + dup_uris = uris; do { GPid pid; @@ -2737,6 +2736,14 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, char *sn_id = NULL; char **wrapped_argv; int i; + const gchar * const wrapper_argv[] = + { + "/bin/sh", + "-e", + "-u", + "-c", "export GIO_LAUNCHED_DESKTOP_FILE_PID=$$; exec \"$@\"", + "sh", /* argv[0] for sh */ + }; old_uris = dup_uris; if (!expand_application_parameters (info, exec_line, &dup_uris, &argc, &argv, error)) @@ -2778,26 +2785,26 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, g_list_free_full (launched_files, g_object_unref); } - if (g_once_init_enter (&gio_launch_desktop_path)) - { - const gchar *tmp; - - /* Allow test suite to specify path to gio-launch-desktop */ - tmp = g_getenv ("GIO_LAUNCH_DESKTOP"); - - /* Fall back on usual searching in $PATH */ - if (tmp == NULL) - tmp = "gio-launch-desktop"; - g_once_init_leave (&gio_launch_desktop_path, tmp); - } - - wrapped_argv = g_new (char *, argc + 2); - wrapped_argv[0] = g_strdup (gio_launch_desktop_path); - + /* Wrap the @argv in a command which will set the + * `GIO_LAUNCHED_DESKTOP_FILE_PID` environment variable. We can’t set this + * in @envp along with `GIO_LAUNCHED_DESKTOP_FILE` because we need to know + * the PID of the new forked process. We can’t use setenv() between fork() + * and exec() because we’d rather use posix_spawn() for speed. + * + * `sh` should be available on all the platforms that `GDesktopAppInfo` + * currently supports (since they are all POSIX). If additional platforms + * need to be supported in future, it will probably have to be replaced + * with a wrapper program (grep the GLib git history for + * `gio-launch-desktop` for an example of this which could be + * resurrected). */ + wrapped_argv = g_new (char *, argc + G_N_ELEMENTS (wrapper_argv) + 1); + + for (i = 0; i < G_N_ELEMENTS (wrapper_argv); i++) + wrapped_argv[i] = g_strdup (wrapper_argv[i]); for (i = 0; i < argc; i++) - wrapped_argv[i + 1] = g_steal_pointer (&argv[i]); + wrapped_argv[i + G_N_ELEMENTS (wrapper_argv)] = g_steal_pointer (&argv[i]); - wrapped_argv[i + 1] = NULL; + wrapped_argv[i + G_N_ELEMENTS (wrapper_argv)] = NULL; g_free (argv); argv = NULL; @@ -2857,7 +2864,6 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, completed = TRUE; out: - g_list_free (dup_uris); g_strfreev (argv); g_strfreev (envp); diff --git a/gio/gdocumentportal.c b/gio/gdocumentportal.c index 154cc74..644829a 100644 --- a/gio/gdocumentportal.c +++ b/gio/gdocumentportal.c @@ -37,56 +37,47 @@ #define HAVE_O_CLOEXEC 1 #endif -static GXdpDocuments *documents; -static char *documents_mountpoint; - static gboolean -init_document_portal (void) +get_document_portal (GXdpDocuments **documents, + char **documents_mountpoint, + GError **error) { - static gsize documents_inited = 0; + GDBusConnection *connection = NULL; - if (g_once_init_enter (&documents_inited)) - { - GError *error = NULL; - GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + *documents = NULL; + *documents_mountpoint = NULL; - if (connection != NULL) - { - documents = gxdp_documents_proxy_new_sync (connection, 0, - "org.freedesktop.portal.Documents", - "/org/freedesktop/portal/documents", - NULL, &error); - if (documents != NULL) - { - gxdp_documents_call_get_mount_point_sync (documents, - &documents_mountpoint, - NULL, &error); - - if (error != NULL) - { - g_warning ("Cannot get document portal mount point: %s", error->message); - g_error_free (error); - } - } - else - { - g_warning ("Cannot create document portal proxy: %s", error->message); - g_error_free (error); - } + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + if (connection == NULL) + { + g_prefix_error (error, "Cannot connect to session bus when initializing document portal: "); + goto out; + } - g_object_unref (connection); - } - else - { - g_warning ("Cannot connect to session bus when initializing document portal: %s", - error->message); - g_error_free (error); - } + *documents = gxdp_documents_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + "org.freedesktop.portal.Documents", + "/org/freedesktop/portal/documents", + NULL, error); + if (*documents == NULL) + { + g_prefix_error (error, "Cannot create document portal proxy: "); + goto out; + } - g_once_init_leave (&documents_inited, 1); + if (!gxdp_documents_call_get_mount_point_sync (*documents, + documents_mountpoint, + NULL, error)) + { + g_clear_object (documents); + g_prefix_error (error, "Cannot get document portal mount point: "); + goto out; } - return (documents != NULL && documents_mountpoint != NULL); +out: + g_clear_object (&connection); + return *documents != NULL; } /* Flags accepted by org.freedesktop.portal.Documents.AddFull */ @@ -102,6 +93,8 @@ g_document_portal_add_documents (GList *uris, const char *app_id, GError **error) { + GXdpDocuments *documents = NULL; + char *documents_mountpoint = NULL; int length; GList *ruris = NULL; gboolean *as_is; @@ -113,10 +106,8 @@ g_document_portal_add_documents (GList *uris, char **doc_ids = NULL; GVariant *extra_out = NULL; - if (!init_document_portal ()) + if (!get_document_portal (&documents, &documents_mountpoint, error)) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, - "Document portal is not available"); return NULL; } @@ -213,6 +204,8 @@ g_document_portal_add_documents (GList *uris, } out: + g_clear_object (&documents); + g_clear_pointer (&documents_mountpoint, g_free); g_clear_object (&fd_list); g_clear_pointer (&extra_out, g_variant_unref); g_clear_pointer (&doc_ids, g_strfreev); diff --git a/gio/gdtlsconnection.c b/gio/gdtlsconnection.c index 1a74e3c..e019d3b 100644 --- a/gio/gdtlsconnection.c +++ b/gio/gdtlsconnection.c @@ -164,9 +164,7 @@ g_dtls_connection_default_init (GDtlsConnectionInterface *iface) * * Since: 2.48 * - * Deprecated: 2.60. Changing the rehandshake mode is no longer - * required for compatibility. Also, rehandshaking has been removed - * from the TLS protocol in TLS 1.3. + * Deprecated: 2.60: The rehandshake mode is ignored. */ g_object_interface_install_property (iface, g_param_spec_enum ("rehandshake-mode", @@ -615,26 +613,10 @@ g_dtls_connection_get_require_close_notify (GDtlsConnection *conn) * @conn: a #GDtlsConnection * @mode: the rehandshaking mode * - * Sets how @conn behaves with respect to rehandshaking requests. - * - * %G_TLS_REHANDSHAKE_NEVER means that it will never agree to - * rehandshake after the initial handshake is complete. (For a client, - * this means it will refuse rehandshake requests from the server, and - * for a server, this means it will close the connection with an error - * if the client attempts to rehandshake.) - * - * %G_TLS_REHANDSHAKE_SAFELY means that the connection will allow a - * rehandshake only if the other end of the connection supports the - * TLS `renegotiation_info` extension. This is the default behavior, - * but means that rehandshaking will not work against older - * implementations that do not support that extension. - * - * %G_TLS_REHANDSHAKE_UNSAFELY means that the connection will allow - * rehandshaking even without the `renegotiation_info` extension. On - * the server side in particular, this is not recommended, since it - * leaves the server open to certain attacks. However, this mode is - * necessary if you need to allow renegotiation with older client - * software. + * Since GLib 2.64, changing the rehandshake mode is no longer supported + * and will have no effect. With TLS 1.3, rehandshaking has been removed from + * the TLS protocol, replaced by separate post-handshake authentication and + * rekey operations. * * Since: 2.48 * @@ -650,7 +632,7 @@ g_dtls_connection_set_rehandshake_mode (GDtlsConnection *conn, g_return_if_fail (G_IS_DTLS_CONNECTION (conn)); g_object_set (G_OBJECT (conn), - "rehandshake-mode", mode, + "rehandshake-mode", G_TLS_REHANDSHAKE_SAFELY, NULL); } G_GNUC_END_IGNORE_DEPRECATIONS @@ -662,22 +644,29 @@ G_GNUC_END_IGNORE_DEPRECATIONS * Gets @conn rehandshaking mode. See * g_dtls_connection_set_rehandshake_mode() for details. * - * Returns: @conn's rehandshaking mode + * Returns: %G_TLS_REHANDSHAKE_SAFELY * * Since: 2.48 + * + * Deprecated: 2.64. Changing the rehandshake mode is no longer + * required for compatibility. Also, rehandshaking has been removed + * from the TLS protocol in TLS 1.3. */ G_GNUC_BEGIN_IGNORE_DEPRECATIONS GTlsRehandshakeMode -g_dtls_connection_get_rehandshake_mode (GDtlsConnection *conn) +g_dtls_connection_get_rehandshake_mode (GDtlsConnection *conn) { GTlsRehandshakeMode mode; - g_return_val_if_fail (G_IS_DTLS_CONNECTION (conn), G_TLS_REHANDSHAKE_NEVER); + g_return_val_if_fail (G_IS_DTLS_CONNECTION (conn), G_TLS_REHANDSHAKE_SAFELY); + /* Continue to call g_object_get(), even though the return value is + * ignored, so that behavior doesn’t change for derived classes. + */ g_object_get (G_OBJECT (conn), "rehandshake-mode", &mode, NULL); - return mode; + return G_TLS_REHANDSHAKE_SAFELY; } G_GNUC_END_IGNORE_DEPRECATIONS @@ -691,28 +680,25 @@ G_GNUC_END_IGNORE_DEPRECATIONS * * On the client side, it is never necessary to call this method; * although the connection needs to perform a handshake after - * connecting (or after sending a "STARTTLS"-type command) and may - * need to rehandshake later if the server requests it, - * #GDtlsConnection will handle this for you automatically when you try - * to send or receive data on the connection. However, you can call - * g_dtls_connection_handshake() manually if you want to know for sure - * whether the initial handshake succeeded or failed (as opposed to - * just immediately trying to write to @conn, in which - * case if it fails, it may not be possible to tell if it failed - * before or after completing the handshake). + * connecting, #GDtlsConnection will handle this for you automatically + * when you try to send or receive data on the connection. You can call + * g_dtls_connection_handshake() manually if you want to know whether + * the initial handshake succeeded or failed (as opposed to just + * immediately trying to use @conn to read or write, in which case, + * if it fails, it may not be possible to tell if it failed before + * or after completing the handshake), but beware that servers may reject + * client authentication after the handshake has completed, so a + * successful handshake does not indicate the connection will be usable. * * Likewise, on the server side, although a handshake is necessary at * the beginning of the communication, you do not need to call this * function explicitly unless you want clearer error reporting. * - * If TLS 1.2 or older is in use, you may call - * g_dtls_connection_handshake() after the initial handshake to - * rehandshake; however, this usage is deprecated because rehandshaking - * is no longer part of the TLS protocol in TLS 1.3. Accordingly, the - * behavior of calling this function after the initial handshake is now - * undefined, except it is guaranteed to be reasonable and - * nondestructive so as to preserve compatibility with code written for - * older versions of GLib. + * Previously, calling g_dtls_connection_handshake() after the initial + * handshake would trigger a rehandshake; however, this usage was + * deprecated in GLib 2.60 because rehandshaking was removed from the + * TLS protocol in TLS 1.3. Since GLib 2.64, calling this function after + * the initial handshake will no longer do anything. * * #GDtlsConnection::accept_certificate may be emitted during the * handshake. diff --git a/gio/gdummytlsbackend.c b/gio/gdummytlsbackend.c index 52342d9..8744b83 100644 --- a/gio/gdummytlsbackend.c +++ b/gio/gdummytlsbackend.c @@ -235,7 +235,9 @@ enum PROP_CONN_SERVER_IDENTITY, PROP_CONN_USE_SSL3, PROP_CONN_ACCEPTED_CAS, - PROP_CONN_AUTHENTICATION_MODE + PROP_CONN_AUTHENTICATION_MODE, + PROP_CONN_ADVERTISED_PROTOCOLS, + PROP_CONN_NEGOTIATED_PROTOCOL, }; static void g_dummy_tls_connection_initable_iface_init (GInitableIface *iface); @@ -301,6 +303,8 @@ g_dummy_tls_connection_class_init (GDummyTlsConnectionClass *connection_class) g_object_class_override_property (gobject_class, PROP_CONN_USE_SSL3, "use-ssl3"); g_object_class_override_property (gobject_class, PROP_CONN_ACCEPTED_CAS, "accepted-cas"); g_object_class_override_property (gobject_class, PROP_CONN_AUTHENTICATION_MODE, "authentication-mode"); + g_object_class_override_property (gobject_class, PROP_CONN_ADVERTISED_PROTOCOLS, "advertised-protocols"); + g_object_class_override_property (gobject_class, PROP_CONN_NEGOTIATED_PROTOCOL, "negotiated-protocol"); } static void diff --git a/gio/gfdonotificationbackend.c b/gio/gfdonotificationbackend.c index c4fa0dc..b6fbfd2 100644 --- a/gio/gfdonotificationbackend.c +++ b/gio/gfdonotificationbackend.c @@ -25,6 +25,7 @@ #include "giomodule-priv.h" #include "gnotification-private.h" #include "gdbusconnection.h" +#include "gdbusnamewatching.h" #include "gactiongroup.h" #include "gaction.h" #include "gthemedicon.h" @@ -42,6 +43,8 @@ struct _GFdoNotificationBackend { GNotificationBackend parent; + guint bus_name_id; + guint notify_subscription; GSList *notifications; }; @@ -205,6 +208,20 @@ notify_signal (GDBusConnection *connection, } } +static void +name_vanished_handler_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GFdoNotificationBackend *backend = user_data; + + if (backend->notifications) + { + g_slist_free_full (backend->notifications, freedesktop_notification_free); + backend->notifications = NULL; + } +} + /* Converts a GNotificationPriority to an urgency level as defined by * the freedesktop spec (0: low, 1: normal, 2: critical). */ @@ -370,6 +387,12 @@ g_fdo_notification_backend_dispose (GObject *object) { GFdoNotificationBackend *backend = G_FDO_NOTIFICATION_BACKEND (object); + if (backend->bus_name_id) + { + g_bus_unwatch_name (backend->bus_name_id); + backend->bus_name_id = 0; + } + if (backend->notify_subscription) { GDBusConnection *session_bus; @@ -407,6 +430,17 @@ g_fdo_notification_backend_send_notification (GNotificationBackend *backend, GFdoNotificationBackend *self = G_FDO_NOTIFICATION_BACKEND (backend); FreedesktopNotification *n, *tmp; + if (self->bus_name_id == 0) + { + self->bus_name_id = g_bus_watch_name_on_connection (backend->dbus_connection, + "org.freedesktop.Notifications", + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + name_vanished_handler_cb, + backend, + NULL); + } + if (self->notify_subscription == 0) { self->notify_subscription = diff --git a/gio/gfile.c b/gio/gfile.c index ba93f7c..a2ded14 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -534,37 +534,6 @@ g_file_get_path (GFile *file) return (* iface->get_path) (file); } -/* Original commit introducing this in libgsystem: - * - * fileutil: Handle recent: and trash: URIs - * - * The gs_file_get_path_cached() was rather brittle in its handling - * of URIs. It would assert() when a GFile didn't have a backing path - * (such as when handling trash: or recent: URIs), and didn't know - * how to get the target URI for those items either. - * - * Make sure that we do not assert() when a backing path cannot be - * found, and handle recent: and trash: URIs. - * - * https://bugzilla.gnome.org/show_bug.cgi?id=708435 - */ -static char * -file_get_target_path (GFile *file) -{ - GFileInfo *info; - const char *target; - char *path; - - info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NONE, NULL, NULL); - if (info == NULL) - return NULL; - target = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); - path = g_filename_from_uri (target, NULL, NULL); - g_object_unref (info); - - return path; -} - static const char * file_peek_path_generic (GFile *file) { @@ -591,11 +560,7 @@ file_peek_path_generic (GFile *file) if (path != NULL) break; - if (g_file_has_uri_scheme (file, "trash") || - g_file_has_uri_scheme (file, "recent")) - new_path = file_get_target_path (file); - else - new_path = g_file_get_path (file); + new_path = g_file_get_path (file); if (new_path == NULL) return NULL; @@ -603,7 +568,10 @@ file_peek_path_generic (GFile *file) if (g_object_replace_qdata ((GObject *) file, _file_path_quark, NULL, (gpointer) new_path, (GDestroyNotify) g_free, NULL)) - break; + { + path = new_path; + break; + } else g_free (new_path); } @@ -936,7 +904,7 @@ g_file_get_child_for_display_name (GFile *file, * of @prefix. * * Virtual: prefix_matches - * Returns: %TRUE if the @files's parent, grandparent, etc is @prefix, + * Returns: %TRUE if the @file's parent, grandparent, etc is @prefix, * %FALSE otherwise. */ gboolean @@ -3652,10 +3620,6 @@ g_file_copy_finish (GFile *file, * If the flag #G_FILE_COPY_OVERWRITE is specified an already * existing @destination file is overwritten. * - * If the flag #G_FILE_COPY_NOFOLLOW_SYMLINKS is specified then symlinks - * will be copied as symlinks, otherwise the target of the - * @source symlink will be copied. - * * If @cancellable is not %NULL, then the operation can be cancelled by * triggering the cancellable object from another thread. If the operation * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. @@ -3759,7 +3723,7 @@ g_file_move (GFile *source, return FALSE; } - flags |= G_FILE_COPY_ALL_METADATA; + flags |= G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS; if (!g_file_copy (source, destination, flags, cancellable, progress_callback, progress_callback_data, error)) @@ -4046,7 +4010,7 @@ g_file_make_symbolic_link (GFile *file, { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - _("Operation not supported")); + _("Symbolic links not supported")); return FALSE; } @@ -4522,7 +4486,7 @@ g_file_query_writable_namespaces (GFile *file, * %NULL to ignore * @error: a #GError, or %NULL * - * Sets an attribute in the file with attribute name @attribute to @value. + * Sets an attribute in the file with attribute name @attribute to @value_p. * * Some attributes can be unset by setting @type to * %G_FILE_ATTRIBUTE_TYPE_INVALID and @value_p to %NULL. @@ -6875,7 +6839,8 @@ g_file_query_default_handler (GFile *file, g_free (uri_scheme); info = g_file_query_info (file, - G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, 0, cancellable, error); @@ -6885,6 +6850,8 @@ g_file_query_default_handler (GFile *file, appinfo = NULL; content_type = g_file_info_get_content_type (info); + if (content_type == NULL) + content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); if (content_type) { /* Don't use is_native(), as we want to support fuse paths if available */ @@ -6926,6 +6893,8 @@ query_default_handler_query_info_cb (GObject *object, } content_type = g_file_info_get_content_type (info); + if (content_type == NULL) + content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); if (content_type) { char *path; @@ -6996,7 +6965,8 @@ g_file_query_default_handler_async (GFile *file, g_free (uri_scheme); g_file_query_info_async (file, - G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, 0, io_priority, cancellable, @@ -7044,7 +7014,7 @@ g_file_query_default_handler_finish (GFile *file, * * Loads the content of the file into memory. The data is always * zero-terminated, but this is not included in the resultant @length. - * The returned @content should be freed with g_free() when no longer + * The returned @contents should be freed with g_free() when no longer * needed. * * If @cancellable is not %NULL, then the operation can be cancelled by @@ -7333,7 +7303,7 @@ g_file_load_partial_contents_async (GFile *file, * Finishes an asynchronous partial load operation that was started * with g_file_load_partial_contents_async(). The data is always * zero-terminated, but this is not included in the resultant @length. - * The returned @content should be freed with g_free() when no longer + * The returned @contents should be freed with g_free() when no longer * needed. * * Returns: %TRUE if the load was successful. If %FALSE and @error is @@ -7430,7 +7400,7 @@ g_file_load_contents_async (GFile *file, * * Finishes an asynchronous load of the @file's contents. * The contents are placed in @contents, and @length is set to the - * size of the @contents string. The @content should be freed with + * size of the @contents string. The @contents should be freed with * g_free() when no longer needed. If @etag_out is present, it will be * set to the new entity tag for the @file. * @@ -7687,7 +7657,7 @@ replace_contents_open_callback (GObject *obj, * If @make_backup is %TRUE, this function will attempt to * make a backup of @file. * - * Note that no copy of @content will be made, so it must stay valid + * Note that no copy of @contents will be made, so it must stay valid * until @callback is called. See g_file_replace_contents_bytes_async() * for a #GBytes version that will automatically hold a reference to the * contents (without copying) for the duration of the call. diff --git a/gio/gfile.h b/gio/gfile.h index 8441d0b..8b6d083 100644 --- a/gio/gfile.h +++ b/gio/gfile.h @@ -111,10 +111,13 @@ typedef struct _GFileIface GFileIface; * @make_directory: Makes a directory. * @make_directory_async: Asynchronously makes a directory. * @make_directory_finish: Finishes making a directory asynchronously. - * @make_symbolic_link: Makes a symbolic link. + * @make_symbolic_link: (nullable): Makes a symbolic link. %NULL if symbolic + * links are unsupported. * @_make_symbolic_link_async: Asynchronously makes a symbolic link * @_make_symbolic_link_finish: Finishes making a symbolic link asynchronously. - * @copy: Copies a file. + * @copy: (nullable): Copies a file. %NULL if copying is unsupported, which will + * cause `GFile` to use a fallback copy method where it reads from the + * source and writes to the destination. * @copy_async: Asynchronously copies a file. * @copy_finish: Finishes an asynchronous copy operation. * @move: Moves a file. diff --git a/gio/gfileinfo.c b/gio/gfileinfo.c index 032d211..5ec5ec4 100644 --- a/gio/gfileinfo.c +++ b/gio/gfileinfo.c @@ -564,7 +564,7 @@ g_file_info_find_value_by_name (GFileInfo *info, * * Checks if a file info structure has an attribute named @attribute. * - * Returns: %TRUE if @Ginfo has an attribute named @attribute, + * Returns: %TRUE if @info has an attribute named @attribute, * %FALSE otherwise. **/ gboolean @@ -588,7 +588,7 @@ g_file_info_has_attribute (GFileInfo *info, * Checks if a file info structure has an attribute in the * specified @name_space. * - * Returns: %TRUE if @Ginfo has an attribute in @name_space, + * Returns: %TRUE if @info has an attribute in @name_space, * %FALSE otherwise. * * Since: 2.22 @@ -2163,8 +2163,9 @@ g_file_info_set_size (GFileInfo *info, * @info: a #GFileInfo. * @mtime: a #GTimeVal. * - * Sets the %G_FILE_ATTRIBUTE_TIME_MODIFIED attribute in the file - * info to the given time value. + * Sets the %G_FILE_ATTRIBUTE_TIME_MODIFIED and + * %G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC attributes in the file info to the + * given time value. * * Deprecated: 2.62: Use g_file_info_set_modification_date_time() instead, as * #GTimeVal is deprecated due to the year 2038 problem. @@ -2200,8 +2201,9 @@ G_GNUC_END_IGNORE_DEPRECATIONS * @info: a #GFileInfo. * @mtime: (not nullable): a #GDateTime. * - * Sets the %G_FILE_ATTRIBUTE_TIME_MODIFIED attribute in the file - * info to the given date/time value. + * Sets the %G_FILE_ATTRIBUTE_TIME_MODIFIED and + * %G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC attributes in the file info to the + * given date/time value. * * Since: 2.62 */ @@ -2389,7 +2391,7 @@ matcher_optimize (GFileAttributeMatcher *matcher) * the number of references falls to 0, the #GFileAttributeMatcher is * automatically destroyed. * - * The @attribute string should be formatted with specific keys separated + * The @attributes string should be formatted with specific keys separated * from namespaces with a double colon. Several "namespace::key" strings may be * concatenated with a single comma (e.g. "standard::type,standard::is-hidden"). * The wildcard "*" may be used to match all keys and namespaces, or diff --git a/gio/gfileinfo.h b/gio/gfileinfo.h index 1629a2e..d245951 100644 --- a/gio/gfileinfo.h +++ b/gio/gfileinfo.h @@ -578,8 +578,10 @@ typedef struct _GFileInfoClass GFileInfoClass; * G_FILE_ATTRIBUTE_UNIX_MODE: * * A key in the "unix" namespace for getting the mode of the file - * (e.g. whether the file is a regular file, symlink, etc). See lstat() - * documentation. This attribute is only available for UNIX file systems. + * (e.g. whether the file is a regular file, symlink, etc). See the + * documentation for `lstat()`: this attribute is equivalent to the `st_mode` + * member of `struct stat`, and includes both the file type and permissions. + * This attribute is only available for UNIX file systems. * Corresponding #GFileAttributeType is %G_FILE_ATTRIBUTE_TYPE_UINT32. **/ #define G_FILE_ATTRIBUTE_UNIX_MODE "unix::mode" /* uint32 */ diff --git a/gio/gio-launch-desktop.c b/gio/gio-launch-desktop.c deleted file mode 100644 index 03845df..0000000 --- a/gio/gio-launch-desktop.c +++ /dev/null @@ -1,52 +0,0 @@ -/* GIO - GLib Input, Output and Streaming Library - * - * Copyright (C) 2018 Endless Mobile, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public License along with this library; if not, see . - * - * Author: Daniel Drake - */ - -/* - * gio-launch-desktop: GDesktopAppInfo helper - * Executable wrapper to set GIO_LAUNCHED_DESKTOP_FILE_PID - * There are complications when doing this in a fork()/exec() codepath, - * and it cannot otherwise be done with posix_spawn(). - * This wrapper is designed to be minimal and lightweight. - * It does not even link against glib. - */ - -#include -#include -#include -#include - -int -main (int argc, char *argv[]) -{ - pid_t pid = getpid (); - char buf[50]; - int r; - - if (argc < 2) - return -1; - - r = snprintf (buf, sizeof (buf), "GIO_LAUNCHED_DESKTOP_FILE_PID=%ld", (long) pid); - if (r >= sizeof (buf)) - return -1; - - putenv (buf); - - return execvp (argv[1], argv + 1); -} diff --git a/gio/gio-tool-copy.c b/gio/gio-tool-copy.c index 4cc4a9d..0083eba 100644 --- a/gio/gio-tool-copy.c +++ b/gio/gio-tool-copy.c @@ -37,6 +37,7 @@ static gboolean interactive = FALSE; static gboolean preserve = FALSE; static gboolean backup = FALSE; static gboolean no_dereference = FALSE; +static gboolean default_permissions = FALSE; static const GOptionEntry entries[] = { { "no-target-directory", 'T', 0, G_OPTION_ARG_NONE, &no_target_directory, N_("No target directory"), NULL }, @@ -45,6 +46,7 @@ static const GOptionEntry entries[] = { { "preserve", 'p', 0, G_OPTION_ARG_NONE, &preserve, N_("Preserve all attributes"), NULL }, { "backup", 'b', 0, G_OPTION_ARG_NONE, &backup, N_("Backup existing destination files"), NULL }, { "no-dereference", 'P', 0, G_OPTION_ARG_NONE, &no_dereference, N_("Never follow symbolic links"), NULL }, + { "default-permissions", 0, 0, G_OPTION_ARG_NONE, &default_permissions, N_("Use default permissions for the destination"), NULL }, { NULL } }; @@ -175,6 +177,8 @@ handle_copy (int argc, char *argv[], gboolean do_help) flags |= G_FILE_COPY_NOFOLLOW_SYMLINKS; if (preserve) flags |= G_FILE_COPY_ALL_METADATA; + if (default_permissions) + flags |= G_FILE_COPY_TARGET_DEFAULT_PERMS; error = NULL; start_time = g_get_monotonic_time (); diff --git a/gio/gio-tool-info.c b/gio/gio-tool-info.c index d6fc6b4..7cf5683 100644 --- a/gio/gio-tool-info.c +++ b/gio/gio-tool-info.c @@ -22,8 +22,11 @@ #include #include -#include "gio-tool.h" +#ifdef G_OS_UNIX +#include +#endif +#include "gio-tool.h" static gboolean writable = FALSE; static gboolean filesystem = FALSE; @@ -120,6 +123,10 @@ show_info (GFile *file, GFileInfo *info) const char *name, *type; char *escaped, *uri; goffset size; + const char *path; +#ifdef G_OS_UNIX + GUnixMountEntry *entry; +#endif name = g_file_info_get_display_name (info); if (name) @@ -159,6 +166,50 @@ show_info (GFile *file, GFileInfo *info) g_print (_("uri: %s\n"), uri); g_free (uri); + path = g_file_peek_path (file); + if (path) + { + g_print (_("local path: %s\n"), path); + +#ifdef G_OS_UNIX + entry = g_unix_mount_at (path, NULL); + if (entry == NULL) + entry = g_unix_mount_for (path, NULL); + if (entry != NULL) + { + gchar *device; + const gchar *root; + gchar *root_string = NULL; + gchar *mount; + gchar *fs; + gchar *options; + + device = g_strescape (g_unix_mount_get_device_path (entry), NULL); + root = g_unix_mount_get_root_path (entry); + if (root != NULL && g_strcmp0 (root, "/") != 0) + { + escaped = g_strescape (root, NULL); + root_string = g_strconcat ("[", escaped, "]", NULL); + g_free (escaped); + } + mount = g_strescape (g_unix_mount_get_mount_path (entry), NULL); + fs = g_strescape (g_unix_mount_get_fs_type (entry), NULL); + options = g_strescape (g_unix_mount_get_options (entry), NULL); + + g_print (_("unix mount: %s%s %s %s %s\n"), device, + root_string ? root_string : "", mount, fs, options); + + g_free (device); + g_free (root_string); + g_free (mount); + g_free (fs); + g_free (options); + + g_unix_mount_free (entry); + } +#endif + } + show_attributes (info); } diff --git a/gio/gio-tool-list.c b/gio/gio-tool-list.c index d1501b8..9f52d15 100644 --- a/gio/gio-tool-list.c +++ b/gio/gio-tool-list.c @@ -29,6 +29,7 @@ static char *attributes = NULL; static gboolean show_hidden = FALSE; static gboolean show_long = FALSE; static gboolean nofollow_symlinks = FALSE; +static gboolean print_display_names = FALSE; static gboolean print_uris = FALSE; static const GOptionEntry entries[] = { @@ -36,6 +37,7 @@ static const GOptionEntry entries[] = { { "hidden", 'h', 0, G_OPTION_ARG_NONE, &show_hidden, N_("Show hidden files"), NULL }, { "long", 'l', 0, G_OPTION_ARG_NONE, &show_long, N_("Use a long listing format"), NULL }, { "nofollow-symlinks", 'n', 0, G_OPTION_ARG_NONE, &nofollow_symlinks, N_("Don’t follow symbolic links"), NULL}, + { "print-display-names", 'd', 0, G_OPTION_ARG_NONE, &print_display_names, N_("Print display names"), NULL }, { "print-uris", 'u', 0, G_OPTION_ARG_NONE, &print_uris, N_("Print full URIs"), NULL}, { NULL } }; @@ -54,7 +56,11 @@ show_file_listing (GFileInfo *info, GFile *parent) if ((g_file_info_get_is_hidden (info)) && !show_hidden) return; - name = g_file_info_get_name (info); + if (print_display_names) + name = g_file_info_get_display_name (info); + else + name = g_file_info_get_name (info); + if (name == NULL) name = ""; @@ -81,7 +87,8 @@ show_file_listing (GFileInfo *info, GFile *parent) char *val_as_string; if (!show_long || - strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_NAME) == 0 || + (!print_display_names && strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_NAME) == 0) || + (print_display_names && strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME) == 0) || strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_SIZE) == 0 || strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_TYPE) == 0 || strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) == 0) @@ -195,7 +202,8 @@ handle_list (int argc, char *argv[], gboolean do_help) if (attributes != NULL) show_long = TRUE; - attributes = g_strconcat (G_FILE_ATTRIBUTE_STANDARD_NAME "," + attributes = g_strconcat (!print_display_names ? G_FILE_ATTRIBUTE_STANDARD_NAME "," : "", + print_display_names ? G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," : "", G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, diff --git a/gio/gio-tool-mount.c b/gio/gio-tool-mount.c index 05647d9..67278bb 100644 --- a/gio/gio-tool-mount.c +++ b/gio/gio-tool-mount.c @@ -53,7 +53,7 @@ static gboolean tcrypt_hidden = FALSE; static gboolean tcrypt_system = FALSE; static guint tcrypt_pim = 0; static const char *unmount_scheme = NULL; -static const char *mount_device_file = NULL; +static const char *mount_id = NULL; static const char *stop_device_file = NULL; static gboolean success = TRUE; @@ -61,7 +61,7 @@ static gboolean success = TRUE; static const GOptionEntry entries[] = { { "mountable", 'm', 0, G_OPTION_ARG_NONE, &mount_mountable, N_("Mount as mountable"), NULL }, - { "device", 'd', 0, G_OPTION_ARG_STRING, &mount_device_file, N_("Mount volume with device file"), N_("DEVICE") }, + { "device", 'd', 0, G_OPTION_ARG_STRING, &mount_id, N_("Mount volume with device file, or other identifier"), N_("ID") }, { "unmount", 'u', 0, G_OPTION_ARG_NONE, &mount_unmount, N_("Unmount"), NULL}, { "eject", 'e', 0, G_OPTION_ARG_NONE, &mount_eject, N_("Eject"), NULL}, { "stop", 't', 0, G_OPTION_ARG_STRING, &stop_device_file, N_("Stop drive with device file"), N_("DEVICE") }, @@ -950,7 +950,7 @@ mount_with_device_file_cb (GObject *object, GVolume *volume; gboolean succeeded; GError *error = NULL; - gchar *device_path = (gchar *)user_data; + gchar *id = (gchar *)user_data; volume = G_VOLUME (object); @@ -958,28 +958,12 @@ mount_with_device_file_cb (GObject *object, if (!succeeded) { - print_error ("%s: %s", device_path, error->message); + print_error ("%s: %s", id, error->message); g_error_free (error); success = FALSE; } - else - { - GMount *mount; - GFile *root; - char *mount_path; - - mount = g_volume_get_mount (volume); - root = g_mount_get_root (mount); - mount_path = g_file_get_path (root); - - g_print (_("Mounted %s at %s\n"), device_path, mount_path); - - g_object_unref (mount); - g_object_unref (root); - g_free (mount_path); - } - g_free (device_path); + g_free (id); outstanding_mounts--; @@ -988,7 +972,7 @@ mount_with_device_file_cb (GObject *object, } static void -mount_with_device_file (const char *device_file) +mount_with_id (const char *id) { GList *volumes; GList *l; @@ -997,10 +981,12 @@ mount_with_device_file (const char *device_file) for (l = volumes; l != NULL; l = l->next) { GVolume *volume = G_VOLUME (l->data); - gchar *id; + gchar *device; + gchar *uuid; - id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); - if (g_strcmp0 (id, device_file) == 0) + device = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); + uuid = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UUID); + if (g_strcmp0 (device, id) == 0 || g_strcmp0 (uuid, id) == 0) { GMountOperation *op; @@ -1011,20 +997,21 @@ mount_with_device_file (const char *device_file) op, NULL, mount_with_device_file_cb, - id); + g_strdup (id)); g_object_unref (op); outstanding_mounts++; } - else - g_free (id); + + g_free (device); + g_free (uuid); } g_list_free_full (volumes, g_object_unref); if (outstanding_mounts == 0) { - print_error ("%s: %s", device_file, _("No volume for device file")); + print_error ("%s: %s", id, _("No volume for given ID")); success = FALSE; } } @@ -1236,8 +1223,8 @@ handle_mount (int argc, char *argv[], gboolean do_help) if (mount_list) list_monitor_items (); - else if (mount_device_file != NULL) - mount_with_device_file (mount_device_file); + else if (mount_id != NULL) + mount_with_id (mount_id); else if (stop_device_file) stop_with_device_file (stop_device_file); else if (unmount_scheme != NULL) diff --git a/gio/gio.h b/gio/gio.h index 8053768..3532b73 100644 --- a/gio/gio.h +++ b/gio/gio.h @@ -47,15 +47,25 @@ #include #include #include +#include #include #include #include #include +#include +#include #include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -64,9 +74,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -88,9 +98,15 @@ #include #include #include +#include +#include #include #include +#include #include +#include +#include +#include #include #include #include @@ -98,6 +114,7 @@ #include #include #include +#include #include #include #include @@ -108,30 +125,31 @@ #include #include #include +#include #include #include #include -#include #include +#include #include #include #include #include #include -#include +#include +#include #include +#include #include #include #include #include -#include #include #include #include -#include -#include #include #include +#include #include #include #include @@ -144,30 +162,13 @@ #include #include #include -#include #include +#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include diff --git a/gio/gio_trace.h b/gio/gio_trace.h index 47d4eac..addb70a 100644 --- a/gio/gio_trace.h +++ b/gio/gio_trace.h @@ -25,7 +25,9 @@ #error "config.h must be included prior to gio_trace.h" #endif -#ifdef HAVE_DTRACE +/* Ignore probes when doing static analysis, as they do weird things which + * confuses the analyser. */ +#if defined(HAVE_DTRACE) && !defined(__clang_analyzer__) /* include the generated probes header and put markers in code */ #include "gio_probes.h" diff --git a/gio/gioenums.h b/gio/gioenums.h index 22fe700..6154d43 100644 --- a/gio/gioenums.h +++ b/gio/gioenums.h @@ -1964,6 +1964,35 @@ typedef enum { G_POLLABLE_RETURN_WOULD_BLOCK = -G_IO_ERROR_WOULD_BLOCK } GPollableReturn; +/** + * GMemoryMonitorWarningLevel: + * @G_MEMORY_MONITOR_WARNING_LEVEL_LOW: Memory on the device is low, processes + * should free up unneeded resources (for example, in-memory caches) so they can + * be used elsewhere. + * @G_MEMORY_MONITOR_WARNING_LEVEL_MEDIUM: Same as @G_MEMORY_MONITOR_WARNING_LEVEL_LOW + * but the device has even less free memory, so processes should try harder to free + * up unneeded resources. If your process does not need to stay running, it is a + * good time for it to quit. + * @G_MEMORY_MONITOR_WARNING_LEVEL_CRITICAL: The system will soon start terminating + * processes to reclaim memory, including background processes. + * + * Memory availability warning levels. + * + * Note that because new values might be added, it is recommended that applications check + * #GMemoryMonitorWarningLevel as ranges, for example: + * |[ + * if (warning_level > G_MEMORY_MONITOR_WARNING_LEVEL_LOW) + * drop_caches (); + * ]| + * + * Since: 2.64 + */ +typedef enum { + G_MEMORY_MONITOR_WARNING_LEVEL_LOW = 50, + G_MEMORY_MONITOR_WARNING_LEVEL_MEDIUM = 100, + G_MEMORY_MONITOR_WARNING_LEVEL_CRITICAL = 255 +} GMemoryMonitorWarningLevel; + G_END_DECLS #endif /* __GIO_ENUMS_H__ */ diff --git a/gio/giomodule.c b/gio/giomodule.c index 1007abd..c420260 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -42,6 +42,9 @@ #include "gnotificationbackend.h" #include "ginitable.h" #include "gnetworkmonitor.h" +#include "gmemorymonitor.h" +#include "gmemorymonitorportal.h" +#include "gmemorymonitordbus.h" #ifdef G_OS_WIN32 #include "gregistrysettingsbackend.h" #endif @@ -1025,6 +1028,9 @@ extern GType _g_network_monitor_netlink_get_type (void); extern GType _g_network_monitor_nm_get_type (void); #endif +extern GType g_memory_monitor_dbus_get_type (void); +extern GType g_memory_monitor_portal_get_type (void); + #ifdef G_OS_UNIX extern GType g_fdo_notification_backend_get_type (void); extern GType g_gtk_notification_backend_get_type (void); @@ -1127,6 +1133,9 @@ _g_io_modules_ensure_extension_points_registered (void) ep = g_io_extension_point_register (G_NOTIFICATION_BACKEND_EXTENSION_POINT_NAME); g_io_extension_point_set_required_type (ep, G_TYPE_NOTIFICATION_BACKEND); + + ep = g_io_extension_point_register (G_MEMORY_MONITOR_EXTENSION_POINT_NAME); + g_io_extension_point_set_required_type (ep, G_TYPE_MEMORY_MONITOR); } G_UNLOCK (registered_extensions); @@ -1144,18 +1153,9 @@ get_gio_module_dir (void) gchar *install_dir; install_dir = g_win32_get_package_installation_directory_of_module (gio_dll); -#ifdef _MSC_VER - /* On Visual Studio builds we have all the libraries and binaries in bin - * so better load the gio modules from bin instead of lib - */ - module_dir = g_build_filename (install_dir, - "bin", "gio", "modules", - NULL); -#else module_dir = g_build_filename (install_dir, "lib", "gio", "modules", NULL); -#endif g_free (install_dir); #else module_dir = g_strdup (GIO_MODULE_DIR); @@ -1235,6 +1235,8 @@ _g_io_modules_ensure_loaded (void) g_type_ensure (g_fdo_notification_backend_get_type ()); g_type_ensure (g_gtk_notification_backend_get_type ()); g_type_ensure (g_portal_notification_backend_get_type ()); + g_type_ensure (g_memory_monitor_dbus_get_type ()); + g_type_ensure (g_memory_monitor_portal_get_type ()); g_type_ensure (g_network_monitor_portal_get_type ()); g_type_ensure (g_proxy_resolver_portal_get_type ()); #endif diff --git a/gio/gioprivate.h b/gio/gioprivate.h index 2bc54e4..608d912 100644 --- a/gio/gioprivate.h +++ b/gio/gioprivate.h @@ -43,6 +43,10 @@ void g_socket_connection_set_cached_remote_address (GSocketConnection *connectio #define G_IOV_MAX IOV_MAX #elif defined(UIO_MAXIOV) #define G_IOV_MAX UIO_MAXIOV +#elif defined(__APPLE__) +/* For macOS/iOS, UIO_MAXIOV is documented in writev(2), but + * only declares it if defined(KERNEL) */ +#define G_IOV_MAX 512 #else /* 16 is the minimum value required by POSIX */ #define G_IOV_MAX 16 diff --git a/gio/giowin32-private.c b/gio/giowin32-private.c new file mode 100644 index 0000000..7120ae0 --- /dev/null +++ b/gio/giowin32-private.c @@ -0,0 +1,447 @@ +/* giowin32-private.c - private glib-gio functions for W32 GAppInfo + * + * Copyright 2019 Руслан Ижбулатов + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see . + */ + + +static gssize +g_utf16_len (const gunichar2 *str) +{ + gssize result; + + for (result = 0; str[0] != 0; str++, result++) + ; + + return result; +} + +static gunichar2 * +g_wcsdup (const gunichar2 *str, gssize str_len) +{ + gssize str_size; + + g_return_val_if_fail (str != NULL, NULL); + + if (str_len == -1) + str_len = g_utf16_len (str); + + g_assert (str_len <= G_MAXSIZE / sizeof (gunichar2) - 1); + str_size = (str_len + 1) * sizeof (gunichar2); + + return g_memdup (str, str_size); +} + +static const gunichar2 * +g_utf16_wchr (const gunichar2 *str, const wchar_t wchr) +{ + for (; str != NULL && str[0] != 0; str++) + if ((wchar_t) str[0] == wchr) + return str; + + return NULL; +} + +static gboolean +g_utf16_to_utf8_and_fold (const gunichar2 *str, + gssize length, + gchar **str_u8, + gchar **str_u8_folded) +{ + gchar *u8; + gchar *folded; + u8 = g_utf16_to_utf8 (str, length, NULL, NULL, NULL); + + if (u8 == NULL) + return FALSE; + + folded = g_utf8_casefold (u8, -1); + + if (str_u8) + *str_u8 = g_steal_pointer (&u8); + + g_free (u8); + + if (str_u8_folded) + *str_u8_folded = g_steal_pointer (&folded); + + g_free (folded); + + return TRUE; +} + +/* Finds the last directory separator in @filename, + * returns a pointer to the position after that separator. + * If the string ends with a separator, returned value + * will be pointing at the NUL terminator. + * If the string does not contain separators, returns the + * string itself. + */ +static const gunichar2 * +g_utf16_find_basename (const gunichar2 *filename, + gssize len) +{ + const gunichar2 *result; + + if (len < 0) + len = g_utf16_len (filename); + if (len == 0) + return filename; + + result = &filename[len - 1]; + + while (result > filename) + { + if ((wchar_t) result[0] == L'/' || + (wchar_t) result[0] == L'\\') + { + result += 1; + break; + } + + result -= 1; + } + + return result; +} + +/* Finds the last directory separator in @filename, + * returns a pointer to the position after that separator. + * If the string ends with a separator, returned value + * will be pointing at the NUL terminator. + * If the string does not contain separators, returns the + * string itself. + */ +static const gchar * +g_utf8_find_basename (const gchar *filename, + gssize len) +{ + const gchar *result; + + if (len < 0) + len = strlen (filename); + if (len == 0) + return filename; + + result = &filename[len - 1]; + + while (result > filename) + { + if (result[0] == '/' || + result[0] == '\\') + { + result += 1; + break; + } + + result -= 1; + } + + return result; +} + +/** + * Parses @commandline, figuring out what the filename being invoked + * is. All returned strings are pointers into @commandline. + * @commandline must be a valid UTF-16 string and not be NULL. + * @after_executable is the first character after executable + * (usually a space, but not always). + * If @comma_separator is TRUE, accepts ',' as a separator between + * the filename and the following argument. + */ +static void +_g_win32_parse_filename (const gunichar2 *commandline, + gboolean comma_separator, + const gunichar2 **executable_start, + gssize *executable_len, + const gunichar2 **executable_basename, + const gunichar2 **after_executable) +{ + const gunichar2 *p; + const gunichar2 *first_argument; + gboolean quoted; + gssize len; + gssize execlen; + gboolean found; + + while ((wchar_t) commandline[0] == L' ') + commandline++; + + quoted = FALSE; + execlen = 0; + found = FALSE; + first_argument = NULL; + + if ((wchar_t) commandline[0] == L'"') + { + quoted = TRUE; + commandline += 1; + } + + len = g_utf16_len (commandline); + p = commandline; + + while (p < &commandline[len]) + { + switch ((wchar_t) p[0]) + { + case L'"': + if (quoted) + { + first_argument = p + 1; + /* Note: this is a valid commandline for opening "c:/file.txt": + * > "notepad"c:/file.txt + */ + p = &commandline[len]; + found = TRUE; + } + else + execlen += 1; + break; + case L' ': + if (!quoted) + { + first_argument = p; + p = &commandline[len]; + found = TRUE; + } + else + execlen += 1; + break; + case L',': + if (!quoted && comma_separator) + { + first_argument = p; + p = &commandline[len]; + found = TRUE; + } + else + execlen += 1; + break; + default: + execlen += 1; + break; + } + p += 1; + } + + if (!found) + first_argument = &commandline[len]; + + if (executable_start) + *executable_start = commandline; + + if (executable_len) + *executable_len = execlen; + + if (executable_basename) + *executable_basename = g_utf16_find_basename (commandline, execlen); + + if (after_executable) + *after_executable = first_argument; +} + +/* Make sure @commandline is a valid UTF-16 string before + * calling this function! + * follow_class_chain_to_handler() does perform such validation. + */ +static void +_g_win32_extract_executable (const gunichar2 *commandline, + gchar **ex_out, + gchar **ex_basename_out, + gchar **ex_folded_out, + gchar **ex_folded_basename_out, + gchar **dll_function_out) +{ + gchar *ex; + gchar *ex_folded; + const gunichar2 *first_argument; + const gunichar2 *executable; + const gunichar2 *executable_basename; + gboolean quoted; + gboolean folded; + gssize execlen; + + _g_win32_parse_filename (commandline, FALSE, &executable, &execlen, &executable_basename, &first_argument); + + commandline = executable; + + while ((wchar_t) first_argument[0] == L' ') + first_argument++; + + folded = g_utf16_to_utf8_and_fold (executable, (gssize) execlen, &ex, &ex_folded); + /* This should never fail as @executable has to be valid UTF-16. */ + g_assert (folded); + + if (dll_function_out) + *dll_function_out = NULL; + + /* See if the executable basename is "rundll32.exe". If so, then + * parse the rest of the commandline as r'"?path-to-dll"?[ ]*,*[ ]*dll_function_to_invoke' + */ + /* Using just "rundll32.exe", without an absolute path, seems + * very exploitable, but MS does that sometimes, so we have + * to accept that. + */ + if ((g_strcmp0 (ex_folded, "rundll32.exe") == 0 || + g_str_has_suffix (ex_folded, "\\rundll32.exe") || + g_str_has_suffix (ex_folded, "/rundll32.exe")) && + first_argument[0] != 0 && + dll_function_out != NULL) + { + /* Corner cases: + * > rundll32.exe c:\some,file,with,commas.dll,some_function + * is treated by rundll32 as: + * dll=c:\some + * function=file,with,commas.dll,some_function + * unless the dll name is surrounded by double quotation marks: + * > rundll32.exe "c:\some,file,with,commas.dll",some_function + * in which case everything works normally. + * Also, quoting only works if it surrounds the file name, i.e: + * > rundll32.exe "c:\some,file"",with,commas.dll",some_function + * will not work. + * Also, comma is optional when filename is quoted or when function + * name is separated from the filename by space(s): + * > rundll32.exe "c:\some,file,with,commas.dll"some_function + * will work, + * > rundll32.exe c:\some_dll_without_commas_or_spaces.dll some_function + * will work too. + * Also, any number of commas is accepted: + * > rundll32.exe c:\some_dll_without_commas_or_spaces.dll , , ,,, , some_function + * works just fine. + * And the ultimate example is: + * > "rundll32.exe""c:\some,file,with,commas.dll"some_function + * and it also works. + * Good job, Microsoft! + */ + const gunichar2 *filename_end = NULL; + gssize filename_len = 0; + gssize function_len = 0; + const gunichar2 *dllpart; + + quoted = FALSE; + + if ((wchar_t) first_argument[0] == L'"') + quoted = TRUE; + + _g_win32_parse_filename (first_argument, TRUE, &dllpart, &filename_len, NULL, &filename_end); + + if (filename_end[0] != 0 && filename_len > 0) + { + const gunichar2 *function_begin = filename_end; + + while ((wchar_t) function_begin[0] == L',' || (wchar_t) function_begin[0] == L' ') + function_begin += 1; + + if (function_begin[0] != 0) + { + gchar *dllpart_utf8; + gchar *dllpart_utf8_folded; + gchar *function_utf8; + gboolean folded; + const gunichar2 *space = g_utf16_wchr (function_begin, L' '); + + if (space) + function_len = space - function_begin; + else + function_len = g_utf16_len (function_begin); + + if (quoted) + first_argument += 1; + + folded = g_utf16_to_utf8_and_fold (first_argument, filename_len, &dllpart_utf8, &dllpart_utf8_folded); + g_assert (folded); + + function_utf8 = g_utf16_to_utf8 (function_begin, function_len, NULL, NULL, NULL); + + /* We only take this branch when dll_function_out is not NULL */ + *dll_function_out = g_steal_pointer (&function_utf8); + + g_free (function_utf8); + + /* + * Free our previous output candidate (rundll32) and replace it with the DLL path, + * then proceed forward as if nothing has changed. + */ + g_free (ex); + g_free (ex_folded); + + ex = dllpart_utf8; + ex_folded = dllpart_utf8_folded; + } + } + } + + if (ex_out) + { + if (ex_basename_out) + *ex_basename_out = (gchar *) g_utf8_find_basename (ex, -1); + + *ex_out = g_steal_pointer (&ex); + } + + g_free (ex); + + if (ex_folded_out) + { + if (ex_folded_basename_out) + *ex_folded_basename_out = (gchar *) g_utf8_find_basename (ex_folded, -1); + + *ex_folded_out = g_steal_pointer (&ex_folded); + } + + g_free (ex_folded); +} + +/** + * rundll32 accepts many different commandlines. Among them is this: + * > rundll32.exe "c:/program files/foo/bar.dll",,, , ,,,, , function_name %1 + * rundll32 just reads the first argument as a potentially quoted + * filename until the quotation ends (if quoted) or until a comma, + * or until a space. Then ignores all subsequent spaces (if any) and commas (if any; + * at least one comma is mandatory only if the filename is not quoted), + * and then interprets the rest of the commandline (until a space or a NUL-byte) + * as a name of a function. + * When GLib tries to run a program, it attempts to correctly re-quote the arguments, + * turning the first argument into "c:/program files/foo/bar.dll,,,". + * This breaks rundll32 parsing logic. + * Try to work around this by ensuring that the syntax is like this: + * > rundll32.exe "c:/program files/foo/bar.dll" function_name + * This syntax is valid for rundll32 *and* GLib spawn routines won't break it. + * + * @commandline must have at least 2 arguments, and the second argument + * must contain a (possibly quoted) filename, followed by a space or + * a comma. This can be checked for with an extract_executable() call - + * it should return a non-null dll_function. + */ +static void +_g_win32_fixup_broken_microsoft_rundll_commandline (gunichar2 *commandline) +{ + const gunichar2 *first_argument; + gunichar2 *after_first_argument; + + _g_win32_parse_filename (commandline, FALSE, NULL, NULL, NULL, &first_argument); + + while ((wchar_t) first_argument[0] == L' ') + first_argument++; + + _g_win32_parse_filename (first_argument, TRUE, NULL, NULL, NULL, (const gunichar2 **) &after_first_argument); + + if ((wchar_t) after_first_argument[0] == L',') + after_first_argument[0] = 0x0020; + /* Else everything is ok (first char after filename is ' ' or the first char + * of the function name - either way this will work). + */ +} diff --git a/gio/glib-compile-resources.c b/gio/glib-compile-resources.c index 9b82eba..f3675c1 100644 --- a/gio/glib-compile-resources.c +++ b/gio/glib-compile-resources.c @@ -1061,6 +1061,7 @@ main (int argc, char **argv) guint8 *data; gsize data_size; gsize i; + const char *export = "G_MODULE_EXPORT"; if (!g_file_get_contents (binary_target, (char **)&data, &data_size, NULL)) @@ -1081,6 +1082,9 @@ main (int argc, char **argv) return 1; } + if (internal) + export = "G_GNUC_INTERNAL"; + g_fprintf (file, "#include \n" "\n" @@ -1140,30 +1144,36 @@ main (int argc, char **argv) g_fprintf (file, "\n" "static GStaticResource static_resource = { %s_resource_data.data, sizeof (%s_resource_data.data)%s, NULL, NULL, NULL };\n" - "%s GResource *%s_get_resource (void);\n" + "\n" + "%s\n" + "GResource *%s_get_resource (void);\n" "GResource *%s_get_resource (void)\n" "{\n" " return g_static_resource_get_resource (&static_resource);\n" "}\n", - c_name, c_name, (external_data ? "" : " - 1 /* nul terminator */"), linkage, c_name, c_name); + c_name, c_name, (external_data ? "" : " - 1 /* nul terminator */"), + export, c_name, c_name); if (manual_register) { g_fprintf (file, "\n" - "%s void %s_unregister_resource (void);\n" + "%s\n" + "void %s_unregister_resource (void);\n" "void %s_unregister_resource (void)\n" "{\n" " g_static_resource_fini (&static_resource);\n" "}\n" "\n" - "%s void %s_register_resource (void);\n" + "%s\n" + "void %s_register_resource (void);\n" "void %s_register_resource (void)\n" "{\n" " g_static_resource_init (&static_resource);\n" "}\n", - linkage, c_name, c_name, linkage, c_name, c_name); + export, c_name, c_name, + export, c_name, c_name); } else { diff --git a/gio/gliststore.c b/gio/gliststore.c index 7b2a453..3c2361e 100644 --- a/gio/gliststore.c +++ b/gio/gliststore.c @@ -494,3 +494,86 @@ g_list_store_splice (GListStore *store, g_list_store_items_changed (store, position, n_removals, n_additions); } + +/** + * g_list_store_find_with_equal_func: + * @store: a #GListStore + * @item: (type GObject): an item + * @equal_func: (scope call): A custom equality check function + * @position: (out) (optional): the first position of @item, if it was found. + * + * Looks up the given @item in the list store by looping over the items and + * comparing them with @compare_func until the first occurrence of @item which + * matches. If @item was not found, then @position will not be set, and this + * method will return %FALSE. + * + * Returns: Whether @store contains @item. If it was found, @position will be + * set to the position where @item occurred for the first time. + * + * Since: 2.64 + */ +gboolean +g_list_store_find_with_equal_func (GListStore *store, + gpointer item, + GEqualFunc equal_func, + guint *position) +{ + GSequenceIter *iter, *begin, *end; + + g_return_val_if_fail (G_IS_LIST_STORE (store), FALSE); + g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type), + FALSE); + g_return_val_if_fail (equal_func != NULL, FALSE); + + /* NOTE: We can't use g_sequence_lookup() or g_sequence_search(), because we + * can't assume the sequence is sorted. */ + begin = g_sequence_get_begin_iter (store->items); + end = g_sequence_get_end_iter (store->items); + + iter = begin; + while (iter != end) + { + gpointer iter_item; + + iter_item = g_sequence_get (iter); + if (equal_func (iter_item, item)) + { + if (position) + *position = g_sequence_iter_get_position (iter); + return TRUE; + } + + iter = g_sequence_iter_next (iter); + } + + return FALSE; +} + +/** + * g_list_store_find: + * @store: a #GListStore + * @item: (type GObject): an item + * @position: (out) (optional): the first position of @item, if it was found. + * + * Looks up the given @item in the list store by looping over the items until + * the first occurrence of @item. If @item was not found, then @position will + * not be set, and this method will return %FALSE. + * + * If you need to compare the two items with a custom comparison function, use + * g_list_store_find_with_equal_func() with a custom #GEqualFunc instead. + * + * Returns: Whether @store contains @item. If it was found, @position will be + * set to the position where @item occurred for the first time. + * + * Since: 2.64 + */ +gboolean +g_list_store_find (GListStore *store, + gpointer item, + guint *position) +{ + return g_list_store_find_with_equal_func (store, + item, + g_direct_equal, + position); +} diff --git a/gio/gliststore.h b/gio/gliststore.h index 407d542..ef3b839 100644 --- a/gio/gliststore.h +++ b/gio/gliststore.h @@ -72,6 +72,17 @@ void g_list_store_splice (GListSt gpointer *additions, guint n_additions); +GLIB_AVAILABLE_IN_2_64 +gboolean g_list_store_find (GListStore *store, + gpointer item, + guint *position); + +GLIB_AVAILABLE_IN_2_64 +gboolean g_list_store_find_with_equal_func (GListStore *store, + gpointer item, + GEqualFunc equal_func, + guint *position); + G_END_DECLS #endif /* __G_LIST_STORE_H__ */ diff --git a/gio/glocalfile.c b/gio/glocalfile.c index 62f30b5..5d01a4b 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -1511,7 +1511,7 @@ g_local_file_delete (GFile *file, { int errsv = errno; - /* Posix allows EEXIST too, but the more sane error + /* Posix allows EEXIST too, but the clearer error is G_IO_ERROR_NOT_FOUND, and it's what nautilus expects */ if (errsv == EEXIST) @@ -2330,13 +2330,13 @@ g_local_file_make_directory (GFile *file, return TRUE; } +#ifdef HAVE_SYMLINK static gboolean g_local_file_make_symbolic_link (GFile *file, const char *symlink_value, GCancellable *cancellable, GError **error) { -#ifdef HAVE_SYMLINK GLocalFile *local = G_LOCAL_FILE (file); if (symlink (symlink_value, local->filename) == -1) @@ -2359,26 +2359,8 @@ g_local_file_make_symbolic_link (GFile *file, return FALSE; } return TRUE; -#else - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Symbolic links not supported")); - return FALSE; -#endif -} - - -static gboolean -g_local_file_copy (GFile *source, - GFile *destination, - GFileCopyFlags flags, - GCancellable *cancellable, - GFileProgressCallback progress_callback, - gpointer progress_callback_data, - GError **error) -{ - /* Fall back to default copy */ - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported"); - return FALSE; } +#endif static gboolean g_local_file_move (GFile *source, @@ -2979,8 +2961,9 @@ g_local_file_file_iface_init (GFileIface *iface) iface->delete_file = g_local_file_delete; iface->trash = g_local_file_trash; iface->make_directory = g_local_file_make_directory; +#ifdef HAVE_SYMLINK iface->make_symbolic_link = g_local_file_make_symbolic_link; - iface->copy = g_local_file_copy; +#endif iface->move = g_local_file_move; iface->monitor_dir = g_local_file_monitor_dir; iface->monitor_file = g_local_file_monitor_file; diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c index 487f8cc..5245567 100644 --- a/gio/glocalfileinfo.c +++ b/gio/glocalfileinfo.c @@ -504,7 +504,10 @@ get_xattrs (const char *path, } if (list_res_size == -1) - return; + { + g_free (list); + return; + } attr = list; while (list_res_size > 0) diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c index a3dd621..a8161bd 100644 --- a/gio/glocalfileoutputstream.c +++ b/gio/glocalfileoutputstream.c @@ -224,9 +224,9 @@ g_local_file_output_stream_write (GOutputStream *stream, #ifdef G_OS_UNIX /* Macro to check if struct iovec and GOutputVector have the same ABI */ #define G_OUTPUT_VECTOR_IS_IOVEC (sizeof (struct iovec) == sizeof (GOutputVector) && \ - sizeof ((struct iovec *) 0)->iov_base == sizeof ((GOutputVector *) 0)->buffer && \ + G_SIZEOF_MEMBER (struct iovec, iov_base) == G_SIZEOF_MEMBER (GOutputVector, buffer) && \ G_STRUCT_OFFSET (struct iovec, iov_base) == G_STRUCT_OFFSET (GOutputVector, buffer) && \ - sizeof ((struct iovec *) 0)->iov_len == sizeof((GOutputVector *) 0)->size && \ + G_SIZEOF_MEMBER (struct iovec, iov_len) == G_SIZEOF_MEMBER (GOutputVector, size) && \ G_STRUCT_OFFSET (struct iovec, iov_len) == G_STRUCT_OFFSET (GOutputVector, size)) static gboolean @@ -979,7 +979,7 @@ handle_overwrite_open (const char *filename, fchown (tmpfd, original_stat.st_uid, original_stat.st_gid) == -1 || #endif #ifdef HAVE_FCHMOD - fchmod (tmpfd, original_stat.st_mode) == -1 || + fchmod (tmpfd, original_stat.st_mode & ~S_IFMT) == -1 || #endif 0 ) diff --git a/gio/glocalvfs.c b/gio/glocalvfs.c index c29d071..f8d85e4 100644 --- a/gio/glocalvfs.c +++ b/gio/glocalvfs.c @@ -27,6 +27,7 @@ #include #include #ifdef G_OS_UNIX +#include "glib-unix.h" #include #endif #include @@ -160,15 +161,17 @@ g_local_vfs_parse_name (GVfs *vfs, struct passwd *passwd_file_entry; char *user_name; - user_name = g_strndup (user_start, user_end - user_start); - passwd_file_entry = getpwnam (user_name); - g_free (user_name); - - if (passwd_file_entry != NULL && - passwd_file_entry->pw_dir != NULL) - user_prefix = g_strdup (passwd_file_entry->pw_dir); - else - user_prefix = g_strdup (g_get_home_dir ()); + user_name = g_strndup (user_start, user_end - user_start); + passwd_file_entry = g_unix_get_passwd_entry (user_name, NULL); + g_free (user_name); + + if (passwd_file_entry != NULL && + passwd_file_entry->pw_dir != NULL) + user_prefix = g_strdup (passwd_file_entry->pw_dir); + else + user_prefix = g_strdup (g_get_home_dir ()); + + g_free (passwd_file_entry); } #else user_prefix = g_strdup (g_get_home_dir ()); diff --git a/gio/gmemorymonitor.c b/gio/gmemorymonitor.c new file mode 100644 index 0000000..4368ee3 --- /dev/null +++ b/gio/gmemorymonitor.c @@ -0,0 +1,150 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2019 Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#include "config.h" +#include "glib.h" +#include "glibintl.h" + +#include "gmemorymonitor.h" +#include "ginetaddress.h" +#include "ginetsocketaddress.h" +#include "ginitable.h" +#include "gioenumtypes.h" +#include "giomodule-priv.h" +#include "gtask.h" + +/** + * SECTION:gmemorymonitor + * @title: GMemoryMonitor + * @short_description: Memory usage monitor + * @include: gio/gio.h + * + * #GMemoryMonitor will monitor system memory and suggest to the application + * when to free memory so as to leave more room for other applications. + * It is implemented on Linux using the [Low Memory Monitor](https://gitlab.freedesktop.org/hadess/low-memory-monitor/) + * ([API documentation](https://hadess.pages.freedesktop.org/low-memory-monitor/)). + * + * There is also an implementation for use inside Flatpak sandboxes. + * + * Possible actions to take when the signal is received are: + * - Free caches + * - Save files that haven't been looked at in a while to disk, ready to be reopened when needed + * - Run a garbage collection cycle + * - Try and compress fragmented allocations + * - Exit on idle if the process has no reason to stay around + * + * See #GMemoryMonitorWarningLevel for details on the various warning levels. + * + * |[ + * static void + * warning_cb (GMemoryMonitor *m, GMemoryMonitorWarningLevel level) + * { + * g_debug ("Warning level: %d", level); + * if (warning_level > G_MEMORY_MONITOR_WARNING_LEVEL_LOW) + * drop_caches (); + * } + * + * static GMemoryMonitor * + * monitor_low_memory (void) + * { + * GMemoryMonitor *m; + * m = g_memory_monitor_dup_default (); + * g_signal_connect (G_OBJECT (m), "low-memory-warning", + * G_CALLBACK (warning_cb), NULL); + * return m; + * } + * ]| + * + * Don't forget to disconnect the #GMemoryMonitor::low-memory-warning + * signal, and unref the #GMemoryMonitor itself when exiting. + * + * Since: 2.64 + */ + +/** + * GMemoryMonitor: + * + * #GMemoryMonitor monitors system memory and indicates when + * the system is low on memory. + * + * Since: 2.64 + */ + +/** + * GMemoryMonitorInterface: + * @g_iface: The parent interface. + * @low_memory_warning: the virtual function pointer for the + * #GMemoryMonitor::low-memory-warning signal. + * + * The virtual function table for #GMemoryMonitor. + * + * Since: 2.64 + */ + +G_DEFINE_INTERFACE_WITH_CODE (GMemoryMonitor, g_memory_monitor, G_TYPE_OBJECT, + g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_INITABLE)) + +enum { + LOW_MEMORY_WARNING, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/** + * g_memory_monitor_dup_default: + * + * Gets a reference to the default #GMemoryMonitor for the system. + * + * Returns: (transfer full): a new reference to the default #GMemoryMonitor + * + * Since: 2.64 + */ +GMemoryMonitor * +g_memory_monitor_dup_default (void) +{ + return g_object_ref (_g_io_module_get_default (G_MEMORY_MONITOR_EXTENSION_POINT_NAME, + "GIO_USE_MEMORY_MONITOR", + NULL)); +} + +static void +g_memory_monitor_default_init (GMemoryMonitorInterface *iface) +{ + /** + * GMemoryMonitor::low-memory-warning: + * @monitor: a #GMemoryMonitor + * @level: the #GMemoryMonitorWarningLevel warning level + * + * Emitted when the system is running low on free memory. The signal + * handler should then take the appropriate action depending on the + * warning level. See the #GMemoryMonitorWarningLevel documentation for + * details. + * + * Since: 2.64 + */ + signals[LOW_MEMORY_WARNING] = + g_signal_new (I_("low-memory-warning"), + G_TYPE_MEMORY_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GMemoryMonitorInterface, low_memory_warning), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + G_TYPE_MEMORY_MONITOR_WARNING_LEVEL); +} diff --git a/gio/gmemorymonitor.h b/gio/gmemorymonitor.h new file mode 100644 index 0000000..a3ad216 --- /dev/null +++ b/gio/gmemorymonitor.h @@ -0,0 +1,62 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#ifndef __G_MEMORY_MONITOR_H__ +#define __G_MEMORY_MONITOR_H__ + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +/** + * G_MEMORY_MONITOR_EXTENSION_POINT_NAME: + * + * Extension point for memory usage monitoring functionality. + * See [Extending GIO][extending-gio]. + * + * Since: 2.64 + */ +#define G_MEMORY_MONITOR_EXTENSION_POINT_NAME "gio-memory-monitor" + +#define G_TYPE_MEMORY_MONITOR (g_memory_monitor_get_type ()) +GLIB_AVAILABLE_IN_2_64 +G_DECLARE_INTERFACE(GMemoryMonitor, g_memory_monitor, g, memory_monitor, GObject) + +#define G_MEMORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_MEMORY_MONITOR, GMemoryMonitor)) +#define G_IS_MEMORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_MEMORY_MONITOR)) +#define G_MEMORY_MONITOR_GET_INTERFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_MEMORY_MONITOR, GMemoryMonitorInterface)) + +struct _GMemoryMonitorInterface { + /*< private >*/ + GTypeInterface g_iface; + + /*< public >*/ + void (*low_memory_warning) (GMemoryMonitor *monitor, + GMemoryMonitorWarningLevel level); +}; + +GLIB_AVAILABLE_IN_2_64 +GMemoryMonitor *g_memory_monitor_dup_default (void); + +G_END_DECLS + +#endif /* __G_MEMORY_MONITOR_H__ */ diff --git a/gio/gmemorymonitordbus.c b/gio/gmemorymonitordbus.c new file mode 100644 index 0000000..a34a58d --- /dev/null +++ b/gio/gmemorymonitordbus.c @@ -0,0 +1,171 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#include "config.h" + +#include "gmemorymonitor.h" +#include "gmemorymonitordbus.h" +#include "gioerror.h" +#include "ginitable.h" +#include "giomodule-priv.h" +#include "glibintl.h" +#include "glib/gstdio.h" +#include "gdbusproxy.h" +#include "gdbusnamewatching.h" + +#define G_MEMORY_MONITOR_DBUS_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable)) + +static void g_memory_monitor_dbus_iface_init (GMemoryMonitorInterface *iface); +static void g_memory_monitor_dbus_initable_iface_init (GInitableIface *iface); + +struct _GMemoryMonitorDBus +{ + GObject parent_instance; + + guint watch_id; + GDBusProxy *proxy; + gulong signal_id; +}; + +G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorDBus, g_memory_monitor_dbus, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + g_memory_monitor_dbus_initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR, + g_memory_monitor_dbus_iface_init) + _g_io_modules_ensure_extension_points_registered (); + g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME, + g_define_type_id, + "dbus", + 30)) + +static void +g_memory_monitor_dbus_init (GMemoryMonitorDBus *dbus) +{ +} + +static void +proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + GMemoryMonitorDBus *dbus) +{ + guint8 level; + + if (g_strcmp0 (signal_name, "LowMemoryWarning") != 0) + return; + if (parameters == NULL) + return; + + g_variant_get (parameters, "(y)", &level); + g_signal_emit_by_name (dbus, "low-memory-warning", level); +} + +static void +lmm_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + GMemoryMonitorDBus *dbus = user_data; + GDBusProxy *proxy; + GError *error = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "org.freedesktop.LowMemoryMonitor", + "/org/freedesktop/LowMemoryMonitor", + "org.freedesktop.LowMemoryMonitor", + NULL, + &error); + + if (!proxy) + { + g_debug ("Failed to create LowMemoryMonitor D-Bus proxy: %s", + error->message); + g_error_free (error); + return; + } + + dbus->signal_id = g_signal_connect (G_OBJECT (proxy), "g-signal", + G_CALLBACK (proxy_signal_cb), dbus); + dbus->proxy = proxy; +} + +static void +lmm_vanished_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GMemoryMonitorDBus *dbus = user_data; + + if (dbus->proxy != NULL) + g_clear_signal_handler (&dbus->signal_id, dbus->proxy); + g_clear_object (&dbus->proxy); +} + +static gboolean +g_memory_monitor_dbus_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GMemoryMonitorDBus *dbus = G_MEMORY_MONITOR_DBUS (initable); + + dbus->watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, + "org.freedesktop.LowMemoryMonitor", + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + lmm_appeared_cb, + lmm_vanished_cb, + dbus, + NULL); + + return TRUE; +} + +static void +g_memory_monitor_dbus_finalize (GObject *object) +{ + GMemoryMonitorDBus *dbus = G_MEMORY_MONITOR_DBUS (object); + + if (dbus->proxy != NULL) + g_clear_signal_handler (&dbus->signal_id, dbus->proxy); + g_clear_object (&dbus->proxy); + g_clear_handle_id (&dbus->watch_id, g_bus_unwatch_name); + + G_OBJECT_CLASS (g_memory_monitor_dbus_parent_class)->finalize (object); +} + +static void +g_memory_monitor_dbus_class_init (GMemoryMonitorDBusClass *nl_class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (nl_class); + + gobject_class->finalize = g_memory_monitor_dbus_finalize; +} + +static void +g_memory_monitor_dbus_iface_init (GMemoryMonitorInterface *monitor_iface) +{ +} + +static void +g_memory_monitor_dbus_initable_iface_init (GInitableIface *iface) +{ + iface->init = g_memory_monitor_dbus_initable_init; +} diff --git a/gio/gmemorymonitordbus.h b/gio/gmemorymonitordbus.h new file mode 100644 index 0000000..e48e755 --- /dev/null +++ b/gio/gmemorymonitordbus.h @@ -0,0 +1,31 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#ifndef __G_MEMORY_MONITOR_DBUS_H__ +#define __G_MEMORY_MONITOR_DBUS_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_MEMORY_MONITOR_DBUS (g_memory_monitor_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (GMemoryMonitorDBus, g_memory_monitor_dbus, G, MEMORY_MONITOR_DBUS, GObject) + +G_END_DECLS + +#endif /* __G_MEMORY_MONITOR_DBUS_H__ */ diff --git a/gio/gmemorymonitorportal.c b/gio/gmemorymonitorportal.c new file mode 100644 index 0000000..440629f --- /dev/null +++ b/gio/gmemorymonitorportal.c @@ -0,0 +1,152 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#include "config.h" + +#include "gmemorymonitor.h" +#include "gmemorymonitorportal.h" +#include "ginitable.h" +#include "giomodule-priv.h" +#include "xdp-dbus.h" +#include "gportalsupport.h" + +#define G_MEMORY_MONITOR_PORTAL_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable)) + +static void g_memory_monitor_portal_iface_init (GMemoryMonitorInterface *iface); +static void g_memory_monitor_portal_initable_iface_init (GInitableIface *iface); + +struct _GMemoryMonitorPortal +{ + GObject parent_instance; + + GDBusProxy *proxy; + gulong signal_id; +}; + +G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorPortal, g_memory_monitor_portal, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + g_memory_monitor_portal_initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR, + g_memory_monitor_portal_iface_init) + _g_io_modules_ensure_extension_points_registered (); + g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME, + g_define_type_id, + "portal", + 40)) + +static void +g_memory_monitor_portal_init (GMemoryMonitorPortal *portal) +{ +} + +static void +proxy_signal (GDBusProxy *proxy, + const char *sender, + const char *signal, + GVariant *parameters, + GMemoryMonitorPortal *portal) +{ + guint8 level; + + if (strcmp (signal, "LowMemoryWarning") != 0) + return; + if (!parameters) + return; + + g_variant_get (parameters, "(y)", &level); + g_signal_emit_by_name (portal, "low-memory-warning", level); +} + +static gboolean +g_memory_monitor_portal_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GMemoryMonitorPortal *portal = G_MEMORY_MONITOR_PORTAL (initable); + GDBusProxy *proxy; + gchar *name_owner = NULL; + + if (!glib_should_use_portal ()) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not using portals"); + return FALSE; + } + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.MemoryMonitor", + cancellable, + error); + if (!proxy) + return FALSE; + + name_owner = g_dbus_proxy_get_name_owner (proxy); + + if (name_owner == NULL) + { + g_object_unref (proxy); + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_NAME_HAS_NO_OWNER, + "Desktop portal not found"); + return FALSE; + } + + g_free (name_owner); + + portal->signal_id = g_signal_connect (proxy, "g-signal", + G_CALLBACK (proxy_signal), portal); + + portal->proxy = proxy; + + return TRUE; +} + +static void +g_memory_monitor_portal_finalize (GObject *object) +{ + GMemoryMonitorPortal *portal = G_MEMORY_MONITOR_PORTAL (object); + + if (portal->proxy != NULL) + g_clear_signal_handler (&portal->signal_id, portal->proxy); + g_clear_object (&portal->proxy); + + G_OBJECT_CLASS (g_memory_monitor_portal_parent_class)->finalize (object); +} + +static void +g_memory_monitor_portal_class_init (GMemoryMonitorPortalClass *nl_class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (nl_class); + + gobject_class->finalize = g_memory_monitor_portal_finalize; +} + +static void +g_memory_monitor_portal_iface_init (GMemoryMonitorInterface *monitor_iface) +{ +} + +static void +g_memory_monitor_portal_initable_iface_init (GInitableIface *iface) +{ + iface->init = g_memory_monitor_portal_initable_init; +} diff --git a/gio/gmemorymonitorportal.h b/gio/gmemorymonitorportal.h new file mode 100644 index 0000000..57074b4 --- /dev/null +++ b/gio/gmemorymonitorportal.h @@ -0,0 +1,31 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#ifndef __G_MEMORY_MONITOR_PORTAL_H__ +#define __G_MEMORY_MONITOR_PORTAL_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_MEMORY_MONITOR_PORTAL (g_memory_monitor_portal_get_type ()) +G_DECLARE_FINAL_TYPE (GMemoryMonitorPortal, g_memory_monitor_portal, G, MEMORY_MONITOR_PORTAL, GObject) + +G_END_DECLS + +#endif /* __G_MEMORY_MONITOR_PORTAL_H__ */ diff --git a/gio/gmenumodel.c b/gio/gmenumodel.c index 8ca45e0..b3f3fb7 100644 --- a/gio/gmenumodel.c +++ b/gio/gmenumodel.c @@ -309,7 +309,7 @@ g_menu_model_real_iterate_item_attributes (GMenuModel *model, else { g_critical ("GMenuModel implementation '%s' doesn't override iterate_item_attributes() " - "and fails to return sane values from get_item_attributes()", + "and fails to return valid values from get_item_attributes()", G_OBJECT_TYPE_NAME (model)); result = NULL; } @@ -373,7 +373,7 @@ g_menu_model_real_iterate_item_links (GMenuModel *model, else { g_critical ("GMenuModel implementation '%s' doesn't override iterate_item_links() " - "and fails to return sane values from get_item_links()", + "and fails to return valid values from get_item_links()", G_OBJECT_TYPE_NAME (model)); result = NULL; } diff --git a/gio/gnetworkmonitorbase.c b/gio/gnetworkmonitorbase.c index 6ac37c4..d503759 100644 --- a/gio/gnetworkmonitorbase.c +++ b/gio/gnetworkmonitorbase.c @@ -45,7 +45,7 @@ enum struct _GNetworkMonitorBasePrivate { - GPtrArray *networks; + GHashTable *networks /* (element-type GInetAddressMask) (owned) */; gboolean have_ipv4_default_route; gboolean have_ipv6_default_route; gboolean is_available; @@ -58,6 +58,9 @@ struct _GNetworkMonitorBasePrivate static guint network_changed_signal = 0; static void queue_network_changed (GNetworkMonitorBase *monitor); +static guint inet_address_mask_hash (gconstpointer key); +static gboolean inet_address_mask_equal (gconstpointer a, + gconstpointer b); G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorBase, g_network_monitor_base, G_TYPE_OBJECT, G_ADD_PRIVATE (GNetworkMonitorBase) @@ -75,7 +78,9 @@ static void g_network_monitor_base_init (GNetworkMonitorBase *monitor) { monitor->priv = g_network_monitor_base_get_instance_private (monitor); - monitor->priv->networks = g_ptr_array_new_with_free_func (g_object_unref); + monitor->priv->networks = g_hash_table_new_full (inet_address_mask_hash, + inet_address_mask_equal, + g_object_unref, NULL); monitor->priv->context = g_main_context_get_thread_default (); if (monitor->priv->context) g_main_context_ref (monitor->priv->context); @@ -149,7 +154,7 @@ g_network_monitor_base_finalize (GObject *object) { GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object); - g_ptr_array_free (monitor->priv->networks, TRUE); + g_hash_table_unref (monitor->priv->networks); if (monitor->priv->network_changed_source) { g_source_destroy (monitor->priv->network_changed_source); @@ -180,15 +185,18 @@ g_network_monitor_base_can_reach_sockaddr (GNetworkMonitorBase *base, GSocketAddress *sockaddr) { GInetAddress *iaddr; - int i; + GHashTableIter iter; + gpointer key; if (!G_IS_INET_SOCKET_ADDRESS (sockaddr)) return FALSE; iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sockaddr)); - for (i = 0; i < base->priv->networks->len; i++) + g_hash_table_iter_init (&iter, base->priv->networks); + while (g_hash_table_iter_next (&iter, &key, NULL)) { - if (g_inet_address_mask_matches (base->priv->networks->pdata[i], iaddr)) + GInetAddressMask *mask = key; + if (g_inet_address_mask_matches (mask, iaddr)) return TRUE; } @@ -205,7 +213,7 @@ g_network_monitor_base_can_reach (GNetworkMonitor *monitor, GSocketAddressEnumerator *enumerator; GSocketAddress *addr; - if (base->priv->networks->len == 0) + if (g_hash_table_size (base->priv->networks) == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE, _("Network unreachable")); @@ -309,7 +317,7 @@ g_network_monitor_base_can_reach_async (GNetworkMonitor *monitor, task = g_task_new (monitor, cancellable, callback, user_data); g_task_set_source_tag (task, g_network_monitor_base_can_reach_async); - if (G_NETWORK_MONITOR_BASE (monitor)->priv->networks->len == 0) + if (g_hash_table_size (G_NETWORK_MONITOR_BASE (monitor)->priv->networks) == 0) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE, _("Network unreachable")); @@ -361,6 +369,60 @@ g_network_monitor_base_initable_iface_init (GInitableIface *iface) iface->init = g_network_monitor_base_initable_init; } +static guint +inet_address_mask_hash (gconstpointer key) +{ + GInetAddressMask *mask = G_INET_ADDRESS_MASK (key); + guint addr_hash; + guint mask_length = g_inet_address_mask_get_length (mask); + GInetAddress *addr = g_inet_address_mask_get_address (mask); + const guint8 *bytes = g_inet_address_to_bytes (addr); + gsize bytes_length = g_inet_address_get_native_size (addr); + + union + { + const guint8 *bytes; + guint32 *hash32; + guint64 *hash64; + } integerifier; + + /* If we can fit the entire address into the hash key, do it. Don’t worry + * about endianness; the address should always be in network endianness. */ + if (bytes_length == sizeof (guint32)) + { + integerifier.bytes = bytes; + addr_hash = *integerifier.hash32; + } + else if (bytes_length == sizeof (guint64)) + { + integerifier.bytes = bytes; + addr_hash = *integerifier.hash64; + } + else + { + gsize i; + + /* Otherwise, fall back to adding the bytes together. We do this, rather + * than XORing them, as routes often have repeated tuples which would + * cancel out under XOR. */ + addr_hash = 0; + for (i = 0; i < bytes_length; i++) + addr_hash += bytes[i]; + } + + return addr_hash + mask_length;; +} + +static gboolean +inet_address_mask_equal (gconstpointer a, + gconstpointer b) +{ + GInetAddressMask *mask_a = G_INET_ADDRESS_MASK (a); + GInetAddressMask *mask_b = G_INET_ADDRESS_MASK (b); + + return g_inet_address_mask_equal (mask_a, mask_b); +} + static gboolean emit_network_changed (gpointer user_data) { @@ -424,7 +486,7 @@ queue_network_changed (GNetworkMonitorBase *monitor) /** * g_network_monitor_base_add_network: * @monitor: the #GNetworkMonitorBase - * @network: a #GInetAddressMask + * @network: (transfer none): a #GInetAddressMask * * Adds @network to @monitor's list of available networks. * @@ -434,15 +496,9 @@ void g_network_monitor_base_add_network (GNetworkMonitorBase *monitor, GInetAddressMask *network) { - int i; - - for (i = 0; i < monitor->priv->networks->len; i++) - { - if (g_inet_address_mask_equal (monitor->priv->networks->pdata[i], network)) - return; - } + if (!g_hash_table_add (monitor->priv->networks, g_object_ref (network))) + return; - g_ptr_array_add (monitor->priv->networks, g_object_ref (network)); if (g_inet_address_mask_get_length (network) == 0) { switch (g_inet_address_mask_get_family (network)) @@ -481,33 +537,25 @@ void g_network_monitor_base_remove_network (GNetworkMonitorBase *monitor, GInetAddressMask *network) { - int i; + if (!g_hash_table_remove (monitor->priv->networks, network)) + return; - for (i = 0; i < monitor->priv->networks->len; i++) + if (g_inet_address_mask_get_length (network) == 0) { - if (g_inet_address_mask_equal (monitor->priv->networks->pdata[i], network)) + switch (g_inet_address_mask_get_family (network)) { - g_ptr_array_remove_index_fast (monitor->priv->networks, i); - - if (g_inet_address_mask_get_length (network) == 0) - { - switch (g_inet_address_mask_get_family (network)) - { - case G_SOCKET_FAMILY_IPV4: - monitor->priv->have_ipv4_default_route = FALSE; - break; - case G_SOCKET_FAMILY_IPV6: - monitor->priv->have_ipv6_default_route = FALSE; - break; - default: - break; - } - } - - queue_network_changed (monitor); - return; + case G_SOCKET_FAMILY_IPV4: + monitor->priv->have_ipv4_default_route = FALSE; + break; + case G_SOCKET_FAMILY_IPV6: + monitor->priv->have_ipv6_default_route = FALSE; + break; + default: + break; } } + + queue_network_changed (monitor); } /** @@ -526,7 +574,7 @@ g_network_monitor_base_set_networks (GNetworkMonitorBase *monitor, { int i; - g_ptr_array_set_size (monitor->priv->networks, 0); + g_hash_table_remove_all (monitor->priv->networks); monitor->priv->have_ipv4_default_route = FALSE; monitor->priv->have_ipv6_default_route = FALSE; diff --git a/gio/gregistrysettingsbackend.c b/gio/gregistrysettingsbackend.c index 26de251..b0e295a 100644 --- a/gio/gregistrysettingsbackend.c +++ b/gio/gregistrysettingsbackend.c @@ -98,7 +98,7 @@ //#define TRACE /* GSettings' limit */ -#define MAX_KEY_NAME_LENGTH 32 +#define MAX_KEY_NAME_LENGTH 128 /* Testing (on Windows XP SP3) shows that WaitForMultipleObjects fails with * "The parameter is incorrect" after 64 watches. We need one for the @@ -201,7 +201,7 @@ trace (const char *format, * equivalent function for g_warning because none of the registry errors can * result from programmer error (Microsoft programmers don't count), instead * they will mostly occur from people messing with the registry by hand. */ -static void +static void G_GNUC_PRINTF (2, 3) g_message_win32_error (DWORD result_code, const gchar *format, ...) @@ -312,7 +312,7 @@ handle_read_error (LONG result, { /* file not found means key value not set, this isn't an error for us. */ if (result != ERROR_FILE_NOT_FOUND) - g_message_win32_error (result, "Unable to query value %s/%s: %s.\n", + g_message_win32_error (result, "Unable to query value %s/%s", path_name, value_name); } diff --git a/gio/gresolver.c b/gio/gresolver.c index 732d217..651e7d0 100644 --- a/gio/gresolver.c +++ b/gio/gresolver.c @@ -296,15 +296,50 @@ remove_duplicates (GList *addrs) } } +static gboolean +hostname_is_localhost (const char *hostname) +{ + size_t len = strlen (hostname); + const char *p; + + /* Match "localhost", "localhost.", "*.localhost" and "*.localhost." */ + if (len < strlen ("localhost")) + return FALSE; + + if (hostname[len - 1] == '.') + len--; + + /* Scan backwards in @hostname to find the right-most dot (excluding the final dot, if it exists, as it was chopped off above). + * We can’t use strrchr() because because we need to operate with string lengths. + * End with @p pointing to the character after the right-most dot. */ + p = hostname + len - 1; + while (p >= hostname) + { + if (*p == '.') + { + p++; + break; + } + else if (p == hostname) + break; + p--; + } + + len -= p - hostname; + + return g_ascii_strncasecmp (p, "localhost", MAX (len, strlen ("localhost"))) == 0; +} + /* Note that this does not follow the "FALSE means @error is set" * convention. The return value tells the caller whether it should * return @addrs and @error to the caller right away, or if it should * continue and trying to resolve the name as a hostname. */ static gboolean -handle_ip_address (const char *hostname, - GList **addrs, - GError **error) +handle_ip_address_or_localhost (const char *hostname, + GList **addrs, + GResolverNameLookupFlags flags, + GError **error) { GInetAddress *addr; @@ -355,6 +390,28 @@ handle_ip_address (const char *hostname, return TRUE; } + /* Always resolve localhost to a loopback address so it can be reliably considered secure. + This behavior is being adopted by browsers: + - https://w3c.github.io/webappsec-secure-contexts/ + - https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/RC9dSw-O3fE/E3_0XaT0BAAJ + - https://chromium.googlesource.com/chromium/src.git/+/8da2a80724a9b896890602ff77ef2216cb951399 + - https://bugs.webkit.org/show_bug.cgi?id=171934 + - https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-06 + */ + if (hostname_is_localhost (hostname)) + { + if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY) + *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6)); + if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY) + *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4)); + if (*addrs == NULL) + { + *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6)); + *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4)); + } + return TRUE; + } + return FALSE; } @@ -374,7 +431,7 @@ lookup_by_name_real (GResolver *resolver, g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* Check if @hostname is just an IP address */ - if (handle_ip_address (hostname, &addrs, error)) + if (handle_ip_address_or_localhost (hostname, &addrs, flags, error)) return addrs; if (g_hostname_is_non_ascii (hostname)) @@ -513,7 +570,7 @@ lookup_by_name_async_real (GResolver *resolver, g_return_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY && flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)); /* Check if @hostname is just an IP address */ - if (handle_ip_address (hostname, &addrs, &error)) + if (handle_ip_address_or_localhost (hostname, &addrs, flags, &error)) { GTask *task; diff --git a/gio/gsettings.c b/gio/gsettings.c index 1e46d5a..eeb9e3b 100644 --- a/gio/gsettings.c +++ b/gio/gsettings.c @@ -2469,7 +2469,7 @@ g_settings_get_child (GSettings *settings, * * Returns: (transfer full) (element-type utf8): a list of the keys on * @settings, in no defined order - * Deprecated: 2.46: Use g_settings_schema_list_keys instead(). + * Deprecated: 2.46: Use g_settings_schema_list_keys() instead. */ gchar ** g_settings_list_keys (GSettings *settings) diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index 3a60b8c..0b94f76 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -345,6 +345,7 @@ initialise_schema_sources (void) { const gchar * const *dirs; const gchar *path; + gchar **extra_schema_dirs; gint i; /* iterate in reverse: count up, then count down */ @@ -357,7 +358,15 @@ initialise_schema_sources (void) try_prepend_data_dir (g_get_user_data_dir ()); if ((path = g_getenv ("GSETTINGS_SCHEMA_DIR")) != NULL) - try_prepend_dir (path); + { + extra_schema_dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, 0); + for (i = 0; extra_schema_dirs[i]; i++); + + while (i--) + try_prepend_dir (extra_schema_dirs[i]); + + g_strfreev (extra_schema_dirs); + } g_once_init_leave (&initialised, TRUE); } @@ -989,7 +998,7 @@ g_settings_schema_get_value (GSettingsSchema *schema, * database: those located at the path returned by this function. * * Relocatable schemas can be referenced by other schemas and can - * threfore describe multiple sets of keys at different locations. For + * therefore describe multiple sets of keys at different locations. For * relocatable schemas, this function will return %NULL. * * Returns: (transfer none): the path of the schema, or %NULL diff --git a/gio/gsocket.c b/gio/gsocket.c index 66073af..2a15bdd 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -364,6 +364,50 @@ _win32_unset_event_mask (GSocket *socket, int mask) recv (sockfd, (gpointer)buf, len, flags) #endif +static gchar * +address_to_string (GSocketAddress *address) +{ + GString *ret = g_string_new (""); + + if (G_IS_INET_SOCKET_ADDRESS (address)) + { + GInetSocketAddress *isa = G_INET_SOCKET_ADDRESS (address); + GInetAddress *ia = g_inet_socket_address_get_address (isa); + GSocketFamily family = g_inet_address_get_family (ia); + gchar *tmp; + + /* Represent IPv6 addresses in URL style: + * ::1 port 12345 -> [::1]:12345 */ + if (family == G_SOCKET_FAMILY_IPV6) + g_string_append_c (ret, '['); + + tmp = g_inet_address_to_string (ia); + g_string_append (ret, tmp); + g_free (tmp); + + if (family == G_SOCKET_FAMILY_IPV6) + { + guint32 scope = g_inet_socket_address_get_scope_id (isa); + + if (scope != 0) + g_string_append_printf (ret, "%%%u", scope); + + g_string_append_c (ret, ']'); + } + + g_string_append_c (ret, ':'); + + g_string_append_printf (ret, "%u", g_inet_socket_address_get_port (isa)); + } + else + { + /* For unknown address types, just show the type */ + g_string_append_printf (ret, "(%s)", G_OBJECT_TYPE_NAME (address)); + } + + return g_string_free (ret, FALSE); +} + static gboolean check_socket (GSocket *socket, GError **error) @@ -2153,9 +2197,13 @@ g_socket_bind (GSocket *socket, g_socket_address_get_native_size (address)) < 0) { int errsv = get_socket_errno (); + gchar *address_string = address_to_string (address); + g_set_error (error, G_IO_ERROR, socket_io_error_from_errno (errsv), - _("Error binding to address: %s"), socket_strerror (errsv)); + _("Error binding to address %s: %s"), + address_string, socket_strerror (errsv)); + g_free (address_string); return FALSE; } diff --git a/gio/gsocketclient.c b/gio/gsocketclient.c index 6adeee2..c994330 100644 --- a/gio/gsocketclient.c +++ b/gio/gsocketclient.c @@ -1337,13 +1337,15 @@ typedef struct GSocketConnectable *connectable; GSocketAddressEnumerator *enumerator; - GProxyAddress *proxy_addr; - GSocket *socket; - GIOStream *connection; + GCancellable *enumeration_cancellable; GSList *connection_attempts; + GSList *successful_connections; GError *last_error; + gboolean enumerated_at_least_once; + gboolean enumeration_completed; + gboolean connection_in_progress; gboolean completed; } GSocketClientAsyncConnectData; @@ -1355,10 +1357,9 @@ g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data) data->task = NULL; g_clear_object (&data->connectable); g_clear_object (&data->enumerator); - g_clear_object (&data->proxy_addr); - g_clear_object (&data->socket); - g_clear_object (&data->connection); + g_clear_object (&data->enumeration_cancellable); g_slist_free_full (data->connection_attempts, connection_attempt_unref); + g_slist_free_full (data->successful_connections, connection_attempt_unref); g_clear_error (&data->last_error); @@ -1370,6 +1371,7 @@ typedef struct GSocketAddress *address; GSocket *socket; GIOStream *connection; + GProxyAddress *proxy_addr; GSocketClientAsyncConnectData *data; /* unowned */ GSource *timeout_source; GCancellable *cancellable; @@ -1401,6 +1403,7 @@ connection_attempt_unref (gpointer pointer) g_clear_object (&attempt->socket); g_clear_object (&attempt->connection); g_clear_object (&attempt->cancellable); + g_clear_object (&attempt->proxy_addr); if (attempt->timeout_source) { g_source_destroy (attempt->timeout_source); @@ -1418,37 +1421,59 @@ connection_attempt_remove (ConnectionAttempt *attempt) } static void -g_socket_client_async_connect_complete (GSocketClientAsyncConnectData *data) +cancel_all_attempts (GSocketClientAsyncConnectData *data) { - g_assert (data->connection); + GSList *l; - if (!G_IS_SOCKET_CONNECTION (data->connection)) + for (l = data->connection_attempts; l; l = g_slist_next (l)) { - GSocketConnection *wrapper_connection; - - wrapper_connection = g_tcp_wrapper_connection_new (data->connection, data->socket); - g_object_unref (data->connection); - data->connection = (GIOStream *)wrapper_connection; + ConnectionAttempt *attempt_entry = l->data; + g_cancellable_cancel (attempt_entry->cancellable); + connection_attempt_unref (attempt_entry); } + g_slist_free (data->connection_attempts); + data->connection_attempts = NULL; - if (!data->completed) + g_slist_free_full (data->successful_connections, connection_attempt_unref); + data->successful_connections = NULL; + + g_cancellable_cancel (data->enumeration_cancellable); +} + +static void +g_socket_client_async_connect_complete (ConnectionAttempt *attempt) +{ + GSocketClientAsyncConnectData *data = attempt->data; + GError *error = NULL; + g_assert (attempt->connection); + g_assert (!data->completed); + + if (!G_IS_SOCKET_CONNECTION (attempt->connection)) { - GError *error = NULL; + GSocketConnection *wrapper_connection; - if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (data->task), &error)) - { - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL); - g_task_return_error (data->task, g_steal_pointer (&error)); - } - else - { - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, data->connection); - g_task_return_pointer (data->task, g_steal_pointer (&data->connection), g_object_unref); - } + wrapper_connection = g_tcp_wrapper_connection_new (attempt->connection, attempt->socket); + g_object_unref (attempt->connection); + attempt->connection = (GIOStream *)wrapper_connection; + } - data->completed = TRUE; + data->completed = TRUE; + cancel_all_attempts (data); + + if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (data->task), &error)) + { + g_debug ("GSocketClient: Connection cancelled!"); + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL); + g_task_return_error (data->task, g_steal_pointer (&error)); + } + else + { + g_debug ("GSocketClient: Connection successful!"); + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, attempt->connection); + g_task_return_pointer (data->task, g_steal_pointer (&attempt->connection), g_object_unref); } + connection_attempt_unref (attempt); g_object_unref (data->task); } @@ -1470,59 +1495,63 @@ static void enumerator_next_async (GSocketClientAsyncConnectData *data, gboolean add_task_ref) { - /* We need to cleanup the state */ - g_clear_object (&data->socket); - g_clear_object (&data->proxy_addr); - g_clear_object (&data->connection); - /* Each enumeration takes a ref. This arg just avoids repeated unrefs when an enumeration starts another enumeration */ if (add_task_ref) g_object_ref (data->task); g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL); + g_debug ("GSocketClient: Starting new address enumeration"); g_socket_address_enumerator_next_async (data->enumerator, - g_task_get_cancellable (data->task), + data->enumeration_cancellable, g_socket_client_enumerator_callback, data); } +static void try_next_connection_or_finish (GSocketClientAsyncConnectData *, gboolean); + static void g_socket_client_tls_handshake_callback (GObject *object, GAsyncResult *result, gpointer user_data) { - GSocketClientAsyncConnectData *data = user_data; + ConnectionAttempt *attempt = user_data; + GSocketClientAsyncConnectData *data = attempt->data; if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (object), result, &data->last_error)) { - g_object_unref (data->connection); - data->connection = G_IO_STREAM (object); + g_object_unref (attempt->connection); + attempt->connection = G_IO_STREAM (object); - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_TLS_HANDSHAKED, data->connectable, data->connection); - g_socket_client_async_connect_complete (data); + g_debug ("GSocketClient: TLS handshake succeeded"); + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_TLS_HANDSHAKED, data->connectable, attempt->connection); + g_socket_client_async_connect_complete (attempt); } else { g_object_unref (object); - enumerator_next_async (data, FALSE); + connection_attempt_unref (attempt); + g_debug ("GSocketClient: TLS handshake failed: %s", data->last_error->message); + try_next_connection_or_finish (data, TRUE); } } static void -g_socket_client_tls_handshake (GSocketClientAsyncConnectData *data) +g_socket_client_tls_handshake (ConnectionAttempt *attempt) { + GSocketClientAsyncConnectData *data = attempt->data; GIOStream *tlsconn; if (!data->client->priv->tls) { - g_socket_client_async_connect_complete (data); + g_socket_client_async_connect_complete (attempt); return; } - tlsconn = g_tls_client_connection_new (data->connection, + g_debug ("GSocketClient: Starting TLS handshake"); + tlsconn = g_tls_client_connection_new (attempt->connection, data->connectable, &data->last_error); if (tlsconn) @@ -1534,11 +1563,12 @@ g_socket_client_tls_handshake (GSocketClientAsyncConnectData *data) G_PRIORITY_DEFAULT, g_task_get_cancellable (data->task), g_socket_client_tls_handshake_callback, - data); + attempt); } else { - enumerator_next_async (data, FALSE); + connection_attempt_unref (attempt); + try_next_connection_or_finish (data, TRUE); } } @@ -1547,23 +1577,38 @@ g_socket_client_proxy_connect_callback (GObject *object, GAsyncResult *result, gpointer user_data) { - GSocketClientAsyncConnectData *data = user_data; + ConnectionAttempt *attempt = user_data; + GSocketClientAsyncConnectData *data = attempt->data; - g_object_unref (data->connection); - data->connection = g_proxy_connect_finish (G_PROXY (object), - result, - &data->last_error); - if (data->connection) + g_object_unref (attempt->connection); + attempt->connection = g_proxy_connect_finish (G_PROXY (object), + result, + &data->last_error); + if (attempt->connection) { - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, data->connection); + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, attempt->connection); } else { - enumerator_next_async (data, FALSE); + connection_attempt_unref (attempt); + try_next_connection_or_finish (data, TRUE); return; } - g_socket_client_tls_handshake (data); + g_socket_client_tls_handshake (attempt); +} + +static void +complete_connection_with_error (GSocketClientAsyncConnectData *data, + GError *error) +{ + g_debug ("GSocketClient: Connection failed: %s", error->message); + g_assert (!data->completed); + + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL); + data->completed = TRUE; + cancel_all_attempts (data); + g_task_return_error (data->task, error); } static gboolean @@ -1577,15 +1622,114 @@ task_completed_or_cancelled (GSocketClientAsyncConnectData *data) return TRUE; else if (g_cancellable_set_error_if_cancelled (cancellable, &error)) { - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL); - g_task_return_error (task, g_steal_pointer (&error)); - data->completed = TRUE; + complete_connection_with_error (data, g_steal_pointer (&error)); return TRUE; } else return FALSE; } +static gboolean +try_next_successful_connection (GSocketClientAsyncConnectData *data) +{ + ConnectionAttempt *attempt; + const gchar *protocol; + GProxy *proxy; + + if (data->connection_in_progress) + return FALSE; + + g_assert (data->successful_connections != NULL); + attempt = data->successful_connections->data; + g_assert (attempt != NULL); + data->successful_connections = g_slist_remove (data->successful_connections, attempt); + data->connection_in_progress = TRUE; + + g_debug ("GSocketClient: Starting application layer connection"); + + if (!attempt->proxy_addr) + { + g_socket_client_tls_handshake (g_steal_pointer (&attempt)); + return TRUE; + } + + protocol = g_proxy_address_get_protocol (attempt->proxy_addr); + + /* The connection should not be anything other than TCP, + * but let's put a safety guard in case + */ + if (!G_IS_TCP_CONNECTION (attempt->connection)) + { + g_critical ("Trying to proxy over non-TCP connection, this is " + "most likely a bug in GLib IO library."); + + g_set_error_literal (&data->last_error, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Proxying over a non-TCP connection is not supported.")); + } + else if (g_hash_table_contains (data->client->priv->app_proxies, protocol)) + { + /* Simply complete the connection, we don't want to do TLS handshake + * as the application proxy handling may need proxy handshake first */ + g_socket_client_async_connect_complete (g_steal_pointer (&attempt)); + return TRUE; + } + else if ((proxy = g_proxy_get_default_for_protocol (protocol))) + { + GIOStream *connection = attempt->connection; + GProxyAddress *proxy_addr = attempt->proxy_addr; + + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATING, data->connectable, attempt->connection); + g_debug ("GSocketClient: Starting proxy connection"); + g_proxy_connect_async (proxy, + connection, + proxy_addr, + g_task_get_cancellable (data->task), + g_socket_client_proxy_connect_callback, + g_steal_pointer (&attempt)); + g_object_unref (proxy); + return TRUE; + } + else + { + g_clear_error (&data->last_error); + + g_set_error (&data->last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Proxy protocol “%s” is not supported."), + protocol); + } + + data->connection_in_progress = FALSE; + g_clear_pointer (&attempt, connection_attempt_unref); + return FALSE; /* All non-return paths are failures */ +} + +static void +try_next_connection_or_finish (GSocketClientAsyncConnectData *data, + gboolean end_current_connection) +{ + if (end_current_connection) + data->connection_in_progress = FALSE; + + if (data->connection_in_progress) + return; + + /* Keep trying successful connections until one works, each iteration pops one */ + while (data->successful_connections) + { + if (try_next_successful_connection (data)) + return; + } + + if (!data->enumeration_completed) + { + enumerator_next_async (data, FALSE); + return; + } + + complete_connection_with_error (data, data->last_error); +} + static void g_socket_client_connected_callback (GObject *source, GAsyncResult *result, @@ -1593,10 +1737,7 @@ g_socket_client_connected_callback (GObject *source, { ConnectionAttempt *attempt = user_data; GSocketClientAsyncConnectData *data = attempt->data; - GSList *l; GError *error = NULL; - GProxy *proxy; - const gchar *protocol; if (task_completed_or_cancelled (data) || g_cancellable_is_cancelled (attempt->cancellable)) { @@ -1618,11 +1759,12 @@ g_socket_client_connected_callback (GObject *source, { clarify_connect_error (error, data->connectable, attempt->address); set_last_error (data, error); + g_debug ("GSocketClient: Connection attempt failed: %s", error->message); connection_attempt_remove (attempt); - enumerator_next_async (data, FALSE); connection_attempt_unref (attempt); + try_next_connection_or_finish (data, FALSE); } - else + else /* Silently ignore cancelled attempts */ { g_clear_error (&error); g_object_unref (data->task); @@ -1632,74 +1774,21 @@ g_socket_client_connected_callback (GObject *source, return; } - data->socket = g_steal_pointer (&attempt->socket); - data->connection = g_steal_pointer (&attempt->connection); - - for (l = data->connection_attempts; l; l = g_slist_next (l)) - { - ConnectionAttempt *attempt_entry = l->data; - g_cancellable_cancel (attempt_entry->cancellable); - connection_attempt_unref (attempt_entry); - } - g_slist_free (data->connection_attempts); - data->connection_attempts = NULL; - connection_attempt_unref (attempt); - - g_socket_connection_set_cached_remote_address ((GSocketConnection*)data->connection, NULL); - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTED, data->connectable, data->connection); + g_socket_connection_set_cached_remote_address ((GSocketConnection*)attempt->connection, NULL); + g_debug ("GSocketClient: TCP connection successful"); + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTED, data->connectable, attempt->connection); /* wrong, but backward compatible */ - g_socket_set_blocking (data->socket, TRUE); + g_socket_set_blocking (attempt->socket, TRUE); - if (!data->proxy_addr) - { - g_socket_client_tls_handshake (data); - return; - } - - protocol = g_proxy_address_get_protocol (data->proxy_addr); - - /* The connection should not be anything other than TCP, - * but let's put a safety guard in case + /* This ends the parallel "happy eyeballs" portion of connecting. + Now that we have a successful tcp connection we will attempt to connect + at the TLS/Proxy layer. If those layers fail we will move on to the next + connection. */ - if (!G_IS_TCP_CONNECTION (data->connection)) - { - g_critical ("Trying to proxy over non-TCP connection, this is " - "most likely a bug in GLib IO library."); - - g_set_error_literal (&data->last_error, - G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - _("Proxying over a non-TCP connection is not supported.")); - - enumerator_next_async (data, FALSE); - } - else if (g_hash_table_contains (data->client->priv->app_proxies, protocol)) - { - /* Simply complete the connection, we don't want to do TLS handshake - * as the application proxy handling may need proxy handshake first */ - g_socket_client_async_connect_complete (data); - } - else if ((proxy = g_proxy_get_default_for_protocol (protocol))) - { - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATING, data->connectable, data->connection); - g_proxy_connect_async (proxy, - data->connection, - data->proxy_addr, - g_task_get_cancellable (data->task), - g_socket_client_proxy_connect_callback, - data); - g_object_unref (proxy); - } - else - { - g_clear_error (&data->last_error); - - g_set_error (&data->last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - _("Proxy protocol “%s” is not supported."), - protocol); - - enumerator_next_async (data, FALSE); - } + connection_attempt_remove (attempt); + data->successful_connections = g_slist_append (data->successful_connections, g_steal_pointer (&attempt)); + try_next_connection_or_finish (data, FALSE); } static gboolean @@ -1707,7 +1796,11 @@ on_connection_attempt_timeout (gpointer data) { ConnectionAttempt *attempt = data; - enumerator_next_async (attempt->data, TRUE); + if (!attempt->data->enumeration_completed) + { + g_debug ("GSocketClient: Timeout reached, trying another enumeration"); + enumerator_next_async (attempt->data, TRUE); + } g_clear_pointer (&attempt->timeout_source, g_source_unref); return G_SOURCE_REMOVE; @@ -1717,9 +1810,9 @@ static void on_connection_cancelled (GCancellable *cancellable, gpointer data) { - GCancellable *attempt_cancellable = data; + GCancellable *linked_cancellable = G_CANCELLABLE (data); - g_cancellable_cancel (attempt_cancellable); + g_cancellable_cancel (linked_cancellable); } static void @@ -1743,39 +1836,49 @@ g_socket_client_enumerator_callback (GObject *object, result, &error); if (address == NULL) { - if (data->connection_attempts) + if (G_UNLIKELY (data->enumeration_completed)) + return; + + data->enumeration_completed = TRUE; + g_debug ("GSocketClient: Address enumeration completed (out of addresses)"); + + /* As per API docs: We only care about error if its the first call, + after that the enumerator is done. + + Note that we don't care about cancellation errors because + task_completed_or_cancelled() above should handle that. + + If this fails and nothing is in progress then we will complete task here. + */ + if ((data->enumerated_at_least_once && !data->connection_attempts && !data->connection_in_progress) || + !data->enumerated_at_least_once) { - g_object_unref (data->task); - return; + g_debug ("GSocketClient: Address enumeration failed: %s", error ? error->message : NULL); + if (data->last_error) + { + g_clear_error (&error); + error = data->last_error; + data->last_error = NULL; + } + else if (!error) + { + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Unknown error on connect")); + } + + complete_connection_with_error (data, error); } - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL); - data->completed = TRUE; - if (!error) - { - if (data->last_error) - { - error = data->last_error; - data->last_error = NULL; - } - else - { - g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Unknown error on connect")); - } - } - g_task_return_error (data->task, error); + /* Enumeration should never trigger again, drop our ref */ g_object_unref (data->task); return; } + data->enumerated_at_least_once = TRUE; + g_debug ("GSocketClient: Address enumeration succeeded"); g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED, data->connectable, NULL); - if (G_IS_PROXY_ADDRESS (address) && - data->client->priv->enable_proxy) - data->proxy_addr = g_object_ref (G_PROXY_ADDRESS (address)); - g_clear_error (&data->last_error); socket = create_socket (data->client, address, &data->last_error); @@ -1793,8 +1896,12 @@ g_socket_client_enumerator_callback (GObject *object, attempt->cancellable = g_cancellable_new (); attempt->connection = (GIOStream *)g_socket_connection_factory_create_connection (socket); attempt->timeout_source = g_timeout_source_new (HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS); + + if (G_IS_PROXY_ADDRESS (address) && data->client->priv->enable_proxy) + attempt->proxy_addr = g_object_ref (G_PROXY_ADDRESS (address)); + g_source_set_callback (attempt->timeout_source, on_connection_attempt_timeout, attempt, NULL); - g_source_attach (attempt->timeout_source, g_main_context_get_thread_default ()); + g_source_attach (attempt->timeout_source, g_task_get_context (data->task)); data->connection_attempts = g_slist_append (data->connection_attempts, attempt); if (g_task_get_cancellable (data->task)) @@ -1802,6 +1909,7 @@ g_socket_client_enumerator_callback (GObject *object, g_object_ref (attempt->cancellable), g_object_unref); g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address); + g_debug ("GSocketClient: Starting TCP connection attempt"); g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTING, data->connectable, attempt->connection); g_socket_connection_connect_async (G_SOCKET_CONNECTION (attempt->connection), address, @@ -1854,24 +1962,48 @@ g_socket_client_connect_async (GSocketClient *client, else data->enumerator = g_socket_connectable_enumerate (connectable); - /* The flow and ownership here isn't quite obvious: - - The task starts an async attempt to connect. - - Each attempt holds a single ref on task. - - Each attempt may create new attempts by timing out (not a failure) so - there are multiple attempts happening in parallel. - - Upon failure an attempt will start a new attempt that steals its ref - until there are no more attempts left and it drops its ref. - - Upon success it will cancel all other attempts and continue on - to the rest of the connection (tls, proxies, etc) which do not - happen in parallel and at the very end drop its ref. - - Upon cancellation an attempt drops its ref. - */ + /* This function tries to match the behavior of g_socket_client_connect () + which is simple enough but much of it is done in parallel to be as responsive + as possible as per Happy Eyeballs (RFC 8305). This complicates flow quite a + bit but we can describe it in 3 sections: + + Firstly we have address enumeration (DNS): + - This may be triggered multiple times by enumerator_next_async(). + - It also has its own cancellable (data->enumeration_cancellable). + - Enumeration is done lazily because GNetworkAddressAddressEnumerator + also does work in parallel and may lazily add new addresses. + - If the first enumeration errors then the task errors. Otherwise all enumerations + will potentially be used (until task or enumeration is cancelled). + + Then we start attempting connections (TCP): + - Each connection is independent and kept in a ConnectionAttempt object. + - They each hold a ref on the main task and have their own cancellable. + - Multiple attempts may happen in parallel as per Happy Eyeballs. + - Upon failure or timeouts more connection attempts are made. + - If no connections succeed the task errors. + - Upon success they are kept in a list of successful connections. + + Lastly we connect at the application layer (TLS, Proxies): + - These are done in serial. + - The reasoning here is that Happy Eyeballs is about making bad connections responsive + at the IP/TCP layers. Issues at the application layer are generally not due to + connectivity issues but rather misconfiguration. + - Upon failure it will try the next TCP connection until it runs out and + the task errors. + - Upon success it cancels everything remaining (enumeration and connections) + and returns the connection. + */ data->task = g_task_new (client, cancellable, callback, user_data); g_task_set_check_cancellable (data->task, FALSE); /* We handle this manually */ g_task_set_source_tag (data->task, g_socket_client_connect_async); g_task_set_task_data (data->task, data, (GDestroyNotify)g_socket_client_async_connect_data_free); + data->enumeration_cancellable = g_cancellable_new (); + if (cancellable) + g_cancellable_connect (cancellable, G_CALLBACK (on_connection_cancelled), + g_object_ref (data->enumeration_cancellable), g_object_unref); + enumerator_next_async (data, FALSE); } @@ -1990,6 +2122,7 @@ g_socket_client_connect_to_uri_async (GSocketClient *client, } else { + g_debug("g_socket_client_connect_to_uri_async"); g_socket_client_connect_async (client, connectable, cancellable, callback, user_data); diff --git a/gio/gsocks5proxy.c b/gio/gsocks5proxy.c index c58be83..a6544df 100644 --- a/gio/gsocks5proxy.c +++ b/gio/gsocks5proxy.c @@ -170,8 +170,22 @@ parse_nego_reply (const guint8 *data, *must_auth = TRUE; break; - case SOCKS5_AUTH_GSSAPI: case SOCKS5_AUTH_NO_ACCEPT: + if (!has_auth) + { + /* The server has said it accepts none of our authentication methods, + * but given the slightly odd implementation of set_nego_msg(), we + * actually only gave it the choice of %SOCKS5_AUTH_NONE, since the + * caller specified no username or password. + * Return %G_IO_ERROR_PROXY_NEED_AUTH so the caller knows that if + * they specify a username and password and try again, authentication + * might succeed (since we’ll send %SOCKS5_AUTH_USR_PASS next time). */ + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_NEED_AUTH, + _("The SOCKSv5 proxy requires authentication.")); + return FALSE; + } + G_GNUC_FALLTHROUGH; + case SOCKS5_AUTH_GSSAPI: default: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED, _("The SOCKSv5 proxy requires an authentication " @@ -229,7 +243,7 @@ set_auth_msg (guint8 *msg, static gboolean check_auth_status (const guint8 *data, GError **error) { - if (data[0] != SOCKS5_VERSION + if (data[0] != SOCKS5_AUTH_VERSION || data[1] != SOCKS5_REP_SUCCEEDED) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED, diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c index b5515c7..8cc9354 100644 --- a/gio/gsubprocess.c +++ b/gio/gsubprocess.c @@ -1595,6 +1595,23 @@ g_subprocess_communicate_internal (GSubprocess *subprocess, if (subprocess->stdin_pipe) { g_assert (stdin_buf != NULL); + +#ifdef G_OS_UNIX + /* We're doing async writes to the pipe, and the async write mechanism assumes + * that streams polling as writable do SOME progress (possibly partial) and then + * stop, but never block. + * + * However, for blocking pipes, unix will return writable if there is *any* space left + * but still block until the full buffer size is available before returning from write. + * So, to avoid async blocking on the main loop we make this non-blocking here. + * + * It should be safe to change the fd because we're the only user at this point as + * per the g_subprocess_communicate() docs, and all the code called by this function + * properly handles non-blocking fds. + */ + g_unix_set_fd_nonblocking (g_unix_output_stream_get_fd (G_UNIX_OUTPUT_STREAM (subprocess->stdin_pipe)), TRUE, NULL); +#endif + state->stdin_buf = g_memory_input_stream_new_from_bytes (stdin_buf); g_output_stream_splice_async (subprocess->stdin_pipe, (GInputStream*)state->stdin_buf, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, diff --git a/gio/gtask.c b/gio/gtask.c index 9950bb3..2059e51 100644 --- a/gio/gtask.c +++ b/gio/gtask.c @@ -27,6 +27,8 @@ #include "glibintl.h" +#include + /** * SECTION:gtask * @short_description: Cancellable synchronous or asynchronous task @@ -1497,7 +1499,7 @@ g_task_start_task_thread (GTask *task, /** * g_task_run_in_thread: * @task: a #GTask - * @task_func: a #GTaskThreadFunc + * @task_func: (scope async): a #GTaskThreadFunc * * Runs @task_func in another thread. When @task_func returns, @task's * #GAsyncReadyCallback will be invoked in @task's #GMainContext. @@ -1540,7 +1542,7 @@ g_task_run_in_thread (GTask *task, /** * g_task_run_in_thread_sync: * @task: a #GTask - * @task_func: a #GTaskThreadFunc + * @task_func: (scope async): a #GTaskThreadFunc * * Runs @task_func in another thread, and waits for it to return or be * cancelled. You can use g_task_propagate_pointer(), etc, afterward @@ -1959,6 +1961,100 @@ g_task_had_error (GTask *task) return FALSE; } +static void +value_free (gpointer value) +{ + g_value_unset (value); + g_free (value); +} + +/** + * g_task_return_value: + * @task: a #GTask + * @result: (nullable) (transfer none): the #GValue result of + * a task function + * + * Sets @task's result to @result (by copying it) and completes the task. + * + * If @result is %NULL then a #GValue of type #G_TYPE_POINTER + * with a value of %NULL will be used for the result. + * + * This is a very generic low-level method intended primarily for use + * by language bindings; for C code, g_task_return_pointer() and the + * like will normally be much easier to use. + * + * Since: 2.64 + */ +void +g_task_return_value (GTask *task, + GValue *result) +{ + GValue *value; + + g_return_if_fail (G_IS_TASK (task)); + g_return_if_fail (!task->ever_returned); + + value = g_new0 (GValue, 1); + + if (result == NULL) + { + g_value_init (value, G_TYPE_POINTER); + g_value_set_pointer (value, NULL); + } + else + { + g_value_init (value, G_VALUE_TYPE (result)); + g_value_copy (result, value); + } + + g_task_return_pointer (task, value, value_free); +} + +/** + * g_task_propagate_value: + * @task: a #GTask + * @value: (out caller-allocates): return location for the #GValue + * @error: return location for a #GError + * + * Gets the result of @task as a #GValue, and transfers ownership of + * that value to the caller. As with g_task_return_value(), this is + * a generic low-level method; g_task_propagate_pointer() and the like + * will usually be more useful for C code. + * + * If the task resulted in an error, or was cancelled, then this will + * instead set @error and return %FALSE. + * + * Since this method transfers ownership of the return value (or + * error) to the caller, you may only call it once. + * + * Returns: %TRUE if @task succeeded, %FALSE on error. + * + * Since: 2.64 + */ +gboolean +g_task_propagate_value (GTask *task, + GValue *value, + GError **error) +{ + g_return_val_if_fail (G_IS_TASK (task), FALSE); + g_return_val_if_fail (value != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (g_task_propagate_error (task, error)) + return FALSE; + + g_return_val_if_fail (task->result_set, FALSE); + g_return_val_if_fail (task->result_destroy == value_free, FALSE); + + memcpy (value, task->result.pointer, sizeof (GValue)); + g_free (task->result.pointer); + + task->result_destroy = NULL; + task->result_set = FALSE; + + return TRUE; +} + /** * g_task_get_completed: * @task: a #GTask. diff --git a/gio/gtask.h b/gio/gtask.h index 4fc1c85..73a31e1 100644 --- a/gio/gtask.h +++ b/gio/gtask.h @@ -142,6 +142,9 @@ void g_task_return_new_error (GTask *task, gint code, const char *format, ...) G_GNUC_PRINTF (4, 5); +GLIB_AVAILABLE_IN_2_64 +void g_task_return_value (GTask *task, + GValue *result); GLIB_AVAILABLE_IN_2_36 gboolean g_task_return_error_if_cancelled (GTask *task); @@ -155,6 +158,10 @@ gboolean g_task_propagate_boolean (GTask *task, GLIB_AVAILABLE_IN_2_36 gssize g_task_propagate_int (GTask *task, GError **error); +GLIB_AVAILABLE_IN_2_64 +gboolean g_task_propagate_value (GTask *task, + GValue *value, + GError **error); GLIB_AVAILABLE_IN_2_36 gboolean g_task_had_error (GTask *task); GLIB_AVAILABLE_IN_2_44 diff --git a/gio/gtestdbus.c b/gio/gtestdbus.c index 11cf029..633c935 100644 --- a/gio/gtestdbus.c +++ b/gio/gtestdbus.c @@ -604,11 +604,12 @@ start_daemon (GTestDBus *self) g_spawn_async_with_pipes (NULL, (gchar **) argv, NULL, -#ifdef G_OS_WIN32 /* We Need this to get the pid returned on win32 */ G_SPAWN_DO_NOT_REAP_CHILD | -#endif - G_SPAWN_SEARCH_PATH, + G_SPAWN_SEARCH_PATH | + /* dbus-daemon will not abuse our descriptors, and + * passing this means we can use posix_spawn() for speed */ + G_SPAWN_LEAVE_DESCRIPTORS_OPEN, NULL, NULL, &self->priv->bus_pid, diff --git a/gio/gtlsclientconnection.c b/gio/gtlsclientconnection.c index b38fad6..d41d0c2 100644 --- a/gio/gtlsclientconnection.c +++ b/gio/gtlsclientconnection.c @@ -103,14 +103,12 @@ g_tls_client_connection_default_init (GTlsClientConnectionInterface *iface) /** * GTlsClientConnection:use-ssl3: * - * If %TRUE, forces the connection to use a fallback version of TLS - * or SSL, rather than trying to negotiate the best version of TLS - * to use. See g_tls_client_connection_set_use_ssl3(). + * SSL 3.0 is no longer supported. See + * g_tls_client_connection_set_use_ssl3() for details. * * Since: 2.28 * - * Deprecated: 2.56: SSL 3.0 is insecure, and this property does not - * generally enable or disable it, despite its name. + * Deprecated: 2.56: SSL 3.0 is insecure. */ g_object_interface_install_property (iface, g_param_spec_boolean ("use-ssl3", @@ -270,16 +268,14 @@ g_tls_client_connection_set_server_identity (GTlsClientConnection *conn, * g_tls_client_connection_get_use_ssl3: * @conn: the #GTlsClientConnection * - * Gets whether @conn will force the lowest-supported TLS protocol - * version rather than attempt to negotiate the highest mutually- - * supported version of TLS; see g_tls_client_connection_set_use_ssl3(). + * SSL 3.0 is no longer supported. See + * g_tls_client_connection_set_use_ssl3() for details. * - * Returns: whether @conn will use the lowest-supported TLS protocol version + * Returns: %FALSE * * Since: 2.28 * - * Deprecated: 2.56: SSL 3.0 is insecure, and this function does not - * actually indicate whether it is enabled. + * Deprecated: 2.56: SSL 3.0 is insecure. */ gboolean g_tls_client_connection_get_use_ssl3 (GTlsClientConnection *conn) @@ -289,32 +285,28 @@ g_tls_client_connection_get_use_ssl3 (GTlsClientConnection *conn) g_return_val_if_fail (G_IS_TLS_CLIENT_CONNECTION (conn), 0); g_object_get (G_OBJECT (conn), "use-ssl3", &use_ssl3, NULL); - return use_ssl3; + return FALSE; } /** * g_tls_client_connection_set_use_ssl3: * @conn: the #GTlsClientConnection - * @use_ssl3: whether to use the lowest-supported protocol version - * - * Since 2.42.1, if @use_ssl3 is %TRUE, this forces @conn to use the - * lowest-supported TLS protocol version rather than trying to properly - * negotiate the highest mutually-supported protocol version with the - * peer. Be aware that SSL 3.0 is generally disabled by the - * #GTlsBackend, so the lowest-supported protocol version is probably - * not SSL 3.0. - * - * Since 2.58, this may additionally cause an RFC 7507 fallback SCSV to - * be sent to the server, causing modern TLS servers to immediately - * terminate the connection. You should generally only use this function - * if you need to connect to broken servers that exhibit TLS protocol - * version intolerance, and when an initial attempt to connect to a - * server normally has already failed. + * @use_ssl3: a #gboolean, ignored + * + * Since GLib 2.42.1, SSL 3.0 is no longer supported. + * + * From GLib 2.42.1 through GLib 2.62, this function could be used to + * force use of TLS 1.0, the lowest-supported TLS protocol version at + * the time. In the past, this was needed to connect to broken TLS + * servers that exhibited protocol version intolerance. Such servers + * are no longer common, and using TLS 1.0 is no longer considered + * acceptable. + * + * Since GLib 2.64, this function does nothing. * * Since: 2.28 * - * Deprecated: 2.56: SSL 3.0 is insecure, and this function does not - * generally enable or disable it, despite its name. + * Deprecated: 2.56: SSL 3.0 is insecure. */ void g_tls_client_connection_set_use_ssl3 (GTlsClientConnection *conn, @@ -322,7 +314,7 @@ g_tls_client_connection_set_use_ssl3 (GTlsClientConnection *conn, { g_return_if_fail (G_IS_TLS_CLIENT_CONNECTION (conn)); - g_object_set (G_OBJECT (conn), "use-ssl3", use_ssl3, NULL); + g_object_set (G_OBJECT (conn), "use-ssl3", FALSE, NULL); } /** @@ -359,12 +351,34 @@ g_tls_client_connection_get_accepted_cas (GTlsClientConnection *conn) * @conn: a #GTlsClientConnection * @source: a #GTlsClientConnection * - * Copies session state from one connection to another. This is - * not normally needed, but may be used when the same session - * needs to be used between different endpoints as is required - * by some protocols such as FTP over TLS. @source should have - * already completed a handshake, and @conn should not have - * completed a handshake. + * Possibly copies session state from one connection to another, for use + * in TLS session resumption. This is not normally needed, but may be + * used when the same session needs to be used between different + * endpoints, as is required by some protocols, such as FTP over TLS. + * @source should have already completed a handshake and, since TLS 1.3, + * it should have been used to read data at least once. @conn should not + * have completed a handshake. + * + * It is not possible to know whether a call to this function will + * actually do anything. Because session resumption is normally used + * only for performance benefit, the TLS backend might not implement + * this function. Even if implemented, it may not actually succeed in + * allowing @conn to resume @source's TLS session, because the server + * may not have sent a session resumption token to @source, or it may + * refuse to accept the token from @conn. There is no way to know + * whether a call to this function is actually successful. + * + * Using this function is not required to benefit from session + * resumption. If the TLS backend supports session resumption, the + * session will be resumed automatically if it is possible to do so + * without weakening the privacy guarantees normally provided by TLS, + * without need to call this function. For example, with TLS 1.3, + * a session ticket will be automatically copied from any + * #GTlsClientConnection that has previously received session tickets + * from the server, provided a ticket is available that has not + * previously been used for session resumption, since session ticket + * reuse would be a privacy weakness. Using this function causes the + * ticket to be copied without regard for privacy considerations. * * Since: 2.46 */ diff --git a/gio/gtlsconnection.c b/gio/gtlsconnection.c index 2e431ae..cc9a183 100644 --- a/gio/gtlsconnection.c +++ b/gio/gtlsconnection.c @@ -139,7 +139,8 @@ g_tls_connection_class_init (GTlsConnectionClass *klass) TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | - G_PARAM_STATIC_STRINGS)); + G_PARAM_STATIC_STRINGS | + G_PARAM_DEPRECATED)); /** * GTlsConnection:database: * @@ -195,6 +196,8 @@ g_tls_connection_class_init (GTlsConnectionClass *klass) * g_tls_connection_set_rehandshake_mode(). * * Since: 2.28 + * + * Deprecated: 2.60: The rehandshake mode is ignored. */ g_object_class_install_property (gobject_class, PROP_REHANDSHAKE_MODE, g_param_spec_enum ("rehandshake-mode", @@ -730,27 +733,10 @@ g_tls_connection_get_require_close_notify (GTlsConnection *conn) * @conn: a #GTlsConnection * @mode: the rehandshaking mode * - * Sets how @conn behaves with respect to rehandshaking requests, when - * TLS 1.2 or older is in use. - * - * %G_TLS_REHANDSHAKE_NEVER means that it will never agree to - * rehandshake after the initial handshake is complete. (For a client, - * this means it will refuse rehandshake requests from the server, and - * for a server, this means it will close the connection with an error - * if the client attempts to rehandshake.) - * - * %G_TLS_REHANDSHAKE_SAFELY means that the connection will allow a - * rehandshake only if the other end of the connection supports the - * TLS `renegotiation_info` extension. This is the default behavior, - * but means that rehandshaking will not work against older - * implementations that do not support that extension. - * - * %G_TLS_REHANDSHAKE_UNSAFELY means that the connection will allow - * rehandshaking even without the `renegotiation_info` extension. On - * the server side in particular, this is not recommended, since it - * leaves the server open to certain attacks. However, this mode is - * necessary if you need to allow renegotiation with older client - * software. + * Since GLib 2.64, changing the rehandshake mode is no longer supported + * and will have no effect. With TLS 1.3, rehandshaking has been removed from + * the TLS protocol, replaced by separate post-handshake authentication and + * rekey operations. * * Since: 2.28 * @@ -766,7 +752,7 @@ g_tls_connection_set_rehandshake_mode (GTlsConnection *conn, g_return_if_fail (G_IS_TLS_CONNECTION (conn)); g_object_set (G_OBJECT (conn), - "rehandshake-mode", mode, + "rehandshake-mode", G_TLS_REHANDSHAKE_SAFELY, NULL); } G_GNUC_END_IGNORE_DEPRECATIONS @@ -778,7 +764,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS * Gets @conn rehandshaking mode. See * g_tls_connection_set_rehandshake_mode() for details. * - * Returns: @conn's rehandshaking mode + * Returns: %G_TLS_REHANDSHAKE_SAFELY * * Since: 2.28 * @@ -792,12 +778,15 @@ g_tls_connection_get_rehandshake_mode (GTlsConnection *conn) { GTlsRehandshakeMode mode; - g_return_val_if_fail (G_IS_TLS_CONNECTION (conn), G_TLS_REHANDSHAKE_NEVER); + g_return_val_if_fail (G_IS_TLS_CONNECTION (conn), G_TLS_REHANDSHAKE_SAFELY); + /* Continue to call g_object_get(), even though the return value is + * ignored, so that behavior doesn’t change for derived classes. + */ g_object_get (G_OBJECT (conn), "rehandshake-mode", &mode, NULL); - return mode; + return G_TLS_REHANDSHAKE_SAFELY; } G_GNUC_END_IGNORE_DEPRECATIONS @@ -887,28 +876,30 @@ g_tls_connection_get_negotiated_protocol (GTlsConnection *conn) * * On the client side, it is never necessary to call this method; * although the connection needs to perform a handshake after - * connecting (or after sending a "STARTTLS"-type command) and may - * need to rehandshake later if the server requests it, + * connecting (or after sending a "STARTTLS"-type command), * #GTlsConnection will handle this for you automatically when you try - * to send or receive data on the connection. However, you can call - * g_tls_connection_handshake() manually if you want to know for sure - * whether the initial handshake succeeded or failed (as opposed to - * just immediately trying to write to @conn's output stream, in which - * case if it fails, it may not be possible to tell if it failed - * before or after completing the handshake). + * to send or receive data on the connection. You can call + * g_tls_connection_handshake() manually if you want to know whether + * the initial handshake succeeded or failed (as opposed to just + * immediately trying to use @conn to read or write, in which case, + * if it fails, it may not be possible to tell if it failed before or + * after completing the handshake), but beware that servers may reject + * client authentication after the handshake has completed, so a + * successful handshake does not indicate the connection will be usable. * * Likewise, on the server side, although a handshake is necessary at * the beginning of the communication, you do not need to call this * function explicitly unless you want clearer error reporting. * - * If TLS 1.2 or older is in use, you may call - * g_tls_connection_handshake() after the initial handshake to - * rehandshake; however, this usage is deprecated because rehandshaking - * is no longer part of the TLS protocol in TLS 1.3. Accordingly, the - * behavior of calling this function after the initial handshake is now - * undefined, except it is guaranteed to be reasonable and - * nondestructive so as to preserve compatibility with code written for - * older versions of GLib. + * Previously, calling g_tls_connection_handshake() after the initial + * handshake would trigger a rehandshake; however, this usage was + * deprecated in GLib 2.60 because rehandshaking was removed from the + * TLS protocol in TLS 1.3. Since GLib 2.64, calling this function after + * the initial handshake will no longer do anything. + * + * When using a #GTlsConnection created by #GSocketClient, the + * #GSocketClient performs the initial handshake, so calling this + * function manually is not recommended. * * #GTlsConnection::accept_certificate may be emitted during the * handshake. diff --git a/gio/gtrashportal.c b/gio/gtrashportal.c index b6aca37..9922be2 100644 --- a/gio/gtrashportal.c +++ b/gio/gtrashportal.c @@ -86,10 +86,10 @@ g_trash_portal_trash_file (GFile *file, path = g_file_get_path (file); - fd = g_open (path, O_RDWR | O_CLOEXEC); + fd = g_open (path, O_RDWR | O_CLOEXEC | O_NOFOLLOW); if (fd == -1 && errno == EISDIR) /* If it is a directory, fall back to O_PATH */ - fd = g_open (path, O_PATH | O_CLOEXEC | O_RDONLY); + fd = g_open (path, O_PATH | O_CLOEXEC | O_RDONLY | O_NOFOLLOW); errsv = errno; diff --git a/gio/gunixmounts.c b/gio/gunixmounts.c index cbf2ee9..1880d99 100644 --- a/gio/gunixmounts.c +++ b/gio/gunixmounts.c @@ -152,7 +152,11 @@ static GList *_g_get_unix_mounts (void); static GList *_g_get_unix_mount_points (void); static gboolean proc_mounts_watch_is_running (void); +G_LOCK_DEFINE_STATIC (proc_mounts_source); + +/* Protected by proc_mounts_source lock */ static guint64 mount_poller_time = 0; +static GSource *proc_mounts_watch_source; #ifdef HAVE_SYS_MNTTAB_H #define MNTOPT_RO "ro" @@ -1485,18 +1489,21 @@ get_mounts_timestamp (void) { const char *monitor_file; struct stat buf; + guint64 timestamp = 0; + + G_LOCK (proc_mounts_source); monitor_file = get_mtab_monitor_file (); /* Don't return mtime for /proc/ files */ if (monitor_file && !g_str_has_prefix (monitor_file, "/proc/")) { if (stat (monitor_file, &buf) == 0) - return (guint64)buf.st_mtime; + timestamp = buf.st_mtime; } else if (proc_mounts_watch_is_running ()) { /* it's being monitored by poll, so return mount_poller_time */ - return mount_poller_time; + timestamp = mount_poller_time; } else { @@ -1505,9 +1512,12 @@ get_mounts_timestamp (void) * return TRUE so any application caches depending on it (like eg. * the one in GIO) get invalidated and don't hold possibly outdated * data - see Bug 787731 */ - return (guint64) g_get_monotonic_time (); + timestamp = g_get_monotonic_time (); } - return 0; + + G_UNLOCK (proc_mounts_source); + + return timestamp; } static guint64 @@ -1704,10 +1714,10 @@ G_DEFINE_TYPE (GUnixMountMonitor, g_unix_mount_monitor, G_TYPE_OBJECT) static GContextSpecificGroup mount_monitor_group; static GFileMonitor *fstab_monitor; static GFileMonitor *mtab_monitor; -static GSource *proc_mounts_watch_source; static GList *mount_poller_mounts; static guint mtab_file_changed_id; +/* Called with proc_mounts_source lock held. */ static gboolean proc_mounts_watch_is_running (void) { @@ -1746,6 +1756,9 @@ mtab_file_changed (GFileMonitor *monitor, GFileMonitorEvent event_type, gpointer user_data) { + GMainContext *context; + GSource *source; + if (event_type != G_FILE_MONITOR_EVENT_CHANGED && event_type != G_FILE_MONITOR_EVENT_CREATED && event_type != G_FILE_MONITOR_EVENT_DELETED) @@ -1758,7 +1771,16 @@ mtab_file_changed (GFileMonitor *monitor, if (mtab_file_changed_id > 0) return; - mtab_file_changed_id = g_idle_add (mtab_file_changed_cb, NULL); + context = g_main_context_get_thread_default (); + if (!context) + context = g_main_context_default (); + + source = g_idle_source_new (); + g_source_set_priority (source, G_PRIORITY_DEFAULT); + g_source_set_callback (source, mtab_file_changed_cb, NULL, NULL); + g_source_set_name (source, "[gio] mtab_file_changed_cb"); + g_source_attach (source, context); + g_source_unref (source); } static gboolean @@ -1768,7 +1790,10 @@ proc_mounts_changed (GIOChannel *channel, { if (cond & G_IO_ERR) { + G_LOCK (proc_mounts_source); mount_poller_time = (guint64) g_get_monotonic_time (); + G_UNLOCK (proc_mounts_source); + g_context_specific_group_emit (&mount_monitor_group, signals[MOUNTS_CHANGED]); } @@ -1802,7 +1827,10 @@ mount_change_poller (gpointer user_data) if (has_changed) { + G_LOCK (proc_mounts_source); mount_poller_time = (guint64) g_get_monotonic_time (); + G_UNLOCK (proc_mounts_source); + g_context_specific_group_emit (&mount_monitor_group, signals[MOUNTPOINTS_CHANGED]); } @@ -1819,11 +1847,13 @@ mount_monitor_stop (void) g_object_unref (fstab_monitor); } + G_LOCK (proc_mounts_source); if (proc_mounts_watch_source != NULL) { g_source_destroy (proc_mounts_watch_source); proc_mounts_watch_source = NULL; } + G_UNLOCK (proc_mounts_source); if (mtab_monitor) { @@ -1831,6 +1861,12 @@ mount_monitor_stop (void) g_object_unref (mtab_monitor); } + if (mtab_file_changed_id) + { + g_source_remove (mtab_file_changed_id); + mtab_file_changed_id = 0; + } + g_list_free_full (mount_poller_mounts, (GDestroyNotify) g_unix_mount_free); } @@ -1869,7 +1905,10 @@ mount_monitor_start (void) } else { + G_LOCK (proc_mounts_source); + proc_mounts_watch_source = g_io_create_watch (proc_mounts_channel, G_IO_ERR); + mount_poller_time = (guint64) g_get_monotonic_time (); g_source_set_callback (proc_mounts_watch_source, (GSourceFunc) proc_mounts_changed, NULL, NULL); @@ -1877,6 +1916,8 @@ mount_monitor_start (void) g_main_context_get_thread_default ()); g_source_unref (proc_mounts_watch_source); g_io_channel_unref (proc_mounts_channel); + + G_UNLOCK (proc_mounts_source); } } else @@ -1889,6 +1930,8 @@ mount_monitor_start (void) } else { + G_LOCK (proc_mounts_source); + proc_mounts_watch_source = g_timeout_source_new_seconds (3); mount_poller_mounts = _g_get_unix_mounts (); mount_poller_time = (guint64)g_get_monotonic_time (); @@ -1898,6 +1941,8 @@ mount_monitor_start (void) g_source_attach (proc_mounts_watch_source, g_main_context_get_thread_default ()); g_source_unref (proc_mounts_watch_source); + + G_UNLOCK (proc_mounts_source); } } diff --git a/gio/gunixoutputstream.c b/gio/gunixoutputstream.c index 506e09a..1af0b44 100644 --- a/gio/gunixoutputstream.c +++ b/gio/gunixoutputstream.c @@ -403,9 +403,9 @@ g_unix_output_stream_write (GOutputStream *stream, /* Macro to check if struct iovec and GOutputVector have the same ABI */ #define G_OUTPUT_VECTOR_IS_IOVEC (sizeof (struct iovec) == sizeof (GOutputVector) && \ - sizeof ((struct iovec *) 0)->iov_base == sizeof ((GOutputVector *) 0)->buffer && \ + G_SIZEOF_MEMBER (struct iovec, iov_base) == G_SIZEOF_MEMBER (GOutputVector, buffer) && \ G_STRUCT_OFFSET (struct iovec, iov_base) == G_STRUCT_OFFSET (GOutputVector, buffer) && \ - sizeof ((struct iovec *) 0)->iov_len == sizeof((GOutputVector *) 0)->size && \ + G_SIZEOF_MEMBER (struct iovec, iov_len) == G_SIZEOF_MEMBER (GOutputVector, size) && \ G_STRUCT_OFFSET (struct iovec, iov_len) == G_STRUCT_OFFSET (GOutputVector, size)) static gboolean diff --git a/gio/gunixsocketaddress.c b/gio/gunixsocketaddress.c index 27e195e..0ab1a62 100644 --- a/gio/gunixsocketaddress.c +++ b/gio/gunixsocketaddress.c @@ -67,7 +67,7 @@ enum }; #ifndef UNIX_PATH_MAX -#define UNIX_PATH_MAX sizeof (((struct sockaddr_un *) 0)->sun_path) +#define UNIX_PATH_MAX G_SIZEOF_MEMBER (struct sockaddr_un, sun_path) #endif struct _GUnixSocketAddressPrivate diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 9f335b3..446d88e 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -139,6 +139,13 @@ struct _GWin32AppInfoHandler { /* Pointer to a location within @executable */ gchar *executable_basename; + /* If not NULL, then @executable and its derived fields contain the name + * of a DLL file (without the name of the function that rundll32.exe should + * invoke), and this field contains the name of the function to be invoked. + * The application is then invoked as 'rundll32.exe "dll_path",dll_function other_arguments...'. + */ + gchar *dll_function; + /* Icon of the application for this handler */ GIcon *icon; @@ -213,6 +220,13 @@ struct _GWin32AppInfoApplication { /* Pointer to a location within @executable */ gchar *executable_basename; + /* If not NULL, then @executable and its derived fields contain the name + * of a DLL file (without the name of the function that rundll32.exe should + * invoke), and this field contains the name of the function to be invoked. + * The application is then invoked as 'rundll32.exe "dll_path",dll_function other_arguments...'. + */ + gchar *dll_function; + /* Explicitly supported URLs, hashmap from map-owned gchar ptr (schema, * UTF-8, folded) -> a GWin32AppInfoHandler * Schema can be used as a key in the urls hashmap. @@ -294,6 +308,7 @@ g_win32_appinfo_handler_dispose (GObject *object) g_clear_pointer (&handler->proxy_command, g_free); g_clear_pointer (&handler->executable_folded, g_free); g_clear_pointer (&handler->executable, g_free); + g_clear_pointer (&handler->dll_function, g_free); g_clear_object (&handler->key); g_clear_object (&handler->proxy_key); g_clear_object (&handler->icon); @@ -332,6 +347,7 @@ g_win32_appinfo_application_dispose (GObject *object) g_clear_pointer (&app->command_u8, g_free); g_clear_pointer (&app->executable_folded, g_free); g_clear_pointer (&app->executable, g_free); + g_clear_pointer (&app->dll_function, g_free); g_clear_pointer (&app->supported_urls, g_hash_table_destroy); g_clear_pointer (&app->supported_exts, g_hash_table_destroy); g_clear_object (&app->icon); @@ -464,17 +480,6 @@ static GWin32RegistryKey *applications_key; /* Watch this key */ static GWin32RegistryKey *classes_root_key; -static gunichar2 * -g_wcsdup (const gunichar2 *str, gssize str_size) -{ - if (str_size == -1) - { - str_size = wcslen (str) + 1; - str_size *= sizeof (gunichar2); - } - return g_memdup (str, str_size); -} - #define URL_ASSOCIATIONS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\" #define USER_CHOICE L"\\UserChoice" #define OPEN_WITH_PROGIDS L"\\OpenWithProgids" @@ -486,6 +491,12 @@ g_wcsdup (const gunichar2 *str, gssize str_size) #define REG_PATH_MAX 256 #define REG_PATH_MAX_SIZE (REG_PATH_MAX * sizeof (gunichar2)) +/* for g_wcsdup(), + * _g_win32_extract_executable(), + * _g_win32_fixup_broken_microsoft_rundll_commandline() + */ +#include "giowin32-private.c" + static gunichar2 * read_resource_string (gunichar2 *res) { @@ -694,41 +705,29 @@ _g_win32_registry_key_build_and_new_w (GError **error, ...) return key; } - +/* Slow and dirty validator for UTF-16 strings */ static gboolean -utf8_and_fold (const gunichar2 *str, - gchar **str_u8, - gchar **str_u8_folded) +g_utf16_validate (const gunichar2 *str, + glong len) { - gchar *u8; - gchar *folded; - u8 = g_utf16_to_utf8 (str, -1, NULL, NULL, NULL); + gchar *tmp; - if (u8 == NULL) + if (str == NULL) return FALSE; - folded = g_utf8_casefold (u8, -1); + tmp = g_utf16_to_utf8 (str, len, NULL, NULL, NULL); - if (folded == NULL) - { - g_free (u8); - return FALSE; - } - - if (str_u8) - *str_u8 = u8; - else - g_free (u8); + if (tmp == NULL) + return FALSE; - if (str_u8_folded) - *str_u8_folded = folded; - else - g_free (folded); + g_free (tmp); return TRUE; } - +/* Does a UTF-16 validity check on *proxy_command and/or *program_command. + * Fails if that check doesn't pass. + */ static gboolean follow_class_chain_to_handler (const gunichar2 *program_id, gsize program_id_size, @@ -772,8 +771,9 @@ follow_class_chain_to_handler (const gunichar2 *program_id, NULL); if (got_value && val_type == G_WIN32_REGISTRY_VALUE_STR) { - if ((program_id_u8 != NULL || program_id_folded != NULL) && - !utf8_and_fold (program_id, program_id_u8, program_id_folded)) + if (((program_id_u8 != NULL || program_id_folded != NULL) && + !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) || + !g_utf16_validate (*program_command, -1)) { g_object_unref (key); g_free (program_command); @@ -805,27 +805,23 @@ follow_class_chain_to_handler (const gunichar2 *program_id, (void **) proxy_id, &proxy_id_size, NULL); + g_object_unref (key); + if (!got_value || (val_type != G_WIN32_REGISTRY_VALUE_STR)) { - g_object_unref (key); g_clear_pointer (proxy_id, g_free); + return FALSE; } - if (proxy_key) - *proxy_key = key; - else - g_object_unref (key); - key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, *proxy_id, SHELL_OPEN_COMMAND, NULL); if (key == NULL) { g_clear_pointer (proxy_id, g_free); - if (proxy_key) - g_clear_object (proxy_key); + return FALSE; } @@ -836,12 +832,17 @@ follow_class_chain_to_handler (const gunichar2 *program_id, (void **) proxy_command, NULL, NULL); - g_object_unref (key); + + if (proxy_key) + *proxy_key = key; + else + g_object_unref (key); if (!got_value || val_type != G_WIN32_REGISTRY_VALUE_STR || ((program_id_u8 != NULL || program_id_folded != NULL) && - !utf8_and_fold (program_id, program_id_u8, program_id_folded))) + !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) || + !g_utf16_validate (*proxy_command, -1)) { g_clear_pointer (proxy_id, g_free); g_clear_pointer (proxy_command, g_free); @@ -853,124 +854,6 @@ follow_class_chain_to_handler (const gunichar2 *program_id, return TRUE; } - -static void -extract_executable (gunichar2 *commandline, - gchar **ex_out, - gchar **ex_basename_out, - gchar **ex_folded_out, - gchar **ex_folded_basename_out) -{ - gchar *ex; - gchar *ex_folded; - gunichar2 *p; - gboolean quoted; - size_t len; - size_t execlen; - gunichar2 *exepart; - gboolean found; - - quoted = FALSE; - execlen = 0; - found = FALSE; - len = wcslen (commandline); - p = commandline; - - while (p < &commandline[len]) - { - switch (p[0]) - { - case L'"': - quoted = !quoted; - break; - case L' ': - if (!quoted) - { - execlen = p - commandline; - p = &commandline[len]; - found = TRUE; - } - break; - default: - break; - } - p += 1; - } - - if (!found) - execlen = len; - - exepart = g_wcsdup (commandline, (execlen + 1) * sizeof (gunichar2)); - exepart[execlen] = L'\0'; - - p = &exepart[0]; - - while (execlen > 0 && exepart[0] == L'"' && exepart[execlen - 1] == L'"') - { - p = &exepart[1]; - exepart[execlen - 1] = L'\0'; - execlen -= 2; - } - - if (!utf8_and_fold (p, &ex, &ex_folded)) - /* Currently no code to handle this case. It shouldn't happen though... */ - g_assert_not_reached (); - - g_free (exepart); - - if (ex_out) - { - *ex_out = ex; - - if (ex_basename_out) - { - *ex_basename_out = &ex[strlen (ex) - 1]; - - while (*ex_basename_out > ex) - { - if ((*ex_basename_out)[0] == '/' || - (*ex_basename_out)[0] == '\\') - { - *ex_basename_out += 1; - break; - } - - *ex_basename_out -= 1; - } - } - } - else - { - g_free (ex); - } - - if (ex_folded_out) - { - *ex_folded_out = ex_folded; - - if (ex_folded_basename_out) - { - *ex_folded_basename_out = &ex_folded[strlen (ex_folded) - 1]; - - while (*ex_folded_basename_out > ex_folded) - { - if ((*ex_folded_basename_out)[0] == '/' || - (*ex_folded_basename_out)[0] == '\\') - { - *ex_folded_basename_out += 1; - break; - } - - *ex_folded_basename_out -= 1; - } - } - } - else - { - g_free (ex_folded); - } -} - static void get_url_association (const gunichar2 *schema) { @@ -998,7 +881,7 @@ get_url_association (const gunichar2 *schema) if (user_choice == NULL) return; - if (!utf8_and_fold (schema, &schema_u8, &schema_folded)) + if (!g_utf16_to_utf8_and_fold (schema, -1, &schema_u8, &schema_folded)) { g_object_unref (user_choice); return; @@ -1065,11 +948,14 @@ get_url_association (const gunichar2 *schema) handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL; handler_rec->proxy_command = proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL); + _g_win32_extract_executable (proxy_command ? proxy_command : program_command, + &handler_rec->executable, + &handler_rec->executable_basename, + &handler_rec->executable_folded, + NULL, + &handler_rec->dll_function); + if (handler_rec->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); read_handler_icon (proxy_key, program_key, &handler_rec->icon); g_hash_table_insert (handlers, g_strdup (program_id_folded), @@ -1147,7 +1033,7 @@ get_file_ext (const gunichar2 *ext) if (user_choice == NULL && open_with_progids == NULL) return; - if (!utf8_and_fold (ext, &ext_u8, &ext_folded)) + if (!g_utf16_to_utf8_and_fold (ext, -1, &ext_u8, &ext_folded)) { g_clear_object (&user_choice); g_clear_object (&open_with_progids); @@ -1207,11 +1093,14 @@ get_file_ext (const gunichar2 *ext) proxy_id ? g_wcsdup (proxy_id, -1) : NULL; handler_rec->proxy_command = proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL); + _g_win32_extract_executable (proxy_command ? proxy_command : program_command, + &handler_rec->executable, + &handler_rec->executable_basename, + &handler_rec->executable_folded, + NULL, + &handler_rec->dll_function); + if (handler_rec->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); read_handler_icon (proxy_key, program_key, &handler_rec->icon); @@ -1325,11 +1214,14 @@ get_file_ext (const gunichar2 *ext) proxy_id ? g_wcsdup (proxy_id, -1) : NULL; handler_rec->proxy_command = proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL); + _g_win32_extract_executable (proxy_command ? proxy_command : program_command, + &handler_rec->executable, + &handler_rec->executable_basename, + &handler_rec->executable_folded, + NULL, + &handler_rec->dll_function); + if (handler_rec->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); read_handler_icon (proxy_key, program_key, &handler_rec->icon); @@ -1637,6 +1529,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea gchar *app_executable_basename; gchar *app_executable_folded; gchar *app_executable_folded_basename; + gchar *app_dll_function; GWin32RegistryKey *associations; app_key_path = g_wcsdup (input_app_key_path, -1); @@ -1652,7 +1545,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea canonical_name += 1; - if (!utf8_and_fold (canonical_name, &canonical_name_u8, &canonical_name_folded)) + if (!g_utf16_to_utf8_and_fold (canonical_name, -1, &canonical_name_u8, &canonical_name_folded)) { g_free (app_key_path); return; @@ -1705,7 +1598,9 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea NULL, NULL); - if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) + if (success && + (vtype != G_WIN32_REGISTRY_VALUE_STR || + !g_utf16_validate (shell_open_command, -1))) { /* Must have a command */ g_clear_pointer (&shell_open_command, g_free); @@ -1717,11 +1612,14 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea return; } - extract_executable (shell_open_command, - &app_executable, - &app_executable_basename, - &app_executable_folded, - &app_executable_folded_basename); + _g_win32_extract_executable (shell_open_command, + &app_executable, + &app_executable_basename, + &app_executable_folded, + &app_executable_folded_basename, + &app_dll_function); + if (app_dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command); app = g_hash_table_lookup (apps_by_id, canonical_name_folded); @@ -1748,6 +1646,8 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea app->user_specific = user_specific; app->default_app = default_app; + app->dll_function = g_strdup (app_dll_function); + g_hash_table_insert (apps_by_id, g_strdup (canonical_name_folded), app); @@ -1966,11 +1866,14 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea proxy_id ? g_wcsdup (proxy_id, -1) : NULL; handler_rec->proxy_command = proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL); + _g_win32_extract_executable (proxy_command ? proxy_command : program_command, + &handler_rec->executable, + &handler_rec->executable_basename, + &handler_rec->executable_folded, + NULL, + &handler_rec->dll_function); + if (handler_rec->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); read_handler_icon (proxy_key, program_key, &handler_rec->icon); @@ -1984,9 +1887,10 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea g_clear_object (&proxy_key); } - if (utf8_and_fold (file_extension, - &file_extension_u8, - &file_extension_folded)) + if (g_utf16_to_utf8_and_fold (file_extension, + -1, + &file_extension_u8, + &file_extension_folded)) { ext = g_hash_table_lookup (extensions, file_extension_folded); @@ -2122,11 +2026,14 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea proxy_id ? g_wcsdup (proxy_id, -1) : NULL; handler_rec->proxy_command = proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL); + _g_win32_extract_executable (proxy_command ? proxy_command : program_command, + &handler_rec->executable, + &handler_rec->executable_basename, + &handler_rec->executable_folded, + NULL, + &handler_rec->dll_function); + if (handler_rec->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); read_handler_icon (proxy_key, program_key, &handler_rec->icon); @@ -2140,9 +2047,10 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea g_clear_object (&proxy_key); } - if (utf8_and_fold (url_schema, - &schema_u8, - &schema_folded)) + if (g_utf16_to_utf8_and_fold (url_schema, + -1, + &schema_u8, + &schema_folded)) { schema = g_hash_table_lookup (urls, schema_folded); @@ -2197,6 +2105,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea g_clear_pointer (&app_executable, g_free); g_clear_pointer (&app_executable_folded, g_free); + g_clear_pointer (&app_dll_function, g_free); g_clear_pointer (&fallback_friendly_name, g_free); g_clear_pointer (&description, g_free); g_clear_pointer (&icon_source, g_free); @@ -2243,8 +2152,6 @@ read_exeapps (void) { GWin32RegistryKey *applications_key; GWin32RegistrySubkeyIter app_iter; - gunichar2 *app_exe_basename; - gsize app_exe_basename_len; applications_key = g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL); @@ -2260,6 +2167,8 @@ read_exeapps (void) while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL)) { + gunichar2 *app_exe_basename; + gsize app_exe_basename_len; GWin32RegistryKey *incapable_app; gunichar2 *friendly_app_name; gboolean success; @@ -2280,7 +2189,8 @@ read_exeapps (void) if (!g_win32_registry_subkey_iter_get_name_w (&app_iter, &app_exe_basename, &app_exe_basename_len, - NULL)) + NULL) || + !g_utf16_validate (app_exe_basename, app_exe_basename_len)) continue; incapable_app = @@ -2291,11 +2201,12 @@ read_exeapps (void) if (incapable_app == NULL) continue; - extract_executable (app_exe_basename, - &appexe, - &appexe_basename, - &appexe_folded, - &appexe_folded_basename); + _g_win32_extract_executable (app_exe_basename, + &appexe, + &appexe_basename, + &appexe_folded, + &appexe_folded_basename, + NULL); shell_open_command_key = g_win32_registry_key_get_child_w (incapable_app, @@ -2314,7 +2225,9 @@ read_exeapps (void) NULL, NULL); - if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) + if (success && + (vtype != G_WIN32_REGISTRY_VALUE_STR || + !g_utf16_validate (shell_open_command, -1))) { g_clear_pointer (&shell_open_command, g_free); } @@ -2385,6 +2298,21 @@ read_exeapps (void) { app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL); + if (shell_open_command) + { + gchar *dll_function; + + _g_win32_extract_executable (shell_open_command, + NULL, + NULL, + NULL, + NULL, + &dll_function); + if (dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command); + g_clear_pointer (&dll_function, g_free); + } + app->command = shell_open_command ? g_wcsdup (shell_open_command, -1) : NULL; @@ -2442,9 +2370,10 @@ read_exeapps (void) NULL)) || (ext_name_len <= 0) || (ext_name[0] != L'.') || - (!utf8_and_fold (ext_name, - &ext_u8, - &ext_folded))) + (!g_utf16_to_utf8_and_fold (ext_name, + -1, + &ext_u8, + &ext_folded))) continue; file_extn = NULL; @@ -2590,11 +2519,14 @@ read_class_extension (GWin32RegistryKey *classes_root, handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL; handler_rec->proxy_command = proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL); + _g_win32_extract_executable (proxy_command ? proxy_command : program_command, + &handler_rec->executable, + &handler_rec->executable_basename, + &handler_rec->executable_folded, + NULL, + &handler_rec->dll_function); + if (handler_rec->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); read_handler_icon (proxy_key, program_key, &handler_rec->icon); g_hash_table_insert (handlers, g_strdup (ext_folded), @@ -2708,11 +2640,14 @@ read_class_url (GWin32RegistryKey *classes_root, handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL; handler_rec->proxy_command = proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL); + _g_win32_extract_executable (proxy_command ? proxy_command : program_command, + &handler_rec->executable, + &handler_rec->executable_basename, + &handler_rec->executable_folded, + NULL, + &handler_rec->dll_function); + if (handler_rec->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); read_handler_icon (proxy_key, program_key, &handler_rec->icon); g_hash_table_insert (handlers, g_strdup (program_id_folded), @@ -2832,9 +2767,10 @@ link_chosen_handlers (void) if (handler->proxy_command && handler->proxy_id && - utf8_and_fold (handler->proxy_id, - NULL, - &proxy_id_folded)) + g_utf16_to_utf8_and_fold (handler->proxy_id, + -1, + NULL, + &proxy_id_folded)) { GWin32AppInfoHandler *proxy; @@ -2890,9 +2826,10 @@ link_chosen_handlers (void) if (handler->proxy_command && handler->proxy_id && - utf8_and_fold (handler->proxy_id, - NULL, - &proxy_id_folded)) + g_utf16_to_utf8_and_fold (handler->proxy_id, + -1, + NULL, + &proxy_id_folded)) { GWin32AppInfoHandler *proxy; @@ -3134,11 +3071,6 @@ link_handlers_to_unregistered_apps (void) (handler->executable_folded == NULL)) continue; - hndexe_fc_basename = g_utf8_casefold (handler->executable_basename, -1); - - if (hndexe_fc_basename == NULL) - continue; - g_hash_table_iter_init (&app_iter, apps_by_id); while (g_hash_table_iter_next (&app_iter, @@ -3159,6 +3091,11 @@ link_handlers_to_unregistered_apps (void) if (handler->app != NULL) continue; + hndexe_fc_basename = g_utf8_casefold (handler->executable_basename, -1); + + if (hndexe_fc_basename == NULL) + continue; + g_hash_table_iter_init (&app_iter, apps_by_exe); while ((hndexe_fc_basename != NULL) && @@ -4379,13 +4316,20 @@ g_app_info_create_from_commandline (const char *commandline, { GWin32AppInfo *info; GWin32AppInfoApplication *app; + gunichar2 *app_command; g_return_val_if_fail (commandline, NULL); - info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL); + app_command = g_utf8_to_utf16 (commandline, -1, NULL, NULL, NULL); + if (app_command == NULL) + return NULL; + + info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL); app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL); + app->command = g_steal_pointer (&app_command); + if (application_name) { app->canonical_name = g_utf8_to_utf16 (application_name, @@ -4397,14 +4341,16 @@ g_app_info_create_from_commandline (const char *commandline, app->canonical_name_folded = g_utf8_casefold (application_name, -1); } - app->command = g_utf8_to_utf16 (commandline, -1, NULL, NULL, NULL); - app->command_u8 = g_strdup (commandline); + _g_win32_extract_executable (app->command, + &app->executable, + &app->executable_basename, + &app->executable_folded, + NULL, + &app->dll_function); + if (app->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (app->command); - extract_executable (app->command, - &app->executable, - &app->executable_basename, - &app->executable_folded, - NULL); + app->command_u8 = g_utf16_to_utf8 (app->command, -1, NULL, NULL, NULL); app->no_open_with = FALSE; app->user_specific = FALSE; diff --git a/gio/gwin32registrykey.c b/gio/gwin32registrykey.c index c19fede..aa78192 100644 --- a/gio/gwin32registrykey.c +++ b/gio/gwin32registrykey.c @@ -2334,8 +2334,6 @@ g_win32_registry_key_set_property (GObject *object, switch (prop_id) { case PROP_PATH: - g_assert (priv->absolute_path_w == NULL); - g_assert (priv->absolute_path == NULL); path = g_value_get_string (value); if (path == NULL) @@ -2346,20 +2344,21 @@ g_win32_registry_key_set_property (GObject *object, if (path_w == NULL) break; - g_free (priv->absolute_path_w); - g_free (priv->absolute_path); + /* Construct only */ + g_assert (priv->absolute_path_w == NULL); + g_assert (priv->absolute_path == NULL); priv->absolute_path_w = path_w; priv->absolute_path = g_value_dup_string (value); break; case PROP_PATH_UTF16: - g_assert (priv->absolute_path_w == NULL); - g_assert (priv->absolute_path == NULL); path_w = (gunichar2 *) g_value_get_pointer (value); if (path_w == NULL) break; + /* Construct only */ + g_assert (priv->absolute_path_w == NULL); priv->absolute_path_w = g_wcsdup (path_w, -1); break; diff --git a/gio/inotify/inotify-helper.c b/gio/inotify/inotify-helper.c index d944587..c2b98b1 100644 --- a/gio/inotify/inotify-helper.c +++ b/gio/inotify/inotify-helper.c @@ -193,7 +193,7 @@ ih_event_callback (ik_event_t *event, * properly. If not, the assumption we have made about event->mask * only ever having a single bit set (apart from IN_ISDIR) is false. * The kernel documentation is lacking here. */ - g_assert (event_flags != -1); + g_assert ((int) event_flags != -1); interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags, event->name, NULL, other, event->timestamp); @@ -201,7 +201,7 @@ ih_event_callback (ik_event_t *event, g_object_unref (other); } } - else if (event_flags != -1) + else if ((int) event_flags != -1) /* unpaired event -- no 'other' field */ interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags, event->name, NULL, NULL, event->timestamp); diff --git a/gio/kqueue/gkqueuefilemonitor.c b/gio/kqueue/gkqueuefilemonitor.c index fd0db4e..c6e4ec4 100644 --- a/gio/kqueue/gkqueuefilemonitor.c +++ b/gio/kqueue/gkqueuefilemonitor.c @@ -122,7 +122,7 @@ static gboolean g_kqueue_file_monitor_is_supported (void); static kqueue_sub *_kqsub_new (gchar *, gchar *, GKqueueFileMonitor *, GFileMonitorSource *); static void _kqsub_free (kqueue_sub *); -static gboolean _kqsub_cancel (kqueue_sub *); +static void _kqsub_cancel (kqueue_sub *); #ifndef O_EVTONLY @@ -547,7 +547,7 @@ _kqsub_free (kqueue_sub *sub) g_slice_free (kqueue_sub, sub); } -static gboolean +static void _kqsub_cancel (kqueue_sub *sub) { /* WARNING: Before calling this function, you must hold a lock on kq_lock @@ -563,7 +563,6 @@ _kqsub_cancel (kqueue_sub *sub) if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1) { g_warning ("Unable to remove event for %s: %s", sub->filename, g_strerror (errno)); - return FALSE; } close (sub->fd); sub->fd = -1; @@ -576,8 +575,6 @@ _kqsub_cancel (kqueue_sub *sub) dl_free (sub->deps); sub->deps = NULL; } - - return TRUE; } gboolean diff --git a/gio/meson.build b/gio/meson.build index 173000f..2ef60ed 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -247,16 +247,6 @@ xdp_dbus_generated = custom_target('xdp-dbus', '--output-directory', '@OUTDIR@', '--generate-c-code', 'xdp-dbus', '--c-namespace', 'GXdp', - '--annotate', 'org.freedesktop.portal.Documents.Add()', - 'org.gtk.GDBus.C.UnixFD', 'true', - '--annotate', 'org.freedesktop.portal.Documents.AddNamed()', - 'org.gtk.GDBus.C.UnixFD', 'true', - '--annotate', 'org.freedesktop.portal.Documents.AddFull()', - 'org.gtk.GDBus.C.UnixFD', 'true', - '--annotate', 'org.freedesktop.portal.OpenURI.OpenFile()', - 'org.gtk.GDBus.C.UnixFD', 'true', - '--annotate', 'org.freedesktop.portal.Trash.TrashFile()', - 'org.gtk.GDBus.C.UnixFD', 'true', '@INPUT@']) # Generate gdbus-generated.{c,h} @@ -393,6 +383,7 @@ if host_system != 'windows' portal_sources = [files( 'gdocumentportal.c', 'gopenuriportal.c', + 'gmemorymonitorportal.c', 'gnetworkmonitorportal.c', 'gproxyresolverportal.c', 'gtrashportal.c', @@ -425,12 +416,6 @@ if host_system != 'windows' contenttype_sources += files('gcontenttype.c') appinfo_sources += files('gdesktopappinfo.c') gio_unix_include_headers += files('gdesktopappinfo.h') - - executable('gio-launch-desktop', 'gio-launch-desktop.c', - install : true, - c_args : gio_c_args, - # intl.lib is not compatible with SAFESEH - link_args : noseh_link_args) endif subdir('xdgmime') @@ -528,6 +513,8 @@ gio_sources = files( 'gloadableicon.c', 'gmarshal-internal.c', 'gmount.c', + 'gmemorymonitor.c', + 'gmemorymonitordbus.c', 'gmemoryinputstream.c', 'gmemoryoutputstream.c', 'gmountoperation.c', @@ -670,6 +657,7 @@ gio_headers = files( 'gloadableicon.h', 'gmount.h', 'gmemoryinputstream.h', + 'gmemorymonitor.h', 'gmemoryoutputstream.h', 'gmountoperation.h', 'gnativesocketaddress.h', @@ -817,9 +805,10 @@ libgio = library('gio-2.0', link_args : [noseh_link_args, glib_link_flags], ) -giomodulesdir = get_option('gio_module_dir') -if giomodulesdir == '' - giomodulesdir = join_paths('${libdir}', 'gio', 'modules') +if get_option('gio_module_dir') != '' + pkgconfig_giomodulesdir = join_paths('${prefix}', get_option('gio_module_dir')) +else + pkgconfig_giomodulesdir = join_paths('${libdir}', 'gio', 'modules') endif schemas_subdir = join_paths('glib-2.0', 'schemas') @@ -830,7 +819,7 @@ pkg.generate(libgio, variables : ['datadir=' + join_paths('${prefix}', get_option('datadir')), 'schemasdir=' + join_paths('${datadir}', schemas_subdir), 'bindir=' + join_paths('${prefix}', get_option('bindir')), - 'giomoduledir=' + giomodulesdir, + 'giomoduledir=' + pkgconfig_giomodulesdir, 'gio=' + join_paths('${bindir}', 'gio'), 'gio_querymodules=' + join_paths('${bindir}', 'gio-querymodules'), 'glib_compile_schemas=' + join_paths('${bindir}', 'glib-compile-schemas'), diff --git a/gio/org.freedesktop.portal.Documents.xml b/gio/org.freedesktop.portal.Documents.xml index d1cb739..da28630 100644 --- a/gio/org.freedesktop.portal.Documents.xml +++ b/gio/org.freedesktop.portal.Documents.xml @@ -8,7 +8,7 @@ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -45,6 +45,12 @@ The permissions that the application has for a document store entry (see org.freedesktop.portal.Documents.GrantPermissions()) are reflected in the POSIX mode bits in the fuse filesystem. + + The D-Bus interface for the document portal is available under the + bus name org.freedesktop.portal.Documents and the object path + /org/freedesktop/portal/documents. + + This documentation describes version 3 of this interface. --> @@ -72,6 +78,7 @@ access to the file. --> + @@ -89,6 +96,7 @@ Creates an entry in the document store for writing a new file. --> + @@ -99,7 +107,7 @@ + @@ -128,6 +142,43 @@ + + + + + + + + + + + + diff --git a/gio/org.freedesktop.portal.OpenURI.xml b/gio/org.freedesktop.portal.OpenURI.xml index 0cc79d1..5ed054c 100644 --- a/gio/org.freedesktop.portal.OpenURI.xml +++ b/gio/org.freedesktop.portal.OpenURI.xml @@ -5,7 +5,7 @@ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -26,26 +26,33 @@ The OpenURI portal allows sandboxed applications to open URIs (e.g. a http: link to the applications homepage) under the control of the user. + + This documentation describes version 3 of this interface. --> @@ -67,19 +83,24 @@ + + + + + + + + + + diff --git a/gio/org.freedesktop.portal.ProxyResolver.xml b/gio/org.freedesktop.portal.ProxyResolver.xml index c6e9ce9..4b39fc0 100644 --- a/gio/org.freedesktop.portal.ProxyResolver.xml +++ b/gio/org.freedesktop.portal.ProxyResolver.xml @@ -5,23 +5,45 @@ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public License - along with this library; if not, see . + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . Author: Matthias Clasen --> - + + + + diff --git a/gio/tests/actions.c b/gio/tests/actions.c index 3d438fc..c183c7a 100644 --- a/gio/tests/actions.c +++ b/gio/tests/actions.c @@ -890,7 +890,7 @@ do_export (gpointer data) bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); path = g_strdup_printf("/%p", data); - for (i = 0; i < 100000; i++) + for (i = 0; i < 10000; i++) { id = g_dbus_connection_export_action_group (bus, path, G_ACTION_GROUP (group), &error); g_assert_no_error (error); diff --git a/gio/tests/cancellable.c b/gio/tests/cancellable.c index 0446282..cd349a8 100644 --- a/gio/tests/cancellable.c +++ b/gio/tests/cancellable.c @@ -188,6 +188,12 @@ test_cancel_multiple_concurrent (void) GCancellable *cancellable; guint i, iterations; + if (!g_test_thorough ()) + { + g_test_skip ("Not running timing heavy test"); + return; + } + cancellable = g_cancellable_new (); loop = g_main_loop_new (NULL, FALSE); diff --git a/gio/tests/codegen.py b/gio/tests/codegen.py new file mode 100644 index 0000000..51de0ed --- /dev/null +++ b/gio/tests/codegen.py @@ -0,0 +1,540 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# +# Copyright © 2018, 2019 Endless Mobile, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +"""Integration tests for gdbus-codegen utility.""" + +import collections +import os +import shutil +import subprocess +import sys +import tempfile +import textwrap +import unittest + +import taptestrunner + + +Result = collections.namedtuple('Result', ('info', 'out', 'err', 'subs')) + + +class TestCodegen(unittest.TestCase): + """Integration test for running gdbus-codegen. + + This can be run when installed or uninstalled. When uninstalled, it + requires G_TEST_BUILDDIR and G_TEST_SRCDIR to be set. + + The idea with this test harness is to test the gdbus-codegen utility, its + handling of command line arguments, its exit statuses, and its handling of + various C source codes. In future we could split out tests for the core + parsing and generation code of gdbus-codegen into separate unit tests, and + just test command line behaviour in this integration test. + """ + # Track the cwd, we want to back out to that to clean up our tempdir + cwd = '' + + def setUp(self): + self.timeout_seconds = 10 # seconds per test + self.tmpdir = tempfile.TemporaryDirectory() + self.cwd = os.getcwd() + os.chdir(self.tmpdir.name) + print('tmpdir:', self.tmpdir.name) + if 'G_TEST_BUILDDIR' in os.environ: + self.__codegen = \ + os.path.join(os.environ['G_TEST_BUILDDIR'], '..', + 'gdbus-2.0', 'codegen', 'gdbus-codegen') + else: + self.__codegen = shutil.which('gdbus-codegen') + print('codegen:', self.__codegen) + + def tearDown(self): + os.chdir(self.cwd) + self.tmpdir.cleanup() + + def runCodegen(self, *args): + argv = [self.__codegen] + + # shebang lines are not supported on native + # Windows consoles + if os.name == 'nt': + argv.insert(0, sys.executable) + + argv.extend(args) + print('Running:', argv) + + env = os.environ.copy() + env['LC_ALL'] = 'C.UTF-8' + print('Environment:', env) + + # We want to ensure consistent line endings... + info = subprocess.run(argv, timeout=self.timeout_seconds, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, + universal_newlines=True) + info.check_returncode() + out = info.stdout.strip() + err = info.stderr.strip() + + # Known substitutions for standard boilerplate + subs = { + 'standard_top_comment': + '/*\n' + ' * This file is generated by gdbus-codegen, do not modify it.\n' + ' *\n' + ' * The license of this code is the same as for the D-Bus interface description\n' + ' * it was derived from. Note that it links to GLib, so must comply with the\n' + ' * LGPL linking clauses.\n' + ' */', + 'standard_config_h_include': + '#ifdef HAVE_CONFIG_H\n' + '# include "config.h"\n' + '#endif', + 'standard_header_includes': + '#include \n' + '#ifdef G_OS_UNIX\n' + '# include \n' + '#endif', + 'standard_typedefs_and_helpers': + 'typedef struct\n' + '{\n' + ' GDBusArgInfo parent_struct;\n' + ' gboolean use_gvariant;\n' + '} _ExtendedGDBusArgInfo;\n' + '\n' + 'typedef struct\n' + '{\n' + ' GDBusMethodInfo parent_struct;\n' + ' const gchar *signal_name;\n' + ' gboolean pass_fdlist;\n' + '} _ExtendedGDBusMethodInfo;\n' + '\n' + 'typedef struct\n' + '{\n' + ' GDBusSignalInfo parent_struct;\n' + ' const gchar *signal_name;\n' + '} _ExtendedGDBusSignalInfo;\n' + '\n' + 'typedef struct\n' + '{\n' + ' GDBusPropertyInfo parent_struct;\n' + ' const gchar *hyphen_name;\n' + ' guint use_gvariant : 1;\n' + ' guint emits_changed_signal : 1;\n' + '} _ExtendedGDBusPropertyInfo;\n' + '\n' + 'typedef struct\n' + '{\n' + ' GDBusInterfaceInfo parent_struct;\n' + ' const gchar *hyphen_name;\n' + '} _ExtendedGDBusInterfaceInfo;\n' + '\n' + 'typedef struct\n' + '{\n' + ' const _ExtendedGDBusPropertyInfo *info;\n' + ' guint prop_id;\n' + ' GValue orig_value; /* the value before the change */\n' + '} ChangedProperty;\n' + '\n' + 'static void\n' + '_changed_property_free (ChangedProperty *data)\n' + '{\n' + ' g_value_unset (&data->orig_value);\n' + ' g_free (data);\n' + '}\n' + '\n' + 'static gboolean\n' + '_g_strv_equal0 (gchar **a, gchar **b)\n' + '{\n' + ' gboolean ret = FALSE;\n' + ' guint n;\n' + ' if (a == NULL && b == NULL)\n' + ' {\n' + ' ret = TRUE;\n' + ' goto out;\n' + ' }\n' + ' if (a == NULL || b == NULL)\n' + ' goto out;\n' + ' if (g_strv_length (a) != g_strv_length (b))\n' + ' goto out;\n' + ' for (n = 0; a[n] != NULL; n++)\n' + ' if (g_strcmp0 (a[n], b[n]) != 0)\n' + ' goto out;\n' + ' ret = TRUE;\n' + 'out:\n' + ' return ret;\n' + '}\n' + '\n' + 'static gboolean\n' + '_g_variant_equal0 (GVariant *a, GVariant *b)\n' + '{\n' + ' gboolean ret = FALSE;\n' + ' if (a == NULL && b == NULL)\n' + ' {\n' + ' ret = TRUE;\n' + ' goto out;\n' + ' }\n' + ' if (a == NULL || b == NULL)\n' + ' goto out;\n' + ' ret = g_variant_equal (a, b);\n' + 'out:\n' + ' return ret;\n' + '}\n' + '\n' + 'G_GNUC_UNUSED static gboolean\n' + '_g_value_equal (const GValue *a, const GValue *b)\n' + '{\n' + ' gboolean ret = FALSE;\n' + ' g_assert (G_VALUE_TYPE (a) == G_VALUE_TYPE (b));\n' + ' switch (G_VALUE_TYPE (a))\n' + ' {\n' + ' case G_TYPE_BOOLEAN:\n' + ' ret = (g_value_get_boolean (a) == g_value_get_boolean (b));\n' + ' break;\n' + ' case G_TYPE_UCHAR:\n' + ' ret = (g_value_get_uchar (a) == g_value_get_uchar (b));\n' + ' break;\n' + ' case G_TYPE_INT:\n' + ' ret = (g_value_get_int (a) == g_value_get_int (b));\n' + ' break;\n' + ' case G_TYPE_UINT:\n' + ' ret = (g_value_get_uint (a) == g_value_get_uint (b));\n' + ' break;\n' + ' case G_TYPE_INT64:\n' + ' ret = (g_value_get_int64 (a) == g_value_get_int64 (b));\n' + ' break;\n' + ' case G_TYPE_UINT64:\n' + ' ret = (g_value_get_uint64 (a) == g_value_get_uint64 (b));\n' + ' break;\n' + ' case G_TYPE_DOUBLE:\n' + ' {\n' + ' /* Avoid -Wfloat-equal warnings by doing a direct bit compare */\n' + ' gdouble da = g_value_get_double (a);\n' + ' gdouble db = g_value_get_double (b);\n' + ' ret = memcmp (&da, &db, sizeof (gdouble)) == 0;\n' + ' }\n' + ' break;\n' + ' case G_TYPE_STRING:\n' + ' ret = (g_strcmp0 (g_value_get_string (a), g_value_get_string (b)) == 0);\n' + ' break;\n' + ' case G_TYPE_VARIANT:\n' + ' ret = _g_variant_equal0 (g_value_get_variant (a), g_value_get_variant (b));\n' + ' break;\n' + ' default:\n' + ' if (G_VALUE_TYPE (a) == G_TYPE_STRV)\n' + ' ret = _g_strv_equal0 (g_value_get_boxed (a), g_value_get_boxed (b));\n' + ' else\n' + ' g_critical ("_g_value_equal() does not handle type %s", g_type_name (G_VALUE_TYPE (a)));\n' + ' break;\n' + ' }\n' + ' return ret;\n' + '}', + } + + result = Result(info, out, err, subs) + + print('Output:', result.out) + return result + + def runCodegenWithInterface(self, interface_contents, *args): + with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, + suffix='.xml', + delete=False) as interface_file: + # Write out the interface. + interface_file.write(interface_contents.encode('utf-8')) + print(interface_file.name + ':', interface_contents) + interface_file.flush() + + return self.runCodegen(interface_file.name, *args) + + def test_help(self): + """Test the --help argument.""" + result = self.runCodegen('--help') + self.assertIn('usage: gdbus-codegen', result.out) + + def test_no_args(self): + """Test running with no arguments at all.""" + with self.assertRaises(subprocess.CalledProcessError): + self.runCodegen() + + def test_empty_interface_header(self): + """Test generating a header with an empty interface file.""" + result = self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--header') + self.assertEqual('', result.err) + self.assertEqual('''{standard_top_comment} + +#ifndef __STDOUT__ +#define __STDOUT__ + +#include + +G_BEGIN_DECLS + + +G_END_DECLS + +#endif /* __STDOUT__ */'''.format(**result.subs), + result.out.strip()) + + def test_empty_interface_body(self): + """Test generating a body with an empty interface file.""" + result = self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--body') + self.assertEqual('', result.err) + self.assertEqual('''{standard_top_comment} + +{standard_config_h_include} + +#include "stdout.h" + +{standard_header_includes} + +{standard_typedefs_and_helpers}'''.format(**result.subs), + result.out.strip()) + + def test_reproducible(self): + """Test builds are reproducible regardless of file ordering.""" + xml_contents1 = ''' + + + + + + + + + + ''' + + xml_contents2 = ''' + + + + + + ''' + + with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, + suffix='1.xml', delete=False) as xml_file1, \ + tempfile.NamedTemporaryFile(dir=self.tmpdir.name, + suffix='2.xml', delete=False) as xml_file2: + # Write out the interfaces. + xml_file1.write(xml_contents1.encode('utf-8')) + xml_file2.write(xml_contents2.encode('utf-8')) + + xml_file1.flush() + xml_file2.flush() + + # Repeat this for headers and bodies. + for header_or_body in ['--header', '--body']: + # Run gdbus-codegen with the interfaces in one order, and then + # again in another order. + result1 = self.runCodegen(xml_file1.name, xml_file2.name, + '--output', '/dev/stdout', + header_or_body) + self.assertEqual('', result1.err) + + result2 = self.runCodegen(xml_file2.name, xml_file1.name, + '--output', '/dev/stdout', + header_or_body) + self.assertEqual('', result2.err) + + # The output should be the same. + self.assertEqual(result1.out, result2.out) + + def test_glib_min_required_invalid(self): + """Test running with an invalid --glib-min-required.""" + with self.assertRaises(subprocess.CalledProcessError): + self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--body', + '--glib-min-required', 'hello mum') + + def test_glib_min_required_too_low(self): + """Test running with a --glib-min-required which is too low (and hence + probably a typo).""" + with self.assertRaises(subprocess.CalledProcessError): + self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--body', + '--glib-min-required', '2.6') + + def test_glib_min_required_major_only(self): + """Test running with a --glib-min-required which contains only a major version.""" + result = self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--header', + '--glib-min-required', '3', + '--glib-max-allowed', '3.2') + self.assertEqual('', result.err) + self.assertNotEqual('', result.out.strip()) + + def test_glib_min_required_with_micro(self): + """Test running with a --glib-min-required which contains a micro version.""" + result = self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--header', + '--glib-min-required', '2.46.2') + self.assertEqual('', result.err) + self.assertNotEqual('', result.out.strip()) + + def test_glib_max_allowed_too_low(self): + """Test running with a --glib-max-allowed which is too low (and hence + probably a typo).""" + with self.assertRaises(subprocess.CalledProcessError): + self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--body', + '--glib-max-allowed', '2.6') + + def test_glib_max_allowed_major_only(self): + """Test running with a --glib-max-allowed which contains only a major version.""" + result = self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--header', + '--glib-max-allowed', '3') + self.assertEqual('', result.err) + self.assertNotEqual('', result.out.strip()) + + def test_glib_max_allowed_with_micro(self): + """Test running with a --glib-max-allowed which contains a micro version.""" + result = self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--header', + '--glib-max-allowed', '2.46.2') + self.assertEqual('', result.err) + self.assertNotEqual('', result.out.strip()) + + def test_glib_max_allowed_unstable(self): + """Test running with a --glib-max-allowed which is unstable. It should + be rounded up to the next stable version number, and hence should not + end up less than --glib-min-required.""" + result = self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--header', + '--glib-max-allowed', '2.63', + '--glib-min-required', '2.64') + self.assertEqual('', result.err) + self.assertNotEqual('', result.out.strip()) + + def test_glib_max_allowed_less_than_min_required(self): + """Test running with a --glib-max-allowed which is less than + --glib-min-required.""" + with self.assertRaises(subprocess.CalledProcessError): + self.runCodegenWithInterface('', + '--output', '/dev/stdout', + '--body', + '--glib-max-allowed', '2.62', + '--glib-min-required', '2.64') + + def test_unix_fd_types_and_annotations(self): + """Test an interface with `h` arguments, no annotation, and GLib < 2.64. + + See issue #1726. + """ + interface_xml = ''' + + + + + + + + + + + + + + + + + + ''' + + # Try without specifying --glib-min-required. + result = self.runCodegenWithInterface(interface_xml, + '--output', '/dev/stdout', + '--header') + self.assertEqual('', result.err) + self.assertEqual(result.out.strip().count('GUnixFDList'), 6) + + # Specify an old --glib-min-required. + result = self.runCodegenWithInterface(interface_xml, + '--output', '/dev/stdout', + '--header', + '--glib-min-required', '2.32') + self.assertEqual('', result.err) + self.assertEqual(result.out.strip().count('GUnixFDList'), 6) + + # Specify a --glib-min-required ≥ 2.64. There should be more + # mentions of `GUnixFDList` now, since the annotation is not needed to + # trigger its use. + result = self.runCodegenWithInterface(interface_xml, + '--output', '/dev/stdout', + '--header', + '--glib-min-required', '2.64') + self.assertEqual('', result.err) + self.assertEqual(result.out.strip().count('GUnixFDList'), 18) + + def test_call_flags_and_timeout_method_args(self): + """Test that generated method call functions have @call_flags and + @timeout_msec args if and only if GLib >= 2.64. + """ + interface_xml = ''' + + + + + ''' + + # Try without specifying --glib-min-required. + result = self.runCodegenWithInterface(interface_xml, + '--output', '/dev/stdout', + '--header') + self.assertEqual('', result.err) + self.assertEqual(result.out.strip().count('GDBusCallFlags call_flags,'), 0) + self.assertEqual(result.out.strip().count('gint timeout_msec,'), 0) + + # Specify an old --glib-min-required. + result = self.runCodegenWithInterface(interface_xml, + '--output', '/dev/stdout', + '--header', + '--glib-min-required', '2.32') + self.assertEqual('', result.err) + self.assertEqual(result.out.strip().count('GDBusCallFlags call_flags,'), 0) + self.assertEqual(result.out.strip().count('gint timeout_msec,'), 0) + + # Specify a --glib-min-required ≥ 2.64. The two arguments should be + # present for both the async and sync method call functions. + result = self.runCodegenWithInterface(interface_xml, + '--output', '/dev/stdout', + '--header', + '--glib-min-required', '2.64') + self.assertEqual('', result.err) + self.assertEqual(result.out.strip().count('GDBusCallFlags call_flags,'), 2) + self.assertEqual(result.out.strip().count('gint timeout_msec,'), 2) + + +if __name__ == '__main__': + unittest.main(testRunner=taptestrunner.TAPTestRunner()) diff --git a/gio/tests/dbus-appinfo.c b/gio/tests/dbus-appinfo.c index 8961a54..7e2fc4d 100644 --- a/gio/tests/dbus-appinfo.c +++ b/gio/tests/dbus-appinfo.c @@ -276,12 +276,95 @@ test_dbus_appinfo (void) g_object_unref (app); } +static void +on_flatpak_launch_uris_finish (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GApplication *app = user_data; + GError *error = NULL; + + g_app_info_launch_uris_finish (G_APP_INFO (object), result, &error); + g_assert_no_error (error); + + g_application_release (app); +} + +static void +on_flatpak_activate (GApplication *app, + gpointer user_data) +{ + GDesktopAppInfo *flatpak_appinfo = user_data; + char *uri; + GList *uris; + + /* The app will be released in on_flatpak_launch_uris_finish */ + g_application_hold (app); + + uri = g_filename_to_uri (g_desktop_app_info_get_filename (flatpak_appinfo), NULL, NULL); + g_assert_nonnull (uri); + uris = g_list_prepend (NULL, uri); + g_app_info_launch_uris_async (G_APP_INFO (flatpak_appinfo), uris, NULL, + NULL, on_flatpak_launch_uris_finish, app); + g_list_free (uris); + g_free (uri); +} + +static void +on_flatpak_open (GApplication *app, + GFile **files, + gint n_files, + const char *hint) +{ + GFile *f; + + g_assert_cmpint (n_files, ==, 1); + g_test_message ("on_flatpak_open received file '%s'", g_file_peek_path (files[0])); + + /* The file has been exported via the document portal */ + f = g_file_new_for_uri ("file:///document-portal/document-id/org.gtk.test.dbusappinfo.flatpak.desktop"); + g_assert_true (g_file_equal (files[0], f)); + g_object_unref (f); +} + +static void +test_flatpak_doc_export (void) +{ + const gchar *argv[] = { "myapp", NULL }; + gchar *desktop_file = NULL; + GDesktopAppInfo *flatpak_appinfo; + GApplication *app; + int status; + + g_test_summary ("Test that files launched via Flatpak apps are made available via the document portal."); + + desktop_file = g_test_build_filename (G_TEST_DIST, + "org.gtk.test.dbusappinfo.flatpak.desktop", + NULL); + flatpak_appinfo = g_desktop_app_info_new_from_filename (desktop_file); + g_assert_nonnull (flatpak_appinfo); + g_free (desktop_file); + + app = g_application_new ("org.gtk.test.dbusappinfo.flatpak", + G_APPLICATION_HANDLES_OPEN); + g_signal_connect (app, "activate", G_CALLBACK (on_flatpak_activate), + flatpak_appinfo); + g_signal_connect (app, "open", G_CALLBACK (on_flatpak_open), NULL); + + status = g_application_run (app, 1, (gchar **) argv); + g_assert_cmpint (status, ==, 0); + + g_object_unref (app); + g_object_unref (flatpak_appinfo); +} + int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/appinfo/dbusappinfo", test_dbus_appinfo); + g_test_add_func ("/appinfo/flatpak-doc-export", test_flatpak_doc_export); return session_bus_run (); } diff --git a/gio/tests/defaultvalue.c b/gio/tests/defaultvalue.c index 84a8635..de0e9b1 100644 --- a/gio/tests/defaultvalue.c +++ b/gio/tests/defaultvalue.c @@ -29,7 +29,6 @@ check_property (const char *output, if (g_param_value_defaults (pspec, value)) return; - g_value_init (&default_value, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_param_value_set_default (pspec, &default_value); v = g_strdup_value_contents (value); diff --git a/gio/tests/fake-document-portal.c b/gio/tests/fake-document-portal.c new file mode 100644 index 0000000..c9bb795 --- /dev/null +++ b/gio/tests/fake-document-portal.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2019 Canonical Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Authors: James Henstridge + */ + +/* A stub implementation of xdg-document-portal covering enough to + * support g_document_portal_add_documents */ + +#include +#include +#include + +#include "fake-document-portal-generated.h" + +static gboolean +on_handle_get_mount_point (FakeDocuments *object, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + fake_documents_complete_get_mount_point (object, + invocation, + "/document-portal"); + return TRUE; +} + +static gboolean +on_handle_add_full (FakeDocuments *object, + GDBusMethodInvocation *invocation, + GUnixFDList *o_path_fds, + guint flags, + const gchar *app_id, + const gchar * const *permissions, + gpointer user_data) +{ + const gchar **doc_ids = NULL; + GVariant *extra_out = NULL; + gsize length, i; + + if (o_path_fds != NULL) + length = g_unix_fd_list_get_length (o_path_fds); + else + length = 0; + + doc_ids = g_new0 (const gchar *, length + 1 /* NULL terminator */); + for (i = 0; i < length; i++) + { + doc_ids[i] = "document-id"; + } + extra_out = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0); + + fake_documents_complete_add_full (object, + invocation, + NULL, + doc_ids, + extra_out); + + g_free (doc_ids); + + return TRUE; +} + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + FakeDocuments *interface; + GError *error = NULL; + + g_test_message ("Acquired a message bus connection"); + + interface = fake_documents_skeleton_new (); + g_signal_connect (interface, + "handle-get-mount-point", + G_CALLBACK (on_handle_get_mount_point), + NULL); + g_signal_connect (interface, + "handle-add-full", + G_CALLBACK (on_handle_add_full), + NULL); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (interface), + connection, + "/org/freedesktop/portal/documents", + &error); + g_assert_no_error (error); +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_test_message ("Acquired the name %s", name); +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_test_message ("Lost the name %s", name); +} + + +gint +main (gint argc, gchar *argv[]) +{ + GMainLoop *loop; + guint id; + + loop = g_main_loop_new (NULL, FALSE); + + id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.freedesktop.portal.Documents", + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + loop, + NULL); + + g_main_loop_run (loop); + + g_bus_unown_name (id); + g_main_loop_unref (loop); + + return 0; +} diff --git a/gio/tests/file.c b/gio/tests/file.c index efb2eaa..8d3aafa 100644 --- a/gio/tests/file.c +++ b/gio/tests/file.c @@ -846,58 +846,103 @@ test_async_delete (void) g_object_unref (file); } -#ifdef G_OS_UNIX static void test_copy_preserve_mode (void) { - GFile *tmpfile; - GFile *dest_tmpfile; - GFileInfo *dest_info; - GFileIOStream *iostream; - GError *local_error = NULL; - GError **error = &local_error; - guint32 romode = S_IFREG | 0600; - guint32 dest_mode; - - tmpfile = g_file_new_tmp ("tmp-copy-preserve-modeXXXXXX", - &iostream, error); - g_assert_no_error (local_error); - g_io_stream_close ((GIOStream*)iostream, NULL, error); - g_assert_no_error (local_error); - g_clear_object (&iostream); - - g_file_set_attribute (tmpfile, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_ATTRIBUTE_TYPE_UINT32, - &romode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL, error); - g_assert_no_error (local_error); - - dest_tmpfile = g_file_new_tmp ("tmp-copy-preserve-modeXXXXXX", - &iostream, error); - g_assert_no_error (local_error); - g_io_stream_close ((GIOStream*)iostream, NULL, error); - g_assert_no_error (local_error); - g_clear_object (&iostream); - - g_file_copy (tmpfile, dest_tmpfile, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA, - NULL, NULL, NULL, error); - g_assert_no_error (local_error); - - dest_info = g_file_query_info (dest_tmpfile, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL, error); - g_assert_no_error (local_error); - - dest_mode = g_file_info_get_attribute_uint32 (dest_info, G_FILE_ATTRIBUTE_UNIX_MODE); - - g_assert_cmpint (dest_mode, ==, romode); - - (void) g_file_delete (tmpfile, NULL, NULL); - (void) g_file_delete (dest_tmpfile, NULL, NULL); - - g_clear_object (&tmpfile); - g_clear_object (&dest_tmpfile); - g_clear_object (&dest_info); -} +#ifdef G_OS_UNIX + mode_t current_umask = umask (0); + const struct + { + guint32 source_mode; + guint32 expected_destination_mode; + gboolean create_destination_before_copy; + GFileCopyFlags copy_flags; + } + vectors[] = + { + /* Overwriting the destination file should copy the permissions from the + * source file, even if %G_FILE_COPY_ALL_METADATA is set: */ + { 0600, 0600, TRUE, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA }, + { 0600, 0600, TRUE, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS }, + /* The same behaviour should hold if the destination file is not being + * overwritten because it doesn’t already exist: */ + { 0600, 0600, FALSE, G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA }, + { 0600, 0600, FALSE, G_FILE_COPY_NOFOLLOW_SYMLINKS }, + /* Anything with %G_FILE_COPY_TARGET_DEFAULT_PERMS should use the current + * umask for the destination file: */ + { 0600, 0666 & ~current_umask, TRUE, G_FILE_COPY_TARGET_DEFAULT_PERMS | G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA }, + { 0600, 0666 & ~current_umask, TRUE, G_FILE_COPY_TARGET_DEFAULT_PERMS | G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS }, + { 0600, 0666 & ~current_umask, FALSE, G_FILE_COPY_TARGET_DEFAULT_PERMS | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA }, + { 0600, 0666 & ~current_umask, FALSE, G_FILE_COPY_TARGET_DEFAULT_PERMS | G_FILE_COPY_NOFOLLOW_SYMLINKS }, + }; + gsize i; + + /* Reset the umask after querying it above. There’s no way to query it without + * changing it. */ + umask (current_umask); + g_test_message ("Current umask: %u", current_umask); + + for (i = 0; i < G_N_ELEMENTS (vectors); i++) + { + GFile *tmpfile; + GFile *dest_tmpfile; + GFileInfo *dest_info; + GFileIOStream *iostream; + GError *local_error = NULL; + guint32 romode = vectors[i].source_mode; + guint32 dest_mode; + + g_test_message ("Vector %" G_GSIZE_FORMAT, i); + + tmpfile = g_file_new_tmp ("tmp-copy-preserve-modeXXXXXX", + &iostream, &local_error); + g_assert_no_error (local_error); + g_io_stream_close ((GIOStream*)iostream, NULL, &local_error); + g_assert_no_error (local_error); + g_clear_object (&iostream); + + g_file_set_attribute (tmpfile, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_ATTRIBUTE_TYPE_UINT32, + &romode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, &local_error); + g_assert_no_error (local_error); + + dest_tmpfile = g_file_new_tmp ("tmp-copy-preserve-modeXXXXXX", + &iostream, &local_error); + g_assert_no_error (local_error); + g_io_stream_close ((GIOStream*)iostream, NULL, &local_error); + g_assert_no_error (local_error); + g_clear_object (&iostream); + + if (!vectors[i].create_destination_before_copy) + { + g_file_delete (dest_tmpfile, NULL, &local_error); + g_assert_no_error (local_error); + } + + g_file_copy (tmpfile, dest_tmpfile, vectors[i].copy_flags, + NULL, NULL, NULL, &local_error); + g_assert_no_error (local_error); + + dest_info = g_file_query_info (dest_tmpfile, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, &local_error); + g_assert_no_error (local_error); + + dest_mode = g_file_info_get_attribute_uint32 (dest_info, G_FILE_ATTRIBUTE_UNIX_MODE); + + g_assert_cmpint (dest_mode & ~S_IFMT, ==, vectors[i].expected_destination_mode); + g_assert_cmpint (dest_mode & S_IFMT, ==, S_IFREG); + + (void) g_file_delete (tmpfile, NULL, NULL); + (void) g_file_delete (dest_tmpfile, NULL, NULL); + + g_clear_object (&tmpfile); + g_clear_object (&dest_tmpfile); + g_clear_object (&dest_info); + } +#else /* if !G_OS_UNIX */ + g_test_skip ("File permissions tests can only be run on Unix") #endif +} static gchar * splice_to_string (GInputStream *stream, @@ -1755,9 +1800,7 @@ main (int argc, char *argv[]) g_test_add_func ("/file/replace-load", test_replace_load); g_test_add_func ("/file/replace-cancel", test_replace_cancel); g_test_add_func ("/file/async-delete", test_async_delete); -#ifdef G_OS_UNIX g_test_add_func ("/file/copy-preserve-mode", test_copy_preserve_mode); -#endif g_test_add_func ("/file/measure", test_measure); g_test_add_func ("/file/measure-async", test_measure_async); g_test_add_func ("/file/load-bytes", test_load_bytes); diff --git a/gio/tests/g-file-info-filesystem-readonly.c b/gio/tests/g-file-info-filesystem-readonly.c index a715ef8..16fa83e 100644 --- a/gio/tests/g-file-info-filesystem-readonly.c +++ b/gio/tests/g-file-info-filesystem-readonly.c @@ -25,7 +25,7 @@ #include #include -static void +static gboolean run (GError **error, const gchar *argv0, ...) @@ -34,6 +34,8 @@ run (GError **error, const gchar *arg; va_list ap; GSubprocess *subprocess; + gchar *command_line = NULL; + gboolean success; args = g_ptr_array_new (); @@ -44,14 +46,20 @@ run (GError **error, g_ptr_array_add (args, NULL); va_end (ap); + command_line = g_strjoinv (" ", (gchar **) args->pdata); + g_test_message ("Running command `%s`", command_line); + g_free (command_line); + subprocess = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); if (subprocess == NULL) - return; + return FALSE; - g_subprocess_wait_check (subprocess, NULL, error); + success = g_subprocess_wait_check (subprocess, NULL, error); g_object_unref (subprocess); + + return success; } static void @@ -61,6 +69,24 @@ assert_remove (const gchar *file) g_error ("failed to remove %s: %s", file, g_strerror (errno)); } +static gboolean +fuse_module_loaded (void) +{ + char *contents = NULL; + gboolean ret; + + if (!g_file_get_contents ("/proc/modules", &contents, NULL, NULL) || + contents == NULL) + { + g_free (contents); + return FALSE; + } + + ret = (strstr (contents, "\nfuse ") != NULL); + g_free (contents); + return ret; +} + static void test_filesystem_readonly (gconstpointer with_mount_monitor) { @@ -87,6 +113,18 @@ test_filesystem_readonly (gconstpointer with_mount_monitor) return; } + /* If the fuse module is loaded but there's no /dev/fuse, then we're + * we're probably in a rootless container and won't be able to + * use bindfs to run our tests */ + if (fuse_module_loaded () && + !g_file_test ("/dev/fuse", G_FILE_TEST_EXISTS)) + { + g_test_skip ("fuse support is needed to run this test (rootless container?)"); + g_free (fusermount); + g_free (bindfs); + return; + } + curdir = g_get_current_dir (); dir_to_mount = g_strdup_printf ("%s/dir_bindfs_to_mount", curdir); file_in_mount = g_strdup_printf ("%s/example.txt", dir_to_mount); @@ -105,8 +143,14 @@ test_filesystem_readonly (gconstpointer with_mount_monitor) /* Use bindfs, which does not need root privileges, to mount the contents of one dir * into another dir (and do the mount as readonly as per passed '-o ro' option) */ - run (&error, bindfs, "-n", "-o", "ro", dir_to_mount, dir_mountpoint, NULL); - g_assert_no_error (error); + if (!run (&error, bindfs, "-n", "-o", "ro", dir_to_mount, dir_mountpoint, NULL)) + { + gchar *skip_message = g_strdup_printf ("Failed to run bindfs to set up test: %s", error->message); + g_test_skip (skip_message); + g_free (skip_message); + g_clear_error (&error); + return; + } /* Let's check now, that the file is in indeed in a readonly filesystem */ file_in_mountpoint = g_strdup_printf ("%s/example.txt", dir_mountpoint); diff --git a/gio/tests/gdbus-addresses.c b/gio/tests/gdbus-addresses.c index 173383d..26c21ee 100644 --- a/gio/tests/gdbus-addresses.c +++ b/gio/tests/gdbus-addresses.c @@ -146,6 +146,7 @@ test_nonce_tcp_address (void) assert_not_supported_address ("nonce-tcp:host=localhost,port=420000"); assert_not_supported_address ("nonce-tcp:host=localhost,port=42x"); assert_not_supported_address ("nonce-tcp:host=localhost,port="); + assert_not_supported_address ("nonce-tcp:host=localhost,port=42,noncefile="); } static void diff --git a/gio/tests/gdbus-connection-flush.c b/gio/tests/gdbus-connection-flush.c index 39f08e8..73a034b 100644 --- a/gio/tests/gdbus-connection-flush.c +++ b/gio/tests/gdbus-connection-flush.c @@ -368,6 +368,9 @@ main (int argc, { gint ret; + /* FIXME: Add debug for https://gitlab.gnome.org/GNOME/glib/issues/1929 */ + g_setenv ("G_DBUS_DEBUG", "authentication", TRUE); + g_test_init (&argc, &argv, NULL); g_test_add ("/gdbus/connection/flush/busy", Fixture, NULL, diff --git a/gio/tests/gdbus-connection-loss.c b/gio/tests/gdbus-connection-loss.c index 8f7023f..a34a992 100644 --- a/gio/tests/gdbus-connection-loss.c +++ b/gio/tests/gdbus-connection-loss.c @@ -124,14 +124,14 @@ main (int argc, g_assert (g_spawn_command_line_async (path, NULL)); g_free (path); - ensure_gdbus_testserver_up (); - /* Create the connection in the main thread */ error = NULL; c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); g_assert_no_error (error); g_assert (c != NULL); + ensure_gdbus_testserver_up (c, NULL); + g_test_add_func ("/gdbus/connection-loss", test_connection_loss); ret = g_test_run(); diff --git a/gio/tests/gdbus-connection.c b/gio/tests/gdbus-connection.c index 6e4c7eb..4690185 100644 --- a/gio/tests/gdbus-connection.c +++ b/gio/tests/gdbus-connection.c @@ -1256,6 +1256,10 @@ main (int argc, char *argv[]) { int ret; + + /* FIXME: Add debug for https://gitlab.gnome.org/GNOME/glib/issues/1957 */ + g_setenv ("G_DBUS_DEBUG", "all", TRUE); + g_test_init (&argc, &argv, NULL); /* all the tests rely on a shared main loop */ diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c index 506c745..fda654c 100644 --- a/gio/tests/gdbus-export.c +++ b/gio/tests/gdbus-export.c @@ -189,7 +189,7 @@ foo_set_property (GDBusConnection *connection, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FILE_INVALID, "Returning some error instead of writing the value '%s' to the property '%s'", - property_name, s); + s, property_name); g_free (s); return FALSE; } @@ -927,7 +927,7 @@ test_dispatch_thread_func (gpointer user_data) { /* _with_closures variant doesn't support customizing error data. */ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FILE_INVALID); - g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.Spawn.FileInvalid: Returning some error instead of writing the value 'NotReadable' to the property ''But Writable you are!''"); + g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.Spawn.FileInvalid: Returning some error instead of writing the value ''But Writable you are!'' to the property 'NotReadable'"); } g_assert (error != NULL && error->domain == G_DBUS_ERROR); g_error_free (error); diff --git a/gio/tests/gdbus-names.c b/gio/tests/gdbus-names.c index 4f95b45..20f429e 100644 --- a/gio/tests/gdbus-names.c +++ b/gio/tests/gdbus-names.c @@ -189,6 +189,7 @@ test_bus_own_name (void) * Stop owning the name - this should invoke our free func */ g_bus_unown_name (id); + g_main_loop_run (loop); g_assert_cmpint (data.num_free_func, ==, 2); /* @@ -296,6 +297,7 @@ test_bus_own_name (void) g_assert_cmpint (data2.num_acquired, ==, 0); g_assert_cmpint (data2.num_lost, ==, 1); g_bus_unown_name (id2); + g_main_loop_run (loop); g_assert_cmpint (data2.num_bus_acquired, ==, 1); g_assert_cmpint (data2.num_acquired, ==, 0); g_assert_cmpint (data2.num_lost, ==, 1); @@ -330,6 +332,7 @@ test_bus_own_name (void) g_assert_cmpint (data2.num_acquired, ==, 0); g_assert_cmpint (data2.num_lost, ==, 1); g_bus_unown_name (id2); + g_main_loop_run (loop); g_assert_cmpint (data2.num_bus_acquired, ==, 0); g_assert_cmpint (data2.num_acquired, ==, 0); g_assert_cmpint (data2.num_lost, ==, 1); @@ -355,6 +358,7 @@ test_bus_own_name (void) g_assert_cmpint (data2.num_acquired, ==, 0); g_assert_cmpint (data2.num_lost, ==, 1); g_bus_unown_name (id2); + g_main_loop_run (loop); g_assert_cmpint (data2.num_bus_acquired, ==, 0); g_assert_cmpint (data2.num_acquired, ==, 0); g_assert_cmpint (data2.num_lost, ==, 1); @@ -365,6 +369,7 @@ test_bus_own_name (void) */ data.expect_null_connection = FALSE; g_bus_unown_name (id); + g_main_loop_run (loop); g_assert_cmpint (data.num_bus_acquired, ==, 1); g_assert_cmpint (data.num_acquired, ==, 1); g_assert_cmpint (data.num_free_func, ==, 4); @@ -418,6 +423,7 @@ test_bus_own_name (void) g_assert_cmpint (data2.num_acquired, ==, 0); g_assert_cmpint (data2.num_lost, ==, 1); g_bus_unown_name (id2); + g_main_loop_run (loop); g_assert_cmpint (data2.num_bus_acquired, ==, 0); g_assert_cmpint (data2.num_acquired, ==, 0); g_assert_cmpint (data2.num_lost, ==, 1); @@ -450,8 +456,9 @@ test_bus_own_name (void) g_assert_cmpint (data2.num_bus_acquired, ==, 0); /* ok, make owner2 release the name - then wait for owner to automagically reacquire it */ g_bus_unown_name (id2); - g_assert_cmpint (data2.num_free_func, ==, 1); g_main_loop_run (loop); + g_main_loop_run (loop); + g_assert_cmpint (data2.num_free_func, ==, 1); g_assert_cmpint (data.num_acquired, ==, 2); g_assert_cmpint (data.num_lost, ==, 1); @@ -466,6 +473,7 @@ test_bus_own_name (void) g_assert_cmpint (data.num_acquired, ==, 2); g_assert_cmpint (data.num_lost, ==, 2); g_bus_unown_name (id); + g_main_loop_run (loop); g_assert_cmpint (data.num_free_func, ==, 5); g_object_unref (c); @@ -645,6 +653,7 @@ test_bus_watch_name (void) /* unown the name */ g_bus_unown_name (owner_id); + g_main_loop_run (loop); g_assert_cmpint (data.num_acquired, ==, 1); g_assert_cmpint (data.num_free_func, ==, 2); @@ -704,6 +713,7 @@ test_bus_watch_name (void) g_assert_cmpint (data.num_free_func, ==, 1); g_bus_unown_name (owner_id); + g_main_loop_run (loop); g_assert_cmpint (data.num_free_func, ==, 2); session_bus_down (); diff --git a/gio/tests/gdbus-object-manager-example/meson.build b/gio/tests/gdbus-object-manager-example/meson.build index 404c377..4cfb848 100644 --- a/gio/tests/gdbus-object-manager-example/meson.build +++ b/gio/tests/gdbus-object-manager-example/meson.build @@ -1,6 +1,7 @@ # FIXME: set UNINSTALLED_GLIB_{SRC|BUILD}DIR=top_{src|build}dir ? +gdbus_example_objectmanager_xml = files('gdbus-example-objectmanager.xml') gdbus_example_objectmanager_generated = custom_target('objectmanager-gen', - input : ['gdbus-example-objectmanager.xml'], + input : gdbus_example_objectmanager_xml, output : ['objectmanager-gen.h', 'objectmanager-gen.c', 'objectmanager-gen-org.gtk.GDBus.Example.ObjectManager.Animal.xml', @@ -23,4 +24,5 @@ libgdbus_example_objectmanager = library('gdbus-example-objectmanager', libgdbus_example_objectmanager_dep = declare_dependency( sources : gdbus_example_objectmanager_generated[0], - link_with : libgdbus_example_objectmanager) + link_with : libgdbus_example_objectmanager, + dependencies : [libgio_dep]) diff --git a/gio/tests/gdbus-serialization.c b/gio/tests/gdbus-serialization.c index 89a2e97..2ca28d9 100644 --- a/gio/tests/gdbus-serialization.c +++ b/gio/tests/gdbus-serialization.c @@ -514,9 +514,8 @@ get_body_signature (GVariant *value) } /* If @value is floating, this assumes ownership. */ -static void -check_serialization (GVariant *value, - const gchar *expected_dbus_1_output) +static gchar * +get_and_check_serialization (GVariant *value) { guchar *blob; gsize blob_size; @@ -525,7 +524,7 @@ check_serialization (GVariant *value, GDBusMessage *recovered_message; GError *error; DBusError dbus_error; - gchar *s; + gchar *s = NULL; guint n; message = g_dbus_message_new (); @@ -597,9 +596,6 @@ check_serialization (GVariant *value, s = dbus_1_message_print (dbus_1_message); dbus_message_unref (dbus_1_message); - g_assert_cmpstr (s, ==, expected_dbus_1_output); - g_free (s); - /* Then serialize back and check that the body is identical */ error = NULL; @@ -624,10 +620,22 @@ check_serialization (GVariant *value, } g_object_unref (message); + + return g_steal_pointer (&s); +} + +/* If @value is floating, this assumes ownership. */ +static void +check_serialization (GVariant *value, + const gchar *expected_dbus_1_output) +{ + gchar *s = get_and_check_serialization (value); + g_assert_cmpstr (s, ==, expected_dbus_1_output); + g_free (s); } static void -message_serialize_basic (void) +test_message_serialize_basic (void) { check_serialization (NULL, ""); @@ -661,10 +669,12 @@ message_serialize_basic (void) /* ---------------------------------------------------------------------------------------------------- */ static void -message_serialize_complex (void) +test_message_serialize_complex (void) { GError *error; GVariant *value; + guint i; + gchar *serialization = NULL; error = NULL; @@ -724,6 +734,37 @@ message_serialize_complex (void) " unix-fd: (not extracted)\n"); g_variant_unref (value); #endif + + /* Deep nesting of variants (just below the recursion limit). */ + value = g_variant_new_string ("buried"); + for (i = 0; i < 64; i++) + value = g_variant_new_variant (value); + value = g_variant_new_tuple (&value, 1); + + serialization = get_and_check_serialization (value); + g_assert_nonnull (serialization); + g_assert_true (g_str_has_prefix (serialization, + "value 0: variant:\n" + " variant:\n" + " variant:\n")); + g_free (serialization); + + /* Deep nesting of arrays and structs (just below the recursion limit). + * See https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature */ + value = g_variant_new_string ("hello"); + for (i = 0; i < 32; i++) + value = g_variant_new_tuple (&value, 1); + for (i = 0; i < 32; i++) + value = g_variant_new_array (NULL, &value, 1); + value = g_variant_new_tuple (&value, 1); + + serialization = get_and_check_serialization (value); + g_assert_nonnull (serialization); + g_assert_true (g_str_has_prefix (serialization, + "value 0: array:\n" + " array:\n" + " array:\n")); + g_free (serialization); } @@ -749,7 +790,7 @@ replace (char *blob, } static void -message_serialize_invalid (void) +test_message_serialize_invalid (void) { guint n; @@ -842,7 +883,7 @@ message_serialize_invalid (void) /* ---------------------------------------------------------------------------------------------------- */ static void -message_serialize_header_checks (void) +test_message_serialize_header_checks (void) { GDBusMessage *message; GDBusMessage *reply; @@ -996,7 +1037,7 @@ message_serialize_header_checks (void) /* ---------------------------------------------------------------------------------------------------- */ static void -message_parse_empty_arrays_of_arrays (void) +test_message_parse_empty_arrays_of_arrays (void) { GVariant *body; GError *error = NULL; @@ -1052,7 +1093,7 @@ message_parse_empty_arrays_of_arrays (void) /* ---------------------------------------------------------------------------------------------------- */ static void -test_double_array (void) +test_message_serialize_double_array (void) { GVariantBuilder builder; GVariant *body; @@ -1252,6 +1293,126 @@ test_message_parse_over_long_signature_header (void) /* ---------------------------------------------------------------------------------------------------- */ +/* Test that an invalid header in a D-Bus message (specifically, containing too + * many levels of nested variant) is gracefully handled with an error rather + * than a crash. The set of bytes here come almost directly from fuzzer output. */ +static void +test_message_parse_deep_header_nesting (void) +{ + const guint8 data[] = { + 'l', /* little-endian byte order */ + 0x20, /* message type */ + 0x20, /* message flags */ + 0x01, /* major protocol version */ + 0x20, 0x20, 0x20, 0x00, /* body length (invalid) */ + 0x20, 0x20, 0x20, 0x20, /* message serial */ + /* a{yv} of header fields: + * (things start to be even more invalid below here) */ + 0x20, 0x20, 0x20, 0x00, /* array length (in bytes) */ + 0x20, /* array key (this is not currently a valid header field) */ + /* Variant array value: */ + 0x01, /* signature length */ + 'v', /* one complete type */ + 0x00, /* nul terminator */ + /* (Variant array value payload) */ + /* Critically, this contains 64 nested variants (minus two for the + * ‘arbitrary valid content’ below, but ignoring two for the `a{yv}` + * above), which in total exceeds %G_DBUS_MAX_TYPE_DEPTH. */ + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, + /* Some arbitrary valid content inside the innermost variant: */ + 0x01, 'y', 0x00, 0xcc, + /* (message body length missing) */ + }; + gsize size = sizeof (data); + GDBusMessage *message = NULL; + GError *local_error = NULL; + + message = g_dbus_message_new_from_blob ((guchar *) data, size, + G_DBUS_CAPABILITY_FLAGS_NONE, + &local_error); + g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_null (message); + + g_clear_error (&local_error); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* Test that an invalid body in a D-Bus message (specifically, containing too + * many levels of nested variant) is gracefully handled with an error rather + * than a crash. The set of bytes here are a modified version of the bytes from + * test_message_parse_deep_header_nesting(). */ +static void +test_message_parse_deep_body_nesting (void) +{ + const guint8 data[] = { + 'l', /* little-endian byte order */ + 0x20, /* message type */ + 0x20, /* message flags */ + 0x01, /* major protocol version */ + 0x20, 0x20, 0x20, 0x00, /* body length (invalid) */ + 0x20, 0x20, 0x20, 0x20, /* message serial */ + /* a{yv} of header fields: */ + 0x07, 0x00, 0x00, 0x00, /* array length (in bytes) */ + 0x08, /* array key (signature field) */ + /* Variant array value: */ + 0x01, /* signature length */ + 'g', /* one complete type */ + 0x00, /* nul terminator */ + /* (Variant array value payload) */ + 0x01, 'v', 0x00, + /* End-of-header padding to reach an 8-byte boundary: */ + 0x00, + /* Message body: over 64 levels of nested variant, which is not valid: */ + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, + /* Some arbitrary valid content inside the innermost variant: */ + 0x01, 'y', 0x00, 0xcc, + }; + gsize size = sizeof (data); + GDBusMessage *message = NULL; + GError *local_error = NULL; + + message = g_dbus_message_new_from_blob ((guchar *) data, size, + G_DBUS_CAPABILITY_FLAGS_NONE, + &local_error); + g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_null (message); + + g_clear_error (&local_error); +} + +/* ---------------------------------------------------------------------------------------------------- */ + int main (int argc, char *argv[]) @@ -1262,15 +1423,19 @@ main (int argc, g_test_init (&argc, &argv, NULL); g_test_bug_base ("https://bugzilla.gnome.org/show_bug.cgi?id="); - g_test_add_func ("/gdbus/message-serialize-basic", message_serialize_basic); - g_test_add_func ("/gdbus/message-serialize-complex", message_serialize_complex); - g_test_add_func ("/gdbus/message-serialize-invalid", message_serialize_invalid); - g_test_add_func ("/gdbus/message-serialize-header-checks", message_serialize_header_checks); - - g_test_add_func ("/gdbus/message-parse-empty-arrays-of-arrays", - message_parse_empty_arrays_of_arrays); - - g_test_add_func ("/gdbus/message-serialize/double-array", test_double_array); + g_test_add_func ("/gdbus/message-serialize/basic", + test_message_serialize_basic); + g_test_add_func ("/gdbus/message-serialize/complex", + test_message_serialize_complex); + g_test_add_func ("/gdbus/message-serialize/invalid", + test_message_serialize_invalid); + g_test_add_func ("/gdbus/message-serialize/header-checks", + test_message_serialize_header_checks); + g_test_add_func ("/gdbus/message-serialize/double-array", + test_message_serialize_double_array); + + g_test_add_func ("/gdbus/message-parse/empty-arrays-of-arrays", + test_message_parse_empty_arrays_of_arrays); g_test_add_func ("/gdbus/message-parse/non-signature-header", test_message_parse_non_signature_header); g_test_add_func ("/gdbus/message-parse/empty-signature-header", @@ -1279,6 +1444,10 @@ main (int argc, test_message_parse_multiple_signature_header); g_test_add_func ("/gdbus/message-parse/over-long-signature-header", test_message_parse_over_long_signature_header); + g_test_add_func ("/gdbus/message-parse/deep-header-nesting", + test_message_parse_deep_header_nesting); + g_test_add_func ("/gdbus/message-parse/deep-body-nesting", + test_message_parse_deep_body_nesting); return g_test_run(); } diff --git a/gio/tests/gdbus-server-auth.c b/gio/tests/gdbus-server-auth.c new file mode 100644 index 0000000..2554ad6 --- /dev/null +++ b/gio/tests/gdbus-server-auth.c @@ -0,0 +1,539 @@ +/* + * Copyright 2019 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#include "config.h" + +#include + +#include +#include + +/* For G_CREDENTIALS_*_SUPPORTED */ +#include + +#ifdef HAVE_DBUS1 +#include +#endif + +typedef enum +{ + INTEROP_FLAGS_EXTERNAL = (1 << 0), + INTEROP_FLAGS_ANONYMOUS = (1 << 1), + INTEROP_FLAGS_SHA1 = (1 << 2), + INTEROP_FLAGS_TCP = (1 << 3), + INTEROP_FLAGS_LIBDBUS = (1 << 4), + INTEROP_FLAGS_ABSTRACT = (1 << 5), + INTEROP_FLAGS_NONE = 0 +} InteropFlags; + +static gboolean +allow_external_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, + const char *mechanism, + G_GNUC_UNUSED gpointer user_data) +{ + if (g_strcmp0 (mechanism, "EXTERNAL") == 0) + { + g_debug ("Accepting EXTERNAL authentication"); + return TRUE; + } + else + { + g_debug ("Rejecting \"%s\" authentication: not EXTERNAL", mechanism); + return FALSE; + } +} + +static gboolean +allow_anonymous_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, + const char *mechanism, + G_GNUC_UNUSED gpointer user_data) +{ + if (g_strcmp0 (mechanism, "ANONYMOUS") == 0) + { + g_debug ("Accepting ANONYMOUS authentication"); + return TRUE; + } + else + { + g_debug ("Rejecting \"%s\" authentication: not ANONYMOUS", mechanism); + return FALSE; + } +} + +static gboolean +allow_sha1_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, + const char *mechanism, + G_GNUC_UNUSED gpointer user_data) +{ + if (g_strcmp0 (mechanism, "DBUS_COOKIE_SHA1") == 0) + { + g_debug ("Accepting DBUS_COOKIE_SHA1 authentication"); + return TRUE; + } + else + { + g_debug ("Rejecting \"%s\" authentication: not DBUS_COOKIE_SHA1", + mechanism); + return FALSE; + } +} + +static gboolean +allow_any_mechanism_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, + const char *mechanism, + G_GNUC_UNUSED gpointer user_data) +{ + g_debug ("Accepting \"%s\" authentication", mechanism); + return TRUE; +} + +static gboolean +authorize_any_authenticated_peer_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, + G_GNUC_UNUSED GIOStream *stream, + GCredentials *credentials, + G_GNUC_UNUSED gpointer user_data) +{ + if (credentials == NULL) + { + g_debug ("Authorizing peer with no credentials"); + } + else + { + gchar *str = g_credentials_to_string (credentials); + + g_debug ("Authorizing peer with credentials: %s", str); + g_free (str); + } + + return TRUE; +} + +static GDBusMessage * +whoami_filter_cb (GDBusConnection *connection, + GDBusMessage *message, + gboolean incoming, + G_GNUC_UNUSED gpointer user_data) +{ + if (!incoming) + return message; + + if (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL && + g_strcmp0 (g_dbus_message_get_member (message), "WhoAmI") == 0) + { + GDBusMessage *reply = g_dbus_message_new_method_reply (message); + gint64 uid = -1; + gint64 pid = -1; +#ifdef G_OS_UNIX + GCredentials *credentials = g_dbus_connection_get_peer_credentials (connection); + + if (credentials != NULL) + { + uid = (gint64) g_credentials_get_unix_user (credentials, NULL); + pid = (gint64) g_credentials_get_unix_pid (credentials, NULL); + } +#endif + + g_dbus_message_set_body (reply, + g_variant_new ("(xx)", uid, pid)); + g_dbus_connection_send_message (connection, reply, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + NULL, NULL); + g_object_unref (reply); + + /* handled */ + g_object_unref (message); + return NULL; + } + + return message; +} + +static gboolean +new_connection_cb (G_GNUC_UNUSED GDBusServer *server, + GDBusConnection *connection, + G_GNUC_UNUSED gpointer user_data) +{ + GCredentials *credentials = g_dbus_connection_get_peer_credentials (connection); + + if (credentials == NULL) + { + g_debug ("New connection from peer with no credentials"); + } + else + { + gchar *str = g_credentials_to_string (credentials); + + g_debug ("New connection from peer with credentials: %s", str); + g_free (str); + } + + g_object_ref (connection); + g_dbus_connection_add_filter (connection, whoami_filter_cb, NULL, NULL); + return TRUE; +} + +#ifdef HAVE_DBUS1 +typedef struct +{ + DBusError error; + DBusConnection *conn; + DBusMessage *call; + DBusMessage *reply; +} LibdbusCall; + +static void +libdbus_call_task_cb (GTask *task, + G_GNUC_UNUSED gpointer source_object, + gpointer task_data, + G_GNUC_UNUSED GCancellable *cancellable) +{ + LibdbusCall *libdbus_call = task_data; + + libdbus_call->reply = dbus_connection_send_with_reply_and_block (libdbus_call->conn, + libdbus_call->call, + -1, + &libdbus_call->error); +} +#endif /* HAVE_DBUS1 */ + +static void +store_result_cb (G_GNUC_UNUSED GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GAsyncResult **result = user_data; + + g_assert_nonnull (result); + g_assert_null (*result); + *result = g_object_ref (res); +} + +static void +assert_expected_uid_pid (InteropFlags flags, + gint64 uid, + gint64 pid) +{ +#ifdef G_OS_UNIX + if (flags & (INTEROP_FLAGS_ANONYMOUS | INTEROP_FLAGS_SHA1 | INTEROP_FLAGS_TCP)) + { + /* No assertion. There is no guarantee whether credentials will be + * passed even though we didn't send them. Conversely, if + * credentials were not passed, + * g_dbus_connection_get_peer_credentials() always returns the + * credentials of the socket, and not the uid that a + * client might have proved it has by using DBUS_COOKIE_SHA1. */ + } + else /* We should prefer EXTERNAL whenever it is allowed. */ + { +#ifdef __linux__ + /* We know that both GDBus and libdbus support full credentials-passing + * on Linux. */ + g_assert_cmpint (uid, ==, getuid ()); + g_assert_cmpint (pid, ==, getpid ()); +#else + g_test_message ("Please open a merge request to add appropriate " + "assertions for your platform"); +#endif + } +#endif /* G_OS_UNIX */ +} + +static void +do_test_server_auth (InteropFlags flags) +{ + GError *error = NULL; + gchar *tmpdir = NULL; + gchar *listenable_address = NULL; + GDBusServer *server = NULL; + GDBusAuthObserver *observer = NULL; + GDBusServerFlags server_flags = G_DBUS_SERVER_FLAGS_RUN_IN_THREAD; + gchar *guid = NULL; + const char *connectable_address; + GDBusConnection *client = NULL; + GAsyncResult *result = NULL; + GVariant *tuple = NULL; + gint64 uid, pid; +#ifdef HAVE_DBUS1 + /* GNOME/glib#1831 seems to involve a race condition, so try a few times + * to see if we can trigger it. */ + gsize i; + gsize n = 20; +#endif + + if (flags & INTEROP_FLAGS_TCP) + { + listenable_address = g_strdup ("tcp:host=127.0.0.1"); + } + else + { +#ifdef G_OS_UNIX + gchar *escaped; + + tmpdir = g_dir_make_tmp ("gdbus-server-auth-XXXXXX", &error); + g_assert_no_error (error); + escaped = g_dbus_address_escape_value (tmpdir); + listenable_address = g_strdup_printf ("unix:%s=%s", + (flags & INTEROP_FLAGS_ABSTRACT) ? "tmpdir" : "dir", + escaped); + g_free (escaped); +#else + g_test_skip ("unix: addresses only work on Unix"); + goto out; +#endif + } + + g_test_message ("Testing GDBus server at %s / libdbus client, with flags: " + "external:%s " + "anonymous:%s " + "sha1:%s " + "abstract:%s " + "tcp:%s", + listenable_address, + (flags & INTEROP_FLAGS_EXTERNAL) ? "true" : "false", + (flags & INTEROP_FLAGS_ANONYMOUS) ? "true" : "false", + (flags & INTEROP_FLAGS_SHA1) ? "true" : "false", + (flags & INTEROP_FLAGS_ABSTRACT) ? "true" : "false", + (flags & INTEROP_FLAGS_TCP) ? "true" : "false"); + +#if !defined(G_CREDENTIALS_UNIX_CREDENTIALS_MESSAGE_SUPPORTED) \ + && !defined(G_CREDENTIALS_SOCKET_GET_CREDENTIALS_SUPPORTED) + if (flags & INTEROP_FLAGS_EXTERNAL) + { + g_test_skip ("EXTERNAL authentication not implemented on this platform"); + goto out; + } +#endif + + if (flags & INTEROP_FLAGS_ANONYMOUS) + server_flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS; + + observer = g_dbus_auth_observer_new (); + + if (flags & INTEROP_FLAGS_EXTERNAL) + g_signal_connect (observer, "allow-mechanism", + G_CALLBACK (allow_external_cb), NULL); + else if (flags & INTEROP_FLAGS_ANONYMOUS) + g_signal_connect (observer, "allow-mechanism", + G_CALLBACK (allow_anonymous_cb), NULL); + else if (flags & INTEROP_FLAGS_SHA1) + g_signal_connect (observer, "allow-mechanism", + G_CALLBACK (allow_sha1_cb), NULL); + else + g_signal_connect (observer, "allow-mechanism", + G_CALLBACK (allow_any_mechanism_cb), NULL); + + g_signal_connect (observer, "authorize-authenticated-peer", + G_CALLBACK (authorize_any_authenticated_peer_cb), + NULL); + + guid = g_dbus_generate_guid (); + server = g_dbus_server_new_sync (listenable_address, + server_flags, + guid, + observer, + NULL, + &error); + g_assert_no_error (error); + g_assert_nonnull (server); + g_signal_connect (server, "new-connection", G_CALLBACK (new_connection_cb), NULL); + g_dbus_server_start (server); + connectable_address = g_dbus_server_get_client_address (server); + g_test_message ("Connectable address: %s", connectable_address); + + result = NULL; + g_dbus_connection_new_for_address (connectable_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, NULL, store_result_cb, &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + client = g_dbus_connection_new_for_address_finish (result, &error); + g_assert_no_error (error); + g_assert_nonnull (client); + g_clear_object (&result); + + g_dbus_connection_call (client, NULL, "/", "com.example.Test", "WhoAmI", + NULL, G_VARIANT_TYPE ("(xx)"), + G_DBUS_CALL_FLAGS_NONE, -1, NULL, store_result_cb, + &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + tuple = g_dbus_connection_call_finish (client, result, &error); + g_assert_no_error (error); + g_assert_nonnull (tuple); + g_clear_object (&result); + g_clear_object (&client); + + uid = -2; + pid = -2; + g_variant_get (tuple, "(xx)", &uid, &pid); + + g_debug ("Server says GDBus client is uid %" G_GINT64_FORMAT ", pid %" G_GINT64_FORMAT, + uid, pid); + + assert_expected_uid_pid (flags, uid, pid); + + g_clear_pointer (&tuple, g_variant_unref); + +#ifdef HAVE_DBUS1 + for (i = 0; i < n; i++) + { + LibdbusCall libdbus_call = { DBUS_ERROR_INIT, NULL, NULL, NULL }; + GTask *task; + + /* The test suite uses %G_TEST_OPTION_ISOLATE_DIRS, which sets + * `HOME=/dev/null` and leaves g_get_home_dir() pointing to the per-test + * temp home directory. Unfortunately, libdbus doesn’t allow the home dir + * to be overridden except using the environment, so copy the per-test + * temp home directory back there so that libdbus uses the same + * `$HOME/.dbus-keyrings` path as GLib. This is not thread-safe. */ + g_setenv ("HOME", g_get_home_dir (), TRUE); + + libdbus_call.conn = dbus_connection_open_private (connectable_address, + &libdbus_call.error); + g_assert_cmpstr (libdbus_call.error.name, ==, NULL); + g_assert_nonnull (libdbus_call.conn); + + libdbus_call.call = dbus_message_new_method_call (NULL, "/", + "com.example.Test", + "WhoAmI"); + + if (libdbus_call.call == NULL) + g_error ("Out of memory"); + + result = NULL; + task = g_task_new (NULL, NULL, store_result_cb, &result); + g_task_set_task_data (task, &libdbus_call, NULL); + g_task_run_in_thread (task, libdbus_call_task_cb); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + g_clear_object (&result); + + g_assert_cmpstr (libdbus_call.error.name, ==, NULL); + g_assert_nonnull (libdbus_call.reply); + + uid = -2; + pid = -2; + dbus_message_get_args (libdbus_call.reply, &libdbus_call.error, + DBUS_TYPE_INT64, &uid, + DBUS_TYPE_INT64, &pid, + DBUS_TYPE_INVALID); + g_assert_cmpstr (libdbus_call.error.name, ==, NULL); + + g_debug ("Server says libdbus client %" G_GSIZE_FORMAT " is uid %" G_GINT64_FORMAT ", pid %" G_GINT64_FORMAT, + i, uid, pid); + assert_expected_uid_pid (flags | INTEROP_FLAGS_LIBDBUS, uid, pid); + + dbus_connection_close (libdbus_call.conn); + dbus_connection_unref (libdbus_call.conn); + dbus_message_unref (libdbus_call.call); + dbus_message_unref (libdbus_call.reply); + g_clear_object (&task); + } +#else /* !HAVE_DBUS1 */ + g_test_skip ("Testing interop with libdbus not supported"); +#endif /* !HAVE_DBUS1 */ + + /* No practical effect, just to avoid -Wunused-label under some + * combinations of #ifdefs */ + goto out; + +out: + if (server != NULL) + g_dbus_server_stop (server); + + if (tmpdir != NULL) + g_assert_cmpstr (g_rmdir (tmpdir) == 0 ? "OK" : g_strerror (errno), + ==, "OK"); + + g_clear_object (&server); + g_clear_object (&observer); + g_free (guid); + g_free (listenable_address); + g_free (tmpdir); +} + +static void +test_server_auth (void) +{ + do_test_server_auth (INTEROP_FLAGS_NONE); +} + +static void +test_server_auth_abstract (void) +{ + do_test_server_auth (INTEROP_FLAGS_ABSTRACT); +} + +static void +test_server_auth_tcp (void) +{ + do_test_server_auth (INTEROP_FLAGS_TCP); +} + +static void +test_server_auth_anonymous (void) +{ + do_test_server_auth (INTEROP_FLAGS_ANONYMOUS); +} + +static void +test_server_auth_anonymous_tcp (void) +{ + do_test_server_auth (INTEROP_FLAGS_ANONYMOUS | INTEROP_FLAGS_TCP); +} + +static void +test_server_auth_external (void) +{ + do_test_server_auth (INTEROP_FLAGS_EXTERNAL); +} + +static void +test_server_auth_sha1 (void) +{ + do_test_server_auth (INTEROP_FLAGS_SHA1); +} + +static void +test_server_auth_sha1_tcp (void) +{ + do_test_server_auth (INTEROP_FLAGS_SHA1 | INTEROP_FLAGS_TCP); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); + + g_test_add_func ("/gdbus/server-auth", test_server_auth); + g_test_add_func ("/gdbus/server-auth/abstract", test_server_auth_abstract); + g_test_add_func ("/gdbus/server-auth/tcp", test_server_auth_tcp); + g_test_add_func ("/gdbus/server-auth/anonymous", test_server_auth_anonymous); + g_test_add_func ("/gdbus/server-auth/anonymous/tcp", test_server_auth_anonymous_tcp); + g_test_add_func ("/gdbus/server-auth/external", test_server_auth_external); + g_test_add_func ("/gdbus/server-auth/sha1", test_server_auth_sha1); + g_test_add_func ("/gdbus/server-auth/sha1/tcp", test_server_auth_sha1_tcp); + + return g_test_run(); +} diff --git a/gio/tests/gdbus-sessionbus.c b/gio/tests/gdbus-sessionbus.c index e900969..29f05d4 100644 --- a/gio/tests/gdbus-sessionbus.c +++ b/gio/tests/gdbus-sessionbus.c @@ -25,8 +25,21 @@ static GTestDBus *singleton = NULL; void session_bus_up (void) { + gchar *relative, *servicesdir; g_assert (singleton == NULL); singleton = g_test_dbus_new (G_TEST_DBUS_NONE); + + /* We ignore deprecations here so that gdbus-test-codegen-old can + * build successfully despite these two functions not being + * available in GLib 2.36 */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + relative = g_test_build_filename (G_TEST_BUILT, "services", NULL); + servicesdir = g_canonicalize_filename (relative, NULL); + G_GNUC_END_IGNORE_DEPRECATIONS + g_free (relative); + + g_test_dbus_add_service_dir (singleton, servicesdir); + g_free (servicesdir); g_test_dbus_up (singleton); } diff --git a/gio/tests/gdbus-test-codegen.c b/gio/tests/gdbus-test-codegen.c index 985cb10..691506f 100644 --- a/gio/tests/gdbus-test-codegen.c +++ b/gio/tests/gdbus-test-codegen.c @@ -25,7 +25,12 @@ #include "gdbus-tests.h" +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 +#include "gdbus-test-codegen-generated-min-required-2-64.h" +#else #include "gdbus-test-codegen-generated.h" +#endif + #include "gdbus-test-codegen-generated-interface-info.h" /* ---------------------------------------------------------------------------------------------------- */ @@ -878,6 +883,10 @@ check_bar_proxy (FooiGenBar *proxy, "/a/path", "asig", "bytestring\xff", +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + G_DBUS_CALL_FLAGS_NONE, + -1, +#endif &ret_val_byte, &ret_val_boolean, &ret_val_int16, @@ -913,6 +922,10 @@ check_bar_proxy (FooiGenBar *proxy, array_of_objpaths, array_of_signatures, array_of_bytestrings, +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + G_DBUS_CALL_FLAGS_NONE, + -1, +#endif &ret_array_of_strings, &ret_array_of_objpaths, &ret_array_of_signatures, @@ -946,7 +959,11 @@ check_bar_proxy (FooiGenBar *proxy, * unimplemented methods. */ error = NULL; +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + ret = foo_igen_bar_call_unimplemented_method_sync (proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL /* GCancellable */, &error); +#else ret = foo_igen_bar_call_unimplemented_method_sync (proxy, NULL /* GCancellable */, &error); +#endif g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD); g_error_free (error); error = NULL; @@ -957,7 +974,11 @@ check_bar_proxy (FooiGenBar *proxy, G_CALLBACK (on_test_signal), data); error = NULL; +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + ret = foo_igen_bar_call_request_signal_emission_sync (proxy, 0, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); +#else ret = foo_igen_bar_call_request_signal_emission_sync (proxy, 0, NULL, &error); +#endif g_assert_no_error (error); g_assert (ret); @@ -1011,7 +1032,11 @@ check_bar_proxy (FooiGenBar *proxy, G_CALLBACK (on_g_properties_changed), data); error = NULL; +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + ret = foo_igen_bar_call_request_multi_property_mods_sync (proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); +#else ret = foo_igen_bar_call_request_multi_property_mods_sync (proxy, NULL, &error); +#endif g_assert_no_error (error); g_assert (ret); g_main_loop_run (thread_loop); @@ -1077,6 +1102,10 @@ check_bar_proxy (FooiGenBar *proxy, */ error = NULL; foo_igen_bar_call_property_cancellation (proxy, +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + G_DBUS_CALL_FLAGS_NONE, + -1, +#endif NULL, /* GCancellable */ (GAsyncReadyCallback) on_property_cancellation_cb, data); @@ -1159,6 +1188,10 @@ check_bat_proxy (FooiGenBat *proxy, g_variant_new_string ("a string"), g_variant_new_bytestring ("a bytestring\xff"), g_variant_new ("(i)", 4200), +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + G_DBUS_CALL_FLAGS_NONE, + -1, +#endif &ret_i, &ret_s, &ret_ay, @@ -1191,18 +1224,30 @@ check_authorize_proxy (FooiGenAuthorize *proxy, /* Check that g-authorize-method works as intended */ error = NULL; +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + ret = foo_igen_authorize_call_check_not_authorized_sync (proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); +#else ret = foo_igen_authorize_call_check_not_authorized_sync (proxy, NULL, &error); +#endif g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED); g_error_free (error); g_assert (!ret); error = NULL; +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + ret = foo_igen_authorize_call_check_authorized_sync (proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); +#else ret = foo_igen_authorize_call_check_authorized_sync (proxy, NULL, &error); +#endif g_assert_no_error (error); g_assert (ret); error = NULL; +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + ret = foo_igen_authorize_call_check_not_authorized_from_object_sync (proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); +#else ret = foo_igen_authorize_call_check_not_authorized_from_object_sync (proxy, NULL, &error); +#endif g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PENDING); g_error_free (error); g_assert (!ret); @@ -1219,7 +1264,11 @@ get_self_via_proxy (FooiGenMethodThreads *proxy_1) gpointer self; error = NULL; +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 + ret = foo_igen_method_threads_call_get_self_sync (proxy_1, G_DBUS_CALL_FLAGS_NONE, -1, &self_str, NULL, &error); +#else ret = foo_igen_method_threads_call_get_self_sync (proxy_1, &self_str, NULL, &error); +#endif g_assert_no_error (error); g_assert (ret); @@ -2589,6 +2638,86 @@ test_standalone_interface_info (void) } /* ---------------------------------------------------------------------------------------------------- */ +static gboolean +handle_hello_fd (FooiGenFDPassing *object, + GDBusMethodInvocation *invocation, + GUnixFDList *fd_list, + const gchar *arg_greeting) +{ + foo_igen_fdpassing_complete_hello_fd (object, invocation, fd_list, arg_greeting); + return TRUE; +} + +#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 +static gboolean +handle_no_annotation (FooiGenFDPassing *object, + GDBusMethodInvocation *invocation, + GUnixFDList *fd_list, + GVariant *arg_greeting, + const gchar *arg_greeting_locale) +{ + foo_igen_fdpassing_complete_no_annotation (object, invocation, fd_list, arg_greeting, arg_greeting_locale); + return TRUE; +} + +static gboolean +handle_no_annotation_nested (FooiGenFDPassing *object, + GDBusMethodInvocation *invocation, + GUnixFDList *fd_list, + GVariant *arg_files) +{ + foo_igen_fdpassing_complete_no_annotation_nested (object, invocation, fd_list); + return TRUE; +} +#else +static gboolean +handle_no_annotation (FooiGenFDPassing *object, + GDBusMethodInvocation *invocation, + GVariant *arg_greeting, + const gchar *arg_greeting_locale) +{ + foo_igen_fdpassing_complete_no_annotation (object, invocation, arg_greeting, arg_greeting_locale); + return TRUE; +} + +static gboolean +handle_no_annotation_nested (FooiGenFDPassing *object, + GDBusMethodInvocation *invocation, + GVariant *arg_files) +{ + foo_igen_fdpassing_complete_no_annotation_nested (object, invocation); + return TRUE; +} +#endif + +/* Test that generated code for methods includes GUnixFDList arguments + * unconditionally if the method is explicitly annotated as C.UnixFD, and only + * emits GUnixFDList arguments when there's merely an 'h' parameter if + * --glib-min-required=2.64 or greater. + */ +static void +test_unix_fd_list (void) +{ + FooiGenFDPassingIface iface; + + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1726"); + + /* This method is explicitly annotated. */ + iface.handle_hello_fd = handle_hello_fd; + + /* This one is not annotated; even though it's got an in and out 'h' + * parameter, for backwards compatibility we cannot emit GUnixFDList + * arguments unless --glib-min-required >= 2.64 was used. + */ + iface.handle_no_annotation = handle_no_annotation; + + /* This method has an 'h' inside a complex type. */ + iface.handle_no_annotation_nested = handle_no_annotation_nested; + + (void) iface; +} + +/* ---------------------------------------------------------------------------------------------------- */ int main (int argc, @@ -2603,6 +2732,7 @@ main (int argc, g_test_add_func ("/gdbus/codegen/autocleanups", test_autocleanups); g_test_add_func ("/gdbus/codegen/deprecations", test_deprecations); g_test_add_func ("/gdbus/codegen/standalone-interface-info", test_standalone_interface_info); + g_test_add_func ("/gdbus/codegen/unix-fd-list", test_unix_fd_list); return session_bus_run (); } diff --git a/gio/tests/gdbus-test-fixture.c b/gio/tests/gdbus-test-fixture.c index f8b9b93..2071bba 100644 --- a/gio/tests/gdbus-test-fixture.c +++ b/gio/tests/gdbus-test-fixture.c @@ -15,16 +15,20 @@ static void fixture_setup (TestFixture *fixture, gconstpointer unused) { GError *error = NULL; + gchar *relative, *servicesdir; /* Create the global dbus-daemon for this test suite */ fixture->dbus = g_test_dbus_new (G_TEST_DBUS_NONE); - /* Add the private directory with our in-tree service files, - * TEST_SERVICES is defined by the build system to point - * to the right directory. + /* Add the private directory with our in-tree service files. */ - g_test_dbus_add_service_dir (fixture->dbus, TEST_SERVICES); + relative = g_test_build_filename (G_TEST_BUILT, "services", NULL); + servicesdir = g_canonicalize_filename (relative, NULL); + g_free (relative); + + g_test_dbus_add_service_dir (fixture->dbus, servicesdir); + g_free (servicesdir); /* Start the private D-Bus daemon */ diff --git a/gio/tests/gdbus-tests.c b/gio/tests/gdbus-tests.c index 35e379b..bed0d24 100644 --- a/gio/tests/gdbus-tests.c +++ b/gio/tests/gdbus-tests.c @@ -86,49 +86,72 @@ _give_up (gpointer data) g_return_val_if_reached (TRUE); } +typedef struct +{ + GMainContext *context; + gboolean name_appeared; + gboolean unwatch_complete; +} WatchData; + +static void +name_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + WatchData *data = user_data; + + g_assert (name_owner != NULL); + data->name_appeared = TRUE; + g_main_context_wakeup (data->context); +} + +static void +watch_free_cb (gpointer user_data) +{ + WatchData *data = user_data; + + data->unwatch_complete = TRUE; + g_main_context_wakeup (data->context); +} + void -ensure_gdbus_testserver_up (void) +ensure_gdbus_testserver_up (GDBusConnection *connection, + GMainContext *context) { - guint id; - gchar *name_owner; - GDBusConnection *connection; - GDBusProxy *proxy; - GError *error = NULL; - - connection = g_bus_get_sync (G_BUS_TYPE_SESSION, - NULL, - &error); - - g_assert_no_error (error); - error = NULL; - - proxy = g_dbus_proxy_new_sync (connection, - G_DBUS_PROXY_FLAGS_NONE, - NULL, /* GDBusInterfaceInfo */ - "com.example.TestService", /* name */ - "/com/example/TestObject", /* object path */ - "com.example.Frob", /* interface */ - NULL, /* GCancellable */ - &error); - g_assert_no_error (error); - - id = g_timeout_add_seconds (60, _give_up, - "waited more than ~ 60s for gdbus-testserver to take its bus name"); - - while (TRUE) - { - name_owner = g_dbus_proxy_get_name_owner (proxy); - - if (name_owner != NULL) - break; - - g_main_context_iteration (NULL, TRUE); - } - - g_source_remove (id); - g_free (name_owner); - g_object_unref (proxy); - g_object_unref (connection); + GSource *timeout_source = NULL; + guint watch_id; + WatchData data = { context, FALSE, FALSE }; + + g_main_context_push_thread_default (context); + + watch_id = g_bus_watch_name_on_connection (connection, + "com.example.TestService", + G_BUS_NAME_WATCHER_FLAGS_NONE, + name_appeared_cb, + NULL, + &data, + watch_free_cb); + + timeout_source = g_timeout_source_new_seconds (60); + g_source_set_callback (timeout_source, _give_up, + "waited more than ~ 60s for gdbus-testserver to take its bus name", + NULL); + g_source_attach (timeout_source, context); + + while (!data.name_appeared) + g_main_context_iteration (context, TRUE); + + g_bus_unwatch_name (watch_id); + watch_id = 0; + + while (!data.unwatch_complete) + g_main_context_iteration (context, TRUE); + + g_source_destroy (timeout_source); + g_source_unref (timeout_source); + + g_main_context_pop_thread_default (context); } /* ---------------------------------------------------------------------------------------------------- */ diff --git a/gio/tests/gdbus-tests.h b/gio/tests/gdbus-tests.h index 00cda37..eaef234 100644 --- a/gio/tests/gdbus-tests.h +++ b/gio/tests/gdbus-tests.h @@ -114,7 +114,8 @@ GDBusConnection *_g_bus_get_priv (GBusType bus_type, GCancellable *cancellable, GError **error); -void ensure_gdbus_testserver_up (void); +void ensure_gdbus_testserver_up (GDBusConnection *connection, + GMainContext *context); G_END_DECLS diff --git a/gio/tests/gdbus-threading.c b/gio/tests/gdbus-threading.c index ffca6f3..2b89fb0 100644 --- a/gio/tests/gdbus-threading.c +++ b/gio/tests/gdbus-threading.c @@ -27,59 +27,77 @@ /* all tests rely on a global connection */ static GDBusConnection *c = NULL; -/* ---------------------------------------------------------------------------------------------------- */ -/* Ensure that signal and method replies are delivered in the right thread */ -/* ---------------------------------------------------------------------------------------------------- */ +typedef struct +{ + GMainContext *context; + gboolean timed_out; +} TimeoutData; -typedef struct { - GThread *thread; - GMainLoop *thread_loop; - guint signal_count; -} DeliveryData; +static gboolean +timeout_cb (gpointer user_data) +{ + TimeoutData *data = user_data; + + data->timed_out = TRUE; + g_main_context_wakeup (data->context); + return G_SOURCE_REMOVE; +} + +/* Check that the given @connection has only one ref, waiting to let any pending + * unrefs complete first. This is typically used on the shared connection, to + * ensure it’s in a correct state before beginning the next test. */ static void -msg_cb_expect_success (GDBusConnection *connection, - GAsyncResult *res, - gpointer user_data) +assert_connection_has_one_ref (GDBusConnection *connection, + GMainContext *context) { - DeliveryData *data = user_data; - GError *error; - GVariant *result; + GSource *timeout_source = NULL; + TimeoutData data = { context, FALSE }; - error = NULL; - result = g_dbus_connection_call_finish (connection, - res, - &error); - g_assert_no_error (error); - g_assert (result != NULL); - g_variant_unref (result); + if (g_atomic_int_get (&G_OBJECT (connection)->ref_count) == 1) + return; - g_assert (g_thread_self () == data->thread); + timeout_source = g_timeout_source_new_seconds (3); + g_source_set_callback (timeout_source, timeout_cb, &data, NULL); + g_source_attach (timeout_source, context); - g_main_loop_quit (data->thread_loop); + while (g_atomic_int_get (&G_OBJECT (connection)->ref_count) != 1 && !data.timed_out) + { + g_debug ("refcount of %p is not right, sleeping", connection); + g_main_context_iteration (NULL, TRUE); + } + + g_source_destroy (timeout_source); + g_source_unref (timeout_source); + + if (g_atomic_int_get (&G_OBJECT (connection)->ref_count) != 1) + g_error ("connection %p had too many refs", connection); } +/* ---------------------------------------------------------------------------------------------------- */ +/* Ensure that signal and method replies are delivered in the right thread */ +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct { + GThread *thread; + GMainContext *context; + guint signal_count; + gboolean unsubscribe_complete; + GAsyncResult *async_result; +} DeliveryData; + static void -msg_cb_expect_error_cancelled (GDBusConnection *connection, - GAsyncResult *res, - gpointer user_data) +async_result_cb (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) { DeliveryData *data = user_data; - GError *error; - GVariant *result; - error = NULL; - result = g_dbus_connection_call_finish (connection, - res, - &error); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - g_assert (!g_dbus_error_is_remote_error (error)); - g_error_free (error); - g_assert (result == NULL); + data->async_result = g_object_ref (res); - g_assert (g_thread_self () == data->thread); + g_assert_true (g_thread_self () == data->thread); - g_main_loop_quit (data->thread_loop); + g_main_context_wakeup (data->context); } static void @@ -93,33 +111,43 @@ signal_handler (GDBusConnection *connection, { DeliveryData *data = user_data; - g_assert (g_thread_self () == data->thread); + g_assert_true (g_thread_self () == data->thread); data->signal_count++; - g_main_loop_quit (data->thread_loop); + g_main_context_wakeup (data->context); +} + +static void +signal_data_free_cb (gpointer user_data) +{ + DeliveryData *data = user_data; + + g_assert_true (g_thread_self () == data->thread); + + data->unsubscribe_complete = TRUE; + + g_main_context_wakeup (data->context); } static gpointer test_delivery_in_thread_func (gpointer _data) { - GMainLoop *thread_loop; GMainContext *thread_context; DeliveryData data; GCancellable *ca; guint subscription_id; - GDBusConnection *priv_c; - GError *error; - - error = NULL; + GError *error = NULL; + GVariant *result_variant = NULL; thread_context = g_main_context_new (); - thread_loop = g_main_loop_new (thread_context, FALSE); g_main_context_push_thread_default (thread_context); data.thread = g_thread_self (); - data.thread_loop = thread_loop; + data.context = thread_context; data.signal_count = 0; + data.unsubscribe_complete = FALSE; + data.async_result = NULL; /* ---------------------------------------------------------------------------------------------------- */ @@ -135,9 +163,16 @@ test_delivery_in_thread_func (gpointer _data) G_DBUS_CALL_FLAGS_NONE, -1, NULL, - (GAsyncReadyCallback) msg_cb_expect_success, + (GAsyncReadyCallback) async_result_cb, &data); - g_main_loop_run (thread_loop); + while (data.async_result == NULL) + g_main_context_iteration (thread_context, TRUE); + + result_variant = g_dbus_connection_call_finish (c, data.async_result, &error); + g_assert_no_error (error); + g_assert_nonnull (result_variant); + g_clear_pointer (&result_variant, g_variant_unref); + g_clear_object (&data.async_result); /* * Check that we never actually send a message if the GCancellable @@ -155,9 +190,18 @@ test_delivery_in_thread_func (gpointer _data) G_DBUS_CALL_FLAGS_NONE, -1, ca, - (GAsyncReadyCallback) msg_cb_expect_error_cancelled, + (GAsyncReadyCallback) async_result_cb, &data); - g_main_loop_run (thread_loop); + while (data.async_result == NULL) + g_main_context_iteration (thread_context, TRUE); + + result_variant = g_dbus_connection_call_finish (c, data.async_result, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert_false (g_dbus_error_is_remote_error (error)); + g_clear_error (&error); + g_assert_null (result_variant); + g_clear_object (&data.async_result); + g_object_unref (ca); /* @@ -173,47 +217,74 @@ test_delivery_in_thread_func (gpointer _data) G_DBUS_CALL_FLAGS_NONE, -1, ca, - (GAsyncReadyCallback) msg_cb_expect_error_cancelled, + (GAsyncReadyCallback) async_result_cb, &data); g_cancellable_cancel (ca); - g_main_loop_run (thread_loop); + + while (data.async_result == NULL) + g_main_context_iteration (thread_context, TRUE); + + result_variant = g_dbus_connection_call_finish (c, data.async_result, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert_false (g_dbus_error_is_remote_error (error)); + g_clear_error (&error); + g_assert_null (result_variant); + g_clear_object (&data.async_result); + g_object_unref (ca); /* * Check that signals are delivered to the correct thread. * - * First we subscribe to the signal, then we create a a private - * connection. This should cause a NameOwnerChanged message from - * the message bus. + * First we subscribe to the signal, then we call EmitSignal(). This should + * cause a TestSignal emission from the testserver. */ subscription_id = g_dbus_connection_signal_subscribe (c, - "org.freedesktop.DBus", /* sender */ - "org.freedesktop.DBus", /* interface */ - "NameOwnerChanged", /* member */ - "/org/freedesktop/DBus", /* path */ + "com.example.TestService", /* sender */ + "com.example.Frob", /* interface */ + "TestSignal", /* member */ + "/com/example/TestObject", /* path */ NULL, G_DBUS_SIGNAL_FLAGS_NONE, signal_handler, &data, - NULL); - g_assert (subscription_id != 0); - g_assert (data.signal_count == 0); + signal_data_free_cb); + g_assert_cmpuint (subscription_id, !=, 0); + g_assert_cmpuint (data.signal_count, ==, 0); - priv_c = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error); - g_assert_no_error (error); - g_assert (priv_c != NULL); + g_dbus_connection_call (c, + "com.example.TestService", /* bus_name */ + "/com/example/TestObject", /* object path */ + "com.example.Frob", /* interface name */ + "EmitSignal", /* method name */ + g_variant_new_parsed ("('hello', @o '/com/example/TestObject')"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) async_result_cb, + &data); + while (data.async_result == NULL || data.signal_count < 1) + g_main_context_iteration (thread_context, TRUE); - g_main_loop_run (thread_loop); - g_assert (data.signal_count == 1); + result_variant = g_dbus_connection_call_finish (c, data.async_result, &error); + g_assert_no_error (error); + g_assert_nonnull (result_variant); + g_clear_pointer (&result_variant, g_variant_unref); + g_clear_object (&data.async_result); - g_object_unref (priv_c); + g_assert_cmpuint (data.signal_count, ==, 1); g_dbus_connection_signal_unsubscribe (c, subscription_id); + subscription_id = 0; + + while (!data.unsubscribe_complete) + g_main_context_iteration (thread_context, TRUE); + g_assert_true (data.unsubscribe_complete); /* ---------------------------------------------------------------------------------------------------- */ g_main_context_pop_thread_default (thread_context); - g_main_loop_unref (thread_loop); g_main_context_unref (thread_context); return NULL; @@ -229,6 +300,8 @@ test_delivery_in_thread (void) NULL); g_thread_join (thread); + + assert_connection_has_one_ref (c, NULL); } /* ---------------------------------------------------------------------------------------------------- */ @@ -257,11 +330,11 @@ sleep_cb (GDBusProxy *proxy, res, &error); g_assert_no_error (error); - g_assert (result != NULL); + g_assert_nonnull (result); g_assert_cmpstr (g_variant_get_type_string (result), ==, "()"); g_variant_unref (result); - g_assert (data->thread == g_thread_self ()); + g_assert_true (data->thread == g_thread_self ()); g_main_loop_quit (data->thread_loop); @@ -317,7 +390,7 @@ test_sleep_in_thread_func (gpointer _data) g_printerr ("S"); //g_debug ("done invoking sync (%p)", g_thread_self ()); g_assert_no_error (error); - g_assert (result != NULL); + g_assert_nonnull (result); g_assert_cmpstr (g_variant_get_type_string (result), ==, "()"); g_variant_unref (result); } @@ -333,7 +406,7 @@ test_sleep_in_thread_func (gpointer _data) static void test_method_calls_on_proxy (GDBusProxy *proxy) { - guint n; + guint n, divisor; /* * Check that multiple threads can do calls without interferring with @@ -352,6 +425,11 @@ test_method_calls_on_proxy (GDBusProxy *proxy) * again with sync calls */ + if (g_test_thorough ()) + divisor = 1; + else + divisor = 10; + for (n = 0; n < 2; n++) { gboolean do_async; @@ -370,7 +448,7 @@ test_method_calls_on_proxy (GDBusProxy *proxy) data1.proxy = proxy; data1.msec = 40; - data1.num = 100; + data1.num = 100 / divisor; data1.async = do_async; thread1 = g_thread_new ("sleep", test_sleep_in_thread_func, @@ -378,7 +456,7 @@ test_method_calls_on_proxy (GDBusProxy *proxy) data2.proxy = proxy; data2.msec = 20; - data2.num = 200; + data2.num = 200 / divisor; data2.async = do_async; thread2 = g_thread_new ("sleep2", test_sleep_in_thread_func, @@ -386,7 +464,7 @@ test_method_calls_on_proxy (GDBusProxy *proxy) data3.proxy = proxy; data3.msec = 100; - data3.num = 40; + data3.num = 40 / divisor; data3.async = do_async; thread3 = g_thread_new ("sleep3", test_sleep_in_thread_func, @@ -403,8 +481,8 @@ test_method_calls_on_proxy (GDBusProxy *proxy) //g_debug ("Elapsed time for %s = %d msec", n == 0 ? "async" : "sync", elapsed_msec); /* elapsed_msec should be 4000 msec +/- change for overhead/inaccuracy */ - g_assert_cmpint (elapsed_msec, >=, 3950); - g_assert_cmpint (elapsed_msec, <, 30000); + g_assert_cmpint (elapsed_msec, >=, 3950 / divisor); + g_assert_cmpint (elapsed_msec, <, 30000 / divisor); if (g_test_verbose ()) g_printerr (" "); @@ -441,6 +519,8 @@ test_method_calls_in_thread (void) if (g_test_verbose ()) g_printerr ("\n"); + + assert_connection_has_one_ref (c, NULL); } #define SLEEP_MIN_USEC 1 @@ -457,8 +537,8 @@ ensure_connection_works (GDBusConnection *conn) "/org/freedesktop/DBus", "org.freedesktop.DBus", "GetId", NULL, NULL, 0, -1, NULL, &error); g_assert_no_error (error); - g_assert (v != NULL); - g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE ("(s)"))); + g_assert_nonnull (v); + g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE ("(s)"))); g_variant_unref (v); } @@ -500,29 +580,16 @@ test_threaded_singleton (void) if (g_test_thorough ()) n = 100000; else - n = 5000; + n = 1000; for (i = 0; i < n; i++) { GThread *thread; - guint j; guint unref_delay, get_delay; GDBusConnection *new_conn; /* We want to be the last ref, so let it finish setting up */ - for (j = 0; j < 100; j++) - { - guint r = g_atomic_int_get (&G_OBJECT (c)->ref_count); - - if (r == 1) - break; - - g_debug ("run %u: refcount is %u, sleeping", i, r); - g_usleep (1000); - } - - if (j == 100) - g_error ("connection had too many refs"); + assert_connection_has_one_ref (c, NULL); if (g_test_verbose () && (i % (n/50)) == 0) g_printerr ("%u%%\n", ((i * 100) / n)); @@ -586,16 +653,16 @@ main (int argc, /* this is safe; testserver will exit once the bus goes away */ path = g_test_build_filename (G_TEST_BUILT, "gdbus-testserver", NULL); - g_assert (g_spawn_command_line_async (path, NULL)); + g_assert_true (g_spawn_command_line_async (path, NULL)); g_free (path); - ensure_gdbus_testserver_up (); - /* Create the connection in the main thread */ error = NULL; c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); g_assert_no_error (error); - g_assert (c != NULL); + g_assert_nonnull (c); + + ensure_gdbus_testserver_up (c, NULL); g_test_add_func ("/gdbus/delivery-in-thread", test_delivery_in_thread); g_test_add_func ("/gdbus/method-calls-in-thread", test_method_calls_in_thread); diff --git a/gio/tests/glistmodel.c b/gio/tests/glistmodel.c index 562037f..5ecf45f 100644 --- a/gio/tests/glistmodel.c +++ b/gio/tests/glistmodel.c @@ -805,6 +805,72 @@ test_store_past_end (void) g_object_unref (store); } +static gboolean +list_model_casecmp_action_by_name (gconstpointer a, + gconstpointer b) +{ + return g_ascii_strcasecmp (g_action_get_name (G_ACTION (a)), + g_action_get_name (G_ACTION (b))) == 0; +} + +/* Test if find() and find_with_equal_func() works */ +static void +test_store_find (void) +{ + GListStore *store; + guint position = 100; + const gchar *item_strs[4] = { "aaa", "bbb", "xxx", "ccc" }; + GSimpleAction *items[4] = { NULL, }; + GSimpleAction *other_item; + guint i; + + store = g_list_store_new (G_TYPE_SIMPLE_ACTION); + + for (i = 0; i < G_N_ELEMENTS (item_strs); i++) + items[i] = g_simple_action_new (item_strs[i], NULL); + + /* Shouldn't crash on an empty list, or change the position pointer */ + g_assert_false (g_list_store_find (store, items[0], NULL)); + g_assert_false (g_list_store_find (store, items[0], &position)); + g_assert_cmpint (position, ==, 100); + + for (i = 0; i < G_N_ELEMENTS (item_strs); i++) + g_list_store_append (store, items[i]); + + /* Check whether it could still find the the elements */ + for (i = 0; i < G_N_ELEMENTS (item_strs); i++) + { + g_assert_true (g_list_store_find (store, items[i], &position)); + g_assert_cmpint (position, ==, i); + /* Shouldn't try to write to position pointer if NULL given */ + g_assert_true (g_list_store_find (store, items[i], NULL)); + } + + /* try to find element not part of the list */ + other_item = g_simple_action_new ("111", NULL); + g_assert_false (g_list_store_find (store, other_item, NULL)); + g_clear_object (&other_item); + + /* Re-add item; find() should only return the first position */ + g_list_store_append (store, items[0]); + g_assert_true (g_list_store_find (store, items[0], &position)); + g_assert_cmpint (position, ==, 0); + + /* try to find element which should only work with custom equality check */ + other_item = g_simple_action_new ("XXX", NULL); + g_assert_false (g_list_store_find (store, other_item, NULL)); + g_assert_true (g_list_store_find_with_equal_func (store, + other_item, + list_model_casecmp_action_by_name, + &position)); + g_assert_cmpint (position, ==, 2); + g_clear_object (&other_item); + + for (i = 0; i < G_N_ELEMENTS (item_strs); i++) + g_clear_object(&items[i]); + g_clear_object (&store); +} + int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); @@ -837,6 +903,7 @@ int main (int argc, char *argv[]) g_test_add_func ("/glistmodel/store/items-changed", test_store_signal_items_changed); g_test_add_func ("/glistmodel/store/past-end", test_store_past_end); + g_test_add_func ("/glistmodel/store/find", test_store_find); return g_test_run (); } diff --git a/gio/tests/live-g-file.c b/gio/tests/live-g-file.c index 240fa8b..2ede210 100644 --- a/gio/tests/live-g-file.c +++ b/gio/tests/live-g-file.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -115,6 +116,65 @@ static gboolean write_test; static gboolean verbose; static gboolean posix_compat; +#ifdef G_OS_UNIX +/* + * check_cap_dac_override: + * @tmpdir: A temporary directory in which we can create and delete files + * + * Check whether the current process can bypass DAC permissions. + * + * Traditionally, "privileged" processes (those with effective uid 0) + * could do this (and bypass many other checks), and "unprivileged" + * processes could not. + * + * In Linux, the special powers of euid 0 are divided into many + * capabilities: see `capabilities(7)`. The one we are interested in + * here is `CAP_DAC_OVERRIDE`. + * + * We do this generically instead of actually looking at the capability + * bits, so that the right thing will happen on non-Linux Unix + * implementations, in particular if they have something equivalent to + * but not identical to Linux permissions. + * + * Returns: %TRUE if we have Linux `CAP_DAC_OVERRIDE` or equivalent + * privileges + */ +static gboolean +check_cap_dac_override (const char *tmpdir) +{ + gchar *dac_denies_write; + gchar *inside; + gboolean have_cap; + + dac_denies_write = g_build_filename (tmpdir, "dac-denies-write", NULL); + inside = g_build_filename (dac_denies_write, "inside", NULL); + + g_assert_cmpint (mkdir (dac_denies_write, S_IRWXU) == 0 ? 0 : errno, ==, 0); + g_assert_cmpint (chmod (dac_denies_write, 0) == 0 ? 0 : errno, ==, 0); + + if (mkdir (inside, S_IRWXU) == 0) + { + g_test_message ("Looks like we have CAP_DAC_OVERRIDE or equivalent"); + g_assert_cmpint (rmdir (inside) == 0 ? 0 : errno, ==, 0); + have_cap = TRUE; + } + else + { + int saved_errno = errno; + + g_test_message ("We do not have CAP_DAC_OVERRIDE or equivalent"); + g_assert_cmpint (saved_errno, ==, EACCES); + have_cap = FALSE; + } + + g_assert_cmpint (chmod (dac_denies_write, S_IRWXU) == 0 ? 0 : errno, ==, 0); + g_assert_cmpint (rmdir (dac_denies_write) == 0 ? 0 : errno, ==, 0); + g_free (dac_denies_write); + g_free (inside); + return have_cap; +} +#endif + #ifdef G_HAVE_ISO_VARARGS #define log(...) if (verbose) g_printerr (__VA_ARGS__) #elif defined(G_HAVE_GNUC_VARARGS) @@ -138,12 +198,12 @@ create_empty_file (GFile * parent, const char *filename, GFileOutputStream *outs; child = g_file_get_child (parent, filename); - g_assert (child != NULL); + g_assert_nonnull (child); error = NULL; outs = g_file_replace (child, NULL, FALSE, create_flags, NULL, &error); g_assert_no_error (error); - g_assert (outs != NULL); + g_assert_nonnull (outs); error = NULL; g_output_stream_close (G_OUTPUT_STREAM (outs), NULL, &error); g_object_unref (outs); @@ -158,10 +218,10 @@ create_empty_dir (GFile * parent, const char *filename) GError *error; child = g_file_get_child (parent, filename); - g_assert (child != NULL); + g_assert_nonnull (child); error = NULL; res = g_file_make_directory (child, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); return child; } @@ -174,10 +234,10 @@ create_symlink (GFile * parent, const char *filename, const char *points_to) GError *error; child = g_file_get_child (parent, filename); - g_assert (child != NULL); + g_assert_nonnull (child); error = NULL; res = g_file_make_symbolic_link (child, points_to, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); return child; } @@ -188,22 +248,22 @@ test_create_structure (gconstpointer test_data) GFile *root; GFile *child; gboolean res; - GError *error; + GError *error = NULL; GFileOutputStream *outs; GDataOutputStream *outds; guint i; struct StructureItem item; - g_assert (test_data != NULL); + g_assert_nonnull (test_data); log ("\n Going to create testing structure in '%s'...\n", (char *) test_data); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); /* create root directory */ - res = g_file_make_directory (root, NULL, NULL); - /* don't care about errors here */ + g_file_make_directory (root, NULL, &error); + g_assert_no_error (error); /* create any other items */ for (i = 0; i < G_N_ELEMENTS (sample_struct); i++) @@ -237,17 +297,16 @@ test_create_structure (gconstpointer test_data) default: break; } - g_assert (child != NULL); + g_assert_nonnull (child); if ((item.mode > 0) && (posix_compat)) { - error = NULL; res = g_file_set_attribute_uint32 (child, G_FILE_ATTRIBUTE_UNIX_MODE, item.mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); } @@ -275,24 +334,22 @@ test_create_structure (gconstpointer test_data) /* create a pattern file */ log (" Creating pattern file..."); child = g_file_get_child (root, "pattern_file"); - g_assert (child != NULL); + g_assert_nonnull (child); - error = NULL; outs = g_file_replace (child, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error); g_assert_no_error (error); - g_assert (outs != NULL); + g_assert_nonnull (outs); outds = g_data_output_stream_new (G_OUTPUT_STREAM (outs)); - g_assert (outds != NULL); + g_assert_nonnull (outds); for (i = 0; i < PATTERN_FILE_SIZE; i++) { - error = NULL; - res = g_data_output_stream_put_byte (outds, i % 256, NULL, &error); + g_data_output_stream_put_byte (outds, i % 256, NULL, &error); g_assert_no_error (error); } - error = NULL; - res = g_output_stream_close (G_OUTPUT_STREAM (outs), NULL, &error); + + g_output_stream_close (G_OUTPUT_STREAM (outs), NULL, &error); g_assert_no_error (error); g_object_unref (outds); g_object_unref (outs); @@ -312,7 +369,7 @@ file_exists (GFile * parent, const char *filename, gboolean * result) *result = FALSE; child = g_file_get_child (parent, filename); - g_assert (child != NULL); + g_assert_nonnull (child); res = g_file_query_exists (child, NULL); if (result) *result = res; @@ -334,7 +391,7 @@ test_attributes (struct StructureItem item, GFileInfo * info) /* standard::type */ has_attr = g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_TYPE); - g_assert_cmpint (has_attr, ==, TRUE); + g_assert_true (has_attr); ftype = g_file_info_get_file_type (info); g_assert_cmpint (ftype, !=, G_FILE_TYPE_UNKNOWN); g_assert_cmpint (ftype, ==, item.file_type); @@ -354,7 +411,7 @@ test_attributes (struct StructureItem item, GFileInfo * info) can_read = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ); - g_assert_cmpint (can_read, ==, TRUE); + g_assert_true (can_read); } /* access::can-write */ @@ -363,25 +420,25 @@ test_attributes (struct StructureItem item, GFileInfo * info) can_write = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); - g_assert_cmpint (can_write, ==, TRUE); + g_assert_true (can_write); } /* standard::name */ name = g_file_info_get_name (info); - g_assert (name != NULL); + g_assert_nonnull (name); /* standard::display-name */ display_name = g_file_info_get_display_name (info); - g_assert (display_name != NULL); + g_assert_nonnull (display_name); utf8_valid = g_utf8_validate (display_name, -1, NULL); - g_assert_cmpint (utf8_valid, ==, TRUE); + g_assert_true (utf8_valid); /* standard::edit-name */ edit_name = g_file_info_get_edit_name (info); if (edit_name) { utf8_valid = g_utf8_validate (edit_name, -1, NULL); - g_assert_cmpint (utf8_valid, ==, TRUE); + g_assert_true (utf8_valid); } /* standard::copy-name */ @@ -391,7 +448,7 @@ test_attributes (struct StructureItem item, GFileInfo * info) if (copy_name) { utf8_valid = g_utf8_validate (copy_name, -1, NULL); - g_assert_cmpint (utf8_valid, ==, TRUE); + g_assert_true (utf8_valid); } /* standard::is-symlink */ @@ -415,7 +472,7 @@ test_attributes (struct StructureItem item, GFileInfo * info) is_hidden = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN); - g_assert_cmpint (is_hidden, ==, TRUE); + g_assert_true (is_hidden); } /* unix::is-mountpoint */ @@ -443,14 +500,13 @@ test_initial_structure (gconstpointer test_data) gssize read, total_read; struct StructureItem item; - - g_assert (test_data != NULL); + g_assert_nonnull (test_data); log ("\n Testing sample structure in '%s'...\n", (char *) test_data); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); res = g_file_query_exists (root, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); /* test the structure */ for (i = 0; i < G_N_ELEMENTS (sample_struct); i++) @@ -463,15 +519,15 @@ test_initial_structure (gconstpointer test_data) log (" Testing file '%s'...\n", item.filename); child = file_exists (root, item.filename, &res); - g_assert (child != NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_nonnull (child); + g_assert_true (res); error = NULL; info = g_file_query_info (child, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error); g_assert_no_error (error); - g_assert (info != NULL); + g_assert_nonnull (info); test_attributes (item, info); @@ -482,22 +538,22 @@ test_initial_structure (gconstpointer test_data) /* read and test the pattern file */ log (" Testing pattern file...\n"); child = file_exists (root, "pattern_file", &res); - g_assert (child != NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_nonnull (child); + g_assert_true (res); error = NULL; info = g_file_query_info (child, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error); g_assert_no_error (error); - g_assert (info != NULL); + g_assert_nonnull (info); size = g_file_info_get_size (info); g_assert_cmpint (size, ==, PATTERN_FILE_SIZE); g_object_unref (info); error = NULL; ins = g_file_read (child, NULL, &error); - g_assert (ins != NULL); + g_assert_nonnull (ins); g_assert_no_error (error); buffer = g_malloc (PATTERN_FILE_SIZE); @@ -519,7 +575,7 @@ test_initial_structure (gconstpointer test_data) error = NULL; res = g_input_stream_close (G_INPUT_STREAM (ins), NULL, &error); g_assert_no_error (error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); for (i = 0; i < PATTERN_FILE_SIZE; i++) g_assert_cmpint (*(buffer + i), ==, i % 256); @@ -542,26 +598,26 @@ traverse_recurse_dirs (GFile * parent, GFile * root) guint i; gboolean found; - g_assert (root != NULL); + g_assert_nonnull (root); error = NULL; enumerator = g_file_enumerate_children (parent, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error); - g_assert (enumerator != NULL); + g_assert_nonnull (enumerator); g_assert_no_error (error); - g_assert (g_file_enumerator_get_container (enumerator) == parent); + g_assert_true (g_file_enumerator_get_container (enumerator) == parent); error = NULL; info = g_file_enumerator_next_file (enumerator, NULL, &error); while ((info) && (!error)) { descend = g_file_enumerator_get_child (enumerator, info); - g_assert (descend != NULL); + g_assert_nonnull (descend); relative_path = g_file_get_relative_path (root, descend); - g_assert (relative_path != NULL); + g_assert_nonnull (relative_path); found = FALSE; for (i = 0; i < G_N_ELEMENTS (sample_struct); i++) @@ -575,7 +631,7 @@ traverse_recurse_dirs (GFile * parent, GFile * root) break; } } - g_assert_cmpint (found, ==, TRUE); + g_assert_true (found); log (" Found file %s, relative to root: %s\n", g_file_info_get_display_name (info), relative_path); @@ -594,9 +650,9 @@ traverse_recurse_dirs (GFile * parent, GFile * root) error = NULL; res = g_file_enumerator_close (enumerator, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); - g_assert (g_file_enumerator_is_closed (enumerator)); + g_assert_true (g_file_enumerator_is_closed (enumerator)); g_object_unref (enumerator); } @@ -607,14 +663,14 @@ test_traverse_structure (gconstpointer test_data) GFile *root; gboolean res; - g_assert (test_data != NULL); + g_assert_nonnull (test_data); log ("\n Traversing through the sample structure in '%s'...\n", (char *) test_data); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); res = g_file_query_exists (root, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); traverse_recurse_dirs (root, root); @@ -636,13 +692,13 @@ test_enumerate (gconstpointer test_data) struct StructureItem item; - g_assert (test_data != NULL); + g_assert_nonnull (test_data); log ("\n Test enumerate '%s'...\n", (char *) test_data); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); res = g_file_query_exists (root, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); for (i = 0; i < G_N_ELEMENTS (sample_struct); i++) @@ -659,7 +715,7 @@ test_enumerate (gconstpointer test_data) { log (" Testing file '%s'\n", item.filename); child = g_file_get_child (root, item.filename); - g_assert (child != NULL); + g_assert_nonnull (child); error = NULL; enumerator = g_file_enumerate_children (child, "*", @@ -668,21 +724,21 @@ test_enumerate (gconstpointer test_data) if ((item.extra_flags & TEST_NOT_EXISTS) == TEST_NOT_EXISTS) { - g_assert (enumerator == NULL); + g_assert_null (enumerator); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); } if ((item.extra_flags & TEST_ENUMERATE_FILE) == TEST_ENUMERATE_FILE) { - g_assert (enumerator == NULL); + g_assert_null (enumerator); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY); } if ((item.extra_flags & TEST_NO_ACCESS) == TEST_NO_ACCESS) { - g_assert (enumerator != NULL); + g_assert_nonnull (enumerator); error = NULL; info = g_file_enumerator_next_file (enumerator, NULL, &error); - g_assert (info == NULL); + g_assert_null (info); g_assert_no_error (error); /* no items should be found, no error should be logged */ } @@ -694,7 +750,7 @@ test_enumerate (gconstpointer test_data) { error = NULL; res = g_file_enumerator_close (enumerator, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); g_object_unref (enumerator); @@ -712,15 +768,18 @@ do_copy_move (GFile * root, struct StructureItem item, const char *target_dir, GFile *dst_dir, *src_file, *dst_file; gboolean res; GError *error; +#ifdef G_OS_UNIX + gboolean have_cap_dac_override = check_cap_dac_override (g_file_peek_path (root)); +#endif log (" do_copy_move: '%s' --> '%s'\n", item.filename, target_dir); dst_dir = g_file_get_child (root, target_dir); - g_assert (dst_dir != NULL); + g_assert_nonnull (dst_dir); src_file = g_file_get_child (root, item.filename); - g_assert (src_file != NULL); + g_assert_nonnull (src_file); dst_file = g_file_get_child (dst_dir, item.filename); - g_assert (dst_file != NULL); + g_assert_nonnull (dst_file); error = NULL; if ((item.extra_flags & TEST_COPY) == TEST_COPY) @@ -743,28 +802,28 @@ do_copy_move (GFile * root, struct StructureItem item, const char *target_dir, if (((item.extra_flags & TEST_NOT_EXISTS) != TEST_NOT_EXISTS) && (extra_flags == TEST_ALREADY_EXISTS)) { - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS); } /* target file is a file, overwrite is not set */ else if (((item.extra_flags & TEST_NOT_EXISTS) != TEST_NOT_EXISTS) && (extra_flags == TEST_TARGET_IS_FILE)) { - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY); } /* source file is directory */ else if ((item.extra_flags & TEST_COPY_ERROR_RECURSE) == TEST_COPY_ERROR_RECURSE) { - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE); } /* source or target path doesn't exist */ else if (((item.extra_flags & TEST_NOT_EXISTS) == TEST_NOT_EXISTS) || (extra_flags == TEST_NOT_EXISTS)) { - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); } /* source or target path permission denied */ @@ -772,21 +831,26 @@ do_copy_move (GFile * root, struct StructureItem item, const char *target_dir, (extra_flags == TEST_NO_ACCESS)) { /* This works for root, see bug #552912 */ - if (test_suite && getuid () == 0) +#ifdef G_OS_UNIX + if (have_cap_dac_override) { - g_assert_cmpint (res, ==, TRUE); + g_test_message ("Unable to exercise g_file_copy() or g_file_move() " + "failing with EACCES: we probably have " + "CAP_DAC_OVERRIDE"); + g_assert_true (res); g_assert_no_error (error); } else +#endif { - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED); } } /* no error should be found, all exceptions defined above */ else { - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); } @@ -809,11 +873,11 @@ test_copy_move (gconstpointer test_data) log ("\n"); - g_assert (test_data != NULL); + g_assert_nonnull (test_data); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); res = g_file_query_exists (root, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); for (i = 0; i < G_N_ELEMENTS (sample_struct); i++) @@ -899,13 +963,13 @@ test_create (gconstpointer test_data) struct StructureItem item; GFileOutputStream *os; - g_assert (test_data != NULL); + g_assert_nonnull (test_data); log ("\n"); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); res = g_file_query_exists (root, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); for (i = 0; i < G_N_ELEMENTS (sample_struct); i++) { @@ -918,7 +982,7 @@ test_create (gconstpointer test_data) log (" test_create: '%s'\n", item.filename); child = g_file_get_child (root, item.filename); - g_assert (child != NULL); + g_assert_nonnull (child); error = NULL; os = NULL; @@ -938,12 +1002,12 @@ test_create (gconstpointer test_data) if (((item.extra_flags & TEST_NOT_EXISTS) == 0) && ((item.extra_flags & TEST_CREATE) == TEST_CREATE)) { - g_assert (os == NULL); + g_assert_null (os); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS); } else if (item.file_type == G_FILE_TYPE_DIRECTORY) { - g_assert (os == NULL); + g_assert_null (os); if ((item.extra_flags & TEST_CREATE) == TEST_CREATE) g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS); else @@ -951,7 +1015,7 @@ test_create (gconstpointer test_data) } else { - g_assert (os != NULL); + g_assert_nonnull (os); g_assert_no_error (error); } @@ -966,7 +1030,7 @@ test_create (gconstpointer test_data) if (error) log (" g_output_stream_close: error %d = %s\n", error->code, error->message); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); g_object_unref (os); } @@ -986,13 +1050,13 @@ test_open (gconstpointer test_data) struct StructureItem item; GFileInputStream *input_stream; - g_assert (test_data != NULL); + g_assert_nonnull (test_data); log ("\n"); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); res = g_file_query_exists (root, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); for (i = 0; i < G_N_ELEMENTS (sample_struct); i++) { @@ -1006,7 +1070,7 @@ test_open (gconstpointer test_data) log (" test_open: '%s'\n", item.filename); child = g_file_get_child (root, item.filename); - g_assert (child != NULL); + g_assert_nonnull (child); error = NULL; input_stream = g_file_read (child, NULL, &error); @@ -1014,17 +1078,17 @@ test_open (gconstpointer test_data) ((item.extra_flags & TEST_INVALID_SYMLINK) == TEST_INVALID_SYMLINK)) { - g_assert (input_stream == NULL); + g_assert_null (input_stream); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); } else if (item.file_type == G_FILE_TYPE_DIRECTORY) { - g_assert (input_stream == NULL); + g_assert_null (input_stream); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY); } else { - g_assert (input_stream != NULL); + g_assert_nonnull (input_stream); g_assert_no_error (error); } @@ -1037,7 +1101,7 @@ test_open (gconstpointer test_data) res = g_input_stream_close (G_INPUT_STREAM (input_stream), NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); g_object_unref (input_stream); } @@ -1058,13 +1122,13 @@ test_delete (gconstpointer test_data) struct StructureItem item; gchar *path; - g_assert (test_data != NULL); + g_assert_nonnull (test_data); log ("\n"); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); res = g_file_query_exists (root, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); for (i = 0; i < G_N_ELEMENTS (sample_struct); i++) { @@ -1077,7 +1141,7 @@ test_delete (gconstpointer test_data) ((item.extra_flags & TEST_DELETE_TRASH) == TEST_DELETE_TRASH)) { child = file_exists (root, item.filename, &res); - g_assert (child != NULL); + g_assert_nonnull (child); /* we don't care about result here */ path = g_file_get_path (child); @@ -1093,17 +1157,17 @@ test_delete (gconstpointer test_data) if ((item.extra_flags & TEST_DELETE_NON_EMPTY) == TEST_DELETE_NON_EMPTY) { - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY); } if ((item.extra_flags & TEST_DELETE_FAILURE) == TEST_DELETE_FAILURE) { - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); } if ((item.extra_flags & TEST_NOT_EXISTS) == TEST_NOT_EXISTS) { - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); } @@ -1125,13 +1189,16 @@ test_make_directory_with_parents (gconstpointer test_data) GFile *root, *child, *grandchild, *greatgrandchild; gboolean res; GError *error = NULL; +#ifdef G_OS_UNIX + gboolean have_cap_dac_override = check_cap_dac_override (test_data); +#endif - g_assert (test_data != NULL); + g_assert_nonnull (test_data); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); res = g_file_query_exists (root, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); child = g_file_get_child (root, "a"); grandchild = g_file_get_child (child, "b"); @@ -1141,27 +1208,27 @@ test_make_directory_with_parents (gconstpointer test_data) * depth 1, 2, or 3 */ res = g_file_make_directory_with_parents (child, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); res = g_file_query_exists (child, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_file_delete (child, NULL, NULL); res = g_file_make_directory_with_parents (grandchild, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); res = g_file_query_exists (grandchild, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_file_delete (grandchild, NULL, NULL); g_file_delete (child, NULL, NULL); res = g_file_make_directory_with_parents (greatgrandchild, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); res = g_file_query_exists (greatgrandchild, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_file_delete (greatgrandchild, NULL, NULL); g_file_delete (grandchild, NULL, NULL); @@ -1175,28 +1242,35 @@ test_make_directory_with_parents (gconstpointer test_data) if (!posix_compat) goto out; -#ifndef G_PLATFORM_WIN32 - if (getuid() == 0) /* permissions are ignored for root */ - goto out; +#ifdef G_OS_UNIX + /* Permissions are ignored if we have CAP_DAC_OVERRIDE or equivalent, + * and in particular if we're root */ + if (have_cap_dac_override) + { + g_test_skip ("Unable to exercise g_file_make_directory_with_parents " + "failing with EACCES: we probably have " + "CAP_DAC_OVERRIDE"); + goto out; + } #endif g_file_make_directory (child, NULL, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); res = g_file_set_attribute_uint32 (child, G_FILE_ATTRIBUTE_UNIX_MODE, S_IRUSR + S_IXUSR, /* -r-x------ */ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); res = g_file_make_directory_with_parents (grandchild, NULL, &error); - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED); g_clear_error (&error); res = g_file_make_directory_with_parents (greatgrandchild, NULL, &error); - g_assert_cmpint (res, ==, FALSE); + g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED); g_clear_error (&error); @@ -1218,7 +1292,7 @@ cleanup_dir_recurse (GFile *parent, GFile *root) GFile *descend; char *relative_path; - g_assert (root != NULL); + g_assert_nonnull (root); enumerator = g_file_enumerate_children (parent, "*", @@ -1232,9 +1306,9 @@ cleanup_dir_recurse (GFile *parent, GFile *root) while ((info) && (!error)) { descend = g_file_enumerator_get_child (enumerator, info); - g_assert (descend != NULL); + g_assert_nonnull (descend); relative_path = g_file_get_relative_path (root, descend); - g_assert (relative_path != NULL); + g_assert_nonnull (relative_path); g_free (relative_path); log (" deleting '%s'\n", g_file_info_get_display_name (info)); @@ -1244,7 +1318,7 @@ cleanup_dir_recurse (GFile *parent, GFile *root) error = NULL; res = g_file_delete (descend, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_object_unref (descend); error = NULL; @@ -1256,7 +1330,7 @@ cleanup_dir_recurse (GFile *parent, GFile *root) error = NULL; res = g_file_enumerator_close (enumerator, NULL, &error); - g_assert_cmpint (res, ==, TRUE); + g_assert_true (res); g_assert_no_error (error); g_object_unref (enumerator); @@ -1267,12 +1341,12 @@ prep_clean_structure (gconstpointer test_data) { GFile *root; - g_assert (test_data != NULL); + g_assert_nonnull (test_data); log ("\n Cleaning target testing structure in '%s'...\n", (char *) test_data); root = g_file_new_for_commandline_arg ((char *) test_data); - g_assert (root != NULL); + g_assert_nonnull (root); cleanup_dir_recurse (root, root); @@ -1347,7 +1421,7 @@ main (int argc, char *argv[]) } g_option_context_free (context); - + /* Write test - clean target directory first */ /* this can be also considered as a test - enumerate + delete */ if (write_test || only_create_struct) diff --git a/gio/tests/memory-monitor-dbus.py.in b/gio/tests/memory-monitor-dbus.py.in new file mode 100755 index 0000000..cd16cf4 --- /dev/null +++ b/gio/tests/memory-monitor-dbus.py.in @@ -0,0 +1,111 @@ +#!/usr/bin/python3 + +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) any +# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text +# of the license. + +__author__ = 'Bastien Nocera' +__email__ = 'hadess@hadess.net' +__copyright__ = '(c) 2019 Red Hat Inc.' +__license__ = 'LGPL 3+' + +import unittest +import sys +import subprocess +import fcntl +import os +import time + +import taptestrunner + +try: + # Do all non-standard imports here so we can skip the tests if any + # needed packages are not available. + import dbus + import dbus.mainloop.glib + import dbusmock + from gi.repository import GLib + from gi.repository import Gio + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + # XDG_DESKTOP_PORTAL_PATH = os.path.expanduser("~/.cache/jhbuild/build/xdg-desktop-portal/xdg-desktop-portal") + XDG_DESKTOP_PORTAL_PATH = "@libexecdir@/xdg-desktop-portal" + + class TestLowMemoryMonitor(dbusmock.DBusTestCase): + '''Test GMemoryMonitorDBus''' + + @classmethod + def setUpClass(klass): + klass.start_system_bus() + klass.dbus_con = klass.get_dbus(True) + + def setUp(self): + try: + Gio.MemoryMonitor + except AttributeError: + raise unittest.SkipTest('Low memory monitor not in ' + 'introspection data. Requires ' + 'GObject-Introspection ≥ 1.63.2') + try: + (self.p_mock, self.obj_lmm) = self.spawn_server_template( + 'low_memory_monitor', {}, stdout=subprocess.PIPE) + except ModuleNotFoundError: + raise unittest.SkipTest("Low memory monitor dbusmock template not " + "found. Requires dbusmock ≥ 0.18.4.") + # set log to nonblocking + flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) + fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) + self.last_warning = -1 + self.dbusmock = dbus.Interface(self.obj_lmm, dbusmock.MOCK_IFACE) + self.memory_monitor = Gio.MemoryMonitor.dup_default() + self.memory_monitor.connect("low-memory-warning", self.memory_warning_cb) + self.mainloop = GLib.MainLoop() + self.main_context = self.mainloop.get_context() + + def tearDown(self): + self.p_mock.terminate() + self.p_mock.wait() + + def memory_warning_cb(self, monitor, level): + self.last_warning = level + self.main_context.wakeup() + + def test_low_memory_warning_signal(self): + '''LowMemoryWarning signal''' + + # Wait 2 seconds + timeout = 2 + while timeout > 0: + time.sleep(0.5) + timeout -= 0.5 + self.main_context.iteration(False) + + self.dbusmock.EmitWarning(100) + # Wait 2 seconds or until warning + timeout = 2 + while timeout > 0 and self.last_warning != 100: + time.sleep(0.5) + timeout -= 0.5 + self.main_context.iteration(False) + self.assertEqual(self.last_warning, 100) + + self.dbusmock.EmitWarning(255) + # Wait 2 seconds or until warning + timeout = 2 + while timeout > 0 and self.last_warning != 255: + time.sleep(0.5) + timeout -= 0.5 + self.main_context.iteration(False) + self.assertEqual(self.last_warning, 255) + +except ImportError as e: + @unittest.skip("Cannot import %s" % e.name) + class TestLowMemoryMonitor(unittest.TestCase): + def test_low_memory_warning_signal(self): + pass + +if __name__ == '__main__': + unittest.main(testRunner=taptestrunner.TAPTestRunner()) diff --git a/gio/tests/memory-monitor-portal.py.in b/gio/tests/memory-monitor-portal.py.in new file mode 100755 index 0000000..cb4a960 --- /dev/null +++ b/gio/tests/memory-monitor-portal.py.in @@ -0,0 +1,127 @@ +#!/usr/bin/python3 + +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) any +# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text +# of the license. + +__author__ = 'Bastien Nocera' +__email__ = 'hadess@hadess.net' +__copyright__ = '(c) 2019 Red Hat Inc.' +__license__ = 'LGPL 3+' + +import unittest +import sys +import subprocess +import fcntl +import os +import time + +import taptestrunner + +try: + # Do all non-standard imports here so we can skip the tests if any + # needed packages are not available. + import dbus + import dbus.mainloop.glib + import dbusmock + from gi.repository import GLib + from gi.repository import Gio + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + # XDG_DESKTOP_PORTAL_PATH = os.path.expanduser("~/.cache/jhbuild/build/xdg-desktop-portal/xdg-desktop-portal") + XDG_DESKTOP_PORTAL_PATH = "@libexecdir@/xdg-desktop-portal" + + class TestLowMemoryMonitorPortal(dbusmock.DBusTestCase): + '''Test GMemoryMonitorPortal''' + + @classmethod + def setUpClass(klass): + klass.start_system_bus() + klass.dbus_con = klass.get_dbus(True) + # Start session bus so that xdg-desktop-portal can run on it + klass.start_session_bus() + + def setUp(self): + try: + Gio.MemoryMonitor + except AttributeError: + raise unittest.SkipTest('Low memory monitor not in ' + 'introspection data. Requires ' + 'GObject-Introspection ≥ 1.63.2') + try: + (self.p_mock, self.obj_lmm) = self.spawn_server_template( + 'low_memory_monitor', {}, stdout=subprocess.PIPE) + except ModuleNotFoundError: + raise unittest.SkipTest("Low memory monitor dbusmock template not " + "found. Requires dbusmock ≥ 0.18.4.") + # set log to nonblocking + flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) + fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) + self.last_warning = -1 + self.dbusmock = dbus.Interface(self.obj_lmm, dbusmock.MOCK_IFACE) + try: + self.xdp = subprocess.Popen([XDG_DESKTOP_PORTAL_PATH]) + except FileNotFoundError: + raise unittest.SkipTest("xdg-desktop-portal not available") + + try: + self.wait_for_bus_object('org.freedesktop.portal.Desktop', + '/org/freedesktop/portal/desktop') + except: + raise + # subprocess.Popen(['gdbus', 'monitor', '--session', '--dest', 'org.freedesktop.portal.Desktop']) + + os.environ['GTK_USE_PORTAL'] = "1" + self.memory_monitor = Gio.MemoryMonitor.dup_default() + assert("GMemoryMonitorPortal" in str(self.memory_monitor)) + self.memory_monitor.connect("low-memory-warning", self.portal_memory_warning_cb) + self.mainloop = GLib.MainLoop() + self.main_context = self.mainloop.get_context() + + def tearDown(self): + self.p_mock.terminate() + self.p_mock.wait() + + def portal_memory_warning_cb(self, monitor, level): + self.last_warning = level + self.main_context.wakeup() + + def test_low_memory_warning_portal_signal(self): + '''LowMemoryWarning signal''' + + # Wait 2 seconds + timeout = 2 + while timeout > 0: + time.sleep(0.5) + timeout -= 0.5 + self.main_context.iteration(False) + + self.dbusmock.EmitWarning(100) + # Wait 2 seconds or until warning + timeout = 2 + while timeout > 0 and self.last_warning != 100: + time.sleep(0.5) + timeout -= 0.5 + self.main_context.iteration(False) + self.assertEqual(self.last_warning, 100) + + self.dbusmock.EmitWarning(255) + # Wait 2 seconds or until warning + timeout = 2 + while timeout > 0 and self.last_warning != 255: + time.sleep(0.5) + timeout -= 0.5 + self.main_context.iteration(False) + self.assertEqual(self.last_warning, 255) + +except ImportError as e: + @unittest.skip("Cannot import %s" % e.name) + class TestLowMemoryMonitorPortal(unittest.TestCase): + def test_low_memory_warning_portal_signal(self): + pass + +if __name__ == '__main__': + unittest.main(testRunner=taptestrunner.TAPTestRunner()) diff --git a/gio/tests/memory-monitor.c b/gio/tests/memory-monitor.c new file mode 100644 index 0000000..06eabef --- /dev/null +++ b/gio/tests/memory-monitor.c @@ -0,0 +1,88 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#include + +static const char * +get_level_string (GMemoryMonitorWarningLevel level) +{ + GEnumClass *eclass; + GEnumValue *value; + + eclass = G_ENUM_CLASS (g_type_class_peek (G_TYPE_MEMORY_MONITOR_WARNING_LEVEL)); + value = g_enum_get_value (eclass, level); + + if (value == NULL) + return "unknown"; + + return value->value_nick; +} + +static void +test_dup_default (void) +{ + GMemoryMonitor *monitor; + + monitor = g_memory_monitor_dup_default (); + g_assert_nonnull (monitor); + g_object_unref (monitor); +} + +static void +warning_cb (GMemoryMonitor *m, + GMemoryMonitorWarningLevel level) +{ + const char *str; + + str = get_level_string (level); + g_debug ("Warning level: %s (%d)", str , level); +} + +static void +do_watch_memory (void) +{ + GMemoryMonitor *m; + GMainLoop *loop; + + m = g_memory_monitor_dup_default (); + g_signal_connect (G_OBJECT (m), "low-memory-warning", + G_CALLBACK (warning_cb), NULL); + + loop = g_main_loop_new (NULL, TRUE); + g_main_loop_run (loop); +} + +int +main (int argc, char **argv) +{ + int ret; + + if (argc == 2 && !strcmp (argv[1], "--watch")) + { + do_watch_memory (); + return 0; + } + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/memory-monitor/default", test_dup_default); + + ret = g_test_run (); + + return ret; +} diff --git a/gio/tests/meson.build b/gio/tests/meson.build index 382dfcc..48891b0 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -7,7 +7,6 @@ common_gio_tests_deps = [ test_c_args = [ '-DG_LOG_DOMAIN="GLib-GIO"', - '-DTEST_SERVICES="@0@/gio/tests/services"'.format(meson.build_root()), '-DGLIB_MKENUMS="@0@"'.format(glib_mkenums), '-DGLIB_COMPILE_SCHEMAS="@0@"'.format(glib_compile_schemas.full_path()), '-UG_DISABLE_ASSERT', @@ -53,6 +52,7 @@ gio_tests = { 'inet-address' : {}, 'io-stream' : {}, 'memory-input-stream' : {}, + 'memory-monitor' : {}, 'memory-output-stream' : {}, 'mount-operation' : {}, 'network-address' : {'extra_sources': ['mock-resolver.c']}, @@ -79,6 +79,7 @@ gio_tests = { 'tls-interaction' : {'extra_sources' : ['gtesttlsbackend.c']}, 'tls-database' : {'extra_sources' : ['gtesttlsbackend.c']}, 'gdbus-address-get-session' : {}, + 'win32-appinfo' : {}, } test_extra_programs = { @@ -87,11 +88,14 @@ test_extra_programs = { 'gsubprocess-testprog' : {}, } +python_tests = [ + 'codegen.py', +] + test_env = environment() test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) test_env.set('GIO_MODULE_DIR', '') -test_env.set('GIO_LAUNCH_DESKTOP', meson.build_root() + '/gio/gio-launch-desktop') # Check for libdbus1 - Optional - is only used in the GDBus test cases # 1.2.14 required for dbus_message_set_serial @@ -115,7 +119,15 @@ if dbus1_dep.found() 'gdbus-serialization' : { 'extra_sources' : ['gdbus-tests.c'], 'dependencies' : [dbus1_dep], - } + }, + 'gdbus-server-auth' : { + 'dependencies' : [dbus1_dep], + }, + } +else + # We can build a cut-down version of this test without libdbus + gio_tests += { + 'gdbus-server-auth' : {}, } endif @@ -218,7 +230,23 @@ if host_machine.system() != 'windows' '--generate-docbook', 'gdbus-test-codegen-generated-doc', annotate_args, '@INPUT@']) - + # Generate gdbus-test-codegen-generated-min-required-2-64.{c,h} + gdbus_test_codegen_generated_min_required_2_64 = custom_target('gdbus-test-codegen-generated-min-required-2-64', + input : ['test-codegen.xml'], + output : ['gdbus-test-codegen-generated-min-required-2-64.h', + 'gdbus-test-codegen-generated-min-required-2-64.c'], + depend_files : gdbus_codegen_built_files, + command : [python, gdbus_codegen, + '--glib-min-required', '2.64', + '--interface-prefix', 'org.project.', + '--output-directory', '@OUTDIR@', + '--generate-c-code', 'gdbus-test-codegen-generated-min-required-2-64', + '--c-generate-object-manager', + '--c-generate-autocleanup', 'all', + '--c-namespace', 'Foo_iGen', + '--generate-docbook', 'gdbus-test-codegen-generated-doc', + annotate_args, + '@INPUT@']) gdbus_test_codegen_generated_interface_info = [ custom_target('gdbus-test-codegen-generated-interface-info-h', input : ['test-codegen.xml'], @@ -270,10 +298,11 @@ if host_machine.system() != 'windows' 'gdbus-proxy-well-known-name' : {'extra_sources' : extra_sources}, 'gdbus-test-codegen' : { 'extra_sources' : [extra_sources, gdbus_test_codegen_generated, gdbus_test_codegen_generated_interface_info], + 'c_args' : ['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32'], }, 'gdbus-threading' : { 'extra_sources' : extra_sources, - 'suite' : ['slow', 'flaky'], + 'suite' : ['slow'], }, 'gmenumodel' : { 'extra_sources' : extra_sources, @@ -288,6 +317,11 @@ if host_machine.system() != 'windows' 'c_args' : ['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_36', '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_36'], }, + 'gdbus-test-codegen-min-required-2-64' : { + 'source' : 'gdbus-test-codegen.c', + 'extra_sources' : [extra_sources, gdbus_test_codegen_generated_min_required_2_64, gdbus_test_codegen_generated_interface_info], + 'c_args' : ['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_64'], + }, 'gapplication' : {'extra_sources' : extra_sources}, } @@ -298,6 +332,24 @@ if host_machine.system() != 'windows' }, } endif + + fake_document_portal_generated = custom_target('fake-document-portal-generated', + input : ['../org.freedesktop.portal.Documents.xml'], + output : ['fake-document-portal-generated.h', + 'fake-document-portal-generated.c'], + depend_files : gdbus_codegen_built_files, + command : [python, gdbus_codegen, + '--interface-prefix', 'org.freedesktop.portal.', + '--output-directory', '@OUTDIR@', + '--generate-c-code', 'fake-document-portal-generated', + '--c-namespace', 'Fake', + '@INPUT@']) + + test_extra_programs += { + 'fake-document-portal' : { + 'extra_sources': fake_document_portal_generated, + }, + } endif # have_dbus_daemon # This test is currently unreliable @@ -378,6 +430,11 @@ test_extra_programs += { }, } +gdbus_example_objectmanager_sources = files( + 'gdbus-example-objectmanager-client.c', + 'gdbus-example-objectmanager-server.c', +) + if cc.get_id() != 'msvc' and cc.get_id() != 'clang-cl' test_extra_programs += { # These three are manual-run tests because they need a session bus but don't bring one up themselves @@ -444,6 +501,7 @@ if installed_tests_enabled 'appinfo-test-static.desktop', 'file.c', 'org.gtk.test.dbusappinfo.desktop', + 'org.gtk.test.dbusappinfo.flatpak.desktop', 'test1.overlay', install_dir : installed_tests_execdir, ) @@ -463,6 +521,32 @@ if installed_tests_enabled ) install_subdir('static-link', install_dir : installed_tests_execdir) install_data('static-link.py', install_dir : installed_tests_execdir) + + memory_monitor_tests = [ + 'memory-monitor-dbus', + 'memory-monitor-portal', + ] + + foreach memory_monitor_test : memory_monitor_tests + cdata = configuration_data() + cdata.set('installed_tests_dir', installed_tests_execdir) + cdata.set('program', memory_monitor_test + '.py') + cdata.set('env', '') + configure_file( + input: installed_tests_template_tap, + output: memory_monitor_test + '.test', + install_dir: installed_tests_metadir, + configuration: cdata + ) + cdata = configuration_data() + cdata.set('libexecdir', join_paths(glib_prefix, get_option('libexecdir'))) + configure_file( + input: memory_monitor_test + '.py.in', + output: memory_monitor_test + '.py', + install_dir : installed_tests_execdir, + configuration: cdata, + ) + endforeach endif if not meson.is_cross_build() or meson.has_exe_wrapper() @@ -577,9 +661,15 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() # Support for --add-symbol was added to LLVM objcopy in 2019 # (https://reviews.llvm.org/D58234). FIXME: This test could be enabled for # LLVM once that support is in a stable release. + objcopy_supports_add_symbol = false objcopy = find_program('objcopy', required : false) + if objcopy.found() + objcopy_supports_add_symbol = run_command(objcopy, '--help').stdout().contains('--add-symbol') + endif + + ld = find_program('ld', required : false) - if build_machine.system() == 'linux' and cc.get_id() == 'gcc' and objcopy.found() + if build_machine.system() == 'linux' and cc.get_id() == 'gcc' and objcopy.found() and objcopy_supports_add_symbol and ld.found() test_gresource_binary = custom_target('test5.gresource', input : 'test5.gresource.xml', output : 'test5.gresource', @@ -608,7 +698,7 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() test_resources_binary = custom_target('test_resources.o', input : test_gresource_binary, output : 'test_resources.o', - command : ['ld', + command : [ld, '-r', '-b','binary', '@INPUT@', @@ -700,5 +790,42 @@ foreach program_name, extra_args : test_extra_programs ) endforeach -# FIXME: subdir('services') +foreach test_name : python_tests + test( + test_name, + python, + args: ['-B', files(test_name)], + env: test_env, + suite: ['gio', 'no-valgrind'], + ) + + if installed_tests_enabled + install_data( + files(test_name), + install_dir: installed_tests_execdir, + install_mode: 'rwxr-xr-x', + ) + + test_conf = configuration_data() + test_conf.set('installed_tests_dir', installed_tests_execdir) + test_conf.set('program', test_name) + test_conf.set('env', '') + configure_file( + input: installed_tests_template_tap, + output: test_name + '.test', + install_dir: installed_tests_metadir, + configuration: test_conf, + ) + endif +endforeach + +# TAP test runner for Python tests +if installed_tests_enabled + install_data( + files('taptestrunner.py'), + install_dir: installed_tests_execdir, + ) +endif + +subdir('services') subdir('modules') diff --git a/gio/tests/mock-resolver.c b/gio/tests/mock-resolver.c index 271aa2c..3a75321 100644 --- a/gio/tests/mock-resolver.c +++ b/gio/tests/mock-resolver.c @@ -112,6 +112,12 @@ do_lookup_by_name (GTask *task, else g_task_return_pointer (task, g_list_copy_deep (self->ipv6_results, copy_object, NULL), NULL); } + else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT) + { + /* This is only the minimal implementation needed for some tests */ + g_assert (self->ipv4_error == NULL && self->ipv6_error == NULL && self->ipv6_results == NULL); + g_task_return_pointer (task, g_list_copy_deep (self->ipv4_results, copy_object, NULL), NULL); + } else g_assert_not_reached (); } @@ -131,6 +137,22 @@ lookup_by_name_with_flags_async (GResolver *resolver, } static GList * +lookup_by_name (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GError **error) +{ + GList *result = NULL; + GTask *task = g_task_new (resolver, cancellable, NULL, NULL); + g_task_set_task_data (task, GUINT_TO_POINTER (G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT), NULL); + g_task_run_in_thread_sync (task, do_lookup_by_name); + result = g_task_propagate_pointer (task, error); + g_object_unref (task); + return result; +} + + +static GList * lookup_by_name_with_flags_finish (GResolver *resolver, GAsyncResult *result, GError **error) @@ -160,6 +182,7 @@ mock_resolver_class_init (MockResolverClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); resolver_class->lookup_by_name_with_flags_async = lookup_by_name_with_flags_async; resolver_class->lookup_by_name_with_flags_finish = lookup_by_name_with_flags_finish; + resolver_class->lookup_by_name = lookup_by_name; object_class->finalize = mock_resolver_finalize; } diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c index bda7605..f06cd75 100644 --- a/gio/tests/network-address.c +++ b/gio/tests/network-address.c @@ -418,13 +418,150 @@ test_loopback_sync (void) g_object_unref (addr); } +static void +test_localhost_sync (void) +{ + GSocketConnectable *addr; /* owned */ + GSocketAddressEnumerator *enumerator; /* owned */ + GSocketAddress *a; /* owned */ + GError *error = NULL; + GResolver *original_resolver; /* owned */ + MockResolver *mock_resolver; /* owned */ + GList *ipv4_results = NULL; /* owned */ + + /* This test ensures that variations of the "localhost" hostname always resolve to a loopback address */ + + /* Set up a DNS resolver that returns nonsense for "localhost" */ + original_resolver = g_resolver_get_default (); + mock_resolver = mock_resolver_new (); + g_resolver_set_default (G_RESOLVER (mock_resolver)); + ipv4_results = g_list_append (ipv4_results, g_inet_address_new_from_string ("123.123.123.123")); + mock_resolver_set_ipv4_results (mock_resolver, ipv4_results); + + addr = g_network_address_new ("localhost.", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv6 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "::1", 616); + g_object_unref (a); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "127.0.0.1", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + addr = g_network_address_new (".localhost", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv6 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "::1", 616); + g_object_unref (a); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "127.0.0.1", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + addr = g_network_address_new ("foo.localhost", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv6 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "::1", 616); + g_object_unref (a); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "127.0.0.1", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + addr = g_network_address_new (".localhost.", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv6 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "::1", 616); + g_object_unref (a); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "127.0.0.1", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + addr = g_network_address_new ("invalid", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "123.123.123.123", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + g_resolver_set_default (original_resolver); + g_list_free_full (ipv4_results, (GDestroyNotify) g_object_unref); + g_object_unref (original_resolver); + g_object_unref (mock_resolver); +} + typedef struct { GList/* */ *addrs; /* owned */ GMainLoop *loop; /* owned */ + GSocketAddressEnumerator *enumerator; /* unowned */ guint delay_ms; gint expected_error_code; } AsyncData; +static void got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data); + +static int +on_delayed_get_addr (gpointer user_data) +{ + AsyncData *data = user_data; + g_socket_address_enumerator_next_async (data->enumerator, NULL, + got_addr, user_data); + return G_SOURCE_REMOVE; +} + static void got_addr (GObject *source_object, GAsyncResult *result, @@ -459,11 +596,14 @@ got_addr (GObject *source_object, g_assert (G_IS_INET_SOCKET_ADDRESS (a)); data->addrs = g_list_prepend (data->addrs, a); - if (data->delay_ms) - g_usleep (data->delay_ms * 1000); - - g_socket_address_enumerator_next_async (enumerator, NULL, - got_addr, user_data); + if (!data->delay_ms) + g_socket_address_enumerator_next_async (enumerator, NULL, + got_addr, user_data); + else + { + data->enumerator = enumerator; + g_timeout_add (data->delay_ms, on_delayed_get_addr, data); + } } } @@ -522,6 +662,51 @@ test_loopback_async (void) } static void +test_localhost_async (void) +{ + GSocketConnectable *addr; /* owned */ + GSocketAddressEnumerator *enumerator; /* owned */ + AsyncData data = { 0, }; + GResolver *original_resolver; /* owned */ + MockResolver *mock_resolver; /* owned */ + GList *ipv4_results = NULL; /* owned */ + + /* This test ensures that variations of the "localhost" hostname always resolve to a loopback address */ + + /* Set up a DNS resolver that returns nonsense for "localhost" */ + original_resolver = g_resolver_get_default (); + mock_resolver = mock_resolver_new (); + g_resolver_set_default (G_RESOLVER (mock_resolver)); + ipv4_results = g_list_append (ipv4_results, g_inet_address_new_from_string ("123.123.123.123")); + mock_resolver_set_ipv4_results (mock_resolver, ipv4_results); + + addr = g_network_address_new ("localhost", 610); + enumerator = g_socket_connectable_enumerate (addr); + + /* Get all the addresses. */ + data.addrs = NULL; + data.delay_ms = 1; + data.loop = g_main_loop_new (NULL, FALSE); + + g_socket_address_enumerator_next_async (enumerator, NULL, got_addr, &data); + g_main_loop_run (data.loop); + + /* Check results. */ + g_assert_cmpuint (g_list_length (data.addrs), ==, 2); + assert_socket_address_matches (data.addrs->data, "::1", 610); + assert_socket_address_matches (data.addrs->next->data, "127.0.0.1", 610); + + g_resolver_set_default (original_resolver); + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); + g_list_free_full (ipv4_results, (GDestroyNotify) g_object_unref); + g_object_unref (original_resolver); + g_object_unref (mock_resolver); + g_object_unref (enumerator); + g_object_unref (addr); + g_main_loop_unref (data.loop); +} + +static void test_to_string (void) { GSocketConnectable *addr = NULL; @@ -1039,6 +1224,8 @@ main (int argc, char *argv[]) g_test_add_func ("/network-address/loopback/basic", test_loopback_basic); g_test_add_func ("/network-address/loopback/sync", test_loopback_sync); g_test_add_func ("/network-address/loopback/async", test_loopback_async); + g_test_add_func ("/network-address/localhost/async", test_localhost_async); + g_test_add_func ("/network-address/localhost/sync", test_localhost_sync); g_test_add_func ("/network-address/to-string", test_to_string); g_test_add ("/network-address/happy-eyeballs/basic", HappyEyeballsFixture, NULL, diff --git a/gio/tests/org.gtk.test.dbusappinfo.flatpak.desktop b/gio/tests/org.gtk.test.dbusappinfo.flatpak.desktop new file mode 100644 index 0000000..9ef248a --- /dev/null +++ b/gio/tests/org.gtk.test.dbusappinfo.flatpak.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Type=Application +Name=Test +DBusActivatable=true +X-Flatpak=org.gtk.test.dbusappinfo.flatpak diff --git a/gio/tests/services/meson.build b/gio/tests/services/meson.build new file mode 100644 index 0000000..059eeb6 --- /dev/null +++ b/gio/tests/services/meson.build @@ -0,0 +1,30 @@ +dbus_service_files = [ + 'org.freedesktop.portal.Documents.service', +] + +srcdir_cdata = configuration_data() +srcdir_cdata.set('installed_tests_dir', meson.current_build_dir() / '..') + +installed_cdata = configuration_data() +installed_cdata.set('installed_tests_dir', installed_tests_execdir) + +foreach service_file : dbus_service_files + configure_file( + input: service_file + '.in', + output: service_file, + configuration: srcdir_cdata, + ) + if installed_tests_enabled + # Build a second copy of the service file for the installed + # version of the tests. + configure_file( + input: service_file + '.in', + output: service_file + '.to-install', + configuration: installed_cdata, + ) + install_data(meson.current_build_dir() / service_file + '.to-install', + install_dir: installed_tests_execdir / 'services', + rename: [service_file], + ) + endif +endforeach diff --git a/gio/tests/services/org.freedesktop.portal.Documents.service.in b/gio/tests/services/org.freedesktop.portal.Documents.service.in new file mode 100644 index 0000000..2769ff7 --- /dev/null +++ b/gio/tests/services/org.freedesktop.portal.Documents.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.portal.Documents +Exec=@installed_tests_dir@/fake-document-portal diff --git a/gio/tests/socket.c b/gio/tests/socket.c index eeebddd..e095d01 100644 --- a/gio/tests/socket.c +++ b/gio/tests/socket.c @@ -140,7 +140,11 @@ create_server_full (GSocketFamily family, { g_socket_set_option (data->server, IPPROTO_IPV6, IPV6_V6ONLY, FALSE, NULL); if (!g_socket_speaks_ipv4 (data->server)) - goto error; + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "IPv6-only server cannot speak IPv4"); + goto error; + } } #endif @@ -342,6 +346,7 @@ test_ip_async (GSocketFamily family) g_clear_error (&error); return; } + g_assert_nonnull (data); addr = g_socket_get_local_address (data->server, &error); g_assert_no_error (error); @@ -1130,6 +1135,12 @@ test_timed_wait (void) gint64 start_time; gint poll_duration; + if (!g_test_thorough ()) + { + g_test_skip ("Not running timing heavy test"); + return; + } + data = create_server (G_SOCKET_FAMILY_IPV4, echo_server_thread, FALSE, &error); if (error != NULL) { diff --git a/gio/tests/static-link.py b/gio/tests/static-link.py index 0af9b1a..e0a064a 100755 --- a/gio/tests/static-link.py +++ b/gio/tests/static-link.py @@ -28,7 +28,7 @@ if not 'GLIB_TEST_COMPILATION' in os.environ: If you wish to run this test, set GLIB_TEST_COMPILATION=1 in the env, and make sure you have glib build dependencies installed, including meson.''') - sys.exit(0) + sys.exit(77) if len(sys.argv) != 2: print('Usage: {} '.format(os.path.basename(sys.argv[0]))) diff --git a/gio/tests/taptestrunner.py b/gio/tests/taptestrunner.py new file mode 100644 index 0000000..2614961 --- /dev/null +++ b/gio/tests/taptestrunner.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# coding=utf-8 + +# Copyright (c) 2015 Remko Tronçon (https://el-tramo.be) +# Copied from https://github.com/remko/pycotap/ +# +# Released under the MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +import unittest +import sys +import base64 +from io import StringIO + +# Log modes +class LogMode(object) : + LogToError, LogToDiagnostics, LogToYAML, LogToAttachment = range(4) + + +class TAPTestResult(unittest.TestResult): + def __init__(self, output_stream, error_stream, message_log, test_output_log): + super(TAPTestResult, self).__init__(self, output_stream) + self.output_stream = output_stream + self.error_stream = error_stream + self.orig_stdout = None + self.orig_stderr = None + self.message = None + self.test_output = None + self.message_log = message_log + self.test_output_log = test_output_log + self.output_stream.write("TAP version 13\n") + self._set_streams() + + def printErrors(self): + self.print_raw("1..%d\n" % self.testsRun) + self._reset_streams() + + def _set_streams(self): + self.orig_stdout = sys.stdout + self.orig_stderr = sys.stderr + if self.message_log == LogMode.LogToError: + self.message = self.error_stream + else: + self.message = StringIO() + if self.test_output_log == LogMode.LogToError: + self.test_output = self.error_stream + else: + self.test_output = StringIO() + + if self.message_log == self.test_output_log: + self.test_output = self.message + sys.stdout = sys.stderr = self.test_output + + def _reset_streams(self): + sys.stdout = self.orig_stdout + sys.stderr = self.orig_stderr + + + def print_raw(self, text): + self.output_stream.write(text) + self.output_stream.flush() + + def print_result(self, result, test, directive = None): + self.output_stream.write("%s %d %s" % (result, self.testsRun, test.id())) + if directive: + self.output_stream.write(" # " + directive) + self.output_stream.write("\n") + self.output_stream.flush() + + def ok(self, test, directive = None): + self.print_result("ok", test, directive) + + def not_ok(self, test): + self.print_result("not ok", test) + + def startTest(self, test): + super(TAPTestResult, self).startTest(test) + + def stopTest(self, test): + super(TAPTestResult, self).stopTest(test) + if self.message_log == self.test_output_log: + logs = [(self.message_log, self.message, "output")] + else: + logs = [ + (self.test_output_log, self.test_output, "test_output"), + (self.message_log, self.message, "message") + ] + for log_mode, log, log_name in logs: + if log_mode != LogMode.LogToError: + output = log.getvalue() + if len(output): + if log_mode == LogMode.LogToYAML: + self.print_raw(" ---\n") + self.print_raw(" " + log_name + ": |\n") + self.print_raw(" " + output.rstrip().replace("\n", "\n ") + "\n") + self.print_raw(" ...\n") + elif log_mode == LogMode.LogToAttachment: + self.print_raw(" ---\n") + self.print_raw(" " + log_name + ":\n") + self.print_raw(" File-Name: " + log_name + ".txt\n") + self.print_raw(" File-Type: text/plain\n") + self.print_raw(" File-Content: " + base64.b64encode(output) + "\n") + self.print_raw(" ...\n") + else: + self.print_raw("# " + output.rstrip().replace("\n", "\n# ") + "\n") + # Truncate doesn't change the current stream position. + # Seek to the beginning to avoid extensions on subsequent writes. + log.seek(0) + log.truncate(0) + + def addSuccess(self, test): + super(TAPTestResult, self).addSuccess(test) + self.ok(test) + + def addError(self, test, err): + super(TAPTestResult, self).addError(test, err) + self.message.write(self.errors[-1][1] + "\n") + self.not_ok(test) + + def addFailure(self, test, err): + super(TAPTestResult, self).addFailure(test, err) + self.message.write(self.failures[-1][1] + "\n") + self.not_ok(test) + + def addSkip(self, test, reason): + super(TAPTestResult, self).addSkip(test, reason) + self.ok(test, "SKIP " + reason) + + def addExpectedFailure(self, test, err): + super(TAPTestResult, self).addExpectedFailure(test, err) + self.ok(test) + + def addUnexpectedSuccess(self, test): + super(TAPTestResult, self).addUnexpectedSuccess(test) + self.message.write("Unexpected success" + "\n") + self.not_ok(test) + + +class TAPTestRunner(object): + def __init__(self, + message_log = LogMode.LogToYAML, + test_output_log = LogMode.LogToDiagnostics, + output_stream = sys.stdout, error_stream = sys.stderr): + self.output_stream = output_stream + self.error_stream = error_stream + self.message_log = message_log + self.test_output_log = test_output_log + + def run(self, test): + result = TAPTestResult( + self.output_stream, + self.error_stream, + self.message_log, + self.test_output_log) + test(result) + result.printErrors() + + return result diff --git a/gio/tests/task.c b/gio/tests/task.c index 0caed44..cca05ce 100644 --- a/gio/tests/task.c +++ b/gio/tests/task.c @@ -2009,6 +2009,47 @@ test_return_pointer (void) g_assert_null (task); } +static void +test_return_value (void) +{ + GObject *object; + GValue value = G_VALUE_INIT; + GValue ret = G_VALUE_INIT; + GTask *task; + GError *error = NULL; + + object = (GObject *)g_dummy_object_new (); + g_assert_cmpint (object->ref_count, ==, 1); + g_object_add_weak_pointer (object, (gpointer *)&object); + + g_value_init (&value, G_TYPE_OBJECT); + g_value_set_object (&value, object); + g_assert_cmpint (object->ref_count, ==, 2); + + task = g_task_new (NULL, NULL, NULL, NULL); + g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task); + g_task_return_value (task, &value); + g_assert_cmpint (object->ref_count, ==, 3); + + g_assert_true (g_task_propagate_value (task, &ret, &error)); + g_assert_no_error (error); + g_assert_true (g_value_get_object (&ret) == object); + g_assert_cmpint (object->ref_count, ==, 3); + + g_object_unref (task); + g_assert_nonnull (task); + wait_for_completed_notification (task); + g_assert_null (task); + + g_assert_cmpint (object->ref_count, ==, 3); + g_value_unset (&ret); + g_assert_cmpint (object->ref_count, ==, 2); + g_value_unset (&value); + g_assert_cmpint (object->ref_count, ==, 1); + g_object_unref (object); + g_assert_null (object); +} + /* test_object_keepalive: GTask takes a ref on its source object */ static GObject *keepalive_object; @@ -2348,6 +2389,7 @@ main (int argc, char **argv) g_test_add_func ("/gtask/return-on-cancel-sync", test_return_on_cancel_sync); g_test_add_func ("/gtask/return-on-cancel-atomic", test_return_on_cancel_atomic); g_test_add_func ("/gtask/return-pointer", test_return_pointer); + g_test_add_func ("/gtask/return-value", test_return_value); g_test_add_func ("/gtask/object-keepalive", test_object_keepalive); g_test_add_func ("/gtask/legacy-error", test_legacy_error); g_test_add_func ("/gtask/return/in-idle/error-first", test_return_in_idle_error_first); diff --git a/gio/tests/test-codegen.xml b/gio/tests/test-codegen.xml index 39d8769..3090cad 100644 --- a/gio/tests/test-codegen.xml +++ b/gio/tests/test-codegen.xml @@ -481,6 +481,15 @@ + + + + + + + + + diff --git a/gio/tests/testfilemonitor.c b/gio/tests/testfilemonitor.c index 6057958..b74dc2b 100644 --- a/gio/tests/testfilemonitor.c +++ b/gio/tests/testfilemonitor.c @@ -27,6 +27,8 @@ setup (Fixture *fixture, fixture->tmp_dir = g_file_new_for_path (path); g_test_message ("Using temporary directory: %s", path); + + g_free (path); } static void diff --git a/gio/tests/unix-streams.c b/gio/tests/unix-streams.c index 65eedb3..5ec8299 100644 --- a/gio/tests/unix-streams.c +++ b/gio/tests/unix-streams.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,8 @@ #include #include -#define DATA "abcdefghijklmnopqrstuvwxyz" +/* sizeof(DATA) will give the number of bytes in the array, plus the terminating nul */ +static const gchar DATA[] = "abcdefghijklmnopqrstuvwxyz"; int writer_pipe[2], reader_pipe[2]; GCancellable *writer_cancel, *reader_cancel, *main_cancel; @@ -118,8 +120,8 @@ reader_thread (gpointer user_data) g_assert_not_reached (); } -char main_buf[sizeof (DATA)]; -gssize main_len, main_offset; +static char main_buf[sizeof (DATA)]; +static gssize main_len, main_offset; static void main_thread_read (GObject *source, GAsyncResult *res, gpointer user_data); static void main_thread_skipped (GObject *source, GAsyncResult *res, gpointer user_data); diff --git a/gio/tests/win32-appinfo.c b/gio/tests/win32-appinfo.c new file mode 100644 index 0000000..78052c3 --- /dev/null +++ b/gio/tests/win32-appinfo.c @@ -0,0 +1,470 @@ +/* GLib testing framework examples and tests + * Copyright (C) 2019 Руслан Ижбулатов + * + * This work is provided "as is"; redistribution and modification + * in whole or in part, in any medium, physical or electronic is + * permitted without restriction. + * + * This work is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * In no event shall the authors or contributors be liable for any + * direct, indirect, incidental, special, exemplary, or consequential + * damages (including, but not limited to, procurement of substitute + * goods or services; loss of use, data, or profits; or business + * interruption) however caused and on any theory of liability, whether + * in contract, strict liability, or tort (including negligence or + * otherwise) arising in any way out of the use of this software, even + * if advised of the possibility of such damage. + */ + +#include +#include +#include + +#include "../giowin32-private.c" + +static int +g_utf16_cmp0 (const gunichar2 *str1, + const gunichar2 *str2) +{ + if (!str1) + return -(str1 != str2); + if (!str2) + return str1 != str2; + + while (TRUE) + { + if (str1[0] > str2[0]) + return 1; + else if (str1[0] < str2[0]) + return -1; + else if (str1[0] == 0 && str2[0] == 0) + return 0; + + str1++; + str2++; + } +} + +#define g_assert_cmputf16(s1, cmp, s2, s1u8, s2u8) \ +G_STMT_START { \ + const gunichar2 *__s1 = (s1), *__s2 = (s2); \ + if (g_utf16_cmp0 (__s1, __s2) cmp 0) ; else \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #s1u8 " " #cmp " " #s2u8, s1u8, #cmp, s2u8); \ +} G_STMT_END + +static void +test_utf16_strfuncs (void) +{ + gsize i; + + struct { + gsize len; + const gunichar2 utf16[10]; + const gchar *utf8; + const gchar *utf8_folded; + } string_cases[] = { + { + 0, + { 0x0000 }, + "", + "", + }, + { + 1, + { 0x0020, 0x0000 }, + " ", + " ", + }, + { + 2, + { 0x0020, 0xd800, 0x0000 }, + NULL, + NULL, + }, + }; + + for (i = 0; i < G_N_ELEMENTS (string_cases); i++) + { + gsize len; + gunichar2 *str; + gboolean success; + gchar *utf8; + gchar *utf8_folded; + + len = g_utf16_len (string_cases[i].utf16); + g_assert_cmpuint (len, ==, string_cases[i].len); + + str = (gunichar2 *) g_utf16_find_basename (string_cases[i].utf16, -1); + /* This only works because all testcases lack separators */ + g_assert_true (string_cases[i].utf16 == str); + + str = g_wcsdup (string_cases[i].utf16, string_cases[i].len); + g_assert_cmpmem (string_cases[i].utf16, len, str, len); + g_free (str); + + str = g_wcsdup (string_cases[i].utf16, -1); + g_assert_cmpmem (string_cases[i].utf16, len, str, len); + g_free (str); + + success = g_utf16_to_utf8_and_fold (string_cases[i].utf16, -1, NULL, NULL); + + if (string_cases[i].utf8 == NULL) + g_assert_false (success); + else + g_assert_true (success); + + utf8 = NULL; + success = g_utf16_to_utf8_and_fold (string_cases[i].utf16, -1, &utf8, NULL); + + if (string_cases[i].utf8 != NULL) + { + g_assert_true (success); + g_assert_cmpstr (string_cases[i].utf8, ==, utf8); + /* This only works because all testcases lack separators */ + g_assert_true (utf8 == g_utf8_find_basename (utf8, len)); + } + + g_free (utf8); + + utf8 = NULL; + utf8_folded = NULL; + success = g_utf16_to_utf8_and_fold (string_cases[i].utf16, -1, &utf8, &utf8_folded); + + if (string_cases[i].utf8 != NULL) + { + g_assert_true (success); + g_assert_cmpstr (string_cases[i].utf8_folded, ==, utf8_folded); + } + + g_free (utf8); + g_free (utf8_folded); + } +} + +struct { + const char *orig; + const char *executable; + const char *executable_basename; + gboolean is_rundll32; + const char *fixed; +} rundll32_commandlines[] = { + { + "%SystemRoot%\\System32\\rundll32.exe \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\", ImageView_Fullscreen %1", + "%SystemRoot%\\System32\\rundll32.exe", + "rundll32.exe", + TRUE, + "%SystemRoot%\\System32\\rundll32.exe \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\" ImageView_Fullscreen %1", + }, + { + "%SystemRoot%/System32/rundll32.exe \"%ProgramFiles%/Windows Photo Viewer/PhotoViewer.dll\", ImageView_Fullscreen %1", + "%SystemRoot%/System32/rundll32.exe", + "rundll32.exe", + TRUE, + "%SystemRoot%/System32/rundll32.exe \"%ProgramFiles%/Windows Photo Viewer/PhotoViewer.dll\" ImageView_Fullscreen %1", + }, + { + "%SystemRoot%\\System32/rundll32.exe \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\", ImageView_Fullscreen %1", + "%SystemRoot%\\System32/rundll32.exe", + "rundll32.exe", + TRUE, + "%SystemRoot%\\System32/rundll32.exe \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\" ImageView_Fullscreen %1", + }, + { + "\"some path with spaces\\rundll32.exe\" \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\", ImageView_Fullscreen %1", + "some path with spaces\\rundll32.exe", + "rundll32.exe", + TRUE, + "\"some path with spaces\\rundll32.exe\" \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\" ImageView_Fullscreen %1", + }, + { + " \"some path with spaces\\rundll32.exe\"\"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\",ImageView_Fullscreen %1", + "some path with spaces\\rundll32.exe", + "rundll32.exe", + TRUE, + " \"some path with spaces\\rundll32.exe\"\"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\" ImageView_Fullscreen %1", + }, + { + "rundll32.exe foo.bar,baz", + "rundll32.exe", + "rundll32.exe", + TRUE, + "rundll32.exe foo.bar baz", + }, + { + " rundll32.exe foo.bar,baz", + "rundll32.exe", + "rundll32.exe", + TRUE, + " rundll32.exe foo.bar baz", + }, + { + "rundll32.exe", + "rundll32.exe", + "rundll32.exe", + FALSE, + NULL, + }, + { + "rundll32.exe ,foobar", + "rundll32.exe", + "rundll32.exe", + FALSE, + NULL, + }, + { + "rundll32.exe ,foobar", + "rundll32.exe", + "rundll32.exe", + FALSE, + NULL, + }, + { + "rundll32.exe foo.dll", + "rundll32.exe", + "rundll32.exe", + FALSE, + NULL, + }, + { + "rundll32.exe \"foo bar\",baz", + "rundll32.exe", + "rundll32.exe", + TRUE, + "rundll32.exe \"foo bar\" baz", + }, + { + "\"rundll32.exe\" \"foo bar\",baz", + "rundll32.exe", + "rundll32.exe", + TRUE, + "\"rundll32.exe\" \"foo bar\" baz", + }, + { + "\"rundll32.exe\" \"foo bar\",, , ,,, , ,,baz", + "rundll32.exe", + "rundll32.exe", + TRUE, + "\"rundll32.exe\" \"foo bar\" , , ,,, , ,,baz", + }, + { + "\"rundll32.exe\" foo.bar,,,,,,,,,baz", + "rundll32.exe", + "rundll32.exe", + TRUE, + "\"rundll32.exe\" foo.bar ,,,,,,,,baz", + }, + { + "\"rundll32.exe\" foo.bar baz", + "rundll32.exe", + "rundll32.exe", + TRUE, + "\"rundll32.exe\" foo.bar baz", + }, + { + "\"RuNdlL32.exe\" foo.bar baz", + "RuNdlL32.exe", + "RuNdlL32.exe", + TRUE, + "\"RuNdlL32.exe\" foo.bar baz", + }, + { + "%SystemRoot%\\System32\\rundll32.exe \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll,\" ImageView_Fullscreen %1", + "%SystemRoot%\\System32\\rundll32.exe", + "rundll32.exe", + TRUE, + "%SystemRoot%\\System32\\rundll32.exe \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll,\" ImageView_Fullscreen %1", + }, + { + "\"rundll32.exe\" \"foo bar,\"baz", + "rundll32.exe", + "rundll32.exe", + TRUE, + "\"rundll32.exe\" \"foo bar,\"baz", + }, + { + "\"rundll32.exe\" some,thing", + "rundll32.exe", + "rundll32.exe", + TRUE, + "\"rundll32.exe\" some thing", + }, + { + "\"rundll32.exe\" some,", + "rundll32.exe", + "rundll32.exe", + FALSE, + "\"rundll32.exe\" some,", + }, + /* These filenames are not allowed on Windows, but our function doesn't care about that */ + { + "run\"dll32.exe foo\".bar,baz", + "run\"dll32.exe", + "run\"dll32.exe", + FALSE, + NULL, + }, + { + "run,dll32.exe foo.bar,baz", + "run,dll32.exe", + "run,dll32.exe", + FALSE, + NULL, + }, + { + "\"rundll32.exe\" some, thing", + "rundll32.exe", + "rundll32.exe", + TRUE, + "\"rundll32.exe\" some thing", + }, + /* Commands with "rundll32" (without the .exe suffix) do exist, + * but GLib currently does not recognize them, so there's no point + * in testing these. + */ +}; + +static void +test_win32_rundll32_fixup (void) +{ + gsize i; + + for (i = 0; i < G_N_ELEMENTS (rundll32_commandlines); i++) + { + gunichar2 *argument; + gunichar2 *expected; + + if (!rundll32_commandlines[i].is_rundll32) + continue; + + argument = g_utf8_to_utf16 (rundll32_commandlines[i].orig, -1, NULL, NULL, NULL); + expected = g_utf8_to_utf16 (rundll32_commandlines[i].fixed, -1, NULL, NULL, NULL); + + g_assert_nonnull (argument); + g_assert_nonnull (expected); + _g_win32_fixup_broken_microsoft_rundll_commandline (argument); + + g_assert_cmputf16 (argument, ==, expected, rundll32_commandlines[i].orig, rundll32_commandlines[i].fixed); + + g_free (argument); + g_free (expected); + } +} + +static void +test_win32_extract_executable (void) +{ + gsize i; + + for (i = 0; i < G_N_ELEMENTS (rundll32_commandlines); i++) + { + gunichar2 *argument; + gchar *dll_function; + gchar *executable; + gchar *executable_basename; + gchar *executable_folded; + gchar *executable_folded_basename; + + argument = g_utf8_to_utf16 (rundll32_commandlines[i].orig, -1, NULL, NULL, NULL); + + _g_win32_extract_executable (argument, NULL, NULL, NULL, NULL, &dll_function); + + if (rundll32_commandlines[i].is_rundll32) + g_assert_nonnull (dll_function); + else + g_assert_null (dll_function); + + g_free (dll_function); + + executable = NULL; + executable_basename = NULL; + executable_folded = NULL; + executable_folded_basename = NULL; + _g_win32_extract_executable (argument, &executable, &executable_basename, &executable_folded, &executable_folded_basename, NULL); + + g_assert_cmpstr (rundll32_commandlines[i].executable, ==, executable); + g_assert_cmpstr (rundll32_commandlines[i].executable_basename, ==, executable_basename); + g_assert_nonnull (executable_folded); + + g_free (executable); + g_free (executable_folded); + + /* Check the corner-case where we don't want to know where basename is */ + executable = NULL; + executable_folded = NULL; + _g_win32_extract_executable (argument, &executable, NULL, &executable_folded, NULL, NULL); + + g_assert_cmpstr (rundll32_commandlines[i].executable, ==, executable); + g_assert_nonnull (executable_folded); + + g_free (executable); + g_free (executable_folded); + + g_free (argument); + } +} + +static void +test_win32_parse_filename (void) +{ + gsize i; + + for (i = 0; i < G_N_ELEMENTS (rundll32_commandlines); i++) + { + gunichar2 *argument; + argument = g_utf8_to_utf16 (rundll32_commandlines[i].orig, -1, NULL, NULL, NULL); + /* Just checking that it doesn't blow up on various (sometimes incorrect) strings */ + _g_win32_parse_filename (argument, FALSE, NULL, NULL, NULL, NULL); + g_free (argument); + } +} + +static void +do_fail_on_broken_utf16_1 (void) +{ + const gunichar2 utf16[] = { 0xd800, 0x0000 }; + _g_win32_extract_executable (utf16, NULL, NULL, NULL, NULL, NULL); +} + +static void +do_fail_on_broken_utf16_2 (void) +{ + /* "rundll32.exe r" */ + gchar *dll_function; + const gunichar2 utf16[] = { 0x0072, 0x0075, 0x006E, 0x0064, 0x006C, 0x006C, 0x0033, 0x0032, + 0x002E, 0x0065, 0x0078, 0x0065, 0x0020, 0xd800, 0x0020, 0x0072, 0x0000 }; + _g_win32_extract_executable (utf16, NULL, NULL, NULL, NULL, &dll_function); +} + +static void +test_fail_on_broken_utf16 (void) +{ + g_test_trap_subprocess ("/appinfo/subprocess/win32-assert-broken-utf16_1", 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*GLib-GIO:ERROR:*giowin32-private.c:*:_g_win32_extract_executable: assertion failed: (folded)*"); + g_test_trap_subprocess ("/appinfo/subprocess/win32-assert-broken-utf16_2", 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*GLib-GIO:ERROR:*giowin32-private.c:*:_g_win32_extract_executable: assertion failed: (folded)*"); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/appinfo/utf16-strfuncs", test_utf16_strfuncs); + g_test_add_func ("/appinfo/win32-extract-executable", test_win32_extract_executable); + g_test_add_func ("/appinfo/win32-rundll32-fixup", test_win32_rundll32_fixup); + g_test_add_func ("/appinfo/win32-parse-filename", test_win32_parse_filename); + g_test_add_func ("/appinfo/win32-utf16-conversion-fail", test_fail_on_broken_utf16); + + g_test_add_func ("/appinfo/subprocess/win32-assert-broken-utf16_1", do_fail_on_broken_utf16_1); + g_test_add_func ("/appinfo/subprocess/win32-assert-broken-utf16_2", do_fail_on_broken_utf16_2); + + return g_test_run (); +} + diff --git a/glib.supp b/glib.supp index 3f7b53e..a732aa5 100644 --- a/glib.supp +++ b/glib.supp @@ -464,6 +464,17 @@ } { + g-task-thread-pool-init + Memcheck:Leak + match-leak-kinds:possible,reachable,definite + fun:malloc + ... + fun:g_thread_new + ... + fun:g_task_thread_pool_init +} + +{ g-io-module-default-proxy-resolver-gnome Memcheck:Leak match-leak-kinds:reachable @@ -478,7 +489,7 @@ { g-threaded-resolver-getaddrinfo-config Memcheck:Leak - match-leak-kinds:reachable + match-leak-kinds:reachable,definite fun:malloc ... fun:__resolv_conf_allocate @@ -880,7 +891,7 @@ { g_private_set_alloc0 Memcheck:Leak - match-leak-kinds:reachable + match-leak-kinds:definite,reachable fun:malloc ... fun:g_private_set_alloc0 @@ -888,7 +899,7 @@ { g_private_set_alloc0-calloc Memcheck:Leak - match-leak-kinds:reachable + match-leak-kinds:definite,reachable fun:calloc ... fun:g_private_set_alloc0 @@ -898,7 +909,7 @@ { g_main_context_push_thread_default Memcheck:Leak - match-leak-kinds:reachable + match-leak-kinds:definite,reachable fun:malloc ... fun:g_queue_new diff --git a/glib/deprecated/gthread-deprecated.c b/glib/deprecated/gthread-deprecated.c index b0d67df..8ca255c 100644 --- a/glib/deprecated/gthread-deprecated.c +++ b/glib/deprecated/gthread-deprecated.c @@ -372,7 +372,7 @@ g_thread_create_full (GThreadFunc func, GThread *thread; thread = g_thread_new_internal (NULL, g_deprecated_thread_proxy, - func, data, stack_size, error); + func, data, stack_size, NULL, error); if (thread && !joinable) { @@ -669,7 +669,7 @@ g_static_rec_mutex_get_rec_mutex_impl (GStaticRecMutex* mutex) if (!g_thread_supported ()) return NULL; - result = g_atomic_pointer_get (&mutex->mutex.mutex); + result = (GRecMutex *) g_atomic_pointer_get (&mutex->mutex.mutex); if (!result) { @@ -680,7 +680,7 @@ g_static_rec_mutex_get_rec_mutex_impl (GStaticRecMutex* mutex) { result = g_slice_new (GRecMutex); g_rec_mutex_init (result); - g_atomic_pointer_set (&mutex->mutex.mutex, result); + g_atomic_pointer_set (&mutex->mutex.mutex, (GMutex *) result); } G_UNLOCK (g_static_mutex); diff --git a/glib/garray.c b/glib/garray.c index 38f64b8..abae3a2 100644 --- a/glib/garray.c +++ b/glib/garray.c @@ -166,6 +166,57 @@ g_array_new (gboolean zero_terminated, } /** + * g_array_steal: + * @array: a #GArray. + * @len: (optional) (out caller-allocates): pointer to retrieve the number of + * elements of the original array + * + * Frees the data in the array and resets the size to zero, while + * the underlying array is preserved for use elsewhere and returned + * to the caller. + * + * If the array was created with the @zero_terminate property + * set to %TRUE, the returned data is zero terminated too. + * + * If array elements contain dynamically-allocated memory, + * the array elements should also be freed by the caller. + * + * A short example of use: + * |[ + * ... + * gpointer data; + * gsize data_len; + * data = g_array_steal (some_array, &data_len); + * ... + * ]| + + * Returns: (transfer full): the element data, which should be + * freed using g_free(). + * + * Since: 2.64 + */ +gpointer +g_array_steal (GArray *array, + gsize *len) +{ + GRealArray *rarray; + gpointer segment; + + g_return_val_if_fail (array != NULL, NULL); + + rarray = (GRealArray *) array; + segment = (gpointer) rarray->data; + + if (len != NULL) + *len = rarray->len; + + rarray->data = NULL; + rarray->len = 0; + rarray->alloc = 0; + return segment; +} + +/** * g_array_sized_new: * @zero_terminated: %TRUE if the array should have an extra element at * the end with all bits cleared @@ -1014,6 +1065,79 @@ g_ptr_array_new (void) } /** + * g_ptr_array_steal: + * @array: a #GPtrArray. + * @len: (optional) (out caller-allocates): pointer to retrieve the number of + * elements of the original array + * + * Frees the data in the array and resets the size to zero, while + * the underlying array is preserved for use elsewhere and returned + * to the caller. + * + * Even if set, the #GDestroyNotify function will never be called + * on the current contents of the array and the caller is + * responsible for freeing the array elements. + * + * An example of use: + * |[ + * g_autoptr(GPtrArray) chunk_buffer = g_ptr_array_new_with_free_func (g_bytes_unref); + * + * // Some part of your application appends a number of chunks to the pointer array. + * g_ptr_array_add (chunk_buffer, g_bytes_new_static ("hello", 5)); + * g_ptr_array_add (chunk_buffer, g_bytes_new_static ("world", 5)); + * + * … + * + * // Periodically, the chunks need to be sent as an array-and-length to some + * // other part of the program. + * GBytes **chunks; + * gsize n_chunks; + * + * chunks = g_ptr_array_steal (chunk_buffer, &n_chunks); + * for (gsize i = 0; i < n_chunks; i++) + * { + * // Do something with each chunk here, and then free them, since + * // g_ptr_array_steal() transfers ownership of all the elements and the + * // array to the caller. + * … + * + * g_bytes_unref (chunks[i]); + * } + * + * g_free (chunks); + * + * // After calling g_ptr_array_steal(), the pointer array can be reused for the + * // next set of chunks. + * g_assert (chunk_buffer->len == 0); + * ]| + * + * Returns: (transfer full): the element data, which should be + * freed using g_free(). + * + * Since: 2.64 + */ +gpointer * +g_ptr_array_steal (GPtrArray *array, + gsize *len) +{ + GRealPtrArray *rarray; + gpointer *segment; + + g_return_val_if_fail (array != NULL, NULL); + + rarray = (GRealPtrArray *) array; + segment = (gpointer *) rarray->pdata; + + if (len != NULL) + *len = rarray->len; + + rarray->pdata = NULL; + rarray->len = 0; + rarray->alloc = 0; + return segment; +} + +/** * g_ptr_array_copy: * @array: #GPtrArray to duplicate * @func: (nullable): a copy function used to copy every element in the array @@ -1055,7 +1179,7 @@ g_ptr_array_copy (GPtrArray *array, for (i = 0; i < array->len; i++) new_array->pdata[i] = func (array->pdata[i], user_data); } - else + else if (array->len > 0) { memcpy (new_array->pdata, array->pdata, array->len * sizeof (*array->pdata)); @@ -1118,9 +1242,12 @@ g_array_copy (GArray *array) new_rarray = (GRealArray *) g_array_sized_new (rarray->zero_terminated, rarray->clear, - rarray->elt_size, rarray->len); + rarray->elt_size, rarray->alloc / rarray->elt_size); new_rarray->len = rarray->len; - memcpy (new_rarray->data, rarray->data, rarray->alloc); + if (rarray->len > 0) + memcpy (new_rarray->data, rarray->data, rarray->len * rarray->elt_size); + + g_array_zero_terminate (new_rarray); return (GArray *) new_rarray; } @@ -1706,7 +1833,7 @@ g_ptr_array_extend (GPtrArray *array_to_extend, rarray_to_extend->pdata[i + rarray_to_extend->len] = func (array->pdata[i], user_data); } - else + else if (array->len > 0) { memcpy (rarray_to_extend->pdata + rarray_to_extend->len, array->pdata, array->len * sizeof (*array->pdata)); @@ -1743,6 +1870,7 @@ g_ptr_array_extend_and_steal (GPtrArray *array_to_extend, * to the elements moved from @array to @array_to_extend. */ pdata = g_steal_pointer (&array->pdata); array->len = 0; + ((GRealPtrArray *) array)->alloc = 0; g_ptr_array_unref (array); g_free (pdata); } @@ -1783,6 +1911,8 @@ g_ptr_array_insert (GPtrArray *array, rarray->pdata[index_] = data; } +/* Please keep this doc-comment in sync with pointer_array_sort_example() + * in glib/tests/array-test.c */ /** * g_ptr_array_sort: * @array: a #GPtrArray @@ -1795,7 +1925,32 @@ g_ptr_array_insert (GPtrArray *array, * * Note that the comparison function for g_ptr_array_sort() doesn't * take the pointers from the array as arguments, it takes pointers to - * the pointers in the array. + * the pointers in the array. Here is a full example of usage: + * + * |[ + * typedef struct + * { + * gchar *name; + * gint size; + * } FileListEntry; + * + * static gint + * sort_filelist (gconstpointer a, gconstpointer b) + * { + * const FileListEntry *entry1 = *((FileListEntry **) a); + * const FileListEntry *entry2 = *((FileListEntry **) b); + * + * return g_ascii_strcasecmp (entry1->name, entry2->name); + * } + * + * … + * g_autoptr (GPtrArray) file_list = NULL; + * + * // initialize file_list array and load with many FileListEntry entries + * ... + * // now sort it with + * g_ptr_array_sort (file_list, sort_filelist); + * ]| * * This is guaranteed to be a stable sort since version 2.32. */ @@ -1813,6 +1968,8 @@ g_ptr_array_sort (GPtrArray *array, NULL); } +/* Please keep this doc-comment in sync with + * pointer_array_sort_with_data_example() in glib/tests/array-test.c */ /** * g_ptr_array_sort_with_data: * @array: a #GPtrArray @@ -1824,7 +1981,52 @@ g_ptr_array_sort (GPtrArray *array, * * Note that the comparison function for g_ptr_array_sort_with_data() * doesn't take the pointers from the array as arguments, it takes - * pointers to the pointers in the array. + * pointers to the pointers in the array. Here is a full example of use: + * + * |[ + * typedef enum { SORT_NAME, SORT_SIZE } SortMode; + * + * typedef struct + * { + * gchar *name; + * gint size; + * } FileListEntry; + * + * static gint + * sort_filelist (gconstpointer a, gconstpointer b, gpointer user_data) + * { + * gint order; + * const SortMode sort_mode = GPOINTER_TO_INT (user_data); + * const FileListEntry *entry1 = *((FileListEntry **) a); + * const FileListEntry *entry2 = *((FileListEntry **) b); + * + * switch (sort_mode) + * { + * case SORT_NAME: + * order = g_ascii_strcasecmp (entry1->name, entry2->name); + * break; + * case SORT_SIZE: + * order = entry1->size - entry2->size; + * break; + * default: + * order = 0; + * break; + * } + * return order; + * } + * + * ... + * g_autoptr (GPtrArray) file_list = NULL; + * SortMode sort_mode; + * + * // initialize file_list array and load with many FileListEntry entries + * ... + * // now sort it with + * sort_mode = SORT_NAME; + * g_ptr_array_sort_with_data (file_list, + * sort_filelist, + * GINT_TO_POINTER (sort_mode)); + * ]| * * This is guaranteed to be a stable sort since version 2.32. */ @@ -2003,6 +2205,28 @@ g_byte_array_new (void) } /** + * g_byte_array_steal: + * @array: a #GByteArray. + * @len: (optional) (out caller-allocates): pointer to retrieve the number of + * elements of the original array + * + * Frees the data in the array and resets the size to zero, while + * the underlying array is preserved for use elsewhere and returned + * to the caller. + * + * Returns: (transfer full): the element data, which should be + * freed using g_free(). + * + * Since: 2.64 + */ +guint8 * +g_byte_array_steal (GByteArray *array, + gsize *len) +{ + return (guint8 *) g_array_steal ((GArray *) array, len); +} + +/** * g_byte_array_new_take: * @data: (transfer full) (array length=len): byte data for the array * @len: length of @data diff --git a/glib/garray.h b/glib/garray.h index 3e7ce77..67131b5 100644 --- a/glib/garray.h +++ b/glib/garray.h @@ -70,6 +70,9 @@ GLIB_AVAILABLE_IN_ALL GArray* g_array_new (gboolean zero_terminated, gboolean clear_, guint element_size); +GLIB_AVAILABLE_IN_2_64 +gpointer g_array_steal (GArray *array, + gsize *len); GLIB_AVAILABLE_IN_ALL GArray* g_array_sized_new (gboolean zero_terminated, gboolean clear_, @@ -137,6 +140,9 @@ GLIB_AVAILABLE_IN_ALL GPtrArray* g_ptr_array_new (void); GLIB_AVAILABLE_IN_ALL GPtrArray* g_ptr_array_new_with_free_func (GDestroyNotify element_free_func); +GLIB_AVAILABLE_IN_2_64 +gpointer* g_ptr_array_steal (GPtrArray *array, + gsize *len); GLIB_AVAILABLE_IN_2_62 GPtrArray *g_ptr_array_copy (GPtrArray *array, GCopyFunc func, @@ -227,6 +233,9 @@ GByteArray* g_byte_array_new (void); GLIB_AVAILABLE_IN_ALL GByteArray* g_byte_array_new_take (guint8 *data, gsize len); +GLIB_AVAILABLE_IN_2_64 +guint8* g_byte_array_steal (GByteArray *array, + gsize *len); GLIB_AVAILABLE_IN_ALL GByteArray* g_byte_array_sized_new (guint reserved_size); GLIB_AVAILABLE_IN_ALL diff --git a/glib/gatomic.c b/glib/gatomic.c index c52aaad..8b8c645 100644 --- a/glib/gatomic.c +++ b/glib/gatomic.c @@ -96,15 +96,6 @@ #if defined (__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) -#if defined(__ATOMIC_SEQ_CST) && !defined(__clang__) -/* The implementation used in this code path in gatomic.h assumes - * 4-byte int */ -G_STATIC_ASSERT (sizeof (gint) == 4); - -/* The implementations in gatomic.h assume 4- or 8-byte pointers */ -G_STATIC_ASSERT (sizeof (void *) == 4 || sizeof (void *) == 8); -#endif - /** * g_atomic_int_get: * @atomic: a pointer to a #gint or #guint diff --git a/glib/gatomic.h b/glib/gatomic.h index 971176e..6bf41bb 100644 --- a/glib/gatomic.h +++ b/glib/gatomic.h @@ -85,93 +85,213 @@ G_END_DECLS #if defined(G_ATOMIC_LOCK_FREE) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) /* We prefer the new C11-style atomic extension of GCC if available */ -#if defined(__ATOMIC_SEQ_CST) && !defined(__clang__) - -/* This assumes sizeof(int) is 4: gatomic.c statically - * asserts that (using G_STATIC_ASSERT at top-level in a header was - * problematic, see #730932) */ +#if defined(__ATOMIC_SEQ_CST) #define g_atomic_int_get(atomic) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + gint gaig_temp; \ (void) (0 ? *(atomic) ^ *(atomic) : 1); \ - (gint) __atomic_load_4 ((atomic), __ATOMIC_SEQ_CST); \ + __atomic_load ((gint *)(atomic), &gaig_temp, __ATOMIC_SEQ_CST); \ + (gint) gaig_temp; \ })) #define g_atomic_int_set(atomic, newval) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + gint gais_temp = (gint) (newval); \ (void) (0 ? *(atomic) ^ (newval) : 1); \ - __atomic_store_4 ((atomic), (newval), __ATOMIC_SEQ_CST); \ + __atomic_store ((gint *)(atomic), &gais_temp, __ATOMIC_SEQ_CST); \ })) -#if GLIB_SIZEOF_VOID_P == 8 - +#if defined(g_has_typeof) #define g_atomic_pointer_get(atomic) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ - guint64 gapg_temp = __atomic_load_8 ((atomic), __ATOMIC_SEQ_CST); \ - (gpointer) gapg_temp; \ + __typeof__(*(atomic)) gapg_temp_newval; \ + __typeof__((atomic)) gapg_temp_atomic = (atomic); \ + __atomic_load (gapg_temp_atomic, &gapg_temp_newval, __ATOMIC_SEQ_CST); \ + gapg_temp_newval; \ })) #define g_atomic_pointer_set(atomic, newval) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + __typeof__((atomic)) gaps_temp_atomic = (atomic); \ + __typeof__(*(atomic)) gaps_temp_newval = (newval); \ (void) (0 ? (gpointer) *(atomic) : NULL); \ - __atomic_store_8 ((atomic), (gsize) (newval), __ATOMIC_SEQ_CST); \ + __atomic_store (gaps_temp_atomic, &gaps_temp_newval, __ATOMIC_SEQ_CST); \ })) - -#else /* GLIB_SIZEOF_VOID_P == 8 */ - -/* This assumes that if sizeof(void *) is not 8, then it is 4: - * gatomic.c statically asserts that (using G_STATIC_ASSERT - * at top-level in a header was problematic, see #730932) */ - +#else /* if !defined(g_has_typeof) */ #define g_atomic_pointer_get(atomic) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ - guint32 gapg_temp = __atomic_load_4 ((atomic), __ATOMIC_SEQ_CST); \ - (gpointer) gapg_temp; \ + gpointer gapg_temp_newval; \ + gpointer *gapg_temp_atomic = (gpointer *)(atomic); \ + __atomic_load (gapg_temp_atomic, &gapg_temp_newval, __ATOMIC_SEQ_CST); \ + gapg_temp_newval; \ })) #define g_atomic_pointer_set(atomic, newval) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + gpointer *gaps_temp_atomic = (gpointer *)(atomic); \ + gpointer gaps_temp_newval = (gpointer)(newval); \ (void) (0 ? (gpointer) *(atomic) : NULL); \ - __atomic_store_4 ((atomic), (gsize) (newval), __ATOMIC_SEQ_CST); \ + __atomic_store (gaps_temp_atomic, &gaps_temp_newval, __ATOMIC_SEQ_CST); \ + })) +#endif /* !defined(g_has_typeof) */ + +#define g_atomic_int_inc(atomic) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ *(atomic) : 1); \ + (void) __atomic_fetch_add ((atomic), 1, __ATOMIC_SEQ_CST); \ + })) +#define g_atomic_int_dec_and_test(atomic) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ *(atomic) : 1); \ + __atomic_fetch_sub ((atomic), 1, __ATOMIC_SEQ_CST) == 1; \ + })) +#define g_atomic_int_compare_and_exchange(atomic, oldval, newval) \ + (G_GNUC_EXTENSION ({ \ + gint gaicae_oldval = (oldval); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (newval) ^ (oldval) : 1); \ + __atomic_compare_exchange_n ((atomic), &gaicae_oldval, (newval), FALSE, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? TRUE : FALSE; \ + })) +#define g_atomic_int_add(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (val) : 1); \ + (gint) __atomic_fetch_add ((atomic), (val), __ATOMIC_SEQ_CST); \ + })) +#define g_atomic_int_and(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (val) : 1); \ + (guint) __atomic_fetch_and ((atomic), (val), __ATOMIC_SEQ_CST); \ + })) +#define g_atomic_int_or(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (val) : 1); \ + (guint) __atomic_fetch_or ((atomic), (val), __ATOMIC_SEQ_CST); \ + })) +#define g_atomic_int_xor(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (val) : 1); \ + (guint) __atomic_fetch_xor ((atomic), (val), __ATOMIC_SEQ_CST); \ })) -#endif /* GLIB_SIZEOF_VOID_P == 8 */ +#define g_atomic_pointer_compare_and_exchange(atomic, oldval, newval) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof (oldval) == sizeof (gpointer)); \ + __typeof__ ((oldval)) gapcae_oldval = (oldval); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + __atomic_compare_exchange_n ((atomic), &gapcae_oldval, (newval), FALSE, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? TRUE : FALSE; \ + })) +#define g_atomic_pointer_add(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + (void) (0 ? (val) ^ (val) : 1); \ + (gssize) __atomic_fetch_add ((atomic), (val), __ATOMIC_SEQ_CST); \ + })) +#define g_atomic_pointer_and(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + volatile gsize *gapa_atomic = (volatile gsize *) (atomic); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gsize)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + (void) (0 ? (val) ^ (val) : 1); \ + (gsize) __atomic_fetch_and (gapa_atomic, (val), __ATOMIC_SEQ_CST); \ + })) +#define g_atomic_pointer_or(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + volatile gsize *gapo_atomic = (volatile gsize *) (atomic); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gsize)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + (void) (0 ? (val) ^ (val) : 1); \ + (gsize) __atomic_fetch_or (gapo_atomic, (val), __ATOMIC_SEQ_CST); \ + })) +#define g_atomic_pointer_xor(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + volatile gsize *gapx_atomic = (volatile gsize *) (atomic); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gsize)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + (void) (0 ? (val) ^ (val) : 1); \ + (gsize) __atomic_fetch_xor (gapx_atomic, (val), __ATOMIC_SEQ_CST); \ + })) #else /* defined(__ATOMIC_SEQ_CST) */ +/* We want to achieve __ATOMIC_SEQ_CST semantics here. See + * https://en.cppreference.com/w/c/atomic/memory_order#Constants. For load + * operations, that means performing an *acquire*: + * > A load operation with this memory order performs the acquire operation on + * > the affected memory location: no reads or writes in the current thread can + * > be reordered before this load. All writes in other threads that release + * > the same atomic variable are visible in the current thread. + * + * “no reads or writes in the current thread can be reordered before this load” + * is implemented using a compiler barrier (a no-op `__asm__` section) to + * prevent instruction reordering. Writes in other threads are synchronised + * using `__sync_synchronize()`. It’s unclear from the GCC documentation whether + * `__sync_synchronize()` acts as a compiler barrier, hence our explicit use of + * one. + * + * For store operations, `__ATOMIC_SEQ_CST` means performing a *release*: + * > A store operation with this memory order performs the release operation: + * > no reads or writes in the current thread can be reordered after this store. + * > All writes in the current thread are visible in other threads that acquire + * > the same atomic variable (see Release-Acquire ordering below) and writes + * > that carry a dependency into the atomic variable become visible in other + * > threads that consume the same atomic (see Release-Consume ordering below). + * + * “no reads or writes in the current thread can be reordered after this store” + * is implemented using a compiler barrier to prevent instruction reordering. + * “All writes in the current thread are visible in other threads” is implemented + * using `__sync_synchronize()`; similarly for “writes that carry a dependency”. + */ #define g_atomic_int_get(atomic) \ (G_GNUC_EXTENSION ({ \ + gint gaig_result; \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ (void) (0 ? *(atomic) ^ *(atomic) : 1); \ + gaig_result = (gint) *(atomic); \ __sync_synchronize (); \ - (gint) *(atomic); \ + __asm__ __volatile__ ("" : : : "memory"); \ + gaig_result; \ })) #define g_atomic_int_set(atomic, newval) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ (void) (0 ? *(atomic) ^ (newval) : 1); \ - *(atomic) = (newval); \ __sync_synchronize (); \ + __asm__ __volatile__ ("" : : : "memory"); \ + *(atomic) = (newval); \ })) #define g_atomic_pointer_get(atomic) \ (G_GNUC_EXTENSION ({ \ + gpointer gapg_result; \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + gapg_result = (gpointer) *(atomic); \ __sync_synchronize (); \ - (gpointer) *(atomic); \ + __asm__ __volatile__ ("" : : : "memory"); \ + gapg_result; \ })) #define g_atomic_pointer_set(atomic, newval) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ (void) (0 ? (gpointer) *(atomic) : NULL); \ - *(atomic) = (__typeof__ (*(atomic))) (gsize) (newval); \ __sync_synchronize (); \ + __asm__ __volatile__ ("" : : : "memory"); \ + *(atomic) = (__typeof__ (*(atomic))) (gsize) (newval); \ })) -#endif /* !defined(__ATOMIC_SEQ_CST) */ - #define g_atomic_int_inc(atomic) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ @@ -250,6 +370,8 @@ G_END_DECLS (gsize) __sync_fetch_and_xor ((atomic), (val)); \ })) +#endif /* !defined(__ATOMIC_SEQ_CST) */ + #else /* defined(G_ATOMIC_LOCK_FREE) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) */ #define g_atomic_int_get(atomic) \ diff --git a/glib/gbacktrace.c b/glib/gbacktrace.c index 86f8841..479ae42 100644 --- a/glib/gbacktrace.c +++ b/glib/gbacktrace.c @@ -72,6 +72,17 @@ static void stack_trace (const char * const *args); #endif +/* Default to using LLDB for backtraces on macOS. */ +#ifdef __APPLE__ +#define USE_LLDB +#endif + +#ifdef USE_LLDB +#define DEBUGGER "lldb" +#else +#define DEBUGGER "gdb" +#endif + /* People want to hit this from their debugger... */ GLIB_AVAILABLE_IN_ALL volatile gboolean glib_on_error_halt; volatile gboolean glib_on_error_halt = TRUE; @@ -231,7 +242,7 @@ g_on_error_stack_trace (const gchar *prg_name) #if defined(G_OS_UNIX) pid_t pid; gchar buf[16]; - const gchar *args[4] = { "gdb", NULL, NULL, NULL }; + const gchar *args[5] = { DEBUGGER, NULL, NULL, NULL, NULL }; int status; if (!prg_name) @@ -239,8 +250,14 @@ g_on_error_stack_trace (const gchar *prg_name) _g_sprintf (buf, "%u", (guint) getpid ()); +#ifdef USE_LLDB + args[1] = prg_name; + args[2] = "-p"; + args[3] = buf; +#else args[1] = prg_name; args[2] = buf; +#endif pid = fork (); if (pid == 0) @@ -250,11 +267,19 @@ g_on_error_stack_trace (const gchar *prg_name) } else if (pid == (pid_t) -1) { - perror ("unable to fork gdb"); + perror ("unable to fork " DEBUGGER); return; } - waitpid (pid, &status, 0); + /* Wait until the child really terminates. On Mac OS X waitpid () + * will also return when the child is being stopped due to tracing. + */ + while (1) + { + pid_t retval = waitpid (pid, &status, 0); + if (WIFEXITED (retval) || WIFSIGNALED (retval)) + break; + } #else if (IsDebuggerPresent ()) G_BREAKPOINT (); @@ -273,6 +298,8 @@ stack_trace_sigchld (int signum) stack_trace_done = TRUE; } +#define BUFSIZE 1024 + static void stack_trace (const char * const *args) { @@ -282,8 +309,8 @@ stack_trace (const char * const *args) fd_set fdset; fd_set readset; struct timeval tv; - int sel, idx, state; - char buffer[256]; + int sel, idx, state, line_idx; + char buffer[BUFSIZE]; char c; stack_trace_done = FALSE; @@ -315,7 +342,7 @@ stack_trace (const char * const *args) close (2); dup (old_err); } - perror ("exec gdb failed"); + perror ("exec " DEBUGGER " failed"); _exit (0); } else if (pid == (pid_t) -1) @@ -327,11 +354,19 @@ stack_trace (const char * const *args) FD_ZERO (&fdset); FD_SET (out_fd[0], &fdset); +#ifdef USE_LLDB + write (in_fd[1], "bt\n", 3); + write (in_fd[1], "p x = 0\n", 8); + write (in_fd[1], "process detach\n", 15); + write (in_fd[1], "quit\n", 5); +#else write (in_fd[1], "backtrace\n", 10); write (in_fd[1], "p x = 0\n", 8); write (in_fd[1], "quit\n", 5); +#endif idx = 0; + line_idx = 0; state = 0; while (1) @@ -348,10 +383,15 @@ stack_trace (const char * const *args) { if (read (out_fd[0], &c, 1)) { + line_idx += 1; switch (state) { case 0: +#ifdef USE_LLDB + if (c == '*' || (c == ' ' && line_idx == 1)) +#else if (c == '#') +#endif { state = 1; idx = 0; @@ -359,13 +399,15 @@ stack_trace (const char * const *args) } break; case 1: - buffer[idx++] = c; + if (idx < BUFSIZE) + buffer[idx++] = c; if ((c == '\n') || (c == '\r')) { buffer[idx] = 0; _g_fprintf (stdout, "%s", buffer); state = 0; idx = 0; + line_idx = 0; } break; default: diff --git a/glib/gbase64.c b/glib/gbase64.c index dd7ed20..f2d110e 100644 --- a/glib/gbase64.c +++ b/glib/gbase64.c @@ -76,10 +76,10 @@ static const char base64_alphabet[] = * be written to it. Due to the way base64 encodes you will need * at least: (@len / 3 + 1) * 4 + 4 bytes (+ 4 may be needed in case of * non-zero state). If you enable line-breaking you will need at least: - * ((@len / 3 + 1) * 4 + 4) / 72 + 1 bytes of extra space. + * ((@len / 3 + 1) * 4 + 4) / 76 + 1 bytes of extra space. * * @break_lines is typically used when putting base64-encoded data in emails. - * It breaks the lines at 72 columns instead of putting all of the text on + * It breaks the lines at 76 columns instead of putting all of the text on * the same line. This avoids problems with long lines in the email system. * Note however that it breaks the lines with `LF` characters, not * `CR LF` sequences, so the result cannot be passed directly to SMTP diff --git a/glib/gbitlock.c b/glib/gbitlock.c index 46e5f7d..23024d0 100644 --- a/glib/gbitlock.c +++ b/glib/gbitlock.c @@ -224,7 +224,7 @@ g_bit_lock (volatile gint *address, guint mask = 1u << lock_bit; guint v; - v = g_atomic_int_get (address); + v = (guint) g_atomic_int_get (address); if (v & mask) { guint class = ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended); diff --git a/glib/gbookmarkfile.c b/glib/gbookmarkfile.c index 25f1234..e22f794 100644 --- a/glib/gbookmarkfile.c +++ b/glib/gbookmarkfile.c @@ -775,13 +775,22 @@ parse_bookmark_element (GMarkupParseContext *context, item = bookmark_item_new (uri); if (added != NULL && !timestamp_from_iso8601 (added, &item->added, error)) - return; + { + bookmark_item_free (item); + return; + } if (modified != NULL && !timestamp_from_iso8601 (modified, &item->modified, error)) - return; + { + bookmark_item_free (item); + return; + } if (visited != NULL && !timestamp_from_iso8601 (visited, &item->visited, error)) - return; + { + bookmark_item_free (item); + return; + } add_error = NULL; g_bookmark_file_add_item (parse_data->bookmark_file, diff --git a/glib/gbytes.c b/glib/gbytes.c index 7b72886..ec69231 100644 --- a/glib/gbytes.c +++ b/glib/gbytes.c @@ -365,7 +365,7 @@ g_bytes_equal (gconstpointer bytes1, g_return_val_if_fail (bytes2 != NULL, FALSE); return b1->size == b2->size && - memcmp (b1->data, b2->data, b1->size) == 0; + (b1->size == 0 || memcmp (b1->data, b2->data, b1->size) == 0); } /** diff --git a/glib/gcharset.c b/glib/gcharset.c index 7bce2a1..bb775bd 100644 --- a/glib/gcharset.c +++ b/glib/gcharset.c @@ -315,7 +315,7 @@ g_get_console_charset (const char **charset) g_free (emsg); } } - /* fall-back to UTF-8 if the rest failed (it's a sane and universal default) */ + /* fall-back to UTF-8 if the rest failed (it's a universal default) */ if (raw == NULL) raw = "UTF-8"; @@ -549,10 +549,15 @@ append_locale_variants (GPtrArray *array, * Returns a list of derived variants of @locale, which can be used to * e.g. construct locale-dependent filenames or search paths. The returned * list is sorted from most desirable to least desirable. - * This function handles territory, charset and extra locale modifiers. + * This function handles territory, charset and extra locale modifiers. See + * [`setlocale(3)`](man:setlocale) for information about locales and their format. * - * For example, if @locale is "fr_BE", then the returned list - * is "fr_BE", "fr". + * @locale itself is guaranteed to be returned in the output. + * + * For example, if @locale is `fr_BE`, then the returned list + * is `fr_BE`, `fr`. If @locale is `en_GB.UTF-8@euro`, then the returned list + * is `en_GB.UTF-8@euro`, `en_GB.UTF-8`, `en_GB@euro`, `en_GB`, `en.UTF-8@euro`, + * `en.UTF-8`, `en@euro`, `en`. * * If you need the list of variants for the current locale, * use g_get_language_names(). diff --git a/glib/gchecksum.c b/glib/gchecksum.c index 1ad21ff..f8a3f9a 100644 --- a/glib/gchecksum.c +++ b/glib/gchecksum.c @@ -1344,6 +1344,7 @@ sha512_sum_close (Sha512sum *sha512) memset (pad + pad_len, 0x00, zeros / 8); pad_len += zeros / 8; zeros = zeros % 8; + (void) zeros; /* don’t care about the dead store */ /* put message bit length at the end of padding */ PUT_UINT64 (sha512->data_len[1], pad, pad_len); diff --git a/glib/gconstructor.h b/glib/gconstructor.h index 603c2dd..9509e59 100644 --- a/glib/gconstructor.h +++ b/glib/gconstructor.h @@ -1,6 +1,6 @@ /* If G_HAS_CONSTRUCTORS is true then the compiler support *both* constructors and - destructors, in a sane way, including e.g. on library unload. If not you're on + destructors, in a usable way, including e.g. on library unload. If not you're on your own. Some compilers need #pragma to handle this, which does not work with macros, diff --git a/glib/gdate.c b/glib/gdate.c index 26b3f59..ec7b95b 100644 --- a/glib/gdate.c +++ b/glib/gdate.c @@ -83,8 +83,8 @@ * * #GDate is simple to use. First you need a "blank" date; you can get a * dynamically allocated date from g_date_new(), or you can declare an - * automatic variable or array and initialize it to a sane state by - * calling g_date_clear(). A cleared date is sane; it's safe to call + * automatic variable or array and initialize it by + * calling g_date_clear(). A cleared date is safe; it's safe to call * g_date_set_dmy() and the other mutator functions to initialize the * value of a cleared date. However, a cleared date is initially * invalid, meaning that it doesn't represent a day that exists. @@ -146,7 +146,7 @@ * * If it's declared on the stack, it will contain garbage so must be * initialized with g_date_clear(). g_date_clear() makes the date invalid - * but sane. An invalid date doesn't represent a day, it's "empty." A date + * but safe. An invalid date doesn't represent a day, it's "empty." A date * becomes valid after you set it to a Julian day or you set a day, month, * and year. */ @@ -259,7 +259,7 @@ * g_date_new: * * Allocates a #GDate and initializes - * it to a sane state. The new date will + * it to a safe state. The new date will * be cleared (as if you'd called g_date_clear()) but invalid (it won't * represent an existing day). Free the return value with g_date_free(). * @@ -420,7 +420,7 @@ static const guint16 days_in_year[2][14] = gboolean g_date_valid_month (GDateMonth m) { - return ( (m > G_DATE_BAD_MONTH) && (m < 13) ); + return (((gint) m > G_DATE_BAD_MONTH) && ((gint) m < 13)); } /** @@ -466,7 +466,7 @@ g_date_valid_day (GDateDay d) gboolean g_date_valid_weekday (GDateWeekday w) { - return ( (w > G_DATE_BAD_WEEKDAY) && (w < 8) ); + return (((gint) w > G_DATE_BAD_WEEKDAY) && ((gint) w < 8)); } /** @@ -862,7 +862,7 @@ g_date_days_between (const GDate *d1, * @date: pointer to one or more dates to clear * @n_dates: number of dates to clear * - * Initializes one or more #GDate structs to a sane but invalid + * Initializes one or more #GDate structs to a safe but invalid * state. The cleared dates will not represent an existing date, but will * not contain garbage. Useful to init a date declared on the stack. * Validity can be tested with g_date_valid(). @@ -2055,7 +2055,7 @@ g_date_compare (const GDate *lhs, * @tm: (not nullable): struct tm to fill * * Fills in the date-related bits of a struct tm using the @date value. - * Initializes the non-date parts with something sane but meaningless. + * Initializes the non-date parts with something safe but meaningless. */ void g_date_to_struct_tm (const GDate *d, diff --git a/glib/gdate.h b/glib/gdate.h index 3bc07bf..65fe811 100644 --- a/glib/gdate.h +++ b/glib/gdate.h @@ -178,7 +178,7 @@ GLIB_AVAILABLE_IN_ALL guint g_date_get_iso8601_week_of_year (const GDate *date); /* If you create a static date struct you need to clear it to get it - * in a sane state before use. You can clear a whole array at + * in a safe state before use. You can clear a whole array at * once with the ndates argument. */ GLIB_AVAILABLE_IN_ALL diff --git a/glib/gdatetime.c b/glib/gdatetime.c index 3be4eba..f93d7d8 100644 --- a/glib/gdatetime.c +++ b/glib/gdatetime.c @@ -935,9 +935,8 @@ g_date_time_new_from_unix (GTimeZone *tz, * time zone @tz. The time is as accurate as the system allows, to a * maximum accuracy of 1 microsecond. * - * This function will always succeed unless the system clock is set to - * truly insane values (or unless GLib is still being used after the - * year 9999). + * This function will always succeed unless GLib is still being used after the + * year 9999. * * You should release the return value by calling g_date_time_unref() * when you are done with it. @@ -1194,6 +1193,11 @@ get_iso8601_seconds (const gchar *text, gsize length, gdouble *value) if (length > 2 && !(text[i] == '.' || text[i] == ',')) return FALSE; + + /* Ignore leap seconds, see g_date_time_new_from_iso8601() */ + if (v >= 60.0 && v <= 61.0) + v = 59.0; + i++; if (i == length) return FALSE; @@ -1429,9 +1433,17 @@ parse_iso8601_time (const gchar *text, gsize length, * * Creates a #GDateTime corresponding to the given * [ISO 8601 formatted string](https://en.wikipedia.org/wiki/ISO_8601) - * @text. ISO 8601 strings of the form