From 5444f8523e92d175850fad2562c618cd6131393e Mon Sep 17 00:00:00 2001 From: hanwenzhao Date: Wed, 9 Oct 2024 20:34:08 +0800 Subject: [PATCH 1/2] Signed-off-by: hanwenzhao Change-Id: I990e2e743346e7505232d4f116e17f6da1b01788 --- glib/deprecated/gallocator.c | 104 + glib/deprecated/gallocator.h | 88 + glib/deprecated/gcache.c | 350 + glib/deprecated/gcache.h | 75 + glib/deprecated/gcompletion.c | 503 + glib/deprecated/gcompletion.h | 83 + glib/deprecated/gmain.h | 135 + glib/deprecated/grel.c | 685 + glib/deprecated/grel.h | 105 + glib/deprecated/gthread-deprecated.c | 1578 ++ glib/deprecated/gthread.h | 293 + glib/dirent/dirent-zip | 19 + glib/dirent/dirent.c | 341 + glib/dirent/dirent.h | 127 + glib/dirent/wdirent.c | 3 + glib/docs.c | 2636 ++ glib/galloca.h | 145 + glib/garcbox.c | 381 + glib/garray.c | 2575 ++ glib/garray.h | 281 + glib/gasyncqueue.c | 906 + glib/gasyncqueue.h | 124 + glib/gasyncqueueprivate.h | 29 + glib/gatomic.c | 967 + glib/gatomic.h | 476 + glib/gbacktrace.c | 455 + glib/gbacktrace.h | 72 + glib/gbase64.c | 462 + glib/gbase64.h | 61 + glib/gbitlock.c | 563 + glib/gbitlock.h | 76 + glib/gbookmarkfile.c | 4022 +++ glib/gbookmarkfile.h | 295 + glib/gbsearcharray.h | 299 + glib/gbytes.c | 612 + glib/gbytes.h | 97 + glib/gcharset.c | 838 + glib/gcharset.h | 47 + glib/gcharsetprivate.h | 34 + glib/gchecksum.c | 1864 ++ glib/gchecksum.h | 104 + glib/gconstructor.h | 159 + glib/gconvert.c | 2066 ++ glib/gconvert.h | 177 + glib/gconvertprivate.h | 40 + glib/gdataset.c | 1251 + glib/gdataset.h | 150 + glib/gdatasetprivate.h | 42 + glib/gdate.c | 2755 ++ glib/gdate.h | 307 + glib/gdatetime.c | 3527 +++ glib/gdatetime.h | 273 + glib/gdir.c | 330 + glib/gdir.h | 52 + glib/gen-unicode-tables.pl | 1552 ++ glib/genviron.c | 701 + glib/genviron.h | 63 + glib/gerror.c | 1157 + glib/gerror.h | 261 + glib/gfileutils.c | 2978 ++ glib/gfileutils.h | 221 + glib/ggettext.c | 642 + glib/ggettext.h | 63 + glib/ghash.c | 2533 ++ glib/ghash.h | 190 + glib/ghmac.c | 441 + glib/ghmac.h | 83 + glib/ghook.c | 1050 + glib/ghook.h | 202 + glib/ghostutils.c | 888 + glib/ghostutils.h | 43 + glib/gi18n-lib.h | 41 + glib/gi18n.h | 37 + glib/giochannel.c | 2582 ++ glib/giochannel.h | 405 + glib/giounix.c | 657 + glib/giowin32.c | 2241 ++ glib/gkeyfile.c | 4710 ++++ glib/gkeyfile.h | 330 + glib/glib-autocleanups.h | 103 + glib/glib-init.c | 458 + glib/glib-init.h | 48 + glib/glib-mirroring-tab/gen-mirroring-tab.c | 230 + glib/glib-mirroring-tab/packtab.c | 422 + glib/glib-mirroring-tab/packtab.h | 48 + glib/glib-object.h | 44 + glib/glib-private.c | 62 + glib/glib-private.h | 204 + glib/glib-typeof.h | 43 + glib/glib-unix.c | 546 + glib/glib-unix.h | 123 + glib/glib.h | 119 + glib/glib.rc.in | 30 + glib/glib.stp.in | 645 + glib/glib_gdb.py | 300 + glib/glib_probes.d | 50 + glib/glib_trace.h | 43 + glib/glibconfig.h | 294 + glib/glibconfig.h.in | 210 + glib/glibintl.h | 44 + glib/glist.c | 1365 + glib/glist.h | 177 + glib/gmacros.h | 1282 + glib/gmain-internal.h | 33 + glib/gmain.c | 6233 +++++ glib/gmain.h | 835 + glib/gmappedfile.c | 428 + glib/gmappedfile.h | 58 + glib/gmarkup.c | 2976 ++ glib/gmarkup.h | 263 + glib/gmem.c | 741 + glib/gmem.h | 414 + glib/gmessages.c | 3495 +++ glib/gmessages.h | 688 + glib/gmirroringtable.h | 945 + glib/gnode.c | 1280 + glib/gnode.h | 307 + glib/gnulib/arg-nonnull.h | 26 + glib/gnulib/asnprintf.c | 36 + glib/gnulib/c++defs.h | 316 + glib/gnulib/float+.h | 147 + glib/gnulib/fpucw.h | 108 + glib/gnulib/frexp.c | 22 + glib/gnulib/frexpl.c | 21 + glib/gnulib/g-gnulib.h | 46 + glib/gnulib/glib-gnulib.patch | 460 + glib/gnulib/gnulib_math.h.in | 2451 ++ glib/gnulib/isinf.c | 30 + glib/gnulib/isnan.c | 189 + glib/gnulib/isnand-nolibm.h | 33 + glib/gnulib/isnand.c | 22 + glib/gnulib/isnanf-nolibm.h | 40 + glib/gnulib/isnanf.c | 20 + glib/gnulib/isnanl-nolibm.h | 33 + glib/gnulib/isnanl.c | 23 + glib/gnulib/printf-args.c | 189 + glib/gnulib/printf-args.h | 158 + glib/gnulib/printf-frexp.c | 190 + glib/gnulib/printf-frexp.h | 23 + glib/gnulib/printf-frexpl.c | 37 + glib/gnulib/printf-frexpl.h | 23 + glib/gnulib/printf-parse.c | 640 + glib/gnulib/printf-parse.h | 193 + glib/gnulib/printf.c | 145 + glib/gnulib/printf.h | 52 + glib/gnulib/signbitd.c | 64 + glib/gnulib/signbitf.c | 64 + glib/gnulib/signbitl.c | 64 + glib/gnulib/vasnprintf.c | 5632 ++++ glib/gnulib/vasnprintf.h | 79 + glib/gnulib/verify.h | 283 + glib/gnulib/xsize.c | 3 + glib/gnulib/xsize.h | 119 + glib/goption.c | 2761 ++ glib/goption.h | 406 + glib/gosxutils.m | 56 + glib/gpattern.c | 527 + glib/gpattern.h | 63 + glib/gpoll.c | 557 + glib/gpoll.h | 120 + glib/gprimes.c | 96 + glib/gprimes.h | 50 + glib/gprintf.c | 370 + glib/gprintf.h | 57 + glib/gprintfint.h | 56 + glib/gqsort.c | 303 + glib/gqsort.h | 45 + glib/gquark.c | 374 + glib/gquark.h | 68 + glib/gqueue.c | 1177 + glib/gqueue.h | 203 + glib/grand.c | 730 + glib/grand.h | 99 + glib/grcbox.c | 502 + glib/grcbox.h | 89 + glib/grcboxprivate.h | 71 + glib/grefcount.c | 294 + glib/grefcount.h | 123 + glib/grefstring.c | 302 + glib/grefstring.h | 57 + glib/gregex.c | 3668 +++ glib/gregex.h | 618 + glib/gscanner.c | 2267 ++ glib/gscanner.h | 299 + glib/gscripttable.h | 3342 +++ glib/gsequence.c | 2085 ++ glib/gsequence.h | 173 + glib/gshell.c | 731 + glib/gshell.h | 57 + glib/gslice.c | 1927 ++ glib/gslice.h | 115 + glib/gslist.c | 1117 + glib/gslist.h | 164 + glib/gspawn-private.h | 115 + glib/gspawn-win32-helper.c | 462 + glib/gspawn-win32.c | 1589 ++ glib/gspawn.c | 2890 ++ glib/gspawn.h | 288 + glib/gstdio-private.c | 166 + glib/gstdio.c | 1784 ++ glib/gstdio.h | 181 + glib/gstdioprivate.h | 72 + glib/gstrfuncs.c | 3500 +++ glib/gstrfuncs.h | 374 + glib/gstring.c | 1266 + glib/gstring.h | 192 + glib/gstringchunk.c | 298 + glib/gstringchunk.h | 57 + glib/gstrvbuilder.c | 178 + glib/gstrvbuilder.h | 67 + glib/gtester-report.in | 498 + glib/gtester.c | 765 + glib/gtestutils.c | 4465 +++ glib/gtestutils.h | 707 + glib/gthread-posix.c | 1639 ++ glib/gthread-win32.c | 753 + glib/gthread.c | 1121 + glib/gthread.h | 601 + glib/gthreadpool.c | 1221 + glib/gthreadpool.h | 103 + glib/gthreadprivate.h | 89 + glib/gtimer.c | 657 + glib/gtimer.h | 78 + glib/gtimezone.c | 2456 ++ glib/gtimezone.h | 96 + glib/gtrace-private.h | 80 + glib/gtrace.c | 177 + glib/gtranslit-data.h | 12 + glib/gtranslit.c | 408 + glib/gtrashstack.c | 152 + glib/gtrashstack.h | 58 + glib/gtree.c | 1792 ++ glib/gtree.h | 179 + glib/gtypes.h | 587 + glib/gunibreak.c | 59 + glib/gunibreak.h | 26020 ++++++++++++++++++ glib/gunichartables.h | 20122 ++++++++++++++ glib/gunicode.h | 962 + glib/gunicodeprivate.h | 32 + glib/gunicollate.c | 687 + glib/gunicomp.h | 1008 + glib/gunidecomp.c | 767 + glib/gunidecomp.h | 14022 ++++++++++ glib/guniprop.c | 1575 ++ glib/guri.c | 2890 ++ glib/guri.h | 418 + glib/guriprivate.h | 36 + glib/gutf8.c | 1870 ++ glib/gutils.c | 3209 +++ glib/gutils.h | 489 + glib/gutilsprivate.h | 57 + glib/guuid.c | 211 + glib/guuid.h | 42 + glib/gvalgrind.h | 32 + glib/gvariant-core.c | 1366 + glib/gvariant-core.h | 42 + glib/gvariant-internal.h | 68 + glib/gvariant-parser.c | 2878 ++ glib/gvariant-serialiser.c | 1926 ++ glib/gvariant-serialiser.h | 97 + glib/gvariant.c | 6197 +++++ glib/gvariant.h | 539 + glib/gvarianttype.c | 1290 + glib/gvarianttype.h | 382 + glib/gvarianttypeinfo.c | 894 + glib/gvarianttypeinfo.h | 161 + glib/gversion.c | 193 + glib/gversion.h | 55 + glib/gversionmacros.h | 1211 + glib/gwakeup.c | 274 + glib/gwakeup.h | 35 + glib/gwin32-private.c | 78 + glib/gwin32.c | 1458 + glib/gwin32.h | 140 + glib/libcharset/.gitignore | 3 + glib/libcharset/codeset.m4 | 21 + glib/libcharset/config.charset | 672 + glib/libcharset/glibc21.m4 | 30 + glib/libcharset/libcharset-glib.patch | 77 + glib/libcharset/libcharset.h | 46 + glib/libcharset/localcharset.c | 461 + glib/libcharset/localcharset.h | 43 + glib/libcharset/make-patch.sh | 28 + glib/libcharset/ref-add.sin | 31 + glib/libcharset/ref-del.sin | 26 + glib/libcharset/update.sh | 33 + glib/libglib-gdb.py.in | 10 + glib/update-gtranslit.py | 456 + glib/valgrind.h | 6648 +++++ glib/win_iconv.c | 2098 ++ 290 files changed, 258444 insertions(+) create mode 100644 glib/deprecated/gallocator.c create mode 100644 glib/deprecated/gallocator.h create mode 100644 glib/deprecated/gcache.c create mode 100644 glib/deprecated/gcache.h create mode 100644 glib/deprecated/gcompletion.c create mode 100644 glib/deprecated/gcompletion.h create mode 100644 glib/deprecated/gmain.h create mode 100644 glib/deprecated/grel.c create mode 100644 glib/deprecated/grel.h create mode 100644 glib/deprecated/gthread-deprecated.c create mode 100644 glib/deprecated/gthread.h create mode 100644 glib/dirent/dirent-zip create mode 100644 glib/dirent/dirent.c create mode 100644 glib/dirent/dirent.h create mode 100644 glib/dirent/wdirent.c create mode 100644 glib/docs.c create mode 100644 glib/galloca.h create mode 100644 glib/garcbox.c create mode 100644 glib/garray.c create mode 100644 glib/garray.h create mode 100644 glib/gasyncqueue.c create mode 100644 glib/gasyncqueue.h create mode 100644 glib/gasyncqueueprivate.h create mode 100644 glib/gatomic.c create mode 100644 glib/gatomic.h create mode 100644 glib/gbacktrace.c create mode 100644 glib/gbacktrace.h create mode 100644 glib/gbase64.c create mode 100644 glib/gbase64.h create mode 100644 glib/gbitlock.c create mode 100644 glib/gbitlock.h create mode 100644 glib/gbookmarkfile.c create mode 100644 glib/gbookmarkfile.h create mode 100644 glib/gbsearcharray.h create mode 100644 glib/gbytes.c create mode 100644 glib/gbytes.h create mode 100644 glib/gcharset.c create mode 100644 glib/gcharset.h create mode 100644 glib/gcharsetprivate.h create mode 100644 glib/gchecksum.c create mode 100644 glib/gchecksum.h create mode 100644 glib/gconstructor.h create mode 100644 glib/gconvert.c create mode 100644 glib/gconvert.h create mode 100644 glib/gconvertprivate.h create mode 100644 glib/gdataset.c create mode 100644 glib/gdataset.h create mode 100644 glib/gdatasetprivate.h create mode 100644 glib/gdate.c create mode 100644 glib/gdate.h create mode 100644 glib/gdatetime.c create mode 100644 glib/gdatetime.h create mode 100644 glib/gdir.c create mode 100644 glib/gdir.h create mode 100755 glib/gen-unicode-tables.pl create mode 100644 glib/genviron.c create mode 100644 glib/genviron.h create mode 100644 glib/gerror.c create mode 100644 glib/gerror.h create mode 100644 glib/gfileutils.c create mode 100644 glib/gfileutils.h create mode 100644 glib/ggettext.c create mode 100644 glib/ggettext.h create mode 100644 glib/ghash.c create mode 100644 glib/ghash.h create mode 100644 glib/ghmac.c create mode 100644 glib/ghmac.h create mode 100644 glib/ghook.c create mode 100644 glib/ghook.h create mode 100644 glib/ghostutils.c create mode 100644 glib/ghostutils.h create mode 100644 glib/gi18n-lib.h create mode 100644 glib/gi18n.h create mode 100644 glib/giochannel.c create mode 100644 glib/giochannel.h create mode 100644 glib/giounix.c create mode 100644 glib/giowin32.c create mode 100644 glib/gkeyfile.c create mode 100644 glib/gkeyfile.h create mode 100644 glib/glib-autocleanups.h create mode 100644 glib/glib-init.c create mode 100644 glib/glib-init.h create mode 100644 glib/glib-mirroring-tab/gen-mirroring-tab.c create mode 100644 glib/glib-mirroring-tab/packtab.c create mode 100644 glib/glib-mirroring-tab/packtab.h create mode 100644 glib/glib-object.h create mode 100644 glib/glib-private.c create mode 100644 glib/glib-private.h create mode 100644 glib/glib-typeof.h create mode 100644 glib/glib-unix.c create mode 100644 glib/glib-unix.h create mode 100644 glib/glib.h create mode 100644 glib/glib.rc.in create mode 100644 glib/glib.stp.in create mode 100644 glib/glib_gdb.py create mode 100644 glib/glib_probes.d create mode 100644 glib/glib_trace.h create mode 100644 glib/glibconfig.h create mode 100644 glib/glibconfig.h.in create mode 100644 glib/glibintl.h create mode 100644 glib/glist.c create mode 100644 glib/glist.h create mode 100644 glib/gmacros.h create mode 100644 glib/gmain-internal.h create mode 100644 glib/gmain.c create mode 100644 glib/gmain.h create mode 100644 glib/gmappedfile.c create mode 100644 glib/gmappedfile.h create mode 100644 glib/gmarkup.c create mode 100644 glib/gmarkup.h create mode 100644 glib/gmem.c create mode 100644 glib/gmem.h create mode 100644 glib/gmessages.c create mode 100644 glib/gmessages.h create mode 100644 glib/gmirroringtable.h create mode 100644 glib/gnode.c create mode 100644 glib/gnode.h create mode 100644 glib/gnulib/arg-nonnull.h create mode 100644 glib/gnulib/asnprintf.c create mode 100644 glib/gnulib/c++defs.h create mode 100644 glib/gnulib/float+.h create mode 100644 glib/gnulib/fpucw.h create mode 100644 glib/gnulib/frexp.c create mode 100644 glib/gnulib/frexpl.c create mode 100644 glib/gnulib/g-gnulib.h create mode 100644 glib/gnulib/glib-gnulib.patch create mode 100644 glib/gnulib/gnulib_math.h.in create mode 100644 glib/gnulib/isinf.c create mode 100644 glib/gnulib/isnan.c create mode 100644 glib/gnulib/isnand-nolibm.h create mode 100644 glib/gnulib/isnand.c create mode 100644 glib/gnulib/isnanf-nolibm.h create mode 100644 glib/gnulib/isnanf.c create mode 100644 glib/gnulib/isnanl-nolibm.h create mode 100644 glib/gnulib/isnanl.c create mode 100644 glib/gnulib/printf-args.c create mode 100644 glib/gnulib/printf-args.h create mode 100644 glib/gnulib/printf-frexp.c create mode 100644 glib/gnulib/printf-frexp.h create mode 100644 glib/gnulib/printf-frexpl.c create mode 100644 glib/gnulib/printf-frexpl.h create mode 100644 glib/gnulib/printf-parse.c create mode 100644 glib/gnulib/printf-parse.h create mode 100644 glib/gnulib/printf.c create mode 100644 glib/gnulib/printf.h create mode 100644 glib/gnulib/signbitd.c create mode 100644 glib/gnulib/signbitf.c create mode 100644 glib/gnulib/signbitl.c create mode 100644 glib/gnulib/vasnprintf.c create mode 100644 glib/gnulib/vasnprintf.h create mode 100644 glib/gnulib/verify.h create mode 100644 glib/gnulib/xsize.c create mode 100644 glib/gnulib/xsize.h create mode 100644 glib/goption.c create mode 100644 glib/goption.h create mode 100644 glib/gosxutils.m create mode 100644 glib/gpattern.c create mode 100644 glib/gpattern.h create mode 100644 glib/gpoll.c create mode 100644 glib/gpoll.h create mode 100644 glib/gprimes.c create mode 100644 glib/gprimes.h create mode 100644 glib/gprintf.c create mode 100644 glib/gprintf.h create mode 100644 glib/gprintfint.h create mode 100644 glib/gqsort.c create mode 100644 glib/gqsort.h create mode 100644 glib/gquark.c create mode 100644 glib/gquark.h create mode 100644 glib/gqueue.c create mode 100644 glib/gqueue.h create mode 100644 glib/grand.c create mode 100644 glib/grand.h create mode 100644 glib/grcbox.c create mode 100644 glib/grcbox.h create mode 100644 glib/grcboxprivate.h create mode 100644 glib/grefcount.c create mode 100644 glib/grefcount.h create mode 100644 glib/grefstring.c create mode 100644 glib/grefstring.h create mode 100644 glib/gregex.c create mode 100644 glib/gregex.h create mode 100644 glib/gscanner.c create mode 100644 glib/gscanner.h create mode 100644 glib/gscripttable.h create mode 100644 glib/gsequence.c create mode 100644 glib/gsequence.h create mode 100644 glib/gshell.c create mode 100644 glib/gshell.h create mode 100644 glib/gslice.c create mode 100644 glib/gslice.h create mode 100644 glib/gslist.c create mode 100644 glib/gslist.h create mode 100644 glib/gspawn-private.h create mode 100644 glib/gspawn-win32-helper.c create mode 100644 glib/gspawn-win32.c create mode 100644 glib/gspawn.c create mode 100644 glib/gspawn.h create mode 100644 glib/gstdio-private.c create mode 100644 glib/gstdio.c create mode 100644 glib/gstdio.h create mode 100644 glib/gstdioprivate.h create mode 100644 glib/gstrfuncs.c create mode 100644 glib/gstrfuncs.h create mode 100644 glib/gstring.c create mode 100644 glib/gstring.h create mode 100644 glib/gstringchunk.c create mode 100644 glib/gstringchunk.h create mode 100644 glib/gstrvbuilder.c create mode 100644 glib/gstrvbuilder.h create mode 100644 glib/gtester-report.in create mode 100644 glib/gtester.c create mode 100644 glib/gtestutils.c create mode 100644 glib/gtestutils.h create mode 100644 glib/gthread-posix.c create mode 100644 glib/gthread-win32.c create mode 100644 glib/gthread.c create mode 100644 glib/gthread.h create mode 100644 glib/gthreadpool.c create mode 100644 glib/gthreadpool.h create mode 100644 glib/gthreadprivate.h create mode 100644 glib/gtimer.c create mode 100644 glib/gtimer.h create mode 100644 glib/gtimezone.c create mode 100644 glib/gtimezone.h create mode 100644 glib/gtrace-private.h create mode 100644 glib/gtrace.c create mode 100644 glib/gtranslit-data.h create mode 100644 glib/gtranslit.c create mode 100644 glib/gtrashstack.c create mode 100644 glib/gtrashstack.h create mode 100644 glib/gtree.c create mode 100644 glib/gtree.h create mode 100644 glib/gtypes.h create mode 100644 glib/gunibreak.c create mode 100644 glib/gunibreak.h create mode 100644 glib/gunichartables.h create mode 100644 glib/gunicode.h create mode 100644 glib/gunicodeprivate.h create mode 100644 glib/gunicollate.c create mode 100644 glib/gunicomp.h create mode 100644 glib/gunidecomp.c create mode 100644 glib/gunidecomp.h create mode 100644 glib/guniprop.c create mode 100644 glib/guri.c create mode 100644 glib/guri.h create mode 100644 glib/guriprivate.h create mode 100644 glib/gutf8.c create mode 100644 glib/gutils.c create mode 100644 glib/gutils.h create mode 100644 glib/gutilsprivate.h create mode 100644 glib/guuid.c create mode 100644 glib/guuid.h create mode 100644 glib/gvalgrind.h create mode 100644 glib/gvariant-core.c create mode 100644 glib/gvariant-core.h create mode 100644 glib/gvariant-internal.h create mode 100644 glib/gvariant-parser.c create mode 100644 glib/gvariant-serialiser.c create mode 100644 glib/gvariant-serialiser.h create mode 100644 glib/gvariant.c create mode 100644 glib/gvariant.h create mode 100644 glib/gvarianttype.c create mode 100644 glib/gvarianttype.h create mode 100644 glib/gvarianttypeinfo.c create mode 100644 glib/gvarianttypeinfo.h create mode 100644 glib/gversion.c create mode 100644 glib/gversion.h create mode 100644 glib/gversionmacros.h create mode 100644 glib/gwakeup.c create mode 100644 glib/gwakeup.h create mode 100644 glib/gwin32-private.c create mode 100644 glib/gwin32.c create mode 100644 glib/gwin32.h create mode 100644 glib/libcharset/.gitignore create mode 100644 glib/libcharset/codeset.m4 create mode 100755 glib/libcharset/config.charset create mode 100644 glib/libcharset/glibc21.m4 create mode 100644 glib/libcharset/libcharset-glib.patch create mode 100644 glib/libcharset/libcharset.h create mode 100644 glib/libcharset/localcharset.c create mode 100644 glib/libcharset/localcharset.h create mode 100755 glib/libcharset/make-patch.sh create mode 100644 glib/libcharset/ref-add.sin create mode 100644 glib/libcharset/ref-del.sin create mode 100755 glib/libcharset/update.sh create mode 100644 glib/libglib-gdb.py.in create mode 100755 glib/update-gtranslit.py create mode 100644 glib/valgrind.h create mode 100644 glib/win_iconv.c diff --git a/glib/deprecated/gallocator.c b/glib/deprecated/gallocator.c new file mode 100644 index 0000000..66483b6 --- /dev/null +++ b/glib/deprecated/gallocator.c @@ -0,0 +1,104 @@ +/* + * 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" + +/* we know we are deprecated here, no need for warnings */ +#ifndef GLIB_DISABLE_DEPRECATION_WARNINGS +#define GLIB_DISABLE_DEPRECATION_WARNINGS +#endif + +#include "gallocator.h" + +#include +#include + +struct _GMemChunk { + guint alloc_size; /* the size of an atom */ +}; + +GMemChunk* +g_mem_chunk_new (const gchar *name, + gint atom_size, + gsize area_size, + gint type) +{ + GMemChunk *mem_chunk; + + g_return_val_if_fail (atom_size > 0, NULL); + + mem_chunk = g_slice_new (GMemChunk); + mem_chunk->alloc_size = atom_size; + + return mem_chunk; +} + +void +g_mem_chunk_destroy (GMemChunk *mem_chunk) +{ + g_return_if_fail (mem_chunk != NULL); + + g_slice_free (GMemChunk, mem_chunk); +} + +gpointer +g_mem_chunk_alloc (GMemChunk *mem_chunk) +{ + g_return_val_if_fail (mem_chunk != NULL, NULL); + + return g_slice_alloc (mem_chunk->alloc_size); +} + +gpointer +g_mem_chunk_alloc0 (GMemChunk *mem_chunk) +{ + g_return_val_if_fail (mem_chunk != NULL, NULL); + + return g_slice_alloc0 (mem_chunk->alloc_size); +} + +void +g_mem_chunk_free (GMemChunk *mem_chunk, + gpointer mem) +{ + g_return_if_fail (mem_chunk != NULL); + + g_slice_free1 (mem_chunk->alloc_size, mem); +} + +GAllocator* +g_allocator_new (const gchar *name, + guint n_preallocs) +{ + /* some (broken) GAllocator uses depend on non-NULL allocators */ + return (void *) 1; +} + +void g_allocator_free (GAllocator *allocator) { } + +void g_mem_chunk_clean (GMemChunk *mem_chunk) { } +void g_mem_chunk_reset (GMemChunk *mem_chunk) { } +void g_mem_chunk_print (GMemChunk *mem_chunk) { } +void g_mem_chunk_info (void) { } +void g_blow_chunks (void) { } + +void g_list_push_allocator (GAllocator *allocator) { } +void g_list_pop_allocator (void) { } + +void g_slist_push_allocator (GAllocator *allocator) { } +void g_slist_pop_allocator (void) { } + +void g_node_push_allocator (GAllocator *allocator) { } +void g_node_pop_allocator (void) { } diff --git a/glib/deprecated/gallocator.h b/glib/deprecated/gallocator.h new file mode 100644 index 0000000..005e92b --- /dev/null +++ b/glib/deprecated/gallocator.h @@ -0,0 +1,88 @@ +/* + * 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_ALLOCATOR_H__ +#define __G_ALLOCATOR_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +typedef struct _GAllocator GAllocator; +typedef struct _GMemChunk GMemChunk; + +#define G_ALLOC_ONLY 1 +#define G_ALLOC_AND_FREE 2 +#define G_ALLOCATOR_LIST 1 +#define G_ALLOCATOR_SLIST 2 +#define G_ALLOCATOR_NODE 3 + +#define g_chunk_new(type, chunk) ((type *) g_mem_chunk_alloc (chunk)) +#define g_chunk_new0(type, chunk) ((type *) g_mem_chunk_alloc0 (chunk)) +#define g_chunk_free(mem, mem_chunk) (g_mem_chunk_free (mem_chunk, mem)) +#define g_mem_chunk_create(type, x, y) (g_mem_chunk_new (NULL, sizeof (type), 0, 0)) + + +GLIB_DEPRECATED +GMemChunk * g_mem_chunk_new (const gchar *name, + gint atom_size, + gsize area_size, + gint type); +GLIB_DEPRECATED +void g_mem_chunk_destroy (GMemChunk *mem_chunk); +GLIB_DEPRECATED +gpointer g_mem_chunk_alloc (GMemChunk *mem_chunk); +GLIB_DEPRECATED +gpointer g_mem_chunk_alloc0 (GMemChunk *mem_chunk); +GLIB_DEPRECATED +void g_mem_chunk_free (GMemChunk *mem_chunk, + gpointer mem); +GLIB_DEPRECATED +void g_mem_chunk_clean (GMemChunk *mem_chunk); +GLIB_DEPRECATED +void g_mem_chunk_reset (GMemChunk *mem_chunk); +GLIB_DEPRECATED +void g_mem_chunk_print (GMemChunk *mem_chunk); +GLIB_DEPRECATED +void g_mem_chunk_info (void); +GLIB_DEPRECATED +void g_blow_chunks (void); + + +GLIB_DEPRECATED +GAllocator * g_allocator_new (const gchar *name, + guint n_preallocs); +GLIB_DEPRECATED +void g_allocator_free (GAllocator *allocator); +GLIB_DEPRECATED +void g_list_push_allocator (GAllocator *allocator); +GLIB_DEPRECATED +void g_list_pop_allocator (void); +GLIB_DEPRECATED +void g_slist_push_allocator (GAllocator *allocator); +GLIB_DEPRECATED +void g_slist_pop_allocator (void); +GLIB_DEPRECATED +void g_node_push_allocator (GAllocator *allocator); +GLIB_DEPRECATED +void g_node_pop_allocator (void); + +G_END_DECLS + +#endif /* __G_ALLOCATOR_H__ */ diff --git a/glib/deprecated/gcache.c b/glib/deprecated/gcache.c new file mode 100644 index 0000000..9e04145 --- /dev/null +++ b/glib/deprecated/gcache.c @@ -0,0 +1,350 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * MT safe + */ + +#include "config.h" + +/* we know we are deprecated here, no need for warnings */ +#ifndef GLIB_DISABLE_DEPRECATION_WARNINGS +#define GLIB_DISABLE_DEPRECATION_WARNINGS +#endif + +#include "gcache.h" + +#include "gslice.h" +#include "ghash.h" +#include "gtestutils.h" + +/** + * SECTION:caches + * @title: Caches + * @short_description: caches allow sharing of complex data structures + * to save resources + * + * A #GCache allows sharing of complex data structures, in order to + * save system resources. + * + * GCache uses keys and values. A GCache key describes the properties + * of a particular resource. A GCache value is the actual resource. + * + * GCache has been marked as deprecated, since this API is rarely + * used and not very actively maintained. + */ + +typedef struct _GCacheNode GCacheNode; + +struct _GCacheNode +{ + /* A reference counted node */ + gpointer value; + gint ref_count; +}; + +/** + * GCache: + * + * The #GCache struct is an opaque data structure containing + * information about a #GCache. It should only be accessed via the + * following functions. + * + * Deprecated:2.32: Use a #GHashTable instead + */ +struct _GCache +{ + /* Called to create a value from a key */ + GCacheNewFunc value_new_func; + + /* Called to destroy a value */ + GCacheDestroyFunc value_destroy_func; + + /* Called to duplicate a key */ + GCacheDupFunc key_dup_func; + + /* Called to destroy a key */ + GCacheDestroyFunc key_destroy_func; + + /* Associates keys with nodes */ + GHashTable *key_table; + + /* Associates nodes with keys */ + GHashTable *value_table; +}; + +static inline GCacheNode* +g_cache_node_new (gpointer value) +{ + GCacheNode *node = g_slice_new (GCacheNode); + node->value = value; + node->ref_count = 1; + return node; +} + +static inline void +g_cache_node_destroy (GCacheNode *node) +{ + g_slice_free (GCacheNode, node); +} + +/** + * g_cache_new: + * @value_new_func: a function to create a new object given a key. + * This is called by g_cache_insert() if an object + * with the given key does not already exist + * @value_destroy_func: a function to destroy an object. It is called + * by g_cache_remove() when the object is no + * longer needed (i.e. its reference count drops + * to 0) + * @key_dup_func: a function to copy a key. It is called by + * g_cache_insert() if the key does not already exist in + * the #GCache + * @key_destroy_func: a function to destroy a key. It is called by + * g_cache_remove() when the object is no longer + * needed (i.e. its reference count drops to 0) + * @hash_key_func: a function to create a hash value from a key + * @hash_value_func: a function to create a hash value from a value + * @key_equal_func: a function to compare two keys. It should return + * %TRUE if the two keys are equivalent + * + * Creates a new #GCache. + * + * Returns: a new #GCache + * + * Deprecated:2.32: Use a #GHashTable instead + */ + +/** + * GCacheNewFunc: + * @key: a #GCache key + * + * Specifies the type of the @value_new_func function passed to + * g_cache_new(). It is passed a #GCache key and should create the + * value corresponding to the key. + * + * Returns: a new #GCache value corresponding to the key. + */ + +/** + * GCacheDestroyFunc: + * @value: the #GCache value to destroy + * + * Specifies the type of the @value_destroy_func and @key_destroy_func + * functions passed to g_cache_new(). The functions are passed a + * pointer to the #GCache key or #GCache value and should free any + * memory and other resources associated with it. + */ + +/** + * GCacheDupFunc: + * @value: the #GCache key to destroy (__not__ a + * #GCache value as it seems) + * + * Specifies the type of the @key_dup_func function passed to + * g_cache_new(). The function is passed a key + * (__not__ a value as the prototype implies) and + * should return a duplicate of the key. + * + * Returns: a copy of the #GCache key + */ +GCache* +g_cache_new (GCacheNewFunc value_new_func, + GCacheDestroyFunc value_destroy_func, + GCacheDupFunc key_dup_func, + GCacheDestroyFunc key_destroy_func, + GHashFunc hash_key_func, + GHashFunc hash_value_func, + GEqualFunc key_equal_func) +{ + GCache *cache; + + g_return_val_if_fail (value_new_func != NULL, NULL); + g_return_val_if_fail (value_destroy_func != NULL, NULL); + g_return_val_if_fail (key_dup_func != NULL, NULL); + g_return_val_if_fail (key_destroy_func != NULL, NULL); + g_return_val_if_fail (hash_key_func != NULL, NULL); + g_return_val_if_fail (hash_value_func != NULL, NULL); + g_return_val_if_fail (key_equal_func != NULL, NULL); + + cache = g_slice_new (GCache); + cache->value_new_func = value_new_func; + cache->value_destroy_func = value_destroy_func; + cache->key_dup_func = key_dup_func; + cache->key_destroy_func = key_destroy_func; + cache->key_table = g_hash_table_new (hash_key_func, key_equal_func); + cache->value_table = g_hash_table_new (hash_value_func, NULL); + + return cache; +} + +/** + * g_cache_destroy: + * @cache: a #GCache + * + * Frees the memory allocated for the #GCache. + * + * Note that it does not destroy the keys and values which were + * contained in the #GCache. + * + * Deprecated:2.32: Use a #GHashTable instead + */ +void +g_cache_destroy (GCache *cache) +{ + g_return_if_fail (cache != NULL); + + g_hash_table_destroy (cache->key_table); + g_hash_table_destroy (cache->value_table); + g_slice_free (GCache, cache); +} + +/** + * g_cache_insert: + * @cache: a #GCache + * @key: a key describing a #GCache object + * + * Gets the value corresponding to the given key, creating it if + * necessary. It first checks if the value already exists in the + * #GCache, by using the @key_equal_func function passed to + * g_cache_new(). If it does already exist it is returned, and its + * reference count is increased by one. If the value does not currently + * exist, if is created by calling the @value_new_func. The key is + * duplicated by calling @key_dup_func and the duplicated key and value + * are inserted into the #GCache. + * + * Returns: a pointer to a #GCache value + * + * Deprecated:2.32: Use a #GHashTable instead + */ +gpointer +g_cache_insert (GCache *cache, + gpointer key) +{ + GCacheNode *node; + gpointer value; + + g_return_val_if_fail (cache != NULL, NULL); + + node = g_hash_table_lookup (cache->key_table, key); + if (node) + { + node->ref_count += 1; + return node->value; + } + + key = (* cache->key_dup_func) (key); + value = (* cache->value_new_func) (key); + node = g_cache_node_new (value); + + g_hash_table_insert (cache->key_table, key, node); + g_hash_table_insert (cache->value_table, value, key); + + return node->value; +} + +/** + * g_cache_remove: + * @cache: a #GCache + * @value: the value to remove + * + * Decreases the reference count of the given value. If it drops to 0 + * then the value and its corresponding key are destroyed, using the + * @value_destroy_func and @key_destroy_func passed to g_cache_new(). + * + * Deprecated:2.32: Use a #GHashTable instead + */ +void +g_cache_remove (GCache *cache, + gconstpointer value) +{ + GCacheNode *node; + gpointer key; + + g_return_if_fail (cache != NULL); + + key = g_hash_table_lookup (cache->value_table, value); + node = g_hash_table_lookup (cache->key_table, key); + + g_return_if_fail (node != NULL); + + node->ref_count -= 1; + if (node->ref_count == 0) + { + g_hash_table_remove (cache->value_table, value); + g_hash_table_remove (cache->key_table, key); + + (* cache->key_destroy_func) (key); + (* cache->value_destroy_func) (node->value); + g_cache_node_destroy (node); + } +} + +/** + * g_cache_key_foreach: + * @cache: a #GCache + * @func: the function to call with each #GCache key + * @user_data: user data to pass to the function + * + * Calls the given function for each of the keys in the #GCache. + * + * NOTE @func is passed three parameters, the value and key of a cache + * entry and the @user_data. The order of value and key is different + * from the order in which g_hash_table_foreach() passes key-value + * pairs to its callback function ! + * + * Deprecated:2.32: Use a #GHashTable instead + */ +void +g_cache_key_foreach (GCache *cache, + GHFunc func, + gpointer user_data) +{ + g_return_if_fail (cache != NULL); + g_return_if_fail (func != NULL); + + g_hash_table_foreach (cache->value_table, func, user_data); +} + +/** + * g_cache_value_foreach: + * @cache: a #GCache + * @func: the function to call with each #GCache value + * @user_data: user data to pass to the function + * + * Calls the given function for each of the values in the #GCache. + * + * Deprecated:2.10: The reason is that it passes pointers to internal + * data structures to @func; use g_cache_key_foreach() instead + */ +void +g_cache_value_foreach (GCache *cache, + GHFunc func, + gpointer user_data) +{ + g_return_if_fail (cache != NULL); + g_return_if_fail (func != NULL); + + g_hash_table_foreach (cache->key_table, func, user_data); +} diff --git a/glib/deprecated/gcache.h b/glib/deprecated/gcache.h new file mode 100644 index 0000000..e1c1f2c --- /dev/null +++ b/glib/deprecated/gcache.h @@ -0,0 +1,75 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_CACHE_H__ +#define __G_CACHE_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +typedef struct _GCache GCache GLIB_DEPRECATED_TYPE_IN_2_26_FOR(GHashTable); + +typedef gpointer (*GCacheNewFunc) (gpointer key) GLIB_DEPRECATED_TYPE_IN_2_26; +typedef gpointer (*GCacheDupFunc) (gpointer value) GLIB_DEPRECATED_TYPE_IN_2_26; +typedef void (*GCacheDestroyFunc) (gpointer value) GLIB_DEPRECATED_TYPE_IN_2_26; + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + +/* Caches + */ +GLIB_DEPRECATED +GCache* g_cache_new (GCacheNewFunc value_new_func, + GCacheDestroyFunc value_destroy_func, + GCacheDupFunc key_dup_func, + GCacheDestroyFunc key_destroy_func, + GHashFunc hash_key_func, + GHashFunc hash_value_func, + GEqualFunc key_equal_func); +GLIB_DEPRECATED +void g_cache_destroy (GCache *cache); +GLIB_DEPRECATED +gpointer g_cache_insert (GCache *cache, + gpointer key); +GLIB_DEPRECATED +void g_cache_remove (GCache *cache, + gconstpointer value); +GLIB_DEPRECATED +void g_cache_key_foreach (GCache *cache, + GHFunc func, + gpointer user_data); +GLIB_DEPRECATED +void g_cache_value_foreach (GCache *cache, + GHFunc func, + gpointer user_data); + +G_GNUC_END_IGNORE_DEPRECATIONS + +G_END_DECLS + +#endif /* __G_CACHE_H__ */ diff --git a/glib/deprecated/gcompletion.c b/glib/deprecated/gcompletion.c new file mode 100644 index 0000000..5f0979b --- /dev/null +++ b/glib/deprecated/gcompletion.c @@ -0,0 +1,503 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * MT safe + */ + +#include "config.h" + +/* we know we are deprecated here, no need for warnings */ +#ifndef GLIB_DISABLE_DEPRECATION_WARNINGS +#define GLIB_DISABLE_DEPRECATION_WARNINGS +#endif + +#include "gcompletion.h" + +#include +#include +#include + +#include + +/** + * SECTION:completion + * @title: Automatic String Completion + * @short_description: support for automatic completion using a group + * of target strings + * + * #GCompletion provides support for automatic completion of a string + * using any group of target strings. It is typically used for file + * name completion as is common in many UNIX shells. + * + * A #GCompletion is created using g_completion_new(). Target items are + * added and removed with g_completion_add_items(), + * g_completion_remove_items() and g_completion_clear_items(). A + * completion attempt is requested with g_completion_complete() or + * g_completion_complete_utf8(). When no longer needed, the + * #GCompletion is freed with g_completion_free(). + * + * Items in the completion can be simple strings (e.g. filenames), or + * pointers to arbitrary data structures. If data structures are used + * you must provide a #GCompletionFunc in g_completion_new(), which + * retrieves the item's string from the data structure. You can change + * the way in which strings are compared by setting a different + * #GCompletionStrncmpFunc in g_completion_set_compare(). + * + * GCompletion has been marked as deprecated, since this API is rarely + * used and not very actively maintained. + **/ + +/** + * GCompletion: + * @items: list of target items (strings or data structures). + * @func: function which is called to get the string associated with a + * target item. It is %NULL if the target items are strings. + * @prefix: the last prefix passed to g_completion_complete() or + * g_completion_complete_utf8(). + * @cache: the list of items which begin with @prefix. + * @strncmp_func: The function to use when comparing strings. Use + * g_completion_set_compare() to modify this function. + * + * The data structure used for automatic completion. + **/ + +/** + * GCompletionFunc: + * @Param1: the completion item. + * + * Specifies the type of the function passed to g_completion_new(). It + * should return the string corresponding to the given target item. + * This is used when you use data structures as #GCompletion items. + * + * Returns: the string corresponding to the item. + **/ + +/** + * GCompletionStrncmpFunc: + * @s1: string to compare with @s2. + * @s2: string to compare with @s1. + * @n: maximal number of bytes to compare. + * + * Specifies the type of the function passed to + * g_completion_set_compare(). This is used when you use strings as + * #GCompletion items. + * + * Returns: an integer less than, equal to, or greater than zero if + * the first @n bytes of @s1 is found, respectively, to be + * less than, to match, or to be greater than the first @n + * bytes of @s2. + **/ + +static void completion_check_cache (GCompletion* cmp, + gchar** new_prefix); + +/** + * g_completion_new: + * @func: the function to be called to return the string representing + * an item in the #GCompletion, or %NULL if strings are going to + * be used as the #GCompletion items. + * + * Creates a new #GCompletion. + * + * Returns: the new #GCompletion. + **/ +GCompletion* +g_completion_new (GCompletionFunc func) +{ + GCompletion* gcomp; + + gcomp = g_new (GCompletion, 1); + gcomp->items = NULL; + gcomp->cache = NULL; + gcomp->prefix = NULL; + gcomp->func = func; + gcomp->strncmp_func = strncmp; + + return gcomp; +} + +/** + * g_completion_add_items: + * @cmp: the #GCompletion. + * @items: (transfer none): the list of items to add. + * + * Adds items to the #GCompletion. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_completion_add_items (GCompletion* cmp, + GList* items) +{ + GList* it; + + g_return_if_fail (cmp != NULL); + + /* optimize adding to cache? */ + if (cmp->cache) + { + g_list_free (cmp->cache); + cmp->cache = NULL; + } + + if (cmp->prefix) + { + g_free (cmp->prefix); + cmp->prefix = NULL; + } + + it = items; + while (it) + { + cmp->items = g_list_prepend (cmp->items, it->data); + it = it->next; + } +} + +/** + * g_completion_remove_items: + * @cmp: the #GCompletion. + * @items: (transfer none): the items to remove. + * + * Removes items from a #GCompletion. The items are not freed, so if the memory + * was dynamically allocated, free @items with g_list_free_full() after calling + * this function. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_completion_remove_items (GCompletion* cmp, + GList* items) +{ + GList* it; + + g_return_if_fail (cmp != NULL); + + it = items; + while (cmp->items && it) + { + cmp->items = g_list_remove (cmp->items, it->data); + it = it->next; + } + + it = items; + while (cmp->cache && it) + { + cmp->cache = g_list_remove(cmp->cache, it->data); + it = it->next; + } +} + +/** + * g_completion_clear_items: + * @cmp: the #GCompletion. + * + * Removes all items from the #GCompletion. The items are not freed, so if the + * memory was dynamically allocated, it should be freed after calling this + * function. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_completion_clear_items (GCompletion* cmp) +{ + g_return_if_fail (cmp != NULL); + + g_list_free (cmp->items); + cmp->items = NULL; + g_list_free (cmp->cache); + cmp->cache = NULL; + g_free (cmp->prefix); + cmp->prefix = NULL; +} + +static void +completion_check_cache (GCompletion* cmp, + gchar** new_prefix) +{ + GList* list; + gsize len; + gsize i; + gsize plen; + gchar* postfix; + gchar* s; + + if (!new_prefix) + return; + if (!cmp->cache) + { + *new_prefix = NULL; + return; + } + + len = strlen(cmp->prefix); + list = cmp->cache; + s = cmp->func ? cmp->func (list->data) : (gchar*) list->data; + postfix = s + len; + plen = strlen (postfix); + list = list->next; + + while (list && plen) + { + s = cmp->func ? cmp->func (list->data) : (gchar*) list->data; + s += len; + for (i = 0; i < plen; ++i) + { + if (postfix[i] != s[i]) + break; + } + plen = i; + list = list->next; + } + + *new_prefix = g_new0 (gchar, len + plen + 1); + strncpy (*new_prefix, cmp->prefix, len); + strncpy (*new_prefix + len, postfix, plen); +} + +/** + * g_completion_complete_utf8: + * @cmp: the #GCompletion + * @prefix: the prefix string, typically used by the user, which is compared + * with each of the items + * @new_prefix: if non-%NULL, returns the longest prefix which is common to all + * items that matched @prefix, or %NULL if no items matched @prefix. + * This string should be freed when no longer needed. + * + * Attempts to complete the string @prefix using the #GCompletion target items. + * In contrast to g_completion_complete(), this function returns the largest common + * prefix that is a valid UTF-8 string, omitting a possible common partial + * character. + * + * You should use this function instead of g_completion_complete() if your + * items are UTF-8 strings. + * + * Returns: (element-type utf8) (transfer none): the list of items whose strings begin with @prefix. This should + * not be changed. + * + * Since: 2.4 + * + * Deprecated: 2.26: Rarely used API + **/ +GList* +g_completion_complete_utf8 (GCompletion *cmp, + const gchar *prefix, + gchar **new_prefix) +{ + GList *list; + gchar *p, *q; + + list = g_completion_complete (cmp, prefix, new_prefix); + + if (new_prefix && *new_prefix) + { + p = *new_prefix + strlen (*new_prefix); + q = g_utf8_find_prev_char (*new_prefix, p); + + switch (g_utf8_get_char_validated (q, p - q)) + { + case (gunichar)-2: + case (gunichar)-1: + *q = 0; + break; + default: ; + } + + } + + return list; +} + +/** + * g_completion_complete: + * @cmp: the #GCompletion. + * @prefix: the prefix string, typically typed by the user, which is + * compared with each of the items. + * @new_prefix: if non-%NULL, returns the longest prefix which is + * common to all items that matched @prefix, or %NULL if + * no items matched @prefix. This string should be freed + * when no longer needed. + * + * Attempts to complete the string @prefix using the #GCompletion + * target items. + * + * Returns: (transfer none): the list of items whose strings begin with + * @prefix. This should not be changed. + * + * Deprecated: 2.26: Rarely used API + **/ +GList* +g_completion_complete (GCompletion* cmp, + const gchar* prefix, + gchar** new_prefix) +{ + gsize plen, len; + gboolean done = FALSE; + GList* list; + + g_return_val_if_fail (cmp != NULL, NULL); + g_return_val_if_fail (prefix != NULL, NULL); + + len = strlen (prefix); + if (cmp->prefix && cmp->cache) + { + plen = strlen (cmp->prefix); + if (plen <= len && ! cmp->strncmp_func (prefix, cmp->prefix, plen)) + { + /* use the cache */ + list = cmp->cache; + while (list) + { + GList *next = list->next; + + if (cmp->strncmp_func (prefix, + cmp->func ? cmp->func (list->data) : (gchar*) list->data, + len)) + cmp->cache = g_list_delete_link (cmp->cache, list); + + list = next; + } + done = TRUE; + } + } + + if (!done) + { + /* normal code */ + g_list_free (cmp->cache); + cmp->cache = NULL; + list = cmp->items; + while (*prefix && list) + { + if (!cmp->strncmp_func (prefix, + cmp->func ? cmp->func (list->data) : (gchar*) list->data, + len)) + cmp->cache = g_list_prepend (cmp->cache, list->data); + list = list->next; + } + } + if (cmp->prefix) + { + g_free (cmp->prefix); + cmp->prefix = NULL; + } + if (cmp->cache) + cmp->prefix = g_strdup (prefix); + completion_check_cache (cmp, new_prefix); + + return *prefix ? cmp->cache : cmp->items; +} + +/** + * g_completion_free: + * @cmp: the #GCompletion. + * + * Frees all memory used by the #GCompletion. The items are not freed, so if + * the memory was dynamically allocated, it should be freed after calling this + * function. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_completion_free (GCompletion* cmp) +{ + g_return_if_fail (cmp != NULL); + + g_completion_clear_items (cmp); + g_free (cmp); +} + +/** + * g_completion_set_compare: + * @cmp: a #GCompletion. + * @strncmp_func: the string comparison function. + * + * Sets the function to use for string comparisons. The default string + * comparison function is strncmp(). + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_completion_set_compare(GCompletion *cmp, + GCompletionStrncmpFunc strncmp_func) +{ + cmp->strncmp_func = strncmp_func; +} + +#ifdef TEST_COMPLETION +#include +int +main (int argc, + char* argv[]) +{ + FILE *file; + gchar buf[1024]; + GList *list; + GList *result; + GList *tmp; + GCompletion *cmp; + gint i; + gchar *longp = NULL; + + if (argc < 3) + { + g_warning ("Usage: %s filename prefix1 [prefix2 ...]", + (argc > 0) ? argv[0] : "gcompletion"); + return 1; + } + + file = fopen (argv[1], "r"); + if (!file) + { + g_warning ("Cannot open %s", argv[1]); + return 1; + } + + cmp = g_completion_new (NULL); + list = g_list_alloc (); + while (fgets (buf, 1024, file)) + { + list->data = g_strdup (buf); + g_completion_add_items (cmp, list); + } + fclose (file); + + for (i = 2; i < argc; ++i) + { + printf ("COMPLETING: %s\n", argv[i]); + result = g_completion_complete (cmp, argv[i], &longp); + g_list_foreach (result, (GFunc) printf, NULL); + printf ("LONG MATCH: %s\n", longp); + g_free (longp); + longp = NULL; + } + + g_list_foreach (cmp->items, (GFunc) g_free, NULL); + g_completion_free (cmp); + g_list_free (list); + + return 0; +} +#endif diff --git a/glib/deprecated/gcompletion.h b/glib/deprecated/gcompletion.h new file mode 100644 index 0000000..2fd1f03 --- /dev/null +++ b/glib/deprecated/gcompletion.h @@ -0,0 +1,83 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_COMPLETION_H__ +#define __G_COMPLETION_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +typedef struct _GCompletion GCompletion; + +typedef gchar* (*GCompletionFunc) (gpointer); + +/* GCompletion + */ + +typedef gint (*GCompletionStrncmpFunc) (const gchar *s1, + const gchar *s2, + gsize n); + +struct _GCompletion +{ + GList* items; + GCompletionFunc func; + + gchar* prefix; + GList* cache; + GCompletionStrncmpFunc strncmp_func; +}; + +GLIB_DEPRECATED_IN_2_26 +GCompletion* g_completion_new (GCompletionFunc func); +GLIB_DEPRECATED_IN_2_26 +void g_completion_add_items (GCompletion* cmp, + GList* items); +GLIB_DEPRECATED_IN_2_26 +void g_completion_remove_items (GCompletion* cmp, + GList* items); +GLIB_DEPRECATED_IN_2_26 +void g_completion_clear_items (GCompletion* cmp); +GLIB_DEPRECATED_IN_2_26 +GList* g_completion_complete (GCompletion* cmp, + const gchar* prefix, + gchar** new_prefix); +GLIB_DEPRECATED_IN_2_26 +GList* g_completion_complete_utf8 (GCompletion *cmp, + const gchar* prefix, + gchar** new_prefix); +GLIB_DEPRECATED_IN_2_26 +void g_completion_set_compare (GCompletion *cmp, + GCompletionStrncmpFunc strncmp_func); +GLIB_DEPRECATED_IN_2_26 +void g_completion_free (GCompletion* cmp); + +G_END_DECLS + +#endif /* __G_COMPLETION_H__ */ diff --git a/glib/deprecated/gmain.h b/glib/deprecated/gmain.h new file mode 100644 index 0000000..5d08eb6 --- /dev/null +++ b/glib/deprecated/gmain.h @@ -0,0 +1,135 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_DEPRECATED_MAIN_H__ +#define __G_DEPRECATED_MAIN_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +/* ============== Compat main loop stuff ================== */ + +/** + * g_main_new: + * @is_running: set to %TRUE to indicate that the loop is running. This + * is not very important since calling g_main_run() will set this + * to %TRUE anyway. + * + * Creates a new #GMainLoop for th default main context. + * + * Returns: a new #GMainLoop + * + * Deprecated: 2.2: Use g_main_loop_new() instead + */ +#define g_main_new(is_running) g_main_loop_new (NULL, is_running) GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_main_loop_new) + +/** + * g_main_run: + * @loop: a #GMainLoop + * + * Runs a main loop until it stops running. + * + * Deprecated: 2.2: Use g_main_loop_run() instead + */ +#define g_main_run(loop) g_main_loop_run(loop) GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_main_loop_run) + +/** + * g_main_quit: + * @loop: a #GMainLoop + * + * Stops the #GMainLoop. + * If g_main_run() was called to run the #GMainLoop, it will now return. + * + * Deprecated: 2.2: Use g_main_loop_quit() instead + */ +#define g_main_quit(loop) g_main_loop_quit(loop) GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_main_loop_quit) + +/** + * g_main_destroy: + * @loop: a #GMainLoop + * + * Frees the memory allocated for the #GMainLoop. + * + * Deprecated: 2.2: Use g_main_loop_unref() instead + */ +#define g_main_destroy(loop) g_main_loop_unref(loop) GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_main_loop_unref) + +/** + * g_main_is_running: + * @loop: a #GMainLoop + * + * Checks if the main loop is running. + * + * Returns: %TRUE if the main loop is running + * + * Deprecated: 2.2: Use g_main_loop_is_running() instead + */ +#define g_main_is_running(loop) g_main_loop_is_running(loop) GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_main_loop_is_running) + +/** + * g_main_iteration: + * @may_block: set to %TRUE if it should block (i.e. wait) until an event + * source becomes ready. It will return after an event source has been + * processed. If set to %FALSE it will return immediately if no event + * source is ready to be processed. + * + * Runs a single iteration for the default #GMainContext. + * + * Returns: %TRUE if more events are pending. + * + * Deprecated: 2.2: Use g_main_context_iteration() instead. + */ +#define g_main_iteration(may_block) g_main_context_iteration (NULL, may_block) GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_main_context_iteration) + +/** + * g_main_pending: + * + * Checks if any events are pending for the default #GMainContext + * (i.e. ready to be processed). + * + * Returns: %TRUE if any events are pending. + * + * Deprecated: 2.2: Use g_main_context_pending() instead. + */ +#define g_main_pending() g_main_context_pending (NULL) GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_main_context_pending) + +/** + * g_main_set_poll_func: + * @func: the function to call to poll all file descriptors + * + * Sets the function to use for the handle polling of file descriptors + * for the default main context. + * + * Deprecated: 2.2: Use g_main_context_set_poll_func() again + */ +#define g_main_set_poll_func(func) g_main_context_set_poll_func (NULL, func) GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_main_context_set_poll_func) + +G_END_DECLS + +#endif /* __G_DEPRECATED_MAIN_H__ */ diff --git a/glib/deprecated/grel.c b/glib/deprecated/grel.c new file mode 100644 index 0000000..b48ec11 --- /dev/null +++ b/glib/deprecated/grel.c @@ -0,0 +1,685 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * MT safe + */ + +#include "config.h" + +/* we know we are deprecated here, no need for warnings */ +#ifndef GLIB_DISABLE_DEPRECATION_WARNINGS +#define GLIB_DISABLE_DEPRECATION_WARNINGS +#endif + +#include "grel.h" + +#include +#include +#include +#include +#include + +#include +#include + +/** + * SECTION:relations + * @title: Relations and Tuples + * @short_description: tables of data which can be indexed on any + * number of fields + * + * A #GRelation is a table of data which can be indexed on any number + * of fields, rather like simple database tables. A #GRelation contains + * a number of records, called tuples. Each record contains a number of + * fields. Records are not ordered, so it is not possible to find the + * record at a particular index. + * + * Note that #GRelation tables are currently limited to 2 fields. + * + * To create a GRelation, use g_relation_new(). + * + * To specify which fields should be indexed, use g_relation_index(). + * Note that this must be called before any tuples are added to the + * #GRelation. + * + * To add records to a #GRelation use g_relation_insert(). + * + * To determine if a given record appears in a #GRelation, use + * g_relation_exists(). Note that fields are compared directly, so + * pointers must point to the exact same position (i.e. different + * copies of the same string will not match.) + * + * To count the number of records which have a particular value in a + * given field, use g_relation_count(). + * + * To get all the records which have a particular value in a given + * field, use g_relation_select(). To access fields of the resulting + * records, use g_tuples_index(). To free the resulting records use + * g_tuples_destroy(). + * + * To delete all records which have a particular value in a given + * field, use g_relation_delete(). + * + * To destroy the #GRelation, use g_relation_destroy(). + * + * To help debug #GRelation objects, use g_relation_print(). + * + * GRelation has been marked as deprecated, since this API has never + * been fully implemented, is not very actively maintained and rarely + * used. + **/ + +typedef struct _GRealTuples GRealTuples; + +/** + * GRelation: + * + * The #GRelation struct is an opaque data structure to represent a + * [Relation][glib-Relations-and-Tuples]. It should + * only be accessed via the following functions. + **/ +struct _GRelation +{ + gint fields; + gint current_field; + + GHashTable *all_tuples; + GHashTable **hashed_tuple_tables; + + gint count; +}; + +/** + * GTuples: + * @len: the number of records that matched. + * + * The #GTuples struct is used to return records (or tuples) from the + * #GRelation by g_relation_select(). It only contains one public + * member - the number of records that matched. To access the matched + * records, you must use g_tuples_index(). + **/ +struct _GRealTuples +{ + gint len; + gint width; + gpointer *data; +}; + +static gboolean +tuple_equal_2 (gconstpointer v_a, + gconstpointer v_b) +{ + gpointer* a = (gpointer*) v_a; + gpointer* b = (gpointer*) v_b; + + return a[0] == b[0] && a[1] == b[1]; +} + +static guint +tuple_hash_2 (gconstpointer v_a) +{ +#if GLIB_SIZEOF_VOID_P > GLIB_SIZEOF_LONG + /* In practise this snippet has been written for 64-bit Windows + * where ints are 32 bits, pointers 64 bits. More exotic platforms + * need more tweaks. + */ + guint* a = (guint*) v_a; + + return (a[0] ^ a[1] ^ a[2] ^ a[3]); +#else + gpointer* a = (gpointer*) v_a; + + return (gulong)a[0] ^ (gulong)a[1]; +#endif +} + +static GHashFunc +tuple_hash (gint fields) +{ + switch (fields) + { + case 2: + return tuple_hash_2; + default: + g_error ("no tuple hash for %d", fields); + } + + return NULL; +} + +static GEqualFunc +tuple_equal (gint fields) +{ + switch (fields) + { + case 2: + return tuple_equal_2; + default: + g_error ("no tuple equal for %d", fields); + } + + return NULL; +} + +/** + * g_relation_new: + * @fields: the number of fields. + * + * Creates a new #GRelation with the given number of fields. Note that + * currently the number of fields must be 2. + * + * Returns: a new #GRelation. + * + * Deprecated: 2.26: Rarely used API + **/ +GRelation* +g_relation_new (gint fields) +{ + GRelation* rel = g_new0 (GRelation, 1); + + rel->fields = fields; + rel->all_tuples = g_hash_table_new (tuple_hash (fields), tuple_equal (fields)); + rel->hashed_tuple_tables = g_new0 (GHashTable*, fields); + + return rel; +} + +static void +relation_delete_value_tuple (gpointer tuple_key, + gpointer tuple_value, + gpointer user_data) +{ + GRelation *relation = user_data; + gpointer *tuple = tuple_value; + g_slice_free1 (relation->fields * sizeof (gpointer), tuple); +} + +static void +g_relation_free_array (gpointer key, gpointer value, gpointer user_data) +{ + g_hash_table_destroy ((GHashTable*) value); +} + +/** + * g_relation_destroy: + * @relation: a #GRelation. + * + * Destroys the #GRelation, freeing all memory allocated. However, it + * does not free memory allocated for the tuple data, so you should + * free that first if appropriate. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_relation_destroy (GRelation *relation) +{ + gint i; + + if (relation) + { + for (i = 0; i < relation->fields; i += 1) + { + if (relation->hashed_tuple_tables[i]) + { + g_hash_table_foreach (relation->hashed_tuple_tables[i], g_relation_free_array, NULL); + g_hash_table_destroy (relation->hashed_tuple_tables[i]); + } + } + + g_hash_table_foreach (relation->all_tuples, relation_delete_value_tuple, relation); + g_hash_table_destroy (relation->all_tuples); + + g_free (relation->hashed_tuple_tables); + g_free (relation); + } +} + +/** + * g_relation_index: + * @relation: a #GRelation. + * @field: the field to index, counting from 0. + * @hash_func: a function to produce a hash value from the field data. + * @key_equal_func: a function to compare two values of the given field. + * + * Creates an index on the given field. Note that this must be called + * before any records are added to the #GRelation. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_relation_index (GRelation *relation, + gint field, + GHashFunc hash_func, + GEqualFunc key_equal_func) +{ + g_return_if_fail (relation != NULL); + + g_return_if_fail (relation->count == 0 && relation->hashed_tuple_tables[field] == NULL); + + relation->hashed_tuple_tables[field] = g_hash_table_new (hash_func, key_equal_func); +} + +/** + * g_relation_insert: + * @relation: a #GRelation. + * @...: the fields of the record to add. These must match the + * number of fields in the #GRelation, and of type #gpointer + * or #gconstpointer. + * + * Inserts a record into a #GRelation. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_relation_insert (GRelation *relation, + ...) +{ + gpointer* tuple = g_slice_alloc (relation->fields * sizeof (gpointer)); + va_list args; + gint i; + + va_start (args, relation); + + for (i = 0; i < relation->fields; i += 1) + tuple[i] = va_arg (args, gpointer); + + va_end (args); + + g_hash_table_insert (relation->all_tuples, tuple, tuple); + + relation->count += 1; + + for (i = 0; i < relation->fields; i += 1) + { + GHashTable *table; + gpointer key; + GHashTable *per_key_table; + + table = relation->hashed_tuple_tables[i]; + + if (table == NULL) + continue; + + key = tuple[i]; + per_key_table = g_hash_table_lookup (table, key); + + if (per_key_table == NULL) + { + per_key_table = g_hash_table_new (tuple_hash (relation->fields), tuple_equal (relation->fields)); + g_hash_table_insert (table, key, per_key_table); + } + + g_hash_table_insert (per_key_table, tuple, tuple); + } +} + +static void +g_relation_delete_tuple (gpointer tuple_key, + gpointer tuple_value, + gpointer user_data) +{ + gpointer *tuple = (gpointer*) tuple_value; + GRelation *relation = (GRelation *) user_data; + gint j; + + g_assert (tuple_key == tuple_value); + + for (j = 0; j < relation->fields; j += 1) + { + GHashTable *one_table = relation->hashed_tuple_tables[j]; + gpointer one_key; + GHashTable *per_key_table; + + if (one_table == NULL) + continue; + + if (j == relation->current_field) + /* can't delete from the table we're foreaching in */ + continue; + + one_key = tuple[j]; + + per_key_table = g_hash_table_lookup (one_table, one_key); + + g_hash_table_remove (per_key_table, tuple); + } + + if (g_hash_table_remove (relation->all_tuples, tuple)) + g_slice_free1 (relation->fields * sizeof (gpointer), tuple); + + relation->count -= 1; +} + +/** + * g_relation_delete: + * @relation: a #GRelation. + * @key: the value to compare with. + * @field: the field of each record to match. + * + * Deletes any records from a #GRelation that have the given key value + * in the given field. + * + * Returns: the number of records deleted. + * + * Deprecated: 2.26: Rarely used API + **/ +gint +g_relation_delete (GRelation *relation, + gconstpointer key, + gint field) +{ + GHashTable *table; + GHashTable *key_table; + gint count; + + g_return_val_if_fail (relation != NULL, 0); + + table = relation->hashed_tuple_tables[field]; + count = relation->count; + + g_return_val_if_fail (table != NULL, 0); + + key_table = g_hash_table_lookup (table, key); + + if (!key_table) + return 0; + + relation->current_field = field; + + g_hash_table_foreach (key_table, g_relation_delete_tuple, relation); + + g_hash_table_remove (table, key); + + g_hash_table_destroy (key_table); + + /* @@@ FIXME: Remove empty hash tables. */ + + return count - relation->count; +} + +static void +g_relation_select_tuple (gpointer tuple_key, + gpointer tuple_value, + gpointer user_data) +{ + gpointer *tuple = (gpointer*) tuple_value; + GRealTuples *tuples = (GRealTuples*) user_data; + gint stride = sizeof (gpointer) * tuples->width; + + g_assert (tuple_key == tuple_value); + + memcpy (tuples->data + (tuples->len * tuples->width), + tuple, + stride); + + tuples->len += 1; +} + +/** + * g_relation_select: + * @relation: a #GRelation. + * @key: the value to compare with. + * @field: the field of each record to match. + * + * Returns all of the tuples which have the given key in the given + * field. Use g_tuples_index() to access the returned records. The + * returned records should be freed with g_tuples_destroy(). + * + * Returns: the records (tuples) that matched. + * + * Deprecated: 2.26: Rarely used API + **/ +GTuples* +g_relation_select (GRelation *relation, + gconstpointer key, + gint field) +{ + GHashTable *table; + GHashTable *key_table; + GRealTuples *tuples; + gint count; + + g_return_val_if_fail (relation != NULL, NULL); + + table = relation->hashed_tuple_tables[field]; + + g_return_val_if_fail (table != NULL, NULL); + + tuples = g_new0 (GRealTuples, 1); + key_table = g_hash_table_lookup (table, key); + + if (!key_table) + return (GTuples*)tuples; + + count = g_relation_count (relation, key, field); + + tuples->data = g_malloc (sizeof (gpointer) * relation->fields * count); + tuples->width = relation->fields; + + g_hash_table_foreach (key_table, g_relation_select_tuple, tuples); + + g_assert (count == tuples->len); + + return (GTuples*)tuples; +} + +/** + * g_relation_count: + * @relation: a #GRelation. + * @key: the value to compare with. + * @field: the field of each record to match. + * + * Returns the number of tuples in a #GRelation that have the given + * value in the given field. + * + * Returns: the number of matches. + * + * Deprecated: 2.26: Rarely used API + **/ +gint +g_relation_count (GRelation *relation, + gconstpointer key, + gint field) +{ + GHashTable *table; + GHashTable *key_table; + + g_return_val_if_fail (relation != NULL, 0); + + table = relation->hashed_tuple_tables[field]; + + g_return_val_if_fail (table != NULL, 0); + + key_table = g_hash_table_lookup (table, key); + + if (!key_table) + return 0; + + return g_hash_table_size (key_table); +} + +/** + * g_relation_exists: + * @relation: a #GRelation. + * @...: the fields of the record to compare. The number must match + * the number of fields in the #GRelation. + * + * Returns %TRUE if a record with the given values exists in a + * #GRelation. Note that the values are compared directly, so that, for + * example, two copies of the same string will not match. + * + * Returns: %TRUE if a record matches. + * + * Deprecated: 2.26: Rarely used API + **/ +gboolean +g_relation_exists (GRelation *relation, ...) +{ + gpointer *tuple = g_slice_alloc (relation->fields * sizeof (gpointer)); + va_list args; + gint i; + gboolean result; + + va_start(args, relation); + + for (i = 0; i < relation->fields; i += 1) + tuple[i] = va_arg(args, gpointer); + + va_end(args); + + result = g_hash_table_lookup (relation->all_tuples, tuple) != NULL; + + g_slice_free1 (relation->fields * sizeof (gpointer), tuple); + + return result; +} + +/** + * g_tuples_destroy: + * @tuples: the tuple data to free. + * + * Frees the records which were returned by g_relation_select(). This + * should always be called after g_relation_select() when you are + * finished with the records. The records are not removed from the + * #GRelation. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_tuples_destroy (GTuples *tuples0) +{ + GRealTuples *tuples = (GRealTuples*) tuples0; + + if (tuples) + { + g_free (tuples->data); + g_free (tuples); + } +} + +/** + * g_tuples_index: + * @tuples: the tuple data, returned by g_relation_select(). + * @index_: the index of the record. + * @field: the field to return. + * + * Gets a field from the records returned by g_relation_select(). It + * returns the given field of the record at the given index. The + * returned value should not be changed. + * + * Returns: the field of the record. + * + * Deprecated: 2.26: Rarely used API + **/ +gpointer +g_tuples_index (GTuples *tuples0, + gint index, + gint field) +{ + GRealTuples *tuples = (GRealTuples*) tuples0; + + g_return_val_if_fail (tuples0 != NULL, NULL); + g_return_val_if_fail (field < tuples->width, NULL); + + return tuples->data[index * tuples->width + field]; +} + +/* Print + */ + +static void +g_relation_print_one (gpointer tuple_key, + gpointer tuple_value, + gpointer user_data) +{ + gint i; + GString *gstring; + GRelation* rel = (GRelation*) user_data; + gpointer* tuples = (gpointer*) tuple_value; + + gstring = g_string_new ("["); + + for (i = 0; i < rel->fields; i += 1) + { + g_string_append_printf (gstring, "%p", tuples[i]); + + if (i < (rel->fields - 1)) + g_string_append (gstring, ","); + } + + g_string_append (gstring, "]"); + g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "%s", gstring->str); + g_string_free (gstring, TRUE); +} + +static void +g_relation_print_index (gpointer tuple_key, + gpointer tuple_value, + gpointer user_data) +{ + GRelation* rel = (GRelation*) user_data; + GHashTable* table = (GHashTable*) tuple_value; + + g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "*** key %p", tuple_key); + + g_hash_table_foreach (table, + g_relation_print_one, + rel); +} + +/** + * g_relation_print: + * @relation: a #GRelation. + * + * Outputs information about all records in a #GRelation, as well as + * the indexes. It is for debugging. + * + * Deprecated: 2.26: Rarely used API + **/ +void +g_relation_print (GRelation *relation) +{ + gint i; + + g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "*** all tuples (%d)", relation->count); + + g_hash_table_foreach (relation->all_tuples, + g_relation_print_one, + relation); + + for (i = 0; i < relation->fields; i += 1) + { + if (relation->hashed_tuple_tables[i] == NULL) + continue; + + g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "*** index %d", i); + + g_hash_table_foreach (relation->hashed_tuple_tables[i], + g_relation_print_index, + relation); + } + +} diff --git a/glib/deprecated/grel.h b/glib/deprecated/grel.h new file mode 100644 index 0000000..3a65240 --- /dev/null +++ b/glib/deprecated/grel.h @@ -0,0 +1,105 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_REL_H__ +#define __G_REL_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +typedef struct _GRelation GRelation; +typedef struct _GTuples GTuples; + +struct _GTuples +{ + guint len; +}; + +/* GRelation + * + * Indexed Relations. Imagine a really simple table in a + * database. Relations are not ordered. This data type is meant for + * maintaining a N-way mapping. + * + * g_relation_new() creates a relation with FIELDS fields + * + * g_relation_destroy() frees all resources + * g_tuples_destroy() frees the result of g_relation_select() + * + * g_relation_index() indexes relation FIELD with the provided + * equality and hash functions. this must be done before any + * calls to insert are made. + * + * g_relation_insert() inserts a new tuple. you are expected to + * provide the right number of fields. + * + * g_relation_delete() deletes all relations with KEY in FIELD + * g_relation_select() returns ... + * g_relation_count() counts ... + */ + +GLIB_DEPRECATED_IN_2_26 +GRelation* g_relation_new (gint fields); +GLIB_DEPRECATED_IN_2_26 +void g_relation_destroy (GRelation *relation); +GLIB_DEPRECATED_IN_2_26 +void g_relation_index (GRelation *relation, + gint field, + GHashFunc hash_func, + GEqualFunc key_equal_func); +GLIB_DEPRECATED_IN_2_26 +void g_relation_insert (GRelation *relation, + ...); +GLIB_DEPRECATED_IN_2_26 +gint g_relation_delete (GRelation *relation, + gconstpointer key, + gint field); +GLIB_DEPRECATED_IN_2_26 +GTuples* g_relation_select (GRelation *relation, + gconstpointer key, + gint field); +GLIB_DEPRECATED_IN_2_26 +gint g_relation_count (GRelation *relation, + gconstpointer key, + gint field); +GLIB_DEPRECATED_IN_2_26 +gboolean g_relation_exists (GRelation *relation, + ...); +GLIB_DEPRECATED_IN_2_26 +void g_relation_print (GRelation *relation); +GLIB_DEPRECATED_IN_2_26 +void g_tuples_destroy (GTuples *tuples); +GLIB_DEPRECATED_IN_2_26 +gpointer g_tuples_index (GTuples *tuples, + gint index_, + gint field); + +G_END_DECLS + +#endif /* __G_REL_H__ */ diff --git a/glib/deprecated/gthread-deprecated.c b/glib/deprecated/gthread-deprecated.c new file mode 100644 index 0000000..be98a74 --- /dev/null +++ b/glib/deprecated/gthread-deprecated.c @@ -0,0 +1,1578 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * gthread.c: MT safety related functions + * Copyright 1998 Sebastian Wilhelmi; University of Karlsruhe + * Owen Taylor + * + * 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" + +/* we know we are deprecated here, no need for warnings */ +#ifndef GLIB_DISABLE_DEPRECATION_WARNINGS +#define GLIB_DISABLE_DEPRECATION_WARNINGS +#endif + +#include "gmessages.h" +#include "gslice.h" +#include "gmain.h" +#include "gthread.h" +#include "gthreadprivate.h" +#include "deprecated/gthread.h" +#include "garray.h" + +#include "gutils.h" + +/* {{{1 Documentation */ + +/** + * SECTION:threads-deprecated + * @title: Deprecated thread API + * @short_description: old thread APIs (for reference only) + * @see_also: #GThread + * + * These APIs are deprecated. You should not use them in new code. + * This section remains only to assist with understanding code that was + * written to use these APIs at some point in the past. + **/ + +/** + * GThreadPriority: + * @G_THREAD_PRIORITY_LOW: a priority lower than normal + * @G_THREAD_PRIORITY_NORMAL: the default priority + * @G_THREAD_PRIORITY_HIGH: a priority higher than normal + * @G_THREAD_PRIORITY_URGENT: the highest priority + * + * Thread priorities. + * + * Deprecated:2.32: Thread priorities no longer have any effect. + */ + +/** + * GThreadFunctions: + * @mutex_new: virtual function pointer for g_mutex_new() + * @mutex_lock: virtual function pointer for g_mutex_lock() + * @mutex_trylock: virtual function pointer for g_mutex_trylock() + * @mutex_unlock: virtual function pointer for g_mutex_unlock() + * @mutex_free: virtual function pointer for g_mutex_free() + * @cond_new: virtual function pointer for g_cond_new() + * @cond_signal: virtual function pointer for g_cond_signal() + * @cond_broadcast: virtual function pointer for g_cond_broadcast() + * @cond_wait: virtual function pointer for g_cond_wait() + * @cond_timed_wait: virtual function pointer for g_cond_timed_wait() + * @cond_free: virtual function pointer for g_cond_free() + * @private_new: virtual function pointer for g_private_new() + * @private_get: virtual function pointer for g_private_get() + * @private_set: virtual function pointer for g_private_set() + * @thread_create: virtual function pointer for g_thread_create() + * @thread_yield: virtual function pointer for g_thread_yield() + * @thread_join: virtual function pointer for g_thread_join() + * @thread_exit: virtual function pointer for g_thread_exit() + * @thread_set_priority: virtual function pointer for + * g_thread_set_priority() + * @thread_self: virtual function pointer for g_thread_self() + * @thread_equal: used internally by recursive mutex locks and by some + * assertion checks + * + * This function table is no longer used by g_thread_init() + * to initialize the thread system. + */ + +/** + * G_THREADS_IMPL_POSIX: + * + * This macro is defined if POSIX style threads are used. + * + * Deprecated:2.32:POSIX threads are in use on all non-Windows systems. + * Use %G_OS_WIN32 to detect Windows. + */ + +/** + * G_THREADS_IMPL_WIN32: + * + * This macro is defined if Windows style threads are used. + * + * Deprecated:2.32:Use %G_OS_WIN32 to detect Windows. + */ + + +/* {{{1 Exported Variables */ + +/* Set this FALSE to have previously-compiled GStaticMutex code use the + * slow path (ie: call into us) to avoid compatibility problems. + */ +gboolean g_thread_use_default_impl = FALSE; + +GThreadFunctions g_thread_functions_for_glib_use = +{ + g_mutex_new, + g_mutex_lock, + g_mutex_trylock, + g_mutex_unlock, + g_mutex_free, + g_cond_new, + g_cond_signal, + g_cond_broadcast, + g_cond_wait, + g_cond_timed_wait, + g_cond_free, + g_private_new, + g_private_get, + g_private_set, + NULL, + g_thread_yield, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static guint64 +gettime (void) +{ + return g_get_monotonic_time () * 1000; +} + +guint64 (*g_thread_gettime) (void) = gettime; + +/* Initialisation {{{1 ---------------------------------------------------- */ +gboolean g_threads_got_initialized = TRUE; + +/** + * g_thread_init: + * @vtable: a function table of type #GThreadFunctions, that provides + * the entry points to the thread system to be used. Since 2.32, + * this parameter is ignored and should always be %NULL + * + * If you use GLib from more than one thread, you must initialize the + * thread system by calling g_thread_init(). + * + * Since version 2.24, calling g_thread_init() multiple times is allowed, + * but nothing happens except for the first call. + * + * Since version 2.32, GLib does not support custom thread implementations + * anymore and the @vtable parameter is ignored and you should pass %NULL. + * + * g_thread_init() must not be called directly or indirectly + * in a callback from GLib. Also no mutexes may be currently locked while + * calling g_thread_init(). + * + * To use g_thread_init() in your program, you have to link + * with the libraries that the command pkg-config --libs + * gthread-2.0 outputs. This is not the case for all the + * other thread-related functions of GLib. Those can be used without + * having to link with the thread libraries. + * + * Deprecated:2.32: This function is no longer necessary. The GLib + * threading system is automatically initialized at the start + * of your program. + */ + +/** + * g_thread_get_initialized: + * + * Indicates if g_thread_init() has been called. + * + * Returns: %TRUE if threads have been initialized. + * + * Since: 2.20 + */ +gboolean +g_thread_get_initialized (void) +{ + return g_thread_supported (); +} + +/* We need this for ABI compatibility */ +GLIB_AVAILABLE_IN_ALL +void g_thread_init_glib (void); +void g_thread_init_glib (void) { } + +/* Internal variables {{{1 */ + +static GSList *g_thread_all_threads = NULL; +static GSList *g_thread_free_indices = NULL; + +/* Protects g_thread_all_threads and g_thread_free_indices */ +G_LOCK_DEFINE_STATIC (g_static_mutex); +G_LOCK_DEFINE_STATIC (g_thread); + +/* Misc. GThread functions {{{1 */ + +/** + * g_thread_set_priority: + * @thread: a #GThread. + * @priority: ignored + * + * This function does nothing. + * + * Deprecated:2.32: Thread priorities no longer have any effect. + */ +void +g_thread_set_priority (GThread *thread, + GThreadPriority priority) +{ +} + +/** + * g_thread_foreach: + * @thread_func: function to call for all #GThread structures + * @user_data: second argument to @thread_func + * + * Call @thread_func on all #GThreads that have been + * created with g_thread_create(). + * + * Note that threads may decide to exit while @thread_func is + * running, so without intimate knowledge about the lifetime of + * foreign threads, @thread_func shouldn't access the GThread* + * pointer passed in as first argument. However, @thread_func will + * not be called for threads which are known to have exited already. + * + * Due to thread lifetime checks, this function has an execution complexity + * which is quadratic in the number of existing threads. + * + * Since: 2.10 + * + * Deprecated:2.32: There aren't many things you can do with a #GThread, + * except comparing it with one that was returned from g_thread_create(). + * There are better ways to find out if your thread is still alive. + */ +void +g_thread_foreach (GFunc thread_func, + gpointer user_data) +{ + GSList *slist = NULL; + GRealThread *thread; + g_return_if_fail (thread_func != NULL); + /* snapshot the list of threads for iteration */ + G_LOCK (g_thread); + slist = g_slist_copy (g_thread_all_threads); + G_UNLOCK (g_thread); + /* walk the list, skipping non-existent threads */ + while (slist) + { + GSList *node = slist; + slist = node->next; + /* check whether the current thread still exists */ + G_LOCK (g_thread); + if (g_slist_find (g_thread_all_threads, node->data)) + thread = node->data; + else + thread = NULL; + G_UNLOCK (g_thread); + if (thread) + thread_func (thread, user_data); + g_slist_free_1 (node); + } +} + +static void +g_enumerable_thread_remove (gpointer data) +{ + GRealThread *thread = data; + + G_LOCK (g_thread); + g_thread_all_threads = g_slist_remove (g_thread_all_threads, thread); + G_UNLOCK (g_thread); +} + +GPrivate enumerable_thread_private = G_PRIVATE_INIT (g_enumerable_thread_remove); + +static void +g_enumerable_thread_add (GRealThread *thread) +{ + G_LOCK (g_thread); + g_thread_all_threads = g_slist_prepend (g_thread_all_threads, thread); + G_UNLOCK (g_thread); + + g_private_set (&enumerable_thread_private, thread); +} + +static gpointer +g_deprecated_thread_proxy (gpointer data) +{ + GRealThread *real = data; + + g_enumerable_thread_add (real); + + return g_thread_proxy (data); +} + +/** + * g_thread_create: + * @func: a function to execute in the new thread + * @data: an argument to supply to the new thread + * @joinable: should this thread be joinable? + * @error: return location for error, or %NULL + * + * This function creates a new thread. + * + * The new thread executes the function @func with the argument @data. + * If the thread was created successfully, it is returned. + * + * @error can be %NULL to ignore errors, or non-%NULL to report errors. + * The error is set, if and only if the function returns %NULL. + * + * This function returns a reference to the created thread only if + * @joinable is %TRUE. In that case, you must free this reference by + * calling g_thread_unref() or g_thread_join(). If @joinable is %FALSE + * then you should probably not touch the return value. + * + * Returns: the new #GThread on success + * + * Deprecated:2.32: Use g_thread_new() instead + */ +GThread * +g_thread_create (GThreadFunc func, + gpointer data, + gboolean joinable, + GError **error) +{ + return g_thread_create_full (func, data, 0, joinable, 0, 0, error); +} + +/** + * g_thread_create_full: + * @func: a function to execute in the new thread. + * @data: an argument to supply to the new thread. + * @stack_size: a stack size for the new thread. + * @joinable: should this thread be joinable? + * @bound: ignored + * @priority: ignored + * @error: return location for error. + * + * This function creates a new thread. + * + * Returns: the new #GThread on success. + * + * Deprecated:2.32: The @bound and @priority arguments are now ignored. + * Use g_thread_new(). + */ +GThread * +g_thread_create_full (GThreadFunc func, + gpointer data, + gulong stack_size, + gboolean joinable, + gboolean bound, + GThreadPriority priority, + GError **error) +{ + GThread *thread; + + thread = g_thread_new_internal (NULL, g_deprecated_thread_proxy, + func, data, stack_size, NULL, error); + + if (thread && !joinable) + { + thread->joinable = FALSE; + g_thread_unref (thread); + } + + return thread; +} + +/* GOnce {{{1 ------------------------------------------------------------- */ +gboolean +g_once_init_enter_impl (volatile gsize *location) +{ + return (g_once_init_enter) (location); +} + +/* GStaticMutex {{{1 ------------------------------------------------------ */ + +/** + * GStaticMutex: + * + * A #GStaticMutex works like a #GMutex. + * + * Prior to GLib 2.32, GStaticMutex had the significant advantage + * that it doesn't need to be created at run-time, but can be defined + * at compile-time. Since 2.32, #GMutex can be statically allocated + * as well, and GStaticMutex has been deprecated. + * + * Here is a version of our give_me_next_number() example using + * a GStaticMutex: + * |[ + * int + * give_me_next_number (void) + * { + * static int current_number = 0; + * int ret_val; + * static GStaticMutex mutex = G_STATIC_MUTEX_INIT; + * + * g_static_mutex_lock (&mutex); + * ret_val = current_number = calc_next_number (current_number); + * g_static_mutex_unlock (&mutex); + * + * return ret_val; + * } + * ]| + * + * Sometimes you would like to dynamically create a mutex. If you don't + * want to require prior calling to g_thread_init(), because your code + * should also be usable in non-threaded programs, you are not able to + * use g_mutex_new() and thus #GMutex, as that requires a prior call to + * g_thread_init(). In these cases you can also use a #GStaticMutex. + * It must be initialized with g_static_mutex_init() before using it + * and freed with with g_static_mutex_free() when not needed anymore to + * free up any allocated resources. + * + * Even though #GStaticMutex is not opaque, it should only be used with + * the following functions, as it is defined differently on different + * platforms. + * + * All of the g_static_mutex_* functions apart from + * g_static_mutex_get_mutex() can also be used even if g_thread_init() + * has not yet been called. Then they do nothing, apart from + * g_static_mutex_trylock() which does nothing but returning %TRUE. + * + * All of the g_static_mutex_* functions are actually macros. Apart from + * taking their addresses, you can however use them as if they were + * functions. + */ + +/** + * G_STATIC_MUTEX_INIT: + * + * A #GStaticMutex must be initialized with this macro, before it can + * be used. This macro can used be to initialize a variable, but it + * cannot be assigned to a variable. In that case you have to use + * g_static_mutex_init(). + * + * |[ + * GStaticMutex my_mutex = G_STATIC_MUTEX_INIT; + * ]| + **/ + +/** + * g_static_mutex_init: + * @mutex: a #GStaticMutex to be initialized. + * + * Initializes @mutex. + * Alternatively you can initialize it with %G_STATIC_MUTEX_INIT. + * + * Deprecated: 2.32: Use g_mutex_init() + */ +void +g_static_mutex_init (GStaticMutex *mutex) +{ + static const GStaticMutex init_mutex = G_STATIC_MUTEX_INIT; + + g_return_if_fail (mutex); + + *mutex = init_mutex; +} + +/* IMPLEMENTATION NOTE: + * + * On some platforms a GStaticMutex is actually a normal GMutex stored + * inside of a structure instead of being allocated dynamically. We can + * only do this for platforms on which we know, in advance, how to + * allocate (size) and initialise (value) that memory. + * + * On other platforms, a GStaticMutex is nothing more than a pointer to + * a GMutex. In that case, the first access we make to the static mutex + * must first allocate the normal GMutex and store it into the pointer. + * + * configure.ac writes macros into glibconfig.h to determine if + * g_static_mutex_get_mutex() accesses the structure in memory directly + * (on platforms where we are able to do that) or if it ends up here, + * where we may have to allocate the GMutex before returning it. + */ + +/** + * g_static_mutex_get_mutex: + * @mutex: a #GStaticMutex. + * + * For some operations (like g_cond_wait()) you must have a #GMutex + * instead of a #GStaticMutex. This function will return the + * corresponding #GMutex for @mutex. + * + * Returns: the #GMutex corresponding to @mutex. + * + * Deprecated: 2.32: Just use a #GMutex + */ +GMutex * +g_static_mutex_get_mutex_impl (GStaticMutex* mutex) +{ + GMutex *result; + + if (!g_thread_supported ()) + return NULL; + + result = g_atomic_pointer_get (&mutex->mutex); + + if (!result) + { + G_LOCK (g_static_mutex); + + result = mutex->mutex; + if (!result) + { + result = g_mutex_new (); + g_atomic_pointer_set (&mutex->mutex, result); + } + + G_UNLOCK (g_static_mutex); + } + + return result; +} + +/* IMPLEMENTATION NOTE: + * + * g_static_mutex_lock(), g_static_mutex_trylock() and + * g_static_mutex_unlock() are all preprocessor macros that wrap the + * corresponding g_mutex_*() function around a call to + * g_static_mutex_get_mutex(). + */ + +/** + * g_static_mutex_lock: + * @mutex: a #GStaticMutex. + * + * Works like g_mutex_lock(), but for a #GStaticMutex. + * + * Deprecated: 2.32: Use g_mutex_lock() + */ + +/** + * g_static_mutex_trylock: + * @mutex: a #GStaticMutex. + * + * Works like g_mutex_trylock(), but for a #GStaticMutex. + * + * Returns: %TRUE, if the #GStaticMutex could be locked. + * + * Deprecated: 2.32: Use g_mutex_trylock() + */ + +/** + * g_static_mutex_unlock: + * @mutex: a #GStaticMutex. + * + * Works like g_mutex_unlock(), but for a #GStaticMutex. + * + * Deprecated: 2.32: Use g_mutex_unlock() + */ + +/** + * g_static_mutex_free: + * @mutex: a #GStaticMutex to be freed. + * + * Releases all resources allocated to @mutex. + * + * You don't have to call this functions for a #GStaticMutex with an + * unbounded lifetime, i.e. objects declared 'static', but if you have + * a #GStaticMutex as a member of a structure and the structure is + * freed, you should also free the #GStaticMutex. + * + * Calling g_static_mutex_free() on a locked mutex may result in + * undefined behaviour. + * + * Deprecated: 2.32: Use g_mutex_clear() + */ +void +g_static_mutex_free (GStaticMutex* mutex) +{ + GMutex **runtime_mutex; + + g_return_if_fail (mutex); + + /* The runtime_mutex is the first (or only) member of GStaticMutex, + * see both versions (of glibconfig.h) in configure.ac. Note, that + * this variable is NULL, if g_thread_init() hasn't been called or + * if we're using the default thread implementation and it provides + * static mutexes. */ + runtime_mutex = ((GMutex**)mutex); + + if (*runtime_mutex) + g_mutex_free (*runtime_mutex); + + *runtime_mutex = NULL; +} + +/* {{{1 GStaticRecMutex */ + +/** + * GStaticRecMutex: + * + * A #GStaticRecMutex works like a #GStaticMutex, but it can be locked + * multiple times by one thread. If you enter it n times, you have to + * unlock it n times again to let other threads lock it. An exception + * is the function g_static_rec_mutex_unlock_full(): that allows you to + * unlock a #GStaticRecMutex completely returning the depth, (i.e. the + * number of times this mutex was locked). The depth can later be used + * to restore the state of the #GStaticRecMutex by calling + * g_static_rec_mutex_lock_full(). In GLib 2.32, #GStaticRecMutex has + * been deprecated in favor of #GRecMutex. + * + * Even though #GStaticRecMutex is not opaque, it should only be used + * with the following functions. + * + * All of the g_static_rec_mutex_* functions can be used even if + * g_thread_init() has not been called. Then they do nothing, apart + * from g_static_rec_mutex_trylock(), which does nothing but returning + * %TRUE. + */ + +/** + * G_STATIC_REC_MUTEX_INIT: + * + * A #GStaticRecMutex must be initialized with this macro before it can + * be used. This macro can used be to initialize a variable, but it + * cannot be assigned to a variable. In that case you have to use + * g_static_rec_mutex_init(). + * + * |[ + * GStaticRecMutex my_mutex = G_STATIC_REC_MUTEX_INIT; + * ]| + */ + +/** + * g_static_rec_mutex_init: + * @mutex: a #GStaticRecMutex to be initialized. + * + * A #GStaticRecMutex must be initialized with this function before it + * can be used. Alternatively you can initialize it with + * %G_STATIC_REC_MUTEX_INIT. + * + * Deprecated: 2.32: Use g_rec_mutex_init() + */ +void +g_static_rec_mutex_init (GStaticRecMutex *mutex) +{ + static const GStaticRecMutex init_mutex = G_STATIC_REC_MUTEX_INIT; + + g_return_if_fail (mutex); + + *mutex = init_mutex; +} + +static GRecMutex * +g_static_rec_mutex_get_rec_mutex_impl (GStaticRecMutex* mutex) +{ + GRecMutex *result; + + if (!g_thread_supported ()) + return NULL; + + result = (GRecMutex *) g_atomic_pointer_get (&mutex->mutex.mutex); + + if (!result) + { + G_LOCK (g_static_mutex); + + result = (GRecMutex *) mutex->mutex.mutex; + if (!result) + { + result = g_slice_new (GRecMutex); + g_rec_mutex_init (result); + g_atomic_pointer_set (&mutex->mutex.mutex, (GMutex *) result); + } + + G_UNLOCK (g_static_mutex); + } + + return result; +} + +/** + * g_static_rec_mutex_lock: + * @mutex: a #GStaticRecMutex to lock. + * + * Locks @mutex. If @mutex is already locked by another thread, the + * current thread will block until @mutex is unlocked by the other + * thread. If @mutex is already locked by the calling thread, this + * functions increases the depth of @mutex and returns immediately. + * + * Deprecated: 2.32: Use g_rec_mutex_lock() + */ +void +g_static_rec_mutex_lock (GStaticRecMutex* mutex) +{ + GRecMutex *rm; + rm = g_static_rec_mutex_get_rec_mutex_impl (mutex); + g_rec_mutex_lock (rm); + mutex->depth++; +} + +/** + * g_static_rec_mutex_trylock: + * @mutex: a #GStaticRecMutex to lock. + * + * Tries to lock @mutex. If @mutex is already locked by another thread, + * it immediately returns %FALSE. Otherwise it locks @mutex and returns + * %TRUE. If @mutex is already locked by the calling thread, this + * functions increases the depth of @mutex and immediately returns + * %TRUE. + * + * Returns: %TRUE, if @mutex could be locked. + * + * Deprecated: 2.32: Use g_rec_mutex_trylock() + */ +gboolean +g_static_rec_mutex_trylock (GStaticRecMutex* mutex) +{ + GRecMutex *rm; + rm = g_static_rec_mutex_get_rec_mutex_impl (mutex); + + if (g_rec_mutex_trylock (rm)) + { + mutex->depth++; + return TRUE; + } + else + return FALSE; +} + +/** + * g_static_rec_mutex_unlock: + * @mutex: a #GStaticRecMutex to unlock. + * + * Unlocks @mutex. Another thread will be allowed to lock @mutex only + * when it has been unlocked as many times as it had been locked + * before. If @mutex is completely unlocked and another thread is + * blocked in a g_static_rec_mutex_lock() call for @mutex, it will be + * woken and can lock @mutex itself. + * + * Deprecated: 2.32: Use g_rec_mutex_unlock() + */ +void +g_static_rec_mutex_unlock (GStaticRecMutex* mutex) +{ + GRecMutex *rm; + rm = g_static_rec_mutex_get_rec_mutex_impl (mutex); + mutex->depth--; + g_rec_mutex_unlock (rm); +} + +/** + * g_static_rec_mutex_lock_full: + * @mutex: a #GStaticRecMutex to lock. + * @depth: number of times this mutex has to be unlocked to be + * completely unlocked. + * + * Works like calling g_static_rec_mutex_lock() for @mutex @depth times. + * + * Deprecated: 2.32: Use g_rec_mutex_lock() + */ +void +g_static_rec_mutex_lock_full (GStaticRecMutex *mutex, + guint depth) +{ + GRecMutex *rm; + + rm = g_static_rec_mutex_get_rec_mutex_impl (mutex); + while (depth--) + { + g_rec_mutex_lock (rm); + mutex->depth++; + } +} + +/** + * g_static_rec_mutex_unlock_full: + * @mutex: a #GStaticRecMutex to completely unlock. + * + * Completely unlocks @mutex. If another thread is blocked in a + * g_static_rec_mutex_lock() call for @mutex, it will be woken and can + * lock @mutex itself. This function returns the number of times that + * @mutex has been locked by the current thread. To restore the state + * before the call to g_static_rec_mutex_unlock_full() you can call + * g_static_rec_mutex_lock_full() with the depth returned by this + * function. + * + * Returns: number of times @mutex has been locked by the current + * thread. + * + * Deprecated: 2.32: Use g_rec_mutex_unlock() + */ +guint +g_static_rec_mutex_unlock_full (GStaticRecMutex *mutex) +{ + GRecMutex *rm; + gint depth; + gint i; + + rm = g_static_rec_mutex_get_rec_mutex_impl (mutex); + + /* all access to mutex->depth done while still holding the lock */ + depth = mutex->depth; + i = mutex->depth; + mutex->depth = 0; + + while (i--) + g_rec_mutex_unlock (rm); + + return depth; +} + +/** + * g_static_rec_mutex_free: + * @mutex: a #GStaticRecMutex to be freed. + * + * Releases all resources allocated to a #GStaticRecMutex. + * + * You don't have to call this functions for a #GStaticRecMutex with an + * unbounded lifetime, i.e. objects declared 'static', but if you have + * a #GStaticRecMutex as a member of a structure and the structure is + * freed, you should also free the #GStaticRecMutex. + * + * Deprecated: 2.32: Use g_rec_mutex_clear() + */ +void +g_static_rec_mutex_free (GStaticRecMutex *mutex) +{ + g_return_if_fail (mutex); + + if (mutex->mutex.mutex) + { + GRecMutex *rm = (GRecMutex *) mutex->mutex.mutex; + + g_rec_mutex_clear (rm); + g_slice_free (GRecMutex, rm); + } +} + +/* GStaticRWLock {{{1 ----------------------------------------------------- */ + +/** + * GStaticRWLock: + * + * The #GStaticRWLock struct represents a read-write lock. A read-write + * lock can be used for protecting data that some portions of code only + * read from, while others also write. In such situations it is + * desirable that several readers can read at once, whereas of course + * only one writer may write at a time. + * + * Take a look at the following example: + * |[ + * GStaticRWLock rwlock = G_STATIC_RW_LOCK_INIT; + * GPtrArray *array; + * + * gpointer + * my_array_get (guint index) + * { + * gpointer retval = NULL; + * + * if (!array) + * return NULL; + * + * g_static_rw_lock_reader_lock (&rwlock); + * if (index < array->len) + * retval = g_ptr_array_index (array, index); + * g_static_rw_lock_reader_unlock (&rwlock); + * + * return retval; + * } + * + * void + * my_array_set (guint index, gpointer data) + * { + * g_static_rw_lock_writer_lock (&rwlock); + * + * if (!array) + * array = g_ptr_array_new (); + * + * if (index >= array->len) + * g_ptr_array_set_size (array, index + 1); + * g_ptr_array_index (array, index) = data; + * + * g_static_rw_lock_writer_unlock (&rwlock); + * } + * ]| + * + * This example shows an array which can be accessed by many readers + * (the my_array_get() function) simultaneously, whereas the writers + * (the my_array_set() function) will only be allowed once at a time + * and only if no readers currently access the array. This is because + * of the potentially dangerous resizing of the array. Using these + * functions is fully multi-thread safe now. + * + * Most of the time, writers should have precedence over readers. That + * means, for this implementation, that as soon as a writer wants to + * lock the data, no other reader is allowed to lock the data, whereas, + * of course, the readers that already have locked the data are allowed + * to finish their operation. As soon as the last reader unlocks the + * data, the writer will lock it. + * + * Even though #GStaticRWLock is not opaque, it should only be used + * with the following functions. + * + * All of the g_static_rw_lock_* functions can be used even if + * g_thread_init() has not been called. Then they do nothing, apart + * from g_static_rw_lock_*_trylock, which does nothing but returning %TRUE. + * + * A read-write lock has a higher overhead than a mutex. For example, both + * g_static_rw_lock_reader_lock() and g_static_rw_lock_reader_unlock() have + * to lock and unlock a #GStaticMutex, so it takes at least twice the time + * to lock and unlock a #GStaticRWLock that it does to lock and unlock a + * #GStaticMutex. So only data structures that are accessed by multiple + * readers, and which keep the lock for a considerable time justify a + * #GStaticRWLock. The above example most probably would fare better with a + * #GStaticMutex. + * + * Deprecated: 2.32: Use a #GRWLock instead + **/ + +/** + * G_STATIC_RW_LOCK_INIT: + * + * A #GStaticRWLock must be initialized with this macro before it can + * be used. This macro can used be to initialize a variable, but it + * cannot be assigned to a variable. In that case you have to use + * g_static_rw_lock_init(). + * + * |[ + * GStaticRWLock my_lock = G_STATIC_RW_LOCK_INIT; + * ]| + */ + +/** + * g_static_rw_lock_init: + * @lock: a #GStaticRWLock to be initialized. + * + * A #GStaticRWLock must be initialized with this function before it + * can be used. Alternatively you can initialize it with + * %G_STATIC_RW_LOCK_INIT. + * + * Deprecated: 2.32: Use g_rw_lock_init() instead + */ +void +g_static_rw_lock_init (GStaticRWLock* lock) +{ + static const GStaticRWLock init_lock = G_STATIC_RW_LOCK_INIT; + + g_return_if_fail (lock); + + *lock = init_lock; +} + +inline static void +g_static_rw_lock_wait (GCond** cond, GStaticMutex* mutex) +{ + if (!*cond) + *cond = g_cond_new (); + g_cond_wait (*cond, g_static_mutex_get_mutex (mutex)); +} + +inline static void +g_static_rw_lock_signal (GStaticRWLock* lock) +{ + if (lock->want_to_write && lock->write_cond) + g_cond_signal (lock->write_cond); + else if (lock->want_to_read && lock->read_cond) + g_cond_broadcast (lock->read_cond); +} + +/** + * g_static_rw_lock_reader_lock: + * @lock: a #GStaticRWLock to lock for reading. + * + * Locks @lock for reading. There may be unlimited concurrent locks for + * reading of a #GStaticRWLock at the same time. If @lock is already + * locked for writing by another thread or if another thread is already + * waiting to lock @lock for writing, this function will block until + * @lock is unlocked by the other writing thread and no other writing + * threads want to lock @lock. This lock has to be unlocked by + * g_static_rw_lock_reader_unlock(). + * + * #GStaticRWLock is not recursive. It might seem to be possible to + * recursively lock for reading, but that can result in a deadlock, due + * to writer preference. + * + * Deprecated: 2.32: Use g_rw_lock_reader_lock() instead + */ +void +g_static_rw_lock_reader_lock (GStaticRWLock* lock) +{ + g_return_if_fail (lock); + + if (!g_threads_got_initialized) + return; + + g_static_mutex_lock (&lock->mutex); + lock->want_to_read++; + while (lock->have_writer || lock->want_to_write) + g_static_rw_lock_wait (&lock->read_cond, &lock->mutex); + lock->want_to_read--; + lock->read_counter++; + g_static_mutex_unlock (&lock->mutex); +} + +/** + * g_static_rw_lock_reader_trylock: + * @lock: a #GStaticRWLock to lock for reading + * + * Tries to lock @lock for reading. If @lock is already locked for + * writing by another thread or if another thread is already waiting to + * lock @lock for writing, immediately returns %FALSE. Otherwise locks + * @lock for reading and returns %TRUE. This lock has to be unlocked by + * g_static_rw_lock_reader_unlock(). + * + * Returns: %TRUE, if @lock could be locked for reading + * + * Deprecated: 2.32: Use g_rw_lock_reader_trylock() instead + */ +gboolean +g_static_rw_lock_reader_trylock (GStaticRWLock* lock) +{ + gboolean ret_val = FALSE; + + g_return_val_if_fail (lock, FALSE); + + if (!g_threads_got_initialized) + return TRUE; + + g_static_mutex_lock (&lock->mutex); + if (!lock->have_writer && !lock->want_to_write) + { + lock->read_counter++; + ret_val = TRUE; + } + g_static_mutex_unlock (&lock->mutex); + return ret_val; +} + +/** + * g_static_rw_lock_reader_unlock: + * @lock: a #GStaticRWLock to unlock after reading + * + * Unlocks @lock. If a thread waits to lock @lock for writing and all + * locks for reading have been unlocked, the waiting thread is woken up + * and can lock @lock for writing. + * + * Deprecated: 2.32: Use g_rw_lock_reader_unlock() instead + */ +void +g_static_rw_lock_reader_unlock (GStaticRWLock* lock) +{ + g_return_if_fail (lock); + + if (!g_threads_got_initialized) + return; + + g_static_mutex_lock (&lock->mutex); + lock->read_counter--; + if (lock->read_counter == 0) + g_static_rw_lock_signal (lock); + g_static_mutex_unlock (&lock->mutex); +} + +/** + * g_static_rw_lock_writer_lock: + * @lock: a #GStaticRWLock to lock for writing + * + * Locks @lock for writing. If @lock is already locked for writing or + * reading by other threads, this function will block until @lock is + * completely unlocked and then lock @lock for writing. While this + * functions waits to lock @lock, no other thread can lock @lock for + * reading. When @lock is locked for writing, no other thread can lock + * @lock (neither for reading nor writing). This lock has to be + * unlocked by g_static_rw_lock_writer_unlock(). + * + * Deprecated: 2.32: Use g_rw_lock_writer_lock() instead + */ +void +g_static_rw_lock_writer_lock (GStaticRWLock* lock) +{ + g_return_if_fail (lock); + + if (!g_threads_got_initialized) + return; + + g_static_mutex_lock (&lock->mutex); + lock->want_to_write++; + while (lock->have_writer || lock->read_counter) + g_static_rw_lock_wait (&lock->write_cond, &lock->mutex); + lock->want_to_write--; + lock->have_writer = TRUE; + g_static_mutex_unlock (&lock->mutex); +} + +/** + * g_static_rw_lock_writer_trylock: + * @lock: a #GStaticRWLock to lock for writing + * + * Tries to lock @lock for writing. If @lock is already locked (for + * either reading or writing) by another thread, it immediately returns + * %FALSE. Otherwise it locks @lock for writing and returns %TRUE. This + * lock has to be unlocked by g_static_rw_lock_writer_unlock(). + * + * Returns: %TRUE, if @lock could be locked for writing + * + * Deprecated: 2.32: Use g_rw_lock_writer_trylock() instead + */ +gboolean +g_static_rw_lock_writer_trylock (GStaticRWLock* lock) +{ + gboolean ret_val = FALSE; + + g_return_val_if_fail (lock, FALSE); + + if (!g_threads_got_initialized) + return TRUE; + + g_static_mutex_lock (&lock->mutex); + if (!lock->have_writer && !lock->read_counter) + { + lock->have_writer = TRUE; + ret_val = TRUE; + } + g_static_mutex_unlock (&lock->mutex); + return ret_val; +} + +/** + * g_static_rw_lock_writer_unlock: + * @lock: a #GStaticRWLock to unlock after writing. + * + * Unlocks @lock. If a thread is waiting to lock @lock for writing and + * all locks for reading have been unlocked, the waiting thread is + * woken up and can lock @lock for writing. If no thread is waiting to + * lock @lock for writing, and some thread or threads are waiting to + * lock @lock for reading, the waiting threads are woken up and can + * lock @lock for reading. + * + * Deprecated: 2.32: Use g_rw_lock_writer_unlock() instead + */ +void +g_static_rw_lock_writer_unlock (GStaticRWLock* lock) +{ + g_return_if_fail (lock); + + if (!g_threads_got_initialized) + return; + + g_static_mutex_lock (&lock->mutex); + lock->have_writer = FALSE; + g_static_rw_lock_signal (lock); + g_static_mutex_unlock (&lock->mutex); +} + +/** + * g_static_rw_lock_free: + * @lock: a #GStaticRWLock to be freed. + * + * Releases all resources allocated to @lock. + * + * You don't have to call this functions for a #GStaticRWLock with an + * unbounded lifetime, i.e. objects declared 'static', but if you have + * a #GStaticRWLock as a member of a structure, and the structure is + * freed, you should also free the #GStaticRWLock. + * + * Deprecated: 2.32: Use a #GRWLock instead + */ +void +g_static_rw_lock_free (GStaticRWLock* lock) +{ + g_return_if_fail (lock); + + if (lock->read_cond) + { + g_cond_free (lock->read_cond); + lock->read_cond = NULL; + } + if (lock->write_cond) + { + g_cond_free (lock->write_cond); + lock->write_cond = NULL; + } + g_static_mutex_free (&lock->mutex); +} + +/* GPrivate {{{1 ------------------------------------------------------ */ + +/** + * g_private_new: + * @notify: a #GDestroyNotify + * + * Creates a new #GPrivate. + * + * Deprecated:2.32: dynamic allocation of #GPrivate is a bad idea. Use + * static storage and G_PRIVATE_INIT() instead. + * + * Returns: a newly allocated #GPrivate (which can never be destroyed) + */ +GPrivate * +g_private_new (GDestroyNotify notify) +{ + GPrivate tmp = G_PRIVATE_INIT (notify); + GPrivate *key; + + key = g_slice_new (GPrivate); + *key = tmp; + + return key; +} + +/* {{{1 GStaticPrivate */ + +typedef struct _GStaticPrivateNode GStaticPrivateNode; +struct _GStaticPrivateNode +{ + gpointer data; + GDestroyNotify destroy; + GStaticPrivate *owner; +}; + +static void +g_static_private_cleanup (gpointer data) +{ + GArray *array = data; + guint i; + + for (i = 0; i < array->len; i++ ) + { + GStaticPrivateNode *node = &g_array_index (array, GStaticPrivateNode, i); + if (node->destroy) + node->destroy (node->data); + } + + g_array_free (array, TRUE); +} + +GPrivate static_private_private = G_PRIVATE_INIT (g_static_private_cleanup); + +/** + * GStaticPrivate: + * + * A #GStaticPrivate works almost like a #GPrivate, but it has one + * significant advantage. It doesn't need to be created at run-time + * like a #GPrivate, but can be defined at compile-time. This is + * similar to the difference between #GMutex and #GStaticMutex. + * + * Now look at our give_me_next_number() example with #GStaticPrivate: + * |[ + * int + * give_me_next_number () + * { + * static GStaticPrivate current_number_key = G_STATIC_PRIVATE_INIT; + * int *current_number = g_static_private_get (¤t_number_key); + * + * if (!current_number) + * { + * current_number = g_new (int, 1); + * *current_number = 0; + * g_static_private_set (¤t_number_key, current_number, g_free); + * } + * + * *current_number = calc_next_number (*current_number); + * + * return *current_number; + * } + * ]| + */ + +/** + * G_STATIC_PRIVATE_INIT: + * + * Every #GStaticPrivate must be initialized with this macro, before it + * can be used. + * + * |[ + * GStaticPrivate my_private = G_STATIC_PRIVATE_INIT; + * ]| + */ + +/** + * g_static_private_init: + * @private_key: a #GStaticPrivate to be initialized + * + * Initializes @private_key. Alternatively you can initialize it with + * %G_STATIC_PRIVATE_INIT. + */ +void +g_static_private_init (GStaticPrivate *private_key) +{ + private_key->index = 0; +} + +/** + * g_static_private_get: + * @private_key: a #GStaticPrivate + * + * Works like g_private_get() only for a #GStaticPrivate. + * + * This function works even if g_thread_init() has not yet been called. + * + * Returns: the corresponding pointer + */ +gpointer +g_static_private_get (GStaticPrivate *private_key) +{ + GArray *array; + gpointer ret = NULL; + + array = g_private_get (&static_private_private); + + if (array && private_key->index != 0 && private_key->index <= array->len) + { + GStaticPrivateNode *node; + + node = &g_array_index (array, GStaticPrivateNode, private_key->index - 1); + + /* Deal with the possibility that the GStaticPrivate which used + * to have this index got freed and the index got allocated to + * a new one. In this case, the data in the node is stale, so + * free it and return NULL. + */ + if (G_UNLIKELY (node->owner != private_key)) + { + if (node->destroy) + node->destroy (node->data); + node->destroy = NULL; + node->data = NULL; + node->owner = NULL; + } + ret = node->data; + } + + return ret; +} + +/** + * g_static_private_set: + * @private_key: a #GStaticPrivate + * @data: the new pointer + * @notify: a function to be called with the pointer whenever the + * current thread ends or sets this pointer again + * + * Sets the pointer keyed to @private_key for the current thread and + * the function @notify to be called with that pointer (%NULL or + * non-%NULL), whenever the pointer is set again or whenever the + * current thread ends. + * + * This function works even if g_thread_init() has not yet been called. + * If g_thread_init() is called later, the @data keyed to @private_key + * will be inherited only by the main thread, i.e. the one that called + * g_thread_init(). + * + * @notify is used quite differently from @destructor in g_private_new(). + */ +void +g_static_private_set (GStaticPrivate *private_key, + gpointer data, + GDestroyNotify notify) +{ + GArray *array; + static guint next_index = 0; + GStaticPrivateNode *node; + + if (!private_key->index) + { + G_LOCK (g_thread); + + if (!private_key->index) + { + if (g_thread_free_indices) + { + private_key->index = GPOINTER_TO_UINT (g_thread_free_indices->data); + g_thread_free_indices = g_slist_delete_link (g_thread_free_indices, + g_thread_free_indices); + } + else + private_key->index = ++next_index; + } + + G_UNLOCK (g_thread); + } + + array = g_private_get (&static_private_private); + if (!array) + { + array = g_array_new (FALSE, TRUE, sizeof (GStaticPrivateNode)); + g_private_set (&static_private_private, array); + } + if (private_key->index > array->len) + g_array_set_size (array, private_key->index); + + node = &g_array_index (array, GStaticPrivateNode, private_key->index - 1); + + if (node->destroy) + node->destroy (node->data); + + node->data = data; + node->destroy = notify; + node->owner = private_key; +} + +/** + * g_static_private_free: + * @private_key: a #GStaticPrivate to be freed + * + * Releases all resources allocated to @private_key. + * + * You don't have to call this functions for a #GStaticPrivate with an + * unbounded lifetime, i.e. objects declared 'static', but if you have + * a #GStaticPrivate as a member of a structure and the structure is + * freed, you should also free the #GStaticPrivate. + */ +void +g_static_private_free (GStaticPrivate *private_key) +{ + guint idx = private_key->index; + + if (!idx) + return; + + private_key->index = 0; + + /* Freeing the per-thread data is deferred to either the + * thread end or the next g_static_private_get() call for + * the same index. + */ + G_LOCK (g_thread); + g_thread_free_indices = g_slist_prepend (g_thread_free_indices, + GUINT_TO_POINTER (idx)); + G_UNLOCK (g_thread); +} + +/* GMutex {{{1 ------------------------------------------------------ */ + +/** + * g_mutex_new: + * + * Allocates and initializes a new #GMutex. + * + * Returns: a newly allocated #GMutex. Use g_mutex_free() to free + * + * Deprecated: 2.32: GMutex can now be statically allocated, or embedded + * in structures and initialised with g_mutex_init(). + */ +GMutex * +g_mutex_new (void) +{ + GMutex *mutex; + + mutex = g_slice_new (GMutex); + g_mutex_init (mutex); + + return mutex; +} + +/** + * g_mutex_free: + * @mutex: a #GMutex + * + * Destroys a @mutex that has been created with g_mutex_new(). + * + * Calling g_mutex_free() on a locked mutex may result + * in undefined behaviour. + * + * Deprecated: 2.32: GMutex can now be statically allocated, or embedded + * in structures and initialised with g_mutex_init(). + */ +void +g_mutex_free (GMutex *mutex) +{ + g_mutex_clear (mutex); + g_slice_free (GMutex, mutex); +} + +/* GCond {{{1 ------------------------------------------------------ */ + +/** + * g_cond_new: + * + * Allocates and initializes a new #GCond. + * + * Returns: a newly allocated #GCond. Free with g_cond_free() + * + * Deprecated: 2.32: GCond can now be statically allocated, or embedded + * in structures and initialised with g_cond_init(). + */ +GCond * +g_cond_new (void) +{ + GCond *cond; + + cond = g_slice_new (GCond); + g_cond_init (cond); + + return cond; +} + +/** + * g_cond_free: + * @cond: a #GCond + * + * Destroys a #GCond that has been created with g_cond_new(). + * + * Calling g_cond_free() for a #GCond on which threads are + * blocking leads to undefined behaviour. + * + * Deprecated: 2.32: GCond can now be statically allocated, or embedded + * in structures and initialised with g_cond_init(). + */ +void +g_cond_free (GCond *cond) +{ + g_cond_clear (cond); + g_slice_free (GCond, cond); +} + +/** + * g_cond_timed_wait: + * @cond: a #GCond + * @mutex: a #GMutex that is currently locked + * @abs_time: a #GTimeVal, determining the final time + * + * Waits until this thread is woken up on @cond, but not longer than + * until the time specified by @abs_time. The @mutex is unlocked before + * falling asleep and locked again before resuming. + * + * If @abs_time is %NULL, g_cond_timed_wait() acts like g_cond_wait(). + * + * This function can be used even if g_thread_init() has not yet been + * called, and, in that case, will immediately return %TRUE. + * + * To easily calculate @abs_time a combination of g_get_real_time() + * and g_time_val_add() can be used. + * + * Returns: %TRUE if @cond was signalled, or %FALSE on timeout + * + * Deprecated:2.32: Use g_cond_wait_until() instead. + */ +gboolean +g_cond_timed_wait (GCond *cond, + GMutex *mutex, + GTimeVal *abs_time) +{ + gint64 end_time; + + if (abs_time == NULL) + { + g_cond_wait (cond, mutex); + return TRUE; + } + + end_time = abs_time->tv_sec; + end_time *= 1000000; + end_time += abs_time->tv_usec; + + /* would be nice if we had clock_rtoffset, but that didn't seem to + * make it into the kernel yet... + */ + end_time += g_get_monotonic_time () - g_get_real_time (); + + return g_cond_wait_until (cond, mutex, end_time); +} + +/* {{{1 Epilogue */ +/* vim: set foldmethod=marker: */ diff --git a/glib/deprecated/gthread.h b/glib/deprecated/gthread.h new file mode 100644 index 0000000..2d490a1 --- /dev/null +++ b/glib/deprecated/gthread.h @@ -0,0 +1,293 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_DEPRECATED_THREAD_H__ +#define __G_DEPRECATED_THREAD_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + +typedef enum +{ + G_THREAD_PRIORITY_LOW, + G_THREAD_PRIORITY_NORMAL, + G_THREAD_PRIORITY_HIGH, + G_THREAD_PRIORITY_URGENT +} GThreadPriority GLIB_DEPRECATED_TYPE_IN_2_32; + +struct _GThread +{ + /*< private >*/ + GThreadFunc func; + gpointer data; + gboolean joinable; + GThreadPriority priority; +}; + +typedef struct _GThreadFunctions GThreadFunctions GLIB_DEPRECATED_TYPE_IN_2_32; +struct _GThreadFunctions +{ + GMutex* (*mutex_new) (void); + void (*mutex_lock) (GMutex *mutex); + gboolean (*mutex_trylock) (GMutex *mutex); + void (*mutex_unlock) (GMutex *mutex); + void (*mutex_free) (GMutex *mutex); + GCond* (*cond_new) (void); + void (*cond_signal) (GCond *cond); + void (*cond_broadcast) (GCond *cond); + void (*cond_wait) (GCond *cond, + GMutex *mutex); + gboolean (*cond_timed_wait) (GCond *cond, + GMutex *mutex, + GTimeVal *end_time); + void (*cond_free) (GCond *cond); + GPrivate* (*private_new) (GDestroyNotify destructor); + gpointer (*private_get) (GPrivate *private_key); + void (*private_set) (GPrivate *private_key, + gpointer data); + void (*thread_create) (GThreadFunc func, + gpointer data, + gulong stack_size, + gboolean joinable, + gboolean bound, + GThreadPriority priority, + gpointer thread, + GError **error); + void (*thread_yield) (void); + void (*thread_join) (gpointer thread); + void (*thread_exit) (void); + void (*thread_set_priority)(gpointer thread, + GThreadPriority priority); + void (*thread_self) (gpointer thread); + gboolean (*thread_equal) (gpointer thread1, + gpointer thread2); +} GLIB_DEPRECATED_TYPE_IN_2_32; + +GLIB_VAR GThreadFunctions g_thread_functions_for_glib_use; +GLIB_VAR gboolean g_thread_use_default_impl; + +GLIB_VAR guint64 (*g_thread_gettime) (void); + +GLIB_DEPRECATED_IN_2_32_FOR(g_thread_new) +GThread *g_thread_create (GThreadFunc func, + gpointer data, + gboolean joinable, + GError **error); + +GLIB_DEPRECATED_IN_2_32_FOR(g_thread_new) +GThread *g_thread_create_full (GThreadFunc func, + gpointer data, + gulong stack_size, + gboolean joinable, + gboolean bound, + GThreadPriority priority, + GError **error); + +GLIB_DEPRECATED_IN_2_32 +void g_thread_set_priority (GThread *thread, + GThreadPriority priority); + +GLIB_DEPRECATED_IN_2_32 +void g_thread_foreach (GFunc thread_func, + gpointer user_data); + +#ifndef G_OS_WIN32 +#include +#include +#endif + +#define g_static_mutex_get_mutex g_static_mutex_get_mutex_impl GLIB_DEPRECATED_MACRO_IN_2_32 +#ifndef G_OS_WIN32 +#define G_STATIC_MUTEX_INIT { NULL, PTHREAD_MUTEX_INITIALIZER } GLIB_DEPRECATED_MACRO_IN_2_32_FOR(g_mutex_init) +#else +#define G_STATIC_MUTEX_INIT { NULL } GLIB_DEPRECATED_MACRO_IN_2_32_FOR(g_mutex_init) +#endif +typedef struct +{ + GMutex *mutex; +#ifndef G_OS_WIN32 + /* only for ABI compatibility reasons */ + pthread_mutex_t unused; +#endif +} GStaticMutex GLIB_DEPRECATED_TYPE_IN_2_32_FOR(GMutex); + +#define g_static_mutex_lock(mutex) \ + g_mutex_lock (g_static_mutex_get_mutex (mutex)) GLIB_DEPRECATED_MACRO_IN_2_32_FOR(g_mutex_lock) +#define g_static_mutex_trylock(mutex) \ + g_mutex_trylock (g_static_mutex_get_mutex (mutex)) GLIB_DEPRECATED_MACRO_IN_2_32_FOR(g_mutex_trylock) +#define g_static_mutex_unlock(mutex) \ + g_mutex_unlock (g_static_mutex_get_mutex (mutex)) GLIB_DEPRECATED_MACRO_IN_2_32_FOR(g_mutex_unlock) + +GLIB_DEPRECATED_IN_2_32_FOR(g_mutex_init) +void g_static_mutex_init (GStaticMutex *mutex); +GLIB_DEPRECATED_IN_2_32_FOR(g_mutex_clear) +void g_static_mutex_free (GStaticMutex *mutex); +GLIB_DEPRECATED_IN_2_32_FOR(GMutex) +GMutex *g_static_mutex_get_mutex_impl (GStaticMutex *mutex); + +typedef struct _GStaticRecMutex GStaticRecMutex GLIB_DEPRECATED_TYPE_IN_2_32_FOR(GRecMutex); +struct _GStaticRecMutex +{ + /*< private >*/ + GStaticMutex mutex; + guint depth; + + /* ABI compat only */ + union { +#ifdef G_OS_WIN32 + void *owner; +#else + pthread_t owner; +#endif + gdouble dummy; + } unused; +} GLIB_DEPRECATED_TYPE_IN_2_32_FOR(GRecMutex); + +#define G_STATIC_REC_MUTEX_INIT { G_STATIC_MUTEX_INIT, 0, { 0 } } GLIB_DEPRECATED_MACRO_IN_2_32_FOR(g_rec_mutex_init) +GLIB_DEPRECATED_IN_2_32_FOR(g_rec_mutex_init) +void g_static_rec_mutex_init (GStaticRecMutex *mutex); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rec_mutex_lock) +void g_static_rec_mutex_lock (GStaticRecMutex *mutex); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rec_mutex_try_lock) +gboolean g_static_rec_mutex_trylock (GStaticRecMutex *mutex); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rec_mutex_unlock) +void g_static_rec_mutex_unlock (GStaticRecMutex *mutex); + +GLIB_DEPRECATED_IN_2_32 +void g_static_rec_mutex_lock_full (GStaticRecMutex *mutex, + guint depth); + +GLIB_DEPRECATED_IN_2_32 +guint g_static_rec_mutex_unlock_full (GStaticRecMutex *mutex); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rec_mutex_free) +void g_static_rec_mutex_free (GStaticRecMutex *mutex); + +typedef struct _GStaticRWLock GStaticRWLock GLIB_DEPRECATED_TYPE_IN_2_32_FOR(GRWLock); +struct _GStaticRWLock +{ + /*< private >*/ + GStaticMutex mutex; + GCond *read_cond; + GCond *write_cond; + guint read_counter; + gboolean have_writer; + guint want_to_read; + guint want_to_write; +} GLIB_DEPRECATED_TYPE_IN_2_32_FOR(GRWLock); + +#define G_STATIC_RW_LOCK_INIT { G_STATIC_MUTEX_INIT, NULL, NULL, 0, FALSE, 0, 0 } GLIB_DEPRECATED_MACRO_IN_2_32_FOR(g_rw_lock_init) + +GLIB_DEPRECATED_IN_2_32_FOR(g_rw_lock_init) +void g_static_rw_lock_init (GStaticRWLock *lock); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rw_lock_reader_lock) +void g_static_rw_lock_reader_lock (GStaticRWLock *lock); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rw_lock_reader_trylock) +gboolean g_static_rw_lock_reader_trylock (GStaticRWLock *lock); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rw_lock_reader_unlock) +void g_static_rw_lock_reader_unlock (GStaticRWLock *lock); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rw_lock_writer_lock) +void g_static_rw_lock_writer_lock (GStaticRWLock *lock); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rw_lock_writer_trylock) +gboolean g_static_rw_lock_writer_trylock (GStaticRWLock *lock); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rw_lock_writer_unlock) +void g_static_rw_lock_writer_unlock (GStaticRWLock *lock); + +GLIB_DEPRECATED_IN_2_32_FOR(g_rw_lock_free) +void g_static_rw_lock_free (GStaticRWLock *lock); + +GLIB_DEPRECATED_IN_2_32 +GPrivate * g_private_new (GDestroyNotify notify); + +typedef struct _GStaticPrivate GStaticPrivate GLIB_DEPRECATED_TYPE_IN_2_32_FOR(GPrivate); +struct _GStaticPrivate +{ + /*< private >*/ + guint index; +} GLIB_DEPRECATED_TYPE_IN_2_32_FOR(GPrivate); + +#define G_STATIC_PRIVATE_INIT { 0 } GLIB_DEPRECATED_MACRO_IN_2_32_FOR(G_PRIVATE_INIT) +GLIB_DEPRECATED_IN_2_32 +void g_static_private_init (GStaticPrivate *private_key); + +GLIB_DEPRECATED_IN_2_32_FOR(g_private_get) +gpointer g_static_private_get (GStaticPrivate *private_key); + +GLIB_DEPRECATED_IN_2_32_FOR(g_private_set) +void g_static_private_set (GStaticPrivate *private_key, + gpointer data, + GDestroyNotify notify); + +GLIB_DEPRECATED_IN_2_32 +void g_static_private_free (GStaticPrivate *private_key); + +GLIB_DEPRECATED_IN_2_32 +gboolean g_once_init_enter_impl (volatile gsize *location); + +GLIB_DEPRECATED_IN_2_32 +void g_thread_init (gpointer vtable); +GLIB_DEPRECATED_IN_2_32 +void g_thread_init_with_errorcheck_mutexes (gpointer vtable); + +GLIB_DEPRECATED_IN_2_32 +gboolean g_thread_get_initialized (void); + +GLIB_VAR gboolean g_threads_got_initialized; + +#define g_thread_supported() (1) GLIB_DEPRECATED_MACRO_IN_2_32 + +GLIB_DEPRECATED_IN_2_32 +GMutex * g_mutex_new (void); +GLIB_DEPRECATED_IN_2_32 +void g_mutex_free (GMutex *mutex); +GLIB_DEPRECATED_IN_2_32 +GCond * g_cond_new (void); +GLIB_DEPRECATED_IN_2_32 +void g_cond_free (GCond *cond); +GLIB_DEPRECATED_IN_2_32 +gboolean g_cond_timed_wait (GCond *cond, + GMutex *mutex, + GTimeVal *timeval); + +G_GNUC_END_IGNORE_DEPRECATIONS + +G_END_DECLS + +#endif /* __G_DEPRECATED_THREAD_H__ */ diff --git a/glib/dirent/dirent-zip b/glib/dirent/dirent-zip new file mode 100644 index 0000000..7301987 --- /dev/null +++ b/glib/dirent/dirent-zip @@ -0,0 +1,19 @@ +#!/bin/sh + +# Build developer package for the dirent library. + +ZIP=/tmp/dirent.zip + +mkdir -p dist/include dist/lib +cp dirent.h dist/include +cp dirent.lib dist/lib + +(cd dist +rm $ZIP +zip $ZIP -@ < + * Significantly revised and rewinddir, seekdir and telldir added by Colin + * Peters + * + */ + +#include +#include +#include +#include +#include + +#include "dirent.h" + +#define WIN32_LEAN_AND_MEAN +#include /* for GetFileAttributes */ + +#include + +#ifdef _UNICODE +#define _tdirent _wdirent +#define _TDIR _WDIR +#define _topendir _wopendir +#define _tclosedir _wclosedir +#define _treaddir _wreaddir +#define _trewinddir _wrewinddir +#define _ttelldir _wtelldir +#define _tseekdir _wseekdir +#else +#define _tdirent dirent +#define _TDIR DIR +#define _topendir opendir +#define _tclosedir closedir +#define _treaddir readdir +#define _trewinddir rewinddir +#define _ttelldir telldir +#define _tseekdir seekdir +#endif + +#define SUFFIX _T("*") +#define SLASH _T("\\") + + +/* + * opendir + * + * Returns a pointer to a DIR structure appropriately filled in to begin + * searching a directory. + */ +_TDIR * +_topendir (const _TCHAR *szPath) +{ + _TDIR *nd; + unsigned int rc; + _TCHAR szFullPath[MAX_PATH]; + + errno = 0; + + if (!szPath) + { + errno = EFAULT; + return (_TDIR *) 0; + } + + if (szPath[0] == _T('\0')) + { + errno = ENOTDIR; + return (_TDIR *) 0; + } + + /* Attempt to determine if the given path really is a directory. */ + rc = GetFileAttributes (szPath); + if (rc == (unsigned int)-1) + { + /* call GetLastError for more error info */ + errno = ENOENT; + return (_TDIR *) 0; + } + if (!(rc & FILE_ATTRIBUTE_DIRECTORY)) + { + /* Error, entry exists but not a directory. */ + errno = ENOTDIR; + return (_TDIR *) 0; + } + + /* Make an absolute pathname. */ + _tfullpath (szFullPath, szPath, MAX_PATH); + + /* Allocate enough space to store DIR structure and the complete + * directory path given. */ + nd = (_TDIR *) malloc (sizeof (_TDIR) + (_tcslen(szFullPath) + _tcslen (SLASH) + + _tcslen(SUFFIX) + 1) * sizeof(_TCHAR)); + + if (!nd) + { + /* Error, out of memory. */ + errno = ENOMEM; + return (_TDIR *) 0; + } + + /* Create the search expression. */ + _tcscpy (nd->dd_name, szFullPath); + + /* Add on a slash if the path does not end with one. */ + if (nd->dd_name[0] != _T('\0') && + nd->dd_name[_tcslen (nd->dd_name) - 1] != _T('/') && + nd->dd_name[_tcslen (nd->dd_name) - 1] != _T('\\')) + { + _tcscat (nd->dd_name, SLASH); + } + + /* Add on the search pattern */ + _tcscat (nd->dd_name, SUFFIX); + + /* Initialize handle to -1 so that a premature closedir doesn't try + * to call _findclose on it. */ + nd->dd_handle = -1; + + /* Initialize the status. */ + nd->dd_stat = 0; + + /* Initialize the dirent structure. ino and reclen are invalid under + * Win32, and name simply points at the appropriate part of the + * findfirst_t structure. */ + nd->dd_dir.d_ino = 0; + nd->dd_dir.d_reclen = 0; + nd->dd_dir.d_namlen = 0; + memset (nd->dd_dir.d_name, 0, sizeof (nd->dd_dir.d_name)); + + return nd; +} + + +/* + * readdir + * + * Return a pointer to a dirent structure filled with the information on the + * next entry in the directory. + */ +struct _tdirent * +_treaddir (_TDIR * dirp) +{ + errno = 0; + + /* Check for valid DIR struct. */ + if (!dirp) + { + errno = EFAULT; + return (struct _tdirent *) 0; + } + + if (dirp->dd_stat < 0) + { + /* We have already returned all files in the directory + * (or the structure has an invalid dd_stat). */ + return (struct _tdirent *) 0; + } + else if (dirp->dd_stat == 0) + { + /* We haven't started the search yet. */ + /* Start the search */ + dirp->dd_handle = _tfindfirst (dirp->dd_name, &(dirp->dd_dta)); + + if (dirp->dd_handle == -1) + { + /* Whoops! Seems there are no files in that + * directory. */ + dirp->dd_stat = -1; + } + else + { + dirp->dd_stat = 1; + } + } + else + { + /* Get the next search entry. */ + if (_tfindnext (dirp->dd_handle, &(dirp->dd_dta))) + { + /* We are off the end or otherwise error. + _findnext sets errno to ENOENT if no more file + Undo this. */ + DWORD winerr = GetLastError(); + if (winerr == ERROR_NO_MORE_FILES) + errno = 0; + _findclose (dirp->dd_handle); + dirp->dd_handle = -1; + dirp->dd_stat = -1; + } + else + { + /* Update the status to indicate the correct + * number. */ + dirp->dd_stat++; + } + } + + if (dirp->dd_stat > 0) + { + /* Successfully got an entry. Everything about the file is + * already appropriately filled in except the length of the + * file name. */ + dirp->dd_dir.d_namlen = _tcslen (dirp->dd_dta.name); + _tcscpy (dirp->dd_dir.d_name, dirp->dd_dta.name); + return &dirp->dd_dir; + } + + return (struct _tdirent *) 0; +} + + +/* + * closedir + * + * Frees up resources allocated by opendir. + */ +int +_tclosedir (_TDIR * dirp) +{ + int rc; + + errno = 0; + rc = 0; + + if (!dirp) + { + errno = EFAULT; + return -1; + } + + if (dirp->dd_handle != -1) + { + rc = _findclose (dirp->dd_handle); + } + + /* Delete the dir structure. */ + free (dirp); + + return rc; +} + +/* + * rewinddir + * + * Return to the beginning of the directory "stream". We simply call findclose + * and then reset things like an opendir. + */ +void +_trewinddir (_TDIR * dirp) +{ + errno = 0; + + if (!dirp) + { + errno = EFAULT; + return; + } + + if (dirp->dd_handle != -1) + { + _findclose (dirp->dd_handle); + } + + dirp->dd_handle = -1; + dirp->dd_stat = 0; +} + +/* + * telldir + * + * Returns the "position" in the "directory stream" which can be used with + * seekdir to go back to an old entry. We simply return the value in stat. + */ +long +_ttelldir (_TDIR * dirp) +{ + errno = 0; + + if (!dirp) + { + errno = EFAULT; + return -1; + } + return dirp->dd_stat; +} + +/* + * seekdir + * + * Seek to an entry previously returned by telldir. We rewind the directory + * and call readdir repeatedly until either dd_stat is the position number + * or -1 (off the end). This is not perfect, in that the directory may + * have changed while we weren't looking. But that is probably the case with + * any such system. + */ +void +_tseekdir (_TDIR * dirp, long lPos) +{ + errno = 0; + + if (!dirp) + { + errno = EFAULT; + return; + } + + if (lPos < -1) + { + /* Seeking to an invalid position. */ + errno = EINVAL; + return; + } + else if (lPos == -1) + { + /* Seek past end. */ + if (dirp->dd_handle != -1) + { + _findclose (dirp->dd_handle); + } + dirp->dd_handle = -1; + dirp->dd_stat = -1; + } + else + { + /* Rewind and read forward to the appropriate index. */ + _trewinddir (dirp); + + while ((dirp->dd_stat < lPos) && _treaddir (dirp)) + ; + } +} diff --git a/glib/dirent/dirent.h b/glib/dirent/dirent.h new file mode 100644 index 0000000..857710f --- /dev/null +++ b/glib/dirent/dirent.h @@ -0,0 +1,127 @@ +/* + * DIRENT.H (formerly DIRLIB.H) + * This file has no copyright assigned and is placed in the Public Domain. + * This file is a part of the mingw-runtime package. + * No warranty is given; refer to the file DISCLAIMER within the package. + * + */ +#ifndef _DIRENT_H_ +#define _DIRENT_H_ + +#include +#include + +#ifndef RC_INVOKED + +#ifdef __cplusplus +extern "C" { +#endif + +struct dirent +{ + long d_ino; /* Always zero. */ + unsigned short d_reclen; /* Always zero. */ + unsigned short d_namlen; /* Length of name in d_name. */ + char d_name[FILENAME_MAX+1]; /* File name plus nul delimiter. */ +}; + +#ifdef _WIN64 +#define INTPTR __int64 +#else +#define INTPTR long +#endif + +/* + * This is an internal data structure. Good programmers will not use it + * except as an argument to one of the functions below. + * dd_stat field is now int (was short in older versions). + */ +typedef struct +{ + /* disk transfer area for this dir */ + struct _finddata_t dd_dta; + + /* dirent struct to return from dir (NOTE: this makes this thread + * safe as long as only one thread uses a particular DIR struct at + * a time) */ + struct dirent dd_dir; + + /* _findnext handle */ + INTPTR dd_handle; + + /* + * Status of search: + * 0 = not started yet (next entry to read is first entry) + * -1 = off the end + * positive = 0 based index of next entry + */ + int dd_stat; + + /* given path for dir with search pattern (struct is extended) */ + char dd_name[1]; +} DIR; + +DIR* __cdecl opendir (const char*); +struct dirent* __cdecl readdir (DIR*); +int __cdecl closedir (DIR*); +void __cdecl rewinddir (DIR*); +long __cdecl telldir (DIR*); +void __cdecl seekdir (DIR*, long); + + +/* wide char versions */ + +struct _wdirent +{ + long d_ino; /* Always zero. */ + unsigned short d_reclen; /* Always zero. */ + unsigned short d_namlen; /* Length of name in d_name. */ + wchar_t d_name[FILENAME_MAX+1]; /* File name plus nul delimiter. */ +}; + +/* + * This is an internal data structure. Good programmers will not use it + * except as an argument to one of the functions below. + */ +typedef struct +{ + /* disk transfer area for this dir */ + struct _wfinddata_t dd_dta; + + /* dirent struct to return from dir (NOTE: this makes this thread + * safe as long as only one thread uses a particular DIR struct at + * a time) */ + struct _wdirent dd_dir; + + /* _findnext handle */ + INTPTR dd_handle; + + /* + * Status of search: + * 0 = not started yet (next entry to read is first entry) + * -1 = off the end + * positive = 0 based index of next entry + */ + int dd_stat; + + /* given path for dir with search pattern (struct is extended) */ + wchar_t dd_name[1]; +} _WDIR; + + + +_WDIR* __cdecl _wopendir (const wchar_t*); +struct _wdirent* __cdecl _wreaddir (_WDIR*); +int __cdecl _wclosedir (_WDIR*); +void __cdecl _wrewinddir (_WDIR*); +long __cdecl _wtelldir (_WDIR*); +void __cdecl _wseekdir (_WDIR*, long); + + +#ifdef __cplusplus +} +#endif + +#endif /* Not RC_INVOKED */ + +#endif /* Not _DIRENT_H_ */ diff --git a/glib/dirent/wdirent.c b/glib/dirent/wdirent.c new file mode 100644 index 0000000..098d854 --- /dev/null +++ b/glib/dirent/wdirent.c @@ -0,0 +1,3 @@ +#define _UNICODE 1 +#define UNICODE 1 +#include "dirent.c" diff --git a/glib/docs.c b/glib/docs.c new file mode 100644 index 0000000..9d6321b --- /dev/null +++ b/glib/docs.c @@ -0,0 +1,2636 @@ +/* + * Copyright © 2011 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 . + * + * Author: Matthias Clasen + */ + + +/* This file collects documentation for macros, typedefs and + * the like, which have no good home in any of the 'real' source + * files. + */ + +/* Basic types {{{1 */ + +/** + * SECTION:types + * @title: Basic Types + * @short_description: standard GLib types, defined for ease-of-use + * and portability + * + * GLib defines a number of commonly used types, which can be divided + * into several groups: + * - New types which are not part of standard C (but are defined in + * various C standard library header files) — #gboolean, #gssize. + * - Integer types which are guaranteed to be the same size across + * all platforms — #gint8, #guint8, #gint16, #guint16, #gint32, + * #guint32, #gint64, #guint64. + * - Types which are easier to use than their standard C counterparts - + * #gpointer, #gconstpointer, #guchar, #guint, #gushort, #gulong. + * - Types which correspond exactly to standard C types, but are + * included for completeness — #gchar, #gint, #gshort, #glong, + * #gfloat, #gdouble. + * - Types which correspond exactly to standard C99 types, but are available + * to use even if your compiler does not support C99 — #gsize, #goffset, + * #gintptr, #guintptr. + * + * GLib also defines macros for the limits of some of the standard + * integer and floating point types, as well as macros for suitable + * printf() formats for these types. + * + * Note that depending on the platform and build configuration, the format + * macros might not be compatible with the system provided printf() function, + * because GLib might use a different printf() implementation internally. + * The format macros will always work with GLib API (like g_print()), and with + * any C99 compatible printf() implementation. + */ + +/** + * gboolean: + * + * A standard boolean type. + * Variables of this type should only contain the value + * %TRUE or %FALSE. + * + * Never directly compare the contents of a #gboolean variable with the values + * %TRUE or %FALSE. Use `if (condition)` to check a #gboolean is "true", instead + * of `if (condition == TRUE)`. Likewise use `if (!condition)` to check a + * #gboolean is "false". + * + * There is no validation when assigning to a #gboolean variable and so it could + * contain any value represented by a #gint. This is why the use of `if + * (condition)` is recommended. All non-zero values in C evaluate to "true". + */ + +/** + * gpointer: + * + * An untyped pointer. + * #gpointer looks better and is easier to use than void*. + */ + +/** + * gconstpointer: + * + * An untyped pointer to constant data. + * The data pointed to should not be changed. + * + * This is typically used in function prototypes to indicate + * that the data pointed to will not be altered by the function. + */ + +/** + * gchar: + * + * Corresponds to the standard C char type. + */ + +/** + * guchar: + * + * Corresponds to the standard C unsigned char type. + */ + +/** + * gint: + * + * Corresponds to the standard C int type. + * Values of this type can range from %G_MININT to %G_MAXINT. + */ + +/** + * G_MININT: + * + * The minimum value which can be held in a #gint. + */ + +/** + * G_MAXINT: + * + * The maximum value which can be held in a #gint. + */ + +/** + * guint: + * + * Corresponds to the standard C unsigned int type. + * Values of this type can range from 0 to %G_MAXUINT. + */ + +/** + * G_MAXUINT: + * + * The maximum value which can be held in a #guint. + */ + +/** + * gshort: + * + * Corresponds to the standard C short type. + * Values of this type can range from %G_MINSHORT to %G_MAXSHORT. + */ + +/** + * G_MINSHORT: + * + * The minimum value which can be held in a #gshort. + */ + +/** + * G_MAXSHORT: + * + * The maximum value which can be held in a #gshort. + */ + +/** + * gushort: + * + * Corresponds to the standard C unsigned short type. + * Values of this type can range from 0 to %G_MAXUSHORT. + */ + +/** + * G_MAXUSHORT: + * + * The maximum value which can be held in a #gushort. + */ + +/** + * glong: + * + * Corresponds to the standard C long type. + * Values of this type can range from %G_MINLONG to %G_MAXLONG. + */ + +/** + * G_MINLONG: + * + * The minimum value which can be held in a #glong. + */ + +/** + * G_MAXLONG: + * + * The maximum value which can be held in a #glong. + */ + +/** + * gulong: + * + * Corresponds to the standard C unsigned long type. + * Values of this type can range from 0 to %G_MAXULONG. + */ + +/** + * G_MAXULONG: + * + * The maximum value which can be held in a #gulong. + */ + +/** + * gint8: + * + * A signed integer guaranteed to be 8 bits on all platforms. + * Values of this type can range from %G_MININT8 (= -128) to + * %G_MAXINT8 (= 127). + */ + +/** + * G_MAXINT8: + * + * The maximum value which can be held in a #gint8. + * + * Since: 2.4 + */ + +/** + * guint8: + * + * An unsigned integer guaranteed to be 8 bits on all platforms. + * Values of this type can range from 0 to %G_MAXUINT8 (= 255). + */ + +/** + * G_MAXUINT8: + * + * The maximum value which can be held in a #guint8. + * + * Since: 2.4 + */ + +/** + * gint16: + * + * A signed integer guaranteed to be 16 bits on all platforms. + * Values of this type can range from %G_MININT16 (= -32,768) to + * %G_MAXINT16 (= 32,767). + * + * To print or scan values of this type, use + * %G_GINT16_MODIFIER and/or %G_GINT16_FORMAT. + */ + +/** + * G_MAXINT16: + * + * The maximum value which can be held in a #gint16. + * + * Since: 2.4 + */ + +/** + * G_GINT16_MODIFIER: + * + * The platform dependent length modifier for conversion specifiers + * for scanning and printing values of type #gint16 or #guint16. It + * is a string literal, but doesn't include the percent-sign, such + * that you can add precision and length modifiers between percent-sign + * and conversion specifier and append a conversion specifier. + * + * The following example prints "0x7b"; + * |[ + * gint16 value = 123; + * g_print ("%#" G_GINT16_MODIFIER "x", value); + * ]| + * + * Since: 2.4 + */ + +/** + * G_GINT16_FORMAT: + * + * This is the platform dependent conversion specifier for scanning and + * printing values of type #gint16. It is a string literal, but doesn't + * include the percent-sign, such that you can add precision and length + * modifiers between percent-sign and conversion specifier. + * + * |[ + * gint16 in; + * gint32 out; + * sscanf ("42", "%" G_GINT16_FORMAT, &in) + * out = in * 1000; + * g_print ("%" G_GINT32_FORMAT, out); + * ]| + */ + +/** + * guint16: + * + * An unsigned integer guaranteed to be 16 bits on all platforms. + * Values of this type can range from 0 to %G_MAXUINT16 (= 65,535). + * + * To print or scan values of this type, use + * %G_GINT16_MODIFIER and/or %G_GUINT16_FORMAT. + */ + +/** + * G_MAXUINT16: + * + * The maximum value which can be held in a #guint16. + * + * Since: 2.4 + */ + +/** + * G_GUINT16_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #guint16. See also %G_GINT16_FORMAT + */ + +/** + * gint32: + * + * A signed integer guaranteed to be 32 bits on all platforms. + * Values of this type can range from %G_MININT32 (= -2,147,483,648) + * to %G_MAXINT32 (= 2,147,483,647). + * + * To print or scan values of this type, use + * %G_GINT32_MODIFIER and/or %G_GINT32_FORMAT. + */ + +/** + * G_MAXINT32: + * + * The maximum value which can be held in a #gint32. + * + * Since: 2.4 + */ + +/** + * G_GINT32_MODIFIER: + * + * The platform dependent length modifier for conversion specifiers + * for scanning and printing values of type #gint32 or #guint32. It + * is a string literal. See also %G_GINT16_MODIFIER. + * + * Since: 2.4 + */ + +/** + * G_GINT32_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #gint32. See also %G_GINT16_FORMAT. + */ + +/** + * guint32: + * + * An unsigned integer guaranteed to be 32 bits on all platforms. + * Values of this type can range from 0 to %G_MAXUINT32 (= 4,294,967,295). + * + * To print or scan values of this type, use + * %G_GINT32_MODIFIER and/or %G_GUINT32_FORMAT. + */ + +/** + * G_MAXUINT32: + * + * The maximum value which can be held in a #guint32. + * + * Since: 2.4 + */ + +/** + * G_GUINT32_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #guint32. See also %G_GINT16_FORMAT. + */ + +/** + * gint64: + * + * A signed integer guaranteed to be 64 bits on all platforms. + * Values of this type can range from %G_MININT64 + * (= -9,223,372,036,854,775,808) to %G_MAXINT64 + * (= 9,223,372,036,854,775,807). + * + * To print or scan values of this type, use + * %G_GINT64_MODIFIER and/or %G_GINT64_FORMAT. + */ + +/** + * G_MAXINT64: + * + * The maximum value which can be held in a #gint64. + */ + +/** + * G_GINT64_MODIFIER: + * + * The platform dependent length modifier for conversion specifiers + * for scanning and printing values of type #gint64 or #guint64. + * It is a string literal. + * + * Some platforms do not support printing 64-bit integers, even + * though the types are supported. On such platforms %G_GINT64_MODIFIER + * is not defined. + * + * Since: 2.4 + */ + +/** + * G_GINT64_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #gint64. See also %G_GINT16_FORMAT. + * + * Some platforms do not support scanning and printing 64-bit integers, + * even though the types are supported. On such platforms %G_GINT64_FORMAT + * is not defined. Note that scanf() may not support 64-bit integers, even + * if %G_GINT64_FORMAT is defined. Due to its weak error handling, scanf() + * is not recommended for parsing anyway; consider using g_ascii_strtoull() + * instead. + */ + +/** + * guint64: + * + * An unsigned integer guaranteed to be 64-bits on all platforms. + * Values of this type can range from 0 to %G_MAXUINT64 + * (= 18,446,744,073,709,551,615). + * + * To print or scan values of this type, use + * %G_GINT64_MODIFIER and/or %G_GUINT64_FORMAT. + */ + +/** + * G_MAXUINT64: + * + * The maximum value which can be held in a #guint64. + */ + +/** + * G_GUINT64_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #guint64. See also %G_GINT16_FORMAT. + * + * Some platforms do not support scanning and printing 64-bit integers, + * even though the types are supported. On such platforms %G_GUINT64_FORMAT + * is not defined. Note that scanf() may not support 64-bit integers, even + * if %G_GINT64_FORMAT is defined. Due to its weak error handling, scanf() + * is not recommended for parsing anyway; consider using g_ascii_strtoull() + * instead. + */ + +/** + * G_GINT64_CONSTANT: + * @val: a literal integer value, e.g. 0x1d636b02300a7aa7 + * + * This macro is used to insert 64-bit integer literals + * into the source code. + */ + +/** + * G_GUINT64_CONSTANT: + * @val: a literal integer value, e.g. 0x1d636b02300a7aa7U + * + * This macro is used to insert 64-bit unsigned integer + * literals into the source code. + * + * Since: 2.10 + */ + +/** + * gfloat: + * + * Corresponds to the standard C float type. + * Values of this type can range from -%G_MAXFLOAT to %G_MAXFLOAT. + */ + +/** + * G_MINFLOAT: + * + * The minimum positive value which can be held in a #gfloat. + * + * If you are interested in the smallest value which can be held + * in a #gfloat, use -%G_MAXFLOAT. + */ + +/** + * G_MAXFLOAT: + * + * The maximum value which can be held in a #gfloat. + */ + +/** + * gdouble: + * + * Corresponds to the standard C double type. + * Values of this type can range from -%G_MAXDOUBLE to %G_MAXDOUBLE. + */ + +/** + * G_MINDOUBLE: + * + * The minimum positive value which can be held in a #gdouble. + * + * If you are interested in the smallest value which can be held + * in a #gdouble, use -%G_MAXDOUBLE. + */ + +/** + * G_MAXDOUBLE: + * + * The maximum value which can be held in a #gdouble. + */ + +/** + * gsize: + * + * An unsigned integer type of the result of the sizeof operator, + * corresponding to the size_t type defined in C99. + * This type is wide enough to hold the numeric value of a pointer, + * so it is usually 32 bit wide on a 32-bit platform and 64 bit wide + * on a 64-bit platform. Values of this type can range from 0 to + * %G_MAXSIZE. + * + * To print or scan values of this type, use + * %G_GSIZE_MODIFIER and/or %G_GSIZE_FORMAT. + */ + +/** + * G_MAXSIZE: + * + * The maximum value which can be held in a #gsize. + * + * Since: 2.4 + */ + +/** + * G_GSIZE_MODIFIER: + * + * The platform dependent length modifier for conversion specifiers + * for scanning and printing values of type #gsize. It + * is a string literal. + * + * Since: 2.6 + */ + +/** + * G_GSIZE_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #gsize. See also %G_GINT16_FORMAT. + * + * Since: 2.6 + */ + +/** + * gssize: + * + * A signed variant of #gsize, corresponding to the + * ssize_t defined on most platforms. + * Values of this type can range from %G_MINSSIZE + * to %G_MAXSSIZE. + * + * To print or scan values of this type, use + * %G_GSSIZE_MODIFIER and/or %G_GSSIZE_FORMAT. + */ + +/** + * G_MINSSIZE: + * + * The minimum value which can be held in a #gssize. + * + * Since: 2.14 + */ + +/** + * G_MAXSSIZE: + * + * The maximum value which can be held in a #gssize. + * + * Since: 2.14 + */ + +/** + * G_GSSIZE_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #gssize. See also %G_GINT16_FORMAT. + * + * Since: 2.6 + */ + +/** + * G_GSSIZE_MODIFIER: + * + * The platform dependent length modifier for conversion specifiers + * for scanning and printing values of type #gssize. It + * is a string literal. + * + * Since: 2.6 + */ + +/** + * goffset: + * + * A signed integer type that is used for file offsets, + * corresponding to the POSIX type `off_t` as if compiling with + * `_FILE_OFFSET_BITS` set to 64. #goffset is always 64 bits wide, even on + * 32-bit architectures. + * Values of this type can range from %G_MINOFFSET to + * %G_MAXOFFSET. + * + * To print or scan values of this type, use + * %G_GOFFSET_MODIFIER and/or %G_GOFFSET_FORMAT. + * + * Since: 2.14 + */ + +/** + * G_MINOFFSET: + * + * The minimum value which can be held in a #goffset. + */ + +/** + * G_MAXOFFSET: + * + * The maximum value which can be held in a #goffset. + */ + +/** + * G_GOFFSET_MODIFIER: + * + * The platform dependent length modifier for conversion specifiers + * for scanning and printing values of type #goffset. It is a string + * literal. See also %G_GINT64_MODIFIER. + * + * Since: 2.20 + */ + +/** + * G_GOFFSET_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #goffset. See also %G_GINT64_FORMAT. + * + * Since: 2.20 + */ + +/** + * G_GOFFSET_CONSTANT: + * @val: a literal integer value, e.g. 0x1d636b02300a7aa7 + * + * This macro is used to insert #goffset 64-bit integer literals + * into the source code. + * + * See also G_GINT64_CONSTANT(). + * + * Since: 2.20 + */ + +/** + * gintptr: + * + * Corresponds to the C99 type intptr_t, + * a signed integer type that can hold any pointer. + * + * To print or scan values of this type, use + * %G_GINTPTR_MODIFIER and/or %G_GINTPTR_FORMAT. + * + * Since: 2.18 + */ + +/** + * G_GINTPTR_MODIFIER: + * + * The platform dependent length modifier for conversion specifiers + * for scanning and printing values of type #gintptr or #guintptr. + * It is a string literal. + * + * Since: 2.22 + */ + +/** + * G_GINTPTR_FORMAT: + * + * This is the platform dependent conversion specifier for scanning + * and printing values of type #gintptr. + * + * Since: 2.22 + */ + +/** + * guintptr: + * + * Corresponds to the C99 type uintptr_t, + * an unsigned integer type that can hold any pointer. + * + * To print or scan values of this type, use + * %G_GINTPTR_MODIFIER and/or %G_GUINTPTR_FORMAT. + * + * Since: 2.18 + */ + +/** + * G_GUINTPTR_FORMAT: + * + * This is the platform dependent conversion specifier + * for scanning and printing values of type #guintptr. + * + * Since: 2.22 + */ + +/* Type conversion {{{1 */ + +/** + * SECTION:type_conversion + * @title: Type Conversion Macros + * @short_description: portably storing integers in pointer variables + * + * Many times GLib, GTK+, and other libraries allow you to pass "user + * data" to a callback, in the form of a void pointer. From time to time + * you want to pass an integer instead of a pointer. You could allocate + * an integer, with something like: + * |[ + * int *ip = g_new (int, 1); + * *ip = 42; + * ]| + * But this is inconvenient, and it's annoying to have to free the + * memory at some later time. + * + * Pointers are always at least 32 bits in size (on all platforms GLib + * intends to support). Thus you can store at least 32-bit integer values + * in a pointer value. Naively, you might try this, but it's incorrect: + * |[ + * gpointer p; + * int i; + * p = (void*) 42; + * i = (int) p; + * ]| + * Again, that example was not correct, don't copy it. + * The problem is that on some systems you need to do this: + * |[ + * gpointer p; + * int i; + * p = (void*) (long) 42; + * i = (int) (long) p; + * ]| + * The GLib macros GPOINTER_TO_INT(), GINT_TO_POINTER(), etc. take care + * to do the right thing on every platform. + * + * Warning: You may not store pointers in integers. This is not + * portable in any way, shape or form. These macros only allow storing + * integers in pointers, and only preserve 32 bits of the integer; values + * outside the range of a 32-bit integer will be mangled. + */ + +/** + * GINT_TO_POINTER: + * @i: integer to stuff into a pointer + * + * Stuffs an integer into a pointer type. + * + * Remember, you may not store pointers in integers. This is not portable + * in any way, shape or form. These macros only allow storing integers in + * pointers, and only preserve 32 bits of the integer; values outside the + * range of a 32-bit integer will be mangled. + */ + +/** + * GPOINTER_TO_INT: + * @p: pointer containing an integer + * + * Extracts an integer from a pointer. The integer must have + * been stored in the pointer with GINT_TO_POINTER(). + * + * Remember, you may not store pointers in integers. This is not portable + * in any way, shape or form. These macros only allow storing integers in + * pointers, and only preserve 32 bits of the integer; values outside the + * range of a 32-bit integer will be mangled. + */ + +/** + * GUINT_TO_POINTER: + * @u: unsigned integer to stuff into the pointer + * + * Stuffs an unsigned integer into a pointer type. + */ + +/** + * GPOINTER_TO_UINT: + * @p: pointer to extract an unsigned integer from + * + * Extracts an unsigned integer from a pointer. The integer must have + * been stored in the pointer with GUINT_TO_POINTER(). + */ + +/** + * GSIZE_TO_POINTER: + * @s: #gsize to stuff into the pointer + * + * Stuffs a #gsize into a pointer type. + */ + +/** + * GPOINTER_TO_SIZE: + * @p: pointer to extract a #gsize from + * + * Extracts a #gsize from a pointer. The #gsize must have + * been stored in the pointer with GSIZE_TO_POINTER(). + */ + +/* Byte order {{{1 */ + +/** + * SECTION:byte_order + * @title: Byte Order Macros + * @short_description: a portable way to convert between different byte orders + * + * These macros provide a portable way to determine the host byte order + * and to convert values between different byte orders. + * + * The byte order is the order in which bytes are stored to create larger + * data types such as the #gint and #glong values. + * The host byte order is the byte order used on the current machine. + * + * Some processors store the most significant bytes (i.e. the bytes that + * hold the largest part of the value) first. These are known as big-endian + * processors. Other processors (notably the x86 family) store the most + * significant byte last. These are known as little-endian processors. + * + * Finally, to complicate matters, some other processors store the bytes in + * a rather curious order known as PDP-endian. For a 4-byte word, the 3rd + * most significant byte is stored first, then the 4th, then the 1st and + * finally the 2nd. + * + * Obviously there is a problem when these different processors communicate + * with each other, for example over networks or by using binary file formats. + * This is where these macros come in. They are typically used to convert + * values into a byte order which has been agreed on for use when + * communicating between different processors. The Internet uses what is + * known as 'network byte order' as the standard byte order (which is in + * fact the big-endian byte order). + * + * Note that the byte order conversion macros may evaluate their arguments + * multiple times, thus you should not use them with arguments which have + * side-effects. + */ + +/** + * G_BYTE_ORDER: + * + * The host byte order. + * This can be either %G_LITTLE_ENDIAN or %G_BIG_ENDIAN (support for + * %G_PDP_ENDIAN may be added in future.) + */ + +/** + * G_LITTLE_ENDIAN: + * + * Specifies one of the possible types of byte order. + * See %G_BYTE_ORDER. + */ + +/** + * G_BIG_ENDIAN: + * + * Specifies one of the possible types of byte order. + * See %G_BYTE_ORDER. + */ + +/** + * G_PDP_ENDIAN: + * + * Specifies one of the possible types of byte order + * (currently unused). See %G_BYTE_ORDER. + */ + +/** + * g_htonl: + * @val: a 32-bit integer value in host byte order + * + * Converts a 32-bit integer value from host to network byte order. + * + * Returns: @val converted to network byte order + */ + +/** + * g_htons: + * @val: a 16-bit integer value in host byte order + * + * Converts a 16-bit integer value from host to network byte order. + * + * Returns: @val converted to network byte order + */ + +/** + * g_ntohl: + * @val: a 32-bit integer value in network byte order + * + * Converts a 32-bit integer value from network to host byte order. + * + * Returns: @val converted to host byte order. + */ + +/** + * g_ntohs: + * @val: a 16-bit integer value in network byte order + * + * Converts a 16-bit integer value from network to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT_FROM_BE: + * @val: a #gint value in big-endian byte order + * + * Converts a #gint value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT_FROM_LE: + * @val: a #gint value in little-endian byte order + * + * Converts a #gint value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT_TO_BE: + * @val: a #gint value in host byte order + * + * Converts a #gint value from host byte order to big-endian. + * + * Returns: @val converted to big-endian byte order + */ + +/** + * GINT_TO_LE: + * @val: a #gint value in host byte order + * + * Converts a #gint value from host byte order to little-endian. + * + * Returns: @val converted to little-endian byte order + */ + +/** + * GUINT_FROM_BE: + * @val: a #guint value in big-endian byte order + * + * Converts a #guint value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GUINT_FROM_LE: + * @val: a #guint value in little-endian byte order + * + * Converts a #guint value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GUINT_TO_BE: + * @val: a #guint value in host byte order + * + * Converts a #guint value from host byte order to big-endian. + * + * Returns: @val converted to big-endian byte order + */ + +/** + * GUINT_TO_LE: + * @val: a #guint value in host byte order + * + * Converts a #guint value from host byte order to little-endian. + * + * Returns: @val converted to little-endian byte order. + */ + +/** + * GLONG_FROM_BE: + * @val: a #glong value in big-endian byte order + * + * Converts a #glong value from big-endian to the host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GLONG_FROM_LE: + * @val: a #glong value in little-endian byte order + * + * Converts a #glong value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GLONG_TO_BE: + * @val: a #glong value in host byte order + * + * Converts a #glong value from host byte order to big-endian. + * + * Returns: @val converted to big-endian byte order + */ + +/** + * GLONG_TO_LE: + * @val: a #glong value in host byte order + * + * Converts a #glong value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GULONG_FROM_BE: + * @val: a #gulong value in big-endian byte order + * + * Converts a #gulong value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GULONG_FROM_LE: + * @val: a #gulong value in little-endian byte order + * + * Converts a #gulong value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GULONG_TO_BE: + * @val: a #gulong value in host byte order + * + * Converts a #gulong value from host byte order to big-endian. + * + * Returns: @val converted to big-endian + */ + +/** + * GULONG_TO_LE: + * @val: a #gulong value in host byte order + * + * Converts a #gulong value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GSIZE_FROM_BE: + * @val: a #gsize value in big-endian byte order + * + * Converts a #gsize value from big-endian to the host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GSIZE_FROM_LE: + * @val: a #gsize value in little-endian byte order + * + * Converts a #gsize value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GSIZE_TO_BE: + * @val: a #gsize value in host byte order + * + * Converts a #gsize value from host byte order to big-endian. + * + * Returns: @val converted to big-endian byte order + */ + +/** + * GSIZE_TO_LE: + * @val: a #gsize value in host byte order + * + * Converts a #gsize value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GSSIZE_FROM_BE: + * @val: a #gssize value in big-endian byte order + * + * Converts a #gssize value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GSSIZE_FROM_LE: + * @val: a #gssize value in little-endian byte order + * + * Converts a #gssize value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GSSIZE_TO_BE: + * @val: a #gssize value in host byte order + * + * Converts a #gssize value from host byte order to big-endian. + * + * Returns: @val converted to big-endian + */ + +/** + * GSSIZE_TO_LE: + * @val: a #gssize value in host byte order + * + * Converts a #gssize value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GINT16_FROM_BE: + * @val: a #gint16 value in big-endian byte order + * + * Converts a #gint16 value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT16_FROM_LE: + * @val: a #gint16 value in little-endian byte order + * + * Converts a #gint16 value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT16_TO_BE: + * @val: a #gint16 value in host byte order + * + * Converts a #gint16 value from host byte order to big-endian. + * + * Returns: @val converted to big-endian + */ + +/** + * GINT16_TO_LE: + * @val: a #gint16 value in host byte order + * + * Converts a #gint16 value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GUINT16_FROM_BE: + * @val: a #guint16 value in big-endian byte order + * + * Converts a #guint16 value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GUINT16_FROM_LE: + * @val: a #guint16 value in little-endian byte order + * + * Converts a #guint16 value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GUINT16_TO_BE: + * @val: a #guint16 value in host byte order + * + * Converts a #guint16 value from host byte order to big-endian. + * + * Returns: @val converted to big-endian + */ + +/** + * GUINT16_TO_LE: + * @val: a #guint16 value in host byte order + * + * Converts a #guint16 value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GINT32_FROM_BE: + * @val: a #gint32 value in big-endian byte order + * + * Converts a #gint32 value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT32_FROM_LE: + * @val: a #gint32 value in little-endian byte order + * + * Converts a #gint32 value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT32_TO_BE: + * @val: a #gint32 value in host byte order + * + * Converts a #gint32 value from host byte order to big-endian. + * + * Returns: @val converted to big-endian + */ + +/** + * GINT32_TO_LE: + * @val: a #gint32 value in host byte order + * + * Converts a #gint32 value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GUINT32_FROM_BE: + * @val: a #guint32 value in big-endian byte order + * + * Converts a #guint32 value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GUINT32_FROM_LE: + * @val: a #guint32 value in little-endian byte order + * + * Converts a #guint32 value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GUINT32_TO_BE: + * @val: a #guint32 value in host byte order + * + * Converts a #guint32 value from host byte order to big-endian. + * + * Returns: @val converted to big-endian + */ + +/** + * GUINT32_TO_LE: + * @val: a #guint32 value in host byte order + * + * Converts a #guint32 value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GINT64_FROM_BE: + * @val: a #gint64 value in big-endian byte order + * + * Converts a #gint64 value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT64_FROM_LE: + * @val: a #gint64 value in little-endian byte order + * + * Converts a #gint64 value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GINT64_TO_BE: + * @val: a #gint64 value in host byte order + * + * Converts a #gint64 value from host byte order to big-endian. + * + * Returns: @val converted to big-endian + */ + +/** + * GINT64_TO_LE: + * @val: a #gint64 value in host byte order + * + * Converts a #gint64 value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GUINT64_FROM_BE: + * @val: a #guint64 value in big-endian byte order + * + * Converts a #guint64 value from big-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GUINT64_FROM_LE: + * @val: a #guint64 value in little-endian byte order + * + * Converts a #guint64 value from little-endian to host byte order. + * + * Returns: @val converted to host byte order + */ + +/** + * GUINT64_TO_BE: + * @val: a #guint64 value in host byte order + * + * Converts a #guint64 value from host byte order to big-endian. + * + * Returns: @val converted to big-endian + */ + +/** + * GUINT64_TO_LE: + * @val: a #guint64 value in host byte order + * + * Converts a #guint64 value from host byte order to little-endian. + * + * Returns: @val converted to little-endian + */ + +/** + * GUINT16_SWAP_BE_PDP: + * @val: a #guint16 value in big-endian or pdp-endian byte order + * + * Converts a #guint16 value between big-endian and pdp-endian byte order. + * The conversion is symmetric so it can be used both ways. + * + * Returns: @val converted to the opposite byte order + */ + +/** + * GUINT16_SWAP_LE_BE: + * @val: a #guint16 value in little-endian or big-endian byte order + * + * Converts a #guint16 value between little-endian and big-endian byte order. + * The conversion is symmetric so it can be used both ways. + * + * Returns: @val converted to the opposite byte order + */ + +/** + * GUINT16_SWAP_LE_PDP: + * @val: a #guint16 value in little-endian or pdp-endian byte order + * + * Converts a #guint16 value between little-endian and pdp-endian byte order. + * The conversion is symmetric so it can be used both ways. + * + * Returns: @val converted to the opposite byte order + */ + +/** + * GUINT32_SWAP_BE_PDP: + * @val: a #guint32 value in big-endian or pdp-endian byte order + * + * Converts a #guint32 value between big-endian and pdp-endian byte order. + * The conversion is symmetric so it can be used both ways. + * + * Returns: @val converted to the opposite byte order + */ + +/** + * GUINT32_SWAP_LE_BE: + * @val: a #guint32 value in little-endian or big-endian byte order + * + * Converts a #guint32 value between little-endian and big-endian byte order. + * The conversion is symmetric so it can be used both ways. + * + * Returns: @val converted to the opposite byte order + */ + +/** + * GUINT32_SWAP_LE_PDP: + * @val: a #guint32 value in little-endian or pdp-endian byte order + * + * Converts a #guint32 value between little-endian and pdp-endian byte order. + * The conversion is symmetric so it can be used both ways. + * + * Returns: @val converted to the opposite byte order + */ + +/** + * GUINT64_SWAP_LE_BE: + * @val: a #guint64 value in little-endian or big-endian byte order + * + * Converts a #guint64 value between little-endian and big-endian byte order. + * The conversion is symmetric so it can be used both ways. + * + * Returns: @val converted to the opposite byte order + */ + +/* Bounds-checked integer arithmetic {{{1 */ +/** + * SECTION:checkedmath + * @title: Bounds-checking integer arithmetic + * @short_description: a set of helpers for performing checked integer arithmetic + * + * GLib offers a set of macros for doing additions and multiplications + * of unsigned integers, with checks for overflows. + * + * The helpers all have three arguments. A pointer to the destination + * is always the first argument and the operands to the operation are + * the other two. + * + * Following standard GLib convention, the helpers return %TRUE in case + * of success (ie: no overflow). + * + * The helpers may be macros, normal functions or inlines. They may be + * implemented with inline assembly or compiler intrinsics where + * available. + * + * Since: 2.48 + */ + +/** + * g_uint_checked_add + * @dest: a pointer to the #guint destination + * @a: the #guint left operand + * @b: the #guint right operand + * + * Performs a checked addition of @a and @b, storing the result in + * @dest. + * + * If the operation is successful, %TRUE is returned. If the operation + * overflows then the state of @dest is undefined and %FALSE is + * returned. + * + * Returns: %TRUE if there was no overflow + * Since: 2.48 + */ + +/** + * g_uint_checked_mul + * @dest: a pointer to the #guint destination + * @a: the #guint left operand + * @b: the #guint right operand + * + * Performs a checked multiplication of @a and @b, storing the result in + * @dest. + * + * If the operation is successful, %TRUE is returned. If the operation + * overflows then the state of @dest is undefined and %FALSE is + * returned. + * + * Returns: %TRUE if there was no overflow + * Since: 2.48 + */ + +/** + * g_uint64_checked_add + * @dest: a pointer to the #guint64 destination + * @a: the #guint64 left operand + * @b: the #guint64 right operand + * + * Performs a checked addition of @a and @b, storing the result in + * @dest. + * + * If the operation is successful, %TRUE is returned. If the operation + * overflows then the state of @dest is undefined and %FALSE is + * returned. + * + * Returns: %TRUE if there was no overflow + * Since: 2.48 + */ + +/** + * g_uint64_checked_mul + * @dest: a pointer to the #guint64 destination + * @a: the #guint64 left operand + * @b: the #guint64 right operand + * + * Performs a checked multiplication of @a and @b, storing the result in + * @dest. + * + * If the operation is successful, %TRUE is returned. If the operation + * overflows then the state of @dest is undefined and %FALSE is + * returned. + * + * Returns: %TRUE if there was no overflow + * Since: 2.48 + */ + +/** + * g_size_checked_add + * @dest: a pointer to the #gsize destination + * @a: the #gsize left operand + * @b: the #gsize right operand + * + * Performs a checked addition of @a and @b, storing the result in + * @dest. + * + * If the operation is successful, %TRUE is returned. If the operation + * overflows then the state of @dest is undefined and %FALSE is + * returned. + * + * Returns: %TRUE if there was no overflow + * Since: 2.48 + */ + +/** + * g_size_checked_mul + * @dest: a pointer to the #gsize destination + * @a: the #gsize left operand + * @b: the #gsize right operand + * + * Performs a checked multiplication of @a and @b, storing the result in + * @dest. + * + * If the operation is successful, %TRUE is returned. If the operation + * overflows then the state of @dest is undefined and %FALSE is + * returned. + * + * Returns: %TRUE if there was no overflow + * Since: 2.48 + */ +/* Numerical Definitions {{{1 */ + +/** + * SECTION:numerical + * @title: Numerical Definitions + * @short_description: mathematical constants, and floating point decomposition + * + * GLib offers mathematical constants such as %G_PI for the value of pi; + * many platforms have these in the C library, but some don't, the GLib + * versions always exist. + * + * The #GFloatIEEE754 and #GDoubleIEEE754 unions are used to access the + * sign, mantissa and exponent of IEEE floats and doubles. These unions are + * defined as appropriate for a given platform. IEEE floats and doubles are + * supported (used for storage) by at least Intel, PPC and Sparc. See + * [IEEE 754-2008](http://en.wikipedia.org/wiki/IEEE_float) + * for more information about IEEE number formats. + */ + +/** + * G_IEEE754_FLOAT_BIAS: + * + * The bias by which exponents in single-precision floats are offset. + */ + +/** + * G_IEEE754_DOUBLE_BIAS: + * + * The bias by which exponents in double-precision floats are offset. + */ + +/** + * GFloatIEEE754: + * @v_float: the double value + * + * The #GFloatIEEE754 and #GDoubleIEEE754 unions are used to access the sign, + * mantissa and exponent of IEEE floats and doubles. These unions are defined + * as appropriate for a given platform. IEEE floats and doubles are supported + * (used for storage) by at least Intel, PPC and Sparc. + */ + +/** + * GDoubleIEEE754: + * @v_double: the double value + * + * The #GFloatIEEE754 and #GDoubleIEEE754 unions are used to access the sign, + * mantissa and exponent of IEEE floats and doubles. These unions are defined + * as appropriate for a given platform. IEEE floats and doubles are supported + * (used for storage) by at least Intel, PPC and Sparc. + */ + +/** + * G_E: + * + * The base of natural logarithms. + */ + +/** + * G_LN2: + * + * The natural logarithm of 2. + */ + +/** + * G_LN10: + * + * The natural logarithm of 10. + */ + +/** + * G_PI: + * + * The value of pi (ratio of circle's circumference to its diameter). + */ + +/** + * G_PI_2: + * + * Pi divided by 2. + */ + +/** + * G_PI_4: + * + * Pi divided by 4. + */ + +/** + * G_SQRT2: + * + * The square root of two. + */ + +/** + * G_LOG_2_BASE_10: + * + * Multiplying the base 2 exponent by this number yields the base 10 exponent. + */ + +/* Macros {{{1 */ + +/** + * SECTION:macros + * @title: Standard Macros + * @short_description: commonly-used macros + * + * These macros provide a few commonly-used features. + */ + +/** + * G_OS_WIN32: + * + * This macro is defined only on Windows. So you can bracket + * Windows-specific code in "\#ifdef G_OS_WIN32". + */ + +/** + * G_OS_UNIX: + * + * This macro is defined only on UNIX. So you can bracket + * UNIX-specific code in "\#ifdef G_OS_UNIX". + */ + +/** + * G_DIR_SEPARATOR: + * + * The directory separator character. + * This is '/' on UNIX machines and '\' under Windows. + */ + +/** + * G_DIR_SEPARATOR_S: + * + * The directory separator as a string. + * This is "/" on UNIX machines and "\" under Windows. + */ + +/** + * G_IS_DIR_SEPARATOR: + * @c: a character + * + * Checks whether a character is a directory + * separator. It returns %TRUE for '/' on UNIX + * machines and for '\' or '/' under Windows. + * + * Since: 2.6 + */ + +/** + * G_SEARCHPATH_SEPARATOR: + * + * The search path separator character. + * This is ':' on UNIX machines and ';' under Windows. + */ + +/** + * G_SEARCHPATH_SEPARATOR_S: + * + * The search path separator as a string. + * This is ":" on UNIX machines and ";" under Windows. + */ + +/** + * TRUE: + * + * Defines the %TRUE value for the #gboolean type. + */ + +/** + * FALSE: + * + * Defines the %FALSE value for the #gboolean type. + */ + +/** + * NULL: + * + * Defines the standard %NULL pointer. + */ + +/** + * MIN: + * @a: a numeric value + * @b: a numeric value + * + * Calculates the minimum of @a and @b. + * + * Returns: the minimum of @a and @b. + */ + +/** + * MAX: + * @a: a numeric value + * @b: a numeric value + * + * Calculates the maximum of @a and @b. + * + * Returns: the maximum of @a and @b. + */ + +/** + * ABS: + * @a: a numeric value + * + * Calculates the absolute value of @a. + * The absolute value is simply the number with any negative sign taken away. + * + * For example, + * - ABS(-10) is 10. + * - ABS(10) is also 10. + * + * Returns: the absolute value of @a. + */ + +/** + * CLAMP: + * @x: the value to clamp + * @low: the minimum value allowed + * @high: the maximum value allowed + * + * Ensures that @x is between the limits set by @low and @high. If @low is + * greater than @high the result is undefined. + * + * For example, + * - CLAMP(5, 10, 15) is 10. + * - CLAMP(15, 5, 10) is 10. + * - CLAMP(20, 15, 25) is 20. + * + * Returns: the value of @x clamped to the range between @low and @high + */ + +/** + * G_APPROX_VALUE: + * @a: a numeric value + * @b: a numeric value + * @epsilon: a numeric value that expresses the tolerance between @a and @b + * + * Evaluates to a truth value if the absolute difference between @a and @b is + * smaller than @epsilon, and to a false value otherwise. + * + * For example, + * - `G_APPROX_VALUE (5, 6, 2)` evaluates to true + * - `G_APPROX_VALUE (3.14, 3.15, 0.001)` evaluates to false + * - `G_APPROX_VALUE (n, 0.f, FLT_EPSILON)` evaluates to true if `n` is within + * the single precision floating point epsilon from zero + * + * Returns: %TRUE if the two values are within the desired range + * + * Since: 2.58 + */ + +/** + * G_STRUCT_MEMBER: + * @member_type: the type of the struct field + * @struct_p: a pointer to a struct + * @struct_offset: the offset of the field from the start of the struct, + * in bytes + * + * Returns a member of a structure at a given offset, using the given type. + * + * Returns: the struct member + */ + +/** + * G_STRUCT_MEMBER_P: + * @struct_p: a pointer to a struct + * @struct_offset: the offset from the start of the struct, in bytes + * + * Returns an untyped pointer to a given offset of a struct. + * + * Returns: an untyped pointer to @struct_p plus @struct_offset bytes + */ + +/** + * G_STRUCT_OFFSET: + * @struct_type: a structure type, e.g. #GtkWidget + * @member: a field in the structure, e.g. @window + * + * Returns the offset, in bytes, of a member of a struct. + * + * Returns: the offset of @member from the start of @struct_type + */ + +/** + * G_N_ELEMENTS: + * @arr: the array + * + * Determines the number of elements in an array. The array must be + * declared so the compiler knows its size at compile-time; this + * macro will not work on an array allocated on the heap, only static + * arrays or arrays on the stack. + */ + +/* Miscellaneous Macros {{{1 */ + +/** + * SECTION:macros_misc + * @title: Miscellaneous Macros + * @short_description: specialized macros which are not used often + * + * These macros provide more specialized features which are not + * needed so often by application programmers. + */ + +/** + * G_STMT_START: + * + * Used within multi-statement macros so that they can be used in places + * where only one statement is expected by the compiler. + */ + +/** + * G_STMT_END: + * + * Used within multi-statement macros so that they can be used in places + * where only one statement is expected by the compiler. + */ + +/** + * G_BEGIN_DECLS: + * + * Used (along with %G_END_DECLS) to bracket header files. If the + * compiler in use is a C++ compiler, adds extern "C" + * around the header. + */ + +/** + * G_END_DECLS: + * + * Used (along with %G_BEGIN_DECLS) to bracket header files. If the + * compiler in use is a C++ compiler, adds extern "C" + * around the header. + */ + +/** + * G_VA_COPY: + * @ap1: the va_list variable to place a copy of @ap2 in + * @ap2: a va_list + * + * Portable way to copy va_list variables. + * + * In order to use this function, you must include string.h yourself, + * because this macro may use memmove() and GLib does not include + * string.h for you. + * + * Each invocation of `G_VA_COPY (ap1, ap2)` must be matched with a + * corresponding `va_end (ap1)` call in the same function. + */ + +/** + * G_STRINGIFY: + * @macro_or_string: a macro or a string + * + * Accepts a macro or a string and converts it into a string after + * preprocessor argument expansion. For example, the following code: + * + * |[ + * #define AGE 27 + * const gchar *greeting = G_STRINGIFY (AGE) " today!"; + * ]| + * + * is transformed by the preprocessor into (code equivalent to): + * + * |[ + * const gchar *greeting = "27 today!"; + * ]| + */ + +/** + * G_PASTE: + * @identifier1: an identifier + * @identifier2: an identifier + * + * Yields a new preprocessor pasted identifier + * @identifier1identifier2 from its expanded + * arguments @identifier1 and @identifier2. For example, + * the following code: + * |[ + * #define GET(traveller,method) G_PASTE(traveller_get_, method) (traveller) + * const gchar *name = GET (traveller, name); + * const gchar *quest = GET (traveller, quest); + * GdkColor *favourite = GET (traveller, favourite_colour); + * ]| + * + * is transformed by the preprocessor into: + * |[ + * const gchar *name = traveller_get_name (traveller); + * const gchar *quest = traveller_get_quest (traveller); + * GdkColor *favourite = traveller_get_favourite_colour (traveller); + * ]| + * + * Since: 2.20 + */ + +/** + * G_STATIC_ASSERT: + * @expr: a constant expression + * + * The G_STATIC_ASSERT() macro lets the programmer check + * a condition at compile time, the condition needs to + * be compile time computable. The macro can be used in + * any place where a typedef is valid. + * + * A typedef is generally allowed in exactly the same places that + * a variable declaration is allowed. For this reason, you should + * not use G_STATIC_ASSERT() in the middle of blocks of code. + * + * The macro should only be used once per source code line. + * + * Since: 2.20 + */ + +/** + * G_STATIC_ASSERT_EXPR: + * @expr: a constant expression + * + * The G_STATIC_ASSERT_EXPR() macro lets the programmer check + * a condition at compile time. The condition needs to be + * compile time computable. + * + * Unlike G_STATIC_ASSERT(), this macro evaluates to an expression + * and, as such, can be used in the middle of other expressions. + * Its value should be ignored. This can be accomplished by placing + * it as the first argument of a comma expression. + * + * |[ + * #define ADD_ONE_TO_INT(x) \ + * (G_STATIC_ASSERT_EXPR(sizeof (x) == sizeof (int)), ((x) + 1)) + * ]| + * + * Since: 2.30 + */ + +/** + * G_GNUC_EXTENSION: + * + * Expands to __extension__ when gcc is used as the compiler. This simply + * tells gcc not to warn about the following non-standard code when compiling + * with the `-pedantic` option. + */ + +/** + * G_GNUC_CHECK_VERSION: + * @major: major version to check against + * @minor: minor version to check against + * + * Expands to a check for a compiler with __GNUC__ defined and a version + * greater than or equal to the major and minor numbers provided. For example, + * the following would only match on compilers such as GCC 4.8 or newer. + * + * |[ + * #if G_GNUC_CHECK_VERSION(4, 8) + * #endif + * ]| + * + * Since: 2.42 + */ + +/** + * G_GNUC_BEGIN_IGNORE_DEPRECATIONS: + * + * Tells gcc (if it is a new enough version) to temporarily stop emitting + * warnings when functions marked with %G_GNUC_DEPRECATED or + * %G_GNUC_DEPRECATED_FOR are called. This is useful for when you have + * one deprecated function calling another one, or when you still have + * regression tests for deprecated functions. + * + * Use %G_GNUC_END_IGNORE_DEPRECATIONS to begin warning again. (If you + * are not compiling with `-Wdeprecated-declarations` then neither macro + * has any effect.) + * + * This macro can be used either inside or outside of a function body, + * but must appear on a line by itself. Both this macro and the corresponding + * %G_GNUC_END_IGNORE_DEPRECATIONS are considered statements, so they + * should not be used around branching or loop conditions; for instance, + * this use is invalid: + * + * |[ + * G_GNUC_BEGIN_IGNORE_DEPRECATIONS + * if (check == some_deprecated_function ()) + * G_GNUC_END_IGNORE_DEPRECATIONS + * { + * do_something (); + * } + * ]| + * + * and you should move the deprecated section outside the condition + * + * |[ + * + * // Solution A + * some_data_t *res; + * + * G_GNUC_BEGIN_IGNORE_DEPRECATIONS + * res = some_deprecated_function (); + * G_GNUC_END_IGNORE_DEPRECATIONS + * + * if (check == res) + * { + * do_something (); + * } + * + * // Solution B + * G_GNUC_BEGIN_IGNORE_DEPRECATIONS + * if (check == some_deprecated_function ()) + * { + * do_something (); + * } + * G_GNUC_END_IGNORE_DEPRECATIONS + * ]| + * + * |[ + * G_GNUC_INTERNAL + * void _g_log_fallback_handler (const gchar *log_domain, + * GLogLevelFlags log_level, + * const gchar *message, + * gpointer unused_data); + * ]| + * + * Since: 2.6 + */ + +/** + * G_LIKELY: + * @expr: the expression + * + * Hints the compiler that the expression is likely to evaluate to + * a true value. The compiler may use this information for optimizations. + * + * |[ + * if (G_LIKELY (random () != 1)) + * g_print ("not one"); + * ]| + * + * Returns: the value of @expr + * + * Since: 2.2 + */ + +/** + * G_UNLIKELY: + * @expr: the expression + * + * Hints the compiler that the expression is unlikely to evaluate to + * a true value. The compiler may use this information for optimizations. + * + * |[ + * if (G_UNLIKELY (random () == 1)) + * g_print ("a random one"); + * ]| + * + * Returns: the value of @expr + * + * Since: 2.2 + */ + +/** + * G_STRLOC: + * + * Expands to a string identifying the current code position. + */ + +/** + * G_STRFUNC: + * + * Expands to a string identifying the current function. + * + * Since: 2.4 + */ + +/** + * G_HAVE_GNUC_VISIBILITY: + * + * Defined to 1 if gcc-style visibility handling is supported. + */ + +/* g_auto(), g_autoptr() and helpers {{{1 */ + +/** + * g_auto: + * @TypeName: a supported variable type + * + * Helper to declare a variable with automatic cleanup. + * + * The variable is cleaned up in a way appropriate to its type when the + * variable goes out of scope. The type must support this. + * The way to clean up the type must have been defined using one of the macros + * G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC() or G_DEFINE_AUTO_CLEANUP_FREE_FUNC(). + * + * This feature is only supported on GCC and clang. This macro is not + * defined on other compilers and should not be used in programs that + * are intended to be portable to those compilers. + * + * This is meant to be used with stack-allocated structures and + * non-pointer types. For the (more commonly used) pointer version, see + * g_autoptr(). + * + * This macro can be used to avoid having to do explicit cleanups of + * local variables when exiting functions. It often vastly simplifies + * handling of error conditions, removing the need for various tricks + * such as `goto out` or repeating of cleanup code. It is also helpful + * for non-error cases. + * + * Consider the following example: + * + * |[ + * GVariant * + * my_func(void) + * { + * g_auto(GQueue) queue = G_QUEUE_INIT; + * g_auto(GVariantBuilder) builder; + * g_auto(GStrv) strv; + * + * g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + * strv = g_strsplit("a:b:c", ":", -1); + * + * ... + * + * if (error_condition) + * return NULL; + * + * ... + * + * return g_variant_builder_end (&builder); + * } + * ]| + * + * You must initialize the variable in some way — either by use of an + * initialiser or by ensuring that an `_init` function will be called on + * it unconditionally before it goes out of scope. + * + * Since: 2.44 + */ + +/** + * g_autoptr: + * @TypeName: a supported variable type + * + * Helper to declare a pointer variable with automatic cleanup. + * + * The variable is cleaned up in a way appropriate to its type when the + * variable goes out of scope. The type must support this. + * The way to clean up the type must have been defined using the macro + * G_DEFINE_AUTOPTR_CLEANUP_FUNC(). + * + * This feature is only supported on GCC and clang. This macro is not + * defined on other compilers and should not be used in programs that + * are intended to be portable to those compilers. + * + * This is meant to be used to declare pointers to types with cleanup + * functions. The type of the variable is a pointer to @TypeName. You + * must not add your own `*`. + * + * This macro can be used to avoid having to do explicit cleanups of + * local variables when exiting functions. It often vastly simplifies + * handling of error conditions, removing the need for various tricks + * such as `goto out` or repeating of cleanup code. It is also helpful + * for non-error cases. + * + * Consider the following example: + * + * |[ + * gboolean + * check_exists(GVariant *dict) + * { + * g_autoptr(GVariant) dirname, basename = NULL; + * g_autofree gchar *path = NULL; + * + * dirname = g_variant_lookup_value (dict, "dirname", G_VARIANT_TYPE_STRING); + * + * if (dirname == NULL) + * return FALSE; + * + * basename = g_variant_lookup_value (dict, "basename", G_VARIANT_TYPE_STRING); + * + * if (basename == NULL) + * return FALSE; + * + * path = g_build_filename (g_variant_get_string (dirname, NULL), + * g_variant_get_string (basename, NULL), + * NULL); + * + * return g_access (path, R_OK) == 0; + * } + * ]| + * + * You must initialise the variable in some way — either by use of an + * initialiser or by ensuring that it is assigned to unconditionally + * before it goes out of scope. + * + * See also g_auto(), g_autofree() and g_steal_pointer(). + * + * Since: 2.44 + */ + +/** + * g_autofree: + * + * Macro to add an attribute to pointer variable to ensure automatic + * cleanup using g_free(). + * + * This macro differs from g_autoptr() in that it is an attribute supplied + * before the type name, rather than wrapping the type definition. Instead + * of using a type-specific lookup, this macro always calls g_free() directly. + * + * This means it's useful for any type that is returned from + * g_malloc(). + * + * Otherwise, this macro has similar constraints as g_autoptr(): only + * supported on GCC and clang, the variable must be initialized, etc. + * + * |[ + * gboolean + * operate_on_malloc_buf (void) + * { + * g_autofree guint8* membuf = NULL; + * + * membuf = g_malloc (8192); + * + * // Some computation on membuf + * + * // membuf will be automatically freed here + * return TRUE; + * } + * ]| + * + * Since: 2.44 + */ + +/** + * g_autolist: + * @TypeName: a supported variable type + * + * Helper to declare a list variable with automatic deep cleanup. + * + * The list is deeply freed, in a way appropriate to the specified type, when the + * variable goes out of scope. The type must support this. + * + * This feature is only supported on GCC and clang. This macro is not + * defined on other compilers and should not be used in programs that + * are intended to be portable to those compilers. + * + * This is meant to be used to declare lists of a type with a cleanup + * function. The type of the variable is a `GList *`. You + * must not add your own `*`. + * + * This macro can be used to avoid having to do explicit cleanups of + * local variables when exiting functions. It often vastly simplifies + * handling of error conditions, removing the need for various tricks + * such as `goto out` or repeating of cleanup code. It is also helpful + * for non-error cases. + * + * See also g_autoslist(), g_autoptr() and g_steal_pointer(). + * + * Since: 2.56 + */ + +/** + * g_autoslist: + * @TypeName: a supported variable type + * + * Helper to declare a singly linked list variable with automatic deep cleanup. + * + * The list is deeply freed, in a way appropriate to the specified type, when the + * variable goes out of scope. The type must support this. + * + * This feature is only supported on GCC and clang. This macro is not + * defined on other compilers and should not be used in programs that + * are intended to be portable to those compilers. + * + * This is meant to be used to declare lists of a type with a cleanup + * function. The type of the variable is a `GSList *`. You + * must not add your own `*`. + * + * This macro can be used to avoid having to do explicit cleanups of + * local variables when exiting functions. It often vastly simplifies + * handling of error conditions, removing the need for various tricks + * such as `goto out` or repeating of cleanup code. It is also helpful + * for non-error cases. + * + * See also g_autolist(), g_autoptr() and g_steal_pointer(). + * + * Since: 2.56 + */ + +/** + * g_autoqueue: + * @TypeName: a supported variable type + * + * Helper to declare a double-ended queue variable with automatic deep cleanup. + * + * The queue is deeply freed, in a way appropriate to the specified type, when the + * variable goes out of scope. The type must support this. + * + * This feature is only supported on GCC and clang. This macro is not + * defined on other compilers and should not be used in programs that + * are intended to be portable to those compilers. + * + * This is meant to be used to declare queues of a type with a cleanup + * function. The type of the variable is a `GQueue *`. You + * must not add your own `*`. + * + * This macro can be used to avoid having to do explicit cleanups of + * local variables when exiting functions. It often vastly simplifies + * handling of error conditions, removing the need for various tricks + * such as `goto out` or repeating of cleanup code. It is also helpful + * for non-error cases. + * + * See also g_autolist(), g_autoptr() and g_steal_pointer(). + * + * Since: 2.62 + */ + + +/** + * G_DEFINE_AUTOPTR_CLEANUP_FUNC: + * @TypeName: a type name to define a g_autoptr() cleanup function for + * @func: the cleanup function + * + * Defines the appropriate cleanup function for a pointer type. + * + * The function will not be called if the variable to be cleaned up + * contains %NULL. + * + * This will typically be the `_free()` or `_unref()` function for the given + * type. + * + * With this definition, it will be possible to use g_autoptr() with + * @TypeName. + * + * |[ + * G_DEFINE_AUTOPTR_CLEANUP_FUNC(GObject, g_object_unref) + * ]| + * + * This macro should be used unconditionally; it is a no-op on compilers + * where cleanup is not supported. + * + * Since: 2.44 + */ + +/** + * G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC: + * @TypeName: a type name to define a g_auto() cleanup function for + * @func: the clear function + * + * Defines the appropriate cleanup function for a type. + * + * This will typically be the `_clear()` function for the given type. + * + * With this definition, it will be possible to use g_auto() with + * @TypeName. + * + * |[ + * G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GQueue, g_queue_clear) + * ]| + * + * This macro should be used unconditionally; it is a no-op on compilers + * where cleanup is not supported. + * + * Since: 2.44 + */ + +/** + * G_DEFINE_AUTO_CLEANUP_FREE_FUNC: + * @TypeName: a type name to define a g_auto() cleanup function for + * @func: the free function + * @none: the "none" value for the type + * + * Defines the appropriate cleanup function for a type. + * + * With this definition, it will be possible to use g_auto() with + * @TypeName. + * + * This function will be rarely used. It is used with pointer-based + * typedefs and non-pointer types where the value of the variable + * represents a resource that must be freed. Two examples are #GStrv + * and file descriptors. + * + * @none specifies the "none" value for the type in question. It is + * probably something like %NULL or `-1`. If the variable is found to + * contain this value then the free function will not be called. + * + * |[ + * G_DEFINE_AUTO_CLEANUP_FREE_FUNC(GStrv, g_strfreev, NULL) + * ]| + * + * This macro should be used unconditionally; it is a no-op on compilers + * where cleanup is not supported. + * + * Since: 2.44 + */ + +/* Warnings and Assertions {{{1 */ + +/** + * SECTION:warnings + * @title: Warnings and Assertions + * @short_description: warnings and assertions to use in runtime code + * + * GLib defines several warning functions and assertions which can be used to + * warn of programmer errors when calling functions, and print error messages + * from command line programs. + * + * The g_return_if_fail(), g_return_val_if_fail(), g_return_if_reached() and + * g_return_val_if_reached() macros are intended as pre-condition assertions, to + * be used at the top of a public function to check that the function’s + * arguments are acceptable. Any failure of such a pre-condition assertion is + * considered a programming error on the part of the caller of the public API, + * and the program is considered to be in an undefined state afterwards. They + * are similar to the libc assert() function, but provide more context on + * failures. + * + * For example: + * |[ + * gboolean + * g_dtls_connection_shutdown (GDtlsConnection *conn, + * gboolean shutdown_read, + * gboolean shutdown_write, + * GCancellable *cancellable, + * GError **error) + * { + * // local variable declarations + * + * g_return_val_if_fail (G_IS_DTLS_CONNECTION (conn), FALSE); + * g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + * g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + * + * // function body + * + * return return_val; + * } + * ]| + * + * g_print(), g_printerr() and g_set_print_handler() are intended to be used for + * output from command line applications, since they output to standard output + * and standard error by default — whereas functions like g_message() and + * g_log() may be redirected to special purpose message windows, files, or the + * system journal. + */ + +/* Windows Compatibility Functions {{{1 */ + +/** + * SECTION:windows + * @title: Windows Compatibility Functions + * @short_description: UNIX emulation on Windows + * + * These functions provide some level of UNIX emulation on the + * Windows platform. If your application really needs the POSIX + * APIs, we suggest you try the Cygwin project. + */ + +/** + * MAXPATHLEN: + * + * Provided for UNIX emulation on Windows; equivalent to UNIX + * macro %MAXPATHLEN, which is the maximum length of a filename + * (including full path). + */ + +/** + * G_WIN32_DLLMAIN_FOR_DLL_NAME: + * @static: empty or "static" + * @dll_name: the name of the (pointer to the) char array where + * the DLL name will be stored. If this is used, you must also + * include `windows.h`. If you need a more complex DLL entry + * point function, you cannot use this + * + * On Windows, this macro defines a DllMain() function that stores + * the actual DLL name that the code being compiled will be included in. + * + * On non-Windows platforms, expands to nothing. + */ + +/** + * G_WIN32_HAVE_WIDECHAR_API: + * + * On Windows, this macro defines an expression which evaluates to + * %TRUE if the code is running on a version of Windows where the wide + * character versions of the Win32 API functions, and the wide character + * versions of the C library functions work. (They are always present in + * the DLLs, but don't work on Windows 9x and Me.) + * + * On non-Windows platforms, it is not defined. + * + * Since: 2.6 + */ + + +/** + * G_WIN32_IS_NT_BASED: + * + * On Windows, this macro defines an expression which evaluates to + * %TRUE if the code is running on an NT-based Windows operating system. + * + * On non-Windows platforms, it is not defined. + * + * Since: 2.6 + */ + + /* Epilogue {{{1 */ +/* vim: set foldmethod=marker: */ diff --git a/glib/galloca.h b/glib/galloca.h new file mode 100644 index 0000000..86f0d76 --- /dev/null +++ b/glib/galloca.h @@ -0,0 +1,145 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_ALLOCA_H__ +#define __G_ALLOCA_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#if defined(__BIONIC__) && defined (GLIB_HAVE_ALLOCA_H) +# include +#elif defined(__GNUC__) +/* GCC does the right thing */ +# undef alloca +# define alloca(size) __builtin_alloca (size) +#elif defined (GLIB_HAVE_ALLOCA_H) +/* a native and working alloca.h is there */ +# include +#else /* !__GNUC__ && !GLIB_HAVE_ALLOCA_H */ +# if defined(_MSC_VER) || defined(__DMC__) +# include +# define alloca _alloca +# else /* !_MSC_VER && !__DMC__ */ +# ifdef _AIX +# pragma alloca +# else /* !_AIX */ +# ifndef alloca /* predefined by HP cc +Olibcalls */ +G_BEGIN_DECLS +char *alloca (); +G_END_DECLS +# endif /* !alloca */ +# endif /* !_AIX */ +# endif /* !_MSC_VER && !__DMC__ */ +#endif /* !__GNUC__ && !GLIB_HAVE_ALLOCA_H */ + +/** + * g_alloca: + * @size: number of bytes to allocate. + * + * Allocates @size bytes on the stack; these bytes will be freed when the current + * stack frame is cleaned up. This macro essentially just wraps the alloca() + * function present on most UNIX variants. + * Thus it provides the same advantages and pitfalls as alloca(): + * + * - alloca() is very fast, as on most systems it's implemented by just adjusting + * the stack pointer register. + * + * - It doesn't cause any memory fragmentation, within its scope, separate alloca() + * blocks just build up and are released together at function end. + * + * - Allocation sizes have to fit into the current stack frame. For instance in a + * threaded environment on Linux, the per-thread stack size is limited to 2 Megabytes, + * so be sparse with alloca() uses. + * + * - Allocation failure due to insufficient stack space is not indicated with a %NULL + * return like e.g. with malloc(). Instead, most systems probably handle it the same + * way as out of stack space situations from infinite function recursion, i.e. + * with a segmentation fault. + * + * - Allowing @size to be specified by an untrusted party would allow for them + * to trigger a segmentation fault by specifying a large size, leading to a + * denial of service vulnerability. @size must always be entirely under the + * control of the program. + * + * - Special care has to be taken when mixing alloca() with GNU C variable sized arrays. + * Stack space allocated with alloca() in the same scope as a variable sized array + * will be freed together with the variable sized array upon exit of that scope, and + * not upon exit of the enclosing function scope. + * + * Returns: space for @size bytes, allocated on the stack + */ +#define g_alloca(size) alloca (size) + +/** + * g_alloca0: + * @size: number of bytes to allocate. + * + * Wraps g_alloca() and initializes allocated memory to zeroes. + * If @size is `0` it returns %NULL. + * + * Note that the @size argument will be evaluated multiple times. + * + * Returns: (nullable) (transfer full): space for @size bytes, allocated on the stack + * + * Since: 2.72 + */ +#define g_alloca0(size) ((size) == 0 ? NULL : memset (g_alloca (size), 0, (size))) + +/** + * g_newa: + * @struct_type: Type of memory chunks to be allocated + * @n_structs: Number of chunks to be allocated + * + * Wraps g_alloca() in a more typesafe manner. + * + * As mentioned in the documentation for g_alloca(), @n_structs must always be + * entirely under the control of the program, or you may introduce a denial of + * service vulnerability. In addition, the multiplication of @struct_type by + * @n_structs is not checked, so an overflow may lead to a remote code execution + * vulnerability. + * + * Returns: Pointer to stack space for @n_structs chunks of type @struct_type + */ +#define g_newa(struct_type, n_structs) ((struct_type*) g_alloca (sizeof (struct_type) * (gsize) (n_structs))) + +/** + * g_newa0: + * @struct_type: the type of the elements to allocate. + * @n_structs: the number of elements to allocate. + * + * Wraps g_alloca0() in a more typesafe manner. + * + * Returns: (nullable) (transfer full): Pointer to stack space for @n_structs + * chunks of type @struct_type + * + * Since: 2.72 + */ +#define g_newa0(struct_type, n_structs) ((struct_type*) g_alloca0 (sizeof (struct_type) * (gsize) (n_structs))) + +#endif /* __G_ALLOCA_H__ */ diff --git a/glib/garcbox.c b/glib/garcbox.c new file mode 100644 index 0000000..70aebb6 --- /dev/null +++ b/glib/garcbox.c @@ -0,0 +1,381 @@ +/* garcbox.c: Atomically reference counted data + * + * Copyright 2018 Emmanuele Bassi + * + * 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 "grcboxprivate.h" + +#include "gmessages.h" +#include "grefcount.h" + +#ifdef ENABLE_VALGRIND +#include "valgrind.h" +#endif + +#include "glib_trace.h" + +#include + +#define G_ARC_BOX(p) (GArcBox *) (((char *) (p)) - G_ARC_BOX_SIZE) + +/** + * SECTION:arcbox + * @Title: Atomically reference counted data + * @Short_description: Allocated memory with atomic reference counting semantics + * + * An "atomically reference counted box", or "ArcBox", is an opaque wrapper + * data type that is guaranteed to be as big as the size of a given data type, + * and which augments the given data type with thread safe reference counting + * semantics for its memory management. + * + * ArcBox is useful if you have a plain old data type, like a structure + * typically placed on the stack, and you wish to provide additional API + * to use it on the heap; or if you want to implement a new type to be + * passed around by reference without necessarily implementing copy/free + * semantics or your own reference counting. + * + * The typical use is: + * + * |[ + * typedef struct { + * char *name; + * char *address; + * char *city; + * char *state; + * int age; + * } Person; + * + * Person * + * person_new (void) + * { + * return g_atomic_rc_box_new0 (Person); + * } + * ]| + * + * Every time you wish to acquire a reference on the memory, you should + * call g_atomic_rc_box_acquire(); similarly, when you wish to release a reference + * you should call g_atomic_rc_box_release(): + * + * |[ + * // Add a Person to the Database; the Database acquires ownership + * // of the Person instance + * void + * add_person_to_database (Database *db, Person *p) + * { + * db->persons = g_list_prepend (db->persons, g_atomic_rc_box_acquire (p)); + * } + * + * // Removes a Person from the Database; the reference acquired by + * // add_person_to_database() is released here + * void + * remove_person_from_database (Database *db, Person *p) + * { + * db->persons = g_list_remove (db->persons, p); + * g_atomic_rc_box_release (p); + * } + * ]| + * + * If you have additional memory allocated inside the structure, you can + * use g_atomic_rc_box_release_full(), which takes a function pointer, which + * will be called if the reference released was the last: + * + * |[ + * void + * person_clear (Person *p) + * { + * g_free (p->name); + * g_free (p->address); + * g_free (p->city); + * g_free (p->state); + * } + * + * void + * remove_person_from_database (Database *db, Person *p) + * { + * db->persons = g_list_remove (db->persons, p); + * g_atomic_rc_box_release_full (p, (GDestroyNotify) person_clear); + * } + * ]| + * + * If you wish to transfer the ownership of a reference counted data + * type without increasing the reference count, you can use g_steal_pointer(): + * + * |[ + * Person *p = g_atomic_rc_box_new (Person); + * + * fill_person_details (p); + * + * add_person_to_database (db, g_steal_pointer (&p)); + * ]| + * + * ## Thread safety + * + * The reference counting operations on data allocated using g_atomic_rc_box_alloc(), + * g_atomic_rc_box_new(), and g_atomic_rc_box_dup() are guaranteed to be atomic, and thus + * can be safely be performed by different threads. It is important to note that + * only the reference acquisition and release are atomic; changes to the content + * of the data are your responsibility. + * + * ## Automatic pointer clean up + * + * If you want to add g_autoptr() support to your plain old data type through + * reference counting, you can use the G_DEFINE_AUTOPTR_CLEANUP_FUNC() and + * g_atomic_rc_box_release(): + * + * |[ + * G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyDataStruct, g_atomic_rc_box_release) + * ]| + * + * If you need to clear the contents of the data, you will need to use an + * ancillary function that calls g_rc_box_release_full(): + * + * |[ + * static void + * my_data_struct_release (MyDataStruct *data) + * { + * // my_data_struct_clear() is defined elsewhere + * g_atomic_rc_box_release_full (data, (GDestroyNotify) my_data_struct_clear); + * } + * + * G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyDataStruct, my_data_struct_release) + * ]| + * + * Since: 2.58 + */ + +/** + * g_atomic_rc_box_alloc: + * @block_size: the size of the allocation, must be greater than 0 + * + * Allocates @block_size bytes of memory, and adds atomic + * reference counting semantics to it. + * + * The data will be freed when its reference count drops to + * zero. + * + * The allocated data is guaranteed to be suitably aligned for any + * built-in type. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +g_atomic_rc_box_alloc (gsize block_size) +{ + g_return_val_if_fail (block_size > 0, NULL); + + return g_rc_box_alloc_full (block_size, STRUCT_ALIGNMENT, TRUE, FALSE); +} + +/** + * g_atomic_rc_box_alloc0: + * @block_size: the size of the allocation, must be greater than 0 + * + * Allocates @block_size bytes of memory, and adds atomic + * reference counting semantics to it. + * + * The contents of the returned data is set to zero. + * + * The data will be freed when its reference count drops to + * zero. + * + * The allocated data is guaranteed to be suitably aligned for any + * built-in type. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +g_atomic_rc_box_alloc0 (gsize block_size) +{ + g_return_val_if_fail (block_size > 0, NULL); + + return g_rc_box_alloc_full (block_size, STRUCT_ALIGNMENT, TRUE, TRUE); +} + +/** + * g_atomic_rc_box_new: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate atomically reference counted + * data with the size of the given @type. + * + * This macro calls g_atomic_rc_box_alloc() with `sizeof (@type)` and + * casts the returned pointer to a pointer of the given @type, + * avoiding a type cast in the source code. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated + * memory, cast to a pointer for the given @type + * + * Since: 2.58 + */ + +/** + * g_atomic_rc_box_new0: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate atomically reference counted + * data with the size of the given @type, and set its contents + * to zero. + * + * This macro calls g_atomic_rc_box_alloc0() with `sizeof (@type)` and + * casts the returned pointer to a pointer of the given @type, + * avoiding a type cast in the source code. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated + * memory, cast to a pointer for the given @type + * + * Since: 2.58 + */ + +/** + * g_atomic_rc_box_dup: + * @block_size: the number of bytes to copy, must be greater than 0 + * @mem_block: (not nullable): the memory to copy + * + * Allocates a new block of data with atomic reference counting + * semantics, and copies @block_size bytes of @mem_block + * into it. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated + * memory + * + * Since: 2.58 + */ +gpointer +(g_atomic_rc_box_dup) (gsize block_size, + gconstpointer mem_block) +{ + gpointer res; + + g_return_val_if_fail (block_size > 0, NULL); + g_return_val_if_fail (mem_block != NULL, NULL); + + res = g_rc_box_alloc_full (block_size, STRUCT_ALIGNMENT, TRUE, FALSE); + memcpy (res, mem_block, block_size); + + return res; +} + +/** + * g_atomic_rc_box_acquire: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Atomically acquires a reference on the data pointed by @mem_block. + * + * Returns: (transfer full) (not nullable): a pointer to the data, + * with its reference count increased + * + * Since: 2.58 + */ +gpointer +(g_atomic_rc_box_acquire) (gpointer mem_block) +{ + GArcBox *real_box = G_ARC_BOX (mem_block); + + g_return_val_if_fail (mem_block != NULL, NULL); +#ifndef G_DISABLE_ASSERT + g_return_val_if_fail (real_box->magic == G_BOX_MAGIC, NULL); +#endif + + g_atomic_ref_count_inc (&real_box->ref_count); + + TRACE (GLIB_RCBOX_ACQUIRE (mem_block, 1)); + + return mem_block; +} + +/** + * g_atomic_rc_box_release: + * @mem_block: (transfer full) (not nullable): a pointer to reference counted data + * + * Atomically releases a reference on the data pointed by @mem_block. + * + * If the reference was the last one, it will free the + * resources allocated for @mem_block. + * + * Since: 2.58 + */ +void +g_atomic_rc_box_release (gpointer mem_block) +{ + g_atomic_rc_box_release_full (mem_block, NULL); +} + +/** + * g_atomic_rc_box_release_full: + * @mem_block: (transfer full) (not nullable): a pointer to reference counted data + * @clear_func: (not nullable): a function to call when clearing the data + * + * Atomically releases a reference on the data pointed by @mem_block. + * + * If the reference was the last one, it will call @clear_func + * to clear the contents of @mem_block, and then will free the + * resources allocated for @mem_block. + * + * Since: 2.58 + */ +void +g_atomic_rc_box_release_full (gpointer mem_block, + GDestroyNotify clear_func) +{ + GArcBox *real_box = G_ARC_BOX (mem_block); + + g_return_if_fail (mem_block != NULL); +#ifndef G_DISABLE_ASSERT + g_return_if_fail (real_box->magic == G_BOX_MAGIC); +#endif + + if (g_atomic_ref_count_dec (&real_box->ref_count)) + { + char *real_mem = (char *) real_box - real_box->private_offset; + + TRACE (GLIB_RCBOX_RELEASE (mem_block, 1)); + + if (clear_func != NULL) + clear_func (mem_block); + + TRACE (GLIB_RCBOX_FREE (mem_block)); + g_free (real_mem); + } +} + +/** + * g_atomic_rc_box_get_size: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Retrieves the size of the reference counted data pointed by @mem_block. + * + * Returns: the size of the data, in bytes + * + * Since: 2.58 + */ +gsize +g_atomic_rc_box_get_size (gpointer mem_block) +{ + GArcBox *real_box = G_ARC_BOX (mem_block); + + g_return_val_if_fail (mem_block != NULL, 0); +#ifndef G_DISABLE_ASSERT + g_return_val_if_fail (real_box->magic == G_BOX_MAGIC, 0); +#endif + + return real_box->mem_size; +} diff --git a/glib/garray.c b/glib/garray.c new file mode 100644 index 0000000..1ab3bee --- /dev/null +++ b/glib/garray.c @@ -0,0 +1,2575 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * MT safe + */ + +#include "config.h" + +#include +#include + +#include "garray.h" + +#include "gbytes.h" +#include "ghash.h" +#include "gslice.h" +#include "gmem.h" +#include "gtestutils.h" +#include "gthread.h" +#include "gmessages.h" +#include "gqsort.h" +#include "grefcount.h" +#include "gutilsprivate.h" + +/** + * SECTION:arrays + * @title: Arrays + * @short_description: arrays of arbitrary elements which grow + * automatically as elements are added + * + * Arrays are similar to standard C arrays, except that they grow + * automatically as elements are added. + * + * Array elements can be of any size (though all elements of one array + * are the same size), and the array can be automatically cleared to + * '0's and zero-terminated. + * + * To create a new array use g_array_new(). + * + * To add elements to an array with a cost of O(n) at worst, use + * g_array_append_val(), g_array_append_vals(), g_array_prepend_val(), + * g_array_prepend_vals(), g_array_insert_val() and g_array_insert_vals(). + * + * To access an element of an array in O(1) (to read it or to write it), + * use g_array_index(). + * + * To set the size of an array, use g_array_set_size(). + * + * To free an array, use g_array_unref() or g_array_free(). + * + * All the sort functions are internally calling a quick-sort (or similar) + * function with an average cost of O(n log(n)) and a worst case + * cost of O(n^2). + * + * Here is an example that stores integers in a #GArray: + * |[ + * GArray *garray; + * gint i; + * // We create a new array to store gint values. + * // We don't want it zero-terminated or cleared to 0's. + * garray = g_array_new (FALSE, FALSE, sizeof (gint)); + * for (i = 0; i < 10000; i++) + * g_array_append_val (garray, i); + * for (i = 0; i < 10000; i++) + * if (g_array_index (garray, gint, i) != i) + * g_print ("ERROR: got %d instead of %d\n", + * g_array_index (garray, gint, i), i); + * g_array_free (garray, TRUE); + * ]| + */ + +#define MIN_ARRAY_SIZE 16 + +typedef struct _GRealArray GRealArray; + +/** + * GArray: + * @data: a pointer to the element data. The data may be moved as + * elements are added to the #GArray. + * @len: the number of elements in the #GArray not including the + * possible terminating zero element. + * + * Contains the public fields of a GArray. + */ +struct _GRealArray +{ + guint8 *data; + guint len; + guint elt_capacity; + guint elt_size; + guint zero_terminated : 1; + guint clear : 1; + gatomicrefcount ref_count; + GDestroyNotify clear_func; +}; + +/** + * g_array_index: + * @a: a #GArray + * @t: the type of the elements + * @i: the index of the element to return + * + * Returns the element of a #GArray at the given index. The return + * value is cast to the given type. This is the main way to read or write an + * element in a #GArray. + * + * Writing an element is typically done by reference, as in the following + * example. This example gets a pointer to an element in a #GArray, and then + * writes to a field in it: + * |[ + * EDayViewEvent *event; + * // This gets a pointer to the 4th element in the array of + * // EDayViewEvent structs. + * event = &g_array_index (events, EDayViewEvent, 3); + * event->start_time = g_get_current_time (); + * ]| + * + * This example reads from and writes to an array of integers: + * |[ + * g_autoptr(GArray) int_array = g_array_new (FALSE, FALSE, sizeof (guint)); + * for (guint i = 0; i < 10; i++) + * g_array_append_val (int_array, i); + * + * guint *my_int = &g_array_index (int_array, guint, 1); + * g_print ("Int at index 1 is %u; decrementing it\n", *my_int); + * *my_int = *my_int - 1; + * ]| + * + * Returns: the element of the #GArray at the index given by @i + */ + +#define g_array_elt_len(array,i) ((gsize)(array)->elt_size * (i)) +#define g_array_elt_pos(array,i) ((array)->data + g_array_elt_len((array),(i))) +#define g_array_elt_zero(array, pos, len) \ + (memset (g_array_elt_pos ((array), pos), 0, g_array_elt_len ((array), len))) +#define g_array_zero_terminate(array) G_STMT_START{ \ + if ((array)->zero_terminated) \ + g_array_elt_zero ((array), (array)->len, 1); \ +}G_STMT_END + +static void g_array_maybe_expand (GRealArray *array, + guint len); + +/** + * g_array_new: + * @zero_terminated: %TRUE if the array should have an extra element at + * the end which is set to 0 + * @clear_: %TRUE if #GArray elements should be automatically cleared + * to 0 when they are allocated + * @element_size: the size of each element in bytes + * + * Creates a new #GArray with a reference count of 1. + * + * Returns: the new #GArray + */ +GArray* +g_array_new (gboolean zero_terminated, + gboolean clear, + guint elt_size) +{ + g_return_val_if_fail (elt_size > 0, NULL); +#if (UINT_WIDTH / 8) >= GLIB_SIZEOF_SIZE_T + g_return_val_if_fail (elt_size <= G_MAXSIZE / 2 - 1, NULL); +#endif + + return g_array_sized_new (zero_terminated, clear, elt_size, 0); +} + +/** + * g_array_steal: + * @array: a #GArray. + * @len: (optional) (out): 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->elt_capacity = 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 + * @clear_: %TRUE if all bits in the array should be cleared to 0 on + * allocation + * @element_size: size of each element in the array + * @reserved_size: number of elements preallocated + * + * Creates a new #GArray with @reserved_size elements preallocated and + * a reference count of 1. This avoids frequent reallocation, if you + * are going to add many elements to the array. Note however that the + * size of the array is still 0. + * + * Returns: the new #GArray + */ +GArray* +g_array_sized_new (gboolean zero_terminated, + gboolean clear, + guint elt_size, + guint reserved_size) +{ + GRealArray *array; + + g_return_val_if_fail (elt_size > 0, NULL); +#if (UINT_WIDTH / 8) >= GLIB_SIZEOF_SIZE_T + g_return_val_if_fail (elt_size <= G_MAXSIZE / 2 - 1, NULL); +#endif + + array = g_slice_new (GRealArray); + + array->data = NULL; + array->len = 0; + array->elt_capacity = 0; + array->zero_terminated = (zero_terminated ? 1 : 0); + array->clear = (clear ? 1 : 0); + array->elt_size = elt_size; + array->clear_func = NULL; + + g_atomic_ref_count_init (&array->ref_count); + + if (array->zero_terminated || reserved_size != 0) + { + g_array_maybe_expand (array, reserved_size); + g_array_zero_terminate(array); + } + + return (GArray*) array; +} + +/** + * g_array_set_clear_func: + * @array: A #GArray + * @clear_func: a function to clear an element of @array + * + * Sets a function to clear an element of @array. + * + * The @clear_func will be called when an element in the array + * data segment is removed and when the array is freed and data + * segment is deallocated as well. @clear_func will be passed a + * pointer to the element to clear, rather than the element itself. + * + * Note that in contrast with other uses of #GDestroyNotify + * functions, @clear_func is expected to clear the contents of + * the array element it is given, but not free the element itself. + * + * |[ + * typedef struct + * { + * gchar *str; + * GObject *obj; + * } ArrayElement; + * + * static void + * array_element_clear (ArrayElement *element) + * { + * g_clear_pointer (&element->str, g_free); + * g_clear_object (&element->obj); + * } + * + * // main code + * GArray *garray = g_array_new (FALSE, FALSE, sizeof (ArrayElement)); + * g_array_set_clear_func (garray, (GDestroyNotify) array_element_clear); + * // assign data to the structure + * g_array_free (garray, TRUE); + * ]| + * + * Since: 2.32 + */ +void +g_array_set_clear_func (GArray *array, + GDestroyNotify clear_func) +{ + GRealArray *rarray = (GRealArray *) array; + + g_return_if_fail (array != NULL); + + rarray->clear_func = clear_func; +} + +/** + * g_array_ref: + * @array: A #GArray + * + * Atomically increments the reference count of @array by one. + * This function is thread-safe and may be called from any thread. + * + * Returns: The passed in #GArray + * + * Since: 2.22 + */ +GArray * +g_array_ref (GArray *array) +{ + GRealArray *rarray = (GRealArray*) array; + g_return_val_if_fail (array, NULL); + + g_atomic_ref_count_inc (&rarray->ref_count); + + return array; +} + +typedef enum +{ + FREE_SEGMENT = 1 << 0, + PRESERVE_WRAPPER = 1 << 1 +} ArrayFreeFlags; + +static gchar *array_free (GRealArray *, ArrayFreeFlags); + +/** + * g_array_unref: + * @array: A #GArray + * + * Atomically decrements the reference count of @array by one. If the + * reference count drops to 0, all memory allocated by the array is + * released. This function is thread-safe and may be called from any + * thread. + * + * Since: 2.22 + */ +void +g_array_unref (GArray *array) +{ + GRealArray *rarray = (GRealArray*) array; + g_return_if_fail (array); + + if (g_atomic_ref_count_dec (&rarray->ref_count)) + array_free (rarray, FREE_SEGMENT); +} + +/** + * g_array_get_element_size: + * @array: A #GArray + * + * Gets the size of the elements in @array. + * + * Returns: Size of each element, in bytes + * + * Since: 2.22 + */ +guint +g_array_get_element_size (GArray *array) +{ + GRealArray *rarray = (GRealArray*) array; + + g_return_val_if_fail (array, 0); + + return rarray->elt_size; +} + +/** + * g_array_free: + * @array: a #GArray + * @free_segment: if %TRUE the actual element data is freed as well + * + * Frees the memory allocated for the #GArray. If @free_segment is + * %TRUE it frees the memory block holding the elements as well. Pass + * %FALSE if you want to free the #GArray wrapper but preserve the + * underlying array for use elsewhere. If the reference count of + * @array is greater than one, the #GArray wrapper is preserved but + * the size of @array will be set to zero. + * + * If array contents point to dynamically-allocated memory, they should + * be freed separately if @free_seg is %TRUE and no @clear_func + * function has been set for @array. + * + * This function is not thread-safe. If using a #GArray from multiple + * threads, use only the atomic g_array_ref() and g_array_unref() + * functions. + * + * Returns: the element data if @free_segment is %FALSE, otherwise + * %NULL. The element data should be freed using g_free(). + */ +gchar* +g_array_free (GArray *farray, + gboolean free_segment) +{ + GRealArray *array = (GRealArray*) farray; + ArrayFreeFlags flags; + + g_return_val_if_fail (array, NULL); + + flags = (free_segment ? FREE_SEGMENT : 0); + + /* if others are holding a reference, preserve the wrapper but do free/return the data */ + if (!g_atomic_ref_count_dec (&array->ref_count)) + flags |= PRESERVE_WRAPPER; + + return array_free (array, flags); +} + +static gchar * +array_free (GRealArray *array, + ArrayFreeFlags flags) +{ + gchar *segment; + + if (flags & FREE_SEGMENT) + { + if (array->clear_func != NULL) + { + guint i; + + for (i = 0; i < array->len; i++) + array->clear_func (g_array_elt_pos (array, i)); + } + + g_free (array->data); + segment = NULL; + } + else + segment = (gchar*) array->data; + + if (flags & PRESERVE_WRAPPER) + { + array->data = NULL; + array->len = 0; + array->elt_capacity = 0; + } + else + { + g_slice_free1 (sizeof (GRealArray), array); + } + + return segment; +} + +/** + * g_array_append_vals: + * @array: a #GArray + * @data: (not nullable): a pointer to the elements to append to the end of the array + * @len: the number of elements to append + * + * Adds @len elements onto the end of the array. + * + * Returns: the #GArray + */ +/** + * g_array_append_val: + * @a: a #GArray + * @v: the value to append to the #GArray + * + * Adds the value on to the end of the array. The array will grow in + * size automatically if necessary. + * + * g_array_append_val() is a macro which uses a reference to the value + * parameter @v. This means that you cannot use it with literal values + * such as "27". You must use variables. + * + * Returns: the #GArray + */ +GArray* +g_array_append_vals (GArray *farray, + gconstpointer data, + guint len) +{ + GRealArray *array = (GRealArray*) farray; + + g_return_val_if_fail (array, NULL); + + if (len == 0) + return farray; + + g_array_maybe_expand (array, len); + + memcpy (g_array_elt_pos (array, array->len), data, + g_array_elt_len (array, len)); + + array->len += len; + + g_array_zero_terminate (array); + + return farray; +} + +/** + * g_array_prepend_vals: + * @array: a #GArray + * @data: (nullable): a pointer to the elements to prepend to the start of the array + * @len: the number of elements to prepend, which may be zero + * + * Adds @len elements onto the start of the array. + * + * @data may be %NULL if (and only if) @len is zero. If @len is zero, this + * function is a no-op. + * + * This operation is slower than g_array_append_vals() since the + * existing elements in the array have to be moved to make space for + * the new elements. + * + * Returns: the #GArray + */ +/** + * g_array_prepend_val: + * @a: a #GArray + * @v: the value to prepend to the #GArray + * + * Adds the value on to the start of the array. The array will grow in + * size automatically if necessary. + * + * This operation is slower than g_array_append_val() since the + * existing elements in the array have to be moved to make space for + * the new element. + * + * g_array_prepend_val() is a macro which uses a reference to the value + * parameter @v. This means that you cannot use it with literal values + * such as "27". You must use variables. + * + * Returns: the #GArray + */ +GArray* +g_array_prepend_vals (GArray *farray, + gconstpointer data, + guint len) +{ + GRealArray *array = (GRealArray*) farray; + + g_return_val_if_fail (array, NULL); + + if (len == 0) + return farray; + + g_array_maybe_expand (array, len); + + memmove (g_array_elt_pos (array, len), g_array_elt_pos (array, 0), + g_array_elt_len (array, array->len)); + + memcpy (g_array_elt_pos (array, 0), data, g_array_elt_len (array, len)); + + array->len += len; + + g_array_zero_terminate (array); + + return farray; +} + +/** + * g_array_insert_vals: + * @array: a #GArray + * @index_: the index to place the elements at + * @data: (nullable): a pointer to the elements to insert + * @len: the number of elements to insert + * + * Inserts @len elements into a #GArray at the given index. + * + * If @index_ is greater than the array’s current length, the array is expanded. + * The elements between the old end of the array and the newly inserted elements + * will be initialised to zero if the array was configured to clear elements; + * otherwise their values will be undefined. + * + * If @index_ is less than the array’s current length, new entries will be + * inserted into the array, and the existing entries above @index_ will be moved + * upwards. + * + * @data may be %NULL if (and only if) @len is zero. If @len is zero, this + * function is a no-op. + * + * Returns: the #GArray + */ +/** + * g_array_insert_val: + * @a: a #GArray + * @i: the index to place the element at + * @v: the value to insert into the array + * + * Inserts an element into an array at the given index. + * + * g_array_insert_val() is a macro which uses a reference to the value + * parameter @v. This means that you cannot use it with literal values + * such as "27". You must use variables. + * + * Returns: the #GArray + */ +GArray* +g_array_insert_vals (GArray *farray, + guint index_, + gconstpointer data, + guint len) +{ + GRealArray *array = (GRealArray*) farray; + + g_return_val_if_fail (array, NULL); + + if (len == 0) + return farray; + + /* Is the index off the end of the array, and hence do we need to over-allocate + * and clear some elements? */ + if (index_ >= array->len) + { + g_array_maybe_expand (array, index_ - array->len + len); + return g_array_append_vals (g_array_set_size (farray, index_), data, len); + } + + g_array_maybe_expand (array, len); + + memmove (g_array_elt_pos (array, len + index_), + g_array_elt_pos (array, index_), + g_array_elt_len (array, array->len - index_)); + + memcpy (g_array_elt_pos (array, index_), data, g_array_elt_len (array, len)); + + array->len += len; + + g_array_zero_terminate (array); + + return farray; +} + +/** + * g_array_set_size: + * @array: a #GArray + * @length: the new size of the #GArray + * + * Sets the size of the array, expanding it if necessary. If the array + * was created with @clear_ set to %TRUE, the new elements are set to 0. + * + * Returns: the #GArray + */ +GArray* +g_array_set_size (GArray *farray, + guint length) +{ + GRealArray *array = (GRealArray*) farray; + + g_return_val_if_fail (array, NULL); + + if (length > array->len) + { + g_array_maybe_expand (array, length - array->len); + + if (array->clear) + g_array_elt_zero (array, array->len, length - array->len); + } + else if (length < array->len) + g_array_remove_range (farray, length, array->len - length); + + array->len = length; + + g_array_zero_terminate (array); + + return farray; +} + +/** + * g_array_remove_index: + * @array: a #GArray + * @index_: the index of the element to remove + * + * Removes the element at the given index from a #GArray. The following + * elements are moved down one place. + * + * Returns: the #GArray + */ +GArray* +g_array_remove_index (GArray *farray, + guint index_) +{ + GRealArray* array = (GRealArray*) farray; + + g_return_val_if_fail (array, NULL); + + g_return_val_if_fail (index_ < array->len, NULL); + + if (array->clear_func != NULL) + array->clear_func (g_array_elt_pos (array, index_)); + + if (index_ != array->len - 1) + memmove (g_array_elt_pos (array, index_), + g_array_elt_pos (array, index_ + 1), + g_array_elt_len (array, array->len - index_ - 1)); + + array->len -= 1; + + if (G_UNLIKELY (g_mem_gc_friendly)) + g_array_elt_zero (array, array->len, 1); + else + g_array_zero_terminate (array); + + return farray; +} + +/** + * g_array_remove_index_fast: + * @array: a @GArray + * @index_: the index of the element to remove + * + * Removes the element at the given index from a #GArray. The last + * element in the array is used to fill in the space, so this function + * does not preserve the order of the #GArray. But it is faster than + * g_array_remove_index(). + * + * Returns: the #GArray + */ +GArray* +g_array_remove_index_fast (GArray *farray, + guint index_) +{ + GRealArray* array = (GRealArray*) farray; + + g_return_val_if_fail (array, NULL); + + g_return_val_if_fail (index_ < array->len, NULL); + + if (array->clear_func != NULL) + array->clear_func (g_array_elt_pos (array, index_)); + + if (index_ != array->len - 1) + memcpy (g_array_elt_pos (array, index_), + g_array_elt_pos (array, array->len - 1), + g_array_elt_len (array, 1)); + + array->len -= 1; + + if (G_UNLIKELY (g_mem_gc_friendly)) + g_array_elt_zero (array, array->len, 1); + else + g_array_zero_terminate (array); + + return farray; +} + +/** + * g_array_remove_range: + * @array: a @GArray + * @index_: the index of the first element to remove + * @length: the number of elements to remove + * + * Removes the given number of elements starting at the given index + * from a #GArray. The following elements are moved to close the gap. + * + * Returns: the #GArray + * + * Since: 2.4 + */ +GArray* +g_array_remove_range (GArray *farray, + guint index_, + guint length) +{ + GRealArray *array = (GRealArray*) farray; + + g_return_val_if_fail (array, NULL); + g_return_val_if_fail (index_ <= array->len, NULL); + g_return_val_if_fail (index_ + length <= array->len, NULL); + + if (array->clear_func != NULL) + { + guint i; + + for (i = 0; i < length; i++) + array->clear_func (g_array_elt_pos (array, index_ + i)); + } + + if (index_ + length != array->len) + memmove (g_array_elt_pos (array, index_), + g_array_elt_pos (array, index_ + length), + (array->len - (index_ + length)) * array->elt_size); + + array->len -= length; + if (G_UNLIKELY (g_mem_gc_friendly)) + g_array_elt_zero (array, array->len, length); + else + g_array_zero_terminate (array); + + return farray; +} + +/** + * g_array_sort: + * @array: a #GArray + * @compare_func: comparison function + * + * Sorts a #GArray using @compare_func which should be a qsort()-style + * comparison function (returns less than zero for first arg is less + * than second arg, zero for equal, greater zero if first arg is + * greater than second arg). + * + * This is guaranteed to be a stable sort since version 2.32. + */ +void +g_array_sort (GArray *farray, + GCompareFunc compare_func) +{ + GRealArray *array = (GRealArray*) farray; + + g_return_if_fail (array != NULL); + + /* Don't use qsort as we want a guaranteed stable sort */ + if (array->len > 0) + g_qsort_with_data (array->data, + array->len, + array->elt_size, + (GCompareDataFunc)compare_func, + NULL); +} + +/** + * g_array_sort_with_data: + * @array: a #GArray + * @compare_func: comparison function + * @user_data: data to pass to @compare_func + * + * Like g_array_sort(), but the comparison function receives an extra + * user data argument. + * + * This is guaranteed to be a stable sort since version 2.32. + * + * There used to be a comment here about making the sort stable by + * using the addresses of the elements in the comparison function. + * This did not actually work, so any such code should be removed. + */ +void +g_array_sort_with_data (GArray *farray, + GCompareDataFunc compare_func, + gpointer user_data) +{ + GRealArray *array = (GRealArray*) farray; + + g_return_if_fail (array != NULL); + + if (array->len > 0) + g_qsort_with_data (array->data, + array->len, + array->elt_size, + compare_func, + user_data); +} + +/** + * g_array_binary_search: + * @array: a #GArray. + * @target: a pointer to the item to look up. + * @compare_func: A #GCompareFunc used to locate @target. + * @out_match_index: (optional) (out): return location + * for the index of the element, if found. + * + * Checks whether @target exists in @array by performing a binary + * search based on the given comparison function @compare_func which + * get pointers to items as arguments. If the element is found, %TRUE + * is returned and the element’s index is returned in @out_match_index + * (if non-%NULL). Otherwise, %FALSE is returned and @out_match_index + * is undefined. If @target exists multiple times in @array, the index + * of the first instance is returned. This search is using a binary + * search, so the @array must absolutely be sorted to return a correct + * result (if not, the function may produce false-negative). + * + * This example defines a comparison function and search an element in a #GArray: + * |[ + * static gint* + * cmpint (gconstpointer a, gconstpointer b) + * { + * const gint *_a = a; + * const gint *_b = b; + * + * return *_a - *_b; + * } + * ... + * gint i = 424242; + * guint matched_index; + * gboolean result = g_array_binary_search (garray, &i, cmpint, &matched_index); + * ... + * ]| + * + * Returns: %TRUE if @target is one of the elements of @array, %FALSE otherwise. + * + * Since: 2.62 + */ +gboolean +g_array_binary_search (GArray *array, + gconstpointer target, + GCompareFunc compare_func, + guint *out_match_index) +{ + gboolean result = FALSE; + GRealArray *_array = (GRealArray *) array; + guint left, middle = 0, right; + gint val; + + g_return_val_if_fail (_array != NULL, FALSE); + g_return_val_if_fail (compare_func != NULL, FALSE); + + if (G_LIKELY(_array->len)) + { + left = 0; + right = _array->len - 1; + + while (left <= right) + { + middle = left + (right - left) / 2; + + val = compare_func (_array->data + (_array->elt_size * middle), target); + if (val == 0) + { + result = TRUE; + break; + } + else if (val < 0) + left = middle + 1; + else if (/* val > 0 && */ middle > 0) + right = middle - 1; + else + break; /* element not found */ + } + } + + if (result && out_match_index != NULL) + *out_match_index = middle; + + return result; +} + +static void +g_array_maybe_expand (GRealArray *array, + guint len) +{ + guint max_len, want_len; + + /* The maximum array length is derived from following constraints: + * - The number of bytes must fit into a gsize / 2. + * - The number of elements must fit into guint. + * - zero terminated arrays must leave space for the terminating element + */ + max_len = MIN (G_MAXSIZE / 2 / array->elt_size, G_MAXUINT) - array->zero_terminated; + + /* Detect potential overflow */ + if G_UNLIKELY ((max_len - array->len) < len) + g_error ("adding %u to array would overflow", len); + + want_len = array->len + len + array->zero_terminated; + if (want_len > array->elt_capacity) + { + gsize want_alloc = g_nearest_pow (g_array_elt_len (array, want_len)); + want_alloc = MAX (want_alloc, MIN_ARRAY_SIZE); + + array->data = g_realloc (array->data, want_alloc); + + if (G_UNLIKELY (g_mem_gc_friendly)) + memset (g_array_elt_pos (array, array->elt_capacity), 0, + g_array_elt_len (array, want_len - array->elt_capacity)); + + array->elt_capacity = MIN (want_alloc / array->elt_size, G_MAXUINT); + } +} + +/** + * SECTION:arrays_pointer + * @title: Pointer Arrays + * @short_description: arrays of pointers to any type of data, which + * grow automatically as new elements are added + * + * Pointer Arrays are similar to Arrays but are used only for storing + * pointers. + * + * If you remove elements from the array, elements at the end of the + * array are moved into the space previously occupied by the removed + * element. This means that you should not rely on the index of particular + * elements remaining the same. You should also be careful when deleting + * elements while iterating over the array. + * + * To create a pointer array, use g_ptr_array_new(). + * + * To add elements to a pointer array, use g_ptr_array_add(). + * + * To remove elements from a pointer array, use g_ptr_array_remove(), + * g_ptr_array_remove_index() or g_ptr_array_remove_index_fast(). + * + * To access an element of a pointer array, use g_ptr_array_index(). + * + * To set the size of a pointer array, use g_ptr_array_set_size(). + * + * To free a pointer array, use g_ptr_array_free(). + * + * An example using a #GPtrArray: + * |[ + * GPtrArray *array; + * gchar *string1 = "one"; + * gchar *string2 = "two"; + * gchar *string3 = "three"; + * + * array = g_ptr_array_new (); + * g_ptr_array_add (array, (gpointer) string1); + * g_ptr_array_add (array, (gpointer) string2); + * g_ptr_array_add (array, (gpointer) string3); + * + * if (g_ptr_array_index (array, 0) != (gpointer) string1) + * g_print ("ERROR: got %p instead of %p\n", + * g_ptr_array_index (array, 0), string1); + * + * g_ptr_array_free (array, TRUE); + * ]| + */ + +typedef struct _GRealPtrArray GRealPtrArray; + +/** + * GPtrArray: + * @pdata: points to the array of pointers, which may be moved when the + * array grows + * @len: number of pointers in the array + * + * Contains the public fields of a pointer array. + */ +struct _GRealPtrArray +{ + gpointer *pdata; + guint len; + guint alloc; + gatomicrefcount ref_count; + GDestroyNotify element_free_func; +}; + +/** + * g_ptr_array_index: + * @array: a #GPtrArray + * @index_: the index of the pointer to return + * + * Returns the pointer at the given index of the pointer array. + * + * This does not perform bounds checking on the given @index_, + * so you are responsible for checking it against the array length. + * + * Returns: the pointer at the given index + */ + +static void g_ptr_array_maybe_expand (GRealPtrArray *array, + guint len); + +static GPtrArray * +ptr_array_new (guint reserved_size, + GDestroyNotify element_free_func) +{ + GRealPtrArray *array; + + array = g_slice_new (GRealPtrArray); + + array->pdata = NULL; + array->len = 0; + array->alloc = 0; + array->element_free_func = element_free_func; + + g_atomic_ref_count_init (&array->ref_count); + + if (reserved_size != 0) + g_ptr_array_maybe_expand (array, reserved_size); + + return (GPtrArray *) array; +} + +/** + * g_ptr_array_new: + * + * Creates a new #GPtrArray with a reference count of 1. + * + * Returns: the new #GPtrArray + */ +GPtrArray* +g_ptr_array_new (void) +{ + return ptr_array_new (0, NULL); +} + +/** + * g_ptr_array_steal: + * @array: a #GPtrArray. + * @len: (optional) (out): 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 + * @user_data: user data passed to the copy function @func, or %NULL + * + * Makes a full (deep) copy of a #GPtrArray. + * + * @func, as a #GCopyFunc, takes two arguments, the data to be copied + * and a @user_data pointer. On common processor architectures, it's safe to + * pass %NULL as @user_data if the copy function takes only one argument. You + * may get compiler warnings from this though if compiling with GCC’s + * `-Wcast-function-type` warning. + * + * If @func is %NULL, then only the pointers (and not what they are + * pointing to) are copied to the new #GPtrArray. + * + * The copy of @array will have the same #GDestroyNotify for its elements as + * @array. + * + * Returns: (transfer full): a deep copy of the initial #GPtrArray. + * + * Since: 2.62 + **/ +GPtrArray * +g_ptr_array_copy (GPtrArray *array, + GCopyFunc func, + gpointer user_data) +{ + GPtrArray *new_array; + + g_return_val_if_fail (array != NULL, NULL); + + new_array = ptr_array_new (array->len, + ((GRealPtrArray *) array)->element_free_func); + + if (func != NULL) + { + guint i; + + for (i = 0; i < array->len; i++) + new_array->pdata[i] = func (array->pdata[i], user_data); + } + else if (array->len > 0) + { + memcpy (new_array->pdata, array->pdata, + array->len * sizeof (*array->pdata)); + } + + new_array->len = array->len; + + return new_array; +} + +/** + * g_ptr_array_sized_new: + * @reserved_size: number of pointers preallocated + * + * Creates a new #GPtrArray with @reserved_size pointers preallocated + * and a reference count of 1. This avoids frequent reallocation, if + * you are going to add many pointers to the array. Note however that + * the size of the array is still 0. + * + * Returns: the new #GPtrArray + */ +GPtrArray* +g_ptr_array_sized_new (guint reserved_size) +{ + return ptr_array_new (reserved_size, NULL); +} + +/** + * g_array_copy: + * @array: A #GArray. + * + * Create a shallow copy of a #GArray. If the array elements consist of + * pointers to data, the pointers are copied but the actual data is not. + * + * Returns: (transfer container): A copy of @array. + * + * Since: 2.62 + **/ +GArray * +g_array_copy (GArray *array) +{ + GRealArray *rarray = (GRealArray *) array; + GRealArray *new_rarray; + + g_return_val_if_fail (rarray != NULL, NULL); + + new_rarray = + (GRealArray *) g_array_sized_new (rarray->zero_terminated, rarray->clear, + rarray->elt_size, rarray->elt_capacity); + new_rarray->len = rarray->len; + 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; +} + +/** + * g_ptr_array_new_with_free_func: + * @element_free_func: (nullable): A function to free elements with + * destroy @array or %NULL + * + * Creates a new #GPtrArray with a reference count of 1 and use + * @element_free_func for freeing each element when the array is destroyed + * either via g_ptr_array_unref(), when g_ptr_array_free() is called with + * @free_segment set to %TRUE or when removing elements. + * + * Returns: A new #GPtrArray + * + * Since: 2.22 + */ +GPtrArray* +g_ptr_array_new_with_free_func (GDestroyNotify element_free_func) +{ + return ptr_array_new (0, element_free_func); +} + +/** + * g_ptr_array_new_full: + * @reserved_size: number of pointers preallocated + * @element_free_func: (nullable): A function to free elements with + * destroy @array or %NULL + * + * Creates a new #GPtrArray with @reserved_size pointers preallocated + * and a reference count of 1. This avoids frequent reallocation, if + * you are going to add many pointers to the array. Note however that + * the size of the array is still 0. It also set @element_free_func + * for freeing each element when the array is destroyed either via + * g_ptr_array_unref(), when g_ptr_array_free() is called with + * @free_segment set to %TRUE or when removing elements. + * + * Returns: A new #GPtrArray + * + * Since: 2.30 + */ +GPtrArray* +g_ptr_array_new_full (guint reserved_size, + GDestroyNotify element_free_func) +{ + return ptr_array_new (reserved_size, element_free_func); +} + +/** + * g_ptr_array_set_free_func: + * @array: A #GPtrArray + * @element_free_func: (nullable): A function to free elements with + * destroy @array or %NULL + * + * Sets a function for freeing each element when @array is destroyed + * either via g_ptr_array_unref(), when g_ptr_array_free() is called + * with @free_segment set to %TRUE or when removing elements. + * + * Since: 2.22 + */ +void +g_ptr_array_set_free_func (GPtrArray *array, + GDestroyNotify element_free_func) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + + g_return_if_fail (array); + + rarray->element_free_func = element_free_func; +} + +/** + * g_ptr_array_ref: + * @array: a #GPtrArray + * + * Atomically increments the reference count of @array by one. + * This function is thread-safe and may be called from any thread. + * + * Returns: The passed in #GPtrArray + * + * Since: 2.22 + */ +GPtrArray* +g_ptr_array_ref (GPtrArray *array) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + + g_return_val_if_fail (array, NULL); + + g_atomic_ref_count_inc (&rarray->ref_count); + + return array; +} + +static gpointer *ptr_array_free (GPtrArray *, ArrayFreeFlags); + +/** + * g_ptr_array_unref: + * @array: A #GPtrArray + * + * Atomically decrements the reference count of @array by one. If the + * reference count drops to 0, the effect is the same as calling + * g_ptr_array_free() with @free_segment set to %TRUE. This function + * is thread-safe and may be called from any thread. + * + * Since: 2.22 + */ +void +g_ptr_array_unref (GPtrArray *array) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + + g_return_if_fail (array); + + if (g_atomic_ref_count_dec (&rarray->ref_count)) + ptr_array_free (array, FREE_SEGMENT); +} + +/** + * g_ptr_array_free: + * @array: a #GPtrArray + * @free_seg: if %TRUE the actual pointer array is freed as well + * + * Frees the memory allocated for the #GPtrArray. If @free_seg is %TRUE + * it frees the memory block holding the elements as well. Pass %FALSE + * if you want to free the #GPtrArray wrapper but preserve the + * underlying array for use elsewhere. If the reference count of @array + * is greater than one, the #GPtrArray wrapper is preserved but the + * size of @array will be set to zero. + * + * If array contents point to dynamically-allocated memory, they should + * be freed separately if @free_seg is %TRUE and no #GDestroyNotify + * function has been set for @array. + * + * This function is not thread-safe. If using a #GPtrArray from multiple + * threads, use only the atomic g_ptr_array_ref() and g_ptr_array_unref() + * functions. + * + * Returns: (transfer full) (nullable): the pointer array if @free_seg is + * %FALSE, otherwise %NULL. The pointer array should be freed using g_free(). + */ +gpointer* +g_ptr_array_free (GPtrArray *array, + gboolean free_segment) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + ArrayFreeFlags flags; + + g_return_val_if_fail (rarray, NULL); + + flags = (free_segment ? FREE_SEGMENT : 0); + + /* if others are holding a reference, preserve the wrapper but + * do free/return the data + */ + if (!g_atomic_ref_count_dec (&rarray->ref_count)) + flags |= PRESERVE_WRAPPER; + + return ptr_array_free (array, flags); +} + +static gpointer * +ptr_array_free (GPtrArray *array, + ArrayFreeFlags flags) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + gpointer *segment; + + if (flags & FREE_SEGMENT) + { + /* Data here is stolen and freed manually. It is an + * error to attempt to access the array data (including + * mutating the array bounds) during destruction). + * + * https://bugzilla.gnome.org/show_bug.cgi?id=769064 + */ + gpointer *stolen_pdata = g_steal_pointer (&rarray->pdata); + if (rarray->element_free_func != NULL) + { + guint i; + + for (i = 0; i < rarray->len; ++i) + rarray->element_free_func (stolen_pdata[i]); + } + + g_free (stolen_pdata); + segment = NULL; + } + else + segment = rarray->pdata; + + if (flags & PRESERVE_WRAPPER) + { + rarray->pdata = NULL; + rarray->len = 0; + rarray->alloc = 0; + } + else + { + g_slice_free1 (sizeof (GRealPtrArray), rarray); + } + + return segment; +} + +static void +g_ptr_array_maybe_expand (GRealPtrArray *array, + guint len) +{ + guint max_len; + + /* The maximum array length is derived from following constraints: + * - The number of bytes must fit into a gsize / 2. + * - The number of elements must fit into guint. + */ + max_len = MIN (G_MAXSIZE / 2 / sizeof (gpointer), G_MAXUINT); + + /* Detect potential overflow */ + if G_UNLIKELY ((max_len - array->len) < len) + g_error ("adding %u to array would overflow", len); + + if ((array->len + len) > array->alloc) + { + guint old_alloc = array->alloc; + gsize want_alloc = g_nearest_pow (sizeof (gpointer) * (array->len + len)); + want_alloc = MAX (want_alloc, MIN_ARRAY_SIZE); + array->alloc = MIN (want_alloc / sizeof (gpointer), G_MAXUINT); + array->pdata = g_realloc (array->pdata, want_alloc); + if (G_UNLIKELY (g_mem_gc_friendly)) + for ( ; old_alloc < array->alloc; old_alloc++) + array->pdata [old_alloc] = NULL; + } +} + +/** + * g_ptr_array_set_size: + * @array: a #GPtrArray + * @length: the new length of the pointer array + * + * Sets the size of the array. When making the array larger, + * newly-added elements will be set to %NULL. When making it smaller, + * if @array has a non-%NULL #GDestroyNotify function then it will be + * called for the removed elements. + */ +void +g_ptr_array_set_size (GPtrArray *array, + gint length) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + guint length_unsigned; + + g_return_if_fail (rarray); + g_return_if_fail (rarray->len == 0 || (rarray->len != 0 && rarray->pdata != NULL)); + g_return_if_fail (length >= 0); + + length_unsigned = (guint) length; + + if (length_unsigned > rarray->len) + { + guint i; + g_ptr_array_maybe_expand (rarray, (length_unsigned - rarray->len)); + /* This is not + * memset (array->pdata + array->len, 0, + * sizeof (gpointer) * (length_unsigned - array->len)); + * to make it really portable. Remember (void*)NULL needn't be + * bitwise zero. It of course is silly not to use memset (..,0,..). + */ + for (i = rarray->len; i < length_unsigned; i++) + rarray->pdata[i] = NULL; + } + else if (length_unsigned < rarray->len) + g_ptr_array_remove_range (array, length_unsigned, rarray->len - length_unsigned); + + rarray->len = length_unsigned; +} + +static gpointer +ptr_array_remove_index (GPtrArray *array, + guint index_, + gboolean fast, + gboolean free_element) +{ + GRealPtrArray *rarray = (GRealPtrArray *) array; + gpointer result; + + g_return_val_if_fail (rarray, NULL); + g_return_val_if_fail (rarray->len == 0 || (rarray->len != 0 && rarray->pdata != NULL), NULL); + + g_return_val_if_fail (index_ < rarray->len, NULL); + + result = rarray->pdata[index_]; + + if (rarray->element_free_func != NULL && free_element) + rarray->element_free_func (rarray->pdata[index_]); + + if (index_ != rarray->len - 1 && !fast) + memmove (rarray->pdata + index_, rarray->pdata + index_ + 1, + sizeof (gpointer) * (rarray->len - index_ - 1)); + else if (index_ != rarray->len - 1) + rarray->pdata[index_] = rarray->pdata[rarray->len - 1]; + + rarray->len -= 1; + + if (G_UNLIKELY (g_mem_gc_friendly)) + rarray->pdata[rarray->len] = NULL; + + return result; +} + +/** + * g_ptr_array_remove_index: + * @array: a #GPtrArray + * @index_: the index of the pointer to remove + * + * Removes the pointer at the given index from the pointer array. + * The following elements are moved down one place. If @array has + * a non-%NULL #GDestroyNotify function it is called for the removed + * element. If so, the return value from this function will potentially point + * to freed memory (depending on the #GDestroyNotify implementation). + * + * Returns: (nullable): the pointer which was removed + */ +gpointer +g_ptr_array_remove_index (GPtrArray *array, + guint index_) +{ + return ptr_array_remove_index (array, index_, FALSE, TRUE); +} + +/** + * g_ptr_array_remove_index_fast: + * @array: a #GPtrArray + * @index_: the index of the pointer to remove + * + * Removes the pointer at the given index from the pointer array. + * The last element in the array is used to fill in the space, so + * this function does not preserve the order of the array. But it + * is faster than g_ptr_array_remove_index(). If @array has a non-%NULL + * #GDestroyNotify function it is called for the removed element. If so, the + * return value from this function will potentially point to freed memory + * (depending on the #GDestroyNotify implementation). + * + * Returns: (nullable): the pointer which was removed + */ +gpointer +g_ptr_array_remove_index_fast (GPtrArray *array, + guint index_) +{ + return ptr_array_remove_index (array, index_, TRUE, TRUE); +} + +/** + * g_ptr_array_steal_index: + * @array: a #GPtrArray + * @index_: the index of the pointer to steal + * + * Removes the pointer at the given index from the pointer array. + * The following elements are moved down one place. The #GDestroyNotify for + * @array is *not* called on the removed element; ownership is transferred to + * the caller of this function. + * + * Returns: (transfer full) (nullable): the pointer which was removed + * Since: 2.58 + */ +gpointer +g_ptr_array_steal_index (GPtrArray *array, + guint index_) +{ + return ptr_array_remove_index (array, index_, FALSE, FALSE); +} + +/** + * g_ptr_array_steal_index_fast: + * @array: a #GPtrArray + * @index_: the index of the pointer to steal + * + * Removes the pointer at the given index from the pointer array. + * The last element in the array is used to fill in the space, so + * this function does not preserve the order of the array. But it + * is faster than g_ptr_array_steal_index(). The #GDestroyNotify for @array is + * *not* called on the removed element; ownership is transferred to the caller + * of this function. + * + * Returns: (transfer full) (nullable): the pointer which was removed + * Since: 2.58 + */ +gpointer +g_ptr_array_steal_index_fast (GPtrArray *array, + guint index_) +{ + return ptr_array_remove_index (array, index_, TRUE, FALSE); +} + +/** + * g_ptr_array_remove_range: + * @array: a @GPtrArray + * @index_: the index of the first pointer to remove + * @length: the number of pointers to remove + * + * Removes the given number of pointers starting at the given index + * from a #GPtrArray. The following elements are moved to close the + * gap. If @array has a non-%NULL #GDestroyNotify function it is + * called for the removed elements. + * + * Returns: the @array + * + * Since: 2.4 + */ +GPtrArray* +g_ptr_array_remove_range (GPtrArray *array, + guint index_, + guint length) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + guint i; + + g_return_val_if_fail (rarray != NULL, NULL); + g_return_val_if_fail (rarray->len == 0 || (rarray->len != 0 && rarray->pdata != NULL), NULL); + g_return_val_if_fail (index_ <= rarray->len, NULL); + g_return_val_if_fail (index_ + length <= rarray->len, NULL); + + if (rarray->element_free_func != NULL) + { + for (i = index_; i < index_ + length; i++) + rarray->element_free_func (rarray->pdata[i]); + } + + if (index_ + length != rarray->len) + { + memmove (&rarray->pdata[index_], + &rarray->pdata[index_ + length], + (rarray->len - (index_ + length)) * sizeof (gpointer)); + } + + rarray->len -= length; + if (G_UNLIKELY (g_mem_gc_friendly)) + { + for (i = 0; i < length; i++) + rarray->pdata[rarray->len + i] = NULL; + } + + return array; +} + +/** + * g_ptr_array_remove: + * @array: a #GPtrArray + * @data: the pointer to remove + * + * Removes the first occurrence of the given pointer from the pointer + * array. The following elements are moved down one place. If @array + * has a non-%NULL #GDestroyNotify function it is called for the + * removed element. + * + * It returns %TRUE if the pointer was removed, or %FALSE if the + * pointer was not found. + * + * Returns: %TRUE if the pointer is removed, %FALSE if the pointer + * is not found in the array + */ +gboolean +g_ptr_array_remove (GPtrArray *array, + gpointer data) +{ + guint i; + + g_return_val_if_fail (array, FALSE); + g_return_val_if_fail (array->len == 0 || (array->len != 0 && array->pdata != NULL), FALSE); + + for (i = 0; i < array->len; i += 1) + { + if (array->pdata[i] == data) + { + g_ptr_array_remove_index (array, i); + return TRUE; + } + } + + return FALSE; +} + +/** + * g_ptr_array_remove_fast: + * @array: a #GPtrArray + * @data: the pointer to remove + * + * Removes the first occurrence of the given pointer from the pointer + * array. The last element in the array is used to fill in the space, + * so this function does not preserve the order of the array. But it + * is faster than g_ptr_array_remove(). If @array has a non-%NULL + * #GDestroyNotify function it is called for the removed element. + * + * It returns %TRUE if the pointer was removed, or %FALSE if the + * pointer was not found. + * + * Returns: %TRUE if the pointer was found in the array + */ +gboolean +g_ptr_array_remove_fast (GPtrArray *array, + gpointer data) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + guint i; + + g_return_val_if_fail (rarray, FALSE); + g_return_val_if_fail (rarray->len == 0 || (rarray->len != 0 && rarray->pdata != NULL), FALSE); + + for (i = 0; i < rarray->len; i += 1) + { + if (rarray->pdata[i] == data) + { + g_ptr_array_remove_index_fast (array, i); + return TRUE; + } + } + + return FALSE; +} + +/** + * g_ptr_array_add: + * @array: a #GPtrArray + * @data: the pointer to add + * + * Adds a pointer to the end of the pointer array. The array will grow + * in size automatically if necessary. + */ +void +g_ptr_array_add (GPtrArray *array, + gpointer data) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + + g_return_if_fail (rarray); + g_return_if_fail (rarray->len == 0 || (rarray->len != 0 && rarray->pdata != NULL)); + + g_ptr_array_maybe_expand (rarray, 1); + + rarray->pdata[rarray->len++] = data; +} + +/** + * g_ptr_array_extend: + * @array_to_extend: a #GPtrArray. + * @array: (transfer none): a #GPtrArray to add to the end of @array_to_extend. + * @func: (nullable): a copy function used to copy every element in the array + * @user_data: user data passed to the copy function @func, or %NULL + * + * Adds all pointers of @array to the end of the array @array_to_extend. + * The array will grow in size automatically if needed. @array_to_extend is + * modified in-place. + * + * @func, as a #GCopyFunc, takes two arguments, the data to be copied + * and a @user_data pointer. On common processor architectures, it's safe to + * pass %NULL as @user_data if the copy function takes only one argument. You + * may get compiler warnings from this though if compiling with GCC’s + * `-Wcast-function-type` warning. + * + * If @func is %NULL, then only the pointers (and not what they are + * pointing to) are copied to the new #GPtrArray. + * + * Since: 2.62 + **/ +void +g_ptr_array_extend (GPtrArray *array_to_extend, + GPtrArray *array, + GCopyFunc func, + gpointer user_data) +{ + GRealPtrArray *rarray_to_extend = (GRealPtrArray *) array_to_extend; + + g_return_if_fail (array_to_extend != NULL); + g_return_if_fail (array != NULL); + + g_ptr_array_maybe_expand (rarray_to_extend, array->len); + + if (func != NULL) + { + guint i; + + for (i = 0; i < array->len; i++) + rarray_to_extend->pdata[i + rarray_to_extend->len] = + func (array->pdata[i], user_data); + } + else if (array->len > 0) + { + memcpy (rarray_to_extend->pdata + rarray_to_extend->len, array->pdata, + array->len * sizeof (*array->pdata)); + } + + rarray_to_extend->len += array->len; +} + +/** + * g_ptr_array_extend_and_steal: + * @array_to_extend: (transfer none): a #GPtrArray. + * @array: (transfer container): a #GPtrArray to add to the end of + * @array_to_extend. + * + * Adds all the pointers in @array to the end of @array_to_extend, transferring + * ownership of each element from @array to @array_to_extend and modifying + * @array_to_extend in-place. @array is then freed. + * + * As with g_ptr_array_free(), @array will be destroyed if its reference count + * is 1. If its reference count is higher, it will be decremented and the + * length of @array set to zero. + * + * Since: 2.62 + **/ +void +g_ptr_array_extend_and_steal (GPtrArray *array_to_extend, + GPtrArray *array) +{ + gpointer *pdata; + + g_ptr_array_extend (array_to_extend, array, NULL, NULL); + + /* Get rid of @array without triggering the GDestroyNotify attached + * 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); +} + +/** + * g_ptr_array_insert: + * @array: a #GPtrArray + * @index_: the index to place the new element at, or -1 to append + * @data: the pointer to add. + * + * Inserts an element into the pointer array at the given index. The + * array will grow in size automatically if necessary. + * + * Since: 2.40 + */ +void +g_ptr_array_insert (GPtrArray *array, + gint index_, + gpointer data) +{ + GRealPtrArray *rarray = (GRealPtrArray *)array; + + g_return_if_fail (rarray); + g_return_if_fail (index_ >= -1); + g_return_if_fail (index_ <= (gint)rarray->len); + + g_ptr_array_maybe_expand (rarray, 1); + + if (index_ < 0) + index_ = rarray->len; + + if ((guint) index_ < rarray->len) + memmove (&(rarray->pdata[index_ + 1]), + &(rarray->pdata[index_]), + (rarray->len - index_) * sizeof (gpointer)); + + rarray->len++; + 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 + * @compare_func: comparison function + * + * Sorts the array, using @compare_func which should be a qsort()-style + * comparison function (returns less than zero for first arg is less + * than second arg, zero for equal, greater than zero if irst arg is + * greater than second arg). + * + * 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. 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. + */ +void +g_ptr_array_sort (GPtrArray *array, + GCompareFunc compare_func) +{ + g_return_if_fail (array != NULL); + + /* Don't use qsort as we want a guaranteed stable sort */ + if (array->len > 0) + g_qsort_with_data (array->pdata, + array->len, + sizeof (gpointer), + (GCompareDataFunc)compare_func, + 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 + * @compare_func: comparison function + * @user_data: data to pass to @compare_func + * + * Like g_ptr_array_sort(), but the comparison function has an extra + * user data argument. + * + * 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. 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. + */ +void +g_ptr_array_sort_with_data (GPtrArray *array, + GCompareDataFunc compare_func, + gpointer user_data) +{ + g_return_if_fail (array != NULL); + + if (array->len > 0) + g_qsort_with_data (array->pdata, + array->len, + sizeof (gpointer), + compare_func, + user_data); +} + +/** + * g_ptr_array_foreach: + * @array: a #GPtrArray + * @func: the function to call for each array element + * @user_data: user data to pass to the function + * + * Calls a function for each element of a #GPtrArray. @func must not + * add elements to or remove elements from the array. + * + * Since: 2.4 + */ +void +g_ptr_array_foreach (GPtrArray *array, + GFunc func, + gpointer user_data) +{ + guint i; + + g_return_if_fail (array); + + for (i = 0; i < array->len; i++) + (*func) (array->pdata[i], user_data); +} + +/** + * g_ptr_array_find: (skip) + * @haystack: pointer array to be searched + * @needle: pointer to look for + * @index_: (optional) (out): return location for the index of + * the element, if found + * + * Checks whether @needle exists in @haystack. If the element is found, %TRUE is + * returned and the element’s index is returned in @index_ (if non-%NULL). + * Otherwise, %FALSE is returned and @index_ is undefined. If @needle exists + * multiple times in @haystack, the index of the first instance is returned. + * + * This does pointer comparisons only. If you want to use more complex equality + * checks, such as string comparisons, use g_ptr_array_find_with_equal_func(). + * + * Returns: %TRUE if @needle is one of the elements of @haystack + * Since: 2.54 + */ +gboolean +g_ptr_array_find (GPtrArray *haystack, + gconstpointer needle, + guint *index_) +{ + return g_ptr_array_find_with_equal_func (haystack, needle, NULL, index_); +} + +/** + * g_ptr_array_find_with_equal_func: (skip) + * @haystack: pointer array to be searched + * @needle: pointer to look for + * @equal_func: (nullable): the function to call for each element, which should + * return %TRUE when the desired element is found; or %NULL to use pointer + * equality + * @index_: (optional) (out): return location for the index of + * the element, if found + * + * Checks whether @needle exists in @haystack, using the given @equal_func. + * If the element is found, %TRUE is returned and the element’s index is + * returned in @index_ (if non-%NULL). Otherwise, %FALSE is returned and @index_ + * is undefined. If @needle exists multiple times in @haystack, the index of + * the first instance is returned. + * + * @equal_func is called with the element from the array as its first parameter, + * and @needle as its second parameter. If @equal_func is %NULL, pointer + * equality is used. + * + * Returns: %TRUE if @needle is one of the elements of @haystack + * Since: 2.54 + */ +gboolean +g_ptr_array_find_with_equal_func (GPtrArray *haystack, + gconstpointer needle, + GEqualFunc equal_func, + guint *index_) +{ + guint i; + + g_return_val_if_fail (haystack != NULL, FALSE); + + if (equal_func == NULL) + equal_func = g_direct_equal; + + for (i = 0; i < haystack->len; i++) + { + if (equal_func (g_ptr_array_index (haystack, i), needle)) + { + if (index_ != NULL) + *index_ = i; + return TRUE; + } + } + + return FALSE; +} + +/** + * SECTION:arrays_byte + * @title: Byte Arrays + * @short_description: arrays of bytes + * + * #GByteArray is a mutable array of bytes based on #GArray, to provide arrays + * of bytes which grow automatically as elements are added. + * + * To create a new #GByteArray use g_byte_array_new(). To add elements to a + * #GByteArray, use g_byte_array_append(), and g_byte_array_prepend(). + * + * To set the size of a #GByteArray, use g_byte_array_set_size(). + * + * To free a #GByteArray, use g_byte_array_free(). + * + * An example for using a #GByteArray: + * |[ + * GByteArray *gbarray; + * gint i; + * + * gbarray = g_byte_array_new (); + * for (i = 0; i < 10000; i++) + * g_byte_array_append (gbarray, (guint8*) "abcd", 4); + * + * for (i = 0; i < 10000; i++) + * { + * g_assert (gbarray->data[4*i] == 'a'); + * g_assert (gbarray->data[4*i+1] == 'b'); + * g_assert (gbarray->data[4*i+2] == 'c'); + * g_assert (gbarray->data[4*i+3] == 'd'); + * } + * + * g_byte_array_free (gbarray, TRUE); + * ]| + * + * See #GBytes if you are interested in an immutable object representing a + * sequence of bytes. + */ + +/** + * GByteArray: + * @data: a pointer to the element data. The data may be moved as + * elements are added to the #GByteArray + * @len: the number of elements in the #GByteArray + * + * Contains the public fields of a GByteArray. + */ + +/** + * g_byte_array_new: + * + * Creates a new #GByteArray with a reference count of 1. + * + * Returns: (transfer full): the new #GByteArray + */ +GByteArray* +g_byte_array_new (void) +{ + return (GByteArray *)g_array_sized_new (FALSE, FALSE, 1, 0); +} + +/** + * g_byte_array_steal: + * @array: a #GByteArray. + * @len: (optional) (out): 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 + * + * Create byte array containing the data. The data will be owned by the array + * and will be freed with g_free(), i.e. it could be allocated using g_strdup(). + * + * Do not use it if @len is greater than %G_MAXUINT. #GByteArray + * stores the length of its data in #guint, which may be shorter than + * #gsize. + * + * Since: 2.32 + * + * Returns: (transfer full): a new #GByteArray + */ +GByteArray* +g_byte_array_new_take (guint8 *data, + gsize len) +{ + GByteArray *array; + GRealArray *real; + + g_return_val_if_fail (len <= G_MAXUINT, NULL); + array = g_byte_array_new (); + real = (GRealArray *)array; + g_assert (real->data == NULL); + g_assert (real->len == 0); + + real->data = data; + real->len = len; + real->elt_capacity = len; + + return array; +} + +/** + * g_byte_array_sized_new: + * @reserved_size: number of bytes preallocated + * + * Creates a new #GByteArray with @reserved_size bytes preallocated. + * This avoids frequent reallocation, if you are going to add many + * bytes to the array. Note however that the size of the array is still + * 0. + * + * Returns: the new #GByteArray + */ +GByteArray* +g_byte_array_sized_new (guint reserved_size) +{ + return (GByteArray *)g_array_sized_new (FALSE, FALSE, 1, reserved_size); +} + +/** + * g_byte_array_free: + * @array: a #GByteArray + * @free_segment: if %TRUE the actual byte data is freed as well + * + * Frees the memory allocated by the #GByteArray. If @free_segment is + * %TRUE it frees the actual byte data. If the reference count of + * @array is greater than one, the #GByteArray wrapper is preserved but + * the size of @array will be set to zero. + * + * Returns: the element data if @free_segment is %FALSE, otherwise + * %NULL. The element data should be freed using g_free(). + */ +guint8* +g_byte_array_free (GByteArray *array, + gboolean free_segment) +{ + return (guint8 *)g_array_free ((GArray *)array, free_segment); +} + +/** + * g_byte_array_free_to_bytes: + * @array: (transfer full): a #GByteArray + * + * Transfers the data from the #GByteArray into a new immutable #GBytes. + * + * The #GByteArray is freed unless the reference count of @array is greater + * than one, the #GByteArray wrapper is preserved but the size of @array + * will be set to zero. + * + * This is identical to using g_bytes_new_take() and g_byte_array_free() + * together. + * + * Since: 2.32 + * + * Returns: (transfer full): a new immutable #GBytes representing same + * byte data that was in the array + */ +GBytes* +g_byte_array_free_to_bytes (GByteArray *array) +{ + gsize length; + + g_return_val_if_fail (array != NULL, NULL); + + length = array->len; + return g_bytes_new_take (g_byte_array_free (array, FALSE), length); +} + +/** + * g_byte_array_ref: + * @array: A #GByteArray + * + * Atomically increments the reference count of @array by one. + * This function is thread-safe and may be called from any thread. + * + * Returns: The passed in #GByteArray + * + * Since: 2.22 + */ +GByteArray* +g_byte_array_ref (GByteArray *array) +{ + return (GByteArray *)g_array_ref ((GArray *)array); +} + +/** + * g_byte_array_unref: + * @array: A #GByteArray + * + * Atomically decrements the reference count of @array by one. If the + * reference count drops to 0, all memory allocated by the array is + * released. This function is thread-safe and may be called from any + * thread. + * + * Since: 2.22 + */ +void +g_byte_array_unref (GByteArray *array) +{ + g_array_unref ((GArray *)array); +} + +/** + * g_byte_array_append: + * @array: a #GByteArray + * @data: the byte data to be added + * @len: the number of bytes to add + * + * Adds the given bytes to the end of the #GByteArray. + * The array will grow in size automatically if necessary. + * + * Returns: the #GByteArray + */ +GByteArray* +g_byte_array_append (GByteArray *array, + const guint8 *data, + guint len) +{ + g_array_append_vals ((GArray *)array, (guint8 *)data, len); + + return array; +} + +/** + * g_byte_array_prepend: + * @array: a #GByteArray + * @data: the byte data to be added + * @len: the number of bytes to add + * + * Adds the given data to the start of the #GByteArray. + * The array will grow in size automatically if necessary. + * + * Returns: the #GByteArray + */ +GByteArray* +g_byte_array_prepend (GByteArray *array, + const guint8 *data, + guint len) +{ + g_array_prepend_vals ((GArray *)array, (guint8 *)data, len); + + return array; +} + +/** + * g_byte_array_set_size: + * @array: a #GByteArray + * @length: the new size of the #GByteArray + * + * Sets the size of the #GByteArray, expanding it if necessary. + * + * Returns: the #GByteArray + */ +GByteArray* +g_byte_array_set_size (GByteArray *array, + guint length) +{ + g_array_set_size ((GArray *)array, length); + + return array; +} + +/** + * g_byte_array_remove_index: + * @array: a #GByteArray + * @index_: the index of the byte to remove + * + * Removes the byte at the given index from a #GByteArray. + * The following bytes are moved down one place. + * + * Returns: the #GByteArray + **/ +GByteArray* +g_byte_array_remove_index (GByteArray *array, + guint index_) +{ + g_array_remove_index ((GArray *)array, index_); + + return array; +} + +/** + * g_byte_array_remove_index_fast: + * @array: a #GByteArray + * @index_: the index of the byte to remove + * + * Removes the byte at the given index from a #GByteArray. The last + * element in the array is used to fill in the space, so this function + * does not preserve the order of the #GByteArray. But it is faster + * than g_byte_array_remove_index(). + * + * Returns: the #GByteArray + */ +GByteArray* +g_byte_array_remove_index_fast (GByteArray *array, + guint index_) +{ + g_array_remove_index_fast ((GArray *)array, index_); + + return array; +} + +/** + * g_byte_array_remove_range: + * @array: a @GByteArray + * @index_: the index of the first byte to remove + * @length: the number of bytes to remove + * + * Removes the given number of bytes starting at the given index from a + * #GByteArray. The following elements are moved to close the gap. + * + * Returns: the #GByteArray + * + * Since: 2.4 + */ +GByteArray* +g_byte_array_remove_range (GByteArray *array, + guint index_, + guint length) +{ + g_return_val_if_fail (array, NULL); + g_return_val_if_fail (index_ <= array->len, NULL); + g_return_val_if_fail (index_ + length <= array->len, NULL); + + return (GByteArray *)g_array_remove_range ((GArray *)array, index_, length); +} + +/** + * g_byte_array_sort: + * @array: a #GByteArray + * @compare_func: comparison function + * + * Sorts a byte array, using @compare_func which should be a + * qsort()-style comparison function (returns less than zero for first + * arg is less than second arg, zero for equal, greater than zero if + * first arg is greater than second arg). + * + * If two array elements compare equal, their order in the sorted array + * is undefined. If you want equal elements to keep their order (i.e. + * you want a stable sort) you can write a comparison function that, + * if two elements would otherwise compare equal, compares them by + * their addresses. + */ +void +g_byte_array_sort (GByteArray *array, + GCompareFunc compare_func) +{ + g_array_sort ((GArray *)array, compare_func); +} + +/** + * g_byte_array_sort_with_data: + * @array: a #GByteArray + * @compare_func: comparison function + * @user_data: data to pass to @compare_func + * + * Like g_byte_array_sort(), but the comparison function takes an extra + * user data argument. + */ +void +g_byte_array_sort_with_data (GByteArray *array, + GCompareDataFunc compare_func, + gpointer user_data) +{ + g_array_sort_with_data ((GArray *)array, compare_func, user_data); +} diff --git a/glib/garray.h b/glib/garray.h new file mode 100644 index 0000000..67131b5 --- /dev/null +++ b/glib/garray.h @@ -0,0 +1,281 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_ARRAY_H__ +#define __G_ARRAY_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +typedef struct _GBytes GBytes; +typedef struct _GArray GArray; +typedef struct _GByteArray GByteArray; +typedef struct _GPtrArray GPtrArray; + +struct _GArray +{ + gchar *data; + guint len; +}; + +struct _GByteArray +{ + guint8 *data; + guint len; +}; + +struct _GPtrArray +{ + gpointer *pdata; + guint len; +}; + +/* Resizable arrays. remove fills any cleared spot and shortens the + * array, while preserving the order. remove_fast will distort the + * order by moving the last element to the position of the removed. + */ + +#define g_array_append_val(a,v) g_array_append_vals (a, &(v), 1) +#define g_array_prepend_val(a,v) g_array_prepend_vals (a, &(v), 1) +#define g_array_insert_val(a,i,v) g_array_insert_vals (a, i, &(v), 1) +#define g_array_index(a,t,i) (((t*) (void *) (a)->data) [(i)]) + +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_, + guint element_size, + guint reserved_size); +GLIB_AVAILABLE_IN_2_62 +GArray* g_array_copy (GArray *array); +GLIB_AVAILABLE_IN_ALL +gchar* g_array_free (GArray *array, + gboolean free_segment); +GLIB_AVAILABLE_IN_ALL +GArray *g_array_ref (GArray *array); +GLIB_AVAILABLE_IN_ALL +void g_array_unref (GArray *array); +GLIB_AVAILABLE_IN_ALL +guint g_array_get_element_size (GArray *array); +GLIB_AVAILABLE_IN_ALL +GArray* g_array_append_vals (GArray *array, + gconstpointer data, + guint len); +GLIB_AVAILABLE_IN_ALL +GArray* g_array_prepend_vals (GArray *array, + gconstpointer data, + guint len); +GLIB_AVAILABLE_IN_ALL +GArray* g_array_insert_vals (GArray *array, + guint index_, + gconstpointer data, + guint len); +GLIB_AVAILABLE_IN_ALL +GArray* g_array_set_size (GArray *array, + guint length); +GLIB_AVAILABLE_IN_ALL +GArray* g_array_remove_index (GArray *array, + guint index_); +GLIB_AVAILABLE_IN_ALL +GArray* g_array_remove_index_fast (GArray *array, + guint index_); +GLIB_AVAILABLE_IN_ALL +GArray* g_array_remove_range (GArray *array, + guint index_, + guint length); +GLIB_AVAILABLE_IN_ALL +void g_array_sort (GArray *array, + GCompareFunc compare_func); +GLIB_AVAILABLE_IN_ALL +void g_array_sort_with_data (GArray *array, + GCompareDataFunc compare_func, + gpointer user_data); +GLIB_AVAILABLE_IN_2_62 +gboolean g_array_binary_search (GArray *array, + gconstpointer target, + GCompareFunc compare_func, + guint *out_match_index); +GLIB_AVAILABLE_IN_ALL +void g_array_set_clear_func (GArray *array, + GDestroyNotify clear_func); + +/* Resizable pointer array. This interface is much less complicated + * than the above. Add appends a pointer. Remove fills any cleared + * spot and shortens the array. remove_fast will again distort order. + */ +#define g_ptr_array_index(array,index_) ((array)->pdata)[index_] +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, + gpointer user_data); +GLIB_AVAILABLE_IN_ALL +GPtrArray* g_ptr_array_sized_new (guint reserved_size); +GLIB_AVAILABLE_IN_ALL +GPtrArray* g_ptr_array_new_full (guint reserved_size, + GDestroyNotify element_free_func); +GLIB_AVAILABLE_IN_ALL +gpointer* g_ptr_array_free (GPtrArray *array, + gboolean free_seg); +GLIB_AVAILABLE_IN_ALL +GPtrArray* g_ptr_array_ref (GPtrArray *array); +GLIB_AVAILABLE_IN_ALL +void g_ptr_array_unref (GPtrArray *array); +GLIB_AVAILABLE_IN_ALL +void g_ptr_array_set_free_func (GPtrArray *array, + GDestroyNotify element_free_func); +GLIB_AVAILABLE_IN_ALL +void g_ptr_array_set_size (GPtrArray *array, + gint length); +GLIB_AVAILABLE_IN_ALL +gpointer g_ptr_array_remove_index (GPtrArray *array, + guint index_); +GLIB_AVAILABLE_IN_ALL +gpointer g_ptr_array_remove_index_fast (GPtrArray *array, + guint index_); +GLIB_AVAILABLE_IN_2_58 +gpointer g_ptr_array_steal_index (GPtrArray *array, + guint index_); +GLIB_AVAILABLE_IN_2_58 +gpointer g_ptr_array_steal_index_fast (GPtrArray *array, + guint index_); +GLIB_AVAILABLE_IN_ALL +gboolean g_ptr_array_remove (GPtrArray *array, + gpointer data); +GLIB_AVAILABLE_IN_ALL +gboolean g_ptr_array_remove_fast (GPtrArray *array, + gpointer data); +GLIB_AVAILABLE_IN_ALL +GPtrArray *g_ptr_array_remove_range (GPtrArray *array, + guint index_, + guint length); +GLIB_AVAILABLE_IN_ALL +void g_ptr_array_add (GPtrArray *array, + gpointer data); +GLIB_AVAILABLE_IN_2_62 +void g_ptr_array_extend (GPtrArray *array_to_extend, + GPtrArray *array, + GCopyFunc func, + gpointer user_data); +GLIB_AVAILABLE_IN_2_62 +void g_ptr_array_extend_and_steal (GPtrArray *array_to_extend, + GPtrArray *array); +GLIB_AVAILABLE_IN_2_40 +void g_ptr_array_insert (GPtrArray *array, + gint index_, + gpointer data); +GLIB_AVAILABLE_IN_ALL +void g_ptr_array_sort (GPtrArray *array, + GCompareFunc compare_func); +GLIB_AVAILABLE_IN_ALL +void g_ptr_array_sort_with_data (GPtrArray *array, + GCompareDataFunc compare_func, + gpointer user_data); +GLIB_AVAILABLE_IN_ALL +void g_ptr_array_foreach (GPtrArray *array, + GFunc func, + gpointer user_data); +GLIB_AVAILABLE_IN_2_54 +gboolean g_ptr_array_find (GPtrArray *haystack, + gconstpointer needle, + guint *index_); +GLIB_AVAILABLE_IN_2_54 +gboolean g_ptr_array_find_with_equal_func (GPtrArray *haystack, + gconstpointer needle, + GEqualFunc equal_func, + guint *index_); + + +/* Byte arrays, an array of guint8. Implemented as a GArray, + * but type-safe. + */ + +GLIB_AVAILABLE_IN_ALL +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 +guint8* g_byte_array_free (GByteArray *array, + gboolean free_segment); +GLIB_AVAILABLE_IN_ALL +GBytes* g_byte_array_free_to_bytes (GByteArray *array); +GLIB_AVAILABLE_IN_ALL +GByteArray *g_byte_array_ref (GByteArray *array); +GLIB_AVAILABLE_IN_ALL +void g_byte_array_unref (GByteArray *array); +GLIB_AVAILABLE_IN_ALL +GByteArray* g_byte_array_append (GByteArray *array, + const guint8 *data, + guint len); +GLIB_AVAILABLE_IN_ALL +GByteArray* g_byte_array_prepend (GByteArray *array, + const guint8 *data, + guint len); +GLIB_AVAILABLE_IN_ALL +GByteArray* g_byte_array_set_size (GByteArray *array, + guint length); +GLIB_AVAILABLE_IN_ALL +GByteArray* g_byte_array_remove_index (GByteArray *array, + guint index_); +GLIB_AVAILABLE_IN_ALL +GByteArray* g_byte_array_remove_index_fast (GByteArray *array, + guint index_); +GLIB_AVAILABLE_IN_ALL +GByteArray* g_byte_array_remove_range (GByteArray *array, + guint index_, + guint length); +GLIB_AVAILABLE_IN_ALL +void g_byte_array_sort (GByteArray *array, + GCompareFunc compare_func); +GLIB_AVAILABLE_IN_ALL +void g_byte_array_sort_with_data (GByteArray *array, + GCompareDataFunc compare_func, + gpointer user_data); + +G_END_DECLS + +#endif /* __G_ARRAY_H__ */ diff --git a/glib/gasyncqueue.c b/glib/gasyncqueue.c new file mode 100644 index 0000000..98c7d8a --- /dev/null +++ b/glib/gasyncqueue.c @@ -0,0 +1,906 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * GAsyncQueue: asynchronous queue implementation, based on GQueue. + * Copyright (C) 2000 Sebastian Wilhelmi; University of Karlsruhe + * + * 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 . + */ + +/* + * MT safe + */ + +#include "config.h" + +#include "gasyncqueue.h" +#include "gasyncqueueprivate.h" + +#include "gmain.h" +#include "gmem.h" +#include "gqueue.h" +#include "gtestutils.h" +#include "gtimer.h" +#include "gthread.h" +#include "deprecated/gthread.h" + + +/** + * SECTION:async_queues + * @title: Asynchronous Queues + * @short_description: asynchronous communication between threads + * @see_also: #GThreadPool + * + * Often you need to communicate between different threads. In general + * it's safer not to do this by shared memory, but by explicit message + * passing. These messages only make sense asynchronously for + * multi-threaded applications though, as a synchronous operation could + * as well be done in the same thread. + * + * Asynchronous queues are an exception from most other GLib data + * structures, as they can be used simultaneously from multiple threads + * without explicit locking and they bring their own builtin reference + * counting. This is because the nature of an asynchronous queue is that + * it will always be used by at least 2 concurrent threads. + * + * For using an asynchronous queue you first have to create one with + * g_async_queue_new(). #GAsyncQueue structs are reference counted, + * use g_async_queue_ref() and g_async_queue_unref() to manage your + * references. + * + * A thread which wants to send a message to that queue simply calls + * g_async_queue_push() to push the message to the queue. + * + * A thread which is expecting messages from an asynchronous queue + * simply calls g_async_queue_pop() for that queue. If no message is + * available in the queue at that point, the thread is now put to sleep + * until a message arrives. The message will be removed from the queue + * and returned. The functions g_async_queue_try_pop() and + * g_async_queue_timeout_pop() can be used to only check for the presence + * of messages or to only wait a certain time for messages respectively. + * + * For almost every function there exist two variants, one that locks + * the queue and one that doesn't. That way you can hold the queue lock + * (acquire it with g_async_queue_lock() and release it with + * g_async_queue_unlock()) over multiple queue accessing instructions. + * This can be necessary to ensure the integrity of the queue, but should + * only be used when really necessary, as it can make your life harder + * if used unwisely. Normally you should only use the locking function + * variants (those without the _unlocked suffix). + * + * In many cases, it may be more convenient to use #GThreadPool when + * you need to distribute work to a set of worker threads instead of + * using #GAsyncQueue manually. #GThreadPool uses a GAsyncQueue + * internally. + */ + +/** + * GAsyncQueue: + * + * An opaque data structure which represents an asynchronous queue. + * + * It should only be accessed through the `g_async_queue_*` functions. + */ +struct _GAsyncQueue +{ + GMutex mutex; + GCond cond; + GQueue queue; + GDestroyNotify item_free_func; + guint waiting_threads; + gint ref_count; +}; + +typedef struct +{ + GCompareDataFunc func; + gpointer user_data; +} SortData; + +/** + * g_async_queue_new: + * + * Creates a new asynchronous queue. + * + * Returns: a new #GAsyncQueue. Free with g_async_queue_unref() + */ +GAsyncQueue * +g_async_queue_new (void) +{ + return g_async_queue_new_full (NULL); +} + +/** + * g_async_queue_new_full: + * @item_free_func: (nullable): function to free queue elements + * + * Creates a new asynchronous queue and sets up a destroy notify + * function that is used to free any remaining queue items when + * the queue is destroyed after the final unref. + * + * Returns: a new #GAsyncQueue. Free with g_async_queue_unref() + * + * Since: 2.16 + */ +GAsyncQueue * +g_async_queue_new_full (GDestroyNotify item_free_func) +{ + GAsyncQueue *queue; + + queue = g_new (GAsyncQueue, 1); + g_mutex_init (&queue->mutex); + g_cond_init (&queue->cond); + g_queue_init (&queue->queue); + queue->waiting_threads = 0; + queue->ref_count = 1; + queue->item_free_func = item_free_func; + + return queue; +} + +/** + * g_async_queue_ref: + * @queue: a #GAsyncQueue + * + * Increases the reference count of the asynchronous @queue by 1. + * You do not need to hold the lock to call this function. + * + * Returns: the @queue that was passed in (since 2.6) + */ +GAsyncQueue * +g_async_queue_ref (GAsyncQueue *queue) +{ + g_return_val_if_fail (queue, NULL); + + g_atomic_int_inc (&queue->ref_count); + + return queue; +} + +/** + * g_async_queue_ref_unlocked: + * @queue: a #GAsyncQueue + * + * Increases the reference count of the asynchronous @queue by 1. + * + * Deprecated: 2.8: Reference counting is done atomically. + * so g_async_queue_ref() can be used regardless of the @queue's + * lock. + */ +void +g_async_queue_ref_unlocked (GAsyncQueue *queue) +{ + g_return_if_fail (queue); + + g_atomic_int_inc (&queue->ref_count); +} + +/** + * g_async_queue_unref_and_unlock: + * @queue: a #GAsyncQueue + * + * Decreases the reference count of the asynchronous @queue by 1 + * and releases the lock. This function must be called while holding + * the @queue's lock. If the reference count went to 0, the @queue + * will be destroyed and the memory allocated will be freed. + * + * Deprecated: 2.8: Reference counting is done atomically. + * so g_async_queue_unref() can be used regardless of the @queue's + * lock. + */ +void +g_async_queue_unref_and_unlock (GAsyncQueue *queue) +{ + g_return_if_fail (queue); + + g_mutex_unlock (&queue->mutex); + g_async_queue_unref (queue); +} + +/** + * g_async_queue_unref: + * @queue: a #GAsyncQueue. + * + * Decreases the reference count of the asynchronous @queue by 1. + * + * If the reference count went to 0, the @queue will be destroyed + * and the memory allocated will be freed. So you are not allowed + * to use the @queue afterwards, as it might have disappeared. + * You do not need to hold the lock to call this function. + */ +void +g_async_queue_unref (GAsyncQueue *queue) +{ + g_return_if_fail (queue); + + if (g_atomic_int_dec_and_test (&queue->ref_count)) + { + g_return_if_fail (queue->waiting_threads == 0); + g_mutex_clear (&queue->mutex); + g_cond_clear (&queue->cond); + if (queue->item_free_func) + g_queue_foreach (&queue->queue, (GFunc) queue->item_free_func, NULL); + g_queue_clear (&queue->queue); + g_free (queue); + } +} + +/** + * g_async_queue_lock: + * @queue: a #GAsyncQueue + * + * Acquires the @queue's lock. If another thread is already + * holding the lock, this call will block until the lock + * becomes available. + * + * Call g_async_queue_unlock() to drop the lock again. + * + * While holding the lock, you can only call the + * g_async_queue_*_unlocked() functions on @queue. Otherwise, + * deadlock may occur. + */ +void +g_async_queue_lock (GAsyncQueue *queue) +{ + g_return_if_fail (queue); + + g_mutex_lock (&queue->mutex); +} + +/** + * g_async_queue_unlock: + * @queue: a #GAsyncQueue + * + * Releases the queue's lock. + * + * Calling this function when you have not acquired + * the with g_async_queue_lock() leads to undefined + * behaviour. + */ +void +g_async_queue_unlock (GAsyncQueue *queue) +{ + g_return_if_fail (queue); + + g_mutex_unlock (&queue->mutex); +} + +/** + * g_async_queue_push: + * @queue: a #GAsyncQueue + * @data: @data to push into the @queue + * + * Pushes the @data into the @queue. @data must not be %NULL. + */ +void +g_async_queue_push (GAsyncQueue *queue, + gpointer data) +{ + g_return_if_fail (queue); + g_return_if_fail (data); + + g_mutex_lock (&queue->mutex); + g_async_queue_push_unlocked (queue, data); + g_mutex_unlock (&queue->mutex); +} + +/** + * g_async_queue_push_unlocked: + * @queue: a #GAsyncQueue + * @data: @data to push into the @queue + * + * Pushes the @data into the @queue. @data must not be %NULL. + * + * This function must be called while holding the @queue's lock. + */ +void +g_async_queue_push_unlocked (GAsyncQueue *queue, + gpointer data) +{ + g_return_if_fail (queue); + g_return_if_fail (data); + + g_queue_push_head (&queue->queue, data); + if (queue->waiting_threads > 0) + g_cond_signal (&queue->cond); +} + +/** + * g_async_queue_push_sorted: + * @queue: a #GAsyncQueue + * @data: the @data to push into the @queue + * @func: the #GCompareDataFunc is used to sort @queue + * @user_data: user data passed to @func. + * + * Inserts @data into @queue using @func to determine the new + * position. + * + * This function requires that the @queue is sorted before pushing on + * new elements, see g_async_queue_sort(). + * + * This function will lock @queue before it sorts the queue and unlock + * it when it is finished. + * + * For an example of @func see g_async_queue_sort(). + * + * Since: 2.10 + */ +void +g_async_queue_push_sorted (GAsyncQueue *queue, + gpointer data, + GCompareDataFunc func, + gpointer user_data) +{ + g_return_if_fail (queue != NULL); + + g_mutex_lock (&queue->mutex); + g_async_queue_push_sorted_unlocked (queue, data, func, user_data); + g_mutex_unlock (&queue->mutex); +} + +static gint +g_async_queue_invert_compare (gpointer v1, + gpointer v2, + SortData *sd) +{ + return -sd->func (v1, v2, sd->user_data); +} + +/** + * g_async_queue_push_sorted_unlocked: + * @queue: a #GAsyncQueue + * @data: the @data to push into the @queue + * @func: the #GCompareDataFunc is used to sort @queue + * @user_data: user data passed to @func. + * + * Inserts @data into @queue using @func to determine the new + * position. + * + * The sort function @func is passed two elements of the @queue. + * It should return 0 if they are equal, a negative value if the + * first element should be higher in the @queue or a positive value + * if the first element should be lower in the @queue than the second + * element. + * + * This function requires that the @queue is sorted before pushing on + * new elements, see g_async_queue_sort(). + * + * This function must be called while holding the @queue's lock. + * + * For an example of @func see g_async_queue_sort(). + * + * Since: 2.10 + */ +void +g_async_queue_push_sorted_unlocked (GAsyncQueue *queue, + gpointer data, + GCompareDataFunc func, + gpointer user_data) +{ + SortData sd; + + g_return_if_fail (queue != NULL); + + sd.func = func; + sd.user_data = user_data; + + g_queue_insert_sorted (&queue->queue, + data, + (GCompareDataFunc)g_async_queue_invert_compare, + &sd); + if (queue->waiting_threads > 0) + g_cond_signal (&queue->cond); +} + +static gpointer +g_async_queue_pop_intern_unlocked (GAsyncQueue *queue, + gboolean wait, + gint64 end_time) +{ + gpointer retval; + + if (!g_queue_peek_tail_link (&queue->queue) && wait) + { + queue->waiting_threads++; + while (!g_queue_peek_tail_link (&queue->queue)) + { + if (end_time == -1) + g_cond_wait (&queue->cond, &queue->mutex); + else + { + if (!g_cond_wait_until (&queue->cond, &queue->mutex, end_time)) + break; + } + } + queue->waiting_threads--; + } + + retval = g_queue_pop_tail (&queue->queue); + + g_assert (retval || !wait || end_time > 0); + + return retval; +} + +/** + * g_async_queue_pop: + * @queue: a #GAsyncQueue + * + * Pops data from the @queue. If @queue is empty, this function + * blocks until data becomes available. + * + * Returns: data from the queue + */ +gpointer +g_async_queue_pop (GAsyncQueue *queue) +{ + gpointer retval; + + g_return_val_if_fail (queue, NULL); + + g_mutex_lock (&queue->mutex); + retval = g_async_queue_pop_intern_unlocked (queue, TRUE, -1); + g_mutex_unlock (&queue->mutex); + + return retval; +} + +/** + * g_async_queue_pop_unlocked: + * @queue: a #GAsyncQueue + * + * Pops data from the @queue. If @queue is empty, this function + * blocks until data becomes available. + * + * This function must be called while holding the @queue's lock. + * + * Returns: data from the queue. + */ +gpointer +g_async_queue_pop_unlocked (GAsyncQueue *queue) +{ + g_return_val_if_fail (queue, NULL); + + return g_async_queue_pop_intern_unlocked (queue, TRUE, -1); +} + +/** + * g_async_queue_try_pop: + * @queue: a #GAsyncQueue + * + * Tries to pop data from the @queue. If no data is available, + * %NULL is returned. + * + * Returns: (nullable): data from the queue or %NULL, when no data is + * available immediately. + */ +gpointer +g_async_queue_try_pop (GAsyncQueue *queue) +{ + gpointer retval; + + g_return_val_if_fail (queue, NULL); + + g_mutex_lock (&queue->mutex); + retval = g_async_queue_pop_intern_unlocked (queue, FALSE, -1); + g_mutex_unlock (&queue->mutex); + + return retval; +} + +/** + * g_async_queue_try_pop_unlocked: + * @queue: a #GAsyncQueue + * + * Tries to pop data from the @queue. If no data is available, + * %NULL is returned. + * + * This function must be called while holding the @queue's lock. + * + * Returns: (nullable): data from the queue or %NULL, when no data is + * available immediately. + */ +gpointer +g_async_queue_try_pop_unlocked (GAsyncQueue *queue) +{ + g_return_val_if_fail (queue, NULL); + + return g_async_queue_pop_intern_unlocked (queue, FALSE, -1); +} + +/** + * g_async_queue_timeout_pop: + * @queue: a #GAsyncQueue + * @timeout: the number of microseconds to wait + * + * Pops data from the @queue. If the queue is empty, blocks for + * @timeout microseconds, or until data becomes available. + * + * If no data is received before the timeout, %NULL is returned. + * + * Returns: (nullable): data from the queue or %NULL, when no data is + * received before the timeout. + */ +gpointer +g_async_queue_timeout_pop (GAsyncQueue *queue, + guint64 timeout) +{ + gint64 end_time = g_get_monotonic_time () + timeout; + gpointer retval; + + g_return_val_if_fail (queue != NULL, NULL); + + g_mutex_lock (&queue->mutex); + retval = g_async_queue_pop_intern_unlocked (queue, TRUE, end_time); + g_mutex_unlock (&queue->mutex); + + return retval; +} + +/** + * g_async_queue_timeout_pop_unlocked: + * @queue: a #GAsyncQueue + * @timeout: the number of microseconds to wait + * + * Pops data from the @queue. If the queue is empty, blocks for + * @timeout microseconds, or until data becomes available. + * + * If no data is received before the timeout, %NULL is returned. + * + * This function must be called while holding the @queue's lock. + * + * Returns: (nullable): data from the queue or %NULL, when no data is + * received before the timeout. + */ +gpointer +g_async_queue_timeout_pop_unlocked (GAsyncQueue *queue, + guint64 timeout) +{ + gint64 end_time = g_get_monotonic_time () + timeout; + + g_return_val_if_fail (queue != NULL, NULL); + + return g_async_queue_pop_intern_unlocked (queue, TRUE, end_time); +} + +/** + * g_async_queue_timed_pop: + * @queue: a #GAsyncQueue + * @end_time: a #GTimeVal, determining the final time + * + * Pops data from the @queue. If the queue is empty, blocks until + * @end_time or until data becomes available. + * + * If no data is received before @end_time, %NULL is returned. + * + * To easily calculate @end_time, a combination of g_get_real_time() + * and g_time_val_add() can be used. + * + * Returns: (nullable): data from the queue or %NULL, when no data is + * received before @end_time. + * + * Deprecated: use g_async_queue_timeout_pop(). + */ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +gpointer +g_async_queue_timed_pop (GAsyncQueue *queue, + GTimeVal *end_time) +{ + gint64 m_end_time; + gpointer retval; + + g_return_val_if_fail (queue, NULL); + + if (end_time != NULL) + { + m_end_time = g_get_monotonic_time () + + ((gint64) end_time->tv_sec * G_USEC_PER_SEC + end_time->tv_usec - g_get_real_time ()); + } + else + m_end_time = -1; + + g_mutex_lock (&queue->mutex); + retval = g_async_queue_pop_intern_unlocked (queue, TRUE, m_end_time); + g_mutex_unlock (&queue->mutex); + + return retval; +} +G_GNUC_END_IGNORE_DEPRECATIONS + +/** + * g_async_queue_timed_pop_unlocked: + * @queue: a #GAsyncQueue + * @end_time: a #GTimeVal, determining the final time + * + * Pops data from the @queue. If the queue is empty, blocks until + * @end_time or until data becomes available. + * + * If no data is received before @end_time, %NULL is returned. + * + * To easily calculate @end_time, a combination of g_get_real_time() + * and g_time_val_add() can be used. + * + * This function must be called while holding the @queue's lock. + * + * Returns: (nullable): data from the queue or %NULL, when no data is + * received before @end_time. + * + * Deprecated: use g_async_queue_timeout_pop_unlocked(). + */ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +gpointer +g_async_queue_timed_pop_unlocked (GAsyncQueue *queue, + GTimeVal *end_time) +{ + gint64 m_end_time; + + g_return_val_if_fail (queue, NULL); + + if (end_time != NULL) + { + m_end_time = g_get_monotonic_time () + + ((gint64) end_time->tv_sec * G_USEC_PER_SEC + end_time->tv_usec - g_get_real_time ()); + } + else + m_end_time = -1; + + return g_async_queue_pop_intern_unlocked (queue, TRUE, m_end_time); +} +G_GNUC_END_IGNORE_DEPRECATIONS + +/** + * g_async_queue_length: + * @queue: a #GAsyncQueue. + * + * Returns the length of the queue. + * + * Actually this function returns the number of data items in + * the queue minus the number of waiting threads, so a negative + * value means waiting threads, and a positive value means available + * entries in the @queue. A return value of 0 could mean n entries + * in the queue and n threads waiting. This can happen due to locking + * of the queue or due to scheduling. + * + * Returns: the length of the @queue + */ +gint +g_async_queue_length (GAsyncQueue *queue) +{ + gint retval; + + g_return_val_if_fail (queue, 0); + + g_mutex_lock (&queue->mutex); + retval = queue->queue.length - queue->waiting_threads; + g_mutex_unlock (&queue->mutex); + + return retval; +} + +/** + * g_async_queue_length_unlocked: + * @queue: a #GAsyncQueue + * + * Returns the length of the queue. + * + * Actually this function returns the number of data items in + * the queue minus the number of waiting threads, so a negative + * value means waiting threads, and a positive value means available + * entries in the @queue. A return value of 0 could mean n entries + * in the queue and n threads waiting. This can happen due to locking + * of the queue or due to scheduling. + * + * This function must be called while holding the @queue's lock. + * + * Returns: the length of the @queue. + */ +gint +g_async_queue_length_unlocked (GAsyncQueue *queue) +{ + g_return_val_if_fail (queue, 0); + + return queue->queue.length - queue->waiting_threads; +} + +/** + * g_async_queue_sort: + * @queue: a #GAsyncQueue + * @func: the #GCompareDataFunc is used to sort @queue + * @user_data: user data passed to @func + * + * Sorts @queue using @func. + * + * The sort function @func is passed two elements of the @queue. + * It should return 0 if they are equal, a negative value if the + * first element should be higher in the @queue or a positive value + * if the first element should be lower in the @queue than the second + * element. + * + * This function will lock @queue before it sorts the queue and unlock + * it when it is finished. + * + * If you were sorting a list of priority numbers to make sure the + * lowest priority would be at the top of the queue, you could use: + * |[ + * gint32 id1; + * gint32 id2; + * + * id1 = GPOINTER_TO_INT (element1); + * id2 = GPOINTER_TO_INT (element2); + * + * return (id1 > id2 ? +1 : id1 == id2 ? 0 : -1); + * ]| + * + * Since: 2.10 + */ +void +g_async_queue_sort (GAsyncQueue *queue, + GCompareDataFunc func, + gpointer user_data) +{ + g_return_if_fail (queue != NULL); + g_return_if_fail (func != NULL); + + g_mutex_lock (&queue->mutex); + g_async_queue_sort_unlocked (queue, func, user_data); + g_mutex_unlock (&queue->mutex); +} + +/** + * g_async_queue_sort_unlocked: + * @queue: a #GAsyncQueue + * @func: the #GCompareDataFunc is used to sort @queue + * @user_data: user data passed to @func + * + * Sorts @queue using @func. + * + * The sort function @func is passed two elements of the @queue. + * It should return 0 if they are equal, a negative value if the + * first element should be higher in the @queue or a positive value + * if the first element should be lower in the @queue than the second + * element. + * + * This function must be called while holding the @queue's lock. + * + * Since: 2.10 + */ +void +g_async_queue_sort_unlocked (GAsyncQueue *queue, + GCompareDataFunc func, + gpointer user_data) +{ + SortData sd; + + g_return_if_fail (queue != NULL); + g_return_if_fail (func != NULL); + + sd.func = func; + sd.user_data = user_data; + + g_queue_sort (&queue->queue, + (GCompareDataFunc)g_async_queue_invert_compare, + &sd); +} + +/** + * g_async_queue_remove: + * @queue: a #GAsyncQueue + * @item: the data to remove from the @queue + * + * Remove an item from the queue. + * + * Returns: %TRUE if the item was removed + * + * Since: 2.46 + */ +gboolean +g_async_queue_remove (GAsyncQueue *queue, + gpointer item) +{ + gboolean ret; + + g_return_val_if_fail (queue != NULL, FALSE); + g_return_val_if_fail (item != NULL, FALSE); + + g_mutex_lock (&queue->mutex); + ret = g_async_queue_remove_unlocked (queue, item); + g_mutex_unlock (&queue->mutex); + + return ret; +} + +/** + * g_async_queue_remove_unlocked: + * @queue: a #GAsyncQueue + * @item: the data to remove from the @queue + * + * Remove an item from the queue. + * + * This function must be called while holding the @queue's lock. + * + * Returns: %TRUE if the item was removed + * + * Since: 2.46 + */ +gboolean +g_async_queue_remove_unlocked (GAsyncQueue *queue, + gpointer item) +{ + g_return_val_if_fail (queue != NULL, FALSE); + g_return_val_if_fail (item != NULL, FALSE); + + return g_queue_remove (&queue->queue, item); +} + +/** + * g_async_queue_push_front: + * @queue: a #GAsyncQueue + * @item: data to push into the @queue + * + * Pushes the @item into the @queue. @item must not be %NULL. + * In contrast to g_async_queue_push(), this function + * pushes the new item ahead of the items already in the queue, + * so that it will be the next one to be popped off the queue. + * + * Since: 2.46 + */ +void +g_async_queue_push_front (GAsyncQueue *queue, + gpointer item) +{ + g_return_if_fail (queue != NULL); + g_return_if_fail (item != NULL); + + g_mutex_lock (&queue->mutex); + g_async_queue_push_front_unlocked (queue, item); + g_mutex_unlock (&queue->mutex); +} + +/** + * g_async_queue_push_front_unlocked: + * @queue: a #GAsyncQueue + * @item: data to push into the @queue + * + * Pushes the @item into the @queue. @item must not be %NULL. + * In contrast to g_async_queue_push_unlocked(), this function + * pushes the new item ahead of the items already in the queue, + * so that it will be the next one to be popped off the queue. + * + * This function must be called while holding the @queue's lock. + * + * Since: 2.46 + */ +void +g_async_queue_push_front_unlocked (GAsyncQueue *queue, + gpointer item) +{ + g_return_if_fail (queue != NULL); + g_return_if_fail (item != NULL); + + g_queue_push_tail (&queue->queue, item); + if (queue->waiting_threads > 0) + g_cond_signal (&queue->cond); +} + +/* + * Private API + */ + +GMutex * +_g_async_queue_get_mutex (GAsyncQueue *queue) +{ + g_return_val_if_fail (queue, NULL); + + return &queue->mutex; +} diff --git a/glib/gasyncqueue.h b/glib/gasyncqueue.h new file mode 100644 index 0000000..73e537b --- /dev/null +++ b/glib/gasyncqueue.h @@ -0,0 +1,124 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_ASYNCQUEUE_H__ +#define __G_ASYNCQUEUE_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +typedef struct _GAsyncQueue GAsyncQueue; + +GLIB_AVAILABLE_IN_ALL +GAsyncQueue *g_async_queue_new (void); +GLIB_AVAILABLE_IN_ALL +GAsyncQueue *g_async_queue_new_full (GDestroyNotify item_free_func); +GLIB_AVAILABLE_IN_ALL +void g_async_queue_lock (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +void g_async_queue_unlock (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +GAsyncQueue *g_async_queue_ref (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +void g_async_queue_unref (GAsyncQueue *queue); + +GLIB_DEPRECATED_FOR(g_async_queue_ref) +void g_async_queue_ref_unlocked (GAsyncQueue *queue); + +GLIB_DEPRECATED_FOR(g_async_queue_unref) +void g_async_queue_unref_and_unlock (GAsyncQueue *queue); + +GLIB_AVAILABLE_IN_ALL +void g_async_queue_push (GAsyncQueue *queue, + gpointer data); +GLIB_AVAILABLE_IN_ALL +void g_async_queue_push_unlocked (GAsyncQueue *queue, + gpointer data); +GLIB_AVAILABLE_IN_ALL +void g_async_queue_push_sorted (GAsyncQueue *queue, + gpointer data, + GCompareDataFunc func, + gpointer user_data); +GLIB_AVAILABLE_IN_ALL +void g_async_queue_push_sorted_unlocked (GAsyncQueue *queue, + gpointer data, + GCompareDataFunc func, + gpointer user_data); +GLIB_AVAILABLE_IN_ALL +gpointer g_async_queue_pop (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +gpointer g_async_queue_pop_unlocked (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +gpointer g_async_queue_try_pop (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +gpointer g_async_queue_try_pop_unlocked (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +gpointer g_async_queue_timeout_pop (GAsyncQueue *queue, + guint64 timeout); +GLIB_AVAILABLE_IN_ALL +gpointer g_async_queue_timeout_pop_unlocked (GAsyncQueue *queue, + guint64 timeout); +GLIB_AVAILABLE_IN_ALL +gint g_async_queue_length (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +gint g_async_queue_length_unlocked (GAsyncQueue *queue); +GLIB_AVAILABLE_IN_ALL +void g_async_queue_sort (GAsyncQueue *queue, + GCompareDataFunc func, + gpointer user_data); +GLIB_AVAILABLE_IN_ALL +void g_async_queue_sort_unlocked (GAsyncQueue *queue, + GCompareDataFunc func, + gpointer user_data); + +GLIB_AVAILABLE_IN_2_46 +gboolean g_async_queue_remove (GAsyncQueue *queue, + gpointer item); +GLIB_AVAILABLE_IN_2_46 +gboolean g_async_queue_remove_unlocked (GAsyncQueue *queue, + gpointer item); +GLIB_AVAILABLE_IN_2_46 +void g_async_queue_push_front (GAsyncQueue *queue, + gpointer item); +GLIB_AVAILABLE_IN_2_46 +void g_async_queue_push_front_unlocked (GAsyncQueue *queue, + gpointer item); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +GLIB_DEPRECATED_FOR(g_async_queue_timeout_pop) +gpointer g_async_queue_timed_pop (GAsyncQueue *queue, + GTimeVal *end_time); +GLIB_DEPRECATED_FOR(g_async_queue_timeout_pop_unlocked) +gpointer g_async_queue_timed_pop_unlocked (GAsyncQueue *queue, + GTimeVal *end_time); +G_GNUC_END_IGNORE_DEPRECATIONS + +G_END_DECLS + +#endif /* __G_ASYNCQUEUE_H__ */ diff --git a/glib/gasyncqueueprivate.h b/glib/gasyncqueueprivate.h new file mode 100644 index 0000000..b1622c5 --- /dev/null +++ b/glib/gasyncqueueprivate.h @@ -0,0 +1,29 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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_ASYNCQUEUEPRIVATE_H__ +#define __G_ASYNCQUEUEPRIVATE_H__ + +#include "gasyncqueue.h" + +G_BEGIN_DECLS + +GMutex *_g_async_queue_get_mutex (GAsyncQueue *queue); + +G_END_DECLS + +#endif /* __G_ASYNCQUEUEPRIVATE_H__ */ diff --git a/glib/gatomic.c b/glib/gatomic.c new file mode 100644 index 0000000..0bc67aa --- /dev/null +++ b/glib/gatomic.c @@ -0,0 +1,967 @@ +/* + * Copyright © 2011 Ryan Lortie + * + * 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: Ryan Lortie + */ + +#include "config.h" + +#include "gatomic.h" + +/** + * SECTION:atomic_operations + * @title: Atomic Operations + * @short_description: basic atomic integer and pointer operations + * @see_also: #GMutex + * + * The following is a collection of compiler macros to provide atomic + * access to integer and pointer-sized values. + * + * The macros that have 'int' in the name will operate on pointers to + * #gint and #guint. The macros with 'pointer' in the name will operate + * on pointers to any pointer-sized value, including #gsize. There is + * no support for 64bit operations on platforms with 32bit pointers + * because it is not generally possible to perform these operations + * atomically. + * + * The get, set and exchange operations for integers and pointers + * nominally operate on #gint and #gpointer, respectively. Of the + * arithmetic operations, the 'add' operation operates on (and returns) + * signed integer values (#gint and #gssize) and the 'and', 'or', and + * 'xor' operations operate on (and return) unsigned integer values + * (#guint and #gsize). + * + * All of the operations act as a full compiler and (where appropriate) + * hardware memory barrier. Acquire and release or producer and + * consumer barrier semantics are not available through this API. + * + * It is very important that all accesses to a particular integer or + * pointer be performed using only this API and that different sizes of + * operation are not mixed or used on overlapping memory regions. Never + * read or assign directly from or to a value -- always use this API. + * + * For simple reference counting purposes you should use + * g_atomic_int_inc() and g_atomic_int_dec_and_test(). Other uses that + * fall outside of simple reference counting patterns are prone to + * subtle bugs and occasionally undefined behaviour. It is also worth + * noting that since all of these operations require global + * synchronisation of the entire machine, they can be quite slow. In + * the case of performing multiple atomic operations it can often be + * faster to simply acquire a mutex lock around the critical area, + * perform the operations normally and then release the lock. + **/ + +/** + * G_ATOMIC_LOCK_FREE: + * + * This macro is defined if the atomic operations of GLib are + * implemented using real hardware atomic operations. This means that + * the GLib atomic API can be used between processes and safely mixed + * with other (hardware) atomic APIs. + * + * If this macro is not defined, the atomic operations may be + * emulated using a mutex. In that case, the GLib atomic operations are + * only atomic relative to themselves and within a single process. + **/ + +/* NOTE CAREFULLY: + * + * This file is the lowest-level part of GLib. + * + * Other lowlevel parts of GLib (threads, slice allocator, g_malloc, + * messages, etc) call into these functions and macros to get work done. + * + * As such, these functions can not call back into any part of GLib + * without risking recursion. + */ + +#ifdef G_ATOMIC_LOCK_FREE + +/* if G_ATOMIC_LOCK_FREE was defined by `meson configure` then we MUST + * implement the atomic operations in a lock-free manner. + */ + +#if defined (__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + +/** + * g_atomic_int_get: + * @atomic: a pointer to a #gint or #guint + * + * Gets the current value of @atomic. + * + * This call acts as a full compiler and hardware + * memory barrier (before the get). + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of the integer + * + * Since: 2.4 + **/ +gint +(g_atomic_int_get) (const volatile gint *atomic) +{ + return g_atomic_int_get (atomic); +} + +/** + * g_atomic_int_set: + * @atomic: a pointer to a #gint or #guint + * @newval: a new value to store + * + * Sets the value of @atomic to @newval. + * + * This call acts as a full compiler and hardware + * memory barrier (after the set). + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Since: 2.4 + */ +void +(g_atomic_int_set) (volatile gint *atomic, + gint newval) +{ + g_atomic_int_set (atomic, newval); +} + +/** + * g_atomic_int_inc: + * @atomic: a pointer to a #gint or #guint + * + * Increments the value of @atomic by 1. + * + * Think of this operation as an atomic version of `{ *atomic += 1; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Since: 2.4 + **/ +void +(g_atomic_int_inc) (volatile gint *atomic) +{ + g_atomic_int_inc (atomic); +} + +/** + * g_atomic_int_dec_and_test: + * @atomic: a pointer to a #gint or #guint + * + * Decrements the value of @atomic by 1. + * + * Think of this operation as an atomic version of + * `{ *atomic -= 1; return (*atomic == 0); }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: %TRUE if the resultant value is zero + * + * Since: 2.4 + **/ +gboolean +(g_atomic_int_dec_and_test) (volatile gint *atomic) +{ + return g_atomic_int_dec_and_test (atomic); +} + +/** + * g_atomic_int_compare_and_exchange: + * @atomic: a pointer to a #gint or #guint + * @oldval: the value to compare with + * @newval: the value to conditionally replace with + * + * Compares @atomic to @oldval and, if equal, sets it to @newval. + * If @atomic was not equal to @oldval then no change occurs. + * + * This compare and exchange is done atomically. + * + * Think of this operation as an atomic version of + * `{ if (*atomic == oldval) { *atomic = newval; return TRUE; } else return FALSE; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: %TRUE if the exchange took place + * + * Since: 2.4 + **/ +gboolean +(g_atomic_int_compare_and_exchange) (volatile gint *atomic, + gint oldval, + gint newval) +{ + return g_atomic_int_compare_and_exchange (atomic, oldval, newval); +} + +/** + * g_atomic_int_add: + * @atomic: a pointer to a #gint or #guint + * @val: the value to add + * + * Atomically adds @val to the value of @atomic. + * + * Think of this operation as an atomic version of + * `{ tmp = *atomic; *atomic += val; return tmp; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * Before version 2.30, this function did not return a value + * (but g_atomic_int_exchange_and_add() did, and had the same meaning). + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of @atomic before the add, signed + * + * Since: 2.4 + **/ +gint +(g_atomic_int_add) (volatile gint *atomic, + gint val) +{ + return g_atomic_int_add (atomic, val); +} + +/** + * g_atomic_int_and: + * @atomic: a pointer to a #gint or #guint + * @val: the value to 'and' + * + * Performs an atomic bitwise 'and' of the value of @atomic and @val, + * storing the result back in @atomic. + * + * This call acts as a full compiler and hardware memory barrier. + * + * Think of this operation as an atomic version of + * `{ tmp = *atomic; *atomic &= val; return tmp; }`. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of @atomic before the operation, unsigned + * + * Since: 2.30 + **/ +guint +(g_atomic_int_and) (volatile guint *atomic, + guint val) +{ + return g_atomic_int_and (atomic, val); +} + +/** + * g_atomic_int_or: + * @atomic: a pointer to a #gint or #guint + * @val: the value to 'or' + * + * Performs an atomic bitwise 'or' of the value of @atomic and @val, + * storing the result back in @atomic. + * + * Think of this operation as an atomic version of + * `{ tmp = *atomic; *atomic |= val; return tmp; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of @atomic before the operation, unsigned + * + * Since: 2.30 + **/ +guint +(g_atomic_int_or) (volatile guint *atomic, + guint val) +{ + return g_atomic_int_or (atomic, val); +} + +/** + * g_atomic_int_xor: + * @atomic: a pointer to a #gint or #guint + * @val: the value to 'xor' + * + * Performs an atomic bitwise 'xor' of the value of @atomic and @val, + * storing the result back in @atomic. + * + * Think of this operation as an atomic version of + * `{ tmp = *atomic; *atomic ^= val; return tmp; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of @atomic before the operation, unsigned + * + * Since: 2.30 + **/ +guint +(g_atomic_int_xor) (volatile guint *atomic, + guint val) +{ + return g_atomic_int_xor (atomic, val); +} + + +/** + * g_atomic_pointer_get: + * @atomic: (not nullable): a pointer to a #gpointer-sized value + * + * Gets the current value of @atomic. + * + * This call acts as a full compiler and hardware + * memory barrier (before the get). + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of the pointer + * + * Since: 2.4 + **/ +gpointer +(g_atomic_pointer_get) (const volatile void *atomic) +{ + return g_atomic_pointer_get ((gpointer *) atomic); +} + +/** + * g_atomic_pointer_set: + * @atomic: (not nullable): a pointer to a #gpointer-sized value + * @newval: a new value to store + * + * Sets the value of @atomic to @newval. + * + * This call acts as a full compiler and hardware + * memory barrier (after the set). + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Since: 2.4 + **/ +void +(g_atomic_pointer_set) (volatile void *atomic, + gpointer newval) +{ + g_atomic_pointer_set ((gpointer *) atomic, newval); +} + +/** + * g_atomic_pointer_compare_and_exchange: + * @atomic: (not nullable): a pointer to a #gpointer-sized value + * @oldval: the value to compare with + * @newval: the value to conditionally replace with + * + * Compares @atomic to @oldval and, if equal, sets it to @newval. + * If @atomic was not equal to @oldval then no change occurs. + * + * This compare and exchange is done atomically. + * + * Think of this operation as an atomic version of + * `{ if (*atomic == oldval) { *atomic = newval; return TRUE; } else return FALSE; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: %TRUE if the exchange took place + * + * Since: 2.4 + **/ +gboolean +(g_atomic_pointer_compare_and_exchange) (volatile void *atomic, + gpointer oldval, + gpointer newval) +{ + return g_atomic_pointer_compare_and_exchange ((gpointer *) atomic, + oldval, newval); +} + +/** + * g_atomic_pointer_add: + * @atomic: (not nullable): a pointer to a #gpointer-sized value + * @val: the value to add + * + * Atomically adds @val to the value of @atomic. + * + * Think of this operation as an atomic version of + * `{ tmp = *atomic; *atomic += val; return tmp; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of @atomic before the add, signed + * + * Since: 2.30 + **/ +gssize +(g_atomic_pointer_add) (volatile void *atomic, + gssize val) +{ + return g_atomic_pointer_add ((gpointer *) atomic, val); +} + +/** + * g_atomic_pointer_and: + * @atomic: (not nullable): a pointer to a #gpointer-sized value + * @val: the value to 'and' + * + * Performs an atomic bitwise 'and' of the value of @atomic and @val, + * storing the result back in @atomic. + * + * Think of this operation as an atomic version of + * `{ tmp = *atomic; *atomic &= val; return tmp; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of @atomic before the operation, unsigned + * + * Since: 2.30 + **/ +gsize +(g_atomic_pointer_and) (volatile void *atomic, + gsize val) +{ + return g_atomic_pointer_and ((gpointer *) atomic, val); +} + +/** + * g_atomic_pointer_or: + * @atomic: (not nullable): a pointer to a #gpointer-sized value + * @val: the value to 'or' + * + * Performs an atomic bitwise 'or' of the value of @atomic and @val, + * storing the result back in @atomic. + * + * Think of this operation as an atomic version of + * `{ tmp = *atomic; *atomic |= val; return tmp; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of @atomic before the operation, unsigned + * + * Since: 2.30 + **/ +gsize +(g_atomic_pointer_or) (volatile void *atomic, + gsize val) +{ + return g_atomic_pointer_or ((gpointer *) atomic, val); +} + +/** + * g_atomic_pointer_xor: + * @atomic: (not nullable): a pointer to a #gpointer-sized value + * @val: the value to 'xor' + * + * Performs an atomic bitwise 'xor' of the value of @atomic and @val, + * storing the result back in @atomic. + * + * Think of this operation as an atomic version of + * `{ tmp = *atomic; *atomic ^= val; return tmp; }`. + * + * This call acts as a full compiler and hardware memory barrier. + * + * While @atomic has a `volatile` qualifier, this is a historical artifact and + * the pointer passed to it should not be `volatile`. + * + * Returns: the value of @atomic before the operation, unsigned + * + * Since: 2.30 + **/ +gsize +(g_atomic_pointer_xor) (volatile void *atomic, + gsize val) +{ + return g_atomic_pointer_xor ((gpointer *) atomic, val); +} + +#elif defined (G_PLATFORM_WIN32) + +#include +#if !defined(_M_AMD64) && !defined (_M_IA64) && !defined(_M_X64) && !(defined _MSC_VER && _MSC_VER <= 1200) +#define InterlockedAnd _InterlockedAnd +#define InterlockedOr _InterlockedOr +#define InterlockedXor _InterlockedXor +#endif + +#if !defined (_MSC_VER) || _MSC_VER <= 1200 +#include "gmessages.h" +/* Inlined versions for older compiler */ +static LONG +_gInterlockedAnd (volatile guint *atomic, + guint val) +{ + LONG i, j; + + j = *atomic; + do { + i = j; + j = InterlockedCompareExchange(atomic, i & val, i); + } while (i != j); + + return j; +} +#define InterlockedAnd(a,b) _gInterlockedAnd(a,b) +static LONG +_gInterlockedOr (volatile guint *atomic, + guint val) +{ + LONG i, j; + + j = *atomic; + do { + i = j; + j = InterlockedCompareExchange(atomic, i | val, i); + } while (i != j); + + return j; +} +#define InterlockedOr(a,b) _gInterlockedOr(a,b) +static LONG +_gInterlockedXor (volatile guint *atomic, + guint val) +{ + LONG i, j; + + j = *atomic; + do { + i = j; + j = InterlockedCompareExchange(atomic, i ^ val, i); + } while (i != j); + + return j; +} +#define InterlockedXor(a,b) _gInterlockedXor(a,b) +#endif + +/* + * http://msdn.microsoft.com/en-us/library/ms684122(v=vs.85).aspx + */ +gint +(g_atomic_int_get) (const volatile gint *atomic) +{ + MemoryBarrier (); + return *atomic; +} + +void +(g_atomic_int_set) (volatile gint *atomic, + gint newval) +{ + *atomic = newval; + MemoryBarrier (); +} + +void +(g_atomic_int_inc) (volatile gint *atomic) +{ + InterlockedIncrement (atomic); +} + +gboolean +(g_atomic_int_dec_and_test) (volatile gint *atomic) +{ + return InterlockedDecrement (atomic) == 0; +} + +gboolean +(g_atomic_int_compare_and_exchange) (volatile gint *atomic, + gint oldval, + gint newval) +{ + return InterlockedCompareExchange (atomic, newval, oldval) == oldval; +} + +gint +(g_atomic_int_add) (volatile gint *atomic, + gint val) +{ + return InterlockedExchangeAdd (atomic, val); +} + +guint +(g_atomic_int_and) (volatile guint *atomic, + guint val) +{ + return InterlockedAnd (atomic, val); +} + +guint +(g_atomic_int_or) (volatile guint *atomic, + guint val) +{ + return InterlockedOr (atomic, val); +} + +guint +(g_atomic_int_xor) (volatile guint *atomic, + guint val) +{ + return InterlockedXor (atomic, val); +} + + +gpointer +(g_atomic_pointer_get) (const volatile void *atomic) +{ + const gpointer *ptr = atomic; + + MemoryBarrier (); + return *ptr; +} + +void +(g_atomic_pointer_set) (volatile void *atomic, + gpointer newval) +{ + gpointer *ptr = atomic; + + *ptr = newval; + MemoryBarrier (); +} + +gboolean +(g_atomic_pointer_compare_and_exchange) (volatile void *atomic, + gpointer oldval, + gpointer newval) +{ + return InterlockedCompareExchangePointer (atomic, newval, oldval) == oldval; +} + +gssize +(g_atomic_pointer_add) (volatile void *atomic, + gssize val) +{ +#if GLIB_SIZEOF_VOID_P == 8 + return InterlockedExchangeAdd64 (atomic, val); +#else + return InterlockedExchangeAdd (atomic, val); +#endif +} + +gsize +(g_atomic_pointer_and) (volatile void *atomic, + gsize val) +{ +#if GLIB_SIZEOF_VOID_P == 8 + return InterlockedAnd64 (atomic, val); +#else + return InterlockedAnd (atomic, val); +#endif +} + +gsize +(g_atomic_pointer_or) (volatile void *atomic, + gsize val) +{ +#if GLIB_SIZEOF_VOID_P == 8 + return InterlockedOr64 (atomic, val); +#else + return InterlockedOr (atomic, val); +#endif +} + +gsize +(g_atomic_pointer_xor) (volatile void *atomic, + gsize val) +{ +#if GLIB_SIZEOF_VOID_P == 8 + return InterlockedXor64 (atomic, val); +#else + return InterlockedXor (atomic, val); +#endif +} +#else + +/* This error occurs when `meson configure` decided that we should be capable + * of lock-free atomics but we find at compile-time that we are not. + */ +#error G_ATOMIC_LOCK_FREE defined, but incapable of lock-free atomics. + +#endif /* defined (__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) */ + +#else /* G_ATOMIC_LOCK_FREE */ + +/* We are not permitted to call into any GLib functions from here, so we + * can not use GMutex. + * + * Fortunately, we already take care of the Windows case above, and all + * non-Windows platforms on which glib runs have pthreads. Use those. + */ +#include + +static pthread_mutex_t g_atomic_lock = PTHREAD_MUTEX_INITIALIZER; + +gint +(g_atomic_int_get) (const volatile gint *atomic) +{ + gint value; + + pthread_mutex_lock (&g_atomic_lock); + value = *atomic; + pthread_mutex_unlock (&g_atomic_lock); + + return value; +} + +void +(g_atomic_int_set) (volatile gint *atomic, + gint value) +{ + pthread_mutex_lock (&g_atomic_lock); + *atomic = value; + pthread_mutex_unlock (&g_atomic_lock); +} + +void +(g_atomic_int_inc) (volatile gint *atomic) +{ + pthread_mutex_lock (&g_atomic_lock); + (*atomic)++; + pthread_mutex_unlock (&g_atomic_lock); +} + +gboolean +(g_atomic_int_dec_and_test) (volatile gint *atomic) +{ + gboolean is_zero; + + pthread_mutex_lock (&g_atomic_lock); + is_zero = --(*atomic) == 0; + pthread_mutex_unlock (&g_atomic_lock); + + return is_zero; +} + +gboolean +(g_atomic_int_compare_and_exchange) (volatile gint *atomic, + gint oldval, + gint newval) +{ + gboolean success; + + pthread_mutex_lock (&g_atomic_lock); + + if ((success = (*atomic == oldval))) + *atomic = newval; + + pthread_mutex_unlock (&g_atomic_lock); + + return success; +} + +gint +(g_atomic_int_add) (volatile gint *atomic, + gint val) +{ + gint oldval; + + pthread_mutex_lock (&g_atomic_lock); + oldval = *atomic; + *atomic = oldval + val; + pthread_mutex_unlock (&g_atomic_lock); + + return oldval; +} + +guint +(g_atomic_int_and) (volatile guint *atomic, + guint val) +{ + guint oldval; + + pthread_mutex_lock (&g_atomic_lock); + oldval = *atomic; + *atomic = oldval & val; + pthread_mutex_unlock (&g_atomic_lock); + + return oldval; +} + +guint +(g_atomic_int_or) (volatile guint *atomic, + guint val) +{ + guint oldval; + + pthread_mutex_lock (&g_atomic_lock); + oldval = *atomic; + *atomic = oldval | val; + pthread_mutex_unlock (&g_atomic_lock); + + return oldval; +} + +guint +(g_atomic_int_xor) (volatile guint *atomic, + guint val) +{ + guint oldval; + + pthread_mutex_lock (&g_atomic_lock); + oldval = *atomic; + *atomic = oldval ^ val; + pthread_mutex_unlock (&g_atomic_lock); + + return oldval; +} + + +gpointer +(g_atomic_pointer_get) (const volatile void *atomic) +{ + const gpointer *ptr = atomic; + gpointer value; + + pthread_mutex_lock (&g_atomic_lock); + value = *ptr; + pthread_mutex_unlock (&g_atomic_lock); + + return value; +} + +void +(g_atomic_pointer_set) (volatile void *atomic, + gpointer newval) +{ + gpointer *ptr = atomic; + + pthread_mutex_lock (&g_atomic_lock); + *ptr = newval; + pthread_mutex_unlock (&g_atomic_lock); +} + +gboolean +(g_atomic_pointer_compare_and_exchange) (volatile void *atomic, + gpointer oldval, + gpointer newval) +{ + gpointer *ptr = atomic; + gboolean success; + + pthread_mutex_lock (&g_atomic_lock); + + if ((success = (*ptr == oldval))) + *ptr = newval; + + pthread_mutex_unlock (&g_atomic_lock); + + return success; +} + +gssize +(g_atomic_pointer_add) (volatile void *atomic, + gssize val) +{ + gssize *ptr = atomic; + gssize oldval; + + pthread_mutex_lock (&g_atomic_lock); + oldval = *ptr; + *ptr = oldval + val; + pthread_mutex_unlock (&g_atomic_lock); + + return oldval; +} + +gsize +(g_atomic_pointer_and) (volatile void *atomic, + gsize val) +{ + gsize *ptr = atomic; + gsize oldval; + + pthread_mutex_lock (&g_atomic_lock); + oldval = *ptr; + *ptr = oldval & val; + pthread_mutex_unlock (&g_atomic_lock); + + return oldval; +} + +gsize +(g_atomic_pointer_or) (volatile void *atomic, + gsize val) +{ + gsize *ptr = atomic; + gsize oldval; + + pthread_mutex_lock (&g_atomic_lock); + oldval = *ptr; + *ptr = oldval | val; + pthread_mutex_unlock (&g_atomic_lock); + + return oldval; +} + +gsize +(g_atomic_pointer_xor) (volatile void *atomic, + gsize val) +{ + gsize *ptr = atomic; + gsize oldval; + + pthread_mutex_lock (&g_atomic_lock); + oldval = *ptr; + *ptr = oldval ^ val; + pthread_mutex_unlock (&g_atomic_lock); + + return oldval; +} + +#endif + +/** + * g_atomic_int_exchange_and_add: + * @atomic: a pointer to a #gint + * @val: the value to add + * + * This function existed before g_atomic_int_add() returned the prior + * value of the integer (which it now does). It is retained only for + * compatibility reasons. Don't use this function in new code. + * + * Returns: the value of @atomic before the add, signed + * Since: 2.4 + * Deprecated: 2.30: Use g_atomic_int_add() instead. + **/ +gint +g_atomic_int_exchange_and_add (volatile gint *atomic, + gint val) +{ + return (g_atomic_int_add) ((gint *) atomic, val); +} diff --git a/glib/gatomic.h b/glib/gatomic.h new file mode 100644 index 0000000..7bb4443 --- /dev/null +++ b/glib/gatomic.h @@ -0,0 +1,476 @@ +/* + * Copyright © 2011 Ryan Lortie + * + * 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: Ryan Lortie + */ + +#ifndef __G_ATOMIC_H__ +#define __G_ATOMIC_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_ALL +gint g_atomic_int_get (const volatile gint *atomic); +GLIB_AVAILABLE_IN_ALL +void g_atomic_int_set (volatile gint *atomic, + gint newval); +GLIB_AVAILABLE_IN_ALL +void g_atomic_int_inc (volatile gint *atomic); +GLIB_AVAILABLE_IN_ALL +gboolean g_atomic_int_dec_and_test (volatile gint *atomic); +GLIB_AVAILABLE_IN_ALL +gboolean g_atomic_int_compare_and_exchange (volatile gint *atomic, + gint oldval, + gint newval); +GLIB_AVAILABLE_IN_ALL +gint g_atomic_int_add (volatile gint *atomic, + gint val); +GLIB_AVAILABLE_IN_2_30 +guint g_atomic_int_and (volatile guint *atomic, + guint val); +GLIB_AVAILABLE_IN_2_30 +guint g_atomic_int_or (volatile guint *atomic, + guint val); +GLIB_AVAILABLE_IN_ALL +guint g_atomic_int_xor (volatile guint *atomic, + guint val); + +GLIB_AVAILABLE_IN_ALL +gpointer g_atomic_pointer_get (const volatile void *atomic); +GLIB_AVAILABLE_IN_ALL +void g_atomic_pointer_set (volatile void *atomic, + gpointer newval); +GLIB_AVAILABLE_IN_ALL +gboolean g_atomic_pointer_compare_and_exchange (volatile void *atomic, + gpointer oldval, + gpointer newval); +GLIB_AVAILABLE_IN_ALL +gssize g_atomic_pointer_add (volatile void *atomic, + gssize val); +GLIB_AVAILABLE_IN_2_30 +gsize g_atomic_pointer_and (volatile void *atomic, + gsize val); +GLIB_AVAILABLE_IN_2_30 +gsize g_atomic_pointer_or (volatile void *atomic, + gsize val); +GLIB_AVAILABLE_IN_ALL +gsize g_atomic_pointer_xor (volatile void *atomic, + gsize val); + +GLIB_DEPRECATED_IN_2_30_FOR(g_atomic_int_add) +gint g_atomic_int_exchange_and_add (volatile gint *atomic, + gint val); + +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 */ +/* OHOS_GLIB_COMPATIBLE + * ohos.glib.compatible.001: glib 2.62.5 update 2.68.1 Incompatible with gstreamer 1.16.2 + * static volatile gsize _init_once = 0; // Conflicts with volatile, + * if (g_once_init_enter (&_init_once)) + * add "&& !defined(__clang__)" + */ +#if defined(__ATOMIC_SEQ_CST) && !defined(__clang__) + +#define g_atomic_int_get(atomic) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + gint gaig_temp; \ + (void) (0 ? *(atomic) ^ *(atomic) : 1); \ + __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 ((gint *)(atomic), &gais_temp, __ATOMIC_SEQ_CST); \ + })) + +#if defined(glib_typeof) +#define g_atomic_pointer_get(atomic) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + glib_typeof (*(atomic)) gapg_temp_newval; \ + glib_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)); \ + glib_typeof ((atomic)) gaps_temp_atomic = (atomic); \ + glib_typeof (*(atomic)) gaps_temp_newval = (newval); \ + (void) (0 ? (gpointer) * (atomic) : NULL); \ + __atomic_store (gaps_temp_atomic, &gaps_temp_newval, __ATOMIC_SEQ_CST); \ + })) +#else /* if !(defined(glib_typeof) */ +#define g_atomic_pointer_get(atomic) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + 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 (gaps_temp_atomic, &gaps_temp_newval, __ATOMIC_SEQ_CST); \ + })) +#endif /* if defined(glib_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; \ + })) +#if defined(glib_typeof) && defined(__cplusplus) && __cplusplus >= 201103L +/* See comments below about equivalent g_atomic_pointer_compare_and_exchange() + * shenanigans for type-safety when compiling in C++ mode. */ +#define g_atomic_int_compare_and_exchange(atomic, oldval, newval) \ + (G_GNUC_EXTENSION ({ \ + glib_typeof (*(atomic)) 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; \ + })) +#else /* if !(defined(glib_typeof) && defined(__cplusplus) && __cplusplus >= 201103L) */ +#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), (void *) (&(gaicae_oldval)), (newval), FALSE, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? TRUE : FALSE; \ + })) +#endif /* defined(glib_typeof) */ +#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); \ + })) + +#if defined(glib_typeof) && defined(__cplusplus) && __cplusplus >= 201103L +/* This is typesafe because we check we can assign oldval to the type of + * (*atomic). Unfortunately it can only be done in C++ because gcc/clang warn + * when atomic is volatile and not oldval, or when atomic is gsize* and oldval + * is NULL. Note that clang++ force us to be typesafe because it is an error if the 2nd + * argument of __atomic_compare_exchange_n() has a different type than the + * first. + * https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1919 + * https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1715#note_1024120. */ +#define g_atomic_pointer_compare_and_exchange(atomic, oldval, newval) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof (oldval) == sizeof (gpointer)); \ + glib_typeof (*(atomic)) 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; \ + })) +#else /* if !(defined(glib_typeof) && defined(__cplusplus) && __cplusplus >= 201103L) */ +#define g_atomic_pointer_compare_and_exchange(atomic, oldval, newval) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof (oldval) == sizeof (gpointer)); \ + gpointer gapcae_oldval = (gpointer)(oldval); \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + __atomic_compare_exchange_n ((atomic), (void *) (&(gapcae_oldval)), (newval), FALSE, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? TRUE : FALSE; \ + })) +#endif /* defined(glib_typeof) */ +#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 ({ \ + gsize *gapa_atomic = (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 ({ \ + gsize *gapo_atomic = (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 ({ \ + gsize *gapx_atomic = (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 (); \ + __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); \ + __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 (); \ + __asm__ __volatile__ ("" : : : "memory"); \ + gapg_result; \ + })) +#if defined(glib_typeof) +#define g_atomic_pointer_set(atomic, newval) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + __sync_synchronize (); \ + __asm__ __volatile__ ("" : : : "memory"); \ + *(atomic) = (glib_typeof (*(atomic))) (gsize) (newval); \ + })) +#else /* if !(defined(glib_typeof) */ +#define g_atomic_pointer_set(atomic, newval) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + __sync_synchronize (); \ + __asm__ __volatile__ ("" : : : "memory"); \ + *(atomic) = (gpointer) (gsize) (newval); \ + })) +#endif /* if defined(glib_typeof) */ + +#define g_atomic_int_inc(atomic) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ *(atomic) : 1); \ + (void) __sync_fetch_and_add ((atomic), 1); \ + })) +#define g_atomic_int_dec_and_test(atomic) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ *(atomic) : 1); \ + __sync_fetch_and_sub ((atomic), 1) == 1; \ + })) +#define g_atomic_int_compare_and_exchange(atomic, oldval, newval) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (newval) ^ (oldval) : 1); \ + __sync_bool_compare_and_swap ((atomic), (oldval), (newval)) ? 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) __sync_fetch_and_add ((atomic), (val)); \ + })) +#define g_atomic_int_and(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (val) : 1); \ + (guint) __sync_fetch_and_and ((atomic), (val)); \ + })) +#define g_atomic_int_or(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (val) : 1); \ + (guint) __sync_fetch_and_or ((atomic), (val)); \ + })) +#define g_atomic_int_xor(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \ + (void) (0 ? *(atomic) ^ (val) : 1); \ + (guint) __sync_fetch_and_xor ((atomic), (val)); \ + })) + +#define g_atomic_pointer_compare_and_exchange(atomic, oldval, newval) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + __sync_bool_compare_and_swap ((atomic), (oldval), (newval)) ? 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) __sync_fetch_and_add ((atomic), (val)); \ + })) +#define g_atomic_pointer_and(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + (void) (0 ? (val) ^ (val) : 1); \ + (gsize) __sync_fetch_and_and ((atomic), (val)); \ + })) +#define g_atomic_pointer_or(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + (void) (0 ? (val) ^ (val) : 1); \ + (gsize) __sync_fetch_and_or ((atomic), (val)); \ + })) +#define g_atomic_pointer_xor(atomic, val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gpointer)); \ + (void) (0 ? (gpointer) *(atomic) : NULL); \ + (void) (0 ? (val) ^ (val) : 1); \ + (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) \ + (g_atomic_int_get ((gint *) (atomic))) +#define g_atomic_int_set(atomic, newval) \ + (g_atomic_int_set ((gint *) (atomic), (gint) (newval))) +#define g_atomic_int_compare_and_exchange(atomic, oldval, newval) \ + (g_atomic_int_compare_and_exchange ((gint *) (atomic), (oldval), (newval))) +#define g_atomic_int_add(atomic, val) \ + (g_atomic_int_add ((gint *) (atomic), (val))) +#define g_atomic_int_and(atomic, val) \ + (g_atomic_int_and ((guint *) (atomic), (val))) +#define g_atomic_int_or(atomic, val) \ + (g_atomic_int_or ((guint *) (atomic), (val))) +#define g_atomic_int_xor(atomic, val) \ + (g_atomic_int_xor ((guint *) (atomic), (val))) +#define g_atomic_int_inc(atomic) \ + (g_atomic_int_inc ((gint *) (atomic))) +#define g_atomic_int_dec_and_test(atomic) \ + (g_atomic_int_dec_and_test ((gint *) (atomic))) + +#if defined(glib_typeof) + /* The (void *) cast in the middle *looks* redundant, because + * g_atomic_pointer_get returns void * already, but it's to silence + * -Werror=bad-function-cast when we're doing something like: + * guintptr a, b; ...; a = g_atomic_pointer_get (&b); + * which would otherwise be assigning the void * result of + * g_atomic_pointer_get directly to the pointer-sized but + * non-pointer-typed result. */ +#define g_atomic_pointer_get(atomic) \ + (glib_typeof (*(atomic))) (void *) ((g_atomic_pointer_get) ((void *) atomic)) +#else /* !(defined(glib_typeof) */ +#define g_atomic_pointer_get(atomic) \ + (g_atomic_pointer_get (atomic)) +#endif + +#define g_atomic_pointer_set(atomic, newval) \ + (g_atomic_pointer_set ((atomic), (gpointer) (newval))) + +#define g_atomic_pointer_compare_and_exchange(atomic, oldval, newval) \ + (g_atomic_pointer_compare_and_exchange ((atomic), (gpointer) (oldval), (gpointer) (newval))) +#define g_atomic_pointer_add(atomic, val) \ + (g_atomic_pointer_add ((atomic), (gssize) (val))) +#define g_atomic_pointer_and(atomic, val) \ + (g_atomic_pointer_and ((atomic), (gsize) (val))) +#define g_atomic_pointer_or(atomic, val) \ + (g_atomic_pointer_or ((atomic), (gsize) (val))) +#define g_atomic_pointer_xor(atomic, val) \ + (g_atomic_pointer_xor ((atomic), (gsize) (val))) + +#endif /* defined(G_ATOMIC_LOCK_FREE) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) */ + +#endif /* __G_ATOMIC_H__ */ diff --git a/glib/gbacktrace.c b/glib/gbacktrace.c new file mode 100644 index 0000000..77cef10 --- /dev/null +++ b/glib/gbacktrace.c @@ -0,0 +1,455 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * MT safe ; except for g_on_error_stack_trace, but who wants thread safety + * then + */ + +#include "config.h" +#include "glibconfig.h" + +#include +#include +#include +#include + +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include + +#include + +#ifdef G_OS_UNIX +#include +#include +#ifdef HAVE_SYS_SELECT_H +#include +#endif /* HAVE_SYS_SELECT_H */ +#endif + +#include + +#ifdef G_OS_WIN32 +# define STRICT /* Strict typing, please */ +# define _WIN32_WINDOWS 0x0401 /* to get IsDebuggerPresent */ +# include +# undef STRICT +#else +# include +#endif + +#include "gbacktrace.h" + +#include "gtypes.h" +#include "gmain.h" +#include "gprintfint.h" +#include "gunicode.h" +#include "gutils.h" + +#ifndef G_OS_WIN32 +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; + +/** + * g_on_error_query: + * @prg_name: the program name, needed by gdb for the "[S]tack trace" + * option. If @prg_name is %NULL, g_get_prgname() is called to get + * the program name (which will work correctly if gdk_init() or + * gtk_init() has been called) + * + * Prompts the user with + * `[E]xit, [H]alt, show [S]tack trace or [P]roceed`. + * This function is intended to be used for debugging use only. + * The following example shows how it can be used together with + * the g_log() functions. + * + * |[ + * #include + * + * static void + * log_handler (const gchar *log_domain, + * GLogLevelFlags log_level, + * const gchar *message, + * gpointer user_data) + * { + * g_log_default_handler (log_domain, log_level, message, user_data); + * + * g_on_error_query (MY_PROGRAM_NAME); + * } + * + * int + * main (int argc, char *argv[]) + * { + * g_log_set_handler (MY_LOG_DOMAIN, + * G_LOG_LEVEL_WARNING | + * G_LOG_LEVEL_ERROR | + * G_LOG_LEVEL_CRITICAL, + * log_handler, + * NULL); + * ... + * ]| + * + * If "[E]xit" is selected, the application terminates with a call + * to _exit(0). + * + * If "[S]tack" trace is selected, g_on_error_stack_trace() is called. + * This invokes gdb, which attaches to the current process and shows + * a stack trace. The prompt is then shown again. + * + * If "[P]roceed" is selected, the function returns. + * + * This function may cause different actions on non-UNIX platforms. + * + * On Windows consider using the `G_DEBUGGER` environment + * variable (see [Running GLib Applications](glib-running.html)) and + * calling g_on_error_stack_trace() instead. + */ +void +g_on_error_query (const gchar *prg_name) +{ +#ifndef G_OS_WIN32 + static const gchar * const query1 = "[E]xit, [H]alt"; + static const gchar * const query2 = ", show [S]tack trace"; + static const gchar * const query3 = " or [P]roceed"; + gchar buf[16]; + + if (!prg_name) + prg_name = g_get_prgname (); + + retry: + + if (prg_name) + _g_fprintf (stdout, + "%s (pid:%u): %s%s%s: ", + prg_name, + (guint) getpid (), + query1, + query2, + query3); + else + _g_fprintf (stdout, + "(process:%u): %s%s: ", + (guint) getpid (), + query1, + query3); + fflush (stdout); + + if (isatty(0) && isatty(1)) + fgets (buf, 8, stdin); + else + strcpy (buf, "E\n"); + + if ((buf[0] == 'E' || buf[0] == 'e') + && buf[1] == '\n') + _exit (0); + else if ((buf[0] == 'P' || buf[0] == 'p') + && buf[1] == '\n') + return; + else if (prg_name + && (buf[0] == 'S' || buf[0] == 's') + && buf[1] == '\n') + { + g_on_error_stack_trace (prg_name); + goto retry; + } + else if ((buf[0] == 'H' || buf[0] == 'h') + && buf[1] == '\n') + { + while (glib_on_error_halt) + ; + glib_on_error_halt = TRUE; + return; + } + else + goto retry; +#else + if (!prg_name) + prg_name = g_get_prgname (); + + /* MessageBox is allowed on UWP apps only when building against + * the debug CRT, which will set -D_DEBUG */ +#if defined(_DEBUG) || !defined(G_WINAPI_ONLY_APP) + { + WCHAR *caption = NULL; + + if (prg_name && *prg_name) + { + caption = g_utf8_to_utf16 (prg_name, -1, NULL, NULL, NULL); + } + + MessageBoxW (NULL, L"g_on_error_query called, program terminating", + caption, + MB_OK|MB_ICONERROR); + + g_free (caption); + } +#else + printf ("g_on_error_query called, program '%s' terminating\n", + (prg_name && *prg_name) ? prg_name : "(null)"); +#endif + _exit(0); +#endif +} + +/** + * g_on_error_stack_trace: + * @prg_name: the program name, needed by gdb for the "[S]tack trace" + * option + * + * Invokes gdb, which attaches to the current process and shows a + * stack trace. Called by g_on_error_query() when the "[S]tack trace" + * option is selected. You can get the current process's program name + * with g_get_prgname(), assuming that you have called gtk_init() or + * gdk_init(). + * + * This function may cause different actions on non-UNIX platforms. + * + * When running on Windows, this function is *not* called by + * g_on_error_query(). If called directly, it will raise an + * exception, which will crash the program. If the `G_DEBUGGER` environment + * variable is set, a debugger will be invoked to attach and + * handle that exception (see [Running GLib Applications](glib-running.html)). + */ +void +g_on_error_stack_trace (const gchar *prg_name) +{ +#if defined(G_OS_UNIX) + pid_t pid; + gchar buf[16]; + const gchar *args[5] = { DEBUGGER, NULL, NULL, NULL, NULL }; + int status; + + if (!prg_name) + return; + + _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) + { + stack_trace (args); + _exit (0); + } + else if (pid == (pid_t) -1) + { + perror ("unable to fork " DEBUGGER); + return; + } + + /* 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 (); + else + g_abort (); +#endif +} + +#ifndef G_OS_WIN32 + +static gboolean stack_trace_done = FALSE; + +static void +stack_trace_sigchld (int signum) +{ + stack_trace_done = TRUE; +} + +#define BUFSIZE 1024 + +static void +stack_trace (const char * const *args) +{ + pid_t pid; + int in_fd[2]; + int out_fd[2]; + fd_set fdset; + fd_set readset; + struct timeval tv; + int sel, idx, state; +#ifdef USE_LLDB + int line_idx; +#endif + char buffer[BUFSIZE]; + char c; + + stack_trace_done = FALSE; + signal (SIGCHLD, stack_trace_sigchld); + + if ((pipe (in_fd) == -1) || (pipe (out_fd) == -1)) + { + perror ("unable to open pipe"); + _exit (0); + } + + pid = fork (); + if (pid == 0) + { + /* Save stderr for printing failure below */ + int old_err = dup (2); + if (old_err != -1) + { + int getfd = fcntl (old_err, F_GETFD); + if (getfd != -1) + (void) fcntl (old_err, F_SETFD, getfd | FD_CLOEXEC); + } + + close (0); dup (in_fd[0]); /* set the stdin to the in pipe */ + close (1); dup (out_fd[1]); /* set the stdout to the out pipe */ + close (2); dup (out_fd[1]); /* set the stderr to the out pipe */ + + execvp (args[0], (char **) args); /* exec gdb */ + + /* Print failure to original stderr */ + if (old_err != -1) + { + close (2); + dup (old_err); + } + perror ("exec " DEBUGGER " failed"); + _exit (0); + } + else if (pid == (pid_t) -1) + { + perror ("unable to fork"); + _exit (0); + } + + 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; +#ifdef USE_LLDB + line_idx = 0; +#endif + state = 0; + + while (1) + { + readset = fdset; + tv.tv_sec = 1; + tv.tv_usec = 0; + + sel = select (FD_SETSIZE, &readset, NULL, NULL, &tv); + if (sel == -1) + break; + + if ((sel > 0) && (FD_ISSET (out_fd[0], &readset))) + { + if (read (out_fd[0], &c, 1)) + { +#ifdef USE_LLDB + line_idx += 1; +#endif + + switch (state) + { + case 0: +#ifdef USE_LLDB + if (c == '*' || (c == ' ' && line_idx == 1)) +#else + if (c == '#') +#endif + { + state = 1; + idx = 0; + buffer[idx++] = c; + } + break; + case 1: + if (idx < BUFSIZE) + buffer[idx++] = c; + if ((c == '\n') || (c == '\r')) + { + buffer[idx] = 0; + _g_fprintf (stdout, "%s", buffer); + state = 0; + idx = 0; +#ifdef USE_LLDB + line_idx = 0; +#endif + } + break; + default: + break; + } + } + } + else if (stack_trace_done) + break; + } + + close (in_fd[0]); + close (in_fd[1]); + close (out_fd[0]); + close (out_fd[1]); + _exit (0); +} + +#endif /* !G_OS_WIN32 */ diff --git a/glib/gbacktrace.h b/glib/gbacktrace.h new file mode 100644 index 0000000..09b8ccb --- /dev/null +++ b/glib/gbacktrace.h @@ -0,0 +1,72 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_BACKTRACE_H__ +#define __G_BACKTRACE_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#ifdef __sun__ +#include +#endif +#include + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_ALL +void g_on_error_query (const gchar *prg_name); +GLIB_AVAILABLE_IN_ALL +void g_on_error_stack_trace (const gchar *prg_name); + +/** + * G_BREAKPOINT: + * + * Inserts a breakpoint instruction into the code. + * + * On architectures which support it, this is implemented as a soft interrupt + * and on other architectures it raises a `SIGTRAP` signal. + * + * `SIGTRAP` is used rather than abort() to allow breakpoints to be skipped past + * in a debugger if they are not the desired target of debugging. + */ +#if (defined (__i386__) || defined (__x86_64__)) && defined (__GNUC__) && __GNUC__ >= 2 +# define G_BREAKPOINT() G_STMT_START{ __asm__ __volatile__ ("int $03"); }G_STMT_END +#elif (defined (_MSC_VER) || defined (__DMC__)) && defined (_M_IX86) +# define G_BREAKPOINT() G_STMT_START{ __asm int 3h }G_STMT_END +#elif defined (_MSC_VER) +# define G_BREAKPOINT() G_STMT_START{ __debugbreak(); }G_STMT_END +#elif defined (__alpha__) && !defined(__osf__) && defined (__GNUC__) && __GNUC__ >= 2 +# define G_BREAKPOINT() G_STMT_START{ __asm__ __volatile__ ("bpt"); }G_STMT_END +#elif defined (__APPLE__) || (defined(_WIN32) && (defined(__clang__) || defined(__GNUC__))) +# define G_BREAKPOINT() G_STMT_START{ __builtin_trap(); }G_STMT_END +#else /* !__i386__ && !__alpha__ */ +# define G_BREAKPOINT() G_STMT_START{ raise (SIGTRAP); }G_STMT_END +#endif /* __i386__ */ + +G_END_DECLS + +#endif /* __G_BACKTRACE_H__ */ diff --git a/glib/gbase64.c b/glib/gbase64.c new file mode 100644 index 0000000..f2d110e --- /dev/null +++ b/glib/gbase64.c @@ -0,0 +1,462 @@ +/* gbase64.c - Base64 encoding/decoding + * + * Copyright (C) 2006 Alexander Larsson + * Copyright (C) 2000-2003 Ximian 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 . + * + * This is based on code in camel, written by: + * Michael Zucchi + * Jeffrey Stedfast + */ + +#include "config.h" + +#include + +#include "gbase64.h" +#include "gtestutils.h" +#include "glibintl.h" + + +/** + * SECTION:base64 + * @title: Base64 Encoding + * @short_description: encodes and decodes data in Base64 format + * + * Base64 is an encoding that allows a sequence of arbitrary bytes to be + * encoded as a sequence of printable ASCII characters. For the definition + * of Base64, see + * [RFC 1421](http://www.ietf.org/rfc/rfc1421.txt) + * or + * [RFC 2045](http://www.ietf.org/rfc/rfc2045.txt). + * Base64 is most commonly used as a MIME transfer encoding + * for email. + * + * GLib supports incremental encoding using g_base64_encode_step() and + * g_base64_encode_close(). Incremental decoding can be done with + * g_base64_decode_step(). To encode or decode data in one go, use + * g_base64_encode() or g_base64_decode(). To avoid memory allocation when + * decoding, you can use g_base64_decode_inplace(). + * + * Support for Base64 encoding has been added in GLib 2.12. + */ + +static const char base64_alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * g_base64_encode_step: + * @in: (array length=len) (element-type guint8): the binary data to encode + * @len: the length of @in + * @break_lines: whether to break long lines + * @out: (out) (array) (element-type guint8): pointer to destination buffer + * @state: (inout): Saved state between steps, initialize to 0 + * @save: (inout): Saved state between steps, initialize to 0 + * + * Incrementally encode a sequence of binary data into its Base-64 stringified + * representation. By calling this function multiple times you can convert + * data in chunks to avoid having to have the full encoded data in memory. + * + * When all of the data has been converted you must call + * g_base64_encode_close() to flush the saved state. + * + * The output buffer must be large enough to fit all the data that will + * 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) / 76 + 1 bytes of extra space. + * + * @break_lines is typically used when putting base64-encoded data in emails. + * 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 + * or certain other protocols. + * + * Returns: The number of bytes of output that was written + * + * Since: 2.12 + */ +gsize +g_base64_encode_step (const guchar *in, + gsize len, + gboolean break_lines, + gchar *out, + gint *state, + gint *save) +{ + char *outptr; + const guchar *inptr; + + g_return_val_if_fail (in != NULL || len == 0, 0); + g_return_val_if_fail (out != NULL, 0); + g_return_val_if_fail (state != NULL, 0); + g_return_val_if_fail (save != NULL, 0); + + if (len == 0) + return 0; + + inptr = in; + outptr = out; + + if (len + ((char *) save) [0] > 2) + { + const guchar *inend = in+len-2; + int c1, c2, c3; + int already; + + already = *state; + + switch (((char *) save) [0]) + { + case 1: + c1 = ((unsigned char *) save) [1]; + goto skip1; + case 2: + c1 = ((unsigned char *) save) [1]; + c2 = ((unsigned char *) save) [2]; + goto skip2; + } + + /* + * yes, we jump into the loop, no i'm not going to change it, + * it's beautiful! + */ + while (inptr < inend) + { + c1 = *inptr++; + skip1: + c2 = *inptr++; + skip2: + c3 = *inptr++; + *outptr++ = base64_alphabet [ c1 >> 2 ]; + *outptr++ = base64_alphabet [ c2 >> 4 | + ((c1&0x3) << 4) ]; + *outptr++ = base64_alphabet [ ((c2 &0x0f) << 2) | + (c3 >> 6) ]; + *outptr++ = base64_alphabet [ c3 & 0x3f ]; + /* this is a bit ugly ... */ + if (break_lines && (++already) >= 19) + { + *outptr++ = '\n'; + already = 0; + } + } + + ((char *)save)[0] = 0; + len = 2 - (inptr - inend); + *state = already; + } + + g_assert (len == 0 || len == 1 || len == 2); + + { + char *saveout; + + /* points to the slot for the next char to save */ + saveout = & (((char *)save)[1]) + ((char *)save)[0]; + + /* len can only be 0 1 or 2 */ + switch(len) + { + case 2: + *saveout++ = *inptr++; + G_GNUC_FALLTHROUGH; + case 1: + *saveout++ = *inptr++; + } + ((char *)save)[0] += len; + } + + return outptr - out; +} + +/** + * g_base64_encode_close: + * @break_lines: whether to break long lines + * @out: (out) (array) (element-type guint8): pointer to destination buffer + * @state: (inout): Saved state from g_base64_encode_step() + * @save: (inout): Saved state from g_base64_encode_step() + * + * Flush the status from a sequence of calls to g_base64_encode_step(). + * + * The output buffer must be large enough to fit all the data that will + * be written to it. It will need up to 4 bytes, or up to 5 bytes if + * line-breaking is enabled. + * + * The @out array will not be automatically nul-terminated. + * + * Returns: The number of bytes of output that was written + * + * Since: 2.12 + */ +gsize +g_base64_encode_close (gboolean break_lines, + gchar *out, + gint *state, + gint *save) +{ + int c1, c2; + char *outptr = out; + + g_return_val_if_fail (out != NULL, 0); + g_return_val_if_fail (state != NULL, 0); + g_return_val_if_fail (save != NULL, 0); + + c1 = ((unsigned char *) save) [1]; + c2 = ((unsigned char *) save) [2]; + + switch (((char *) save) [0]) + { + case 2: + outptr [2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ]; + g_assert (outptr [2] != 0); + goto skip; + case 1: + outptr[2] = '='; + c2 = 0; /* saved state here is not relevant */ + skip: + outptr [0] = base64_alphabet [ c1 >> 2 ]; + outptr [1] = base64_alphabet [ c2 >> 4 | ( (c1&0x3) << 4 )]; + outptr [3] = '='; + outptr += 4; + break; + } + if (break_lines) + *outptr++ = '\n'; + + *save = 0; + *state = 0; + + return outptr - out; +} + +/** + * g_base64_encode: + * @data: (array length=len) (element-type guint8) (nullable): the binary data to encode + * @len: the length of @data + * + * Encode a sequence of binary data into its Base-64 stringified + * representation. + * + * Returns: (transfer full): a newly allocated, zero-terminated Base-64 + * encoded string representing @data. The returned string must + * be freed with g_free(). + * + * Since: 2.12 + */ +gchar * +g_base64_encode (const guchar *data, + gsize len) +{ + gchar *out; + gint state = 0, outlen; + gint save = 0; + + g_return_val_if_fail (data != NULL || len == 0, NULL); + + /* We can use a smaller limit here, since we know the saved state is 0, + +1 is needed for trailing \0, also check for unlikely integer overflow */ + g_return_val_if_fail (len < ((G_MAXSIZE - 1) / 4 - 1) * 3, NULL); + + out = g_malloc ((len / 3 + 1) * 4 + 1); + + outlen = g_base64_encode_step (data, len, FALSE, out, &state, &save); + outlen += g_base64_encode_close (FALSE, out + outlen, &state, &save); + out[outlen] = '\0'; + + return (gchar *) out; +} + +static const unsigned char mime_base64_rank[256] = { + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +}; + +/** + * g_base64_decode_step: (skip) + * @in: (array length=len) (element-type guint8): binary input data + * @len: max length of @in data to decode + * @out: (out caller-allocates) (array) (element-type guint8): output buffer + * @state: (inout): Saved state between steps, initialize to 0 + * @save: (inout): Saved state between steps, initialize to 0 + * + * Incrementally decode a sequence of binary data from its Base-64 stringified + * representation. By calling this function multiple times you can convert + * data in chunks to avoid having to have the full encoded data in memory. + * + * The output buffer must be large enough to fit all the data that will + * be written to it. Since base64 encodes 3 bytes in 4 chars you need + * at least: (@len / 4) * 3 + 3 bytes (+ 3 may be needed in case of non-zero + * state). + * + * Returns: The number of bytes of output that was written + * + * Since: 2.12 + **/ +gsize +g_base64_decode_step (const gchar *in, + gsize len, + guchar *out, + gint *state, + guint *save) +{ + const guchar *inptr; + guchar *outptr; + const guchar *inend; + guchar c, rank; + guchar last[2]; + unsigned int v; + int i; + + g_return_val_if_fail (in != NULL || len == 0, 0); + g_return_val_if_fail (out != NULL, 0); + g_return_val_if_fail (state != NULL, 0); + g_return_val_if_fail (save != NULL, 0); + + if (len == 0) + return 0; + + inend = (const guchar *)in+len; + outptr = out; + + /* convert 4 base64 bytes to 3 normal bytes */ + v=*save; + i=*state; + + last[0] = last[1] = 0; + + /* we use the sign in the state to determine if we got a padding character + in the previous sequence */ + if (i < 0) + { + i = -i; + last[0] = '='; + } + + inptr = (const guchar *)in; + while (inptr < inend) + { + c = *inptr++; + rank = mime_base64_rank [c]; + if (rank != 0xff) + { + last[1] = last[0]; + last[0] = c; + v = (v<<6) | rank; + i++; + if (i==4) + { + *outptr++ = v>>16; + if (last[1] != '=') + *outptr++ = v>>8; + if (last[0] != '=') + *outptr++ = v; + i=0; + } + } + } + + *save = v; + *state = last[0] == '=' ? -i : i; + + return outptr - out; +} + +/** + * g_base64_decode: + * @text: (not nullable): zero-terminated string with base64 text to decode + * @out_len: (out): The length of the decoded data is written here + * + * Decode a sequence of Base-64 encoded text into binary data. Note + * that the returned binary data is not necessarily zero-terminated, + * so it should not be used as a character string. + * + * Returns: (transfer full) (array length=out_len) (element-type guint8): + * newly allocated buffer containing the binary data + * that @text represents. The returned buffer must + * be freed with g_free(). + * + * Since: 2.12 + */ +guchar * +g_base64_decode (const gchar *text, + gsize *out_len) +{ + guchar *ret; + gsize input_length; + gint state = 0; + guint save = 0; + + g_return_val_if_fail (text != NULL, NULL); + g_return_val_if_fail (out_len != NULL, NULL); + + input_length = strlen (text); + + /* We can use a smaller limit here, since we know the saved state is 0, + +1 used to avoid calling g_malloc0(0), and hence returning NULL */ + ret = g_malloc0 ((input_length / 4) * 3 + 1); + + *out_len = g_base64_decode_step (text, input_length, ret, &state, &save); + + return ret; +} + +/** + * g_base64_decode_inplace: + * @text: (inout) (array length=out_len) (element-type guint8): zero-terminated + * string with base64 text to decode + * @out_len: (inout): The length of the decoded data is written here + * + * Decode a sequence of Base-64 encoded text into binary data + * by overwriting the input data. + * + * Returns: (transfer none): The binary data that @text responds. This pointer + * is the same as the input @text. + * + * Since: 2.20 + */ +guchar * +g_base64_decode_inplace (gchar *text, + gsize *out_len) +{ + gint input_length, state = 0; + guint save = 0; + + g_return_val_if_fail (text != NULL, NULL); + g_return_val_if_fail (out_len != NULL, NULL); + + input_length = strlen (text); + + g_return_val_if_fail (input_length > 1, NULL); + + *out_len = g_base64_decode_step (text, input_length, (guchar *) text, &state, &save); + + return (guchar *) text; +} diff --git a/glib/gbase64.h b/glib/gbase64.h new file mode 100644 index 0000000..662c597 --- /dev/null +++ b/glib/gbase64.h @@ -0,0 +1,61 @@ +/* gbase64.h - Base64 coding functions + * + * Copyright (C) 2005 Alexander Larsson + * + * 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_BASE64_H__ +#define __G_BASE64_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_ALL +gsize g_base64_encode_step (const guchar *in, + gsize len, + gboolean break_lines, + gchar *out, + gint *state, + gint *save); +GLIB_AVAILABLE_IN_ALL +gsize g_base64_encode_close (gboolean break_lines, + gchar *out, + gint *state, + gint *save); +GLIB_AVAILABLE_IN_ALL +gchar* g_base64_encode (const guchar *data, + gsize len) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +gsize g_base64_decode_step (const gchar *in, + gsize len, + guchar *out, + gint *state, + guint *save); +GLIB_AVAILABLE_IN_ALL +guchar *g_base64_decode (const gchar *text, + gsize *out_len) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +guchar *g_base64_decode_inplace (gchar *text, + gsize *out_len); + + +G_END_DECLS + +#endif /* __G_BASE64_H__ */ diff --git a/glib/gbitlock.c b/glib/gbitlock.c new file mode 100644 index 0000000..9384d1a --- /dev/null +++ b/glib/gbitlock.c @@ -0,0 +1,563 @@ +/* + * Copyright © 2008 Ryan Lortie + * Copyright © 2010 Codethink 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 . + * + * Author: Ryan Lortie + */ + +#include "config.h" + +#include "gbitlock.h" + +#include +#include +#include +#include +#include +#include + +#include "gthreadprivate.h" + +#ifdef G_BIT_LOCK_FORCE_FUTEX_EMULATION +#undef HAVE_FUTEX +#endif + +#ifndef HAVE_FUTEX +static GMutex g_futex_mutex; +static GSList *g_futex_address_list = NULL; +#endif + +#ifdef HAVE_FUTEX +/* + * We have headers for futex(2) on the build machine. This does not + * imply that every system that ever runs the resulting glib will have + * kernel support for futex, but you'd have to have a pretty old + * kernel in order for that not to be the case. + * + * If anyone actually gets bit by this, please file a bug. :) + */ +#include +#include +#include + +#ifndef FUTEX_WAIT_PRIVATE +#define FUTEX_WAIT_PRIVATE FUTEX_WAIT +#define FUTEX_WAKE_PRIVATE FUTEX_WAKE +#endif + +/* < private > + * g_futex_wait: + * @address: a pointer to an integer + * @value: the value that should be at @address + * + * Atomically checks that the value stored at @address is equal to + * @value and then blocks. If the value stored at @address is not + * equal to @value then this function returns immediately. + * + * To unblock, call g_futex_wake() on @address. + * + * This call may spuriously unblock (for example, in response to the + * process receiving a signal) but this is not guaranteed. Unlike the + * Linux system call of a similar name, there is no guarantee that a + * waiting process will unblock due to a g_futex_wake() call in a + * separate process. + */ +static void +g_futex_wait (const gint *address, + gint value) +{ + syscall (__NR_futex, address, (gsize) FUTEX_WAIT_PRIVATE, (gsize) value, NULL); +} + +/* < private > + * g_futex_wake: + * @address: a pointer to an integer + * + * Nominally, wakes one thread that is blocked in g_futex_wait() on + * @address (if any thread is currently waiting). + * + * As mentioned in the documentation for g_futex_wait(), spurious + * wakeups may occur. As such, this call may result in more than one + * thread being woken up. + */ +static void +g_futex_wake (const gint *address) +{ + syscall (__NR_futex, address, (gsize) FUTEX_WAKE_PRIVATE, (gsize) 1, NULL); +} + +#else + +/* emulate futex(2) */ +typedef struct +{ + const gint *address; + gint ref_count; + GCond wait_queue; +} WaitAddress; + +static WaitAddress * +g_futex_find_address (const gint *address) +{ + GSList *node; + + for (node = g_futex_address_list; node; node = node->next) + { + WaitAddress *waiter = node->data; + + if (waiter->address == address) + return waiter; + } + + return NULL; +} + +static void +g_futex_wait (const gint *address, + gint value) +{ + g_mutex_lock (&g_futex_mutex); + if G_LIKELY (g_atomic_int_get (address) == value) + { + WaitAddress *waiter; + + if ((waiter = g_futex_find_address (address)) == NULL) + { + waiter = g_slice_new (WaitAddress); + waiter->address = address; + g_cond_init (&waiter->wait_queue); + waiter->ref_count = 0; + g_futex_address_list = + g_slist_prepend (g_futex_address_list, waiter); + } + + waiter->ref_count++; + g_cond_wait (&waiter->wait_queue, &g_futex_mutex); + + if (!--waiter->ref_count) + { + g_futex_address_list = + g_slist_remove (g_futex_address_list, waiter); + g_cond_clear (&waiter->wait_queue); + g_slice_free (WaitAddress, waiter); + } + } + g_mutex_unlock (&g_futex_mutex); +} + +static void +g_futex_wake (const gint *address) +{ + WaitAddress *waiter; + + /* need to lock here for two reasons: + * 1) need to acquire/release lock to ensure waiter is not in + * the process of registering a wait + * 2) need to -stay- locked until the end to ensure a wake() + * in another thread doesn't cause 'waiter' to stop existing + */ + g_mutex_lock (&g_futex_mutex); + if ((waiter = g_futex_find_address (address))) + g_cond_signal (&waiter->wait_queue); + g_mutex_unlock (&g_futex_mutex); +} +#endif + +#define CONTENTION_CLASSES 11 +static gint g_bit_lock_contended[CONTENTION_CLASSES]; /* (atomic) */ + +#if (defined (i386) || defined (__amd64__)) + #if G_GNUC_CHECK_VERSION(4, 5) + #define USE_ASM_GOTO 1 + #endif +#endif + +/** + * g_bit_lock: + * @address: a pointer to an integer + * @lock_bit: a bit value between 0 and 31 + * + * Sets the indicated @lock_bit in @address. If the bit is already + * set, this call will block until g_bit_unlock() unsets the + * corresponding bit. + * + * Attempting to lock on two different bits within the same integer is + * not supported and will very probably cause deadlocks. + * + * The value of the bit that is set is (1u << @bit). If @bit is not + * between 0 and 31 then the result is undefined. + * + * This function accesses @address atomically. All other accesses to + * @address must be atomic in order for this function to work + * reliably. While @address has a `volatile` qualifier, this is a historical + * artifact and the argument passed to it should not be `volatile`. + * + * Since: 2.24 + **/ +void +g_bit_lock (volatile gint *address, + gint lock_bit) +{ + gint *address_nonvolatile = (gint *) address; + +#ifdef USE_ASM_GOTO + retry: + __asm__ volatile goto ("lock bts %1, (%0)\n" + "jc %l[contended]" + : /* no output */ + : "r" (address), "r" (lock_bit) + : "cc", "memory" + : contended); + return; + + contended: + { + guint mask = 1u << lock_bit; + guint v; + + v = (guint) g_atomic_int_get (address_nonvolatile); + if (v & mask) + { + guint class = ((gsize) address_nonvolatile) % G_N_ELEMENTS (g_bit_lock_contended); + + g_atomic_int_add (&g_bit_lock_contended[class], +1); + g_futex_wait (address_nonvolatile, v); + g_atomic_int_add (&g_bit_lock_contended[class], -1); + } + } + goto retry; +#else + guint mask = 1u << lock_bit; + guint v; + + retry: + v = g_atomic_int_or (address_nonvolatile, mask); + if (v & mask) + /* already locked */ + { + guint class = ((gsize) address_nonvolatile) % G_N_ELEMENTS (g_bit_lock_contended); + + g_atomic_int_add (&g_bit_lock_contended[class], +1); + g_futex_wait (address_nonvolatile, v); + g_atomic_int_add (&g_bit_lock_contended[class], -1); + + goto retry; + } +#endif +} + +/** + * g_bit_trylock: + * @address: a pointer to an integer + * @lock_bit: a bit value between 0 and 31 + * + * Sets the indicated @lock_bit in @address, returning %TRUE if + * successful. If the bit is already set, returns %FALSE immediately. + * + * Attempting to lock on two different bits within the same integer is + * not supported. + * + * The value of the bit that is set is (1u << @bit). If @bit is not + * between 0 and 31 then the result is undefined. + * + * This function accesses @address atomically. All other accesses to + * @address must be atomic in order for this function to work + * reliably. While @address has a `volatile` qualifier, this is a historical + * artifact and the argument passed to it should not be `volatile`. + * + * Returns: %TRUE if the lock was acquired + * + * Since: 2.24 + **/ +gboolean +g_bit_trylock (volatile gint *address, + gint lock_bit) +{ +#ifdef USE_ASM_GOTO + gboolean result; + + __asm__ volatile ("lock bts %2, (%1)\n" + "setnc %%al\n" + "movzx %%al, %0" + : "=r" (result) + : "r" (address), "r" (lock_bit) + : "cc", "memory"); + + return result; +#else + gint *address_nonvolatile = (gint *) address; + guint mask = 1u << lock_bit; + guint v; + + v = g_atomic_int_or (address_nonvolatile, mask); + + return ~v & mask; +#endif +} + +/** + * g_bit_unlock: + * @address: a pointer to an integer + * @lock_bit: a bit value between 0 and 31 + * + * Clears the indicated @lock_bit in @address. If another thread is + * currently blocked in g_bit_lock() on this same bit then it will be + * woken up. + * + * This function accesses @address atomically. All other accesses to + * @address must be atomic in order for this function to work + * reliably. While @address has a `volatile` qualifier, this is a historical + * artifact and the argument passed to it should not be `volatile`. + * + * Since: 2.24 + **/ +void +g_bit_unlock (volatile gint *address, + gint lock_bit) +{ + gint *address_nonvolatile = (gint *) address; + +#ifdef USE_ASM_GOTO + __asm__ volatile ("lock btr %1, (%0)" + : /* no output */ + : "r" (address), "r" (lock_bit) + : "cc", "memory"); +#else + guint mask = 1u << lock_bit; + + g_atomic_int_and (address_nonvolatile, ~mask); +#endif + + { + guint class = ((gsize) address_nonvolatile) % G_N_ELEMENTS (g_bit_lock_contended); + + if (g_atomic_int_get (&g_bit_lock_contended[class])) + g_futex_wake (address_nonvolatile); + } +} + + +/* We emulate pointer-sized futex(2) because the kernel API only + * supports integers. + * + * We assume that the 'interesting' part is always the lower order bits. + * This assumption holds because pointer bitlocks are restricted to + * using the low order bits of the pointer as the lock. + * + * On 32 bits, there is nothing to do since the pointer size is equal to + * the integer size. On little endian the lower-order bits don't move, + * so do nothing. Only on 64bit big endian do we need to do a bit of + * pointer arithmetic: the low order bits are shifted by 4 bytes. We + * have a helper function that always does the right thing here. + * + * Since we always consider the low-order bits of the integer value, a + * simple cast from (gsize) to (guint) always takes care of that. + * + * After that, pointer-sized futex becomes as simple as: + * + * g_futex_wait (g_futex_int_address (address), (guint) value); + * + * and + * + * g_futex_wake (g_futex_int_address (int_address)); + */ +static const gint * +g_futex_int_address (const void *address) +{ + const gint *int_address = address; + + /* this implementation makes these (reasonable) assumptions: */ + G_STATIC_ASSERT (G_BYTE_ORDER == G_LITTLE_ENDIAN || + (G_BYTE_ORDER == G_BIG_ENDIAN && + sizeof (int) == 4 && + (sizeof (gpointer) == 4 || sizeof (gpointer) == 8))); + +#if G_BYTE_ORDER == G_BIG_ENDIAN && GLIB_SIZEOF_VOID_P == 8 + int_address++; +#endif + + return int_address; +} + +/** + * g_pointer_bit_lock: + * @address: (not nullable): a pointer to a #gpointer-sized value + * @lock_bit: a bit value between 0 and 31 + * + * This is equivalent to g_bit_lock, but working on pointers (or other + * pointer-sized values). + * + * For portability reasons, you may only lock on the bottom 32 bits of + * the pointer. + * + * While @address has a `volatile` qualifier, this is a historical + * artifact and the argument passed to it should not be `volatile`. + * + * Since: 2.30 + **/ +void +(g_pointer_bit_lock) (volatile void *address, + gint lock_bit) +{ + void *address_nonvolatile = (void *) address; + + g_return_if_fail (lock_bit < 32); + + { +#ifdef USE_ASM_GOTO + retry: + __asm__ volatile goto ("lock bts %1, (%0)\n" + "jc %l[contended]" + : /* no output */ + : "r" (address), "r" ((gsize) lock_bit) + : "cc", "memory" + : contended); + return; + + contended: + { + gsize *pointer_address = address_nonvolatile; + gsize mask = 1u << lock_bit; + gsize v; + + v = (gsize) g_atomic_pointer_get (pointer_address); + if (v & mask) + { + guint class = ((gsize) address_nonvolatile) % G_N_ELEMENTS (g_bit_lock_contended); + + g_atomic_int_add (&g_bit_lock_contended[class], +1); + g_futex_wait (g_futex_int_address (address_nonvolatile), v); + g_atomic_int_add (&g_bit_lock_contended[class], -1); + } + } + goto retry; +#else + gsize *pointer_address = address_nonvolatile; + gsize mask = 1u << lock_bit; + gsize v; + + retry: + v = g_atomic_pointer_or (pointer_address, mask); + if (v & mask) + /* already locked */ + { + guint class = ((gsize) address_nonvolatile) % G_N_ELEMENTS (g_bit_lock_contended); + + g_atomic_int_add (&g_bit_lock_contended[class], +1); + g_futex_wait (g_futex_int_address (address_nonvolatile), (guint) v); + g_atomic_int_add (&g_bit_lock_contended[class], -1); + + goto retry; + } +#endif + } +} + +/** + * g_pointer_bit_trylock: + * @address: (not nullable): a pointer to a #gpointer-sized value + * @lock_bit: a bit value between 0 and 31 + * + * This is equivalent to g_bit_trylock(), but working on pointers (or + * other pointer-sized values). + * + * For portability reasons, you may only lock on the bottom 32 bits of + * the pointer. + * + * While @address has a `volatile` qualifier, this is a historical + * artifact and the argument passed to it should not be `volatile`. + * + * Returns: %TRUE if the lock was acquired + * + * Since: 2.30 + **/ +gboolean +(g_pointer_bit_trylock) (volatile void *address, + gint lock_bit) +{ + g_return_val_if_fail (lock_bit < 32, FALSE); + + { +#ifdef USE_ASM_GOTO + gboolean result; + + __asm__ volatile ("lock bts %2, (%1)\n" + "setnc %%al\n" + "movzx %%al, %0" + : "=r" (result) + : "r" (address), "r" ((gsize) lock_bit) + : "cc", "memory"); + + return result; +#else + void *address_nonvolatile = (void *) address; + gsize *pointer_address = address_nonvolatile; + gsize mask = 1u << lock_bit; + gsize v; + + g_return_val_if_fail (lock_bit < 32, FALSE); + + v = g_atomic_pointer_or (pointer_address, mask); + + return ~v & mask; +#endif + } +} + +/** + * g_pointer_bit_unlock: + * @address: (not nullable): a pointer to a #gpointer-sized value + * @lock_bit: a bit value between 0 and 31 + * + * This is equivalent to g_bit_unlock, but working on pointers (or other + * pointer-sized values). + * + * For portability reasons, you may only lock on the bottom 32 bits of + * the pointer. + * + * While @address has a `volatile` qualifier, this is a historical + * artifact and the argument passed to it should not be `volatile`. + * + * Since: 2.30 + **/ +void +(g_pointer_bit_unlock) (volatile void *address, + gint lock_bit) +{ + void *address_nonvolatile = (void *) address; + + g_return_if_fail (lock_bit < 32); + + { +#ifdef USE_ASM_GOTO + __asm__ volatile ("lock btr %1, (%0)" + : /* no output */ + : "r" (address), "r" ((gsize) lock_bit) + : "cc", "memory"); +#else + gsize *pointer_address = address_nonvolatile; + gsize mask = 1u << lock_bit; + + g_atomic_pointer_and (pointer_address, ~mask); +#endif + + { + guint class = ((gsize) address_nonvolatile) % G_N_ELEMENTS (g_bit_lock_contended); + if (g_atomic_int_get (&g_bit_lock_contended[class])) + g_futex_wake (g_futex_int_address (address_nonvolatile)); + } + } +} diff --git a/glib/gbitlock.h b/glib/gbitlock.h new file mode 100644 index 0000000..8054bc8 --- /dev/null +++ b/glib/gbitlock.h @@ -0,0 +1,76 @@ +/* + * Copyright © 2008 Ryan Lortie + * Copyright © 2010 Codethink 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 . + * + * Author: Ryan Lortie + */ + +#ifndef __G_BITLOCK_H__ +#define __G_BITLOCK_H__ + +#include + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_ALL +void g_bit_lock (volatile gint *address, + gint lock_bit); +GLIB_AVAILABLE_IN_ALL +gboolean g_bit_trylock (volatile gint *address, + gint lock_bit); +GLIB_AVAILABLE_IN_ALL +void g_bit_unlock (volatile gint *address, + gint lock_bit); + +GLIB_AVAILABLE_IN_ALL +void g_pointer_bit_lock (volatile void *address, + gint lock_bit); +GLIB_AVAILABLE_IN_ALL +gboolean g_pointer_bit_trylock (volatile void *address, + gint lock_bit); +GLIB_AVAILABLE_IN_ALL +void g_pointer_bit_unlock (volatile void *address, + gint lock_bit); + +#ifdef __GNUC__ + +#define g_pointer_bit_lock(address, lock_bit) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(address) == sizeof (gpointer)); \ + g_pointer_bit_lock ((address), (lock_bit)); \ + })) + +#define g_pointer_bit_trylock(address, lock_bit) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(address) == sizeof (gpointer)); \ + g_pointer_bit_trylock ((address), (lock_bit)); \ + })) + +#define g_pointer_bit_unlock(address, lock_bit) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(address) == sizeof (gpointer)); \ + g_pointer_bit_unlock ((address), (lock_bit)); \ + })) + +#endif + +G_END_DECLS + +#endif /* __G_BITLOCK_H_ */ diff --git a/glib/gbookmarkfile.c b/glib/gbookmarkfile.c new file mode 100644 index 0000000..f98b71e --- /dev/null +++ b/glib/gbookmarkfile.c @@ -0,0 +1,4022 @@ +/* gbookmarkfile.c: parsing and building desktop bookmarks + * + * Copyright (C) 2005-2006 Emmanuele Bassi + * + * 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 "gbookmarkfile.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gconvert.h" +#include "gdataset.h" +#include "gdatetime.h" +#include "gerror.h" +#include "gfileutils.h" +#include "ghash.h" +#include "glibintl.h" +#include "glist.h" +#include "gmain.h" +#include "gmarkup.h" +#include "gmem.h" +#include "gmessages.h" +#include "gshell.h" +#include "gslice.h" +#include "gstdio.h" +#include "gstring.h" +#include "gstrfuncs.h" +#include "gtimer.h" +#include "gutils.h" + + +/** + * SECTION:bookmarkfile + * @title: Bookmark file parser + * @short_description: parses files containing bookmarks + * + * GBookmarkFile lets you parse, edit or create files containing bookmarks + * to URI, along with some meta-data about the resource pointed by the URI + * like its MIME type, the application that is registering the bookmark and + * the icon that should be used to represent the bookmark. The data is stored + * using the + * [Desktop Bookmark Specification](http://www.gnome.org/~ebassi/bookmark-spec). + * + * The syntax of the bookmark files is described in detail inside the + * Desktop Bookmark Specification, here is a quick summary: bookmark + * files use a sub-class of the XML Bookmark Exchange Language + * specification, consisting of valid UTF-8 encoded XML, under the + * root element; each bookmark is stored inside a + * element, using its URI: no relative paths can + * be used inside a bookmark file. The bookmark may have a user defined + * title and description, to be used instead of the URI. Under the + * element, with its owner attribute set to + * `http://freedesktop.org`, is stored the meta-data about a resource + * pointed by its URI. The meta-data consists of the resource's MIME + * type; the applications that have registered a bookmark; the groups + * to which a bookmark belongs to; a visibility flag, used to set the + * bookmark as "private" to the applications and groups that has it + * registered; the URI and MIME type of an icon, to be used when + * displaying the bookmark inside a GUI. + * + * Here is an example of a bookmark file: + * [bookmarks.xbel](https://gitlab.gnome.org/GNOME/glib/-/blob/HEAD/glib/tests/bookmarks.xbel) + * + * A bookmark file might contain more than one bookmark; each bookmark + * is accessed through its URI. + * + * The important caveat of bookmark files is that when you add a new + * bookmark you must also add the application that is registering it, using + * g_bookmark_file_add_application() or g_bookmark_file_set_application_info(). + * If a bookmark has no applications then it won't be dumped when creating + * the on disk representation, using g_bookmark_file_to_data() or + * g_bookmark_file_to_file(). + * + * The #GBookmarkFile parser was added in GLib 2.12. + */ + +/* XBEL 1.0 standard entities */ +#define XBEL_VERSION "1.0" +#define XBEL_DTD_NICK "xbel" +#define XBEL_DTD_SYSTEM "+//IDN python.org//DTD XML Bookmark " \ + "Exchange Language 1.0//EN//XML" + +#define XBEL_DTD_URI "http://www.python.org/topics/xml/dtds/xbel-1.0.dtd" + +#define XBEL_ROOT_ELEMENT "xbel" +#define XBEL_FOLDER_ELEMENT "folder" /* unused */ +#define XBEL_BOOKMARK_ELEMENT "bookmark" +#define XBEL_ALIAS_ELEMENT "alias" /* unused */ +#define XBEL_SEPARATOR_ELEMENT "separator" /* unused */ +#define XBEL_TITLE_ELEMENT "title" +#define XBEL_DESC_ELEMENT "desc" +#define XBEL_INFO_ELEMENT "info" +#define XBEL_METADATA_ELEMENT "metadata" + +#define XBEL_VERSION_ATTRIBUTE "version" +#define XBEL_FOLDED_ATTRIBUTE "folded" /* unused */ +#define XBEL_OWNER_ATTRIBUTE "owner" +#define XBEL_ADDED_ATTRIBUTE "added" +#define XBEL_VISITED_ATTRIBUTE "visited" +#define XBEL_MODIFIED_ATTRIBUTE "modified" +#define XBEL_ID_ATTRIBUTE "id" +#define XBEL_HREF_ATTRIBUTE "href" +#define XBEL_REF_ATTRIBUTE "ref" /* unused */ + +#define XBEL_YES_VALUE "yes" +#define XBEL_NO_VALUE "no" + +/* Desktop bookmark spec entities */ +#define BOOKMARK_METADATA_OWNER "http://freedesktop.org" + +#define BOOKMARK_NAMESPACE_NAME "bookmark" +#define BOOKMARK_NAMESPACE_URI "http://www.freedesktop.org/standards/desktop-bookmarks" + +#define BOOKMARK_GROUPS_ELEMENT "groups" +#define BOOKMARK_GROUP_ELEMENT "group" +#define BOOKMARK_APPLICATIONS_ELEMENT "applications" +#define BOOKMARK_APPLICATION_ELEMENT "application" +#define BOOKMARK_ICON_ELEMENT "icon" +#define BOOKMARK_PRIVATE_ELEMENT "private" + +#define BOOKMARK_NAME_ATTRIBUTE "name" +#define BOOKMARK_EXEC_ATTRIBUTE "exec" +#define BOOKMARK_COUNT_ATTRIBUTE "count" +#define BOOKMARK_TIMESTAMP_ATTRIBUTE "timestamp" /* deprecated by "modified" */ +#define BOOKMARK_MODIFIED_ATTRIBUTE "modified" +#define BOOKMARK_HREF_ATTRIBUTE "href" +#define BOOKMARK_TYPE_ATTRIBUTE "type" + +/* Shared MIME Info entities */ +#define MIME_NAMESPACE_NAME "mime" +#define MIME_NAMESPACE_URI "http://www.freedesktop.org/standards/shared-mime-info" +#define MIME_TYPE_ELEMENT "mime-type" +#define MIME_TYPE_ATTRIBUTE "type" + + +typedef struct _BookmarkAppInfo BookmarkAppInfo; +typedef struct _BookmarkMetadata BookmarkMetadata; +typedef struct _BookmarkItem BookmarkItem; +typedef struct _ParseData ParseData; + +struct _BookmarkAppInfo +{ + gchar *name; + gchar *exec; + + guint count; + + GDateTime *stamp; /* (owned) */ +}; + +struct _BookmarkMetadata +{ + gchar *mime_type; + + GList *groups; + + GList *applications; + GHashTable *apps_by_name; + + gchar *icon_href; + gchar *icon_mime; + + guint is_private : 1; +}; + +struct _BookmarkItem +{ + gchar *uri; + + gchar *title; + gchar *description; + + GDateTime *added; /* (owned) */ + GDateTime *modified; /* (owned) */ + GDateTime *visited; /* (owned) */ + + BookmarkMetadata *metadata; +}; + +struct _GBookmarkFile +{ + gchar *title; + gchar *description; + + /* we store our items in a list and keep a copy inside + * a hash table for faster lookup performances + */ + GList *items; + GHashTable *items_by_uri; +}; + +/* parser state machine */ +typedef enum +{ + STATE_STARTED = 0, + + STATE_ROOT, + STATE_BOOKMARK, + STATE_TITLE, + STATE_DESC, + STATE_INFO, + STATE_METADATA, + STATE_APPLICATIONS, + STATE_APPLICATION, + STATE_GROUPS, + STATE_GROUP, + STATE_MIME, + STATE_ICON, + + STATE_FINISHED +} ParserState; + +static void g_bookmark_file_init (GBookmarkFile *bookmark); +static void g_bookmark_file_clear (GBookmarkFile *bookmark); +static gboolean g_bookmark_file_parse (GBookmarkFile *bookmark, + const gchar *buffer, + gsize length, + GError **error); +static gchar * g_bookmark_file_dump (GBookmarkFile *bookmark, + gsize *length, + GError **error); +static BookmarkItem *g_bookmark_file_lookup_item (GBookmarkFile *bookmark, + const gchar *uri); +static void g_bookmark_file_add_item (GBookmarkFile *bookmark, + BookmarkItem *item, + GError **error); + +static gboolean timestamp_from_iso8601 (const gchar *iso_date, + GDateTime **out_date_time, + GError **error); + +/******************************** + * BookmarkAppInfo * + * * + * Application metadata storage * + ********************************/ +static BookmarkAppInfo * +bookmark_app_info_new (const gchar *name) +{ + BookmarkAppInfo *retval; + + g_warn_if_fail (name != NULL); + + retval = g_slice_new (BookmarkAppInfo); + + retval->name = g_strdup (name); + retval->exec = NULL; + retval->count = 0; + retval->stamp = NULL; + + return retval; +} + +static void +bookmark_app_info_free (BookmarkAppInfo *app_info) +{ + if (!app_info) + return; + + g_free (app_info->name); + g_free (app_info->exec); + g_clear_pointer (&app_info->stamp, g_date_time_unref); + + g_slice_free (BookmarkAppInfo, app_info); +} + +static gchar * +bookmark_app_info_dump (BookmarkAppInfo *app_info) +{ + gchar *retval; + gchar *name, *exec, *modified, *count; + + g_warn_if_fail (app_info != NULL); + + if (app_info->count == 0) + return NULL; + + name = g_markup_escape_text (app_info->name, -1); + exec = g_markup_escape_text (app_info->exec, -1); + modified = g_date_time_format_iso8601 (app_info->stamp); + count = g_strdup_printf ("%u", app_info->count); + + retval = g_strconcat (" " + "<" BOOKMARK_NAMESPACE_NAME ":" BOOKMARK_APPLICATION_ELEMENT + " " BOOKMARK_NAME_ATTRIBUTE "=\"", name, "\"" + " " BOOKMARK_EXEC_ATTRIBUTE "=\"", exec, "\"" + " " BOOKMARK_MODIFIED_ATTRIBUTE "=\"", modified, "\"" + " " BOOKMARK_COUNT_ATTRIBUTE "=\"", count, "\"/>\n", + NULL); + + g_free (name); + g_free (exec); + g_free (modified); + g_free (count); + + return retval; +} + + +/*********************** + * BookmarkMetadata * + * * + * Metadata storage * + ***********************/ +static BookmarkMetadata * +bookmark_metadata_new (void) +{ + BookmarkMetadata *retval; + + retval = g_slice_new (BookmarkMetadata); + + retval->mime_type = NULL; + + retval->groups = NULL; + + retval->applications = NULL; + retval->apps_by_name = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + NULL); + + retval->is_private = FALSE; + + retval->icon_href = NULL; + retval->icon_mime = NULL; + + return retval; +} + +static void +bookmark_metadata_free (BookmarkMetadata *metadata) +{ + if (!metadata) + return; + + g_free (metadata->mime_type); + + g_list_free_full (metadata->groups, g_free); + g_list_free_full (metadata->applications, (GDestroyNotify) bookmark_app_info_free); + + g_hash_table_destroy (metadata->apps_by_name); + + g_free (metadata->icon_href); + g_free (metadata->icon_mime); + + g_slice_free (BookmarkMetadata, metadata); +} + +static gchar * +bookmark_metadata_dump (BookmarkMetadata *metadata) +{ + GString *retval; + gchar *buffer; + + if (!metadata->applications) + return NULL; + + retval = g_string_sized_new (1024); + + /* metadata container */ + g_string_append (retval, + " " + "<" XBEL_METADATA_ELEMENT + " " XBEL_OWNER_ATTRIBUTE "=\"" BOOKMARK_METADATA_OWNER + "\">\n"); + + /* mime type */ + if (metadata->mime_type) { + buffer = g_strconcat (" " + "<" MIME_NAMESPACE_NAME ":" MIME_TYPE_ELEMENT " " + MIME_TYPE_ATTRIBUTE "=\"", metadata->mime_type, "\"/>\n", + NULL); + g_string_append (retval, buffer); + g_free (buffer); + } + + if (metadata->groups) + { + GList *l; + + /* open groups container */ + g_string_append (retval, + " " + "<" BOOKMARK_NAMESPACE_NAME + ":" BOOKMARK_GROUPS_ELEMENT ">\n"); + + for (l = g_list_last (metadata->groups); l != NULL; l = l->prev) + { + gchar *group_name; + + group_name = g_markup_escape_text ((gchar *) l->data, -1); + buffer = g_strconcat (" " + "<" BOOKMARK_NAMESPACE_NAME + ":" BOOKMARK_GROUP_ELEMENT ">", + group_name, + "\n", NULL); + g_string_append (retval, buffer); + + g_free (buffer); + g_free (group_name); + } + + /* close groups container */ + g_string_append (retval, + " " + "\n"); + } + + if (metadata->applications) + { + GList *l; + + /* open applications container */ + g_string_append (retval, + " " + "<" BOOKMARK_NAMESPACE_NAME + ":" BOOKMARK_APPLICATIONS_ELEMENT ">\n"); + + for (l = g_list_last (metadata->applications); l != NULL; l = l->prev) + { + BookmarkAppInfo *app_info = (BookmarkAppInfo *) l->data; + gchar *app_data; + + g_warn_if_fail (app_info != NULL); + + app_data = bookmark_app_info_dump (app_info); + + if (app_data) + { + retval = g_string_append (retval, app_data); + + g_free (app_data); + } + } + + /* close applications container */ + g_string_append (retval, + " " + "\n"); + } + + /* icon */ + if (metadata->icon_href) + { + if (!metadata->icon_mime) + metadata->icon_mime = g_strdup ("application/octet-stream"); + + buffer = g_strconcat (" " + "<" BOOKMARK_NAMESPACE_NAME + ":" BOOKMARK_ICON_ELEMENT + " " BOOKMARK_HREF_ATTRIBUTE "=\"", metadata->icon_href, + "\" " BOOKMARK_TYPE_ATTRIBUTE "=\"", metadata->icon_mime, "\"/>\n", NULL); + g_string_append (retval, buffer); + + g_free (buffer); + } + + /* private hint */ + if (metadata->is_private) + g_string_append (retval, + " " + "<" BOOKMARK_NAMESPACE_NAME + ":" BOOKMARK_PRIVATE_ELEMENT "/>\n"); + + /* close metadata container */ + g_string_append (retval, + " " + "\n"); + + return g_string_free (retval, FALSE); +} + +/****************************************************** + * BookmarkItem * + * * + * Storage for a single bookmark item inside the list * + ******************************************************/ +static BookmarkItem * +bookmark_item_new (const gchar *uri) +{ + BookmarkItem *item; + + g_warn_if_fail (uri != NULL); + + item = g_slice_new (BookmarkItem); + item->uri = g_strdup (uri); + + item->title = NULL; + item->description = NULL; + + item->added = NULL; + item->modified = NULL; + item->visited = NULL; + + item->metadata = NULL; + + return item; +} + +static void +bookmark_item_free (BookmarkItem *item) +{ + if (!item) + return; + + g_free (item->uri); + g_free (item->title); + g_free (item->description); + + if (item->metadata) + bookmark_metadata_free (item->metadata); + + g_clear_pointer (&item->added, g_date_time_unref); + g_clear_pointer (&item->modified, g_date_time_unref); + g_clear_pointer (&item->visited, g_date_time_unref); + + g_slice_free (BookmarkItem, item); +} + +static void +bookmark_item_touch_modified (BookmarkItem *item) +{ + g_clear_pointer (&item->modified, g_date_time_unref); + item->modified = g_date_time_new_now_utc (); +} + +static gchar * +bookmark_item_dump (BookmarkItem *item) +{ + GString *retval; + gchar *escaped_uri; + + /* at this point, we must have at least a registered application; if we don't + * we don't screw up the bookmark file, and just skip this item + */ + if (!item->metadata || !item->metadata->applications) + { + g_warning ("Item for URI '%s' has no registered applications: skipping.", item->uri); + return NULL; + } + + retval = g_string_sized_new (4096); + + g_string_append (retval, " <" XBEL_BOOKMARK_ELEMENT " "); + + escaped_uri = g_markup_escape_text (item->uri, -1); + + g_string_append (retval, XBEL_HREF_ATTRIBUTE "=\""); + g_string_append (retval, escaped_uri); + g_string_append (retval , "\" "); + + g_free (escaped_uri); + + if (item->added) + { + char *added; + + added = g_date_time_format_iso8601 (item->added); + g_string_append (retval, XBEL_ADDED_ATTRIBUTE "=\""); + g_string_append (retval, added); + g_string_append (retval, "\" "); + g_free (added); + } + + if (item->modified) + { + char *modified; + + modified = g_date_time_format_iso8601 (item->modified); + g_string_append (retval, XBEL_MODIFIED_ATTRIBUTE "=\""); + g_string_append (retval, modified); + g_string_append (retval, "\" "); + g_free (modified); + } + + if (item->visited) + { + char *visited; + + visited = g_date_time_format_iso8601 (item->visited); + g_string_append (retval, XBEL_VISITED_ATTRIBUTE "=\""); + g_string_append (retval, visited); + g_string_append (retval, "\" "); + g_free (visited); + } + + if (retval->str[retval->len - 1] == ' ') + g_string_truncate (retval, retval->len - 1); + g_string_append (retval, ">\n"); + + if (item->title) + { + gchar *escaped_title; + + escaped_title = g_markup_escape_text (item->title, -1); + g_string_append (retval, " " "<" XBEL_TITLE_ELEMENT ">"); + g_string_append (retval, escaped_title); + g_string_append (retval, "\n"); + + g_free (escaped_title); + } + + if (item->description) + { + gchar *escaped_desc; + + escaped_desc = g_markup_escape_text (item->description, -1); + g_string_append (retval, " " "<" XBEL_DESC_ELEMENT ">"); + g_string_append (retval, escaped_desc); + g_string_append (retval, "\n"); + + g_free (escaped_desc); + } + + if (item->metadata) + { + gchar *metadata; + + metadata = bookmark_metadata_dump (item->metadata); + if (metadata) + { + g_string_append (retval, " " "<" XBEL_INFO_ELEMENT ">\n"); + g_string_append (retval, metadata); + g_string_append (retval, " " "\n"); + + g_free (metadata); + } + } + + g_string_append (retval, " \n"); + + return g_string_free (retval, FALSE); +} + +static BookmarkAppInfo * +bookmark_item_lookup_app_info (BookmarkItem *item, + const gchar *app_name) +{ + g_warn_if_fail (item != NULL && app_name != NULL); + + if (!item->metadata) + return NULL; + + return g_hash_table_lookup (item->metadata->apps_by_name, app_name); +} + +/************************* + * GBookmarkFile * + *************************/ + +static void +g_bookmark_file_init (GBookmarkFile *bookmark) +{ + bookmark->title = NULL; + bookmark->description = NULL; + + bookmark->items = NULL; + bookmark->items_by_uri = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + NULL); +} + +static void +g_bookmark_file_clear (GBookmarkFile *bookmark) +{ + g_free (bookmark->title); + g_free (bookmark->description); + + g_list_free_full (bookmark->items, (GDestroyNotify) bookmark_item_free); + bookmark->items = NULL; + + if (bookmark->items_by_uri) + { + g_hash_table_destroy (bookmark->items_by_uri); + + bookmark->items_by_uri = NULL; + } +} + +struct _ParseData +{ + ParserState state; + + GHashTable *namespaces; + + GBookmarkFile *bookmark_file; + BookmarkItem *current_item; +}; + +static ParseData * +parse_data_new (void) +{ + ParseData *retval; + + retval = g_new (ParseData, 1); + + retval->state = STATE_STARTED; + retval->namespaces = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + retval->bookmark_file = NULL; + retval->current_item = NULL; + + return retval; +} + +static void +parse_data_free (ParseData *parse_data) +{ + g_hash_table_destroy (parse_data->namespaces); + + g_free (parse_data); +} + +#define IS_ATTRIBUTE(s,a) ((0 == strcmp ((s), (a)))) + +static void +parse_bookmark_element (GMarkupParseContext *context, + ParseData *parse_data, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + const gchar *uri, *added, *modified, *visited; + const gchar *attr; + gint i; + BookmarkItem *item; + GError *add_error; + + g_warn_if_fail ((parse_data != NULL) && (parse_data->state == STATE_BOOKMARK)); + + i = 0; + uri = added = modified = visited = NULL; + for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i]) + { + if (IS_ATTRIBUTE (attr, XBEL_HREF_ATTRIBUTE)) + uri = attribute_values[i]; + else if (IS_ATTRIBUTE (attr, XBEL_ADDED_ATTRIBUTE)) + added = attribute_values[i]; + else if (IS_ATTRIBUTE (attr, XBEL_MODIFIED_ATTRIBUTE)) + modified = attribute_values[i]; + else if (IS_ATTRIBUTE (attr, XBEL_VISITED_ATTRIBUTE)) + visited = attribute_values[i]; + else + { + /* bookmark is defined by the XBEL spec, so we need + * to error out if the element has different or + * missing attributes + */ + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + _("Unexpected attribute “%s” for element “%s”"), + attr, + XBEL_BOOKMARK_ELEMENT); + return; + } + } + + if (!uri) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Attribute “%s” of element “%s” not found"), + XBEL_HREF_ATTRIBUTE, + XBEL_BOOKMARK_ELEMENT); + return; + } + + g_warn_if_fail (parse_data->current_item == NULL); + + item = bookmark_item_new (uri); + + if (added != NULL && !timestamp_from_iso8601 (added, &item->added, error)) + { + bookmark_item_free (item); + return; + } + + if (modified != NULL && !timestamp_from_iso8601 (modified, &item->modified, error)) + { + bookmark_item_free (item); + return; + } + + if (visited != NULL && !timestamp_from_iso8601 (visited, &item->visited, error)) + { + bookmark_item_free (item); + return; + } + + add_error = NULL; + g_bookmark_file_add_item (parse_data->bookmark_file, + item, + &add_error); + if (add_error) + { + bookmark_item_free (item); + + g_propagate_error (error, add_error); + + return; + } + + parse_data->current_item = item; +} + +static void +parse_application_element (GMarkupParseContext *context, + ParseData *parse_data, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + const gchar *name, *exec, *count, *stamp, *modified; + const gchar *attr; + gint i; + BookmarkItem *item; + BookmarkAppInfo *ai; + + g_warn_if_fail ((parse_data != NULL) && (parse_data->state == STATE_APPLICATION)); + + i = 0; + name = exec = count = stamp = modified = NULL; + for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i]) + { + if (IS_ATTRIBUTE (attr, BOOKMARK_NAME_ATTRIBUTE)) + name = attribute_values[i]; + else if (IS_ATTRIBUTE (attr, BOOKMARK_EXEC_ATTRIBUTE)) + exec = attribute_values[i]; + else if (IS_ATTRIBUTE (attr, BOOKMARK_COUNT_ATTRIBUTE)) + count = attribute_values[i]; + else if (IS_ATTRIBUTE (attr, BOOKMARK_TIMESTAMP_ATTRIBUTE)) + stamp = attribute_values[i]; + else if (IS_ATTRIBUTE (attr, BOOKMARK_MODIFIED_ATTRIBUTE)) + modified = attribute_values[i]; + } + + /* the "name" and "exec" attributes are mandatory */ + if (!name) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Attribute “%s” of element “%s” not found"), + BOOKMARK_NAME_ATTRIBUTE, + BOOKMARK_APPLICATION_ELEMENT); + return; + } + + if (!exec) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Attribute “%s” of element “%s” not found"), + BOOKMARK_EXEC_ATTRIBUTE, + BOOKMARK_APPLICATION_ELEMENT); + return; + } + + g_warn_if_fail (parse_data->current_item != NULL); + item = parse_data->current_item; + + ai = bookmark_item_lookup_app_info (item, name); + if (!ai) + { + ai = bookmark_app_info_new (name); + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + item->metadata->applications = g_list_prepend (item->metadata->applications, ai); + g_hash_table_replace (item->metadata->apps_by_name, ai->name, ai); + } + + g_free (ai->exec); + ai->exec = g_strdup (exec); + + if (count) + ai->count = atoi (count); + else + ai->count = 1; + + g_clear_pointer (&ai->stamp, g_date_time_unref); + if (modified != NULL) + { + if (!timestamp_from_iso8601 (modified, &ai->stamp, error)) + return; + } + else + { + /* the timestamp attribute has been deprecated but we still parse + * it for backward compatibility + */ + if (stamp) + ai->stamp = g_date_time_new_from_unix_utc (atol (stamp)); + else + ai->stamp = g_date_time_new_now_utc (); + } +} + +static void +parse_mime_type_element (GMarkupParseContext *context, + ParseData *parse_data, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + const gchar *type; + const gchar *attr; + gint i; + BookmarkItem *item; + + g_warn_if_fail ((parse_data != NULL) && (parse_data->state == STATE_MIME)); + + i = 0; + type = NULL; + for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i]) + { + if (IS_ATTRIBUTE (attr, MIME_TYPE_ATTRIBUTE)) + type = attribute_values[i]; + } + + if (!type) + type = "application/octet-stream"; + + g_warn_if_fail (parse_data->current_item != NULL); + item = parse_data->current_item; + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + g_free (item->metadata->mime_type); + item->metadata->mime_type = g_strdup (type); +} + +static void +parse_icon_element (GMarkupParseContext *context, + ParseData *parse_data, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + const gchar *href; + const gchar *type; + const gchar *attr; + gint i; + BookmarkItem *item; + + g_warn_if_fail ((parse_data != NULL) && (parse_data->state == STATE_ICON)); + + i = 0; + href = NULL; + type = NULL; + for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i]) + { + if (IS_ATTRIBUTE (attr, BOOKMARK_HREF_ATTRIBUTE)) + href = attribute_values[i]; + else if (IS_ATTRIBUTE (attr, BOOKMARK_TYPE_ATTRIBUTE)) + type = attribute_values[i]; + } + + /* the "href" attribute is mandatory */ + if (!href) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Attribute “%s” of element “%s” not found"), + BOOKMARK_HREF_ATTRIBUTE, + BOOKMARK_ICON_ELEMENT); + return; + } + + if (!type) + type = "application/octet-stream"; + + g_warn_if_fail (parse_data->current_item != NULL); + item = parse_data->current_item; + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + g_free (item->metadata->icon_href); + g_free (item->metadata->icon_mime); + item->metadata->icon_href = g_strdup (href); + item->metadata->icon_mime = g_strdup (type); +} + +/* scans through the attributes of an element for the "xmlns" pragma, and + * adds any resulting namespace declaration to a per-parser hashtable, using + * the namespace name as a key for the namespace URI; if no key was found, + * the namespace is considered as default, and stored under the "default" key. + * + * FIXME: this works on the assumption that the generator of the XBEL file + * is either this code or is smart enough to place the namespace declarations + * inside the main root node or inside the metadata node and does not redefine + * a namespace inside an inner node; this does *not* conform to the + * XML-NS standard, although is a close approximation. In order to make this + * conformant to the XML-NS specification we should use a per-element + * namespace table inside GMarkup and ask it to resolve the namespaces for us. + */ +static void +map_namespace_to_name (ParseData *parse_data, + const gchar **attribute_names, + const gchar **attribute_values) +{ + const gchar *attr; + gint i; + + g_warn_if_fail (parse_data != NULL); + + if (!attribute_names || !attribute_names[0]) + return; + + i = 0; + for (attr = attribute_names[i]; attr; attr = attribute_names[++i]) + { + if (g_str_has_prefix (attr, "xmlns")) + { + gchar *namespace_name, *namespace_uri; + gchar *p; + + p = g_utf8_strchr (attr, -1, ':'); + if (p) + p = g_utf8_next_char (p); + else + p = "default"; + + namespace_name = g_strdup (p); + namespace_uri = g_strdup (attribute_values[i]); + + g_hash_table_replace (parse_data->namespaces, + namespace_name, + namespace_uri); + } + } +} + +/* checks whether @element_full is equal to @element. + * + * if @namespace is set, it tries to resolve the namespace to a known URI, + * and if found is prepended to the element name, from which is separated + * using the character specified in the @sep parameter. + */ +static gboolean +is_element_full (ParseData *parse_data, + const gchar *element_full, + const gchar *namespace, + const gchar *element, + const gchar sep) +{ + gchar *ns_uri, *ns_name; + const gchar *p, *element_name; + gboolean retval; + + g_warn_if_fail (parse_data != NULL); + g_warn_if_fail (element_full != NULL); + + if (!element) + return FALSE; + + /* no namespace requested: dumb element compare */ + if (!namespace) + return (0 == strcmp (element_full, element)); + + /* search for namespace separator; if none found, assume we are under the + * default namespace, and set ns_name to our "default" marker; if no default + * namespace has been set, just do a plain comparison between @full_element + * and @element. + */ + p = g_utf8_strchr (element_full, -1, ':'); + if (p) + { + ns_name = g_strndup (element_full, p - element_full); + element_name = g_utf8_next_char (p); + } + else + { + ns_name = g_strdup ("default"); + element_name = element_full; + } + + ns_uri = g_hash_table_lookup (parse_data->namespaces, ns_name); + if (!ns_uri) + { + /* no default namespace found */ + g_free (ns_name); + + return (0 == strcmp (element_full, element)); + } + + retval = (0 == strcmp (ns_uri, namespace) && + 0 == strcmp (element_name, element)); + + g_free (ns_name); + + return retval; +} + +#define IS_ELEMENT(p,s,e) (is_element_full ((p), (s), NULL, (e), '\0')) +#define IS_ELEMENT_NS(p,s,n,e) (is_element_full ((p), (s), (n), (e), '|')) + +static const gchar * +parser_state_to_element_name (ParserState state) +{ + switch (state) + { + case STATE_STARTED: + case STATE_FINISHED: + return "(top-level)"; + case STATE_ROOT: + return XBEL_ROOT_ELEMENT; + case STATE_BOOKMARK: + return XBEL_BOOKMARK_ELEMENT; + case STATE_TITLE: + return XBEL_TITLE_ELEMENT; + case STATE_DESC: + return XBEL_DESC_ELEMENT; + case STATE_INFO: + return XBEL_INFO_ELEMENT; + case STATE_METADATA: + return XBEL_METADATA_ELEMENT; + case STATE_APPLICATIONS: + return BOOKMARK_APPLICATIONS_ELEMENT; + case STATE_APPLICATION: + return BOOKMARK_APPLICATION_ELEMENT; + case STATE_GROUPS: + return BOOKMARK_GROUPS_ELEMENT; + case STATE_GROUP: + return BOOKMARK_GROUP_ELEMENT; + case STATE_MIME: + return MIME_TYPE_ELEMENT; + case STATE_ICON: + return BOOKMARK_ICON_ELEMENT; + default: + g_assert_not_reached (); + } +} + +static void +start_element_raw_cb (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseData *parse_data = (ParseData *) user_data; + + /* we must check for namespace declarations first + * + * XXX - we could speed up things by checking for namespace declarations + * only on the root node, where they usually are; this would probably break + * on streams not produced by us or by "smart" generators + */ + map_namespace_to_name (parse_data, attribute_names, attribute_values); + + switch (parse_data->state) + { + case STATE_STARTED: + if (IS_ELEMENT (parse_data, element_name, XBEL_ROOT_ELEMENT)) + { + const gchar *attr; + gint i; + + i = 0; + for (attr = attribute_names[i]; attr; attr = attribute_names[++i]) + { + if ((IS_ATTRIBUTE (attr, XBEL_VERSION_ATTRIBUTE)) && + (0 == strcmp (attribute_values[i], XBEL_VERSION))) + parse_data->state = STATE_ROOT; + } + } + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Unexpected tag “%s”, tag “%s” expected"), + element_name, XBEL_ROOT_ELEMENT); + break; + case STATE_ROOT: + if (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT)) + parse_data->state = STATE_TITLE; + else if (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT)) + parse_data->state = STATE_DESC; + else if (IS_ELEMENT (parse_data, element_name, XBEL_BOOKMARK_ELEMENT)) + { + GError *inner_error = NULL; + + parse_data->state = STATE_BOOKMARK; + + parse_bookmark_element (context, + parse_data, + attribute_names, + attribute_values, + &inner_error); + if (inner_error) + g_propagate_error (error, inner_error); + } + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Unexpected tag “%s” inside “%s”"), + element_name, + XBEL_ROOT_ELEMENT); + break; + case STATE_BOOKMARK: + if (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT)) + parse_data->state = STATE_TITLE; + else if (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT)) + parse_data->state = STATE_DESC; + else if (IS_ELEMENT (parse_data, element_name, XBEL_INFO_ELEMENT)) + parse_data->state = STATE_INFO; + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Unexpected tag “%s” inside “%s”"), + element_name, + XBEL_BOOKMARK_ELEMENT); + break; + case STATE_INFO: + if (IS_ELEMENT (parse_data, element_name, XBEL_METADATA_ELEMENT)) + { + const gchar *attr; + gint i; + + i = 0; + for (attr = attribute_names[i]; attr; attr = attribute_names[++i]) + { + if ((IS_ATTRIBUTE (attr, XBEL_OWNER_ATTRIBUTE)) && + (0 == strcmp (attribute_values[i], BOOKMARK_METADATA_OWNER))) + { + parse_data->state = STATE_METADATA; + + if (!parse_data->current_item->metadata) + parse_data->current_item->metadata = bookmark_metadata_new (); + } + } + } + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Unexpected tag “%s”, tag “%s” expected"), + element_name, + XBEL_METADATA_ELEMENT); + break; + case STATE_METADATA: + if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATIONS_ELEMENT)) + parse_data->state = STATE_APPLICATIONS; + else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUPS_ELEMENT)) + parse_data->state = STATE_GROUPS; + else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_PRIVATE_ELEMENT)) + parse_data->current_item->metadata->is_private = TRUE; + else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_ICON_ELEMENT)) + { + GError *inner_error = NULL; + + parse_data->state = STATE_ICON; + + parse_icon_element (context, + parse_data, + attribute_names, + attribute_values, + &inner_error); + if (inner_error) + g_propagate_error (error, inner_error); + } + else if (IS_ELEMENT_NS (parse_data, element_name, MIME_NAMESPACE_URI, MIME_TYPE_ELEMENT)) + { + GError *inner_error = NULL; + + parse_data->state = STATE_MIME; + + parse_mime_type_element (context, + parse_data, + attribute_names, + attribute_values, + &inner_error); + if (inner_error) + g_propagate_error (error, inner_error); + } + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ELEMENT, + _("Unexpected tag “%s” inside “%s”"), + element_name, + XBEL_METADATA_ELEMENT); + break; + case STATE_APPLICATIONS: + if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATION_ELEMENT)) + { + GError *inner_error = NULL; + + parse_data->state = STATE_APPLICATION; + + parse_application_element (context, + parse_data, + attribute_names, + attribute_values, + &inner_error); + if (inner_error) + g_propagate_error (error, inner_error); + } + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Unexpected tag “%s”, tag “%s” expected"), + element_name, + BOOKMARK_APPLICATION_ELEMENT); + break; + case STATE_GROUPS: + if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUP_ELEMENT)) + parse_data->state = STATE_GROUP; + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Unexpected tag “%s”, tag “%s” expected"), + element_name, + BOOKMARK_GROUP_ELEMENT); + break; + + case STATE_TITLE: + case STATE_DESC: + case STATE_APPLICATION: + case STATE_GROUP: + case STATE_MIME: + case STATE_ICON: + case STATE_FINISHED: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Unexpected tag “%s” inside “%s”"), + element_name, + parser_state_to_element_name (parse_data->state)); + break; + + default: + g_assert_not_reached (); + break; + } +} + +static void +end_element_raw_cb (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseData *parse_data = (ParseData *) user_data; + + if (IS_ELEMENT (parse_data, element_name, XBEL_ROOT_ELEMENT)) + parse_data->state = STATE_FINISHED; + else if (IS_ELEMENT (parse_data, element_name, XBEL_BOOKMARK_ELEMENT)) + { + parse_data->current_item = NULL; + + parse_data->state = STATE_ROOT; + } + else if ((IS_ELEMENT (parse_data, element_name, XBEL_INFO_ELEMENT)) || + (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT)) || + (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT))) + { + if (parse_data->current_item) + parse_data->state = STATE_BOOKMARK; + else + parse_data->state = STATE_ROOT; + } + else if (IS_ELEMENT (parse_data, element_name, XBEL_METADATA_ELEMENT)) + parse_data->state = STATE_INFO; + else if (IS_ELEMENT_NS (parse_data, element_name, + BOOKMARK_NAMESPACE_URI, + BOOKMARK_APPLICATION_ELEMENT)) + parse_data->state = STATE_APPLICATIONS; + else if (IS_ELEMENT_NS (parse_data, element_name, + BOOKMARK_NAMESPACE_URI, + BOOKMARK_GROUP_ELEMENT)) + parse_data->state = STATE_GROUPS; + else if ((IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATIONS_ELEMENT)) || + (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUPS_ELEMENT)) || + (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_PRIVATE_ELEMENT)) || + (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_ICON_ELEMENT)) || + (IS_ELEMENT_NS (parse_data, element_name, MIME_NAMESPACE_URI, MIME_TYPE_ELEMENT))) + parse_data->state = STATE_METADATA; +} + +static void +text_raw_cb (GMarkupParseContext *context, + const gchar *text, + gsize length, + gpointer user_data, + GError **error) +{ + ParseData *parse_data = (ParseData *) user_data; + gchar *payload; + + payload = g_strndup (text, length); + + switch (parse_data->state) + { + case STATE_TITLE: + if (parse_data->current_item) + { + g_free (parse_data->current_item->title); + parse_data->current_item->title = g_strdup (payload); + } + else + { + g_free (parse_data->bookmark_file->title); + parse_data->bookmark_file->title = g_strdup (payload); + } + break; + case STATE_DESC: + if (parse_data->current_item) + { + g_free (parse_data->current_item->description); + parse_data->current_item->description = g_strdup (payload); + } + else + { + g_free (parse_data->bookmark_file->description); + parse_data->bookmark_file->description = g_strdup (payload); + } + break; + case STATE_GROUP: + { + GList *groups; + + g_warn_if_fail (parse_data->current_item != NULL); + + if (!parse_data->current_item->metadata) + parse_data->current_item->metadata = bookmark_metadata_new (); + + groups = parse_data->current_item->metadata->groups; + parse_data->current_item->metadata->groups = g_list_prepend (groups, g_strdup (payload)); + } + break; + case STATE_ROOT: + case STATE_BOOKMARK: + case STATE_INFO: + case STATE_METADATA: + case STATE_APPLICATIONS: + case STATE_APPLICATION: + case STATE_GROUPS: + case STATE_MIME: + case STATE_ICON: + break; + default: + g_warn_if_reached (); + break; + } + + g_free (payload); +} + +static const GMarkupParser markup_parser = +{ + start_element_raw_cb, /* start_element */ + end_element_raw_cb, /* end_element */ + text_raw_cb, /* text */ + NULL, /* passthrough */ + NULL +}; + +static gboolean +g_bookmark_file_parse (GBookmarkFile *bookmark, + const gchar *buffer, + gsize length, + GError **error) +{ + GMarkupParseContext *context; + ParseData *parse_data; + GError *parse_error, *end_error; + gboolean retval; + + g_warn_if_fail (bookmark != NULL); + + if (!buffer) + return FALSE; + + parse_error = NULL; + end_error = NULL; + + if (length == (gsize) -1) + length = strlen (buffer); + + parse_data = parse_data_new (); + parse_data->bookmark_file = bookmark; + + context = g_markup_parse_context_new (&markup_parser, + G_MARKUP_PARSE_FLAGS_NONE, + parse_data, + (GDestroyNotify) parse_data_free); + + retval = g_markup_parse_context_parse (context, + buffer, + length, + &parse_error); + if (!retval) + g_propagate_error (error, parse_error); + else + { + retval = g_markup_parse_context_end_parse (context, &end_error); + if (!retval) + g_propagate_error (error, end_error); + } + + g_markup_parse_context_free (context); + + return retval; +} + +static gchar * +g_bookmark_file_dump (GBookmarkFile *bookmark, + gsize *length, + GError **error) +{ + GString *retval; + gchar *buffer; + GList *l; + + retval = g_string_sized_new (4096); + + g_string_append (retval, + "\n" +#if 0 + /* XXX - do we really need the doctype? */ + "\n" +#endif + "<" XBEL_ROOT_ELEMENT " " XBEL_VERSION_ATTRIBUTE "=\"" XBEL_VERSION "\"\n" + " xmlns:" BOOKMARK_NAMESPACE_NAME "=\"" BOOKMARK_NAMESPACE_URI "\"\n" + " xmlns:" MIME_NAMESPACE_NAME "=\"" MIME_NAMESPACE_URI "\"\n>"); + + if (bookmark->title) + { + gchar *escaped_title; + + escaped_title = g_markup_escape_text (bookmark->title, -1); + + buffer = g_strconcat (" " + "<" XBEL_TITLE_ELEMENT ">", + escaped_title, + "\n", NULL); + + g_string_append (retval, buffer); + + g_free (buffer); + g_free (escaped_title); + } + + if (bookmark->description) + { + gchar *escaped_desc; + + escaped_desc = g_markup_escape_text (bookmark->description, -1); + + buffer = g_strconcat (" " + "<" XBEL_DESC_ELEMENT ">", + escaped_desc, + "\n", NULL); + g_string_append (retval, buffer); + + g_free (buffer); + g_free (escaped_desc); + } + + if (!bookmark->items) + goto out; + else + retval = g_string_append (retval, "\n"); + + /* the items are stored in reverse order */ + for (l = g_list_last (bookmark->items); + l != NULL; + l = l->prev) + { + BookmarkItem *item = (BookmarkItem *) l->data; + gchar *item_dump; + + item_dump = bookmark_item_dump (item); + if (!item_dump) + continue; + + retval = g_string_append (retval, item_dump); + + g_free (item_dump); + } + +out: + g_string_append (retval, ""); + + if (length) + *length = retval->len; + + return g_string_free (retval, FALSE); +} + +/************** + * Misc * + **************/ + +static gboolean +timestamp_from_iso8601 (const gchar *iso_date, + GDateTime **out_date_time, + GError **error) +{ + GDateTime *dt = g_date_time_new_from_iso8601 (iso_date, NULL); + if (dt == NULL) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, G_BOOKMARK_FILE_ERROR_READ, + _("Invalid date/time ‘%s’ in bookmark file"), iso_date); + return FALSE; + } + + *out_date_time = g_steal_pointer (&dt); + return TRUE; +} + +G_DEFINE_QUARK (g-bookmark-file-error-quark, g_bookmark_file_error) + +/******************** + * Public API * + ********************/ + +/** + * g_bookmark_file_new: (constructor) + * + * Creates a new empty #GBookmarkFile object. + * + * Use g_bookmark_file_load_from_file(), g_bookmark_file_load_from_data() + * or g_bookmark_file_load_from_data_dirs() to read an existing bookmark + * file. + * + * Returns: an empty #GBookmarkFile + * + * Since: 2.12 + */ +GBookmarkFile * +g_bookmark_file_new (void) +{ + GBookmarkFile *bookmark; + + bookmark = g_new (GBookmarkFile, 1); + + g_bookmark_file_init (bookmark); + + return bookmark; +} + +/** + * g_bookmark_file_free: + * @bookmark: a #GBookmarkFile + * + * Frees a #GBookmarkFile. + * + * Since: 2.12 + */ +void +g_bookmark_file_free (GBookmarkFile *bookmark) +{ + if (!bookmark) + return; + + g_bookmark_file_clear (bookmark); + + g_free (bookmark); +} + +/** + * g_bookmark_file_load_from_data: + * @bookmark: an empty #GBookmarkFile struct + * @data: (array length=length) (element-type guint8): desktop bookmarks + * loaded in memory + * @length: the length of @data in bytes + * @error: return location for a #GError, or %NULL + * + * Loads a bookmark file from memory into an empty #GBookmarkFile + * structure. If the object cannot be created then @error is set to a + * #GBookmarkFileError. + * + * Returns: %TRUE if a desktop bookmark could be loaded. + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_load_from_data (GBookmarkFile *bookmark, + const gchar *data, + gsize length, + GError **error) +{ + GError *parse_error; + gboolean retval; + + g_return_val_if_fail (bookmark != NULL, FALSE); + + if (length == (gsize) -1) + length = strlen (data); + + if (bookmark->items) + { + g_bookmark_file_clear (bookmark); + g_bookmark_file_init (bookmark); + } + + parse_error = NULL; + retval = g_bookmark_file_parse (bookmark, data, length, &parse_error); + + if (!retval) + g_propagate_error (error, parse_error); + + return retval; +} + +/** + * g_bookmark_file_load_from_file: + * @bookmark: an empty #GBookmarkFile struct + * @filename: (type filename): the path of a filename to load, in the + * GLib file name encoding + * @error: return location for a #GError, or %NULL + * + * Loads a desktop bookmark file into an empty #GBookmarkFile structure. + * If the file could not be loaded then @error is set to either a #GFileError + * or #GBookmarkFileError. + * + * Returns: %TRUE if a desktop bookmark file could be loaded + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_load_from_file (GBookmarkFile *bookmark, + const gchar *filename, + GError **error) +{ + gboolean ret = FALSE; + gchar *buffer = NULL; + gsize len; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + if (!g_file_get_contents (filename, &buffer, &len, error)) + goto out; + + if (!g_bookmark_file_load_from_data (bookmark, buffer, len, error)) + goto out; + + ret = TRUE; + out: + g_free (buffer); + return ret; +} + + +/* Iterates through all the directories in *dirs trying to + * find file. When it successfully locates file, returns a + * string its absolute path. It also leaves the unchecked + * directories in *dirs. You should free the returned string + * + * Adapted from gkeyfile.c + */ +static gchar * +find_file_in_data_dirs (const gchar *file, + gchar ***dirs, + GError **error) +{ + gchar **data_dirs, *data_dir, *path; + + path = NULL; + + if (dirs == NULL) + return NULL; + + data_dirs = *dirs; + path = NULL; + while (data_dirs && (data_dir = *data_dirs) && !path) + { + gchar *candidate_file, *sub_dir; + + candidate_file = (gchar *) file; + sub_dir = g_strdup (""); + while (candidate_file != NULL && !path) + { + gchar *p; + + path = g_build_filename (data_dir, sub_dir, + candidate_file, NULL); + + candidate_file = strchr (candidate_file, '-'); + + if (candidate_file == NULL) + break; + + candidate_file++; + + g_free (sub_dir); + sub_dir = g_strndup (file, candidate_file - file - 1); + + for (p = sub_dir; *p != '\0'; p++) + { + if (*p == '-') + *p = G_DIR_SEPARATOR; + } + } + g_free (sub_dir); + data_dirs++; + } + + *dirs = data_dirs; + + if (!path) + { + g_set_error_literal (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_FILE_NOT_FOUND, + _("No valid bookmark file found in data dirs")); + + return NULL; + } + + return path; +} + + +/** + * g_bookmark_file_load_from_data_dirs: + * @bookmark: a #GBookmarkFile + * @file: (type filename): a relative path to a filename to open and parse + * @full_path: (out) (optional) (type filename): return location for a string + * containing the full path of the file, or %NULL + * @error: return location for a #GError, or %NULL + * + * This function looks for a desktop bookmark file named @file in the + * paths returned from g_get_user_data_dir() and g_get_system_data_dirs(), + * loads the file into @bookmark and returns the file's full path in + * @full_path. If the file could not be loaded then @error is + * set to either a #GFileError or #GBookmarkFileError. + * + * Returns: %TRUE if a key file could be loaded, %FALSE otherwise + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_load_from_data_dirs (GBookmarkFile *bookmark, + const gchar *file, + gchar **full_path, + GError **error) +{ + GError *file_error = NULL; + gchar **all_data_dirs, **data_dirs; + const gchar *user_data_dir; + const gchar * const * system_data_dirs; + gsize i, j; + gchar *output_path; + gboolean found_file; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (!g_path_is_absolute (file), FALSE); + + user_data_dir = g_get_user_data_dir (); + system_data_dirs = g_get_system_data_dirs (); + all_data_dirs = g_new0 (gchar *, g_strv_length ((gchar **)system_data_dirs) + 2); + + i = 0; + all_data_dirs[i++] = g_strdup (user_data_dir); + + j = 0; + while (system_data_dirs[j] != NULL) + all_data_dirs[i++] = g_strdup (system_data_dirs[j++]); + + found_file = FALSE; + data_dirs = all_data_dirs; + output_path = NULL; + while (*data_dirs != NULL && !found_file) + { + g_free (output_path); + + output_path = find_file_in_data_dirs (file, &data_dirs, &file_error); + + if (file_error) + { + g_propagate_error (error, file_error); + break; + } + + found_file = g_bookmark_file_load_from_file (bookmark, + output_path, + &file_error); + if (file_error) + { + g_propagate_error (error, file_error); + break; + } + } + + if (found_file && full_path) + *full_path = output_path; + else + g_free (output_path); + + g_strfreev (all_data_dirs); + + return found_file; +} + + +/** + * g_bookmark_file_to_data: + * @bookmark: a #GBookmarkFile + * @length: (out) (optional): return location for the length of the returned string, or %NULL + * @error: return location for a #GError, or %NULL + * + * This function outputs @bookmark as a string. + * + * Returns: (transfer full) (array length=length) (element-type guint8): + * a newly allocated string holding the contents of the #GBookmarkFile + * + * Since: 2.12 + */ +gchar * +g_bookmark_file_to_data (GBookmarkFile *bookmark, + gsize *length, + GError **error) +{ + GError *write_error = NULL; + gchar *retval; + + g_return_val_if_fail (bookmark != NULL, NULL); + + retval = g_bookmark_file_dump (bookmark, length, &write_error); + if (write_error) + { + g_propagate_error (error, write_error); + + return NULL; + } + + return retval; +} + +/** + * g_bookmark_file_to_file: + * @bookmark: a #GBookmarkFile + * @filename: (type filename): path of the output file + * @error: return location for a #GError, or %NULL + * + * This function outputs @bookmark into a file. The write process is + * guaranteed to be atomic by using g_file_set_contents() internally. + * + * Returns: %TRUE if the file was successfully written. + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_to_file (GBookmarkFile *bookmark, + const gchar *filename, + GError **error) +{ + gchar *data; + GError *data_error, *write_error; + gsize len; + gboolean retval; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + data_error = NULL; + data = g_bookmark_file_to_data (bookmark, &len, &data_error); + if (data_error) + { + g_propagate_error (error, data_error); + + return FALSE; + } + + write_error = NULL; + g_file_set_contents (filename, data, len, &write_error); + if (write_error) + { + g_propagate_error (error, write_error); + + retval = FALSE; + } + else + retval = TRUE; + + g_free (data); + + return retval; +} + +static BookmarkItem * +g_bookmark_file_lookup_item (GBookmarkFile *bookmark, + const gchar *uri) +{ + g_warn_if_fail (bookmark != NULL && uri != NULL); + + return g_hash_table_lookup (bookmark->items_by_uri, uri); +} + +/* this function adds a new item to the list */ +static void +g_bookmark_file_add_item (GBookmarkFile *bookmark, + BookmarkItem *item, + GError **error) +{ + g_warn_if_fail (bookmark != NULL); + g_warn_if_fail (item != NULL); + + /* this should never happen; and if it does, then we are + * screwing up something big time. + */ + if (G_UNLIKELY (g_bookmark_file_has_item (bookmark, item->uri))) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_INVALID_URI, + _("A bookmark for URI “%s” already exists"), + item->uri); + return; + } + + bookmark->items = g_list_prepend (bookmark->items, item); + + g_hash_table_replace (bookmark->items_by_uri, + item->uri, + item); + + if (item->added == NULL) + item->added = g_date_time_new_now_utc (); + + if (item->modified == NULL) + item->modified = g_date_time_new_now_utc (); + + if (item->visited == NULL) + item->visited = g_date_time_new_now_utc (); +} + +/** + * g_bookmark_file_remove_item: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Removes the bookmark for @uri from the bookmark file @bookmark. + * + * Returns: %TRUE if the bookmark was removed successfully. + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_remove_item (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, uri); + + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return FALSE; + } + + bookmark->items = g_list_remove (bookmark->items, item); + g_hash_table_remove (bookmark->items_by_uri, item->uri); + + bookmark_item_free (item); + + return TRUE; +} + +/** + * g_bookmark_file_has_item: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * + * Looks whether the desktop bookmark has an item with its URI set to @uri. + * + * Returns: %TRUE if @uri is inside @bookmark, %FALSE otherwise + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_has_item (GBookmarkFile *bookmark, + const gchar *uri) +{ + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + return (NULL != g_hash_table_lookup (bookmark->items_by_uri, uri)); +} + +/** + * g_bookmark_file_get_uris: + * @bookmark: a #GBookmarkFile + * @length: (out) (optional): return location for the number of returned URIs, or %NULL + * + * Returns all URIs of the bookmarks in the bookmark file @bookmark. + * The array of returned URIs will be %NULL-terminated, so @length may + * optionally be %NULL. + * + * Returns: (array length=length) (transfer full): a newly allocated %NULL-terminated array of strings. + * Use g_strfreev() to free it. + * + * Since: 2.12 + */ +gchar ** +g_bookmark_file_get_uris (GBookmarkFile *bookmark, + gsize *length) +{ + GList *l; + gchar **uris; + gsize i, n_items; + + g_return_val_if_fail (bookmark != NULL, NULL); + + n_items = g_list_length (bookmark->items); + uris = g_new0 (gchar *, n_items + 1); + + /* the items are stored in reverse order, so we walk the list backward */ + for (l = g_list_last (bookmark->items), i = 0; l != NULL; l = l->prev) + { + BookmarkItem *item = (BookmarkItem *) l->data; + + g_warn_if_fail (item != NULL); + + uris[i++] = g_strdup (item->uri); + } + uris[i] = NULL; + + if (length) + *length = i; + + return uris; +} + +/** + * g_bookmark_file_set_title: + * @bookmark: a #GBookmarkFile + * @uri: (nullable): a valid URI or %NULL + * @title: a UTF-8 encoded string + * + * Sets @title as the title of the bookmark for @uri inside the + * bookmark file @bookmark. + * + * If @uri is %NULL, the title of @bookmark is set. + * + * If a bookmark for @uri cannot be found then it is created. + * + * Since: 2.12 + */ +void +g_bookmark_file_set_title (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *title) +{ + g_return_if_fail (bookmark != NULL); + + if (!uri) + { + g_free (bookmark->title); + bookmark->title = g_strdup (title); + } + else + { + BookmarkItem *item; + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + g_free (item->title); + item->title = g_strdup (title); + + bookmark_item_touch_modified (item); + } +} + +/** + * g_bookmark_file_get_title: + * @bookmark: a #GBookmarkFile + * @uri: (nullable): a valid URI or %NULL + * @error: return location for a #GError, or %NULL + * + * Returns the title of the bookmark for @uri. + * + * If @uri is %NULL, the title of @bookmark is returned. + * + * In the event the URI cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: (transfer full): a newly allocated string or %NULL if the specified + * URI cannot be found. + * + * Since: 2.12 + */ +gchar * +g_bookmark_file_get_title (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, NULL); + + if (!uri) + return g_strdup (bookmark->title); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return NULL; + } + + return g_strdup (item->title); +} + +/** + * g_bookmark_file_set_description: + * @bookmark: a #GBookmarkFile + * @uri: (nullable): a valid URI or %NULL + * @description: a string + * + * Sets @description as the description of the bookmark for @uri. + * + * If @uri is %NULL, the description of @bookmark is set. + * + * If a bookmark for @uri cannot be found then it is created. + * + * Since: 2.12 + */ +void +g_bookmark_file_set_description (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *description) +{ + g_return_if_fail (bookmark != NULL); + + if (!uri) + { + g_free (bookmark->description); + bookmark->description = g_strdup (description); + } + else + { + BookmarkItem *item; + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + g_free (item->description); + item->description = g_strdup (description); + + bookmark_item_touch_modified (item); + } +} + +/** + * g_bookmark_file_get_description: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Retrieves the description of the bookmark for @uri. + * + * In the event the URI cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: (transfer full): a newly allocated string or %NULL if the specified + * URI cannot be found. + * + * Since: 2.12 + */ +gchar * +g_bookmark_file_get_description (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, NULL); + + if (!uri) + return g_strdup (bookmark->description); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return NULL; + } + + return g_strdup (item->description); +} + +/** + * g_bookmark_file_set_mime_type: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @mime_type: a MIME type + * + * Sets @mime_type as the MIME type of the bookmark for @uri. + * + * If a bookmark for @uri cannot be found then it is created. + * + * Since: 2.12 + */ +void +g_bookmark_file_set_mime_type (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *mime_type) +{ + BookmarkItem *item; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + g_return_if_fail (mime_type != NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + g_free (item->metadata->mime_type); + + item->metadata->mime_type = g_strdup (mime_type); + bookmark_item_touch_modified (item); +} + +/** + * g_bookmark_file_get_mime_type: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Retrieves the MIME type of the resource pointed by @uri. + * + * In the event the URI cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. In the + * event that the MIME type cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_INVALID_VALUE. + * + * Returns: (transfer full): a newly allocated string or %NULL if the specified + * URI cannot be found. + * + * Since: 2.12 + */ +gchar * +g_bookmark_file_get_mime_type (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, NULL); + g_return_val_if_fail (uri != NULL, NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return NULL; + } + + if (!item->metadata) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_INVALID_VALUE, + _("No MIME type defined in the bookmark for URI “%s”"), + uri); + return NULL; + } + + return g_strdup (item->metadata->mime_type); +} + +/** + * g_bookmark_file_set_is_private: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @is_private: %TRUE if the bookmark should be marked as private + * + * Sets the private flag of the bookmark for @uri. + * + * If a bookmark for @uri cannot be found then it is created. + * + * Since: 2.12 + */ +void +g_bookmark_file_set_is_private (GBookmarkFile *bookmark, + const gchar *uri, + gboolean is_private) +{ + BookmarkItem *item; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + item->metadata->is_private = (is_private == TRUE); + bookmark_item_touch_modified (item); +} + +/** + * g_bookmark_file_get_is_private: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Gets whether the private flag of the bookmark for @uri is set. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. In the + * event that the private flag cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_INVALID_VALUE. + * + * Returns: %TRUE if the private flag is set, %FALSE otherwise. + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_get_is_private (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return FALSE; + } + + if (!item->metadata) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_INVALID_VALUE, + _("No private flag has been defined in bookmark for URI “%s”"), + uri); + return FALSE; + } + + return item->metadata->is_private; +} + +/** + * g_bookmark_file_set_added: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @added: a timestamp or -1 to use the current time + * + * Sets the time the bookmark for @uri was added into @bookmark. + * + * If no bookmark for @uri is found then it is created. + * + * Since: 2.12 + * Deprecated: 2.66: Use g_bookmark_file_set_added_date_time() instead, as + * `time_t` is deprecated due to the year 2038 problem. + */ +void +g_bookmark_file_set_added (GBookmarkFile *bookmark, + const gchar *uri, + time_t added) +{ + GDateTime *added_dt = (added != (time_t) -1) ? g_date_time_new_from_unix_utc (added) : g_date_time_new_now_utc (); + g_bookmark_file_set_added_date_time (bookmark, uri, added_dt); + g_date_time_unref (added_dt); +} + +/** + * g_bookmark_file_set_added_date_time: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @added: a #GDateTime + * + * Sets the time the bookmark for @uri was added into @bookmark. + * + * If no bookmark for @uri is found then it is created. + * + * Since: 2.66 + */ +void +g_bookmark_file_set_added_date_time (GBookmarkFile *bookmark, + const char *uri, + GDateTime *added) +{ + BookmarkItem *item; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + g_return_if_fail (added != NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + g_clear_pointer (&item->added, g_date_time_unref); + item->added = g_date_time_ref (added); + g_clear_pointer (&item->modified, g_date_time_unref); + item->modified = g_date_time_ref (added); +} + +/** + * g_bookmark_file_get_added: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Gets the time the bookmark for @uri was added to @bookmark + * + * In the event the URI cannot be found, -1 is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: a timestamp + * + * Since: 2.12 + * Deprecated: 2.66: Use g_bookmark_file_get_added_date_time() instead, as + * `time_t` is deprecated due to the year 2038 problem. + */ +time_t +g_bookmark_file_get_added (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) +{ + GDateTime *added = g_bookmark_file_get_added_date_time (bookmark, uri, error); + return (added != NULL) ? g_date_time_to_unix (added) : (time_t) -1; +} + +/** + * g_bookmark_file_get_added_date_time: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Gets the time the bookmark for @uri was added to @bookmark + * + * In the event the URI cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: (transfer none): a #GDateTime + * + * Since: 2.66 + */ +GDateTime * +g_bookmark_file_get_added_date_time (GBookmarkFile *bookmark, + const char *uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, NULL); + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return NULL; + } + + return item->added; +} + +/** + * g_bookmark_file_set_modified: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @modified: a timestamp or -1 to use the current time + * + * Sets the last time the bookmark for @uri was last modified. + * + * If no bookmark for @uri is found then it is created. + * + * The "modified" time should only be set when the bookmark's meta-data + * was actually changed. Every function of #GBookmarkFile that + * modifies a bookmark also changes the modification time, except for + * g_bookmark_file_set_visited_date_time(). + * + * Since: 2.12 + * Deprecated: 2.66: Use g_bookmark_file_set_modified_date_time() instead, as + * `time_t` is deprecated due to the year 2038 problem. + */ +void +g_bookmark_file_set_modified (GBookmarkFile *bookmark, + const gchar *uri, + time_t modified) +{ + GDateTime *modified_dt = (modified != (time_t) -1) ? g_date_time_new_from_unix_utc (modified) : g_date_time_new_now_utc (); + g_bookmark_file_set_modified_date_time (bookmark, uri, modified_dt); + g_date_time_unref (modified_dt); +} + +/** + * g_bookmark_file_set_modified_date_time: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @modified: a #GDateTime + * + * Sets the last time the bookmark for @uri was last modified. + * + * If no bookmark for @uri is found then it is created. + * + * The "modified" time should only be set when the bookmark's meta-data + * was actually changed. Every function of #GBookmarkFile that + * modifies a bookmark also changes the modification time, except for + * g_bookmark_file_set_visited_date_time(). + * + * Since: 2.66 + */ +void +g_bookmark_file_set_modified_date_time (GBookmarkFile *bookmark, + const char *uri, + GDateTime *modified) +{ + BookmarkItem *item; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + g_return_if_fail (modified != NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + g_clear_pointer (&item->modified, g_date_time_unref); + item->modified = g_date_time_ref (modified); +} + +/** + * g_bookmark_file_get_modified: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Gets the time when the bookmark for @uri was last modified. + * + * In the event the URI cannot be found, -1 is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: a timestamp + * + * Since: 2.12 + * Deprecated: 2.66: Use g_bookmark_file_get_modified_date_time() instead, as + * `time_t` is deprecated due to the year 2038 problem. + */ +time_t +g_bookmark_file_get_modified (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) +{ + GDateTime *modified = g_bookmark_file_get_modified_date_time (bookmark, uri, error); + return (modified != NULL) ? g_date_time_to_unix (modified) : (time_t) -1; +} + +/** + * g_bookmark_file_get_modified_date_time: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Gets the time when the bookmark for @uri was last modified. + * + * In the event the URI cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: (transfer none): a #GDateTime + * + * Since: 2.66 + */ +GDateTime * +g_bookmark_file_get_modified_date_time (GBookmarkFile *bookmark, + const char *uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, NULL); + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return NULL; + } + + return item->modified; +} + +/** + * g_bookmark_file_set_visited: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @visited: a timestamp or -1 to use the current time + * + * Sets the time the bookmark for @uri was last visited. + * + * If no bookmark for @uri is found then it is created. + * + * The "visited" time should only be set if the bookmark was launched, + * either using the command line retrieved by g_bookmark_file_get_application_info() + * or by the default application for the bookmark's MIME type, retrieved + * using g_bookmark_file_get_mime_type(). Changing the "visited" time + * does not affect the "modified" time. + * + * Since: 2.12 + * Deprecated: 2.66: Use g_bookmark_file_set_visited_date_time() instead, as + * `time_t` is deprecated due to the year 2038 problem. + */ +void +g_bookmark_file_set_visited (GBookmarkFile *bookmark, + const gchar *uri, + time_t visited) +{ + GDateTime *visited_dt = (visited != (time_t) -1) ? g_date_time_new_from_unix_utc (visited) : g_date_time_new_now_utc (); + g_bookmark_file_set_visited_date_time (bookmark, uri, visited_dt); + g_date_time_unref (visited_dt); +} + +/** + * g_bookmark_file_set_visited_date_time: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @visited: a #GDateTime + * + * Sets the time the bookmark for @uri was last visited. + * + * If no bookmark for @uri is found then it is created. + * + * The "visited" time should only be set if the bookmark was launched, + * either using the command line retrieved by g_bookmark_file_get_application_info() + * or by the default application for the bookmark's MIME type, retrieved + * using g_bookmark_file_get_mime_type(). Changing the "visited" time + * does not affect the "modified" time. + * + * Since: 2.66 + */ +void +g_bookmark_file_set_visited_date_time (GBookmarkFile *bookmark, + const char *uri, + GDateTime *visited) +{ + BookmarkItem *item; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + g_return_if_fail (visited != NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + g_clear_pointer (&item->visited, g_date_time_unref); + item->visited = g_date_time_ref (visited); +} + +/** + * g_bookmark_file_get_visited: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Gets the time the bookmark for @uri was last visited. + * + * In the event the URI cannot be found, -1 is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: a timestamp. + * + * Since: 2.12 + * Deprecated: 2.66: Use g_bookmark_file_get_visited_date_time() instead, as + * `time_t` is deprecated due to the year 2038 problem. + */ +time_t +g_bookmark_file_get_visited (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) +{ + GDateTime *visited = g_bookmark_file_get_visited_date_time (bookmark, uri, error); + return (visited != NULL) ? g_date_time_to_unix (visited) : (time_t) -1; +} + +/** + * g_bookmark_file_get_visited_date_time: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Gets the time the bookmark for @uri was last visited. + * + * In the event the URI cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: (transfer none): a #GDateTime + * + * Since: 2.66 + */ +GDateTime * +g_bookmark_file_get_visited_date_time (GBookmarkFile *bookmark, + const char *uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, NULL); + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return NULL; + } + + return item->visited; +} + +/** + * g_bookmark_file_has_group: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @group: the group name to be searched + * @error: return location for a #GError, or %NULL + * + * Checks whether @group appears in the list of groups to which + * the bookmark for @uri belongs to. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: %TRUE if @group was found. + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_has_group (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *group, + GError **error) +{ + BookmarkItem *item; + GList *l; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return FALSE; + } + + if (!item->metadata) + return FALSE; + + for (l = item->metadata->groups; l != NULL; l = l->next) + { + if (strcmp (l->data, group) == 0) + return TRUE; + } + + return FALSE; + +} + +/** + * g_bookmark_file_add_group: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @group: the group name to be added + * + * Adds @group to the list of groups to which the bookmark for @uri + * belongs to. + * + * If no bookmark for @uri is found then it is created. + * + * Since: 2.12 + */ +void +g_bookmark_file_add_group (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *group) +{ + BookmarkItem *item; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + g_return_if_fail (group != NULL && group[0] != '\0'); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + if (!g_bookmark_file_has_group (bookmark, uri, group, NULL)) + { + item->metadata->groups = g_list_prepend (item->metadata->groups, + g_strdup (group)); + + bookmark_item_touch_modified (item); + } +} + +/** + * g_bookmark_file_remove_group: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @group: the group name to be removed + * @error: return location for a #GError, or %NULL + * + * Removes @group from the list of groups to which the bookmark + * for @uri belongs to. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * In the event no group was defined, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_INVALID_VALUE. + * + * Returns: %TRUE if @group was successfully removed. + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_remove_group (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *group, + GError **error) +{ + BookmarkItem *item; + GList *l; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return FALSE; + } + + if (!item->metadata) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_INVALID_VALUE, + _("No groups set in bookmark for URI “%s”"), + uri); + return FALSE; + } + + for (l = item->metadata->groups; l != NULL; l = l->next) + { + if (strcmp (l->data, group) == 0) + { + item->metadata->groups = g_list_remove_link (item->metadata->groups, l); + g_free (l->data); + g_list_free_1 (l); + + bookmark_item_touch_modified (item); + + return TRUE; + } + } + + return FALSE; +} + +/** + * g_bookmark_file_set_groups: + * @bookmark: a #GBookmarkFile + * @uri: an item's URI + * @groups: (nullable) (array length=length) (element-type utf8): an array of + * group names, or %NULL to remove all groups + * @length: number of group name values in @groups + * + * Sets a list of group names for the item with URI @uri. Each previously + * set group name list is removed. + * + * If @uri cannot be found then an item for it is created. + * + * Since: 2.12 + */ +void +g_bookmark_file_set_groups (GBookmarkFile *bookmark, + const gchar *uri, + const gchar **groups, + gsize length) +{ + BookmarkItem *item; + gsize i; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + g_return_if_fail (groups != NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + g_list_free_full (item->metadata->groups, g_free); + item->metadata->groups = NULL; + + if (groups) + { + for (i = 0; i < length && groups[i] != NULL; i++) + item->metadata->groups = g_list_append (item->metadata->groups, + g_strdup (groups[i])); + } + + bookmark_item_touch_modified (item); +} + +/** + * g_bookmark_file_get_groups: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @length: (out) (optional): return location for the length of the returned string, or %NULL + * @error: return location for a #GError, or %NULL + * + * Retrieves the list of group names of the bookmark for @uri. + * + * In the event the URI cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * The returned array is %NULL terminated, so @length may optionally + * be %NULL. + * + * Returns: (array length=length) (transfer full): a newly allocated %NULL-terminated array of group names. + * Use g_strfreev() to free it. + * + * Since: 2.12 + */ +gchar ** +g_bookmark_file_get_groups (GBookmarkFile *bookmark, + const gchar *uri, + gsize *length, + GError **error) +{ + BookmarkItem *item; + GList *l; + gsize len, i; + gchar **retval; + + g_return_val_if_fail (bookmark != NULL, NULL); + g_return_val_if_fail (uri != NULL, NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return NULL; + } + + if (!item->metadata) + { + if (length) + *length = 0; + + return NULL; + } + + len = g_list_length (item->metadata->groups); + retval = g_new0 (gchar *, len + 1); + for (l = g_list_last (item->metadata->groups), i = 0; + l != NULL; + l = l->prev) + { + gchar *group_name = (gchar *) l->data; + + g_warn_if_fail (group_name != NULL); + + retval[i++] = g_strdup (group_name); + } + retval[i] = NULL; + + if (length) + *length = len; + + return retval; +} + +/** + * g_bookmark_file_add_application: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @name: (nullable): the name of the application registering the bookmark + * or %NULL + * @exec: (nullable): command line to be used to launch the bookmark or %NULL + * + * Adds the application with @name and @exec to the list of + * applications that have registered a bookmark for @uri into + * @bookmark. + * + * Every bookmark inside a #GBookmarkFile must have at least an + * application registered. Each application must provide a name, a + * command line useful for launching the bookmark, the number of times + * the bookmark has been registered by the application and the last + * time the application registered this bookmark. + * + * If @name is %NULL, the name of the application will be the + * same returned by g_get_application_name(); if @exec is %NULL, the + * command line will be a composition of the program name as + * returned by g_get_prgname() and the "\%u" modifier, which will be + * expanded to the bookmark's URI. + * + * This function will automatically take care of updating the + * registrations count and timestamping in case an application + * with the same @name had already registered a bookmark for + * @uri inside @bookmark. + * + * If no bookmark for @uri is found, one is created. + * + * Since: 2.12 + */ +void +g_bookmark_file_add_application (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + const gchar *exec) +{ + BookmarkItem *item; + gchar *app_name, *app_exec; + GDateTime *stamp; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + if (name && name[0] != '\0') + app_name = g_strdup (name); + else + app_name = g_strdup (g_get_application_name ()); + + if (exec && exec[0] != '\0') + app_exec = g_strdup (exec); + else + app_exec = g_strjoin (" ", g_get_prgname(), "%u", NULL); + + stamp = g_date_time_new_now_utc (); + + g_bookmark_file_set_application_info (bookmark, uri, + app_name, + app_exec, + -1, + stamp, + NULL); + + g_date_time_unref (stamp); + g_free (app_exec); + g_free (app_name); +} + +/** + * g_bookmark_file_remove_application: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @name: the name of the application + * @error: return location for a #GError or %NULL + * + * Removes application registered with @name from the list of applications + * that have registered a bookmark for @uri inside @bookmark. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * In the event that no application with name @app_name has registered + * a bookmark for @uri, %FALSE is returned and error is set to + * %G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED. + * + * Returns: %TRUE if the application was successfully removed. + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_remove_application (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + GError **error) +{ + GError *set_error; + gboolean retval; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + set_error = NULL; + retval = g_bookmark_file_set_application_info (bookmark, uri, + name, + "", + 0, + NULL, + &set_error); + if (set_error) + { + g_propagate_error (error, set_error); + + return FALSE; + } + + return retval; +} + +/** + * g_bookmark_file_has_application: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @name: the name of the application + * @error: return location for a #GError or %NULL + * + * Checks whether the bookmark for @uri inside @bookmark has been + * registered by application @name. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: %TRUE if the application @name was found + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_has_application (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return FALSE; + } + + return (NULL != bookmark_item_lookup_app_info (item, name)); +} + +/** + * g_bookmark_file_set_app_info: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @name: an application's name + * @exec: an application's command line + * @count: the number of registrations done for this application + * @stamp: the time of the last registration for this application + * @error: return location for a #GError or %NULL + * + * Sets the meta-data of application @name inside the list of + * applications that have registered a bookmark for @uri inside + * @bookmark. + * + * You should rarely use this function; use g_bookmark_file_add_application() + * and g_bookmark_file_remove_application() instead. + * + * @name can be any UTF-8 encoded string used to identify an + * application. + * @exec can have one of these two modifiers: "\%f", which will + * be expanded as the local file name retrieved from the bookmark's + * URI; "\%u", which will be expanded as the bookmark's URI. + * The expansion is done automatically when retrieving the stored + * command line using the g_bookmark_file_get_application_info() function. + * @count is the number of times the application has registered the + * bookmark; if is < 0, the current registration count will be increased + * by one, if is 0, the application with @name will be removed from + * the list of registered applications. + * @stamp is the Unix time of the last registration; if it is -1, the + * current time will be used. + * + * If you try to remove an application by setting its registration count to + * zero, and no bookmark for @uri is found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND; similarly, + * in the event that no application @name has registered a bookmark + * for @uri, %FALSE is returned and error is set to + * %G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED. Otherwise, if no bookmark + * for @uri is found, one is created. + * + * Returns: %TRUE if the application's meta-data was successfully + * changed. + * + * Since: 2.12 + * Deprecated: 2.66: Use g_bookmark_file_set_application_info() instead, as + * `time_t` is deprecated due to the year 2038 problem. + */ +gboolean +g_bookmark_file_set_app_info (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + const gchar *exec, + gint count, + time_t stamp, + GError **error) +{ + GDateTime *stamp_dt = (stamp != (time_t) -1) ? g_date_time_new_from_unix_utc (stamp) : g_date_time_new_now_utc (); + gboolean retval; + retval = g_bookmark_file_set_application_info (bookmark, uri, name, exec, count, + stamp_dt, error); + g_date_time_unref (stamp_dt); + return retval; +} + +/** + * g_bookmark_file_set_application_info: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @name: an application's name + * @exec: an application's command line + * @count: the number of registrations done for this application + * @stamp: (nullable): the time of the last registration for this application, + * which may be %NULL if @count is 0 + * @error: return location for a #GError or %NULL + * + * Sets the meta-data of application @name inside the list of + * applications that have registered a bookmark for @uri inside + * @bookmark. + * + * You should rarely use this function; use g_bookmark_file_add_application() + * and g_bookmark_file_remove_application() instead. + * + * @name can be any UTF-8 encoded string used to identify an + * application. + * @exec can have one of these two modifiers: "\%f", which will + * be expanded as the local file name retrieved from the bookmark's + * URI; "\%u", which will be expanded as the bookmark's URI. + * The expansion is done automatically when retrieving the stored + * command line using the g_bookmark_file_get_application_info() function. + * @count is the number of times the application has registered the + * bookmark; if is < 0, the current registration count will be increased + * by one, if is 0, the application with @name will be removed from + * the list of registered applications. + * @stamp is the Unix time of the last registration. + * + * If you try to remove an application by setting its registration count to + * zero, and no bookmark for @uri is found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND; similarly, + * in the event that no application @name has registered a bookmark + * for @uri, %FALSE is returned and error is set to + * %G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED. Otherwise, if no bookmark + * for @uri is found, one is created. + * + * Returns: %TRUE if the application's meta-data was successfully + * changed. + * + * Since: 2.66 + */ +gboolean +g_bookmark_file_set_application_info (GBookmarkFile *bookmark, + const char *uri, + const char *name, + const char *exec, + int count, + GDateTime *stamp, + GError **error) +{ + BookmarkItem *item; + BookmarkAppInfo *ai; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (exec != NULL, FALSE); + g_return_val_if_fail (count == 0 || stamp != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + if (count == 0) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return FALSE; + } + else + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + } + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + ai = bookmark_item_lookup_app_info (item, name); + if (!ai) + { + if (count == 0) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED, + _("No application with name “%s” registered a bookmark for “%s”"), + name, + uri); + return FALSE; + } + else + { + ai = bookmark_app_info_new (name); + + item->metadata->applications = g_list_prepend (item->metadata->applications, ai); + g_hash_table_replace (item->metadata->apps_by_name, ai->name, ai); + } + } + + if (count == 0) + { + item->metadata->applications = g_list_remove (item->metadata->applications, ai); + g_hash_table_remove (item->metadata->apps_by_name, ai->name); + bookmark_app_info_free (ai); + + bookmark_item_touch_modified (item); + + return TRUE; + } + else if (count > 0) + ai->count = count; + else + ai->count += 1; + + g_clear_pointer (&ai->stamp, g_date_time_unref); + ai->stamp = g_date_time_ref (stamp); + + if (exec && exec[0] != '\0') + { + g_free (ai->exec); + ai->exec = g_shell_quote (exec); + } + + bookmark_item_touch_modified (item); + + return TRUE; +} + +/* expands the application's command line */ +static gchar * +expand_exec_line (const gchar *exec_fmt, + const gchar *uri) +{ + GString *exec; + gchar ch; + + exec = g_string_sized_new (512); + while ((ch = *exec_fmt++) != '\0') + { + if (ch != '%') + { + exec = g_string_append_c (exec, ch); + continue; + } + + ch = *exec_fmt++; + switch (ch) + { + case '\0': + goto out; + case 'U': + case 'u': + g_string_append (exec, uri); + break; + case 'F': + case 'f': + { + gchar *file = g_filename_from_uri (uri, NULL, NULL); + if (file) + { + g_string_append (exec, file); + g_free (file); + } + else + { + g_string_free (exec, TRUE); + return NULL; + } + } + break; + case '%': + default: + exec = g_string_append_c (exec, ch); + break; + } + } + + out: + return g_string_free (exec, FALSE); +} + +/** + * g_bookmark_file_get_app_info: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @name: an application's name + * @exec: (out) (optional): return location for the command line of the application, or %NULL + * @count: (out) (optional): return location for the registration count, or %NULL + * @stamp: (out) (optional): return location for the last registration time, or %NULL + * @error: return location for a #GError, or %NULL + * + * Gets the registration information of @app_name for the bookmark for + * @uri. See g_bookmark_file_set_application_info() for more information about + * the returned data. + * + * The string returned in @app_exec must be freed. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. In the + * event that no application with name @app_name has registered a bookmark + * for @uri, %FALSE is returned and error is set to + * %G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED. In the event that unquoting + * the command line fails, an error of the %G_SHELL_ERROR domain is + * set and %FALSE is returned. + * + * Returns: %TRUE on success. + * + * Since: 2.12 + * Deprecated: 2.66: Use g_bookmark_file_get_application_info() instead, as + * `time_t` is deprecated due to the year 2038 problem. + */ +gboolean +g_bookmark_file_get_app_info (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + gchar **exec, + guint *count, + time_t *stamp, + GError **error) +{ + GDateTime *stamp_dt = NULL; + gboolean retval; + + retval = g_bookmark_file_get_application_info (bookmark, uri, name, exec, count, &stamp_dt, error); + if (!retval) + return FALSE; + + if (stamp != NULL) + *stamp = g_date_time_to_unix (stamp_dt); + + return TRUE; +} + +/** + * g_bookmark_file_get_application_info: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @name: an application's name + * @exec: (out) (optional): return location for the command line of the application, or %NULL + * @count: (out) (optional): return location for the registration count, or %NULL + * @stamp: (out) (optional) (transfer none): return location for the last registration time, or %NULL + * @error: return location for a #GError, or %NULL + * + * Gets the registration information of @app_name for the bookmark for + * @uri. See g_bookmark_file_set_application_info() for more information about + * the returned data. + * + * The string returned in @app_exec must be freed. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. In the + * event that no application with name @app_name has registered a bookmark + * for @uri, %FALSE is returned and error is set to + * %G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED. In the event that unquoting + * the command line fails, an error of the %G_SHELL_ERROR domain is + * set and %FALSE is returned. + * + * Returns: %TRUE on success. + * + * Since: 2.66 + */ +gboolean +g_bookmark_file_get_application_info (GBookmarkFile *bookmark, + const char *uri, + const char *name, + char **exec, + unsigned int *count, + GDateTime **stamp, + GError **error) +{ + BookmarkItem *item; + BookmarkAppInfo *ai; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return FALSE; + } + + ai = bookmark_item_lookup_app_info (item, name); + if (!ai) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED, + _("No application with name “%s” registered a bookmark for “%s”"), + name, + uri); + return FALSE; + } + + if (exec) + { + GError *unquote_error = NULL; + gchar *command_line; + + command_line = g_shell_unquote (ai->exec, &unquote_error); + if (unquote_error) + { + g_propagate_error (error, unquote_error); + return FALSE; + } + + *exec = expand_exec_line (command_line, uri); + if (!*exec) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_INVALID_URI, + _("Failed to expand exec line “%s” with URI “%s”"), + ai->exec, uri); + g_free (command_line); + + return FALSE; + } + else + g_free (command_line); + } + + if (count) + *count = ai->count; + + if (stamp) + *stamp = ai->stamp; + + return TRUE; +} + +/** + * g_bookmark_file_get_applications: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @length: (out) (optional): return location of the length of the returned list, or %NULL + * @error: return location for a #GError, or %NULL + * + * Retrieves the names of the applications that have registered the + * bookmark for @uri. + * + * In the event the URI cannot be found, %NULL is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: (array length=length) (transfer full): a newly allocated %NULL-terminated array of strings. + * Use g_strfreev() to free it. + * + * Since: 2.12 + */ +gchar ** +g_bookmark_file_get_applications (GBookmarkFile *bookmark, + const gchar *uri, + gsize *length, + GError **error) +{ + BookmarkItem *item; + GList *l; + gchar **apps; + gsize i, n_apps; + + g_return_val_if_fail (bookmark != NULL, NULL); + g_return_val_if_fail (uri != NULL, NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return NULL; + } + + if (!item->metadata) + { + if (length) + *length = 0; + + return NULL; + } + + n_apps = g_list_length (item->metadata->applications); + apps = g_new0 (gchar *, n_apps + 1); + + for (l = g_list_last (item->metadata->applications), i = 0; + l != NULL; + l = l->prev) + { + BookmarkAppInfo *ai; + + ai = (BookmarkAppInfo *) l->data; + + g_warn_if_fail (ai != NULL); + g_warn_if_fail (ai->name != NULL); + + apps[i++] = g_strdup (ai->name); + } + apps[i] = NULL; + + if (length) + *length = i; + + return apps; +} + +/** + * g_bookmark_file_get_size: + * @bookmark: a #GBookmarkFile + * + * Gets the number of bookmarks inside @bookmark. + * + * Returns: the number of bookmarks + * + * Since: 2.12 + */ +gint +g_bookmark_file_get_size (GBookmarkFile *bookmark) +{ + g_return_val_if_fail (bookmark != NULL, 0); + + return g_list_length (bookmark->items); +} + +/** + * g_bookmark_file_move_item: + * @bookmark: a #GBookmarkFile + * @old_uri: a valid URI + * @new_uri: (nullable): a valid URI, or %NULL + * @error: return location for a #GError or %NULL + * + * Changes the URI of a bookmark item from @old_uri to @new_uri. Any + * existing bookmark for @new_uri will be overwritten. If @new_uri is + * %NULL, then the bookmark is removed. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: %TRUE if the URI was successfully changed + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_move_item (GBookmarkFile *bookmark, + const gchar *old_uri, + const gchar *new_uri, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (old_uri != NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, old_uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + old_uri); + return FALSE; + } + + if (new_uri && new_uri[0] != '\0') + { + if (g_strcmp0 (old_uri, new_uri) == 0) + return TRUE; + + if (g_bookmark_file_has_item (bookmark, new_uri)) + { + if (!g_bookmark_file_remove_item (bookmark, new_uri, error)) + return FALSE; + } + + g_hash_table_steal (bookmark->items_by_uri, item->uri); + + g_free (item->uri); + item->uri = g_strdup (new_uri); + bookmark_item_touch_modified (item); + + g_hash_table_replace (bookmark->items_by_uri, item->uri, item); + + return TRUE; + } + else + { + if (!g_bookmark_file_remove_item (bookmark, old_uri, error)) + return FALSE; + + return TRUE; + } +} + +/** + * g_bookmark_file_set_icon: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @href: (nullable): the URI of the icon for the bookmark, or %NULL + * @mime_type: the MIME type of the icon for the bookmark + * + * Sets the icon for the bookmark for @uri. If @href is %NULL, unsets + * the currently set icon. @href can either be a full URL for the icon + * file or the icon name following the Icon Naming specification. + * + * If no bookmark for @uri is found one is created. + * + * Since: 2.12 + */ +void +g_bookmark_file_set_icon (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *href, + const gchar *mime_type) +{ + BookmarkItem *item; + + g_return_if_fail (bookmark != NULL); + g_return_if_fail (uri != NULL); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + item = bookmark_item_new (uri); + g_bookmark_file_add_item (bookmark, item, NULL); + } + + if (!item->metadata) + item->metadata = bookmark_metadata_new (); + + g_free (item->metadata->icon_href); + g_free (item->metadata->icon_mime); + + item->metadata->icon_href = g_strdup (href); + + if (mime_type && mime_type[0] != '\0') + item->metadata->icon_mime = g_strdup (mime_type); + else + item->metadata->icon_mime = g_strdup ("application/octet-stream"); + + bookmark_item_touch_modified (item); +} + +/** + * g_bookmark_file_get_icon: + * @bookmark: a #GBookmarkFile + * @uri: a valid URI + * @href: (out) (optional): return location for the icon's location or %NULL + * @mime_type: (out) (optional): return location for the icon's MIME type or %NULL + * @error: return location for a #GError or %NULL + * + * Gets the icon of the bookmark for @uri. + * + * In the event the URI cannot be found, %FALSE is returned and + * @error is set to %G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND. + * + * Returns: %TRUE if the icon for the bookmark for the URI was found. + * You should free the returned strings. + * + * Since: 2.12 + */ +gboolean +g_bookmark_file_get_icon (GBookmarkFile *bookmark, + const gchar *uri, + gchar **href, + gchar **mime_type, + GError **error) +{ + BookmarkItem *item; + + g_return_val_if_fail (bookmark != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + item = g_bookmark_file_lookup_item (bookmark, uri); + if (!item) + { + g_set_error (error, G_BOOKMARK_FILE_ERROR, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + _("No bookmark found for URI “%s”"), + uri); + return FALSE; + } + + if ((!item->metadata) || (!item->metadata->icon_href)) + return FALSE; + + if (href) + *href = g_strdup (item->metadata->icon_href); + + if (mime_type) + *mime_type = g_strdup (item->metadata->icon_mime); + + return TRUE; +} diff --git a/glib/gbookmarkfile.h b/glib/gbookmarkfile.h new file mode 100644 index 0000000..82ea98d --- /dev/null +++ b/glib/gbookmarkfile.h @@ -0,0 +1,295 @@ +/* gbookmarkfile.h: parsing and building desktop bookmarks + * + * Copyright (C) 2005-2006 Emmanuele Bassi + * + * 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_BOOKMARK_FILE_H__ +#define __G_BOOKMARK_FILE_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +/** + * G_BOOKMARK_FILE_ERROR: + * + * Error domain for bookmark file parsing. + * + * Errors in this domain will be from the #GBookmarkFileError + * enumeration. See #GError for information on error domains. + */ +#define G_BOOKMARK_FILE_ERROR (g_bookmark_file_error_quark ()) + + +/** + * GBookmarkFileError: + * @G_BOOKMARK_FILE_ERROR_INVALID_URI: URI was ill-formed + * @G_BOOKMARK_FILE_ERROR_INVALID_VALUE: a requested field was not found + * @G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED: a requested application did + * not register a bookmark + * @G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND: a requested URI was not found + * @G_BOOKMARK_FILE_ERROR_READ: document was ill formed + * @G_BOOKMARK_FILE_ERROR_UNKNOWN_ENCODING: the text being parsed was + * in an unknown encoding + * @G_BOOKMARK_FILE_ERROR_WRITE: an error occurred while writing + * @G_BOOKMARK_FILE_ERROR_FILE_NOT_FOUND: requested file was not found + * + * Error codes returned by bookmark file parsing. + */ +typedef enum +{ + G_BOOKMARK_FILE_ERROR_INVALID_URI, + G_BOOKMARK_FILE_ERROR_INVALID_VALUE, + G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED, + G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND, + G_BOOKMARK_FILE_ERROR_READ, + G_BOOKMARK_FILE_ERROR_UNKNOWN_ENCODING, + G_BOOKMARK_FILE_ERROR_WRITE, + G_BOOKMARK_FILE_ERROR_FILE_NOT_FOUND +} GBookmarkFileError; + +GLIB_AVAILABLE_IN_ALL +GQuark g_bookmark_file_error_quark (void); + +/** + * GBookmarkFile: + * + * An opaque data structure representing a set of bookmarks. + */ +typedef struct _GBookmarkFile GBookmarkFile; + +GLIB_AVAILABLE_IN_ALL +GBookmarkFile *g_bookmark_file_new (void); +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_free (GBookmarkFile *bookmark); + +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_load_from_file (GBookmarkFile *bookmark, + const gchar *filename, + GError **error); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_load_from_data (GBookmarkFile *bookmark, + const gchar *data, + gsize length, + GError **error); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_load_from_data_dirs (GBookmarkFile *bookmark, + const gchar *file, + gchar **full_path, + GError **error); +GLIB_AVAILABLE_IN_ALL +gchar * g_bookmark_file_to_data (GBookmarkFile *bookmark, + gsize *length, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_to_file (GBookmarkFile *bookmark, + const gchar *filename, + GError **error); + +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_set_title (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *title); +GLIB_AVAILABLE_IN_ALL +gchar * g_bookmark_file_get_title (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_set_description (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *description); +GLIB_AVAILABLE_IN_ALL +gchar * g_bookmark_file_get_description (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_set_mime_type (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *mime_type); +GLIB_AVAILABLE_IN_ALL +gchar * g_bookmark_file_get_mime_type (GBookmarkFile *bookmark, + const gchar *uri, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_set_groups (GBookmarkFile *bookmark, + const gchar *uri, + const gchar **groups, + gsize length); +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_add_group (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *group); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_has_group (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *group, + GError **error); +GLIB_AVAILABLE_IN_ALL +gchar ** g_bookmark_file_get_groups (GBookmarkFile *bookmark, + const gchar *uri, + gsize *length, + GError **error); +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_add_application (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + const gchar *exec); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_has_application (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + GError **error); +GLIB_AVAILABLE_IN_ALL +gchar ** g_bookmark_file_get_applications (GBookmarkFile *bookmark, + const gchar *uri, + gsize *length, + GError **error); +GLIB_DEPRECATED_IN_2_66_FOR(g_bookmark_file_set_application_info) +gboolean g_bookmark_file_set_app_info (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + const gchar *exec, + gint count, + time_t stamp, + GError **error); +GLIB_AVAILABLE_IN_2_66 +gboolean g_bookmark_file_set_application_info (GBookmarkFile *bookmark, + const char *uri, + const char *name, + const char *exec, + int count, + GDateTime *stamp, + GError **error); +GLIB_DEPRECATED_IN_2_66_FOR(g_bookmark_file_get_application_info) +gboolean g_bookmark_file_get_app_info (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + gchar **exec, + guint *count, + time_t *stamp, + GError **error); +GLIB_AVAILABLE_IN_2_66 +gboolean g_bookmark_file_get_application_info (GBookmarkFile *bookmark, + const char *uri, + const char *name, + char **exec, + unsigned int *count, + GDateTime **stamp, + GError **error); +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_set_is_private (GBookmarkFile *bookmark, + const gchar *uri, + gboolean is_private); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_get_is_private (GBookmarkFile *bookmark, + const gchar *uri, + GError **error); +GLIB_AVAILABLE_IN_ALL +void g_bookmark_file_set_icon (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *href, + const gchar *mime_type); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_get_icon (GBookmarkFile *bookmark, + const gchar *uri, + gchar **href, + gchar **mime_type, + GError **error); +GLIB_DEPRECATED_IN_2_66_FOR(g_bookmark_file_set_added_date_time) +void g_bookmark_file_set_added (GBookmarkFile *bookmark, + const gchar *uri, + time_t added); +GLIB_AVAILABLE_IN_2_66 +void g_bookmark_file_set_added_date_time (GBookmarkFile *bookmark, + const char *uri, + GDateTime *added); +GLIB_DEPRECATED_IN_2_66_FOR(g_bookmark_file_get_added_date_time) +time_t g_bookmark_file_get_added (GBookmarkFile *bookmark, + const gchar *uri, + GError **error); +GLIB_AVAILABLE_IN_2_66 +GDateTime *g_bookmark_file_get_added_date_time (GBookmarkFile *bookmark, + const char *uri, + GError **error); +GLIB_DEPRECATED_IN_2_66_FOR(g_bookmark_file_set_modified_date_time) +void g_bookmark_file_set_modified (GBookmarkFile *bookmark, + const gchar *uri, + time_t modified); +GLIB_AVAILABLE_IN_2_66 +void g_bookmark_file_set_modified_date_time (GBookmarkFile *bookmark, + const char *uri, + GDateTime *modified); +GLIB_DEPRECATED_IN_2_66_FOR(g_bookmark_file_get_modified_date_time) +time_t g_bookmark_file_get_modified (GBookmarkFile *bookmark, + const gchar *uri, + GError **error); +GLIB_AVAILABLE_IN_2_66 +GDateTime *g_bookmark_file_get_modified_date_time (GBookmarkFile *bookmark, + const char *uri, + GError **error); +GLIB_DEPRECATED_IN_2_66_FOR(g_bookmark_file_set_visited_date_time) +void g_bookmark_file_set_visited (GBookmarkFile *bookmark, + const gchar *uri, + time_t visited); +GLIB_AVAILABLE_IN_2_66 +void g_bookmark_file_set_visited_date_time (GBookmarkFile *bookmark, + const char *uri, + GDateTime *visited); +GLIB_DEPRECATED_IN_2_66_FOR(g_bookmark_file_get_visited_date_time) +time_t g_bookmark_file_get_visited (GBookmarkFile *bookmark, + const gchar *uri, + GError **error); +GLIB_AVAILABLE_IN_2_66 +GDateTime *g_bookmark_file_get_visited_date_time (GBookmarkFile *bookmark, + const char *uri, + GError **error); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_has_item (GBookmarkFile *bookmark, + const gchar *uri); +GLIB_AVAILABLE_IN_ALL +gint g_bookmark_file_get_size (GBookmarkFile *bookmark); +GLIB_AVAILABLE_IN_ALL +gchar ** g_bookmark_file_get_uris (GBookmarkFile *bookmark, + gsize *length); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_remove_group (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *group, + GError **error); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_remove_application (GBookmarkFile *bookmark, + const gchar *uri, + const gchar *name, + GError **error); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_remove_item (GBookmarkFile *bookmark, + const gchar *uri, + GError **error); +GLIB_AVAILABLE_IN_ALL +gboolean g_bookmark_file_move_item (GBookmarkFile *bookmark, + const gchar *old_uri, + const gchar *new_uri, + GError **error); + +G_END_DECLS + +#endif /* __G_BOOKMARK_FILE_H__ */ diff --git a/glib/gbsearcharray.h b/glib/gbsearcharray.h new file mode 100644 index 0000000..39afa3f --- /dev/null +++ b/glib/gbsearcharray.h @@ -0,0 +1,299 @@ +/* GBSearchArray - Binary Searchable Array implementation + * Copyright (C) 2000-2003 Tim Janik + * + * This software is provided "as is"; redistribution and modification + * is permitted, provided that the following disclaimer is retained. + * + * This software 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. + */ +#ifndef __G_BSEARCH_ARRAY_H__ +#define __G_BSEARCH_ARRAY_H__ + +#include +#include + + +G_BEGIN_DECLS /* c++ guards */ + +/* this implementation is intended to be usable in third-party code + * simply by pasting the contents of this file. as such, the + * implementation needs to be self-contained within this file. + */ + +/* convenience macro to avoid signed overflow for value comparisons */ +#define G_BSEARCH_ARRAY_CMP(v1,v2) ((v1) > (v2) ? +1 : (v1) == (v2) ? 0 : -1) + + +/* --- typedefs --- */ +typedef gint (*GBSearchCompareFunc) (gconstpointer bsearch_node1, /* key */ + gconstpointer bsearch_node2); +typedef enum +{ + G_BSEARCH_ARRAY_ALIGN_POWER2 = 1 << 0, /* align memory to power2 sizes */ + G_BSEARCH_ARRAY_AUTO_SHRINK = 1 << 1 /* shrink array upon removal */ +} GBSearchArrayFlags; + + +/* --- structures --- */ +typedef struct +{ + guint sizeof_node; + GBSearchCompareFunc cmp_nodes; + guint flags; +} GBSearchConfig; +typedef union +{ + guint n_nodes; + /*< private >*/ + gpointer alignment_dummy1; + glong alignment_dummy2; + gdouble alignment_dummy3; +} GBSearchArray; + + +/* --- public API --- */ +static inline GBSearchArray* g_bsearch_array_create (const GBSearchConfig *bconfig); +static inline gpointer g_bsearch_array_get_nth (GBSearchArray *barray, + const GBSearchConfig *bconfig, + guint nth); +static inline guint g_bsearch_array_get_index (GBSearchArray *barray, + const GBSearchConfig *bconfig, + gconstpointer node_in_array); +static inline GBSearchArray* g_bsearch_array_remove (GBSearchArray *barray, + const GBSearchConfig *bconfig, + guint index_); +/* provide uninitialized space at index for node insertion */ +static inline GBSearchArray* g_bsearch_array_grow (GBSearchArray *barray, + const GBSearchConfig *bconfig, + guint index); +/* insert key_node into array if it does not exist, otherwise do nothing */ +static inline GBSearchArray* g_bsearch_array_insert (GBSearchArray *barray, + const GBSearchConfig *bconfig, + gconstpointer key_node); +/* insert key_node into array if it does not exist, + * otherwise replace the existing node's contents with key_node + */ +static inline GBSearchArray* g_bsearch_array_replace (GBSearchArray *barray, + const GBSearchConfig *bconfig, + gconstpointer key_node); +static inline void g_bsearch_array_free (GBSearchArray *barray, + const GBSearchConfig *bconfig); +#define g_bsearch_array_get_n_nodes(barray) (((GBSearchArray*) (barray))->n_nodes) + +/* g_bsearch_array_lookup(): + * return NULL or exact match node + */ +#define g_bsearch_array_lookup(barray, bconfig, key_node) \ + g_bsearch_array_lookup_fuzzy ((barray), (bconfig), (key_node), 0) + +/* g_bsearch_array_lookup_sibling(): + * return NULL for barray->n_nodes==0, otherwise return the + * exact match node, or, if there's no such node, return the + * node last visited, which is pretty close to an exact match + * (will be one off into either direction). + */ +#define g_bsearch_array_lookup_sibling(barray, bconfig, key_node) \ + g_bsearch_array_lookup_fuzzy ((barray), (bconfig), (key_node), 1) + +/* g_bsearch_array_lookup_insertion(): + * return NULL for barray->n_nodes==0 or exact match, otherwise + * return the node where key_node should be inserted (may be one + * after end, i.e. g_bsearch_array_get_index(result) <= barray->n_nodes). + */ +#define g_bsearch_array_lookup_insertion(barray, bconfig, key_node) \ + g_bsearch_array_lookup_fuzzy ((barray), (bconfig), (key_node), 2) + + +/* --- implementation --- */ +/* helper macro to cut down realloc()s */ +#define G_BSEARCH_UPPER_POWER2(n) ((n) ? 1 << g_bit_storage ((n) - 1) : 0) +#define G_BSEARCH_ARRAY_NODES(barray) (((guint8*) (barray)) + sizeof (GBSearchArray)) +static inline GBSearchArray* +g_bsearch_array_create (const GBSearchConfig *bconfig) +{ + GBSearchArray *barray; + guint size; + + g_return_val_if_fail (bconfig != NULL, NULL); + + size = sizeof (GBSearchArray) + bconfig->sizeof_node; + if (bconfig->flags & G_BSEARCH_ARRAY_ALIGN_POWER2) + size = G_BSEARCH_UPPER_POWER2 (size); + barray = (GBSearchArray *) g_malloc (size); + memset (barray, 0, sizeof (GBSearchArray)); + + return barray; +} +static inline gpointer +g_bsearch_array_lookup_fuzzy (GBSearchArray *barray, + const GBSearchConfig *bconfig, + gconstpointer key_node, + const guint sibling_or_after); +static inline gpointer +g_bsearch_array_lookup_fuzzy (GBSearchArray *barray, + const GBSearchConfig *bconfig, + gconstpointer key_node, + const guint sibling_or_after) +{ + GBSearchCompareFunc cmp_nodes = bconfig->cmp_nodes; + guint8 *check = NULL, *nodes = G_BSEARCH_ARRAY_NODES (barray); + guint n_nodes = barray->n_nodes, offs = 0; + guint sizeof_node = bconfig->sizeof_node; + gint cmp = 0; + + while (offs < n_nodes) + { + guint i = (offs + n_nodes) >> 1; + + check = nodes + i * sizeof_node; + cmp = cmp_nodes (key_node, check); + if (cmp == 0) + return sibling_or_after > 1 ? NULL : check; + else if (cmp < 0) + n_nodes = i; + else /* (cmp > 0) */ + offs = i + 1; + } + + /* check is last mismatch, cmp > 0 indicates greater key */ + return G_LIKELY (!sibling_or_after) ? NULL : (sibling_or_after > 1 && cmp > 0) ? check + sizeof_node : check; +} +static inline gpointer +g_bsearch_array_get_nth (GBSearchArray *barray, + const GBSearchConfig *bconfig, + guint nth) +{ + return (G_LIKELY (nth < barray->n_nodes) ? + G_BSEARCH_ARRAY_NODES (barray) + nth * bconfig->sizeof_node : + NULL); +} +static inline guint +g_bsearch_array_get_index (GBSearchArray *barray, + const GBSearchConfig *bconfig, + gconstpointer node_in_array) +{ + guint distance = ((guint8*) node_in_array) - G_BSEARCH_ARRAY_NODES (barray); + + g_return_val_if_fail (node_in_array != NULL, barray->n_nodes); + + distance /= bconfig->sizeof_node; + + return MIN (distance, barray->n_nodes + 1); /* may return one after end */ +} +static inline GBSearchArray* +g_bsearch_array_grow (GBSearchArray *barray, + const GBSearchConfig *bconfig, + guint index_) +{ + guint old_size = barray->n_nodes * bconfig->sizeof_node; + guint new_size = old_size + bconfig->sizeof_node; + guint8 *node; + + g_return_val_if_fail (index_ <= barray->n_nodes, NULL); + + if (G_UNLIKELY (bconfig->flags & G_BSEARCH_ARRAY_ALIGN_POWER2)) + { + new_size = G_BSEARCH_UPPER_POWER2 (sizeof (GBSearchArray) + new_size); + old_size = G_BSEARCH_UPPER_POWER2 (sizeof (GBSearchArray) + old_size); + if (old_size != new_size) + barray = (GBSearchArray *) g_realloc (barray, new_size); + } + else + barray = (GBSearchArray *) g_realloc (barray, sizeof (GBSearchArray) + new_size); + node = G_BSEARCH_ARRAY_NODES (barray) + index_ * bconfig->sizeof_node; + memmove (node + bconfig->sizeof_node, node, (barray->n_nodes - index_) * bconfig->sizeof_node); + barray->n_nodes += 1; + return barray; +} +static inline GBSearchArray* +g_bsearch_array_insert (GBSearchArray *barray, + const GBSearchConfig *bconfig, + gconstpointer key_node) +{ + guint8 *node; + + if (G_UNLIKELY (!barray->n_nodes)) + { + barray = g_bsearch_array_grow (barray, bconfig, 0); + node = G_BSEARCH_ARRAY_NODES (barray); + } + else + { + node = (guint8 *) g_bsearch_array_lookup_insertion (barray, bconfig, key_node); + if (G_LIKELY (node)) + { + guint index_ = g_bsearch_array_get_index (barray, bconfig, node); + + /* grow and insert */ + barray = g_bsearch_array_grow (barray, bconfig, index_); + node = G_BSEARCH_ARRAY_NODES (barray) + index_ * bconfig->sizeof_node; + } + else /* no insertion needed, node already there */ + return barray; + } + memcpy (node, key_node, bconfig->sizeof_node); + return barray; +} +static inline GBSearchArray* +g_bsearch_array_replace (GBSearchArray *barray, + const GBSearchConfig *bconfig, + gconstpointer key_node) +{ + guint8 *node = (guint8 *) g_bsearch_array_lookup (barray, bconfig, key_node); + if (G_LIKELY (node)) /* expected path */ + memcpy (node, key_node, bconfig->sizeof_node); + else /* revert to insertion */ + barray = g_bsearch_array_insert (barray, bconfig, key_node); + return barray; +} +static inline GBSearchArray* +g_bsearch_array_remove (GBSearchArray *barray, + const GBSearchConfig *bconfig, + guint index_) +{ + guint8 *node; + + g_return_val_if_fail (index_ < barray->n_nodes, NULL); + + barray->n_nodes -= 1; + node = G_BSEARCH_ARRAY_NODES (barray) + index_ * bconfig->sizeof_node; + memmove (node, node + bconfig->sizeof_node, (barray->n_nodes - index_) * bconfig->sizeof_node); + if (G_UNLIKELY (bconfig->flags & G_BSEARCH_ARRAY_AUTO_SHRINK)) + { + guint new_size = barray->n_nodes * bconfig->sizeof_node; + guint old_size = new_size + bconfig->sizeof_node; + + if (G_UNLIKELY (bconfig->flags & G_BSEARCH_ARRAY_ALIGN_POWER2)) + { + new_size = G_BSEARCH_UPPER_POWER2 (sizeof (GBSearchArray) + new_size); + old_size = G_BSEARCH_UPPER_POWER2 (sizeof (GBSearchArray) + old_size); + if (old_size != new_size) + barray = (GBSearchArray *) g_realloc (barray, new_size); + } + else + barray = (GBSearchArray *) g_realloc (barray, sizeof (GBSearchArray) + new_size); + } + return barray; +} +static inline void +g_bsearch_array_free (GBSearchArray *barray, + const GBSearchConfig *bconfig) +{ + g_return_if_fail (barray != NULL); + + g_free (barray); +} + +G_END_DECLS /* c++ guards */ + +#endif /* !__G_BSEARCH_ARRAY_H__ */ diff --git a/glib/gbytes.c b/glib/gbytes.c new file mode 100644 index 0000000..a6ca0e3 --- /dev/null +++ b/glib/gbytes.c @@ -0,0 +1,612 @@ +/* + * Copyright © 2009, 2010 Codethink Limited + * Copyright © 2011 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 . + * + * Author: Ryan Lortie + * Stef Walter + */ + +#include "config.h" + +#include "gbytes.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * GBytes: + * + * A simple refcounted data type representing an immutable sequence of zero or + * more bytes from an unspecified origin. + * + * The purpose of a #GBytes is to keep the memory region that it holds + * alive for as long as anyone holds a reference to the bytes. When + * the last reference count is dropped, the memory is released. Multiple + * unrelated callers can use byte data in the #GBytes without coordinating + * their activities, resting assured that the byte data will not change or + * move while they hold a reference. + * + * A #GBytes can come from many different origins that may have + * different procedures for freeing the memory region. Examples are + * memory from g_malloc(), from memory slices, from a #GMappedFile or + * memory from other allocators. + * + * #GBytes work well as keys in #GHashTable. Use g_bytes_equal() and + * g_bytes_hash() as parameters to g_hash_table_new() or g_hash_table_new_full(). + * #GBytes can also be used as keys in a #GTree by passing the g_bytes_compare() + * function to g_tree_new(). + * + * The data pointed to by this bytes must not be modified. For a mutable + * array of bytes see #GByteArray. Use g_bytes_unref_to_array() to create a + * mutable array for a #GBytes sequence. To create an immutable #GBytes from + * a mutable #GByteArray, use the g_byte_array_free_to_bytes() function. + * + * Since: 2.32 + **/ + +/* Keep in sync with glib/tests/bytes.c */ +struct _GBytes +{ + gconstpointer data; /* may be NULL iff (size == 0) */ + gsize size; /* may be 0 */ + gatomicrefcount ref_count; + GDestroyNotify free_func; + gpointer user_data; +}; + +/** + * g_bytes_new: + * @data: (transfer none) (array length=size) (element-type guint8) (nullable): + * the data to be used for the bytes + * @size: the size of @data + * + * Creates a new #GBytes from @data. + * + * @data is copied. If @size is 0, @data may be %NULL. + * + * Returns: (transfer full): a new #GBytes + * + * Since: 2.32 + */ +GBytes * +g_bytes_new (gconstpointer data, + gsize size) +{ + g_return_val_if_fail (data != NULL || size == 0, NULL); + + return g_bytes_new_take (g_memdup2 (data, size), size); +} + +/** + * g_bytes_new_take: + * @data: (transfer full) (array length=size) (element-type guint8) (nullable): + * the data to be used for the bytes + * @size: the size of @data + * + * Creates a new #GBytes from @data. + * + * After this call, @data belongs to the bytes and may no longer be + * modified by the caller. g_free() will be called on @data when the + * bytes is no longer in use. Because of this @data must have been created by + * a call to g_malloc(), g_malloc0() or g_realloc() or by one of the many + * functions that wrap these calls (such as g_new(), g_strdup(), etc). + * + * For creating #GBytes with memory from other allocators, see + * g_bytes_new_with_free_func(). + * + * @data may be %NULL if @size is 0. + * + * Returns: (transfer full): a new #GBytes + * + * Since: 2.32 + */ +GBytes * +g_bytes_new_take (gpointer data, + gsize size) +{ + return g_bytes_new_with_free_func (data, size, g_free, data); +} + + +/** + * g_bytes_new_static: (skip) + * @data: (transfer full) (array length=size) (element-type guint8) (nullable): + * the data to be used for the bytes + * @size: the size of @data + * + * Creates a new #GBytes from static data. + * + * @data must be static (ie: never modified or freed). It may be %NULL if @size + * is 0. + * + * Returns: (transfer full): a new #GBytes + * + * Since: 2.32 + */ +GBytes * +g_bytes_new_static (gconstpointer data, + gsize size) +{ + return g_bytes_new_with_free_func (data, size, NULL, NULL); +} + +/** + * g_bytes_new_with_free_func: (skip) + * @data: (array length=size) (element-type guint8) (nullable): + * the data to be used for the bytes + * @size: the size of @data + * @free_func: the function to call to release the data + * @user_data: data to pass to @free_func + * + * Creates a #GBytes from @data. + * + * When the last reference is dropped, @free_func will be called with the + * @user_data argument. + * + * @data must not be modified after this call is made until @free_func has + * been called to indicate that the bytes is no longer in use. + * + * @data may be %NULL if @size is 0. + * + * Returns: (transfer full): a new #GBytes + * + * Since: 2.32 + */ +GBytes * +g_bytes_new_with_free_func (gconstpointer data, + gsize size, + GDestroyNotify free_func, + gpointer user_data) +{ + GBytes *bytes; + + g_return_val_if_fail (data != NULL || size == 0, NULL); + + bytes = g_slice_new (GBytes); + bytes->data = data; + bytes->size = size; + bytes->free_func = free_func; + bytes->user_data = user_data; + g_atomic_ref_count_init (&bytes->ref_count); + + return (GBytes *)bytes; +} + +/** + * g_bytes_new_from_bytes: + * @bytes: a #GBytes + * @offset: offset which subsection starts at + * @length: length of subsection + * + * Creates a #GBytes which is a subsection of another #GBytes. The @offset + + * @length may not be longer than the size of @bytes. + * + * A reference to @bytes will be held by the newly created #GBytes until + * the byte data is no longer needed. + * + * Since 2.56, if @offset is 0 and @length matches the size of @bytes, then + * @bytes will be returned with the reference count incremented by 1. If @bytes + * is a slice of another #GBytes, then the resulting #GBytes will reference + * the same #GBytes instead of @bytes. This allows consumers to simplify the + * usage of #GBytes when asynchronously writing to streams. + * + * Returns: (transfer full): a new #GBytes + * + * Since: 2.32 + */ +GBytes * +g_bytes_new_from_bytes (GBytes *bytes, + gsize offset, + gsize length) +{ + gchar *base; + + /* Note that length may be 0. */ + g_return_val_if_fail (bytes != NULL, NULL); + g_return_val_if_fail (offset <= bytes->size, NULL); + g_return_val_if_fail (offset + length <= bytes->size, NULL); + + /* Avoid an extra GBytes if all bytes were requested */ + if (offset == 0 && length == bytes->size) + return g_bytes_ref (bytes); + + base = (gchar *)bytes->data + offset; + + /* Avoid referencing intermediate GBytes. In practice, this should + * only loop once. + */ + while (bytes->free_func == (gpointer)g_bytes_unref) + bytes = bytes->user_data; + + g_return_val_if_fail (bytes != NULL, NULL); + g_return_val_if_fail (base >= (gchar *)bytes->data, NULL); + g_return_val_if_fail (base <= (gchar *)bytes->data + bytes->size, NULL); + g_return_val_if_fail (base + length <= (gchar *)bytes->data + bytes->size, NULL); + + return g_bytes_new_with_free_func (base, length, + (GDestroyNotify)g_bytes_unref, g_bytes_ref (bytes)); +} + +/** + * g_bytes_get_data: + * @bytes: a #GBytes + * @size: (out) (optional): location to return size of byte data + * + * Get the byte data in the #GBytes. This data should not be modified. + * + * This function will always return the same pointer for a given #GBytes. + * + * %NULL may be returned if @size is 0. This is not guaranteed, as the #GBytes + * may represent an empty string with @data non-%NULL and @size as 0. %NULL will + * not be returned if @size is non-zero. + * + * Returns: (transfer none) (array length=size) (element-type guint8) (nullable): + * a pointer to the byte data, or %NULL + * + * Since: 2.32 + */ +gconstpointer +g_bytes_get_data (GBytes *bytes, + gsize *size) +{ + g_return_val_if_fail (bytes != NULL, NULL); + if (size) + *size = bytes->size; + return bytes->data; +} + +/** + * g_bytes_get_size: + * @bytes: a #GBytes + * + * Get the size of the byte data in the #GBytes. + * + * This function will always return the same value for a given #GBytes. + * + * Returns: the size + * + * Since: 2.32 + */ +gsize +g_bytes_get_size (GBytes *bytes) +{ + g_return_val_if_fail (bytes != NULL, 0); + return bytes->size; +} + + +/** + * g_bytes_ref: + * @bytes: a #GBytes + * + * Increase the reference count on @bytes. + * + * Returns: the #GBytes + * + * Since: 2.32 + */ +GBytes * +g_bytes_ref (GBytes *bytes) +{ + g_return_val_if_fail (bytes != NULL, NULL); + + g_atomic_ref_count_inc (&bytes->ref_count); + + return bytes; +} + +/** + * g_bytes_unref: + * @bytes: (nullable): a #GBytes + * + * Releases a reference on @bytes. This may result in the bytes being + * freed. If @bytes is %NULL, it will return immediately. + * + * Since: 2.32 + */ +void +g_bytes_unref (GBytes *bytes) +{ + if (bytes == NULL) + return; + + if (g_atomic_ref_count_dec (&bytes->ref_count)) + { + if (bytes->free_func != NULL) + bytes->free_func (bytes->user_data); + g_slice_free (GBytes, bytes); + } +} + +/** + * g_bytes_equal: + * @bytes1: (type GLib.Bytes): a pointer to a #GBytes + * @bytes2: (type GLib.Bytes): a pointer to a #GBytes to compare with @bytes1 + * + * Compares the two #GBytes values being pointed to and returns + * %TRUE if they are equal. + * + * This function can be passed to g_hash_table_new() as the @key_equal_func + * parameter, when using non-%NULL #GBytes pointers as keys in a #GHashTable. + * + * Returns: %TRUE if the two keys match. + * + * Since: 2.32 + */ +gboolean +g_bytes_equal (gconstpointer bytes1, + gconstpointer bytes2) +{ + const GBytes *b1 = bytes1; + const GBytes *b2 = bytes2; + + g_return_val_if_fail (bytes1 != NULL, FALSE); + g_return_val_if_fail (bytes2 != NULL, FALSE); + + return b1->size == b2->size && + (b1->size == 0 || memcmp (b1->data, b2->data, b1->size) == 0); +} + +/** + * g_bytes_hash: + * @bytes: (type GLib.Bytes): a pointer to a #GBytes key + * + * Creates an integer hash code for the byte data in the #GBytes. + * + * This function can be passed to g_hash_table_new() as the @key_hash_func + * parameter, when using non-%NULL #GBytes pointers as keys in a #GHashTable. + * + * Returns: a hash value corresponding to the key. + * + * Since: 2.32 + */ +guint +g_bytes_hash (gconstpointer bytes) +{ + const GBytes *a = bytes; + const signed char *p, *e; + guint32 h = 5381; + + g_return_val_if_fail (bytes != NULL, 0); + + for (p = (signed char *)a->data, e = (signed char *)a->data + a->size; p != e; p++) + h = (h << 5) + h + *p; + + return h; +} + +/** + * g_bytes_compare: + * @bytes1: (type GLib.Bytes): a pointer to a #GBytes + * @bytes2: (type GLib.Bytes): a pointer to a #GBytes to compare with @bytes1 + * + * Compares the two #GBytes values. + * + * This function can be used to sort GBytes instances in lexicographical order. + * + * If @bytes1 and @bytes2 have different length but the shorter one is a + * prefix of the longer one then the shorter one is considered to be less than + * the longer one. Otherwise the first byte where both differ is used for + * comparison. If @bytes1 has a smaller value at that position it is + * considered less, otherwise greater than @bytes2. + * + * Returns: a negative value if @bytes1 is less than @bytes2, a positive value + * if @bytes1 is greater than @bytes2, and zero if @bytes1 is equal to + * @bytes2 + * + * + * Since: 2.32 + */ +gint +g_bytes_compare (gconstpointer bytes1, + gconstpointer bytes2) +{ + const GBytes *b1 = bytes1; + const GBytes *b2 = bytes2; + gint ret; + + g_return_val_if_fail (bytes1 != NULL, 0); + g_return_val_if_fail (bytes2 != NULL, 0); + + ret = memcmp (b1->data, b2->data, MIN (b1->size, b2->size)); + if (ret == 0 && b1->size != b2->size) + ret = b1->size < b2->size ? -1 : 1; + return ret; +} + +static gpointer +try_steal_and_unref (GBytes *bytes, + GDestroyNotify free_func, + gsize *size) +{ + gpointer result; + + if (bytes->free_func != free_func || bytes->data == NULL || + bytes->user_data != bytes->data) + return NULL; + + /* Are we the only reference? */ + if (g_atomic_ref_count_compare (&bytes->ref_count, 1)) + { + *size = bytes->size; + result = (gpointer)bytes->data; + g_slice_free (GBytes, bytes); + return result; + } + + return NULL; +} + + +/** + * g_bytes_unref_to_data: + * @bytes: (transfer full): a #GBytes + * @size: (out): location to place the length of the returned data + * + * Unreferences the bytes, and returns a pointer the same byte data + * contents. + * + * As an optimization, the byte data is returned without copying if this was + * the last reference to bytes and bytes was created with g_bytes_new(), + * g_bytes_new_take() or g_byte_array_free_to_bytes(). In all other cases the + * data is copied. + * + * Returns: (transfer full) (array length=size) (element-type guint8) + * (not nullable): a pointer to the same byte data, which should be + * freed with g_free() + * + * Since: 2.32 + */ +gpointer +g_bytes_unref_to_data (GBytes *bytes, + gsize *size) +{ + gpointer result; + + g_return_val_if_fail (bytes != NULL, NULL); + g_return_val_if_fail (size != NULL, NULL); + + /* + * Optimal path: if this is was the last reference, then we can return + * the data from this GBytes without copying. + */ + + result = try_steal_and_unref (bytes, g_free, size); + if (result == NULL) + { + /* + * Copy: Non g_malloc (or compatible) allocator, or static memory, + * so we have to copy, and then unref. + */ + result = g_memdup2 (bytes->data, bytes->size); + *size = bytes->size; + g_bytes_unref (bytes); + } + + return result; +} + +/** + * g_bytes_unref_to_array: + * @bytes: (transfer full): a #GBytes + * + * Unreferences the bytes, and returns a new mutable #GByteArray containing + * the same byte data. + * + * As an optimization, the byte data is transferred to the array without copying + * if this was the last reference to bytes and bytes was created with + * g_bytes_new(), g_bytes_new_take() or g_byte_array_free_to_bytes(). In all + * other cases the data is copied. + * + * Do not use it if @bytes contains more than %G_MAXUINT + * bytes. #GByteArray stores the length of its data in #guint, which + * may be shorter than #gsize, that @bytes is using. + * + * Returns: (transfer full): a new mutable #GByteArray containing the same byte data + * + * Since: 2.32 + */ +GByteArray * +g_bytes_unref_to_array (GBytes *bytes) +{ + gpointer data; + gsize size; + + g_return_val_if_fail (bytes != NULL, NULL); + + data = g_bytes_unref_to_data (bytes, &size); + return g_byte_array_new_take (data, size); +} + +/** + * g_bytes_get_region: + * @bytes: a #GBytes + * @element_size: a non-zero element size + * @offset: an offset to the start of the region within the @bytes + * @n_elements: the number of elements in the region + * + * Gets a pointer to a region in @bytes. + * + * The region starts at @offset many bytes from the start of the data + * and contains @n_elements many elements of @element_size size. + * + * @n_elements may be zero, but @element_size must always be non-zero. + * Ideally, @element_size is a static constant (eg: sizeof a struct). + * + * This function does careful bounds checking (including checking for + * arithmetic overflows) and returns a non-%NULL pointer if the + * specified region lies entirely within the @bytes. If the region is + * in some way out of range, or if an overflow has occurred, then %NULL + * is returned. + * + * Note: it is possible to have a valid zero-size region. In this case, + * the returned pointer will be equal to the base pointer of the data of + * @bytes, plus @offset. This will be non-%NULL except for the case + * where @bytes itself was a zero-sized region. Since it is unlikely + * that you will be using this function to check for a zero-sized region + * in a zero-sized @bytes, %NULL effectively always means "error". + * + * Returns: (nullable): the requested region, or %NULL in case of an error + * + * Since: 2.70 + */ +gconstpointer +g_bytes_get_region (GBytes *bytes, + gsize element_size, + gsize offset, + gsize n_elements) +{ + gsize total_size; + gsize end_offset; + + g_return_val_if_fail (element_size > 0, NULL); + + /* No other assertion checks here. If something is wrong then we will + * simply crash (via NULL dereference or divide-by-zero). + */ + + if (!g_size_checked_mul (&total_size, element_size, n_elements)) + return NULL; + + if (!g_size_checked_add (&end_offset, offset, total_size)) + return NULL; + + /* We now have: + * + * 0 <= offset <= end_offset + * + * So we need only check that end_offset is within the range of the + * size of @bytes and we're good to go. + */ + + if (end_offset > bytes->size) + return NULL; + + /* We now have: + * + * 0 <= offset <= end_offset <= bytes->size + */ + + return ((guchar *) bytes->data) + offset; +} \ No newline at end of file diff --git a/glib/gbytes.h b/glib/gbytes.h new file mode 100644 index 0000000..37cad86 --- /dev/null +++ b/glib/gbytes.h @@ -0,0 +1,97 @@ +/* + * Copyright © 2009, 2010 Codethink Limited + * Copyright © 2011 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 . + * + * Author: Ryan Lortie + * Stef Walter + */ + +#ifndef __G_BYTES_H__ +#define __G_BYTES_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_ALL +GBytes * g_bytes_new (gconstpointer data, + gsize size); + +GLIB_AVAILABLE_IN_ALL +GBytes * g_bytes_new_take (gpointer data, + gsize size); + +GLIB_AVAILABLE_IN_ALL +GBytes * g_bytes_new_static (gconstpointer data, + gsize size); + +GLIB_AVAILABLE_IN_ALL +GBytes * g_bytes_new_with_free_func (gconstpointer data, + gsize size, + GDestroyNotify free_func, + gpointer user_data); + +GLIB_AVAILABLE_IN_ALL +GBytes * g_bytes_new_from_bytes (GBytes *bytes, + gsize offset, + gsize length); + +GLIB_AVAILABLE_IN_ALL +gconstpointer g_bytes_get_data (GBytes *bytes, + gsize *size); + +GLIB_AVAILABLE_IN_ALL +gsize g_bytes_get_size (GBytes *bytes); + +GLIB_AVAILABLE_IN_ALL +GBytes * g_bytes_ref (GBytes *bytes); + +GLIB_AVAILABLE_IN_ALL +void g_bytes_unref (GBytes *bytes); + +GLIB_AVAILABLE_IN_ALL +gpointer g_bytes_unref_to_data (GBytes *bytes, + gsize *size); + +GLIB_AVAILABLE_IN_ALL +GByteArray * g_bytes_unref_to_array (GBytes *bytes); + +GLIB_AVAILABLE_IN_ALL +guint g_bytes_hash (gconstpointer bytes); + +GLIB_AVAILABLE_IN_ALL +gboolean g_bytes_equal (gconstpointer bytes1, + gconstpointer bytes2); + +GLIB_AVAILABLE_IN_ALL +gint g_bytes_compare (gconstpointer bytes1, + gconstpointer bytes2); + +GLIB_AVAILABLE_IN_2_70 +gconstpointer g_bytes_get_region (GBytes *bytes, + gsize element_size, + gsize offset, + gsize n_elements); + + +G_END_DECLS + +#endif /* __G_BYTES_H__ */ diff --git a/glib/gcharset.c b/glib/gcharset.c new file mode 100644 index 0000000..09d3fa4 --- /dev/null +++ b/glib/gcharset.c @@ -0,0 +1,838 @@ +/* gcharset.c - Charset information + * + * Copyright (C) 2011 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 "gcharset.h" +#include "gcharsetprivate.h" + +#include "garray.h" +#include "genviron.h" +#include "ghash.h" +#include "gmessages.h" +#include "gstrfuncs.h" +#include "gthread.h" +#include "gthreadprivate.h" +#ifdef G_OS_WIN32 +#include "gwin32.h" +#endif + +#include "libcharset/libcharset.h" + +#include +#include + +#if (HAVE_LANGINFO_TIME_CODESET || HAVE_LANGINFO_CODESET) +#include +#endif + +#include +#ifdef G_OS_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +G_LOCK_DEFINE_STATIC (aliases); + +static GHashTable * +get_alias_hash (void) +{ + static GHashTable *alias_hash = NULL; + const char *aliases; + + G_LOCK (aliases); + + if (!alias_hash) + { + alias_hash = g_hash_table_new (g_str_hash, g_str_equal); + + aliases = _g_locale_get_charset_aliases (); + while (*aliases != '\0') + { + const char *canonical; + const char *alias; + const char **alias_array; + int count = 0; + + alias = aliases; + aliases += strlen (aliases) + 1; + canonical = aliases; + aliases += strlen (aliases) + 1; + + alias_array = g_hash_table_lookup (alias_hash, canonical); + if (alias_array) + { + while (alias_array[count]) + count++; + } + + alias_array = g_renew (const char *, alias_array, count + 2); + alias_array[count] = alias; + alias_array[count + 1] = NULL; + + g_hash_table_insert (alias_hash, (char *)canonical, alias_array); + } + } + + G_UNLOCK (aliases); + + return alias_hash; +} + +/* As an abuse of the alias table, the following routines gets + * the charsets that are aliases for the canonical name. + */ +const char ** +_g_charset_get_aliases (const char *canonical_name) +{ + GHashTable *alias_hash = get_alias_hash (); + + return g_hash_table_lookup (alias_hash, canonical_name); +} + +static gboolean +g_utf8_get_charset_internal (const char *raw_data, + const char **a) +{ + /* Allow CHARSET to override the charset of any locale category. Users should + * probably never be setting this — instead, just add the charset after a `.` + * in `LANGUAGE`/`LC_ALL`/`LC_*`/`LANG`. I can’t find any reference (in + * `git log`, code comments, or man pages) to this environment variable being + * standardised or documented or even used anywhere outside GLib. Perhaps it + * should eventually be removed. */ + const char *charset = g_getenv ("CHARSET"); + + if (charset && *charset) + { + *a = charset; + + if (charset && strstr (charset, "UTF-8")) + return TRUE; + else + return FALSE; + } + + /* The libcharset code tries to be thread-safe without + * a lock, but has a memory leak and a missing memory + * barrier, so we lock for it + */ + G_LOCK (aliases); + charset = _g_locale_charset_unalias (raw_data); + G_UNLOCK (aliases); + + if (charset && *charset) + { + *a = charset; + + if (charset && strstr (charset, "UTF-8")) + return TRUE; + else + return FALSE; + } + + /* Assume this for compatibility at present. */ + *a = "US-ASCII"; + + return FALSE; +} + +typedef struct _GCharsetCache GCharsetCache; + +struct _GCharsetCache { + gboolean is_utf8; + gchar *raw; + gchar *charset; +}; + +static void +charset_cache_free (gpointer data) +{ + GCharsetCache *cache = data; + g_free (cache->raw); + g_free (cache->charset); + g_free (cache); +} + +/** + * g_get_charset: + * @charset: (out) (optional) (transfer none): return location for character set + * name, or %NULL. + * + * Obtains the character set for the [current locale][setlocale]; you + * might use this character set as an argument to g_convert(), to convert + * from the current locale's encoding to some other encoding. (Frequently + * g_locale_to_utf8() and g_locale_from_utf8() are nice shortcuts, though.) + * + * On Windows the character set returned by this function is the + * so-called system default ANSI code-page. That is the character set + * used by the "narrow" versions of C library and Win32 functions that + * handle file names. It might be different from the character set + * used by the C library's current locale. + * + * On Linux, the character set is found by consulting nl_langinfo() if + * available. If not, the environment variables `LC_ALL`, `LC_CTYPE`, `LANG` + * and `CHARSET` are queried in order. + * + * The return value is %TRUE if the locale's encoding is UTF-8, in that + * case you can perhaps avoid calling g_convert(). + * + * The string returned in @charset is not allocated, and should not be + * freed. + * + * Returns: %TRUE if the returned charset is UTF-8 + */ +gboolean +g_get_charset (const char **charset) +{ + static GPrivate cache_private = G_PRIVATE_INIT (charset_cache_free); + GCharsetCache *cache = g_private_get (&cache_private); + const gchar *raw; + + if (!cache) + cache = g_private_set_alloc0 (&cache_private, sizeof (GCharsetCache)); + + G_LOCK (aliases); + raw = _g_locale_charset_raw (); + G_UNLOCK (aliases); + + if (cache->raw == NULL || strcmp (cache->raw, raw) != 0) + { + const gchar *new_charset; + + g_free (cache->raw); + g_free (cache->charset); + cache->raw = g_strdup (raw); + cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset); + cache->charset = g_strdup (new_charset); + } + + if (charset) + *charset = cache->charset; + + return cache->is_utf8; +} + +/* + * Do the same as g_get_charset() but it temporarily set locale (LC_ALL to + * LC_TIME) to correctly check for charset about time conversion relatives. + * + * Returns: %TRUE if the returned charset is UTF-8 + */ +gboolean +_g_get_time_charset (const char **charset) +{ + static GPrivate cache_private = G_PRIVATE_INIT (charset_cache_free); + GCharsetCache *cache = g_private_get (&cache_private); + const gchar *raw; + + if (!cache) + cache = g_private_set_alloc0 (&cache_private, sizeof (GCharsetCache)); + +#ifdef HAVE_LANGINFO_TIME_CODESET + raw = nl_langinfo (_NL_TIME_CODESET); +#else + G_LOCK (aliases); + raw = _g_locale_charset_raw (); + G_UNLOCK (aliases); +#endif + + if (cache->raw == NULL || strcmp (cache->raw, raw) != 0) + { + const gchar *new_charset; + + g_free (cache->raw); + g_free (cache->charset); + cache->raw = g_strdup (raw); + cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset); + cache->charset = g_strdup (new_charset); + } + + if (charset) + *charset = cache->charset; + + return cache->is_utf8; +} +/* + * Do the same as g_get_charset() but it temporarily set locale (LC_ALL to + * LC_CTYPE) to correctly check for charset about CTYPE conversion relatives. + * + * Returns: %TRUE if the returned charset is UTF-8 + */ +gboolean +_g_get_ctype_charset (const char **charset) +{ + static GPrivate cache_private = G_PRIVATE_INIT (charset_cache_free); + GCharsetCache *cache = g_private_get (&cache_private); + const gchar *raw; + + if (!cache) + cache = g_private_set_alloc0 (&cache_private, sizeof (GCharsetCache)); + +#ifdef HAVE_LANGINFO_CODESET + raw = nl_langinfo (CODESET); +#else + G_LOCK (aliases); + raw = _g_locale_charset_raw (); + G_UNLOCK (aliases); +#endif + + if (cache->raw == NULL || strcmp (cache->raw, raw) != 0) + { + const gchar *new_charset; + + g_free (cache->raw); + g_free (cache->charset); + cache->raw = g_strdup (raw); + cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset); + cache->charset = g_strdup (new_charset); + } + + if (charset) + *charset = cache->charset; + + return cache->is_utf8; +} + +/** + * g_get_codeset: + * + * Gets the character set for the current locale. + * + * Returns: a newly allocated string containing the name + * of the character set. This string must be freed with g_free(). + */ +gchar * +g_get_codeset (void) +{ + const gchar *charset; + + g_get_charset (&charset); + + return g_strdup (charset); +} + +/** + * g_get_console_charset: + * @charset: (out) (optional) (transfer none): return location for character set + * name, or %NULL. + * + * Obtains the character set used by the console attached to the process, + * which is suitable for printing output to the terminal. + * + * Usually this matches the result returned by g_get_charset(), but in + * environments where the locale's character set does not match the encoding + * of the console this function tries to guess a more suitable value instead. + * + * On Windows the character set returned by this function is the + * output code page used by the console associated with the calling process. + * If the codepage can't be determined (for example because there is no + * console attached) UTF-8 is assumed. + * + * The return value is %TRUE if the locale's encoding is UTF-8, in that + * case you can perhaps avoid calling g_convert(). + * + * The string returned in @charset is not allocated, and should not be + * freed. + * + * Returns: %TRUE if the returned charset is UTF-8 + * + * Since: 2.62 + */ +gboolean +g_get_console_charset (const char **charset) +{ +#ifdef G_OS_WIN32 + static GPrivate cache_private = G_PRIVATE_INIT (charset_cache_free); + GCharsetCache *cache = g_private_get (&cache_private); + const gchar *locale; + unsigned int cp; + char buf[2 + 20 + 1]; /* "CP" + G_MAXUINT64 (to be safe) in decimal form (20 bytes) + "\0" */ + const gchar *raw = NULL; + + if (!cache) + cache = g_private_set_alloc0 (&cache_private, sizeof (GCharsetCache)); + + /* first try to query $LANG (works for Cygwin/MSYS/MSYS2 and others using mintty) */ + locale = g_getenv ("LANG"); + if (locale != NULL && locale[0] != '\0') + { + /* If the locale name contains an encoding after the dot, return it. */ + const char *dot = strchr (locale, '.'); + + if (dot != NULL) + { + const char *modifier; + + dot++; + /* Look for the possible @... trailer and remove it, if any. */ + modifier = strchr (dot, '@'); + if (modifier == NULL) + raw = dot; + else if ((gsize) (modifier - dot) < sizeof (buf)) + { + memcpy (buf, dot, modifier - dot); + buf[modifier - dot] = '\0'; + raw = buf; + } + } + } + /* next try querying console codepage using native win32 API */ + if (raw == NULL) + { + cp = GetConsoleOutputCP (); + if (cp) + { + sprintf (buf, "CP%u", cp); + raw = buf; + } + else if (GetLastError () != ERROR_INVALID_HANDLE) + { + gchar *emsg = g_win32_error_message (GetLastError ()); + g_warning ("Failed to determine console output code page: %s. " + "Falling back to UTF-8", emsg); + g_free (emsg); + } + } + /* fall-back to UTF-8 if the rest failed (it's a universal default) */ + if (raw == NULL) + raw = "UTF-8"; + + if (cache->raw == NULL || strcmp (cache->raw, raw) != 0) + { + const gchar *new_charset; + + g_free (cache->raw); + g_free (cache->charset); + cache->raw = g_strdup (raw); + cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset); + cache->charset = g_strdup (new_charset); + } + + if (charset) + *charset = cache->charset; + + return cache->is_utf8; +#else + /* assume the locale settings match the console encoding on non-Windows OSs */ + return g_get_charset (charset); +#endif +} + +#ifndef G_OS_WIN32 + +/* read an alias file for the locales */ +static void +read_aliases (const gchar *file, + GHashTable *alias_table) +{ + FILE *fp; + char buf[256]; + + fp = fopen (file,"r"); + if (!fp) + return; + while (fgets (buf, 256, fp)) + { + char *p, *q; + + g_strstrip (buf); + + /* Line is a comment */ + if ((buf[0] == '#') || (buf[0] == '\0')) + continue; + + /* Reads first column */ + for (p = buf, q = NULL; *p; p++) { + if ((*p == '\t') || (*p == ' ') || (*p == ':')) { + *p = '\0'; + q = p+1; + while ((*q == '\t') || (*q == ' ')) { + q++; + } + break; + } + } + /* The line only had one column */ + if (!q || *q == '\0') + continue; + + /* Read second column */ + for (p = q; *p; p++) { + if ((*p == '\t') || (*p == ' ')) { + *p = '\0'; + break; + } + } + + /* Add to alias table if necessary */ + if (!g_hash_table_lookup (alias_table, buf)) { + g_hash_table_insert (alias_table, g_strdup (buf), g_strdup (q)); + } + } + fclose (fp); +} + +#endif + +static char * +unalias_lang (char *lang) +{ +#ifndef G_OS_WIN32 + static GHashTable *alias_table = NULL; + char *p; + int i; + + if (g_once_init_enter (&alias_table)) + { + GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal); + read_aliases ("/usr/share/locale/locale.alias", table); + g_once_init_leave (&alias_table, table); + } + + i = 0; + while ((p = g_hash_table_lookup (alias_table, lang)) && (strcmp (p, lang) != 0)) + { + lang = p; + if (i++ == 30) + { + static gboolean said_before = FALSE; + if (!said_before) + g_warning ("Too many alias levels for a locale, " + "may indicate a loop"); + said_before = TRUE; + return lang; + } + } +#endif + return lang; +} + +/* Mask for components of locale spec. The ordering here is from + * least significant to most significant + */ +enum +{ + COMPONENT_CODESET = 1 << 0, + COMPONENT_TERRITORY = 1 << 1, + COMPONENT_MODIFIER = 1 << 2 +}; + +/* Break an X/Open style locale specification into components + */ +static guint +explode_locale (const gchar *locale, + gchar **language, + gchar **territory, + gchar **codeset, + gchar **modifier) +{ + const gchar *uscore_pos; + const gchar *at_pos; + const gchar *dot_pos; + + guint mask = 0; + + uscore_pos = strchr (locale, '_'); + dot_pos = strchr (uscore_pos ? uscore_pos : locale, '.'); + at_pos = strchr (dot_pos ? dot_pos : (uscore_pos ? uscore_pos : locale), '@'); + + if (at_pos) + { + mask |= COMPONENT_MODIFIER; + *modifier = g_strdup (at_pos); + } + else + at_pos = locale + strlen (locale); + + if (dot_pos) + { + mask |= COMPONENT_CODESET; + *codeset = g_strndup (dot_pos, at_pos - dot_pos); + } + else + dot_pos = at_pos; + + if (uscore_pos) + { + mask |= COMPONENT_TERRITORY; + *territory = g_strndup (uscore_pos, dot_pos - uscore_pos); + } + else + uscore_pos = dot_pos; + + *language = g_strndup (locale, uscore_pos - locale); + + return mask; +} + +/* + * Compute all interesting variants for a given locale name - + * by stripping off different components of the value. + * + * For simplicity, we assume that the locale is in + * X/Open format: language[_territory][.codeset][@modifier] + * + * TODO: Extend this to handle the CEN format (see the GNUlibc docs) + * as well. We could just copy the code from glibc wholesale + * but it is big, ugly, and complicated, so I'm reluctant + * to do so when this should handle 99% of the time... + */ +static void +append_locale_variants (GPtrArray *array, + const gchar *locale) +{ + gchar *language = NULL; + gchar *territory = NULL; + gchar *codeset = NULL; + gchar *modifier = NULL; + + guint mask; + guint i, j; + + g_return_if_fail (locale != NULL); + + mask = explode_locale (locale, &language, &territory, &codeset, &modifier); + + /* Iterate through all possible combinations, from least attractive + * to most attractive. + */ + for (j = 0; j <= mask; ++j) + { + i = mask - j; + + if ((i & ~mask) == 0) + { + gchar *val = g_strconcat (language, + (i & COMPONENT_TERRITORY) ? territory : "", + (i & COMPONENT_CODESET) ? codeset : "", + (i & COMPONENT_MODIFIER) ? modifier : "", + NULL); + g_ptr_array_add (array, val); + } + } + + g_free (language); + if (mask & COMPONENT_CODESET) + g_free (codeset); + if (mask & COMPONENT_TERRITORY) + g_free (territory); + if (mask & COMPONENT_MODIFIER) + g_free (modifier); +} + +/** + * g_get_locale_variants: + * @locale: a locale identifier + * + * 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. See + * [`setlocale(3)`](man:setlocale) for information about locales and their format. + * + * @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(). + * + * Returns: (transfer full) (array zero-terminated=1) (element-type utf8): a newly + * allocated array of newly allocated strings with the locale variants. Free with + * g_strfreev(). + * + * Since: 2.28 + */ +gchar ** +g_get_locale_variants (const gchar *locale) +{ + GPtrArray *array; + + g_return_val_if_fail (locale != NULL, NULL); + + array = g_ptr_array_sized_new (8); + append_locale_variants (array, locale); + g_ptr_array_add (array, NULL); + + return (gchar **) g_ptr_array_free (array, FALSE); +} + +/* The following is (partly) taken from the gettext package. + Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc. */ + +static const gchar * +guess_category_value (const gchar *category_name) +{ + const gchar *retval; + + /* The highest priority value is the 'LANGUAGE' environment + variable. This is a GNU extension. */ + retval = g_getenv ("LANGUAGE"); + if ((retval != NULL) && (retval[0] != '\0')) + return retval; + + /* 'LANGUAGE' is not set. So we have to proceed with the POSIX + methods of looking to 'LC_ALL', 'LC_xxx', and 'LANG'. On some + systems this can be done by the 'setlocale' function itself. */ + + /* Setting of LC_ALL overwrites all other. */ + retval = g_getenv ("LC_ALL"); + if ((retval != NULL) && (retval[0] != '\0')) + return retval; + + /* Next comes the name of the desired category. */ + retval = g_getenv (category_name); + if ((retval != NULL) && (retval[0] != '\0')) + return retval; + + /* Last possibility is the LANG environment variable. */ + retval = g_getenv ("LANG"); + if ((retval != NULL) && (retval[0] != '\0')) + return retval; + +#ifdef G_PLATFORM_WIN32 + /* g_win32_getlocale() first checks for LC_ALL, LC_MESSAGES and + * LANG, which we already did above. Oh well. The main point of + * calling g_win32_getlocale() is to get the thread's locale as used + * by Windows and the Microsoft C runtime (in the "English_United + * States" format) translated into the Unixish format. + */ + { + char *locale = g_win32_getlocale (); + retval = g_intern_string (locale); + g_free (locale); + return retval; + } +#endif + + return NULL; +} + +typedef struct _GLanguageNamesCache GLanguageNamesCache; + +struct _GLanguageNamesCache { + gchar *languages; + gchar **language_names; +}; + +static void +language_names_cache_free (gpointer data) +{ + GLanguageNamesCache *cache = data; + g_free (cache->languages); + g_strfreev (cache->language_names); + g_free (cache); +} + +/** + * g_get_language_names: + * + * Computes a list of applicable locale names, 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 and always contains + * the default locale "C". + * + * For example, if LANGUAGE=de:en_US, then the returned list is + * "de", "en_US", "en", "C". + * + * This function consults the environment variables `LANGUAGE`, `LC_ALL`, + * `LC_MESSAGES` and `LANG` to find the list of locales specified by the + * user. + * + * Returns: (array zero-terminated=1) (transfer none): a %NULL-terminated array of strings owned by GLib + * that must not be modified or freed. + * + * Since: 2.6 + */ +const gchar * const * +g_get_language_names (void) +{ + return g_get_language_names_with_category ("LC_MESSAGES"); +} + +/** + * g_get_language_names_with_category: + * @category_name: a locale category name + * + * Computes a list of applicable locale names with a locale category name, + * which can be used to construct the fallback locale-dependent filenames + * or search paths. The returned list is sorted from most desirable to + * least desirable and always contains the default locale "C". + * + * This function consults the environment variables `LANGUAGE`, `LC_ALL`, + * @category_name, and `LANG` to find the list of locales specified by the + * user. + * + * g_get_language_names() returns g_get_language_names_with_category("LC_MESSAGES"). + * + * Returns: (array zero-terminated=1) (transfer none): a %NULL-terminated array of strings owned by + * the thread g_get_language_names_with_category was called from. + * It must not be modified or freed. It must be copied if planned to be used in another thread. + * + * Since: 2.58 + */ +const gchar * const * +g_get_language_names_with_category (const gchar *category_name) +{ + static GPrivate cache_private = G_PRIVATE_INIT ((void (*)(gpointer)) g_hash_table_unref); + GHashTable *cache = g_private_get (&cache_private); + const gchar *languages; + GLanguageNamesCache *name_cache; + + g_return_val_if_fail (category_name != NULL, NULL); + + if (!cache) + { + cache = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, language_names_cache_free); + g_private_set (&cache_private, cache); + } + + languages = guess_category_value (category_name); + if (!languages) + languages = "C"; + + name_cache = (GLanguageNamesCache *) g_hash_table_lookup (cache, category_name); + if (!(name_cache && name_cache->languages && + strcmp (name_cache->languages, languages) == 0)) + { + GPtrArray *array; + gchar **alist, **a; + + g_hash_table_remove (cache, category_name); + + array = g_ptr_array_sized_new (8); + + alist = g_strsplit (languages, ":", 0); + for (a = alist; *a; a++) + append_locale_variants (array, unalias_lang (*a)); + g_strfreev (alist); + g_ptr_array_add (array, g_strdup ("C")); + g_ptr_array_add (array, NULL); + + name_cache = g_new0 (GLanguageNamesCache, 1); + name_cache->languages = g_strdup (languages); + name_cache->language_names = (gchar **) g_ptr_array_free (array, FALSE); + g_hash_table_insert (cache, g_strdup (category_name), name_cache); + } + + return (const gchar * const *) name_cache->language_names; +} diff --git a/glib/gcharset.h b/glib/gcharset.h new file mode 100644 index 0000000..82020f6 --- /dev/null +++ b/glib/gcharset.h @@ -0,0 +1,47 @@ +/* gcharset.h - Charset functions + * + * Copyright (C) 2011 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_CHARSET_H__ +#define __G_CHARSET_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_ALL +gboolean g_get_charset (const char **charset); +GLIB_AVAILABLE_IN_ALL +gchar * g_get_codeset (void); +GLIB_AVAILABLE_IN_2_62 +gboolean g_get_console_charset (const char **charset); + +GLIB_AVAILABLE_IN_ALL +const gchar * const * g_get_language_names (void); +GLIB_AVAILABLE_IN_2_58 +const gchar * const * g_get_language_names_with_category + (const gchar *category_name); +GLIB_AVAILABLE_IN_ALL +gchar ** g_get_locale_variants (const gchar *locale); + +G_END_DECLS + +#endif /* __G_CHARSET_H__ */ diff --git a/glib/gcharsetprivate.h b/glib/gcharsetprivate.h new file mode 100644 index 0000000..9b1def2 --- /dev/null +++ b/glib/gcharsetprivate.h @@ -0,0 +1,34 @@ +/* gunicodeprivate.h + * + * Copyright (C) 2012 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_CHARSET_PRIVATE_H__ +#define __G_CHARSET_PRIVATE_H__ + +#include "gcharset.h" + +G_BEGIN_DECLS + +const char ** _g_charset_get_aliases (const char *canonical_name); + +gboolean _g_get_time_charset (const char **charset); + +gboolean _g_get_ctype_charset (const char **charset); + +G_END_DECLS + +#endif diff --git a/glib/gchecksum.c b/glib/gchecksum.c new file mode 100644 index 0000000..29b479b --- /dev/null +++ b/glib/gchecksum.c @@ -0,0 +1,1864 @@ +/* gchecksum.h - data hashing functions + * + * Copyright (C) 2007 Emmanuele Bassi + * + * 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 "gchecksum.h" + +#include "gslice.h" +#include "gmem.h" +#include "gstrfuncs.h" +#include "gtestutils.h" +#include "gtypes.h" +#include "glibintl.h" + + +/** + * SECTION:checksum + * @title: Data Checksums + * @short_description: computes the checksum for data + * + * GLib provides a generic API for computing checksums (or "digests") + * for a sequence of arbitrary bytes, using various hashing algorithms + * like MD5, SHA-1 and SHA-256. Checksums are commonly used in various + * environments and specifications. + * + * GLib supports incremental checksums using the GChecksum data + * structure, by calling g_checksum_update() as long as there's data + * available and then using g_checksum_get_string() or + * g_checksum_get_digest() to compute the checksum and return it either + * as a string in hexadecimal form, or as a raw sequence of bytes. To + * compute the checksum for binary blobs and NUL-terminated strings in + * one go, use the convenience functions g_compute_checksum_for_data() + * and g_compute_checksum_for_string(), respectively. + * + * Support for checksums has been added in GLib 2.16 + **/ + +#define IS_VALID_TYPE(type) ((type) >= G_CHECKSUM_MD5 && (type) <= G_CHECKSUM_SHA384) + +/* The fact that these are lower case characters is part of the ABI */ +static const gchar hex_digits[] = "0123456789abcdef"; + +#define MD5_DATASIZE 64 +#define MD5_DIGEST_LEN 16 + +typedef struct +{ + guint32 buf[4]; + guint32 bits[2]; + + union { + guchar data[MD5_DATASIZE]; + guint32 data32[MD5_DATASIZE / 4]; + } u; + + guchar digest[MD5_DIGEST_LEN]; +} Md5sum; + +#define SHA1_DATASIZE 64 +#define SHA1_DIGEST_LEN 20 + +typedef struct +{ + guint32 buf[5]; + guint32 bits[2]; + + /* we pack 64 unsigned chars into 16 32-bit unsigned integers */ + guint32 data[16]; + + guchar digest[SHA1_DIGEST_LEN]; +} Sha1sum; + +#define SHA256_DATASIZE 64 +#define SHA256_DIGEST_LEN 32 + +typedef struct +{ + guint32 buf[8]; + guint32 bits[2]; + + guint8 data[SHA256_DATASIZE]; + + guchar digest[SHA256_DIGEST_LEN]; +} Sha256sum; + +/* SHA2 is common thing for SHA-384, SHA-512, SHA-512/224 and SHA-512/256 */ +#define SHA2_BLOCK_LEN 128 /* 1024 bits message block */ +#define SHA384_DIGEST_LEN 48 +#define SHA512_DIGEST_LEN 64 + +typedef struct +{ + guint64 H[8]; + + guint8 block[SHA2_BLOCK_LEN]; + guint8 block_len; + + guint64 data_len[2]; + + guchar digest[SHA512_DIGEST_LEN]; +} Sha512sum; + +struct _GChecksum +{ + GChecksumType type; + + gchar *digest_str; + + union { + Md5sum md5; + Sha1sum sha1; + Sha256sum sha256; + Sha512sum sha512; + } sum; +}; + +/* we need different byte swapping functions because MD5 expects buffers + * to be little-endian, while SHA1 and SHA256 expect them in big-endian + * form. + */ + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define md5_byte_reverse(buffer,length) +#else +/* assume that the passed buffer is integer aligned */ +static inline void +md5_byte_reverse (guchar *buffer, + gulong length) +{ + guint32 bit; + + do + { + bit = (guint32) ((unsigned) buffer[3] << 8 | buffer[2]) << 16 | + ((unsigned) buffer[1] << 8 | buffer[0]); + * (guint32 *) buffer = bit; + buffer += 4; + } + while (--length); +} +#endif /* G_BYTE_ORDER == G_LITTLE_ENDIAN */ + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define sha_byte_reverse(buffer,length) +#else +static inline void +sha_byte_reverse (guint32 *buffer, + gint length) +{ + length /= sizeof (guint32); + while (length--) + { + *buffer = GUINT32_SWAP_LE_BE (*buffer); + ++buffer; + } +} +#endif /* G_BYTE_ORDER == G_BIG_ENDIAN */ + +static gchar * +digest_to_string (guint8 *digest, + gsize digest_len) +{ + gsize i, len = digest_len * 2; + gchar *retval; + + retval = g_new (gchar, len + 1); + + for (i = 0; i < digest_len; i++) + { + guint8 byte = digest[i]; + + retval[2 * i] = hex_digits[byte >> 4]; + retval[2 * i + 1] = hex_digits[byte & 0xf]; + } + + retval[len] = 0; + + return retval; +} + +/* + * MD5 Checksum + */ + +/* This MD5 digest computation is based on the equivalent code + * written by Colin Plumb. It came with this notice: + * + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + */ + +static void +md5_sum_init (Md5sum *md5) +{ + /* arbitrary constants */ + md5->buf[0] = 0x67452301; + md5->buf[1] = 0xefcdab89; + md5->buf[2] = 0x98badcfe; + md5->buf[3] = 0x10325476; + + md5->bits[0] = md5->bits[1] = 0; +} + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. md5_sum_update() + * blocks the data and converts bytes into longwords for this routine. + */ +static void +md5_transform (guint32 buf[4], + guint32 const in[16]) +{ + guint32 a, b, c, d; + +/* The four core functions - F1 is optimized somewhat */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1 (z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define md5_step(f, w, x, y, z, data, s) \ + ( w += f (x, y, z) + data, w = w << s | w >> (32 - s), w += x ) + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + md5_step (F1, a, b, c, d, in[0] + 0xd76aa478, 7); + md5_step (F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + md5_step (F1, c, d, a, b, in[2] + 0x242070db, 17); + md5_step (F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + md5_step (F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + md5_step (F1, d, a, b, c, in[5] + 0x4787c62a, 12); + md5_step (F1, c, d, a, b, in[6] + 0xa8304613, 17); + md5_step (F1, b, c, d, a, in[7] + 0xfd469501, 22); + md5_step (F1, a, b, c, d, in[8] + 0x698098d8, 7); + md5_step (F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + md5_step (F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + md5_step (F1, b, c, d, a, in[11] + 0x895cd7be, 22); + md5_step (F1, a, b, c, d, in[12] + 0x6b901122, 7); + md5_step (F1, d, a, b, c, in[13] + 0xfd987193, 12); + md5_step (F1, c, d, a, b, in[14] + 0xa679438e, 17); + md5_step (F1, b, c, d, a, in[15] + 0x49b40821, 22); + + md5_step (F2, a, b, c, d, in[1] + 0xf61e2562, 5); + md5_step (F2, d, a, b, c, in[6] + 0xc040b340, 9); + md5_step (F2, c, d, a, b, in[11] + 0x265e5a51, 14); + md5_step (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + md5_step (F2, a, b, c, d, in[5] + 0xd62f105d, 5); + md5_step (F2, d, a, b, c, in[10] + 0x02441453, 9); + md5_step (F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + md5_step (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + md5_step (F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + md5_step (F2, d, a, b, c, in[14] + 0xc33707d6, 9); + md5_step (F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + md5_step (F2, b, c, d, a, in[8] + 0x455a14ed, 20); + md5_step (F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + md5_step (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + md5_step (F2, c, d, a, b, in[7] + 0x676f02d9, 14); + md5_step (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + md5_step (F3, a, b, c, d, in[5] + 0xfffa3942, 4); + md5_step (F3, d, a, b, c, in[8] + 0x8771f681, 11); + md5_step (F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + md5_step (F3, b, c, d, a, in[14] + 0xfde5380c, 23); + md5_step (F3, a, b, c, d, in[1] + 0xa4beea44, 4); + md5_step (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + md5_step (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + md5_step (F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + md5_step (F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + md5_step (F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + md5_step (F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + md5_step (F3, b, c, d, a, in[6] + 0x04881d05, 23); + md5_step (F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + md5_step (F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + md5_step (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + md5_step (F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + md5_step (F4, a, b, c, d, in[0] + 0xf4292244, 6); + md5_step (F4, d, a, b, c, in[7] + 0x432aff97, 10); + md5_step (F4, c, d, a, b, in[14] + 0xab9423a7, 15); + md5_step (F4, b, c, d, a, in[5] + 0xfc93a039, 21); + md5_step (F4, a, b, c, d, in[12] + 0x655b59c3, 6); + md5_step (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + md5_step (F4, c, d, a, b, in[10] + 0xffeff47d, 15); + md5_step (F4, b, c, d, a, in[1] + 0x85845dd1, 21); + md5_step (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + md5_step (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + md5_step (F4, c, d, a, b, in[6] + 0xa3014314, 15); + md5_step (F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + md5_step (F4, a, b, c, d, in[4] + 0xf7537e82, 6); + md5_step (F4, d, a, b, c, in[11] + 0xbd3af235, 10); + md5_step (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + md5_step (F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; + +#undef F1 +#undef F2 +#undef F3 +#undef F4 +#undef md5_step +} + +static void +md5_sum_update (Md5sum *md5, + const guchar *data, + gsize length) +{ + guint32 bit; + + bit = md5->bits[0]; + md5->bits[0] = bit + ((guint32) length << 3); + + /* carry from low to high */ + if (md5->bits[0] < bit) + md5->bits[1] += 1; + + md5->bits[1] += length >> 29; + + /* bytes already in Md5sum->u.data */ + bit = (bit >> 3) & 0x3f; + + /* handle any leading odd-sized chunks */ + if (bit) + { + guchar *p = md5->u.data + bit; + + bit = MD5_DATASIZE - bit; + if (length < bit) + { + memcpy (p, data, length); + return; + } + + memcpy (p, data, bit); + + md5_byte_reverse (md5->u.data, 16); + md5_transform (md5->buf, md5->u.data32); + + data += bit; + length -= bit; + } + + /* process data in 64-byte chunks */ + while (length >= MD5_DATASIZE) + { + memcpy (md5->u.data, data, MD5_DATASIZE); + + md5_byte_reverse (md5->u.data, 16); + md5_transform (md5->buf, md5->u.data32); + + data += MD5_DATASIZE; + length -= MD5_DATASIZE; + } + + /* handle any remaining bytes of data */ + memcpy (md5->u.data, data, length); +} + +/* closes a checksum */ +static void +md5_sum_close (Md5sum *md5) +{ + guint count; + guchar *p; + + /* Compute number of bytes mod 64 */ + count = (md5->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. + * This is safe since there is always at least one byte free + */ + p = md5->u.data + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = MD5_DATASIZE - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) + { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (p, 0, count); + + md5_byte_reverse (md5->u.data, 16); + md5_transform (md5->buf, md5->u.data32); + + /* Now fill the next block with 56 bytes */ + memset (md5->u.data, 0, MD5_DATASIZE - 8); + } + else + { + /* Pad block to 56 bytes */ + memset (p, 0, count - 8); + } + + md5_byte_reverse (md5->u.data, 14); + + /* Append length in bits and transform */ + md5->u.data32[14] = md5->bits[0]; + md5->u.data32[15] = md5->bits[1]; + + md5_transform (md5->buf, md5->u.data32); + md5_byte_reverse ((guchar *) md5->buf, 4); + + memcpy (md5->digest, md5->buf, 16); + + /* Reset buffers in case they contain sensitive data */ + memset (md5->buf, 0, sizeof (md5->buf)); + memset (md5->u.data, 0, sizeof (md5->u.data)); +} + +static gchar * +md5_sum_to_string (Md5sum *md5) +{ + return digest_to_string (md5->digest, MD5_DIGEST_LEN); +} + +static void +md5_sum_digest (Md5sum *md5, + guint8 *digest) +{ + gint i; + + for (i = 0; i < MD5_DIGEST_LEN; i++) + digest[i] = md5->digest[i]; +} + +/* + * SHA-1 Checksum + */ + +/* The following implementation comes from D-Bus dbus-sha.c. I've changed + * it to use GLib types and to work more like the MD5 implementation above. + * I left the comments to have a history of this code. + * -- Emmanuele Bassi, ebassi@gnome.org + */ + +/* The following comments have the history of where this code + * comes from. I actually copied it from GNet in GNOME CVS. + * - hp@redhat.com + */ + +/* + * sha.h : Implementation of the Secure Hash Algorithm + * + * Part of the Python Cryptography Toolkit, version 1.0.0 + * + * Copyright (C) 1995, A.M. Kuchling + * + * Distribute and use freely; there are no restrictions on further + * dissemination and usage except those imposed by the laws of your + * country of residence. + * + */ + +/* SHA: NIST's Secure Hash Algorithm */ + +/* Based on SHA code originally posted to sci.crypt by Peter Gutmann + in message <30ajo5$oe8@ccu2.auckland.ac.nz>. + Modified to test for endianness on creation of SHA objects by AMK. + Also, the original specification of SHA was found to have a weakness + by NSA/NIST. This code implements the fixed version of SHA. +*/ + +/* Here's the first paragraph of Peter Gutmann's posting: + +The following is my SHA (FIPS 180) code updated to allow use of the "fixed" +SHA, thanks to Jim Gillogly and an anonymous contributor for the information on +what's changed in the new version. The fix is a simple change which involves +adding a single rotate in the initial expansion function. It is unknown +whether this is an optimal solution to the problem which was discovered in the +SHA or whether it's simply a bandaid which fixes the problem with a minimum of +effort (for example the reengineering of a great many Capstone chips). +*/ + +static void +sha1_sum_init (Sha1sum *sha1) +{ + /* initialize constants */ + sha1->buf[0] = 0x67452301L; + sha1->buf[1] = 0xEFCDAB89L; + sha1->buf[2] = 0x98BADCFEL; + sha1->buf[3] = 0x10325476L; + sha1->buf[4] = 0xC3D2E1F0L; + + /* initialize bits */ + sha1->bits[0] = sha1->bits[1] = 0; +} + +/* The SHA f()-functions. */ + +#define f1(x,y,z) (z ^ (x & (y ^ z))) /* Rounds 0-19 */ +#define f2(x,y,z) (x ^ y ^ z) /* Rounds 20-39 */ +#define f3(x,y,z) (( x & y) | (z & (x | y))) /* Rounds 40-59 */ +#define f4(x,y,z) (x ^ y ^ z) /* Rounds 60-79 */ + +/* The SHA Mysterious Constants */ +#define K1 0x5A827999L /* Rounds 0-19 */ +#define K2 0x6ED9EBA1L /* Rounds 20-39 */ +#define K3 0x8F1BBCDCL /* Rounds 40-59 */ +#define K4 0xCA62C1D6L /* Rounds 60-79 */ + +/* 32-bit rotate left - kludged with shifts */ +#define ROTL(n,X) (((X) << n ) | ((X) >> (32 - n))) + +/* The initial expanding function. The hash function is defined over an + 80-word expanded input array W, where the first 16 are copies of the input + data, and the remaining 64 are defined by + + W[ i ] = W[ i - 16 ] ^ W[ i - 14 ] ^ W[ i - 8 ] ^ W[ i - 3 ] + + This implementation generates these values on the fly in a circular + buffer - thanks to Colin Plumb, colin@nyx10.cs.du.edu for this + optimization. + + The updated SHA changes the expanding function by adding a rotate of 1 + bit. Thanks to Jim Gillogly, jim@rand.org, and an anonymous contributor + for this information */ + +#define expand(W,i) (W[ i & 15 ] = ROTL (1, (W[ i & 15] ^ \ + W[(i - 14) & 15] ^ \ + W[(i - 8) & 15] ^ \ + W[(i - 3) & 15]))) + + +/* The prototype SHA sub-round. The fundamental sub-round is: + + a' = e + ROTL( 5, a ) + f( b, c, d ) + k + data; + b' = a; + c' = ROTL( 30, b ); + d' = c; + e' = d; + + but this is implemented by unrolling the loop 5 times and renaming the + variables ( e, a, b, c, d ) = ( a', b', c', d', e' ) each iteration. + This code is then replicated 20 times for each of the 4 functions, using + the next 20 values from the W[] array each time */ + +#define subRound(a, b, c, d, e, f, k, data) \ + (e += ROTL (5, a) + f(b, c, d) + k + data, b = ROTL (30, b)) + +static void +sha1_transform (guint32 buf[5], + guint32 in[16]) +{ + guint32 A, B, C, D, E; + + A = buf[0]; + B = buf[1]; + C = buf[2]; + D = buf[3]; + E = buf[4]; + + /* Heavy mangling, in 4 sub-rounds of 20 iterations each. */ + subRound (A, B, C, D, E, f1, K1, in[0]); + subRound (E, A, B, C, D, f1, K1, in[1]); + subRound (D, E, A, B, C, f1, K1, in[2]); + subRound (C, D, E, A, B, f1, K1, in[3]); + subRound (B, C, D, E, A, f1, K1, in[4]); + subRound (A, B, C, D, E, f1, K1, in[5]); + subRound (E, A, B, C, D, f1, K1, in[6]); + subRound (D, E, A, B, C, f1, K1, in[7]); + subRound (C, D, E, A, B, f1, K1, in[8]); + subRound (B, C, D, E, A, f1, K1, in[9]); + subRound (A, B, C, D, E, f1, K1, in[10]); + subRound (E, A, B, C, D, f1, K1, in[11]); + subRound (D, E, A, B, C, f1, K1, in[12]); + subRound (C, D, E, A, B, f1, K1, in[13]); + subRound (B, C, D, E, A, f1, K1, in[14]); + subRound (A, B, C, D, E, f1, K1, in[15]); + subRound (E, A, B, C, D, f1, K1, expand (in, 16)); + subRound (D, E, A, B, C, f1, K1, expand (in, 17)); + subRound (C, D, E, A, B, f1, K1, expand (in, 18)); + subRound (B, C, D, E, A, f1, K1, expand (in, 19)); + + subRound (A, B, C, D, E, f2, K2, expand (in, 20)); + subRound (E, A, B, C, D, f2, K2, expand (in, 21)); + subRound (D, E, A, B, C, f2, K2, expand (in, 22)); + subRound (C, D, E, A, B, f2, K2, expand (in, 23)); + subRound (B, C, D, E, A, f2, K2, expand (in, 24)); + subRound (A, B, C, D, E, f2, K2, expand (in, 25)); + subRound (E, A, B, C, D, f2, K2, expand (in, 26)); + subRound (D, E, A, B, C, f2, K2, expand (in, 27)); + subRound (C, D, E, A, B, f2, K2, expand (in, 28)); + subRound (B, C, D, E, A, f2, K2, expand (in, 29)); + subRound (A, B, C, D, E, f2, K2, expand (in, 30)); + subRound (E, A, B, C, D, f2, K2, expand (in, 31)); + subRound (D, E, A, B, C, f2, K2, expand (in, 32)); + subRound (C, D, E, A, B, f2, K2, expand (in, 33)); + subRound (B, C, D, E, A, f2, K2, expand (in, 34)); + subRound (A, B, C, D, E, f2, K2, expand (in, 35)); + subRound (E, A, B, C, D, f2, K2, expand (in, 36)); + subRound (D, E, A, B, C, f2, K2, expand (in, 37)); + subRound (C, D, E, A, B, f2, K2, expand (in, 38)); + subRound (B, C, D, E, A, f2, K2, expand (in, 39)); + + subRound (A, B, C, D, E, f3, K3, expand (in, 40)); + subRound (E, A, B, C, D, f3, K3, expand (in, 41)); + subRound (D, E, A, B, C, f3, K3, expand (in, 42)); + subRound (C, D, E, A, B, f3, K3, expand (in, 43)); + subRound (B, C, D, E, A, f3, K3, expand (in, 44)); + subRound (A, B, C, D, E, f3, K3, expand (in, 45)); + subRound (E, A, B, C, D, f3, K3, expand (in, 46)); + subRound (D, E, A, B, C, f3, K3, expand (in, 47)); + subRound (C, D, E, A, B, f3, K3, expand (in, 48)); + subRound (B, C, D, E, A, f3, K3, expand (in, 49)); + subRound (A, B, C, D, E, f3, K3, expand (in, 50)); + subRound (E, A, B, C, D, f3, K3, expand (in, 51)); + subRound (D, E, A, B, C, f3, K3, expand (in, 52)); + subRound (C, D, E, A, B, f3, K3, expand (in, 53)); + subRound (B, C, D, E, A, f3, K3, expand (in, 54)); + subRound (A, B, C, D, E, f3, K3, expand (in, 55)); + subRound (E, A, B, C, D, f3, K3, expand (in, 56)); + subRound (D, E, A, B, C, f3, K3, expand (in, 57)); + subRound (C, D, E, A, B, f3, K3, expand (in, 58)); + subRound (B, C, D, E, A, f3, K3, expand (in, 59)); + + subRound (A, B, C, D, E, f4, K4, expand (in, 60)); + subRound (E, A, B, C, D, f4, K4, expand (in, 61)); + subRound (D, E, A, B, C, f4, K4, expand (in, 62)); + subRound (C, D, E, A, B, f4, K4, expand (in, 63)); + subRound (B, C, D, E, A, f4, K4, expand (in, 64)); + subRound (A, B, C, D, E, f4, K4, expand (in, 65)); + subRound (E, A, B, C, D, f4, K4, expand (in, 66)); + subRound (D, E, A, B, C, f4, K4, expand (in, 67)); + subRound (C, D, E, A, B, f4, K4, expand (in, 68)); + subRound (B, C, D, E, A, f4, K4, expand (in, 69)); + subRound (A, B, C, D, E, f4, K4, expand (in, 70)); + subRound (E, A, B, C, D, f4, K4, expand (in, 71)); + subRound (D, E, A, B, C, f4, K4, expand (in, 72)); + subRound (C, D, E, A, B, f4, K4, expand (in, 73)); + subRound (B, C, D, E, A, f4, K4, expand (in, 74)); + subRound (A, B, C, D, E, f4, K4, expand (in, 75)); + subRound (E, A, B, C, D, f4, K4, expand (in, 76)); + subRound (D, E, A, B, C, f4, K4, expand (in, 77)); + subRound (C, D, E, A, B, f4, K4, expand (in, 78)); + subRound (B, C, D, E, A, f4, K4, expand (in, 79)); + + /* Build message digest */ + buf[0] += A; + buf[1] += B; + buf[2] += C; + buf[3] += D; + buf[4] += E; +} + +#undef K1 +#undef K2 +#undef K3 +#undef K4 +#undef f1 +#undef f2 +#undef f3 +#undef f4 +#undef ROTL +#undef expand +#undef subRound + +static void +sha1_sum_update (Sha1sum *sha1, + const guchar *buffer, + gsize count) +{ + guint32 tmp; + guint dataCount; + + /* Update bitcount */ + tmp = sha1->bits[0]; + if ((sha1->bits[0] = tmp + ((guint32) count << 3) ) < tmp) + sha1->bits[1] += 1; /* Carry from low to high */ + sha1->bits[1] += count >> 29; + + /* Get count of bytes already in data */ + dataCount = (guint) (tmp >> 3) & 0x3F; + + /* Handle any leading odd-sized chunks */ + if (dataCount) + { + guchar *p = (guchar *) sha1->data + dataCount; + + dataCount = SHA1_DATASIZE - dataCount; + if (count < dataCount) + { + memcpy (p, buffer, count); + return; + } + + memcpy (p, buffer, dataCount); + + sha_byte_reverse (sha1->data, SHA1_DATASIZE); + sha1_transform (sha1->buf, sha1->data); + + buffer += dataCount; + count -= dataCount; + } + + /* Process data in SHA1_DATASIZE chunks */ + while (count >= SHA1_DATASIZE) + { + memcpy (sha1->data, buffer, SHA1_DATASIZE); + + sha_byte_reverse (sha1->data, SHA1_DATASIZE); + sha1_transform (sha1->buf, sha1->data); + + buffer += SHA1_DATASIZE; + count -= SHA1_DATASIZE; + } + + /* Handle any remaining bytes of data. */ + memcpy (sha1->data, buffer, count); +} + +/* Final wrapup - pad to SHA_DATASIZE-byte boundary with the bit pattern + 1 0* (64-bit count of bits processed, MSB-first) */ +static void +sha1_sum_close (Sha1sum *sha1) +{ + gint count; + guchar *data_p; + + /* Compute number of bytes mod 64 */ + count = (gint) ((sha1->bits[0] >> 3) & 0x3f); + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + data_p = (guchar *) sha1->data + count; + *data_p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = SHA1_DATASIZE - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) + { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (data_p, 0, count); + + sha_byte_reverse (sha1->data, SHA1_DATASIZE); + sha1_transform (sha1->buf, sha1->data); + + /* Now fill the next block with 56 bytes */ + memset (sha1->data, 0, SHA1_DATASIZE - 8); + } + else + { + /* Pad block to 56 bytes */ + memset (data_p, 0, count - 8); + } + + /* Append length in bits and transform */ + sha1->data[14] = sha1->bits[1]; + sha1->data[15] = sha1->bits[0]; + + sha_byte_reverse (sha1->data, SHA1_DATASIZE - 8); + sha1_transform (sha1->buf, sha1->data); + sha_byte_reverse (sha1->buf, SHA1_DIGEST_LEN); + + memcpy (sha1->digest, sha1->buf, SHA1_DIGEST_LEN); + + /* Reset buffers in case they contain sensitive data */ + memset (sha1->buf, 0, sizeof (sha1->buf)); + memset (sha1->data, 0, sizeof (sha1->data)); +} + +static gchar * +sha1_sum_to_string (Sha1sum *sha1) +{ + return digest_to_string (sha1->digest, SHA1_DIGEST_LEN); +} + +static void +sha1_sum_digest (Sha1sum *sha1, + guint8 *digest) +{ + gint i; + + for (i = 0; i < SHA1_DIGEST_LEN; i++) + digest[i] = sha1->digest[i]; +} + +/* + * SHA-256 Checksum + */ + +/* adapted from the SHA256 implementation in gsk/src/hash/gskhash.c. + * + * Copyright (C) 2006 Dave Benson + * Released under the terms of the GNU Lesser General Public License + */ + +static void +sha256_sum_init (Sha256sum *sha256) +{ + sha256->buf[0] = 0x6a09e667; + sha256->buf[1] = 0xbb67ae85; + sha256->buf[2] = 0x3c6ef372; + sha256->buf[3] = 0xa54ff53a; + sha256->buf[4] = 0x510e527f; + sha256->buf[5] = 0x9b05688c; + sha256->buf[6] = 0x1f83d9ab; + sha256->buf[7] = 0x5be0cd19; + + sha256->bits[0] = sha256->bits[1] = 0; +} + +#define GET_UINT32(n,b,i) G_STMT_START{ \ + (n) = ((guint32) (b)[(i) ] << 24) \ + | ((guint32) (b)[(i) + 1] << 16) \ + | ((guint32) (b)[(i) + 2] << 8) \ + | ((guint32) (b)[(i) + 3] ); } G_STMT_END + +#define PUT_UINT32(n,b,i) G_STMT_START{ \ + (b)[(i) ] = (guint8) ((n) >> 24); \ + (b)[(i) + 1] = (guint8) ((n) >> 16); \ + (b)[(i) + 2] = (guint8) ((n) >> 8); \ + (b)[(i) + 3] = (guint8) ((n) ); } G_STMT_END + +static void +sha256_transform (guint32 buf[8], + guint8 const data[64]) +{ + guint32 temp1, temp2, W[64]; + guint32 A, B, C, D, E, F, G, H; + + GET_UINT32 (W[0], data, 0); + GET_UINT32 (W[1], data, 4); + GET_UINT32 (W[2], data, 8); + GET_UINT32 (W[3], data, 12); + GET_UINT32 (W[4], data, 16); + GET_UINT32 (W[5], data, 20); + GET_UINT32 (W[6], data, 24); + GET_UINT32 (W[7], data, 28); + GET_UINT32 (W[8], data, 32); + GET_UINT32 (W[9], data, 36); + GET_UINT32 (W[10], data, 40); + GET_UINT32 (W[11], data, 44); + GET_UINT32 (W[12], data, 48); + GET_UINT32 (W[13], data, 52); + GET_UINT32 (W[14], data, 56); + GET_UINT32 (W[15], data, 60); + +#define SHR(x,n) ((x & 0xFFFFFFFF) >> n) +#define ROTR(x,n) (SHR (x,n) | (x << (32 - n))) + +#define S0(x) (ROTR (x, 7) ^ ROTR (x,18) ^ SHR (x, 3)) +#define S1(x) (ROTR (x,17) ^ ROTR (x,19) ^ SHR (x,10)) +#define S2(x) (ROTR (x, 2) ^ ROTR (x,13) ^ ROTR (x,22)) +#define S3(x) (ROTR (x, 6) ^ ROTR (x,11) ^ ROTR (x,25)) + +#define F0(x,y,z) ((x & y) | (z & (x | y))) +#define F1(x,y,z) (z ^ (x & (y ^ z))) + +#define R(t) (W[t] = S1(W[t - 2]) + W[t - 7] + \ + S0(W[t - 15]) + W[t - 16]) + +#define P(a,b,c,d,e,f,g,h,x,K) G_STMT_START { \ + temp1 = h + S3(e) + F1(e,f,g) + K + x; \ + temp2 = S2(a) + F0(a,b,c); \ + d += temp1; h = temp1 + temp2; } G_STMT_END + + A = buf[0]; + B = buf[1]; + C = buf[2]; + D = buf[3]; + E = buf[4]; + F = buf[5]; + G = buf[6]; + H = buf[7]; + + P (A, B, C, D, E, F, G, H, W[ 0], 0x428A2F98); + P (H, A, B, C, D, E, F, G, W[ 1], 0x71374491); + P (G, H, A, B, C, D, E, F, W[ 2], 0xB5C0FBCF); + P (F, G, H, A, B, C, D, E, W[ 3], 0xE9B5DBA5); + P (E, F, G, H, A, B, C, D, W[ 4], 0x3956C25B); + P (D, E, F, G, H, A, B, C, W[ 5], 0x59F111F1); + P (C, D, E, F, G, H, A, B, W[ 6], 0x923F82A4); + P (B, C, D, E, F, G, H, A, W[ 7], 0xAB1C5ED5); + P (A, B, C, D, E, F, G, H, W[ 8], 0xD807AA98); + P (H, A, B, C, D, E, F, G, W[ 9], 0x12835B01); + P (G, H, A, B, C, D, E, F, W[10], 0x243185BE); + P (F, G, H, A, B, C, D, E, W[11], 0x550C7DC3); + P (E, F, G, H, A, B, C, D, W[12], 0x72BE5D74); + P (D, E, F, G, H, A, B, C, W[13], 0x80DEB1FE); + P (C, D, E, F, G, H, A, B, W[14], 0x9BDC06A7); + P (B, C, D, E, F, G, H, A, W[15], 0xC19BF174); + P (A, B, C, D, E, F, G, H, R(16), 0xE49B69C1); + P (H, A, B, C, D, E, F, G, R(17), 0xEFBE4786); + P (G, H, A, B, C, D, E, F, R(18), 0x0FC19DC6); + P (F, G, H, A, B, C, D, E, R(19), 0x240CA1CC); + P (E, F, G, H, A, B, C, D, R(20), 0x2DE92C6F); + P (D, E, F, G, H, A, B, C, R(21), 0x4A7484AA); + P (C, D, E, F, G, H, A, B, R(22), 0x5CB0A9DC); + P (B, C, D, E, F, G, H, A, R(23), 0x76F988DA); + P (A, B, C, D, E, F, G, H, R(24), 0x983E5152); + P (H, A, B, C, D, E, F, G, R(25), 0xA831C66D); + P (G, H, A, B, C, D, E, F, R(26), 0xB00327C8); + P (F, G, H, A, B, C, D, E, R(27), 0xBF597FC7); + P (E, F, G, H, A, B, C, D, R(28), 0xC6E00BF3); + P (D, E, F, G, H, A, B, C, R(29), 0xD5A79147); + P (C, D, E, F, G, H, A, B, R(30), 0x06CA6351); + P (B, C, D, E, F, G, H, A, R(31), 0x14292967); + P (A, B, C, D, E, F, G, H, R(32), 0x27B70A85); + P (H, A, B, C, D, E, F, G, R(33), 0x2E1B2138); + P (G, H, A, B, C, D, E, F, R(34), 0x4D2C6DFC); + P (F, G, H, A, B, C, D, E, R(35), 0x53380D13); + P (E, F, G, H, A, B, C, D, R(36), 0x650A7354); + P (D, E, F, G, H, A, B, C, R(37), 0x766A0ABB); + P (C, D, E, F, G, H, A, B, R(38), 0x81C2C92E); + P (B, C, D, E, F, G, H, A, R(39), 0x92722C85); + P (A, B, C, D, E, F, G, H, R(40), 0xA2BFE8A1); + P (H, A, B, C, D, E, F, G, R(41), 0xA81A664B); + P (G, H, A, B, C, D, E, F, R(42), 0xC24B8B70); + P (F, G, H, A, B, C, D, E, R(43), 0xC76C51A3); + P (E, F, G, H, A, B, C, D, R(44), 0xD192E819); + P (D, E, F, G, H, A, B, C, R(45), 0xD6990624); + P (C, D, E, F, G, H, A, B, R(46), 0xF40E3585); + P (B, C, D, E, F, G, H, A, R(47), 0x106AA070); + P (A, B, C, D, E, F, G, H, R(48), 0x19A4C116); + P (H, A, B, C, D, E, F, G, R(49), 0x1E376C08); + P (G, H, A, B, C, D, E, F, R(50), 0x2748774C); + P (F, G, H, A, B, C, D, E, R(51), 0x34B0BCB5); + P (E, F, G, H, A, B, C, D, R(52), 0x391C0CB3); + P (D, E, F, G, H, A, B, C, R(53), 0x4ED8AA4A); + P (C, D, E, F, G, H, A, B, R(54), 0x5B9CCA4F); + P (B, C, D, E, F, G, H, A, R(55), 0x682E6FF3); + P (A, B, C, D, E, F, G, H, R(56), 0x748F82EE); + P (H, A, B, C, D, E, F, G, R(57), 0x78A5636F); + P (G, H, A, B, C, D, E, F, R(58), 0x84C87814); + P (F, G, H, A, B, C, D, E, R(59), 0x8CC70208); + P (E, F, G, H, A, B, C, D, R(60), 0x90BEFFFA); + P (D, E, F, G, H, A, B, C, R(61), 0xA4506CEB); + P (C, D, E, F, G, H, A, B, R(62), 0xBEF9A3F7); + P (B, C, D, E, F, G, H, A, R(63), 0xC67178F2); + +#undef SHR +#undef ROTR +#undef S0 +#undef S1 +#undef S2 +#undef S3 +#undef F0 +#undef F1 +#undef R +#undef P + + buf[0] += A; + buf[1] += B; + buf[2] += C; + buf[3] += D; + buf[4] += E; + buf[5] += F; + buf[6] += G; + buf[7] += H; +} + +static void +sha256_sum_update (Sha256sum *sha256, + const guchar *buffer, + gsize length) +{ + guint32 left, fill; + const guint8 *input = buffer; + + if (length == 0) + return; + + left = sha256->bits[0] & 0x3F; + fill = 64 - left; + + sha256->bits[0] += length; + sha256->bits[0] &= 0xFFFFFFFF; + + if (sha256->bits[0] < length) + sha256->bits[1]++; + + if (left > 0 && length >= fill) + { + memcpy ((sha256->data + left), input, fill); + + sha256_transform (sha256->buf, sha256->data); + length -= fill; + input += fill; + + left = 0; + } + + while (length >= SHA256_DATASIZE) + { + sha256_transform (sha256->buf, input); + + length -= 64; + input += 64; + } + + if (length) + memcpy (sha256->data + left, input, length); +} + +static guint8 sha256_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static void +sha256_sum_close (Sha256sum *sha256) +{ + guint32 last, padn; + guint32 high, low; + guint8 msglen[8]; + + high = (sha256->bits[0] >> 29) + | (sha256->bits[1] << 3); + low = (sha256->bits[0] << 3); + + PUT_UINT32 (high, msglen, 0); + PUT_UINT32 (low, msglen, 4); + + last = sha256->bits[0] & 0x3F; + padn = (last < 56) ? (56 - last) : (120 - last); + + sha256_sum_update (sha256, sha256_padding, padn); + sha256_sum_update (sha256, msglen, 8); + + PUT_UINT32 (sha256->buf[0], sha256->digest, 0); + PUT_UINT32 (sha256->buf[1], sha256->digest, 4); + PUT_UINT32 (sha256->buf[2], sha256->digest, 8); + PUT_UINT32 (sha256->buf[3], sha256->digest, 12); + PUT_UINT32 (sha256->buf[4], sha256->digest, 16); + PUT_UINT32 (sha256->buf[5], sha256->digest, 20); + PUT_UINT32 (sha256->buf[6], sha256->digest, 24); + PUT_UINT32 (sha256->buf[7], sha256->digest, 28); +} + +#undef PUT_UINT32 +#undef GET_UINT32 + +static gchar * +sha256_sum_to_string (Sha256sum *sha256) +{ + return digest_to_string (sha256->digest, SHA256_DIGEST_LEN); +} + +static void +sha256_sum_digest (Sha256sum *sha256, + guint8 *digest) +{ + gint i; + + for (i = 0; i < SHA256_DIGEST_LEN; i++) + digest[i] = sha256->digest[i]; +} + +/* + * SHA-384, SHA-512, SHA-512/224 and SHA-512/256 Checksums + * + * Implemented following FIPS-180-4 standard at + * http://csrc.nist.gov/publications/fips/fips180-4/fips180-4.pdf. + * References in the form [§x.y.z] map to sections in that document. + * + * Author(s): Eduardo Lima Mitev + * Igor Gnatenko + */ + +/* SHA-384, SHA-512, SHA-512/224 and SHA-512/256 functions [§4.1.3] */ +#define Ch(x,y,z) ((x & y) ^ (~x & z)) +#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) +#define SHR(n,x) (x >> n) +#define ROTR(n,x) (SHR (n, x) | (x << (64 - n))) +#define SIGMA0(x) (ROTR (28, x) ^ ROTR (34, x) ^ ROTR (39, x)) +#define SIGMA1(x) (ROTR (14, x) ^ ROTR (18, x) ^ ROTR (41, x)) +#define sigma0(x) (ROTR ( 1, x) ^ ROTR ( 8, x) ^ SHR ( 7, x)) +#define sigma1(x) (ROTR (19, x) ^ ROTR (61, x) ^ SHR ( 6, x)) + +#define PUT_UINT64(n,b,i) G_STMT_START{ \ + (b)[(i) ] = (guint8) (n >> 56); \ + (b)[(i) + 1] = (guint8) (n >> 48); \ + (b)[(i) + 2] = (guint8) (n >> 40); \ + (b)[(i) + 3] = (guint8) (n >> 32); \ + (b)[(i) + 4] = (guint8) (n >> 24); \ + (b)[(i) + 5] = (guint8) (n >> 16); \ + (b)[(i) + 6] = (guint8) (n >> 8); \ + (b)[(i) + 7] = (guint8) (n ); } G_STMT_END + +/* SHA-384 and SHA-512 constants [§4.2.3] */ +static const guint64 SHA2_K[80] = { + G_GUINT64_CONSTANT (0x428a2f98d728ae22), G_GUINT64_CONSTANT (0x7137449123ef65cd), + G_GUINT64_CONSTANT (0xb5c0fbcfec4d3b2f), G_GUINT64_CONSTANT (0xe9b5dba58189dbbc), + G_GUINT64_CONSTANT (0x3956c25bf348b538), G_GUINT64_CONSTANT (0x59f111f1b605d019), + G_GUINT64_CONSTANT (0x923f82a4af194f9b), G_GUINT64_CONSTANT (0xab1c5ed5da6d8118), + G_GUINT64_CONSTANT (0xd807aa98a3030242), G_GUINT64_CONSTANT (0x12835b0145706fbe), + G_GUINT64_CONSTANT (0x243185be4ee4b28c), G_GUINT64_CONSTANT (0x550c7dc3d5ffb4e2), + G_GUINT64_CONSTANT (0x72be5d74f27b896f), G_GUINT64_CONSTANT (0x80deb1fe3b1696b1), + G_GUINT64_CONSTANT (0x9bdc06a725c71235), G_GUINT64_CONSTANT (0xc19bf174cf692694), + G_GUINT64_CONSTANT (0xe49b69c19ef14ad2), G_GUINT64_CONSTANT (0xefbe4786384f25e3), + G_GUINT64_CONSTANT (0x0fc19dc68b8cd5b5), G_GUINT64_CONSTANT (0x240ca1cc77ac9c65), + G_GUINT64_CONSTANT (0x2de92c6f592b0275), G_GUINT64_CONSTANT (0x4a7484aa6ea6e483), + G_GUINT64_CONSTANT (0x5cb0a9dcbd41fbd4), G_GUINT64_CONSTANT (0x76f988da831153b5), + G_GUINT64_CONSTANT (0x983e5152ee66dfab), G_GUINT64_CONSTANT (0xa831c66d2db43210), + G_GUINT64_CONSTANT (0xb00327c898fb213f), G_GUINT64_CONSTANT (0xbf597fc7beef0ee4), + G_GUINT64_CONSTANT (0xc6e00bf33da88fc2), G_GUINT64_CONSTANT (0xd5a79147930aa725), + G_GUINT64_CONSTANT (0x06ca6351e003826f), G_GUINT64_CONSTANT (0x142929670a0e6e70), + G_GUINT64_CONSTANT (0x27b70a8546d22ffc), G_GUINT64_CONSTANT (0x2e1b21385c26c926), + G_GUINT64_CONSTANT (0x4d2c6dfc5ac42aed), G_GUINT64_CONSTANT (0x53380d139d95b3df), + G_GUINT64_CONSTANT (0x650a73548baf63de), G_GUINT64_CONSTANT (0x766a0abb3c77b2a8), + G_GUINT64_CONSTANT (0x81c2c92e47edaee6), G_GUINT64_CONSTANT (0x92722c851482353b), + G_GUINT64_CONSTANT (0xa2bfe8a14cf10364), G_GUINT64_CONSTANT (0xa81a664bbc423001), + G_GUINT64_CONSTANT (0xc24b8b70d0f89791), G_GUINT64_CONSTANT (0xc76c51a30654be30), + G_GUINT64_CONSTANT (0xd192e819d6ef5218), G_GUINT64_CONSTANT (0xd69906245565a910), + G_GUINT64_CONSTANT (0xf40e35855771202a), G_GUINT64_CONSTANT (0x106aa07032bbd1b8), + G_GUINT64_CONSTANT (0x19a4c116b8d2d0c8), G_GUINT64_CONSTANT (0x1e376c085141ab53), + G_GUINT64_CONSTANT (0x2748774cdf8eeb99), G_GUINT64_CONSTANT (0x34b0bcb5e19b48a8), + G_GUINT64_CONSTANT (0x391c0cb3c5c95a63), G_GUINT64_CONSTANT (0x4ed8aa4ae3418acb), + G_GUINT64_CONSTANT (0x5b9cca4f7763e373), G_GUINT64_CONSTANT (0x682e6ff3d6b2b8a3), + G_GUINT64_CONSTANT (0x748f82ee5defb2fc), G_GUINT64_CONSTANT (0x78a5636f43172f60), + G_GUINT64_CONSTANT (0x84c87814a1f0ab72), G_GUINT64_CONSTANT (0x8cc702081a6439ec), + G_GUINT64_CONSTANT (0x90befffa23631e28), G_GUINT64_CONSTANT (0xa4506cebde82bde9), + G_GUINT64_CONSTANT (0xbef9a3f7b2c67915), G_GUINT64_CONSTANT (0xc67178f2e372532b), + G_GUINT64_CONSTANT (0xca273eceea26619c), G_GUINT64_CONSTANT (0xd186b8c721c0c207), + G_GUINT64_CONSTANT (0xeada7dd6cde0eb1e), G_GUINT64_CONSTANT (0xf57d4f7fee6ed178), + G_GUINT64_CONSTANT (0x06f067aa72176fba), G_GUINT64_CONSTANT (0x0a637dc5a2c898a6), + G_GUINT64_CONSTANT (0x113f9804bef90dae), G_GUINT64_CONSTANT (0x1b710b35131c471b), + G_GUINT64_CONSTANT (0x28db77f523047d84), G_GUINT64_CONSTANT (0x32caab7b40c72493), + G_GUINT64_CONSTANT (0x3c9ebe0a15c9bebc), G_GUINT64_CONSTANT (0x431d67c49c100d4c), + G_GUINT64_CONSTANT (0x4cc5d4becb3e42b6), G_GUINT64_CONSTANT (0x597f299cfc657e2a), + G_GUINT64_CONSTANT (0x5fcb6fab3ad6faec), G_GUINT64_CONSTANT (0x6c44198c4a475817) +}; + + +static void +sha384_sum_init (Sha512sum *sha512) +{ + /* Initial Hash Value [§5.3.4] */ + sha512->H[0] = G_GUINT64_CONSTANT (0xcbbb9d5dc1059ed8); + sha512->H[1] = G_GUINT64_CONSTANT (0x629a292a367cd507); + sha512->H[2] = G_GUINT64_CONSTANT (0x9159015a3070dd17); + sha512->H[3] = G_GUINT64_CONSTANT (0x152fecd8f70e5939); + sha512->H[4] = G_GUINT64_CONSTANT (0x67332667ffc00b31); + sha512->H[5] = G_GUINT64_CONSTANT (0x8eb44a8768581511); + sha512->H[6] = G_GUINT64_CONSTANT (0xdb0c2e0d64f98fa7); + sha512->H[7] = G_GUINT64_CONSTANT (0x47b5481dbefa4fa4); + + sha512->block_len = 0; + + sha512->data_len[0] = 0; + sha512->data_len[1] = 0; +} + +static void +sha512_sum_init (Sha512sum *sha512) +{ + /* Initial Hash Value [§5.3.5] */ + sha512->H[0] = G_GUINT64_CONSTANT (0x6a09e667f3bcc908); + sha512->H[1] = G_GUINT64_CONSTANT (0xbb67ae8584caa73b); + sha512->H[2] = G_GUINT64_CONSTANT (0x3c6ef372fe94f82b); + sha512->H[3] = G_GUINT64_CONSTANT (0xa54ff53a5f1d36f1); + sha512->H[4] = G_GUINT64_CONSTANT (0x510e527fade682d1); + sha512->H[5] = G_GUINT64_CONSTANT (0x9b05688c2b3e6c1f); + sha512->H[6] = G_GUINT64_CONSTANT (0x1f83d9abfb41bd6b); + sha512->H[7] = G_GUINT64_CONSTANT (0x5be0cd19137e2179); + + sha512->block_len = 0; + + sha512->data_len[0] = 0; + sha512->data_len[1] = 0; +} + +static void +sha512_transform (guint64 H[8], + guint8 const data[SHA2_BLOCK_LEN]) +{ + gint i; + gint t; + guint64 a, b, c, d, e, f, g, h; + guint64 M[16]; + guint64 W[80]; + + /* SHA-512 hash computation [§6.4.2] */ + + /* prepare the message schedule */ + for (i = 0; i < 16; i++) + { + gint p = i * 8; + + M[i] = + ((guint64) data[p + 0] << 56) | + ((guint64) data[p + 1] << 48) | + ((guint64) data[p + 2] << 40) | + ((guint64) data[p + 3] << 32) | + ((guint64) data[p + 4] << 24) | + ((guint64) data[p + 5] << 16) | + ((guint64) data[p + 6] << 8) | + ((guint64) data[p + 7] ); + } + + for (t = 0; t < 80; t++) + if (t < 16) + W[t] = M[t]; + else + W[t] = sigma1 (W[t - 2]) + W[t - 7] + sigma0 (W[t - 15]) + W[t - 16]; + + /* initialize the eight working variables */ + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + + for (t = 0; t < 80; t++) + { + guint64 T1, T2; + + T1 = h + SIGMA1 (e) + Ch (e, f, g) + SHA2_K[t] + W[t]; + T2 = SIGMA0 (a) + Maj (a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + /* Compute the intermediate hash value H */ + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; +} + +static void +sha512_sum_update (Sha512sum *sha512, + const guchar *buffer, + gsize length) +{ + gsize block_left, offset = 0; + + if (length == 0) + return; + + sha512->data_len[0] += length * 8; + if (sha512->data_len[0] < length) + sha512->data_len[1]++; + + /* try to fill current block */ + block_left = SHA2_BLOCK_LEN - sha512->block_len; + if (block_left > 0) + { + gsize fill_len; + + fill_len = MIN (block_left, length); + memcpy (sha512->block + sha512->block_len, buffer, fill_len); + sha512->block_len += fill_len; + length -= fill_len; + offset += fill_len; + + if (sha512->block_len == SHA2_BLOCK_LEN) + { + sha512_transform (sha512->H, sha512->block); + sha512->block_len = 0; + } + } + + /* process complete blocks */ + while (length >= SHA2_BLOCK_LEN) + { + memcpy (sha512->block, buffer + offset, SHA2_BLOCK_LEN); + + sha512_transform (sha512->H, sha512->block); + + length -= SHA2_BLOCK_LEN; + offset += SHA2_BLOCK_LEN; + } + + /* keep remaining data for next block */ + if (length > 0) + { + memcpy (sha512->block, buffer + offset, length); + sha512->block_len = length; + } +} + +static void +sha512_sum_close (Sha512sum *sha512) +{ + guint l; + gint zeros; + guint8 pad[SHA2_BLOCK_LEN * 2] = { 0, }; + guint pad_len = 0; + gint i; + + /* apply padding [§5.1.2] */ + l = sha512->block_len * 8; + zeros = 896 - (l + 1); + + if (zeros < 0) + zeros += 128 * 8; + + pad[0] = 0x80; /* 1000 0000 */ + zeros -= 7; + pad_len++; + + 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); + pad_len += 8; + + PUT_UINT64 (sha512->data_len[0], pad, pad_len); + pad_len += 8; + + /* update checksum with the padded block */ + sha512_sum_update (sha512, pad, pad_len); + + /* copy resulting 64-bit words into digest */ + for (i = 0; i < 8; i++) + PUT_UINT64 (sha512->H[i], sha512->digest, i * 8); +} + +static gchar * +sha384_sum_to_string (Sha512sum *sha512) +{ + return digest_to_string (sha512->digest, SHA384_DIGEST_LEN); +} + +static gchar * +sha512_sum_to_string (Sha512sum *sha512) +{ + return digest_to_string (sha512->digest, SHA512_DIGEST_LEN); +} + +static void +sha384_sum_digest (Sha512sum *sha512, + guint8 *digest) +{ + memcpy (digest, sha512->digest, SHA384_DIGEST_LEN); +} + +static void +sha512_sum_digest (Sha512sum *sha512, + guint8 *digest) +{ + memcpy (digest, sha512->digest, SHA512_DIGEST_LEN); +} + +#undef Ch +#undef Maj +#undef SHR +#undef ROTR +#undef SIGMA0 +#undef SIGMA1 +#undef sigma0 +#undef sigma1 + +#undef PUT_UINT64 + +/* + * Public API + */ + +/** + * g_checksum_type_get_length: + * @checksum_type: a #GChecksumType + * + * Gets the length in bytes of digests of type @checksum_type + * + * Returns: the checksum length, or -1 if @checksum_type is + * not supported. + * + * Since: 2.16 + */ +gssize +g_checksum_type_get_length (GChecksumType checksum_type) +{ + gssize len = -1; + + switch (checksum_type) + { + case G_CHECKSUM_MD5: + len = MD5_DIGEST_LEN; + break; + case G_CHECKSUM_SHA1: + len = SHA1_DIGEST_LEN; + break; + case G_CHECKSUM_SHA256: + len = SHA256_DIGEST_LEN; + break; + case G_CHECKSUM_SHA384: + len = SHA384_DIGEST_LEN; + break; + case G_CHECKSUM_SHA512: + len = SHA512_DIGEST_LEN; + break; + default: + len = -1; + break; + } + + return len; +} + +/** + * g_checksum_new: + * @checksum_type: the desired type of checksum + * + * Creates a new #GChecksum, using the checksum algorithm @checksum_type. + * If the @checksum_type is not known, %NULL is returned. + * A #GChecksum can be used to compute the checksum, or digest, of an + * arbitrary binary blob, using different hashing algorithms. + * + * A #GChecksum works by feeding a binary blob through g_checksum_update() + * until there is data to be checked; the digest can then be extracted + * using g_checksum_get_string(), which will return the checksum as a + * hexadecimal string; or g_checksum_get_digest(), which will return a + * vector of raw bytes. Once either g_checksum_get_string() or + * g_checksum_get_digest() have been called on a #GChecksum, the checksum + * will be closed and it won't be possible to call g_checksum_update() + * on it anymore. + * + * Returns: (transfer full) (nullable): the newly created #GChecksum, or %NULL. + * Use g_checksum_free() to free the memory allocated by it. + * + * Since: 2.16 + */ +GChecksum * +g_checksum_new (GChecksumType checksum_type) +{ + GChecksum *checksum; + + if (! IS_VALID_TYPE (checksum_type)) + return NULL; + + checksum = g_slice_new0 (GChecksum); + checksum->type = checksum_type; + + g_checksum_reset (checksum); + + return checksum; +} + +/** + * g_checksum_reset: + * @checksum: the #GChecksum to reset + * + * Resets the state of the @checksum back to its initial state. + * + * Since: 2.18 + **/ +void +g_checksum_reset (GChecksum *checksum) +{ + g_return_if_fail (checksum != NULL); + + g_free (checksum->digest_str); + checksum->digest_str = NULL; + + switch (checksum->type) + { + case G_CHECKSUM_MD5: + md5_sum_init (&(checksum->sum.md5)); + break; + case G_CHECKSUM_SHA1: + sha1_sum_init (&(checksum->sum.sha1)); + break; + case G_CHECKSUM_SHA256: + sha256_sum_init (&(checksum->sum.sha256)); + break; + case G_CHECKSUM_SHA384: + sha384_sum_init (&(checksum->sum.sha512)); + break; + case G_CHECKSUM_SHA512: + sha512_sum_init (&(checksum->sum.sha512)); + break; + default: + g_assert_not_reached (); + break; + } +} + +/** + * g_checksum_copy: + * @checksum: the #GChecksum to copy + * + * Copies a #GChecksum. If @checksum has been closed, by calling + * g_checksum_get_string() or g_checksum_get_digest(), the copied + * checksum will be closed as well. + * + * Returns: (transfer full): the copy of the passed #GChecksum. Use + * g_checksum_free() when finished using it. + * + * Since: 2.16 + */ +GChecksum * +g_checksum_copy (const GChecksum *checksum) +{ + GChecksum *copy; + + g_return_val_if_fail (checksum != NULL, NULL); + + copy = g_slice_new (GChecksum); + *copy = *checksum; + + copy->digest_str = g_strdup (checksum->digest_str); + + return copy; +} + +/** + * g_checksum_free: + * @checksum: a #GChecksum + * + * Frees the memory allocated for @checksum. + * + * Since: 2.16 + */ +void +g_checksum_free (GChecksum *checksum) +{ + if (G_LIKELY (checksum)) + { + g_free (checksum->digest_str); + + g_slice_free (GChecksum, checksum); + } +} + +/** + * g_checksum_update: + * @checksum: a #GChecksum + * @data: (array length=length) (element-type guint8): buffer used to compute the checksum + * @length: size of the buffer, or -1 if it is a null-terminated string. + * + * Feeds @data into an existing #GChecksum. The checksum must still be + * open, that is g_checksum_get_string() or g_checksum_get_digest() must + * not have been called on @checksum. + * + * Since: 2.16 + */ +void +g_checksum_update (GChecksum *checksum, + const guchar *data, + gssize length) +{ + g_return_if_fail (checksum != NULL); + g_return_if_fail (length == 0 || data != NULL); + + if (length < 0) + length = strlen ((const gchar *) data); + + if (checksum->digest_str) + { + g_warning ("The checksum '%s' has been closed and cannot be updated " + "anymore.", + checksum->digest_str); + return; + } + + switch (checksum->type) + { + case G_CHECKSUM_MD5: + md5_sum_update (&(checksum->sum.md5), data, length); + break; + case G_CHECKSUM_SHA1: + sha1_sum_update (&(checksum->sum.sha1), data, length); + break; + case G_CHECKSUM_SHA256: + sha256_sum_update (&(checksum->sum.sha256), data, length); + break; + case G_CHECKSUM_SHA384: + case G_CHECKSUM_SHA512: + sha512_sum_update (&(checksum->sum.sha512), data, length); + break; + default: + g_assert_not_reached (); + break; + } +} + +/** + * g_checksum_get_string: + * @checksum: a #GChecksum + * + * Gets the digest as a hexadecimal string. + * + * Once this function has been called the #GChecksum can no longer be + * updated with g_checksum_update(). + * + * The hexadecimal characters will be lower case. + * + * Returns: the hexadecimal representation of the checksum. The + * returned string is owned by the checksum and should not be modified + * or freed. + * + * Since: 2.16 + */ +const gchar * +g_checksum_get_string (GChecksum *checksum) +{ + gchar *str = NULL; + + g_return_val_if_fail (checksum != NULL, NULL); + + if (checksum->digest_str) + return checksum->digest_str; + + switch (checksum->type) + { + case G_CHECKSUM_MD5: + md5_sum_close (&(checksum->sum.md5)); + str = md5_sum_to_string (&(checksum->sum.md5)); + break; + case G_CHECKSUM_SHA1: + sha1_sum_close (&(checksum->sum.sha1)); + str = sha1_sum_to_string (&(checksum->sum.sha1)); + break; + case G_CHECKSUM_SHA256: + sha256_sum_close (&(checksum->sum.sha256)); + str = sha256_sum_to_string (&(checksum->sum.sha256)); + break; + case G_CHECKSUM_SHA384: + sha512_sum_close (&(checksum->sum.sha512)); + str = sha384_sum_to_string (&(checksum->sum.sha512)); + break; + case G_CHECKSUM_SHA512: + sha512_sum_close (&(checksum->sum.sha512)); + str = sha512_sum_to_string (&(checksum->sum.sha512)); + break; + default: + g_assert_not_reached (); + break; + } + + checksum->digest_str = str; + + return checksum->digest_str; +} + +/** + * g_checksum_get_digest: (skip) + * @checksum: a #GChecksum + * @buffer: (array length=digest_len): output buffer + * @digest_len: (inout): an inout parameter. The caller initializes it to the size of @buffer. + * After the call it contains the length of the digest. + * + * Gets the digest from @checksum as a raw binary vector and places it + * into @buffer. The size of the digest depends on the type of checksum. + * + * Once this function has been called, the #GChecksum is closed and can + * no longer be updated with g_checksum_update(). + * + * Since: 2.16 + */ +void +g_checksum_get_digest (GChecksum *checksum, + guint8 *buffer, + gsize *digest_len) +{ + gboolean checksum_open = FALSE; + gchar *str = NULL; + gsize len; + + g_return_if_fail (checksum != NULL); + + len = g_checksum_type_get_length (checksum->type); + g_return_if_fail (*digest_len >= len); + + checksum_open = !!(checksum->digest_str == NULL); + + switch (checksum->type) + { + case G_CHECKSUM_MD5: + if (checksum_open) + { + md5_sum_close (&(checksum->sum.md5)); + str = md5_sum_to_string (&(checksum->sum.md5)); + } + md5_sum_digest (&(checksum->sum.md5), buffer); + break; + case G_CHECKSUM_SHA1: + if (checksum_open) + { + sha1_sum_close (&(checksum->sum.sha1)); + str = sha1_sum_to_string (&(checksum->sum.sha1)); + } + sha1_sum_digest (&(checksum->sum.sha1), buffer); + break; + case G_CHECKSUM_SHA256: + if (checksum_open) + { + sha256_sum_close (&(checksum->sum.sha256)); + str = sha256_sum_to_string (&(checksum->sum.sha256)); + } + sha256_sum_digest (&(checksum->sum.sha256), buffer); + break; + case G_CHECKSUM_SHA384: + if (checksum_open) + { + sha512_sum_close (&(checksum->sum.sha512)); + str = sha384_sum_to_string (&(checksum->sum.sha512)); + } + sha384_sum_digest (&(checksum->sum.sha512), buffer); + break; + case G_CHECKSUM_SHA512: + if (checksum_open) + { + sha512_sum_close (&(checksum->sum.sha512)); + str = sha512_sum_to_string (&(checksum->sum.sha512)); + } + sha512_sum_digest (&(checksum->sum.sha512), buffer); + break; + default: + g_assert_not_reached (); + break; + } + + if (str) + checksum->digest_str = str; + + *digest_len = len; +} + +/** + * g_compute_checksum_for_data: + * @checksum_type: a #GChecksumType + * @data: (array length=length) (element-type guint8): binary blob to compute the digest of + * @length: length of @data + * + * Computes the checksum for a binary @data of @length. This is a + * convenience wrapper for g_checksum_new(), g_checksum_get_string() + * and g_checksum_free(). + * + * The hexadecimal string returned will be in lower case. + * + * Returns: (transfer full) (nullable): the digest of the binary data as a + * string in hexadecimal, or %NULL if g_checksum_new() fails for + * @checksum_type. The returned string should be freed with g_free() when + * done using it. + * + * Since: 2.16 + */ +gchar * +g_compute_checksum_for_data (GChecksumType checksum_type, + const guchar *data, + gsize length) +{ + GChecksum *checksum; + gchar *retval; + + g_return_val_if_fail (length == 0 || data != NULL, NULL); + + checksum = g_checksum_new (checksum_type); + if (!checksum) + return NULL; + + g_checksum_update (checksum, data, length); + retval = g_strdup (g_checksum_get_string (checksum)); + g_checksum_free (checksum); + + return retval; +} + +/** + * g_compute_checksum_for_string: + * @checksum_type: a #GChecksumType + * @str: the string to compute the checksum of + * @length: the length of the string, or -1 if the string is null-terminated. + * + * Computes the checksum of a string. + * + * The hexadecimal string returned will be in lower case. + * + * Returns: (transfer full) (nullable): the checksum as a hexadecimal string, + * or %NULL if g_checksum_new() fails for @checksum_type. The returned string + * should be freed with g_free() when done using it. + * + * Since: 2.16 + */ +gchar * +g_compute_checksum_for_string (GChecksumType checksum_type, + const gchar *str, + gssize length) +{ + g_return_val_if_fail (length == 0 || str != NULL, NULL); + + if (length < 0) + length = strlen (str); + + return g_compute_checksum_for_data (checksum_type, (const guchar *) str, length); +} + +/** + * g_compute_checksum_for_bytes: + * @checksum_type: a #GChecksumType + * @data: binary blob to compute the digest of + * + * Computes the checksum for a binary @data. This is a + * convenience wrapper for g_checksum_new(), g_checksum_get_string() + * and g_checksum_free(). + * + * The hexadecimal string returned will be in lower case. + * + * Returns: (transfer full) (nullable): the digest of the binary data as a + * string in hexadecimal, or %NULL if g_checksum_new() fails for + * @checksum_type. The returned string should be freed with g_free() when + * done using it. + * + * Since: 2.34 + */ +gchar * +g_compute_checksum_for_bytes (GChecksumType checksum_type, + GBytes *data) +{ + gconstpointer byte_data; + gsize length; + + g_return_val_if_fail (data != NULL, NULL); + + byte_data = g_bytes_get_data (data, &length); + return g_compute_checksum_for_data (checksum_type, byte_data, length); +} diff --git a/glib/gchecksum.h b/glib/gchecksum.h new file mode 100644 index 0000000..5bb52d8 --- /dev/null +++ b/glib/gchecksum.h @@ -0,0 +1,104 @@ +/* gchecksum.h - data hashing functions + * + * Copyright (C) 2007 Emmanuele Bassi + * + * 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_CHECKSUM_H__ +#define __G_CHECKSUM_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * GChecksumType: + * @G_CHECKSUM_MD5: Use the MD5 hashing algorithm + * @G_CHECKSUM_SHA1: Use the SHA-1 hashing algorithm + * @G_CHECKSUM_SHA256: Use the SHA-256 hashing algorithm + * @G_CHECKSUM_SHA384: Use the SHA-384 hashing algorithm (Since: 2.51) + * @G_CHECKSUM_SHA512: Use the SHA-512 hashing algorithm (Since: 2.36) + * + * The hashing algorithm to be used by #GChecksum when performing the + * digest of some data. + * + * Note that the #GChecksumType enumeration may be extended at a later + * date to include new hashing algorithm types. + * + * Since: 2.16 + */ +typedef enum { + G_CHECKSUM_MD5, + G_CHECKSUM_SHA1, + G_CHECKSUM_SHA256, + G_CHECKSUM_SHA512, + G_CHECKSUM_SHA384 +} GChecksumType; + +/** + * GChecksum: + * + * An opaque structure representing a checksumming operation. + * + * To create a new GChecksum, use g_checksum_new(). To free + * a GChecksum, use g_checksum_free(). + * + * Since: 2.16 + */ +typedef struct _GChecksum GChecksum; + +GLIB_AVAILABLE_IN_ALL +gssize g_checksum_type_get_length (GChecksumType checksum_type); + +GLIB_AVAILABLE_IN_ALL +GChecksum * g_checksum_new (GChecksumType checksum_type); +GLIB_AVAILABLE_IN_ALL +void g_checksum_reset (GChecksum *checksum); +GLIB_AVAILABLE_IN_ALL +GChecksum * g_checksum_copy (const GChecksum *checksum); +GLIB_AVAILABLE_IN_ALL +void g_checksum_free (GChecksum *checksum); +GLIB_AVAILABLE_IN_ALL +void g_checksum_update (GChecksum *checksum, + const guchar *data, + gssize length); +GLIB_AVAILABLE_IN_ALL +const gchar * g_checksum_get_string (GChecksum *checksum); +GLIB_AVAILABLE_IN_ALL +void g_checksum_get_digest (GChecksum *checksum, + guint8 *buffer, + gsize *digest_len); + +GLIB_AVAILABLE_IN_ALL +gchar *g_compute_checksum_for_data (GChecksumType checksum_type, + const guchar *data, + gsize length); +GLIB_AVAILABLE_IN_ALL +gchar *g_compute_checksum_for_string (GChecksumType checksum_type, + const gchar *str, + gssize length); + +GLIB_AVAILABLE_IN_2_34 +gchar *g_compute_checksum_for_bytes (GChecksumType checksum_type, + GBytes *data); + +G_END_DECLS + +#endif /* __G_CHECKSUM_H__ */ diff --git a/glib/gconstructor.h b/glib/gconstructor.h new file mode 100644 index 0000000..c5e0dfa --- /dev/null +++ b/glib/gconstructor.h @@ -0,0 +1,159 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_CONSTRUCTOR_H__ +#define __G_CONSTRUCTOR_H__ + +/* + If G_HAS_CONSTRUCTORS is true then the compiler support *both* constructors and + 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, + so the way you need to use this is (for constructors): + + #ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA + #pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(my_constructor) + #endif + G_DEFINE_CONSTRUCTOR(my_constructor) + static void my_constructor(void) { + ... + } + +*/ + +#ifndef __GTK_DOC_IGNORE__ + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) + +#define G_HAS_CONSTRUCTORS 1 + +#define G_DEFINE_CONSTRUCTOR(_func) static void __attribute__((constructor)) _func (void); +#define G_DEFINE_DESTRUCTOR(_func) static void __attribute__((destructor)) _func (void); + +#elif defined (_MSC_VER) && (_MSC_VER >= 1500) +/* Visual studio 2008 and later has _Pragma */ + +/* + * Only try to include gslist.h if not already included via glib.h, + * so that items using gconstructor.h outside of GLib (such as + * GResources) continue to build properly. + */ +#ifndef __G_LIB_H__ +#include "gslist.h" +#endif + +#include + +#define G_HAS_CONSTRUCTORS 1 + +/* We do some weird things to avoid the constructors being optimized + * away on VS2015 if WholeProgramOptimization is enabled. First we + * make a reference to the array from the wrapper to make sure its + * references. Then we use a pragma to make sure the wrapper function + * symbol is always included at the link stage. Also, the symbols + * need to be extern (but not dllexport), even though they are not + * really used from another object file. + */ + +/* We need to account for differences between the mangling of symbols + * for x86 and x64/ARM/ARM64 programs, as symbols on x86 are prefixed + * with an underscore but symbols on x64/ARM/ARM64 are not. + */ +#ifdef _M_IX86 +#define G_MSVC_SYMBOL_PREFIX "_" +#else +#define G_MSVC_SYMBOL_PREFIX "" +#endif + +#define G_DEFINE_CONSTRUCTOR(_func) G_MSVC_CTOR (_func, G_MSVC_SYMBOL_PREFIX) +#define G_DEFINE_DESTRUCTOR(_func) G_MSVC_DTOR (_func, G_MSVC_SYMBOL_PREFIX) + +#define G_MSVC_CTOR(_func,_sym_prefix) \ + static void _func(void); \ + extern int (* _array ## _func)(void); \ + int _func ## _wrapper(void) { _func(); g_slist_find (NULL, _array ## _func); return 0; } \ + __pragma(comment(linker,"/include:" _sym_prefix # _func "_wrapper")) \ + __pragma(section(".CRT$XCU",read)) \ + __declspec(allocate(".CRT$XCU")) int (* _array ## _func)(void) = _func ## _wrapper; + +#define G_MSVC_DTOR(_func,_sym_prefix) \ + static void _func(void); \ + extern int (* _array ## _func)(void); \ + int _func ## _constructor(void) { atexit (_func); g_slist_find (NULL, _array ## _func); return 0; } \ + __pragma(comment(linker,"/include:" _sym_prefix # _func "_constructor")) \ + __pragma(section(".CRT$XCU",read)) \ + __declspec(allocate(".CRT$XCU")) int (* _array ## _func)(void) = _func ## _constructor; + +#elif defined (_MSC_VER) + +#define G_HAS_CONSTRUCTORS 1 + +/* Pre Visual studio 2008 must use #pragma section */ +#define G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA 1 +#define G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA 1 + +#define G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(_func) \ + section(".CRT$XCU",read) +#define G_DEFINE_CONSTRUCTOR(_func) \ + static void _func(void); \ + static int _func ## _wrapper(void) { _func(); return 0; } \ + __declspec(allocate(".CRT$XCU")) static int (*p)(void) = _func ## _wrapper; + +#define G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(_func) \ + section(".CRT$XCU",read) +#define G_DEFINE_DESTRUCTOR(_func) \ + static void _func(void); \ + static int _func ## _constructor(void) { atexit (_func); return 0; } \ + __declspec(allocate(".CRT$XCU")) static int (* _array ## _func)(void) = _func ## _constructor; + +#elif defined(__SUNPRO_C) + +/* This is not tested, but i believe it should work, based on: + * http://opensource.apple.com/source/OpenSSL098/OpenSSL098-35/src/fips/fips_premain.c + */ + +#define G_HAS_CONSTRUCTORS 1 + +#define G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA 1 +#define G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA 1 + +#define G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(_func) \ + init(_func) +#define G_DEFINE_CONSTRUCTOR(_func) \ + static void _func(void); + +#define G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(_func) \ + fini(_func) +#define G_DEFINE_DESTRUCTOR(_func) \ + static void _func(void); + +#else + +/* constructors not supported for this compiler */ + +#endif + +#endif /* __GTK_DOC_IGNORE__ */ +#endif /* __G_CONSTRUCTOR_H__ */ diff --git a/glib/gconvert.c b/glib/gconvert.c new file mode 100644 index 0000000..052f019 --- /dev/null +++ b/glib/gconvert.c @@ -0,0 +1,2066 @@ +/* GLIB - Library of useful routines for C programming + * + * gconvert.c: Convert between character sets using iconv + * Copyright Red Hat Inc., 2000 + * Authors: Havoc Pennington , Owen Taylor + * + * 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 "glibconfig.h" + +#ifndef G_OS_WIN32 +#include +#endif +#include +#include +#include +#include + +#ifdef G_OS_WIN32 +#include "win_iconv.c" +#endif + +#ifdef G_PLATFORM_WIN32 +#define STRICT +#include +#undef STRICT +#endif + +#include "gconvert.h" +#include "gconvertprivate.h" + +#include "gcharsetprivate.h" +#include "gslist.h" +#include "gstrfuncs.h" +#include "gtestutils.h" +#include "gthread.h" +#include "gthreadprivate.h" +#include "gunicode.h" +#include "gfileutils.h" +#include "genviron.h" + +#include "glibintl.h" + + +/** + * SECTION:conversions + * @title: Character Set Conversion + * @short_description: convert strings between different character sets + * + * The g_convert() family of function wraps the functionality of iconv(). + * In addition to pure character set conversions, GLib has functions to + * deal with the extra complications of encodings for file names. + * + * ## File Name Encodings + * + * Historically, UNIX has not had a defined encoding for file names: + * a file name is valid as long as it does not have path separators + * in it ("/"). However, displaying file names may require conversion: + * from the character set in which they were created, to the character + * set in which the application operates. Consider the Spanish file name + * "Presentación.sxi". If the application which created it uses + * ISO-8859-1 for its encoding, + * |[ + * Character: P r e s e n t a c i ó n . s x i + * Hex code: 50 72 65 73 65 6e 74 61 63 69 f3 6e 2e 73 78 69 + * ]| + * However, if the application use UTF-8, the actual file name on + * disk would look like this: + * |[ + * Character: P r e s e n t a c i ó n . s x i + * Hex code: 50 72 65 73 65 6e 74 61 63 69 c3 b3 6e 2e 73 78 69 + * ]| + * Glib uses UTF-8 for its strings, and GUI toolkits like GTK+ that use + * GLib do the same thing. If you get a file name from the file system, + * for example, from readdir() or from g_dir_read_name(), and you wish + * to display the file name to the user, you will need to convert it + * into UTF-8. The opposite case is when the user types the name of a + * file they wish to save: the toolkit will give you that string in + * UTF-8 encoding, and you will need to convert it to the character + * set used for file names before you can create the file with open() + * or fopen(). + * + * By default, GLib assumes that file names on disk are in UTF-8 + * encoding. This is a valid assumption for file systems which + * were created relatively recently: most applications use UTF-8 + * encoding for their strings, and that is also what they use for + * the file names they create. However, older file systems may + * still contain file names created in "older" encodings, such as + * ISO-8859-1. In this case, for compatibility reasons, you may want + * to instruct GLib to use that particular encoding for file names + * rather than UTF-8. You can do this by specifying the encoding for + * file names in the [`G_FILENAME_ENCODING`][G_FILENAME_ENCODING] + * environment variable. For example, if your installation uses + * ISO-8859-1 for file names, you can put this in your `~/.profile`: + * |[ + * export G_FILENAME_ENCODING=ISO-8859-1 + * ]| + * GLib provides the functions g_filename_to_utf8() and + * g_filename_from_utf8() to perform the necessary conversions. + * These functions convert file names from the encoding specified + * in `G_FILENAME_ENCODING` to UTF-8 and vice-versa. This + * [diagram][file-name-encodings-diagram] illustrates how + * these functions are used to convert between UTF-8 and the + * encoding for file names in the file system. + * + * ## Conversion between file name encodings # {#file-name-encodings-diagram) + * + * ![](file-name-encodings.png) + * + * ## Checklist for Application Writers + * + * This section is a practical summary of the detailed + * things to do to make sure your applications process file + * name encodings correctly. + * + * 1. If you get a file name from the file system from a function + * such as readdir() or gtk_file_chooser_get_filename(), you do + * not need to do any conversion to pass that file name to + * functions like open(), rename(), or fopen() -- those are "raw" + * file names which the file system understands. + * + * 2. If you need to display a file name, convert it to UTF-8 first + * by using g_filename_to_utf8(). If conversion fails, display a + * string like "Unknown file name". Do not convert this string back + * into the encoding used for file names if you wish to pass it to + * the file system; use the original file name instead. + * + * For example, the document window of a word processor could display + * "Unknown file name" in its title bar but still let the user save + * the file, as it would keep the raw file name internally. This + * can happen if the user has not set the `G_FILENAME_ENCODING` + * environment variable even though they have files whose names are + * not encoded in UTF-8. + * + * 3. If your user interface lets the user type a file name for saving + * or renaming, convert it to the encoding used for file names in + * the file system by using g_filename_from_utf8(). Pass the converted + * file name to functions like fopen(). If conversion fails, ask the + * user to enter a different file name. This can happen if the user + * types Japanese characters when `G_FILENAME_ENCODING` is set to + * `ISO-8859-1`, for example. + */ + +/* We try to terminate strings in unknown charsets with this many zero bytes + * to ensure that multibyte strings really are nul-terminated when we return + * them from g_convert() and friends. + */ +#define NUL_TERMINATOR_LENGTH 4 + +G_DEFINE_QUARK (g_convert_error, g_convert_error) + +static gboolean +try_conversion (const char *to_codeset, + const char *from_codeset, + iconv_t *cd) +{ + *cd = iconv_open (to_codeset, from_codeset); + + if (*cd == (iconv_t)-1 && errno == EINVAL) + return FALSE; + else + return TRUE; +} + +static gboolean +try_to_aliases (const char **to_aliases, + const char *from_codeset, + iconv_t *cd) +{ + if (to_aliases) + { + const char **p = to_aliases; + while (*p) + { + if (try_conversion (*p, from_codeset, cd)) + return TRUE; + + p++; + } + } + + return FALSE; +} + +/** + * g_iconv_open: (skip) + * @to_codeset: destination codeset + * @from_codeset: source codeset + * + * Same as the standard UNIX routine iconv_open(), but + * may be implemented via libiconv on UNIX flavors that lack + * a native implementation. + * + * GLib provides g_convert() and g_locale_to_utf8() which are likely + * more convenient than the raw iconv wrappers. + * + * Returns: a "conversion descriptor", or (GIConv)-1 if + * opening the converter failed. + **/ +GIConv +g_iconv_open (const gchar *to_codeset, + const gchar *from_codeset) +{ + iconv_t cd; + + if (!try_conversion (to_codeset, from_codeset, &cd)) + { + const char **to_aliases = _g_charset_get_aliases (to_codeset); + const char **from_aliases = _g_charset_get_aliases (from_codeset); + + if (from_aliases) + { + const char **p = from_aliases; + while (*p) + { + if (try_conversion (to_codeset, *p, &cd)) + goto out; + + if (try_to_aliases (to_aliases, *p, &cd)) + goto out; + + p++; + } + } + + if (try_to_aliases (to_aliases, from_codeset, &cd)) + goto out; + } + + out: + return (cd == (iconv_t)-1) ? (GIConv)-1 : (GIConv)cd; +} + +/** + * g_iconv: (skip) + * @converter: conversion descriptor from g_iconv_open() + * @inbuf: bytes to convert + * @inbytes_left: inout parameter, bytes remaining to convert in @inbuf + * @outbuf: converted output bytes + * @outbytes_left: inout parameter, bytes available to fill in @outbuf + * + * Same as the standard UNIX routine iconv(), but + * may be implemented via libiconv on UNIX flavors that lack + * a native implementation. + * + * GLib provides g_convert() and g_locale_to_utf8() which are likely + * more convenient than the raw iconv wrappers. + * + * Note that the behaviour of iconv() for characters which are valid in the + * input character set, but which have no representation in the output character + * set, is implementation defined. This function may return success (with a + * positive number of non-reversible conversions as replacement characters were + * used), or it may return -1 and set an error such as %EILSEQ, in such a + * situation. + * + * Returns: count of non-reversible conversions, or -1 on error + **/ +gsize +g_iconv (GIConv converter, + gchar **inbuf, + gsize *inbytes_left, + gchar **outbuf, + gsize *outbytes_left) +{ + iconv_t cd = (iconv_t)converter; + + return iconv (cd, inbuf, inbytes_left, outbuf, outbytes_left); +} + +/** + * g_iconv_close: (skip) + * @converter: a conversion descriptor from g_iconv_open() + * + * Same as the standard UNIX routine iconv_close(), but + * may be implemented via libiconv on UNIX flavors that lack + * a native implementation. Should be called to clean up + * the conversion descriptor from g_iconv_open() when + * you are done converting things. + * + * GLib provides g_convert() and g_locale_to_utf8() which are likely + * more convenient than the raw iconv wrappers. + * + * Returns: -1 on error, 0 on success + **/ +gint +g_iconv_close (GIConv converter) +{ + iconv_t cd = (iconv_t)converter; + + return iconv_close (cd); +} + +static GIConv +open_converter (const gchar *to_codeset, + const gchar *from_codeset, + GError **error) +{ + GIConv cd; + + cd = g_iconv_open (to_codeset, from_codeset); + + if (cd == (GIConv) -1) + { + /* Something went wrong. */ + if (error) + { + if (errno == EINVAL) + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_NO_CONVERSION, + _("Conversion from character set “%s” to “%s” is not supported"), + from_codeset, to_codeset); + else + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_FAILED, + _("Could not open converter from “%s” to “%s”"), + from_codeset, to_codeset); + } + } + + return cd; +} + +static int +close_converter (GIConv cd) +{ + if (cd == (GIConv) -1) + return 0; + + return g_iconv_close (cd); +} + +/** + * g_convert_with_iconv: (skip) + * @str: (array length=len) (element-type guint8): + * the string to convert. + * @len: the length of the string in bytes, or -1 if the string is + * nul-terminated (Note that some encodings may allow nul + * bytes to occur inside strings. In that case, using -1 + * for the @len parameter is unsafe) + * @converter: conversion descriptor from g_iconv_open() + * @bytes_read: (out) (optional): location to store the number of bytes in + * the input string that were successfully converted, or %NULL. + * Even if the conversion was successful, this may be + * less than @len if there were partial characters + * at the end of the input. If the error + * %G_CONVERT_ERROR_ILLEGAL_SEQUENCE occurs, the value + * stored will be the byte offset after the last valid + * input sequence. + * @bytes_written: (out) (optional): the number of bytes stored in + * the output buffer (not including the terminating nul). + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts a string from one character set to another. + * + * Note that you should use g_iconv() for streaming conversions. + * Despite the fact that @bytes_read can return information about partial + * characters, the g_convert_... functions are not generally suitable + * for streaming. If the underlying converter maintains internal state, + * then this won't be preserved across successive calls to g_convert(), + * g_convert_with_iconv() or g_convert_with_fallback(). (An example of + * this is the GNU C converter for CP1255 which does not emit a base + * character until it knows that the next character is not a mark that + * could combine with the base character.) + * + * Characters which are valid in the input character set, but which have no + * representation in the output character set will result in a + * %G_CONVERT_ERROR_ILLEGAL_SEQUENCE error. This is in contrast to the iconv() + * specification, which leaves this behaviour implementation defined. Note that + * this is the same error code as is returned for an invalid byte sequence in + * the input character set. To get defined behaviour for conversion of + * unrepresentable characters, use g_convert_with_fallback(). + * + * Returns: (array length=bytes_written) (element-type guint8) (transfer full): + * If the conversion was successful, a newly allocated buffer + * containing the converted string, which must be freed with + * g_free(). Otherwise %NULL and @error will be set. + **/ +gchar* +g_convert_with_iconv (const gchar *str, + gssize len, + GIConv converter, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + gchar *dest; + gchar *outp; + const gchar *p; + gsize inbytes_remaining; + gsize outbytes_remaining; + gsize err; + gsize outbuf_size; + gboolean have_error = FALSE; + gboolean done = FALSE; + gboolean reset = FALSE; + + g_return_val_if_fail (converter != (GIConv) -1, NULL); + + if (len < 0) + len = strlen (str); + + p = str; + inbytes_remaining = len; + outbuf_size = len + NUL_TERMINATOR_LENGTH; + + outbytes_remaining = outbuf_size - NUL_TERMINATOR_LENGTH; + outp = dest = g_malloc (outbuf_size); + + while (!done && !have_error) + { + if (reset) + err = g_iconv (converter, NULL, &inbytes_remaining, &outp, &outbytes_remaining); + else + err = g_iconv (converter, (char **)&p, &inbytes_remaining, &outp, &outbytes_remaining); + + if (err == (gsize) -1) + { + switch (errno) + { + case EINVAL: + /* Incomplete text, do not report an error */ + done = TRUE; + break; + case E2BIG: + { + gsize used = outp - dest; + + outbuf_size *= 2; + dest = g_realloc (dest, outbuf_size); + + outp = dest + used; + outbytes_remaining = outbuf_size - used - NUL_TERMINATOR_LENGTH; + } + break; + case EILSEQ: + g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, + _("Invalid byte sequence in conversion input")); + have_error = TRUE; + break; + default: + { + int errsv = errno; + + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_FAILED, + _("Error during conversion: %s"), + g_strerror (errsv)); + } + have_error = TRUE; + break; + } + } + else if (err > 0) + { + /* @err gives the number of replacement characters used. */ + g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, + _("Unrepresentable character in conversion input")); + have_error = TRUE; + } + else + { + if (!reset) + { + /* call g_iconv with NULL inbuf to cleanup shift state */ + reset = TRUE; + inbytes_remaining = 0; + } + else + done = TRUE; + } + } + + memset (outp, 0, NUL_TERMINATOR_LENGTH); + + if (bytes_read) + *bytes_read = p - str; + else + { + if ((p - str) != len) + { + if (!have_error) + { + g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_PARTIAL_INPUT, + _("Partial character sequence at end of input")); + have_error = TRUE; + } + } + } + + if (bytes_written) + *bytes_written = outp - dest; /* Doesn't include '\0' */ + + if (have_error) + { + g_free (dest); + return NULL; + } + else + return dest; +} + +/** + * g_convert: + * @str: (array length=len) (element-type guint8): + * the string to convert. + * @len: the length of the string in bytes, or -1 if the string is + * nul-terminated (Note that some encodings may allow nul + * bytes to occur inside strings. In that case, using -1 + * for the @len parameter is unsafe) + * @to_codeset: name of character set into which to convert @str + * @from_codeset: character set of @str. + * @bytes_read: (out) (optional): location to store the number of bytes in + * the input string that were successfully converted, or %NULL. + * Even if the conversion was successful, this may be + * less than @len if there were partial characters + * at the end of the input. If the error + * %G_CONVERT_ERROR_ILLEGAL_SEQUENCE occurs, the value + * stored will be the byte offset after the last valid + * input sequence. + * @bytes_written: (out) (optional): the number of bytes stored in + * the output buffer (not including the terminating nul). + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts a string from one character set to another. + * + * Note that you should use g_iconv() for streaming conversions. + * Despite the fact that @bytes_read can return information about partial + * characters, the g_convert_... functions are not generally suitable + * for streaming. If the underlying converter maintains internal state, + * then this won't be preserved across successive calls to g_convert(), + * g_convert_with_iconv() or g_convert_with_fallback(). (An example of + * this is the GNU C converter for CP1255 which does not emit a base + * character until it knows that the next character is not a mark that + * could combine with the base character.) + * + * Using extensions such as "//TRANSLIT" may not work (or may not work + * well) on many platforms. Consider using g_str_to_ascii() instead. + * + * Returns: (array length=bytes_written) (element-type guint8) (transfer full): + * If the conversion was successful, a newly allocated buffer + * containing the converted string, which must be freed with g_free(). + * Otherwise %NULL and @error will be set. + **/ +gchar* +g_convert (const gchar *str, + gssize len, + const gchar *to_codeset, + const gchar *from_codeset, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + gchar *res; + GIConv cd; + + g_return_val_if_fail (str != NULL, NULL); + g_return_val_if_fail (to_codeset != NULL, NULL); + g_return_val_if_fail (from_codeset != NULL, NULL); + + cd = open_converter (to_codeset, from_codeset, error); + + if (cd == (GIConv) -1) + { + if (bytes_read) + *bytes_read = 0; + + if (bytes_written) + *bytes_written = 0; + + return NULL; + } + + res = g_convert_with_iconv (str, len, cd, + bytes_read, bytes_written, + error); + + close_converter (cd); + + return res; +} + +/** + * g_convert_with_fallback: + * @str: (array length=len) (element-type guint8): + * the string to convert. + * @len: the length of the string in bytes, or -1 if the string is + * nul-terminated (Note that some encodings may allow nul + * bytes to occur inside strings. In that case, using -1 + * for the @len parameter is unsafe) + * @to_codeset: name of character set into which to convert @str + * @from_codeset: character set of @str. + * @fallback: UTF-8 string to use in place of characters not + * present in the target encoding. (The string must be + * representable in the target encoding). + * If %NULL, characters not in the target encoding will + * be represented as Unicode escapes \uxxxx or \Uxxxxyyyy. + * @bytes_read: (out) (optional): location to store the number of bytes in + * the input string that were successfully converted, or %NULL. + * Even if the conversion was successful, this may be + * less than @len if there were partial characters + * at the end of the input. + * @bytes_written: (out) (optional): the number of bytes stored in + * the output buffer (not including the terminating nul). + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts a string from one character set to another, possibly + * including fallback sequences for characters not representable + * in the output. Note that it is not guaranteed that the specification + * for the fallback sequences in @fallback will be honored. Some + * systems may do an approximate conversion from @from_codeset + * to @to_codeset in their iconv() functions, + * in which case GLib will simply return that approximate conversion. + * + * Note that you should use g_iconv() for streaming conversions. + * Despite the fact that @bytes_read can return information about partial + * characters, the g_convert_... functions are not generally suitable + * for streaming. If the underlying converter maintains internal state, + * then this won't be preserved across successive calls to g_convert(), + * g_convert_with_iconv() or g_convert_with_fallback(). (An example of + * this is the GNU C converter for CP1255 which does not emit a base + * character until it knows that the next character is not a mark that + * could combine with the base character.) + * + * Returns: (array length=bytes_written) (element-type guint8) (transfer full): + * If the conversion was successful, a newly allocated buffer + * containing the converted string, which must be freed with g_free(). + * Otherwise %NULL and @error will be set. + **/ +gchar* +g_convert_with_fallback (const gchar *str, + gssize len, + const gchar *to_codeset, + const gchar *from_codeset, + const gchar *fallback, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + gchar *utf8; + gchar *dest; + gchar *outp; + const gchar *insert_str = NULL; + const gchar *p; + gsize inbytes_remaining; + const gchar *save_p = NULL; + gsize save_inbytes = 0; + gsize outbytes_remaining; + gsize err; + GIConv cd; + gsize outbuf_size; + gboolean have_error = FALSE; + gboolean done = FALSE; + + GError *local_error = NULL; + + g_return_val_if_fail (str != NULL, NULL); + g_return_val_if_fail (to_codeset != NULL, NULL); + g_return_val_if_fail (from_codeset != NULL, NULL); + + if (len < 0) + len = strlen (str); + + /* Try an exact conversion; we only proceed if this fails + * due to an illegal sequence in the input string. + */ + dest = g_convert (str, len, to_codeset, from_codeset, + bytes_read, bytes_written, &local_error); + if (!local_error) + return dest; + + if (!g_error_matches (local_error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE)) + { + g_propagate_error (error, local_error); + return NULL; + } + else + g_error_free (local_error); + + local_error = NULL; + + /* No go; to proceed, we need a converter from "UTF-8" to + * to_codeset, and the string as UTF-8. + */ + cd = open_converter (to_codeset, "UTF-8", error); + if (cd == (GIConv) -1) + { + if (bytes_read) + *bytes_read = 0; + + if (bytes_written) + *bytes_written = 0; + + return NULL; + } + + utf8 = g_convert (str, len, "UTF-8", from_codeset, + bytes_read, &inbytes_remaining, error); + if (!utf8) + { + close_converter (cd); + if (bytes_written) + *bytes_written = 0; + return NULL; + } + + /* Now the heart of the code. We loop through the UTF-8 string, and + * whenever we hit an offending character, we form fallback, convert + * the fallback to the target codeset, and then go back to + * converting the original string after finishing with the fallback. + * + * The variables save_p and save_inbytes store the input state + * for the original string while we are converting the fallback + */ + p = utf8; + + outbuf_size = len + NUL_TERMINATOR_LENGTH; + outbytes_remaining = outbuf_size - NUL_TERMINATOR_LENGTH; + outp = dest = g_malloc (outbuf_size); + + while (!done && !have_error) + { + gsize inbytes_tmp = inbytes_remaining; + err = g_iconv (cd, (char **)&p, &inbytes_tmp, &outp, &outbytes_remaining); + inbytes_remaining = inbytes_tmp; + + if (err == (gsize) -1) + { + switch (errno) + { + case EINVAL: + g_assert_not_reached(); + break; + case E2BIG: + { + gsize used = outp - dest; + + outbuf_size *= 2; + dest = g_realloc (dest, outbuf_size); + + outp = dest + used; + outbytes_remaining = outbuf_size - used - NUL_TERMINATOR_LENGTH; + + break; + } + case EILSEQ: + if (save_p) + { + /* Error converting fallback string - fatal + */ + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, + _("Cannot convert fallback “%s” to codeset “%s”"), + insert_str, to_codeset); + have_error = TRUE; + break; + } + else if (p) + { + if (!fallback) + { + gunichar ch = g_utf8_get_char (p); + insert_str = g_strdup_printf (ch < 0x10000 ? "\\u%04x" : "\\U%08x", + ch); + } + else + insert_str = fallback; + + save_p = g_utf8_next_char (p); + save_inbytes = inbytes_remaining - (save_p - p); + p = insert_str; + inbytes_remaining = strlen (p); + break; + } + /* if p is null */ + G_GNUC_FALLTHROUGH; + default: + { + int errsv = errno; + + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_FAILED, + _("Error during conversion: %s"), + g_strerror (errsv)); + } + + have_error = TRUE; + break; + } + } + else + { + if (save_p) + { + if (!fallback) + g_free ((gchar *)insert_str); + p = save_p; + inbytes_remaining = save_inbytes; + save_p = NULL; + } + else if (p) + { + /* call g_iconv with NULL inbuf to cleanup shift state */ + p = NULL; + inbytes_remaining = 0; + } + else + done = TRUE; + } + } + + /* Cleanup + */ + memset (outp, 0, NUL_TERMINATOR_LENGTH); + + close_converter (cd); + + if (bytes_written) + *bytes_written = outp - dest; /* Doesn't include '\0' */ + + g_free (utf8); + + if (have_error) + { + if (save_p && !fallback) + g_free ((gchar *)insert_str); + g_free (dest); + return NULL; + } + else + return dest; +} + +/* + * g_locale_to_utf8 + * + * + */ + +/* + * Validate @string as UTF-8. @len can be negative if @string is + * nul-terminated, or a non-negative value in bytes. If @string ends in an + * incomplete sequence, or contains any illegal sequences or nul codepoints, + * %NULL will be returned and the error set to + * %G_CONVERT_ERROR_ILLEGAL_SEQUENCE. + * On success, @bytes_read and @bytes_written, if provided, will be set to + * the number of bytes in @string up to @len or the terminating nul byte. + * On error, @bytes_read will be set to the byte offset after the last valid + * and non-nul UTF-8 sequence in @string, and @bytes_written will be set to 0. + */ +static gchar * +strdup_len (const gchar *string, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + gsize real_len; + const gchar *end_valid; + + if (!g_utf8_validate (string, len, &end_valid)) + { + if (bytes_read) + *bytes_read = end_valid - string; + if (bytes_written) + *bytes_written = 0; + + g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, + _("Invalid byte sequence in conversion input")); + return NULL; + } + + real_len = end_valid - string; + + if (bytes_read) + *bytes_read = real_len; + if (bytes_written) + *bytes_written = real_len; + + return g_strndup (string, real_len); +} + +typedef enum +{ + CONVERT_CHECK_NO_NULS_IN_INPUT = 1 << 0, + CONVERT_CHECK_NO_NULS_IN_OUTPUT = 1 << 1 +} ConvertCheckFlags; + +/* + * Convert from @string in the encoding identified by @from_codeset, + * returning a string in the encoding identifed by @to_codeset. + * @len can be negative if @string is nul-terminated, or a non-negative + * value in bytes. Flags defined in #ConvertCheckFlags can be set in @flags + * to check the input, the output, or both, for embedded nul bytes. + * On success, @bytes_read, if provided, will be set to the number of bytes + * in @string up to @len or the terminating nul byte, and @bytes_written, if + * provided, will be set to the number of output bytes written into the + * returned buffer, excluding the terminating nul sequence. + * On error, @bytes_read will be set to the byte offset after the last valid + * sequence in @string, and @bytes_written will be set to 0. + */ +static gchar * +convert_checked (const gchar *string, + gssize len, + const gchar *to_codeset, + const gchar *from_codeset, + ConvertCheckFlags flags, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + gchar *out; + gsize outbytes; + + if ((flags & CONVERT_CHECK_NO_NULS_IN_INPUT) && len > 0) + { + const gchar *early_nul = memchr (string, '\0', len); + if (early_nul != NULL) + { + if (bytes_read) + *bytes_read = early_nul - string; + if (bytes_written) + *bytes_written = 0; + + g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, + _("Embedded NUL byte in conversion input")); + return NULL; + } + } + + out = g_convert (string, len, to_codeset, from_codeset, + bytes_read, &outbytes, error); + if (out == NULL) + { + if (bytes_written) + *bytes_written = 0; + return NULL; + } + + if ((flags & CONVERT_CHECK_NO_NULS_IN_OUTPUT) + && memchr (out, '\0', outbytes) != NULL) + { + g_free (out); + if (bytes_written) + *bytes_written = 0; + g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_EMBEDDED_NUL, + _("Embedded NUL byte in conversion output")); + return NULL; + } + + if (bytes_written) + *bytes_written = outbytes; + return out; +} + +/** + * g_locale_to_utf8: + * @opsysstring: (array length=len) (element-type guint8): a string in the + * encoding of the current locale. On Windows + * this means the system codepage. + * @len: the length of the string, or -1 if the string is + * nul-terminated (Note that some encodings may allow nul + * bytes to occur inside strings. In that case, using -1 + * for the @len parameter is unsafe) + * @bytes_read: (out) (optional): location to store the number of bytes in the + * input string that were successfully converted, or %NULL. + * Even if the conversion was successful, this may be + * less than @len if there were partial characters + * at the end of the input. If the error + * %G_CONVERT_ERROR_ILLEGAL_SEQUENCE occurs, the value + * stored will be the byte offset after the last valid + * input sequence. + * @bytes_written: (out) (optional): the number of bytes stored in the output + * buffer (not including the terminating nul). + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts a string which is in the encoding used for strings by + * the C runtime (usually the same as that used by the operating + * system) in the [current locale][setlocale] into a UTF-8 string. + * + * If the source encoding is not UTF-8 and the conversion output contains a + * nul character, the error %G_CONVERT_ERROR_EMBEDDED_NUL is set and the + * function returns %NULL. + * If the source encoding is UTF-8, an embedded nul character is treated with + * the %G_CONVERT_ERROR_ILLEGAL_SEQUENCE error for backward compatibility with + * earlier versions of this library. Use g_convert() to produce output that + * may contain embedded nul characters. + * + * Returns: (type utf8): The converted string, or %NULL on an error. + **/ +gchar * +g_locale_to_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + const char *charset; + + if (g_get_charset (&charset)) + return strdup_len (opsysstring, len, bytes_read, bytes_written, error); + else + return convert_checked (opsysstring, len, "UTF-8", charset, + CONVERT_CHECK_NO_NULS_IN_OUTPUT, + bytes_read, bytes_written, error); +} + +/* + * Do the exact same as g_locale_to_utf8 except that the charset would + * be retrieved from _g_get_time_charset (which uses LC_TIME) + * + * Returns: The converted string, or %NULL on an error. + */ +gchar * +_g_time_locale_to_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + const char *charset; + + if (_g_get_time_charset (&charset)) + return strdup_len (opsysstring, len, bytes_read, bytes_written, error); + else + return convert_checked (opsysstring, len, "UTF-8", charset, + CONVERT_CHECK_NO_NULS_IN_OUTPUT, + bytes_read, bytes_written, error); +} + +/* + * Do the exact same as g_locale_to_utf8 except that the charset would + * be retrieved from _g_get_ctype_charset (which uses LC_CTYPE) + * + * Returns: The converted string, or %NULL on an error. + */ +gchar * +_g_ctype_locale_to_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + const char *charset; + + if (_g_get_ctype_charset (&charset)) + return strdup_len (opsysstring, len, bytes_read, bytes_written, error); + else + return convert_checked (opsysstring, len, "UTF-8", charset, + CONVERT_CHECK_NO_NULS_IN_OUTPUT, + bytes_read, bytes_written, error); +} + +/** + * g_locale_from_utf8: + * @utf8string: a UTF-8 encoded string + * @len: the length of the string, or -1 if the string is + * nul-terminated. + * @bytes_read: (out) (optional): location to store the number of bytes in the + * input string that were successfully converted, or %NULL. + * Even if the conversion was successful, this may be + * less than @len if there were partial characters + * at the end of the input. If the error + * %G_CONVERT_ERROR_ILLEGAL_SEQUENCE occurs, the value + * stored will be the byte offset after the last valid + * input sequence. + * @bytes_written: (out) (optional): the number of bytes stored in the output + * buffer (not including the terminating nul). + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts a string from UTF-8 to the encoding used for strings by + * the C runtime (usually the same as that used by the operating + * system) in the [current locale][setlocale]. On Windows this means + * the system codepage. + * + * The input string shall not contain nul characters even if the @len + * argument is positive. A nul character found inside the string will result + * in error %G_CONVERT_ERROR_ILLEGAL_SEQUENCE. Use g_convert() to convert + * input that may contain embedded nul characters. + * + * Returns: (array length=bytes_written) (element-type guint8) (transfer full): + * A newly-allocated buffer containing the converted string, + * or %NULL on an error, and error will be set. + **/ +gchar * +g_locale_from_utf8 (const gchar *utf8string, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + const gchar *charset; + + if (g_get_charset (&charset)) + return strdup_len (utf8string, len, bytes_read, bytes_written, error); + else + return convert_checked (utf8string, len, charset, "UTF-8", + CONVERT_CHECK_NO_NULS_IN_INPUT, + bytes_read, bytes_written, error); +} + +#ifndef G_PLATFORM_WIN32 + +typedef struct _GFilenameCharsetCache GFilenameCharsetCache; + +struct _GFilenameCharsetCache { + gboolean is_utf8; + gchar *charset; + gchar **filename_charsets; +}; + +static void +filename_charset_cache_free (gpointer data) +{ + GFilenameCharsetCache *cache = data; + g_free (cache->charset); + g_strfreev (cache->filename_charsets); + g_free (cache); +} + +/** + * g_get_filename_charsets: + * @filename_charsets: (out) (transfer none) (array zero-terminated=1): + * return location for the %NULL-terminated list of encoding names + * + * Determines the preferred character sets used for filenames. + * The first character set from the @charsets is the filename encoding, the + * subsequent character sets are used when trying to generate a displayable + * representation of a filename, see g_filename_display_name(). + * + * On Unix, the character sets are determined by consulting the + * environment variables `G_FILENAME_ENCODING` and `G_BROKEN_FILENAMES`. + * On Windows, the character set used in the GLib API is always UTF-8 + * and said environment variables have no effect. + * + * `G_FILENAME_ENCODING` may be set to a comma-separated list of + * character set names. The special token "\@locale" is taken + * to mean the character set for the [current locale][setlocale]. + * If `G_FILENAME_ENCODING` is not set, but `G_BROKEN_FILENAMES` is, + * the character set of the current locale is taken as the filename + * encoding. If neither environment variable is set, UTF-8 is taken + * as the filename encoding, but the character set of the current locale + * is also put in the list of encodings. + * + * The returned @charsets belong to GLib and must not be freed. + * + * Note that on Unix, regardless of the locale character set or + * `G_FILENAME_ENCODING` value, the actual file names present + * on a system might be in any random encoding or just gibberish. + * + * Returns: %TRUE if the filename encoding is UTF-8. + * + * Since: 2.6 + */ +gboolean +g_get_filename_charsets (const gchar ***filename_charsets) +{ + static GPrivate cache_private = G_PRIVATE_INIT (filename_charset_cache_free); + GFilenameCharsetCache *cache = g_private_get (&cache_private); + const gchar *charset; + + if (!cache) + cache = g_private_set_alloc0 (&cache_private, sizeof (GFilenameCharsetCache)); + + g_get_charset (&charset); + + if (!(cache->charset && strcmp (cache->charset, charset) == 0)) + { + const gchar *new_charset; + const gchar *p; + gint i; + + g_free (cache->charset); + g_strfreev (cache->filename_charsets); + cache->charset = g_strdup (charset); + + p = g_getenv ("G_FILENAME_ENCODING"); + if (p != NULL && p[0] != '\0') + { + cache->filename_charsets = g_strsplit (p, ",", 0); + cache->is_utf8 = (strcmp (cache->filename_charsets[0], "UTF-8") == 0); + + for (i = 0; cache->filename_charsets[i]; i++) + { + if (strcmp ("@locale", cache->filename_charsets[i]) == 0) + { + g_get_charset (&new_charset); + g_free (cache->filename_charsets[i]); + cache->filename_charsets[i] = g_strdup (new_charset); + } + } + } + else if (g_getenv ("G_BROKEN_FILENAMES") != NULL) + { + cache->filename_charsets = g_new0 (gchar *, 2); + cache->is_utf8 = g_get_charset (&new_charset); + cache->filename_charsets[0] = g_strdup (new_charset); + } + else + { + cache->filename_charsets = g_new0 (gchar *, 3); + cache->is_utf8 = TRUE; + cache->filename_charsets[0] = g_strdup ("UTF-8"); + if (!g_get_charset (&new_charset)) + cache->filename_charsets[1] = g_strdup (new_charset); + } + } + + if (filename_charsets) + *filename_charsets = (const gchar **)cache->filename_charsets; + + return cache->is_utf8; +} + +#else /* G_PLATFORM_WIN32 */ + +gboolean +g_get_filename_charsets (const gchar ***filename_charsets) +{ + static const gchar *charsets[] = { + "UTF-8", + NULL + }; + +#ifdef G_OS_WIN32 + /* On Windows GLib pretends that the filename charset is UTF-8 */ + if (filename_charsets) + *filename_charsets = charsets; + + return TRUE; +#else + gboolean result; + + /* Cygwin works like before */ + result = g_get_charset (&(charsets[0])); + + if (filename_charsets) + *filename_charsets = charsets; + + return result; +#endif +} + +#endif /* G_PLATFORM_WIN32 */ + +static gboolean +get_filename_charset (const gchar **filename_charset) +{ + const gchar **charsets; + gboolean is_utf8; + + is_utf8 = g_get_filename_charsets (&charsets); + + if (filename_charset) + *filename_charset = charsets[0]; + + return is_utf8; +} + +/** + * g_filename_to_utf8: + * @opsysstring: (type filename): a string in the encoding for filenames + * @len: the length of the string, or -1 if the string is + * nul-terminated (Note that some encodings may allow nul + * bytes to occur inside strings. In that case, using -1 + * for the @len parameter is unsafe) + * @bytes_read: (out) (optional): location to store the number of bytes in the + * input string that were successfully converted, or %NULL. + * Even if the conversion was successful, this may be + * less than @len if there were partial characters + * at the end of the input. If the error + * %G_CONVERT_ERROR_ILLEGAL_SEQUENCE occurs, the value + * stored will be the byte offset after the last valid + * input sequence. + * @bytes_written: (out) (optional): the number of bytes stored in the output + * buffer (not including the terminating nul). + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts a string which is in the encoding used by GLib for + * filenames into a UTF-8 string. Note that on Windows GLib uses UTF-8 + * for filenames; on other platforms, this function indirectly depends on + * the [current locale][setlocale]. + * + * The input string shall not contain nul characters even if the @len + * argument is positive. A nul character found inside the string will result + * in error %G_CONVERT_ERROR_ILLEGAL_SEQUENCE. + * If the source encoding is not UTF-8 and the conversion output contains a + * nul character, the error %G_CONVERT_ERROR_EMBEDDED_NUL is set and the + * function returns %NULL. Use g_convert() to produce output that + * may contain embedded nul characters. + * + * Returns: (type utf8): The converted string, or %NULL on an error. + **/ +gchar* +g_filename_to_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + const gchar *charset; + + g_return_val_if_fail (opsysstring != NULL, NULL); + + if (get_filename_charset (&charset)) + return strdup_len (opsysstring, len, bytes_read, bytes_written, error); + else + return convert_checked (opsysstring, len, "UTF-8", charset, + CONVERT_CHECK_NO_NULS_IN_INPUT | + CONVERT_CHECK_NO_NULS_IN_OUTPUT, + bytes_read, bytes_written, error); +} + +/** + * g_filename_from_utf8: + * @utf8string: (type utf8): a UTF-8 encoded string. + * @len: the length of the string, or -1 if the string is + * nul-terminated. + * @bytes_read: (out) (optional): location to store the number of bytes in + * the input string that were successfully converted, or %NULL. + * Even if the conversion was successful, this may be + * less than @len if there were partial characters + * at the end of the input. If the error + * %G_CONVERT_ERROR_ILLEGAL_SEQUENCE occurs, the value + * stored will be the byte offset after the last valid + * input sequence. + * @bytes_written: (out) (optional): the number of bytes stored in + * the output buffer (not including the terminating nul). + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts a string from UTF-8 to the encoding GLib uses for + * filenames. Note that on Windows GLib uses UTF-8 for filenames; + * on other platforms, this function indirectly depends on the + * [current locale][setlocale]. + * + * The input string shall not contain nul characters even if the @len + * argument is positive. A nul character found inside the string will result + * in error %G_CONVERT_ERROR_ILLEGAL_SEQUENCE. If the filename encoding is + * not UTF-8 and the conversion output contains a nul character, the error + * %G_CONVERT_ERROR_EMBEDDED_NUL is set and the function returns %NULL. + * + * Returns: (type filename): + * The converted string, or %NULL on an error. + **/ +gchar* +g_filename_from_utf8 (const gchar *utf8string, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + const gchar *charset; + + if (get_filename_charset (&charset)) + return strdup_len (utf8string, len, bytes_read, bytes_written, error); + else + return convert_checked (utf8string, len, charset, "UTF-8", + CONVERT_CHECK_NO_NULS_IN_INPUT | + CONVERT_CHECK_NO_NULS_IN_OUTPUT, + bytes_read, bytes_written, error); +} + +/* Test of haystack has the needle prefix, comparing case + * insensitive. haystack may be UTF-8, but needle must + * contain only ascii. */ +static gboolean +has_case_prefix (const gchar *haystack, const gchar *needle) +{ + const gchar *h, *n; + + /* Eat one character at a time. */ + h = haystack; + n = needle; + + while (*n && *h && + g_ascii_tolower (*n) == g_ascii_tolower (*h)) + { + n++; + h++; + } + + return *n == '\0'; +} + +typedef enum { + UNSAFE_ALL = 0x1, /* Escape all unsafe characters */ + UNSAFE_ALLOW_PLUS = 0x2, /* Allows '+' */ + UNSAFE_PATH = 0x8, /* Allows '/', '&', '=', ':', '@', '+', '$' and ',' */ + UNSAFE_HOST = 0x10, /* Allows '/' and ':' and '@' */ + UNSAFE_SLASHES = 0x20 /* Allows all characters except for '/' and '%' */ +} UnsafeCharacterSet; + +static const guchar acceptable[96] = { + /* A table of the ASCII chars from space (32) to DEL (127) */ + /* ! " # $ % & ' ( ) * + , - . / */ + 0x00,0x3F,0x20,0x20,0x28,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x2A,0x28,0x3F,0x3F,0x1C, + /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x20, + /* @ A B C D E F G H I J K L M N O */ + 0x38,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, + /* P Q R S T U V W X Y Z [ \ ] ^ _ */ + 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F, + /* ` a b c d e f g h i j k l m n o */ + 0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, + /* p q r s t u v w x y z { | } ~ DEL */ + 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20 +}; + +static const gchar hex[] = "0123456789ABCDEF"; + +/* Note: This escape function works on file: URIs, but if you want to + * escape something else, please read RFC-2396 */ +static gchar * +g_escape_uri_string (const gchar *string, + UnsafeCharacterSet mask) +{ +#define ACCEPTABLE(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask)) + + const gchar *p; + gchar *q; + gchar *result; + int c; + gint unacceptable; + UnsafeCharacterSet use_mask; + + g_return_val_if_fail (mask == UNSAFE_ALL + || mask == UNSAFE_ALLOW_PLUS + || mask == UNSAFE_PATH + || mask == UNSAFE_HOST + || mask == UNSAFE_SLASHES, NULL); + + unacceptable = 0; + use_mask = mask; + for (p = string; *p != '\0'; p++) + { + c = (guchar) *p; + if (!ACCEPTABLE (c)) + unacceptable++; + } + + result = g_malloc (p - string + unacceptable * 2 + 1); + + use_mask = mask; + for (q = result, p = string; *p != '\0'; p++) + { + c = (guchar) *p; + + if (!ACCEPTABLE (c)) + { + *q++ = '%'; /* means hex coming */ + *q++ = hex[c >> 4]; + *q++ = hex[c & 15]; + } + else + *q++ = *p; + } + + *q = '\0'; + + return result; +} + + +static gchar * +g_escape_file_uri (const gchar *hostname, + const gchar *pathname) +{ + char *escaped_hostname = NULL; + char *escaped_path; + char *res; + +#ifdef G_OS_WIN32 + char *p, *backslash; + + /* Turn backslashes into forward slashes. That's what Netscape + * does, and they are actually more or less equivalent in Windows. + */ + + pathname = g_strdup (pathname); + p = (char *) pathname; + + while ((backslash = strchr (p, '\\')) != NULL) + { + *backslash = '/'; + p = backslash + 1; + } +#endif + + if (hostname && *hostname != '\0') + { + escaped_hostname = g_escape_uri_string (hostname, UNSAFE_HOST); + } + + escaped_path = g_escape_uri_string (pathname, UNSAFE_PATH); + + res = g_strconcat ("file://", + (escaped_hostname) ? escaped_hostname : "", + (*escaped_path != '/') ? "/" : "", + escaped_path, + NULL); + +#ifdef G_OS_WIN32 + g_free ((char *) pathname); +#endif + + g_free (escaped_hostname); + g_free (escaped_path); + + return res; +} + +static int +unescape_character (const char *scanner) +{ + int first_digit; + int second_digit; + + first_digit = g_ascii_xdigit_value (scanner[0]); + if (first_digit < 0) + return -1; + + second_digit = g_ascii_xdigit_value (scanner[1]); + if (second_digit < 0) + return -1; + + return (first_digit << 4) | second_digit; +} + +static gchar * +g_unescape_uri_string (const char *escaped, + int len, + const char *illegal_escaped_characters, + gboolean ascii_must_not_be_escaped) +{ + const gchar *in, *in_end; + gchar *out, *result; + int c; + + if (escaped == NULL) + return NULL; + + if (len < 0) + len = strlen (escaped); + + result = g_malloc (len + 1); + + out = result; + for (in = escaped, in_end = escaped + len; in < in_end; in++) + { + c = *in; + + if (c == '%') + { + /* catch partial escape sequences past the end of the substring */ + if (in + 3 > in_end) + break; + + c = unescape_character (in + 1); + + /* catch bad escape sequences and NUL characters */ + if (c <= 0) + break; + + /* catch escaped ASCII */ + if (ascii_must_not_be_escaped && c <= 0x7F) + break; + + /* catch other illegal escaped characters */ + if (strchr (illegal_escaped_characters, c) != NULL) + break; + + in += 2; + } + + *out++ = c; + } + + g_assert (out - result <= len); + *out = '\0'; + + if (in != in_end) + { + g_free (result); + return NULL; + } + + return result; +} + +static gboolean +is_asciialphanum (gunichar c) +{ + return c <= 0x7F && g_ascii_isalnum (c); +} + +static gboolean +is_asciialpha (gunichar c) +{ + return c <= 0x7F && g_ascii_isalpha (c); +} + +/* allows an empty string */ +static gboolean +hostname_validate (const char *hostname) +{ + const char *p; + gunichar c, first_char, last_char; + + p = hostname; + if (*p == '\0') + return TRUE; + do + { + /* read in a label */ + c = g_utf8_get_char (p); + p = g_utf8_next_char (p); + if (!is_asciialphanum (c)) + return FALSE; + first_char = c; + do + { + last_char = c; + c = g_utf8_get_char (p); + p = g_utf8_next_char (p); + } + while (is_asciialphanum (c) || c == '-'); + if (last_char == '-') + return FALSE; + + /* if that was the last label, check that it was a toplabel */ + if (c == '\0' || (c == '.' && *p == '\0')) + return is_asciialpha (first_char); + } + while (c == '.'); + return FALSE; +} + +/** + * g_filename_from_uri: + * @uri: a uri describing a filename (escaped, encoded in ASCII). + * @hostname: (out) (optional) (nullable): Location to store hostname for the URI. + * If there is no hostname in the URI, %NULL will be + * stored in this location. + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts an escaped ASCII-encoded URI to a local filename in the + * encoding used for filenames. + * + * Returns: (type filename): a newly-allocated string holding + * the resulting filename, or %NULL on an error. + **/ +gchar * +g_filename_from_uri (const gchar *uri, + gchar **hostname, + GError **error) +{ + const char *path_part; + const char *host_part; + char *unescaped_hostname; + char *result; + char *filename; + int offs; +#ifdef G_OS_WIN32 + char *p, *slash; +#endif + + if (hostname) + *hostname = NULL; + + if (!has_case_prefix (uri, "file:/")) + { + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI, + _("The URI “%s” is not an absolute URI using the “file” scheme"), + uri); + return NULL; + } + + path_part = uri + strlen ("file:"); + + if (strchr (path_part, '#') != NULL) + { + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI, + _("The local file URI “%s” may not include a “#”"), + uri); + return NULL; + } + + if (has_case_prefix (path_part, "///")) + path_part += 2; + else if (has_case_prefix (path_part, "//")) + { + path_part += 2; + host_part = path_part; + + path_part = strchr (path_part, '/'); + + if (path_part == NULL) + { + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI, + _("The URI “%s” is invalid"), + uri); + return NULL; + } + + unescaped_hostname = g_unescape_uri_string (host_part, path_part - host_part, "", TRUE); + + if (unescaped_hostname == NULL || + !hostname_validate (unescaped_hostname)) + { + g_free (unescaped_hostname); + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI, + _("The hostname of the URI “%s” is invalid"), + uri); + return NULL; + } + + if (hostname) + *hostname = unescaped_hostname; + else + g_free (unescaped_hostname); + } + + filename = g_unescape_uri_string (path_part, -1, "/", FALSE); + + if (filename == NULL) + { + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI, + _("The URI “%s” contains invalidly escaped characters"), + uri); + return NULL; + } + + offs = 0; +#ifdef G_OS_WIN32 + /* Drop localhost */ + if (hostname && *hostname != NULL && + g_ascii_strcasecmp (*hostname, "localhost") == 0) + { + g_free (*hostname); + *hostname = NULL; + } + + /* Turn slashes into backslashes, because that's the canonical spelling */ + p = filename; + while ((slash = strchr (p, '/')) != NULL) + { + *slash = '\\'; + p = slash + 1; + } + + /* Windows URIs with a drive letter can be like "file://host/c:/foo" + * or "file://host/c|/foo" (some Netscape versions). In those cases, start + * the filename from the drive letter. + */ + if (g_ascii_isalpha (filename[1])) + { + if (filename[2] == ':') + offs = 1; + else if (filename[2] == '|') + { + filename[2] = ':'; + offs = 1; + } + } +#endif + + result = g_strdup (filename + offs); + g_free (filename); + + return result; +} + +/** + * g_filename_to_uri: + * @filename: (type filename): an absolute filename specified in the GLib file + * name encoding, which is the on-disk file name bytes on Unix, and UTF-8 + * on Windows + * @hostname: (nullable): A UTF-8 encoded hostname, or %NULL for none. + * @error: location to store the error occurring, or %NULL to ignore + * errors. Any of the errors in #GConvertError may occur. + * + * Converts an absolute filename to an escaped ASCII-encoded URI, with the path + * component following Section 3.3. of RFC 2396. + * + * Returns: a newly-allocated string holding the resulting + * URI, or %NULL on an error. + **/ +gchar * +g_filename_to_uri (const gchar *filename, + const gchar *hostname, + GError **error) +{ + char *escaped_uri; + + g_return_val_if_fail (filename != NULL, NULL); + + if (!g_path_is_absolute (filename)) + { + g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_NOT_ABSOLUTE_PATH, + _("The pathname “%s” is not an absolute path"), + filename); + return NULL; + } + + if (hostname && + !(g_utf8_validate (hostname, -1, NULL) + && hostname_validate (hostname))) + { + g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, + _("Invalid hostname")); + return NULL; + } + +#ifdef G_OS_WIN32 + /* Don't use localhost unnecessarily */ + if (hostname && g_ascii_strcasecmp (hostname, "localhost") == 0) + hostname = NULL; +#endif + + escaped_uri = g_escape_file_uri (hostname, filename); + + return escaped_uri; +} + +/** + * g_uri_list_extract_uris: + * @uri_list: an URI list + * + * Splits an URI list conforming to the text/uri-list + * mime type defined in RFC 2483 into individual URIs, + * discarding any comments. The URIs are not validated. + * + * Returns: (transfer full): a newly allocated %NULL-terminated list + * of strings holding the individual URIs. The array should be freed + * with g_strfreev(). + * + * Since: 2.6 + */ +gchar ** +g_uri_list_extract_uris (const gchar *uri_list) +{ + GPtrArray *uris; + const gchar *p, *q; + + uris = g_ptr_array_new (); + + p = uri_list; + + /* We don't actually try to validate the URI according to RFC + * 2396, or even check for allowed characters - we just ignore + * comments and trim whitespace off the ends. We also + * allow LF delimination as well as the specified CRLF. + * + * We do allow comments like specified in RFC 2483. + */ + while (p) + { + if (*p != '#') + { + while (g_ascii_isspace (*p)) + p++; + + q = p; + while (*q && (*q != '\n') && (*q != '\r')) + q++; + + if (q > p) + { + q--; + while (q > p && g_ascii_isspace (*q)) + q--; + + if (q > p) + g_ptr_array_add (uris, g_strndup (p, q - p + 1)); + } + } + p = strchr (p, '\n'); + if (p) + p++; + } + + g_ptr_array_add (uris, NULL); + + return (gchar **) g_ptr_array_free (uris, FALSE); +} + +/** + * g_filename_display_basename: + * @filename: (type filename): an absolute pathname in the + * GLib file name encoding + * + * Returns the display basename for the particular filename, guaranteed + * to be valid UTF-8. The display name might not be identical to the filename, + * for instance there might be problems converting it to UTF-8, and some files + * can be translated in the display. + * + * If GLib cannot make sense of the encoding of @filename, as a last resort it + * replaces unknown characters with U+FFFD, the Unicode replacement character. + * You can search the result for the UTF-8 encoding of this character (which is + * "\357\277\275" in octal notation) to find out if @filename was in an invalid + * encoding. + * + * You must pass the whole absolute pathname to this functions so that + * translation of well known locations can be done. + * + * This function is preferred over g_filename_display_name() if you know the + * whole path, as it allows translation. + * + * Returns: a newly allocated string containing + * a rendition of the basename of the filename in valid UTF-8 + * + * Since: 2.6 + **/ +gchar * +g_filename_display_basename (const gchar *filename) +{ + char *basename; + char *display_name; + + g_return_val_if_fail (filename != NULL, NULL); + + basename = g_path_get_basename (filename); + display_name = g_filename_display_name (basename); + g_free (basename); + return display_name; +} + +/** + * g_filename_display_name: + * @filename: (type filename): a pathname hopefully in the + * GLib file name encoding + * + * Converts a filename into a valid UTF-8 string. The conversion is + * not necessarily reversible, so you should keep the original around + * and use the return value of this function only for display purposes. + * Unlike g_filename_to_utf8(), the result is guaranteed to be non-%NULL + * even if the filename actually isn't in the GLib file name encoding. + * + * If GLib cannot make sense of the encoding of @filename, as a last resort it + * replaces unknown characters with U+FFFD, the Unicode replacement character. + * You can search the result for the UTF-8 encoding of this character (which is + * "\357\277\275" in octal notation) to find out if @filename was in an invalid + * encoding. + * + * If you know the whole pathname of the file you should use + * g_filename_display_basename(), since that allows location-based + * translation of filenames. + * + * Returns: a newly allocated string containing + * a rendition of the filename in valid UTF-8 + * + * Since: 2.6 + **/ +gchar * +g_filename_display_name (const gchar *filename) +{ + gint i; + const gchar **charsets; + gchar *display_name = NULL; + gboolean is_utf8; + + is_utf8 = g_get_filename_charsets (&charsets); + + if (is_utf8) + { + if (g_utf8_validate (filename, -1, NULL)) + display_name = g_strdup (filename); + } + + if (!display_name) + { + /* Try to convert from the filename charsets to UTF-8. + * Skip the first charset if it is UTF-8. + */ + for (i = is_utf8 ? 1 : 0; charsets[i]; i++) + { + display_name = g_convert (filename, -1, "UTF-8", charsets[i], + NULL, NULL, NULL); + + if (display_name) + break; + } + } + + /* if all conversions failed, we replace invalid UTF-8 + * by a question mark + */ + if (!display_name) + display_name = g_utf8_make_valid (filename, -1); + + return display_name; +} + +#ifdef G_OS_WIN32 + +/* Binary compatibility versions. Not for newly compiled code. */ + +_GLIB_EXTERN gchar *g_filename_to_utf8_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; +_GLIB_EXTERN gchar *g_filename_from_utf8_utf8 (const gchar *utf8string, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; +_GLIB_EXTERN gchar *g_filename_from_uri_utf8 (const gchar *uri, + gchar **hostname, + GError **error) G_GNUC_MALLOC; +_GLIB_EXTERN gchar *g_filename_to_uri_utf8 (const gchar *filename, + const gchar *hostname, + GError **error) G_GNUC_MALLOC; + +gchar * +g_filename_to_utf8_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + return g_filename_to_utf8 (opsysstring, len, bytes_read, bytes_written, error); +} + +gchar * +g_filename_from_utf8_utf8 (const gchar *utf8string, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + return g_filename_from_utf8 (utf8string, len, bytes_read, bytes_written, error); +} + +gchar * +g_filename_from_uri_utf8 (const gchar *uri, + gchar **hostname, + GError **error) +{ + return g_filename_from_uri (uri, hostname, error); +} + +gchar * +g_filename_to_uri_utf8 (const gchar *filename, + const gchar *hostname, + GError **error) +{ + return g_filename_to_uri (filename, hostname, error); +} + +#endif diff --git a/glib/gconvert.h b/glib/gconvert.h new file mode 100644 index 0000000..be58ecf --- /dev/null +++ b/glib/gconvert.h @@ -0,0 +1,177 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_CONVERT_H__ +#define __G_CONVERT_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +/** + * GConvertError: + * @G_CONVERT_ERROR_NO_CONVERSION: Conversion between the requested character + * sets is not supported. + * @G_CONVERT_ERROR_ILLEGAL_SEQUENCE: Invalid byte sequence in conversion input; + * or the character sequence could not be represented in the target + * character set. + * @G_CONVERT_ERROR_FAILED: Conversion failed for some reason. + * @G_CONVERT_ERROR_PARTIAL_INPUT: Partial character sequence at end of input. + * @G_CONVERT_ERROR_BAD_URI: URI is invalid. + * @G_CONVERT_ERROR_NOT_ABSOLUTE_PATH: Pathname is not an absolute path. + * @G_CONVERT_ERROR_NO_MEMORY: No memory available. Since: 2.40 + * @G_CONVERT_ERROR_EMBEDDED_NUL: An embedded NUL character is present in + * conversion output where a NUL-terminated string is expected. + * Since: 2.56 + * + * Error codes returned by character set conversion routines. + */ +typedef enum +{ + G_CONVERT_ERROR_NO_CONVERSION, + G_CONVERT_ERROR_ILLEGAL_SEQUENCE, + G_CONVERT_ERROR_FAILED, + G_CONVERT_ERROR_PARTIAL_INPUT, + G_CONVERT_ERROR_BAD_URI, + G_CONVERT_ERROR_NOT_ABSOLUTE_PATH, + G_CONVERT_ERROR_NO_MEMORY, + G_CONVERT_ERROR_EMBEDDED_NUL +} GConvertError; + +/** + * G_CONVERT_ERROR: + * + * Error domain for character set conversions. Errors in this domain will + * be from the #GConvertError enumeration. See #GError for information on + * error domains. + */ +#define G_CONVERT_ERROR g_convert_error_quark() +GLIB_AVAILABLE_IN_ALL +GQuark g_convert_error_quark (void); + +/** + * GIConv: (skip) + * + * The GIConv struct wraps an iconv() conversion descriptor. It contains + * private data and should only be accessed using the following functions. + */ +typedef struct _GIConv *GIConv; + +GLIB_AVAILABLE_IN_ALL +GIConv g_iconv_open (const gchar *to_codeset, + const gchar *from_codeset); +GLIB_AVAILABLE_IN_ALL +gsize g_iconv (GIConv converter, + gchar **inbuf, + gsize *inbytes_left, + gchar **outbuf, + gsize *outbytes_left); +GLIB_AVAILABLE_IN_ALL +gint g_iconv_close (GIConv converter); + + +GLIB_AVAILABLE_IN_ALL +gchar* g_convert (const gchar *str, + gssize len, + const gchar *to_codeset, + const gchar *from_codeset, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +gchar* g_convert_with_iconv (const gchar *str, + gssize len, + GIConv converter, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +gchar* g_convert_with_fallback (const gchar *str, + gssize len, + const gchar *to_codeset, + const gchar *from_codeset, + const gchar *fallback, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; + + +/* Convert between libc's idea of strings and UTF-8. + */ +GLIB_AVAILABLE_IN_ALL +gchar* g_locale_to_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +gchar* g_locale_from_utf8 (const gchar *utf8string, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; + +/* Convert between the operating system (or C runtime) + * representation of file names and UTF-8. + */ +GLIB_AVAILABLE_IN_ALL +gchar* g_filename_to_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +gchar* g_filename_from_utf8 (const gchar *utf8string, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; + +GLIB_AVAILABLE_IN_ALL +gchar *g_filename_from_uri (const gchar *uri, + gchar **hostname, + GError **error) G_GNUC_MALLOC; + +GLIB_AVAILABLE_IN_ALL +gchar *g_filename_to_uri (const gchar *filename, + const gchar *hostname, + GError **error) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +gchar *g_filename_display_name (const gchar *filename) G_GNUC_MALLOC; +GLIB_AVAILABLE_IN_ALL +gboolean g_get_filename_charsets (const gchar ***filename_charsets); + +GLIB_AVAILABLE_IN_ALL +gchar *g_filename_display_basename (const gchar *filename) G_GNUC_MALLOC; + +GLIB_AVAILABLE_IN_ALL +gchar **g_uri_list_extract_uris (const gchar *uri_list); + +G_END_DECLS + +#endif /* __G_CONVERT_H__ */ diff --git a/glib/gconvertprivate.h b/glib/gconvertprivate.h new file mode 100644 index 0000000..5bdc87f --- /dev/null +++ b/glib/gconvertprivate.h @@ -0,0 +1,40 @@ +/* gconvertprivate.h - Private GLib gconvert functions + * + * Copyright 2020 Frederic Martinsons + * + * 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_CONVERTPRIVATE_H__ +#define __G_CONVERTPRIVATE_H__ + +G_BEGIN_DECLS + +#include "glib.h" + +gchar *_g_time_locale_to_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; + +gchar *_g_ctype_locale_to_utf8 (const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) G_GNUC_MALLOC; + +G_END_DECLS + +#endif /* __G_CONVERTPRIVATE_H__ */ diff --git a/glib/gdataset.c b/glib/gdataset.c new file mode 100644 index 0000000..796d203 --- /dev/null +++ b/glib/gdataset.c @@ -0,0 +1,1251 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * gdataset.c: Generic dataset mechanism, similar to GtkObject data. + * Copyright (C) 1998 Tim Janik + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * MT safe ; except for g_data*_foreach() + */ + +#include "config.h" + +#include + +#include "gdataset.h" +#include "gbitlock.h" + +#include "gslice.h" +#include "gdatasetprivate.h" +#include "ghash.h" +#include "gquark.h" +#include "gstrfuncs.h" +#include "gtestutils.h" +#include "gthread.h" +#include "glib_trace.h" + +/** + * SECTION:datasets + * @title: Datasets + * @short_description: associate groups of data elements with + * particular memory locations + * + * Datasets associate groups of data elements with particular memory + * locations. These are useful if you need to associate data with a + * structure returned from an external library. Since you cannot modify + * the structure, you use its location in memory as the key into a + * dataset, where you can associate any number of data elements with it. + * + * There are two forms of most of the dataset functions. The first form + * uses strings to identify the data elements associated with a + * location. The second form uses #GQuark identifiers, which are + * created with a call to g_quark_from_string() or + * g_quark_from_static_string(). The second form is quicker, since it + * does not require looking up the string in the hash table of #GQuark + * identifiers. + * + * There is no function to create a dataset. It is automatically + * created as soon as you add elements to it. + * + * To add data elements to a dataset use g_dataset_id_set_data(), + * g_dataset_id_set_data_full(), g_dataset_set_data() and + * g_dataset_set_data_full(). + * + * To get data elements from a dataset use g_dataset_id_get_data() and + * g_dataset_get_data(). + * + * To iterate over all data elements in a dataset use + * g_dataset_foreach() (not thread-safe). + * + * To remove data elements from a dataset use + * g_dataset_id_remove_data() and g_dataset_remove_data(). + * + * To destroy a dataset, use g_dataset_destroy(). + **/ + +/** + * SECTION:datalist + * @title: Keyed Data Lists + * @short_description: lists of data elements which are accessible by a + * string or GQuark identifier + * + * Keyed data lists provide lists of arbitrary data elements which can + * be accessed either with a string or with a #GQuark corresponding to + * the string. + * + * The #GQuark methods are quicker, since the strings have to be + * converted to #GQuarks anyway. + * + * Data lists are used for associating arbitrary data with #GObjects, + * using g_object_set_data() and related functions. + * + * To create a datalist, use g_datalist_init(). + * + * To add data elements to a datalist use g_datalist_id_set_data(), + * g_datalist_id_set_data_full(), g_datalist_set_data() and + * g_datalist_set_data_full(). + * + * To get data elements from a datalist use g_datalist_id_get_data() + * and g_datalist_get_data(). + * + * To iterate over all data elements in a datalist use + * g_datalist_foreach() (not thread-safe). + * + * To remove data elements from a datalist use + * g_datalist_id_remove_data() and g_datalist_remove_data(). + * + * To remove all data elements from a datalist, use g_datalist_clear(). + **/ + +/** + * GData: + * + * An opaque data structure that represents a keyed data list. + * + * See also: [Keyed data lists][glib-Keyed-Data-Lists]. + **/ + +/** + * GDestroyNotify: + * @data: the data element. + * + * Specifies the type of function which is called when a data element + * is destroyed. It is passed the pointer to the data element and + * should free any memory and resources allocated for it. + **/ + +#define G_DATALIST_FLAGS_MASK_INTERNAL 0x7 + +/* datalist pointer accesses have to be carried out atomically */ +#define G_DATALIST_GET_POINTER(datalist) \ + ((GData*) ((gsize) g_atomic_pointer_get (datalist) & ~(gsize) G_DATALIST_FLAGS_MASK_INTERNAL)) + +#define G_DATALIST_SET_POINTER(datalist, pointer) G_STMT_START { \ + gpointer _oldv, _newv; \ + do { \ + _oldv = g_atomic_pointer_get (datalist); \ + _newv = (gpointer) (((gsize) _oldv & G_DATALIST_FLAGS_MASK_INTERNAL) | (gsize) pointer); \ + } while (!g_atomic_pointer_compare_and_exchange ((void**) datalist, _oldv, _newv)); \ +} G_STMT_END + +/* --- structures --- */ +typedef struct { + GQuark key; + gpointer data; + GDestroyNotify destroy; +} GDataElt; + +typedef struct _GDataset GDataset; +struct _GData +{ + guint32 len; /* Number of elements */ + guint32 alloc; /* Number of allocated elements */ + GDataElt data[1]; /* Flexible array */ +}; + +struct _GDataset +{ + gconstpointer location; + GData *datalist; +}; + + +/* --- prototypes --- */ +static inline GDataset* g_dataset_lookup (gconstpointer dataset_location); +static inline void g_datalist_clear_i (GData **datalist); +static void g_dataset_destroy_internal (GDataset *dataset); +static inline gpointer g_data_set_internal (GData **datalist, + GQuark key_id, + gpointer data, + GDestroyNotify destroy_func, + GDataset *dataset); +static void g_data_initialize (void); + +/* Locking model: + * Each standalone GDataList is protected by a bitlock in the datalist pointer, + * which protects that modification of the non-flags part of the datalist pointer + * and the contents of the datalist. + * + * For GDataSet we have a global lock g_dataset_global that protects + * the global dataset hash and cache, and additionally it protects the + * datalist such that we can avoid to use the bit lock in a few places + * where it is easy. + */ + +/* --- variables --- */ +G_LOCK_DEFINE_STATIC (g_dataset_global); +static GHashTable *g_dataset_location_ht = NULL; +static GDataset *g_dataset_cached = NULL; /* should this be + thread specific? */ + +/* --- functions --- */ + +#define DATALIST_LOCK_BIT 2 + +static void +g_datalist_lock (GData **datalist) +{ + g_pointer_bit_lock ((void **)datalist, DATALIST_LOCK_BIT); +} + +static void +g_datalist_unlock (GData **datalist) +{ + g_pointer_bit_unlock ((void **)datalist, DATALIST_LOCK_BIT); +} + +/* Called with the datalist lock held, or the dataset global + * lock for dataset lists + */ +static void +g_datalist_clear_i (GData **datalist) +{ + GData *data; + guint i; + + data = G_DATALIST_GET_POINTER (datalist); + G_DATALIST_SET_POINTER (datalist, NULL); + + if (data) + { + G_UNLOCK (g_dataset_global); + for (i = 0; i < data->len; i++) + { + if (data->data[i].data && data->data[i].destroy) + data->data[i].destroy (data->data[i].data); + } + G_LOCK (g_dataset_global); + + g_free (data); + } + +} + +/** + * g_datalist_clear: (skip) + * @datalist: a datalist. + * + * Frees all the data elements of the datalist. + * The data elements' destroy functions are called + * if they have been set. + **/ +void +g_datalist_clear (GData **datalist) +{ + GData *data; + guint i; + + g_return_if_fail (datalist != NULL); + + g_datalist_lock (datalist); + + data = G_DATALIST_GET_POINTER (datalist); + G_DATALIST_SET_POINTER (datalist, NULL); + + g_datalist_unlock (datalist); + + if (data) + { + for (i = 0; i < data->len; i++) + { + if (data->data[i].data && data->data[i].destroy) + data->data[i].destroy (data->data[i].data); + } + + g_free (data); + } +} + +/* HOLDS: g_dataset_global_lock */ +static inline GDataset* +g_dataset_lookup (gconstpointer dataset_location) +{ + GDataset *dataset; + + if (g_dataset_cached && g_dataset_cached->location == dataset_location) + return g_dataset_cached; + + dataset = g_hash_table_lookup (g_dataset_location_ht, dataset_location); + if (dataset) + g_dataset_cached = dataset; + + return dataset; +} + +/* HOLDS: g_dataset_global_lock */ +static void +g_dataset_destroy_internal (GDataset *dataset) +{ + gconstpointer dataset_location; + + dataset_location = dataset->location; + while (dataset) + { + if (G_DATALIST_GET_POINTER(&dataset->datalist) == NULL) + { + if (dataset == g_dataset_cached) + g_dataset_cached = NULL; + g_hash_table_remove (g_dataset_location_ht, dataset_location); + g_slice_free (GDataset, dataset); + break; + } + + g_datalist_clear_i (&dataset->datalist); + dataset = g_dataset_lookup (dataset_location); + } +} + +/** + * g_dataset_destroy: + * @dataset_location: (not nullable): the location identifying the dataset. + * + * Destroys the dataset, freeing all memory allocated, and calling any + * destroy functions set for data elements. + */ +void +g_dataset_destroy (gconstpointer dataset_location) +{ + g_return_if_fail (dataset_location != NULL); + + G_LOCK (g_dataset_global); + if (g_dataset_location_ht) + { + GDataset *dataset; + + dataset = g_dataset_lookup (dataset_location); + if (dataset) + g_dataset_destroy_internal (dataset); + } + G_UNLOCK (g_dataset_global); +} + +/* HOLDS: g_dataset_global_lock if dataset != null */ +static inline gpointer +g_data_set_internal (GData **datalist, + GQuark key_id, + gpointer new_data, + GDestroyNotify new_destroy_func, + GDataset *dataset) +{ + GData *d, *old_d; + GDataElt old, *data, *data_last, *data_end; + + g_datalist_lock (datalist); + + d = G_DATALIST_GET_POINTER (datalist); + + if (new_data == NULL) /* remove */ + { + if (d) + { + data = d->data; + data_last = data + d->len - 1; + while (data <= data_last) + { + if (data->key == key_id) + { + old = *data; + if (data != data_last) + *data = *data_last; + d->len--; + + /* We don't bother to shrink, but if all data are now gone + * we at least free the memory + */ + if (d->len == 0) + { + G_DATALIST_SET_POINTER (datalist, NULL); + g_free (d); + /* datalist may be situated in dataset, so must not be + * unlocked after we free it + */ + g_datalist_unlock (datalist); + + /* the dataset destruction *must* be done + * prior to invocation of the data destroy function + */ + if (dataset) + g_dataset_destroy_internal (dataset); + } + else + { + g_datalist_unlock (datalist); + } + + /* We found and removed an old value + * the GData struct *must* already be unlinked + * when invoking the destroy function. + * we use (new_data==NULL && new_destroy_func!=NULL) as + * a special hint combination to "steal" + * data without destroy notification + */ + if (old.destroy && !new_destroy_func) + { + if (dataset) + G_UNLOCK (g_dataset_global); + old.destroy (old.data); + if (dataset) + G_LOCK (g_dataset_global); + old.data = NULL; + } + + return old.data; + } + data++; + } + } + } + else + { + old.data = NULL; + if (d) + { + data = d->data; + data_end = data + d->len; + while (data < data_end) + { + if (data->key == key_id) + { + if (!data->destroy) + { + data->data = new_data; + data->destroy = new_destroy_func; + g_datalist_unlock (datalist); + } + else + { + old = *data; + data->data = new_data; + data->destroy = new_destroy_func; + + g_datalist_unlock (datalist); + + /* We found and replaced an old value + * the GData struct *must* already be unlinked + * when invoking the destroy function. + */ + if (dataset) + G_UNLOCK (g_dataset_global); + old.destroy (old.data); + if (dataset) + G_LOCK (g_dataset_global); + } + return NULL; + } + data++; + } + } + + /* The key was not found, insert it */ + old_d = d; + if (d == NULL) + { + d = g_malloc (sizeof (GData)); + d->len = 0; + d->alloc = 1; + } + else if (d->len == d->alloc) + { + d->alloc = d->alloc * 2; + d = g_realloc (d, sizeof (GData) + (d->alloc - 1) * sizeof (GDataElt)); + } + if (old_d != d) + G_DATALIST_SET_POINTER (datalist, d); + + d->data[d->len].key = key_id; + d->data[d->len].data = new_data; + d->data[d->len].destroy = new_destroy_func; + d->len++; + } + + g_datalist_unlock (datalist); + + return NULL; + +} + +/** + * g_dataset_id_set_data_full: (skip) + * @dataset_location: (not nullable): the location identifying the dataset. + * @key_id: the #GQuark id to identify the data element. + * @data: the data element. + * @destroy_func: the function to call when the data element is + * removed. This function will be called with the data + * element and can be used to free any memory allocated + * for it. + * + * Sets the data element associated with the given #GQuark id, and also + * the function to call when the data element is destroyed. Any + * previous data with the same key is removed, and its destroy function + * is called. + **/ +/** + * g_dataset_set_data_full: (skip) + * @l: the location identifying the dataset. + * @k: the string to identify the data element. + * @d: the data element. + * @f: the function to call when the data element is removed. This + * function will be called with the data element and can be used to + * free any memory allocated for it. + * + * Sets the data corresponding to the given string identifier, and the + * function to call when the data element is destroyed. + **/ +/** + * g_dataset_id_set_data: + * @l: the location identifying the dataset. + * @k: the #GQuark id to identify the data element. + * @d: the data element. + * + * Sets the data element associated with the given #GQuark id. Any + * previous data with the same key is removed, and its destroy function + * is called. + **/ +/** + * g_dataset_set_data: + * @l: the location identifying the dataset. + * @k: the string to identify the data element. + * @d: the data element. + * + * Sets the data corresponding to the given string identifier. + **/ +/** + * g_dataset_id_remove_data: + * @l: the location identifying the dataset. + * @k: the #GQuark id identifying the data element. + * + * Removes a data element from a dataset. The data element's destroy + * function is called if it has been set. + **/ +/** + * g_dataset_remove_data: + * @l: the location identifying the dataset. + * @k: the string identifying the data element. + * + * Removes a data element corresponding to a string. Its destroy + * function is called if it has been set. + **/ +void +g_dataset_id_set_data_full (gconstpointer dataset_location, + GQuark key_id, + gpointer data, + GDestroyNotify destroy_func) +{ + GDataset *dataset; + + g_return_if_fail (dataset_location != NULL); + if (!data) + g_return_if_fail (destroy_func == NULL); + if (!key_id) + { + if (data) + g_return_if_fail (key_id > 0); + else + return; + } + + G_LOCK (g_dataset_global); + if (!g_dataset_location_ht) + g_data_initialize (); + + dataset = g_dataset_lookup (dataset_location); + if (!dataset) + { + dataset = g_slice_new (GDataset); + dataset->location = dataset_location; + g_datalist_init (&dataset->datalist); + g_hash_table_insert (g_dataset_location_ht, + (gpointer) dataset->location, + dataset); + } + + g_data_set_internal (&dataset->datalist, key_id, data, destroy_func, dataset); + G_UNLOCK (g_dataset_global); +} + +/** + * g_datalist_id_set_data_full: (skip) + * @datalist: a datalist. + * @key_id: the #GQuark to identify the data element. + * @data: (nullable): the data element or %NULL to remove any previous element + * corresponding to @key_id. + * @destroy_func: (nullable): the function to call when the data element is + * removed. This function will be called with the data + * element and can be used to free any memory allocated + * for it. If @data is %NULL, then @destroy_func must + * also be %NULL. + * + * Sets the data corresponding to the given #GQuark id, and the + * function to be called when the element is removed from the datalist. + * Any previous data with the same key is removed, and its destroy + * function is called. + **/ +/** + * g_datalist_set_data_full: (skip) + * @dl: a datalist. + * @k: the string to identify the data element. + * @d: (nullable): the data element, or %NULL to remove any previous element + * corresponding to @k. + * @f: (nullable): the function to call when the data element is removed. + * This function will be called with the data element and can be used to + * free any memory allocated for it. If @d is %NULL, then @f must + * also be %NULL. + * + * Sets the data element corresponding to the given string identifier, + * and the function to be called when the data element is removed. + **/ +/** + * g_datalist_id_set_data: + * @dl: a datalist. + * @q: the #GQuark to identify the data element. + * @d: (nullable): the data element, or %NULL to remove any previous element + * corresponding to @q. + * + * Sets the data corresponding to the given #GQuark id. Any previous + * data with the same key is removed, and its destroy function is + * called. + **/ +/** + * g_datalist_set_data: + * @dl: a datalist. + * @k: the string to identify the data element. + * @d: (nullable): the data element, or %NULL to remove any previous element + * corresponding to @k. + * + * Sets the data element corresponding to the given string identifier. + **/ +/** + * g_datalist_id_remove_data: + * @dl: a datalist. + * @q: the #GQuark identifying the data element. + * + * Removes an element, using its #GQuark identifier. + **/ +/** + * g_datalist_remove_data: + * @dl: a datalist. + * @k: the string identifying the data element. + * + * Removes an element using its string identifier. The data element's + * destroy function is called if it has been set. + **/ +void +g_datalist_id_set_data_full (GData **datalist, + GQuark key_id, + gpointer data, + GDestroyNotify destroy_func) +{ + g_return_if_fail (datalist != NULL); + if (!data) + g_return_if_fail (destroy_func == NULL); + if (!key_id) + { + if (data) + g_return_if_fail (key_id > 0); + else + return; + } + + g_data_set_internal (datalist, key_id, data, destroy_func, NULL); +} + +/** + * g_dataset_id_remove_no_notify: (skip) + * @dataset_location: (not nullable): the location identifying the dataset. + * @key_id: the #GQuark ID identifying the data element. + * + * Removes an element, without calling its destroy notification + * function. + * + * Returns: (nullable): the data previously stored at @key_id, + * or %NULL if none. + **/ +/** + * g_dataset_remove_no_notify: (skip) + * @l: the location identifying the dataset. + * @k: the string identifying the data element. + * + * Removes an element, without calling its destroy notifier. + **/ +gpointer +g_dataset_id_remove_no_notify (gconstpointer dataset_location, + GQuark key_id) +{ + gpointer ret_data = NULL; + + g_return_val_if_fail (dataset_location != NULL, NULL); + + G_LOCK (g_dataset_global); + if (key_id && g_dataset_location_ht) + { + GDataset *dataset; + + dataset = g_dataset_lookup (dataset_location); + if (dataset) + ret_data = g_data_set_internal (&dataset->datalist, key_id, NULL, (GDestroyNotify) 42, dataset); + } + G_UNLOCK (g_dataset_global); + + return ret_data; +} + +/** + * g_datalist_id_remove_no_notify: (skip) + * @datalist: a datalist. + * @key_id: the #GQuark identifying a data element. + * + * Removes an element, without calling its destroy notification + * function. + * + * Returns: (nullable): the data previously stored at @key_id, + * or %NULL if none. + **/ +/** + * g_datalist_remove_no_notify: (skip) + * @dl: a datalist. + * @k: the string identifying the data element. + * + * Removes an element, without calling its destroy notifier. + **/ +gpointer +g_datalist_id_remove_no_notify (GData **datalist, + GQuark key_id) +{ + gpointer ret_data = NULL; + + g_return_val_if_fail (datalist != NULL, NULL); + + if (key_id) + ret_data = g_data_set_internal (datalist, key_id, NULL, (GDestroyNotify) 42, NULL); + + return ret_data; +} + +/** + * g_dataset_id_get_data: + * @dataset_location: (not nullable): the location identifying the dataset. + * @key_id: the #GQuark id to identify the data element. + * + * Gets the data element corresponding to a #GQuark. + * + * Returns: (transfer none) (nullable): the data element corresponding to + * the #GQuark, or %NULL if it is not found. + **/ +/** + * g_dataset_get_data: + * @l: the location identifying the dataset. + * @k: the string identifying the data element. + * + * Gets the data element corresponding to a string. + * + * Returns: (transfer none) (nullable): the data element corresponding to + * the string, or %NULL if it is not found. + **/ +gpointer +g_dataset_id_get_data (gconstpointer dataset_location, + GQuark key_id) +{ + gpointer retval = NULL; + + g_return_val_if_fail (dataset_location != NULL, NULL); + + G_LOCK (g_dataset_global); + if (key_id && g_dataset_location_ht) + { + GDataset *dataset; + + dataset = g_dataset_lookup (dataset_location); + if (dataset) + retval = g_datalist_id_get_data (&dataset->datalist, key_id); + } + G_UNLOCK (g_dataset_global); + + return retval; +} + +/** + * g_datalist_id_get_data: + * @datalist: a datalist. + * @key_id: the #GQuark identifying a data element. + * + * Retrieves the data element corresponding to @key_id. + * + * Returns: (transfer none) (nullable): the data element, or %NULL if + * it is not found. + */ +gpointer +g_datalist_id_get_data (GData **datalist, + GQuark key_id) +{ + return g_datalist_id_dup_data (datalist, key_id, NULL, NULL); +} + +/** + * GDuplicateFunc: + * @data: the data to duplicate + * @user_data: (closure): user data that was specified in + * g_datalist_id_dup_data() + * + * The type of functions that are used to 'duplicate' an object. + * What this means depends on the context, it could just be + * incrementing the reference count, if @data is a ref-counted + * object. + * + * Returns: a duplicate of data + */ + +/** + * g_datalist_id_dup_data: (skip) + * @datalist: location of a datalist + * @key_id: the #GQuark identifying a data element + * @dup_func: (nullable) (scope call): function to duplicate the old value + * @user_data: (closure): passed as user_data to @dup_func + * + * This is a variant of g_datalist_id_get_data() which + * returns a 'duplicate' of the value. @dup_func defines the + * meaning of 'duplicate' in this context, it could e.g. + * take a reference on a ref-counted object. + * + * If the @key_id is not set in the datalist then @dup_func + * will be called with a %NULL argument. + * + * Note that @dup_func is called while the datalist is locked, so it + * is not allowed to read or modify the datalist. + * + * This function can be useful to avoid races when multiple + * threads are using the same datalist and the same key. + * + * Returns: (nullable): the result of calling @dup_func on the value + * associated with @key_id in @datalist, or %NULL if not set. + * If @dup_func is %NULL, the value is returned unmodified. + * + * Since: 2.34 + */ +gpointer +g_datalist_id_dup_data (GData **datalist, + GQuark key_id, + GDuplicateFunc dup_func, + gpointer user_data) +{ + gpointer val = NULL; + gpointer retval = NULL; + GData *d; + GDataElt *data, *data_end; + + g_datalist_lock (datalist); + + d = G_DATALIST_GET_POINTER (datalist); + if (d) + { + data = d->data; + data_end = data + d->len; + do + { + if (data->key == key_id) + { + val = data->data; + break; + } + data++; + } + while (data < data_end); + } + + if (dup_func) + retval = dup_func (val, user_data); + else + retval = val; + + g_datalist_unlock (datalist); + + return retval; +} + +/** + * g_datalist_id_replace_data: (skip) + * @datalist: location of a datalist + * @key_id: the #GQuark identifying a data element + * @oldval: (nullable): the old value to compare against + * @newval: (nullable): the new value to replace it with + * @destroy: (nullable): destroy notify for the new value + * @old_destroy: (out) (optional): destroy notify for the existing value + * + * Compares the member that is associated with @key_id in + * @datalist to @oldval, and if they are the same, replace + * @oldval with @newval. + * + * This is like a typical atomic compare-and-exchange + * operation, for a member of @datalist. + * + * If the previous value was replaced then ownership of the + * old value (@oldval) is passed to the caller, including + * the registered destroy notify for it (passed out in @old_destroy). + * Its up to the caller to free this as they wish, which may + * or may not include using @old_destroy as sometimes replacement + * should not destroy the object in the normal way. + * + * Returns: %TRUE if the existing value for @key_id was replaced + * by @newval, %FALSE otherwise. + * + * Since: 2.34 + */ +gboolean +g_datalist_id_replace_data (GData **datalist, + GQuark key_id, + gpointer oldval, + gpointer newval, + GDestroyNotify destroy, + GDestroyNotify *old_destroy) +{ + gpointer val = NULL; + GData *d; + GDataElt *data, *data_end; + + g_return_val_if_fail (datalist != NULL, FALSE); + g_return_val_if_fail (key_id != 0, FALSE); + + if (old_destroy) + *old_destroy = NULL; + + g_datalist_lock (datalist); + + d = G_DATALIST_GET_POINTER (datalist); + if (d) + { + data = d->data; + data_end = data + d->len - 1; + while (data <= data_end) + { + if (data->key == key_id) + { + val = data->data; + if (val == oldval) + { + if (old_destroy) + *old_destroy = data->destroy; + if (newval != NULL) + { + data->data = newval; + data->destroy = destroy; + } + else + { + if (data != data_end) + *data = *data_end; + d->len--; + + /* We don't bother to shrink, but if all data are now gone + * we at least free the memory + */ + if (d->len == 0) + { + G_DATALIST_SET_POINTER (datalist, NULL); + g_free (d); + } + } + } + break; + } + data++; + } + } + + if (val == NULL && oldval == NULL && newval != NULL) + { + GData *old_d; + + /* insert newval */ + old_d = d; + if (d == NULL) + { + d = g_malloc (sizeof (GData)); + d->len = 0; + d->alloc = 1; + } + else if (d->len == d->alloc) + { + d->alloc = d->alloc * 2; + d = g_realloc (d, sizeof (GData) + (d->alloc - 1) * sizeof (GDataElt)); + } + if (old_d != d) + G_DATALIST_SET_POINTER (datalist, d); + + d->data[d->len].key = key_id; + d->data[d->len].data = newval; + d->data[d->len].destroy = destroy; + d->len++; + } + + g_datalist_unlock (datalist); + + return val == oldval; +} + +/** + * g_datalist_get_data: + * @datalist: a datalist. + * @key: the string identifying a data element. + * + * Gets a data element, using its string identifier. This is slower than + * g_datalist_id_get_data() because it compares strings. + * + * Returns: (transfer none) (nullable): the data element, or %NULL if it + * is not found. + **/ +gpointer +g_datalist_get_data (GData **datalist, + const gchar *key) +{ + gpointer res = NULL; + GData *d; + GDataElt *data, *data_end; + + g_return_val_if_fail (datalist != NULL, NULL); + + g_datalist_lock (datalist); + + d = G_DATALIST_GET_POINTER (datalist); + if (d) + { + data = d->data; + data_end = data + d->len; + while (data < data_end) + { + if (g_strcmp0 (g_quark_to_string (data->key), key) == 0) + { + res = data->data; + break; + } + data++; + } + } + + g_datalist_unlock (datalist); + + return res; +} + +/** + * GDataForeachFunc: + * @key_id: the #GQuark id to identifying the data element. + * @data: the data element. + * @user_data: (closure): user data passed to g_dataset_foreach(). + * + * Specifies the type of function passed to g_dataset_foreach(). It is + * called with each #GQuark id and associated data element, together + * with the @user_data parameter supplied to g_dataset_foreach(). + **/ + +/** + * g_dataset_foreach: + * @dataset_location: (not nullable): the location identifying the dataset. + * @func: (scope call): the function to call for each data element. + * @user_data: (closure): user data to pass to the function. + * + * Calls the given function for each data element which is associated + * with the given location. Note that this function is NOT thread-safe. + * So unless @dataset_location can be protected from any modifications + * during invocation of this function, it should not be called. + * + * @func can make changes to the dataset, but the iteration will not + * reflect changes made during the g_dataset_foreach() call, other + * than skipping over elements that are removed. + **/ +void +g_dataset_foreach (gconstpointer dataset_location, + GDataForeachFunc func, + gpointer user_data) +{ + GDataset *dataset; + + g_return_if_fail (dataset_location != NULL); + g_return_if_fail (func != NULL); + + G_LOCK (g_dataset_global); + if (g_dataset_location_ht) + { + dataset = g_dataset_lookup (dataset_location); + G_UNLOCK (g_dataset_global); + if (dataset) + g_datalist_foreach (&dataset->datalist, func, user_data); + } + else + { + G_UNLOCK (g_dataset_global); + } +} + +/** + * g_datalist_foreach: + * @datalist: a datalist. + * @func: (scope call): the function to call for each data element. + * @user_data: (closure): user data to pass to the function. + * + * Calls the given function for each data element of the datalist. The + * function is called with each data element's #GQuark id and data, + * together with the given @user_data parameter. Note that this + * function is NOT thread-safe. So unless @datalist can be protected + * from any modifications during invocation of this function, it should + * not be called. + * + * @func can make changes to @datalist, but the iteration will not + * reflect changes made during the g_datalist_foreach() call, other + * than skipping over elements that are removed. + **/ +void +g_datalist_foreach (GData **datalist, + GDataForeachFunc func, + gpointer user_data) +{ + GData *d; + guint i, j, len; + GQuark *keys; + + g_return_if_fail (datalist != NULL); + g_return_if_fail (func != NULL); + + d = G_DATALIST_GET_POINTER (datalist); + if (d == NULL) + return; + + /* We make a copy of the keys so that we can handle it changing + in the callback */ + len = d->len; + keys = g_new (GQuark, len); + for (i = 0; i < len; i++) + keys[i] = d->data[i].key; + + for (i = 0; i < len; i++) + { + /* A previous callback might have removed a later item, so always check that + it still exists before calling */ + d = G_DATALIST_GET_POINTER (datalist); + + if (d == NULL) + break; + for (j = 0; j < d->len; j++) + { + if (d->data[j].key == keys[i]) { + func (d->data[i].key, d->data[i].data, user_data); + break; + } + } + } + g_free (keys); +} + +/** + * g_datalist_init: (skip) + * @datalist: a pointer to a pointer to a datalist. + * + * Resets the datalist to %NULL. It does not free any memory or call + * any destroy functions. + **/ +void +g_datalist_init (GData **datalist) +{ + g_return_if_fail (datalist != NULL); + + g_atomic_pointer_set (datalist, NULL); +} + +/** + * g_datalist_set_flags: + * @datalist: pointer to the location that holds a list + * @flags: the flags to turn on. The values of the flags are + * restricted by %G_DATALIST_FLAGS_MASK (currently + * 3; giving two possible boolean flags). + * A value for @flags that doesn't fit within the mask is + * an error. + * + * Turns on flag values for a data list. This function is used + * to keep a small number of boolean flags in an object with + * a data list without using any additional space. It is + * not generally useful except in circumstances where space + * is very tight. (It is used in the base #GObject type, for + * example.) + * + * Since: 2.8 + **/ +void +g_datalist_set_flags (GData **datalist, + guint flags) +{ + g_return_if_fail (datalist != NULL); + g_return_if_fail ((flags & ~G_DATALIST_FLAGS_MASK) == 0); + + g_atomic_pointer_or (datalist, (gsize)flags); +} + +/** + * g_datalist_unset_flags: + * @datalist: pointer to the location that holds a list + * @flags: the flags to turn off. The values of the flags are + * restricted by %G_DATALIST_FLAGS_MASK (currently + * 3: giving two possible boolean flags). + * A value for @flags that doesn't fit within the mask is + * an error. + * + * Turns off flag values for a data list. See g_datalist_unset_flags() + * + * Since: 2.8 + **/ +void +g_datalist_unset_flags (GData **datalist, + guint flags) +{ + g_return_if_fail (datalist != NULL); + g_return_if_fail ((flags & ~G_DATALIST_FLAGS_MASK) == 0); + + g_atomic_pointer_and (datalist, ~(gsize)flags); +} + +/** + * g_datalist_get_flags: + * @datalist: pointer to the location that holds a list + * + * Gets flags values packed in together with the datalist. + * See g_datalist_set_flags(). + * + * Returns: the flags of the datalist + * + * Since: 2.8 + **/ +guint +g_datalist_get_flags (GData **datalist) +{ + g_return_val_if_fail (datalist != NULL, 0); + + return G_DATALIST_GET_FLAGS (datalist); /* atomic macro */ +} + +/* HOLDS: g_dataset_global_lock */ +static void +g_data_initialize (void) +{ + g_return_if_fail (g_dataset_location_ht == NULL); + + g_dataset_location_ht = g_hash_table_new (g_direct_hash, NULL); + g_dataset_cached = NULL; +} diff --git a/glib/gdataset.h b/glib/gdataset.h new file mode 100644 index 0000000..89a34c7 --- /dev/null +++ b/glib/gdataset.h @@ -0,0 +1,150 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_DATASET_H__ +#define __G_DATASET_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +typedef struct _GData GData; + +typedef void (*GDataForeachFunc) (GQuark key_id, + gpointer data, + gpointer user_data); + +/* Keyed Data List + */ +GLIB_AVAILABLE_IN_ALL +void g_datalist_init (GData **datalist); +GLIB_AVAILABLE_IN_ALL +void g_datalist_clear (GData **datalist); +GLIB_AVAILABLE_IN_ALL +gpointer g_datalist_id_get_data (GData **datalist, + GQuark key_id); +GLIB_AVAILABLE_IN_ALL +void g_datalist_id_set_data_full (GData **datalist, + GQuark key_id, + gpointer data, + GDestroyNotify destroy_func); + +typedef gpointer (*GDuplicateFunc) (gpointer data, gpointer user_data); + +GLIB_AVAILABLE_IN_2_34 +gpointer g_datalist_id_dup_data (GData **datalist, + GQuark key_id, + GDuplicateFunc dup_func, + gpointer user_data); +GLIB_AVAILABLE_IN_2_34 +gboolean g_datalist_id_replace_data (GData **datalist, + GQuark key_id, + gpointer oldval, + gpointer newval, + GDestroyNotify destroy, + GDestroyNotify *old_destroy); + +GLIB_AVAILABLE_IN_ALL +gpointer g_datalist_id_remove_no_notify (GData **datalist, + GQuark key_id); +GLIB_AVAILABLE_IN_ALL +void g_datalist_foreach (GData **datalist, + GDataForeachFunc func, + gpointer user_data); + +/** + * G_DATALIST_FLAGS_MASK: + * + * A bitmask that restricts the possible flags passed to + * g_datalist_set_flags(). Passing a flags value where + * flags & ~G_DATALIST_FLAGS_MASK != 0 is an error. + */ +#define G_DATALIST_FLAGS_MASK 0x3 + +GLIB_AVAILABLE_IN_ALL +void g_datalist_set_flags (GData **datalist, + guint flags); +GLIB_AVAILABLE_IN_ALL +void g_datalist_unset_flags (GData **datalist, + guint flags); +GLIB_AVAILABLE_IN_ALL +guint g_datalist_get_flags (GData **datalist); + +#define g_datalist_id_set_data(dl, q, d) \ + g_datalist_id_set_data_full ((dl), (q), (d), NULL) +#define g_datalist_id_remove_data(dl, q) \ + g_datalist_id_set_data ((dl), (q), NULL) +#define g_datalist_set_data_full(dl, k, d, f) \ + g_datalist_id_set_data_full ((dl), g_quark_from_string (k), (d), (f)) +#define g_datalist_remove_no_notify(dl, k) \ + g_datalist_id_remove_no_notify ((dl), g_quark_try_string (k)) +#define g_datalist_set_data(dl, k, d) \ + g_datalist_set_data_full ((dl), (k), (d), NULL) +#define g_datalist_remove_data(dl, k) \ + g_datalist_id_set_data ((dl), g_quark_try_string (k), NULL) + +/* Location Associated Keyed Data + */ +GLIB_AVAILABLE_IN_ALL +void g_dataset_destroy (gconstpointer dataset_location); +GLIB_AVAILABLE_IN_ALL +gpointer g_dataset_id_get_data (gconstpointer dataset_location, + GQuark key_id); +GLIB_AVAILABLE_IN_ALL +gpointer g_datalist_get_data (GData **datalist, + const gchar *key); +GLIB_AVAILABLE_IN_ALL +void g_dataset_id_set_data_full (gconstpointer dataset_location, + GQuark key_id, + gpointer data, + GDestroyNotify destroy_func); +GLIB_AVAILABLE_IN_ALL +gpointer g_dataset_id_remove_no_notify (gconstpointer dataset_location, + GQuark key_id); +GLIB_AVAILABLE_IN_ALL +void g_dataset_foreach (gconstpointer dataset_location, + GDataForeachFunc func, + gpointer user_data); +#define g_dataset_id_set_data(l, k, d) \ + g_dataset_id_set_data_full ((l), (k), (d), NULL) +#define g_dataset_id_remove_data(l, k) \ + g_dataset_id_set_data ((l), (k), NULL) +#define g_dataset_get_data(l, k) \ + (g_dataset_id_get_data ((l), g_quark_try_string (k))) +#define g_dataset_set_data_full(l, k, d, f) \ + g_dataset_id_set_data_full ((l), g_quark_from_string (k), (d), (f)) +#define g_dataset_remove_no_notify(l, k) \ + g_dataset_id_remove_no_notify ((l), g_quark_try_string (k)) +#define g_dataset_set_data(l, k, d) \ + g_dataset_set_data_full ((l), (k), (d), NULL) +#define g_dataset_remove_data(l, k) \ + g_dataset_id_set_data ((l), g_quark_try_string (k), NULL) + +G_END_DECLS + +#endif /* __G_DATASET_H__ */ diff --git a/glib/gdatasetprivate.h b/glib/gdatasetprivate.h new file mode 100644 index 0000000..eb95278 --- /dev/null +++ b/glib/gdatasetprivate.h @@ -0,0 +1,42 @@ +/* GLIB - Library of useful routines for C programming + * gdataset-private.h: Internal macros for accessing dataset values + * Copyright (C) 2005 Red Hat + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_DATASETPRIVATE_H__ +#define __G_DATASETPRIVATE_H__ + +#include + +G_BEGIN_DECLS + +/* GET_FLAGS is implemented via atomic pointer access, to allow memory + * barriers to take effect without acquiring the global dataset mutex. + */ +#define G_DATALIST_GET_FLAGS(datalist) \ + ((gsize) g_atomic_pointer_get (datalist) & G_DATALIST_FLAGS_MASK) + + +G_END_DECLS + +#endif /* __G_DATASETPRIVATE_H__ */ diff --git a/glib/gdate.c b/glib/gdate.c new file mode 100644 index 0000000..68c8689 --- /dev/null +++ b/glib/gdate.c @@ -0,0 +1,2755 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * MT safe + */ + +#include "config.h" +#include "glibconfig.h" + +#define DEBUG_MSG(x) /* */ +#ifdef G_ENABLE_DEBUG +/* #define DEBUG_MSG(args) g_message args ; */ +#endif + +#include +#include +#include +#include + +#ifdef G_OS_WIN32 +#include +#endif + +#include "gdate.h" + +#include "gconvert.h" +#include "gmem.h" +#include "gstrfuncs.h" +#include "gtestutils.h" +#include "gthread.h" +#include "gunicode.h" + +#ifdef G_OS_WIN32 +#include "garray.h" +#endif + +/** + * SECTION:date + * @title: Date and Time Functions + * @short_description: calendrical calculations and miscellaneous time stuff + * + * The #GDate data structure represents a day between January 1, Year 1, + * and sometime a few thousand years in the future (right now it will go + * to the year 65535 or so, but g_date_set_parse() only parses up to the + * year 8000 or so - just count on "a few thousand"). #GDate is meant to + * represent everyday dates, not astronomical dates or historical dates + * or ISO timestamps or the like. It extrapolates the current Gregorian + * calendar forward and backward in time; there is no attempt to change + * the calendar to match time periods or locations. #GDate does not store + * time information; it represents a day. + * + * The #GDate implementation has several nice features; it is only a + * 64-bit struct, so storing large numbers of dates is very efficient. It + * can keep both a Julian and day-month-year representation of the date, + * since some calculations are much easier with one representation or the + * other. A Julian representation is simply a count of days since some + * fixed day in the past; for #GDate the fixed day is January 1, 1 AD. + * ("Julian" dates in the #GDate API aren't really Julian dates in the + * technical sense; technically, Julian dates count from the start of the + * Julian period, Jan 1, 4713 BC). + * + * #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 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. + * It is undefined to call any of the date calculation routines on an + * invalid date. If you obtain a date from a user or other + * unpredictable source, you should check its validity with the + * g_date_valid() predicate. g_date_valid() is also used to check for + * errors with g_date_set_parse() and other functions that can + * fail. Dates can be invalidated by calling g_date_clear() again. + * + * It is very important to use the API to access the #GDate + * struct. Often only the day-month-year or only the Julian + * representation is valid. Sometimes neither is valid. Use the API. + * + * GLib also features #GDateTime which represents a precise time. + */ + +/** + * G_USEC_PER_SEC: + * + * Number of microseconds in one second (1 million). + * This macro is provided for code readability. + */ + +/** + * GTimeVal: + * @tv_sec: seconds + * @tv_usec: microseconds + * + * Represents a precise time, with seconds and microseconds. + * + * Similar to the struct timeval returned by the `gettimeofday()` + * UNIX system call. + * + * GLib is attempting to unify around the use of 64-bit integers to + * represent microsecond-precision time. As such, this type will be + * removed from a future version of GLib. A consequence of using `glong` for + * `tv_sec` is that on 32-bit systems `GTimeVal` is subject to the year 2038 + * problem. + * + * Deprecated: 2.62: Use #GDateTime or #guint64 instead. + */ + +/** + * GDate: + * @julian_days: the Julian representation of the date + * @julian: this bit is set if @julian_days is valid + * @dmy: this is set if @day, @month and @year are valid + * @day: the day of the day-month-year representation of the date, + * as a number between 1 and 31 + * @month: the day of the day-month-year representation of the date, + * as a number between 1 and 12 + * @year: the day of the day-month-year representation of the date + * + * Represents a day between January 1, Year 1 and a few thousand years in + * the future. None of its members should be accessed directly. + * + * If the `GDate` is obtained from g_date_new(), it will be safe + * to mutate but invalid and thus not safe for calendrical computations. + * + * 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 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. + */ + +/** + * GTime: + * + * Simply a replacement for `time_t`. It has been deprecated + * since it is not equivalent to `time_t` on 64-bit platforms + * with a 64-bit `time_t`. + * + * Unrelated to #GTimer. + * + * Note that #GTime is defined to always be a 32-bit integer, + * unlike `time_t` which may be 64-bit on some systems. Therefore, + * #GTime will overflow in the year 2038, and you cannot use the + * address of a #GTime variable as argument to the UNIX time() + * function. + * + * Instead, do the following: + * + * |[ + * time_t ttime; + * GTime gtime; + * + * time (&ttime); + * gtime = (GTime)ttime; + * ]| + * + * Deprecated: 2.62: This is not [Y2038-safe](https://en.wikipedia.org/wiki/Year_2038_problem). + * Use #GDateTime or #time_t instead. + */ + +/** + * GDateDMY: + * @G_DATE_DAY: a day + * @G_DATE_MONTH: a month + * @G_DATE_YEAR: a year + * + * This enumeration isn't used in the API, but may be useful if you need + * to mark a number as a day, month, or year. + */ + +/** + * GDateDay: + * + * Integer representing a day of the month; between 1 and 31. + * + * The %G_DATE_BAD_DAY value represents an invalid day of the month. + */ + +/** + * GDateMonth: + * @G_DATE_BAD_MONTH: invalid value + * @G_DATE_JANUARY: January + * @G_DATE_FEBRUARY: February + * @G_DATE_MARCH: March + * @G_DATE_APRIL: April + * @G_DATE_MAY: May + * @G_DATE_JUNE: June + * @G_DATE_JULY: July + * @G_DATE_AUGUST: August + * @G_DATE_SEPTEMBER: September + * @G_DATE_OCTOBER: October + * @G_DATE_NOVEMBER: November + * @G_DATE_DECEMBER: December + * + * Enumeration representing a month; values are %G_DATE_JANUARY, + * %G_DATE_FEBRUARY, etc. %G_DATE_BAD_MONTH is the invalid value. + */ + +/** + * GDateYear: + * + * Integer type representing a year. + * + * The %G_DATE_BAD_YEAR value is the invalid value. The year + * must be 1 or higher; negative ([BCE](https://en.wikipedia.org/wiki/Common_Era)) + * years are not allowed. + * + * The year is represented with four digits. + */ + +/** + * GDateWeekday: + * @G_DATE_BAD_WEEKDAY: invalid value + * @G_DATE_MONDAY: Monday + * @G_DATE_TUESDAY: Tuesday + * @G_DATE_WEDNESDAY: Wednesday + * @G_DATE_THURSDAY: Thursday + * @G_DATE_FRIDAY: Friday + * @G_DATE_SATURDAY: Saturday + * @G_DATE_SUNDAY: Sunday + * + * Enumeration representing a day of the week; %G_DATE_MONDAY, + * %G_DATE_TUESDAY, etc. %G_DATE_BAD_WEEKDAY is an invalid weekday. + */ + +/** + * G_DATE_BAD_DAY: + * + * Represents an invalid #GDateDay. + */ + +/** + * G_DATE_BAD_JULIAN: + * + * Represents an invalid Julian day number. + */ + +/** + * G_DATE_BAD_YEAR: + * + * Represents an invalid year. + */ + +/** + * g_date_new: + * + * Allocates a #GDate and initializes + * 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(). + * + * Returns: a newly-allocated #GDate + */ +GDate* +g_date_new (void) +{ + GDate *d = g_new0 (GDate, 1); /* happily, 0 is the invalid flag for everything. */ + + return d; +} + +/** + * g_date_new_dmy: + * @day: day of the month + * @month: month of the year + * @year: year + * + * Create a new #GDate representing the given day-month-year triplet. + * + * The triplet you pass in must represent a valid date. Use g_date_valid_dmy() + * if needed to validate it. The returned #GDate is guaranteed to be non-%NULL + * and valid. + * + * Returns: (transfer full) (not nullable): a newly-allocated #GDate + * initialized with @day, @month, and @year + */ +GDate* +g_date_new_dmy (GDateDay day, + GDateMonth m, + GDateYear y) +{ + GDate *d; + g_return_val_if_fail (g_date_valid_dmy (day, m, y), NULL); + + d = g_new (GDate, 1); + + d->julian = FALSE; + d->dmy = TRUE; + + d->month = m; + d->day = day; + d->year = y; + + g_assert (g_date_valid (d)); + + return d; +} + +/** + * g_date_new_julian: + * @julian_day: days since January 1, Year 1 + * + * Create a new #GDate representing the given Julian date. + * + * The @julian_day you pass in must be valid. Use g_date_valid_julian() if + * needed to validate it. The returned #GDate is guaranteed to be non-%NULL and + * valid. + * + * Returns: (transfer full) (not nullable): a newly-allocated #GDate initialized + * with @julian_day + */ +GDate* +g_date_new_julian (guint32 julian_day) +{ + GDate *d; + g_return_val_if_fail (g_date_valid_julian (julian_day), NULL); + + d = g_new (GDate, 1); + + d->julian = TRUE; + d->dmy = FALSE; + + d->julian_days = julian_day; + + g_assert (g_date_valid (d)); + + return d; +} + +/** + * g_date_free: + * @date: a #GDate to free + * + * Frees a #GDate returned from g_date_new(). + */ +void +g_date_free (GDate *date) +{ + g_return_if_fail (date != NULL); + + g_free (date); +} + +/** + * g_date_copy: + * @date: a #GDate to copy + * + * Copies a GDate to a newly-allocated GDate. If the input was invalid + * (as determined by g_date_valid()), the invalid state will be copied + * as is into the new object. + * + * Returns: (transfer full): a newly-allocated #GDate initialized from @date + * + * Since: 2.56 + */ +GDate * +g_date_copy (const GDate *date) +{ + GDate *res; + g_return_val_if_fail (date != NULL, NULL); + + if (g_date_valid (date)) + res = g_date_new_julian (g_date_get_julian (date)); + else + { + res = g_date_new (); + *res = *date; + } + + return res; +} + +/** + * g_date_valid: + * @date: a #GDate to check + * + * Returns %TRUE if the #GDate represents an existing day. The date must not + * contain garbage; it should have been initialized with g_date_clear() + * if it wasn't allocated by one of the g_date_new() variants. + * + * Returns: Whether the date is valid + */ +gboolean +g_date_valid (const GDate *d) +{ + g_return_val_if_fail (d != NULL, FALSE); + + return (d->julian || d->dmy); +} + +static const guint8 days_in_months[2][13] = +{ /* error, jan feb mar apr may jun jul aug sep oct nov dec */ + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } /* leap year */ +}; + +static const guint16 days_in_year[2][14] = +{ /* 0, jan feb mar apr may jun jul aug sep oct nov dec */ + { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } +}; + +/** + * g_date_valid_month: + * @month: month + * + * Returns %TRUE if the month value is valid. The 12 #GDateMonth + * enumeration values are the only valid months. + * + * Returns: %TRUE if the month is valid + */ +gboolean +g_date_valid_month (GDateMonth m) +{ + return (((gint) m > G_DATE_BAD_MONTH) && ((gint) m < 13)); +} + +/** + * g_date_valid_year: + * @year: year + * + * Returns %TRUE if the year is valid. Any year greater than 0 is valid, + * though there is a 16-bit limit to what #GDate will understand. + * + * Returns: %TRUE if the year is valid + */ +gboolean +g_date_valid_year (GDateYear y) +{ + return ( y > G_DATE_BAD_YEAR ); +} + +/** + * g_date_valid_day: + * @day: day to check + * + * Returns %TRUE if the day of the month is valid (a day is valid if it's + * between 1 and 31 inclusive). + * + * Returns: %TRUE if the day is valid + */ + +gboolean +g_date_valid_day (GDateDay d) +{ + return ( (d > G_DATE_BAD_DAY) && (d < 32) ); +} + +/** + * g_date_valid_weekday: + * @weekday: weekday + * + * Returns %TRUE if the weekday is valid. The seven #GDateWeekday enumeration + * values are the only valid weekdays. + * + * Returns: %TRUE if the weekday is valid + */ +gboolean +g_date_valid_weekday (GDateWeekday w) +{ + return (((gint) w > G_DATE_BAD_WEEKDAY) && ((gint) w < 8)); +} + +/** + * g_date_valid_julian: + * @julian_date: Julian day to check + * + * Returns %TRUE if the Julian day is valid. Anything greater than zero + * is basically a valid Julian, though there is a 32-bit limit. + * + * Returns: %TRUE if the Julian day is valid + */ +gboolean +g_date_valid_julian (guint32 j) +{ + return (j > G_DATE_BAD_JULIAN); +} + +/** + * g_date_valid_dmy: + * @day: day + * @month: month + * @year: year + * + * Returns %TRUE if the day-month-year triplet forms a valid, existing day + * in the range of days #GDate understands (Year 1 or later, no more than + * a few thousand years in the future). + * + * Returns: %TRUE if the date is a valid one + */ +gboolean +g_date_valid_dmy (GDateDay d, + GDateMonth m, + GDateYear y) +{ + /* No need to check the upper bound of @y, because #GDateYear is 16 bits wide, + * just like #GDate.year. */ + return ( (m > G_DATE_BAD_MONTH) && + (m < 13) && + (d > G_DATE_BAD_DAY) && + (y > G_DATE_BAD_YEAR) && /* must check before using g_date_is_leap_year */ + (d <= (g_date_is_leap_year (y) ? + days_in_months[1][m] : days_in_months[0][m])) ); +} + + +/* "Julian days" just means an absolute number of days, where Day 1 == + * Jan 1, Year 1 + */ +static void +g_date_update_julian (const GDate *const_d) +{ + GDate *d = (GDate *) const_d; + GDateYear year; + gint idx; + + g_return_if_fail (d != NULL); + g_return_if_fail (d->dmy != 0); + g_return_if_fail (!d->julian); + g_return_if_fail (g_date_valid_dmy (d->day, d->month, d->year)); + + /* What we actually do is: multiply years * 365 days in the year, + * add the number of years divided by 4, subtract the number of + * years divided by 100 and add the number of years divided by 400, + * which accounts for leap year stuff. Code from Steffen Beyer's + * DateCalc. + */ + + year = d->year - 1; /* we know d->year > 0 since it's valid */ + + d->julian_days = year * 365U; + d->julian_days += (year >>= 2); /* divide by 4 and add */ + d->julian_days -= (year /= 25); /* divides original # years by 100 */ + d->julian_days += year >> 2; /* divides by 4, which divides original by 400 */ + + idx = g_date_is_leap_year (d->year) ? 1 : 0; + + d->julian_days += days_in_year[idx][d->month] + d->day; + + g_return_if_fail (g_date_valid_julian (d->julian_days)); + + d->julian = TRUE; +} + +static void +g_date_update_dmy (const GDate *const_d) +{ + GDate *d = (GDate *) const_d; + GDateYear y; + GDateMonth m; + GDateDay day; + + guint32 A, B, C, D, E, M; + + g_return_if_fail (d != NULL); + g_return_if_fail (d->julian); + g_return_if_fail (!d->dmy); + g_return_if_fail (g_date_valid_julian (d->julian_days)); + + /* Formula taken from the Calendar FAQ; the formula was for the + * Julian Period which starts on 1 January 4713 BC, so we add + * 1,721,425 to the number of days before doing the formula. + * + * I'm sure this can be simplified for our 1 January 1 AD period + * start, but I can't figure out how to unpack the formula. + */ + + A = d->julian_days + 1721425 + 32045; + B = ( 4 *(A + 36524) )/ 146097 - 1; + C = A - (146097 * B)/4; + D = ( 4 * (C + 365) ) / 1461 - 1; + E = C - ((1461*D) / 4); + M = (5 * (E - 1) + 2)/153; + + m = M + 3 - (12*(M/10)); + day = E - (153*M + 2)/5; + y = 100 * B + D - 4800 + (M/10); + +#ifdef G_ENABLE_DEBUG + if (!g_date_valid_dmy (day, m, y)) + g_warning ("OOPS julian: %u computed dmy: %u %u %u", + d->julian_days, day, m, y); +#endif + + d->month = m; + d->day = day; + d->year = y; + + d->dmy = TRUE; +} + +/** + * g_date_get_weekday: + * @date: a #GDate + * + * Returns the day of the week for a #GDate. The date must be valid. + * + * Returns: day of the week as a #GDateWeekday. + */ +GDateWeekday +g_date_get_weekday (const GDate *d) +{ + g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_WEEKDAY); + + if (!d->julian) + g_date_update_julian (d); + + g_return_val_if_fail (d->julian, G_DATE_BAD_WEEKDAY); + + return ((d->julian_days - 1) % 7) + 1; +} + +/** + * g_date_get_month: + * @date: a #GDate to get the month from + * + * Returns the month of the year. The date must be valid. + * + * Returns: month of the year as a #GDateMonth + */ +GDateMonth +g_date_get_month (const GDate *d) +{ + g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_MONTH); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_val_if_fail (d->dmy, G_DATE_BAD_MONTH); + + return d->month; +} + +/** + * g_date_get_year: + * @date: a #GDate + * + * Returns the year of a #GDate. The date must be valid. + * + * Returns: year in which the date falls + */ +GDateYear +g_date_get_year (const GDate *d) +{ + g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_YEAR); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_val_if_fail (d->dmy, G_DATE_BAD_YEAR); + + return d->year; +} + +/** + * g_date_get_day: + * @date: a #GDate to extract the day of the month from + * + * Returns the day of the month. The date must be valid. + * + * Returns: day of the month + */ +GDateDay +g_date_get_day (const GDate *d) +{ + g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_DAY); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_val_if_fail (d->dmy, G_DATE_BAD_DAY); + + return d->day; +} + +/** + * g_date_get_julian: + * @date: a #GDate to extract the Julian day from + * + * Returns the Julian day or "serial number" of the #GDate. The + * Julian day is simply the number of days since January 1, Year 1; i.e., + * January 1, Year 1 is Julian day 1; January 2, Year 1 is Julian day 2, + * etc. The date must be valid. + * + * Returns: Julian day + */ +guint32 +g_date_get_julian (const GDate *d) +{ + g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_JULIAN); + + if (!d->julian) + g_date_update_julian (d); + + g_return_val_if_fail (d->julian, G_DATE_BAD_JULIAN); + + return d->julian_days; +} + +/** + * g_date_get_day_of_year: + * @date: a #GDate to extract day of year from + * + * Returns the day of the year, where Jan 1 is the first day of the + * year. The date must be valid. + * + * Returns: day of the year + */ +guint +g_date_get_day_of_year (const GDate *d) +{ + gint idx; + + g_return_val_if_fail (g_date_valid (d), 0); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_val_if_fail (d->dmy, 0); + + idx = g_date_is_leap_year (d->year) ? 1 : 0; + + return (days_in_year[idx][d->month] + d->day); +} + +/** + * g_date_get_monday_week_of_year: + * @date: a #GDate + * + * Returns the week of the year, where weeks are understood to start on + * Monday. If the date is before the first Monday of the year, return 0. + * The date must be valid. + * + * Returns: week of the year + */ +guint +g_date_get_monday_week_of_year (const GDate *d) +{ + GDateWeekday wd; + guint day; + GDate first; + + g_return_val_if_fail (g_date_valid (d), 0); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_val_if_fail (d->dmy, 0); + + g_date_clear (&first, 1); + + g_date_set_dmy (&first, 1, 1, d->year); + + wd = g_date_get_weekday (&first) - 1; /* make Monday day 0 */ + day = g_date_get_day_of_year (d) - 1; + + return ((day + wd)/7U + (wd == 0 ? 1 : 0)); +} + +/** + * g_date_get_sunday_week_of_year: + * @date: a #GDate + * + * Returns the week of the year during which this date falls, if + * weeks are understood to begin on Sunday. The date must be valid. + * Can return 0 if the day is before the first Sunday of the year. + * + * Returns: week number + */ +guint +g_date_get_sunday_week_of_year (const GDate *d) +{ + GDateWeekday wd; + guint day; + GDate first; + + g_return_val_if_fail (g_date_valid (d), 0); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_val_if_fail (d->dmy, 0); + + g_date_clear (&first, 1); + + g_date_set_dmy (&first, 1, 1, d->year); + + wd = g_date_get_weekday (&first); + if (wd == 7) wd = 0; /* make Sunday day 0 */ + day = g_date_get_day_of_year (d) - 1; + + return ((day + wd)/7U + (wd == 0 ? 1 : 0)); +} + +/** + * g_date_get_iso8601_week_of_year: + * @date: a valid #GDate + * + * Returns the week of the year, where weeks are interpreted according + * to ISO 8601. + * + * Returns: ISO 8601 week number of the year. + * + * Since: 2.6 + **/ +guint +g_date_get_iso8601_week_of_year (const GDate *d) +{ + guint j, d4, L, d1, w; + + g_return_val_if_fail (g_date_valid (d), 0); + + if (!d->julian) + g_date_update_julian (d); + + g_return_val_if_fail (d->julian, 0); + + /* Formula taken from the Calendar FAQ; the formula was for the + * Julian Period which starts on 1 January 4713 BC, so we add + * 1,721,425 to the number of days before doing the formula. + */ + j = d->julian_days + 1721425; + d4 = (j + 31741 - (j % 7)) % 146097 % 36524 % 1461; + L = d4 / 1460; + d1 = ((d4 - L) % 365) + L; + w = d1 / 7 + 1; + + return w; +} + +/** + * g_date_days_between: + * @date1: the first date + * @date2: the second date + * + * Computes the number of days between two dates. + * If @date2 is prior to @date1, the returned value is negative. + * Both dates must be valid. + * + * Returns: the number of days between @date1 and @date2 + */ +gint +g_date_days_between (const GDate *d1, + const GDate *d2) +{ + g_return_val_if_fail (g_date_valid (d1), 0); + g_return_val_if_fail (g_date_valid (d2), 0); + + return (gint)g_date_get_julian (d2) - (gint)g_date_get_julian (d1); +} + +/** + * g_date_clear: + * @date: pointer to one or more dates to clear + * @n_dates: number of dates to clear + * + * 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(). + */ +void +g_date_clear (GDate *d, guint ndates) +{ + g_return_if_fail (d != NULL); + g_return_if_fail (ndates != 0); + + memset (d, 0x0, ndates*sizeof (GDate)); +} + +G_LOCK_DEFINE_STATIC (g_date_global); + +/* These are for the parser, output to the user should use * + * g_date_strftime () - this creates more never-freed memory to annoy + * all those memory debugger users. :-) + */ + +static gchar *long_month_names[13] = +{ + NULL, +}; + +static gchar *long_month_names_alternative[13] = +{ + NULL, +}; + +static gchar *short_month_names[13] = +{ + NULL, +}; + +static gchar *short_month_names_alternative[13] = +{ + NULL, +}; + +/* This tells us if we need to update the parse info */ +static gchar *current_locale = NULL; + +/* order of these in the current locale */ +static GDateDMY dmy_order[3] = +{ + G_DATE_DAY, G_DATE_MONTH, G_DATE_YEAR +}; + +/* Where to chop two-digit years: i.e., for the 1930 default, numbers + * 29 and below are counted as in the year 2000, numbers 30 and above + * are counted as in the year 1900. + */ + +static const GDateYear twodigit_start_year = 1930; + +/* It is impossible to enter a year between 1 AD and 99 AD with this + * in effect. + */ +static gboolean using_twodigit_years = FALSE; + +/* Adjustment of locale era to AD, non-zero means using locale era + */ +static gint locale_era_adjust = 0; + +struct _GDateParseTokens { + gint num_ints; + gint n[3]; + guint month; +}; + +typedef struct _GDateParseTokens GDateParseTokens; + +static inline gboolean +update_month_match (gsize *longest, + const gchar *haystack, + const gchar *needle) +{ + gsize length; + + if (needle == NULL) + return FALSE; + + length = strlen (needle); + if (*longest >= length) + return FALSE; + + if (strstr (haystack, needle) == NULL) + return FALSE; + + *longest = length; + return TRUE; +} + +#define NUM_LEN 10 + +/* HOLDS: g_date_global_lock */ +static void +g_date_fill_parse_tokens (const gchar *str, GDateParseTokens *pt) +{ + gchar num[4][NUM_LEN+1]; + gint i; + const guchar *s; + + /* We count 4, but store 3; so we can give an error + * if there are 4. + */ + num[0][0] = num[1][0] = num[2][0] = num[3][0] = '\0'; + + s = (const guchar *) str; + pt->num_ints = 0; + while (*s && pt->num_ints < 4) + { + + i = 0; + while (*s && g_ascii_isdigit (*s) && i < NUM_LEN) + { + num[pt->num_ints][i] = *s; + ++s; + ++i; + } + + if (i > 0) + { + num[pt->num_ints][i] = '\0'; + ++(pt->num_ints); + } + + if (*s == '\0') break; + + ++s; + } + + pt->n[0] = pt->num_ints > 0 ? atoi (num[0]) : 0; + pt->n[1] = pt->num_ints > 1 ? atoi (num[1]) : 0; + pt->n[2] = pt->num_ints > 2 ? atoi (num[2]) : 0; + + pt->month = G_DATE_BAD_MONTH; + + if (pt->num_ints < 3) + { + gsize longest = 0; + gchar *casefold; + gchar *normalized; + + casefold = g_utf8_casefold (str, -1); + normalized = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); + g_free (casefold); + + for (i = 1; i < 13; ++i) + { + /* Here month names may be in a genitive case if the language + * grammatical rules require it. + * Examples of how January may look in some languages: + * Catalan: "de gener", Croatian: "siječnja", Polish: "stycznia", + * Upper Sorbian: "januara". + * Note that most of the languages can't or don't use the the + * genitive case here so they use nominative everywhere. + * For example, English always uses "January". + */ + if (update_month_match (&longest, normalized, long_month_names[i])) + pt->month = i; + + /* Here month names will be in a nominative case. + * Examples of how January may look in some languages: + * Catalan: "gener", Croatian: "Siječanj", Polish: "styczeń", + * Upper Sorbian: "Januar". + */ + if (update_month_match (&longest, normalized, long_month_names_alternative[i])) + pt->month = i; + + /* Differences between abbreviated nominative and abbreviated + * genitive month names are visible in very few languages but + * let's handle them. + */ + if (update_month_match (&longest, normalized, short_month_names[i])) + pt->month = i; + + if (update_month_match (&longest, normalized, short_month_names_alternative[i])) + pt->month = i; + } + + g_free (normalized); + } +} + +/* HOLDS: g_date_global_lock */ +static void +g_date_prepare_to_parse (const gchar *str, + GDateParseTokens *pt) +{ + const gchar *locale = setlocale (LC_TIME, NULL); + gboolean recompute_localeinfo = FALSE; + GDate d; + + g_return_if_fail (locale != NULL); /* should not happen */ + + g_date_clear (&d, 1); /* clear for scratch use */ + + if ( (current_locale == NULL) || (strcmp (locale, current_locale) != 0) ) + recompute_localeinfo = TRUE; /* Uh, there used to be a reason for the temporary */ + + if (recompute_localeinfo) + { + int i = 1; + GDateParseTokens testpt; + gchar buf[128]; + + g_free (current_locale); /* still works if current_locale == NULL */ + + current_locale = g_strdup (locale); + + short_month_names[0] = "Error"; + long_month_names[0] = "Error"; + + while (i < 13) + { + gchar *casefold; + + g_date_set_dmy (&d, 1, i, 1976); + + g_return_if_fail (g_date_valid (&d)); + + g_date_strftime (buf, 127, "%b", &d); + + casefold = g_utf8_casefold (buf, -1); + g_free (short_month_names[i]); + short_month_names[i] = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); + g_free (casefold); + + g_date_strftime (buf, 127, "%B", &d); + casefold = g_utf8_casefold (buf, -1); + g_free (long_month_names[i]); + long_month_names[i] = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); + g_free (casefold); + + g_date_strftime (buf, 127, "%Ob", &d); + casefold = g_utf8_casefold (buf, -1); + g_free (short_month_names_alternative[i]); + short_month_names_alternative[i] = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); + g_free (casefold); + + g_date_strftime (buf, 127, "%OB", &d); + casefold = g_utf8_casefold (buf, -1); + g_free (long_month_names_alternative[i]); + long_month_names_alternative[i] = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); + g_free (casefold); + + ++i; + } + + /* Determine DMY order */ + + /* had to pick a random day - don't change this, some strftimes + * are broken on some days, and this one is good so far. */ + g_date_set_dmy (&d, 4, 7, 1976); + + g_date_strftime (buf, 127, "%x", &d); + + g_date_fill_parse_tokens (buf, &testpt); + + using_twodigit_years = FALSE; + locale_era_adjust = 0; + dmy_order[0] = G_DATE_DAY; + dmy_order[1] = G_DATE_MONTH; + dmy_order[2] = G_DATE_YEAR; + + i = 0; + while (i < testpt.num_ints) + { + switch (testpt.n[i]) + { + case 7: + dmy_order[i] = G_DATE_MONTH; + break; + case 4: + dmy_order[i] = G_DATE_DAY; + break; + case 76: + using_twodigit_years = TRUE; + G_GNUC_FALLTHROUGH; + case 1976: + dmy_order[i] = G_DATE_YEAR; + break; + default: + /* assume locale era */ + locale_era_adjust = 1976 - testpt.n[i]; + dmy_order[i] = G_DATE_YEAR; + break; + } + ++i; + } + +#if defined(G_ENABLE_DEBUG) && 0 + DEBUG_MSG (("**GDate prepared a new set of locale-specific parse rules.")); + i = 1; + while (i < 13) + { + DEBUG_MSG ((" %s %s", long_month_names[i], short_month_names[i])); + ++i; + } + DEBUG_MSG (("Alternative month names:")); + i = 1; + while (i < 13) + { + DEBUG_MSG ((" %s %s", long_month_names_alternative[i], short_month_names_alternative[i])); + ++i; + } + if (using_twodigit_years) + { + DEBUG_MSG (("**Using twodigit years with cutoff year: %u", twodigit_start_year)); + } + { + gchar *strings[3]; + i = 0; + while (i < 3) + { + switch (dmy_order[i]) + { + case G_DATE_MONTH: + strings[i] = "Month"; + break; + case G_DATE_YEAR: + strings[i] = "Year"; + break; + case G_DATE_DAY: + strings[i] = "Day"; + break; + default: + strings[i] = NULL; + break; + } + ++i; + } + DEBUG_MSG (("**Order: %s, %s, %s", strings[0], strings[1], strings[2])); + DEBUG_MSG (("**Sample date in this locale: '%s'", buf)); + } +#endif + } + + g_date_fill_parse_tokens (str, pt); +} + +static guint +convert_twodigit_year (guint y) +{ + if (using_twodigit_years && y < 100) + { + guint two = twodigit_start_year % 100; + guint century = (twodigit_start_year / 100) * 100; + + if (y < two) + century += 100; + + y += century; + } + return y; +} + +/** + * g_date_set_parse: + * @date: a #GDate to fill in + * @str: string to parse + * + * Parses a user-inputted string @str, and try to figure out what date it + * represents, taking the [current locale][setlocale] into account. If the + * string is successfully parsed, the date will be valid after the call. + * Otherwise, it will be invalid. You should check using g_date_valid() + * to see whether the parsing succeeded. + * + * This function is not appropriate for file formats and the like; it + * isn't very precise, and its exact behavior varies with the locale. + * It's intended to be a heuristic routine that guesses what the user + * means by a given string (and it does work pretty well in that + * capacity). + */ +void +g_date_set_parse (GDate *d, + const gchar *str) +{ + GDateParseTokens pt; + guint m = G_DATE_BAD_MONTH, day = G_DATE_BAD_DAY, y = G_DATE_BAD_YEAR; + gsize str_len; + + g_return_if_fail (d != NULL); + + /* set invalid */ + g_date_clear (d, 1); + + /* Anything longer than this is ridiculous and could take a while to normalize. + * This limit is chosen arbitrarily. */ + str_len = strlen (str); + if (str_len > 200) + return; + + /* The input has to be valid UTF-8. */ + if (!g_utf8_validate_len (str, str_len, NULL)) + return; + + G_LOCK (g_date_global); + + g_date_prepare_to_parse (str, &pt); + + DEBUG_MSG (("Found %d ints, '%d' '%d' '%d' and written out month %d", + pt.num_ints, pt.n[0], pt.n[1], pt.n[2], pt.month)); + + + if (pt.num_ints == 4) + { + G_UNLOCK (g_date_global); + return; /* presumably a typo; bail out. */ + } + + if (pt.num_ints > 1) + { + int i = 0; + int j = 0; + + g_assert (pt.num_ints < 4); /* i.e., it is 2 or 3 */ + + while (i < pt.num_ints && j < 3) + { + switch (dmy_order[j]) + { + case G_DATE_MONTH: + { + if (pt.num_ints == 2 && pt.month != G_DATE_BAD_MONTH) + { + m = pt.month; + ++j; /* skip months, but don't skip this number */ + continue; + } + else + m = pt.n[i]; + } + break; + case G_DATE_DAY: + { + if (pt.num_ints == 2 && pt.month == G_DATE_BAD_MONTH) + { + day = 1; + ++j; /* skip days, since we may have month/year */ + continue; + } + day = pt.n[i]; + } + break; + case G_DATE_YEAR: + { + y = pt.n[i]; + + if (locale_era_adjust != 0) + { + y += locale_era_adjust; + } + + y = convert_twodigit_year (y); + } + break; + default: + break; + } + + ++i; + ++j; + } + + + if (pt.num_ints == 3 && !g_date_valid_dmy (day, m, y)) + { + /* Try YYYY MM DD */ + y = pt.n[0]; + m = pt.n[1]; + day = pt.n[2]; + + if (using_twodigit_years && y < 100) + y = G_DATE_BAD_YEAR; /* avoids ambiguity */ + } + else if (pt.num_ints == 2) + { + if (m == G_DATE_BAD_MONTH && pt.month != G_DATE_BAD_MONTH) + m = pt.month; + } + } + else if (pt.num_ints == 1) + { + if (pt.month != G_DATE_BAD_MONTH) + { + /* Month name and year? */ + m = pt.month; + day = 1; + y = pt.n[0]; + } + else + { + /* Try yyyymmdd and yymmdd */ + + m = (pt.n[0]/100) % 100; + day = pt.n[0] % 100; + y = pt.n[0]/10000; + + y = convert_twodigit_year (y); + } + } + + /* See if we got anything valid out of all this. */ + /* y < 8000 is to catch 19998 style typos; the library is OK up to 65535 or so */ + if (y < 8000 && g_date_valid_dmy (day, m, y)) + { + d->month = m; + d->day = day; + d->year = y; + d->dmy = TRUE; + } +#ifdef G_ENABLE_DEBUG + else + { + DEBUG_MSG (("Rejected DMY %u %u %u", day, m, y)); + } +#endif + G_UNLOCK (g_date_global); +} + +/** + * g_date_set_time_t: + * @date: a #GDate + * @timet: time_t value to set + * + * Sets the value of a date to the date corresponding to a time + * specified as a time_t. The time to date conversion is done using + * the user's current timezone. + * + * To set the value of a date to the current day, you could write: + * |[ + * time_t now = time (NULL); + * if (now == (time_t) -1) + * // handle the error + * g_date_set_time_t (date, now); + * ]| + * + * Since: 2.10 + */ +void +g_date_set_time_t (GDate *date, + time_t timet) +{ + struct tm tm; + + g_return_if_fail (date != NULL); + +#ifdef HAVE_LOCALTIME_R + localtime_r (&timet, &tm); +#else + { + struct tm *ptm = localtime (&timet); + + if (ptm == NULL) + { + /* Happens at least in Microsoft's C library if you pass a + * negative time_t. Use 2000-01-01 as default date. + */ +#ifndef G_DISABLE_CHECKS + g_return_if_fail_warning (G_LOG_DOMAIN, "g_date_set_time", "ptm != NULL"); +#endif + + tm.tm_mon = 0; + tm.tm_mday = 1; + tm.tm_year = 100; + } + else + memcpy ((void *) &tm, (void *) ptm, sizeof(struct tm)); + } +#endif + + date->julian = FALSE; + + date->month = tm.tm_mon + 1; + date->day = tm.tm_mday; + date->year = tm.tm_year + 1900; + + g_return_if_fail (g_date_valid_dmy (date->day, date->month, date->year)); + + date->dmy = TRUE; +} + + +/** + * g_date_set_time: + * @date: a #GDate. + * @time_: #GTime value to set. + * + * Sets the value of a date from a #GTime value. + * The time to date conversion is done using the user's current timezone. + * + * Deprecated: 2.10: Use g_date_set_time_t() instead. + */ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +void +g_date_set_time (GDate *date, + GTime time_) +{ + g_date_set_time_t (date, (time_t) time_); +} +G_GNUC_END_IGNORE_DEPRECATIONS + +/** + * g_date_set_time_val: + * @date: a #GDate + * @timeval: #GTimeVal value to set + * + * Sets the value of a date from a #GTimeVal value. Note that the + * @tv_usec member is ignored, because #GDate can't make use of the + * additional precision. + * + * The time to date conversion is done using the user's current timezone. + * + * Since: 2.10 + * Deprecated: 2.62: #GTimeVal is not year-2038-safe. Use g_date_set_time_t() + * instead. + */ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +void +g_date_set_time_val (GDate *date, + GTimeVal *timeval) +{ + g_date_set_time_t (date, (time_t) timeval->tv_sec); +} +G_GNUC_END_IGNORE_DEPRECATIONS + +/** + * g_date_set_month: + * @date: a #GDate + * @month: month to set + * + * Sets the month of the year for a #GDate. If the resulting + * day-month-year triplet is invalid, the date will be invalid. + */ +void +g_date_set_month (GDate *d, + GDateMonth m) +{ + g_return_if_fail (d != NULL); + g_return_if_fail (g_date_valid_month (m)); + + if (d->julian && !d->dmy) g_date_update_dmy(d); + d->julian = FALSE; + + d->month = m; + + if (g_date_valid_dmy (d->day, d->month, d->year)) + d->dmy = TRUE; + else + d->dmy = FALSE; +} + +/** + * g_date_set_day: + * @date: a #GDate + * @day: day to set + * + * Sets the day of the month for a #GDate. If the resulting + * day-month-year triplet is invalid, the date will be invalid. + */ +void +g_date_set_day (GDate *d, + GDateDay day) +{ + g_return_if_fail (d != NULL); + g_return_if_fail (g_date_valid_day (day)); + + if (d->julian && !d->dmy) g_date_update_dmy(d); + d->julian = FALSE; + + d->day = day; + + if (g_date_valid_dmy (d->day, d->month, d->year)) + d->dmy = TRUE; + else + d->dmy = FALSE; +} + +/** + * g_date_set_year: + * @date: a #GDate + * @year: year to set + * + * Sets the year for a #GDate. If the resulting day-month-year + * triplet is invalid, the date will be invalid. + */ +void +g_date_set_year (GDate *d, + GDateYear y) +{ + g_return_if_fail (d != NULL); + g_return_if_fail (g_date_valid_year (y)); + + if (d->julian && !d->dmy) g_date_update_dmy(d); + d->julian = FALSE; + + d->year = y; + + if (g_date_valid_dmy (d->day, d->month, d->year)) + d->dmy = TRUE; + else + d->dmy = FALSE; +} + +/** + * g_date_set_dmy: + * @date: a #GDate + * @day: day + * @month: month + * @y: year + * + * Sets the value of a #GDate from a day, month, and year. + * The day-month-year triplet must be valid; if you aren't + * sure it is, call g_date_valid_dmy() to check before you + * set it. + */ +void +g_date_set_dmy (GDate *d, + GDateDay day, + GDateMonth m, + GDateYear y) +{ + g_return_if_fail (d != NULL); + g_return_if_fail (g_date_valid_dmy (day, m, y)); + + d->julian = FALSE; + + d->month = m; + d->day = day; + d->year = y; + + d->dmy = TRUE; +} + +/** + * g_date_set_julian: + * @date: a #GDate + * @julian_date: Julian day number (days since January 1, Year 1) + * + * Sets the value of a #GDate from a Julian day number. + */ +void +g_date_set_julian (GDate *d, + guint32 j) +{ + g_return_if_fail (d != NULL); + g_return_if_fail (g_date_valid_julian (j)); + + d->julian_days = j; + d->julian = TRUE; + d->dmy = FALSE; +} + +/** + * g_date_is_first_of_month: + * @date: a #GDate to check + * + * Returns %TRUE if the date is on the first of a month. + * The date must be valid. + * + * Returns: %TRUE if the date is the first of the month + */ +gboolean +g_date_is_first_of_month (const GDate *d) +{ + g_return_val_if_fail (g_date_valid (d), FALSE); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_val_if_fail (d->dmy, FALSE); + + if (d->day == 1) return TRUE; + else return FALSE; +} + +/** + * g_date_is_last_of_month: + * @date: a #GDate to check + * + * Returns %TRUE if the date is the last day of the month. + * The date must be valid. + * + * Returns: %TRUE if the date is the last day of the month + */ +gboolean +g_date_is_last_of_month (const GDate *d) +{ + gint idx; + + g_return_val_if_fail (g_date_valid (d), FALSE); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_val_if_fail (d->dmy, FALSE); + + idx = g_date_is_leap_year (d->year) ? 1 : 0; + + if (d->day == days_in_months[idx][d->month]) return TRUE; + else return FALSE; +} + +/** + * g_date_add_days: + * @date: a #GDate to increment + * @n_days: number of days to move the date forward + * + * Increments a date some number of days. + * To move forward by weeks, add weeks*7 days. + * The date must be valid. + */ +void +g_date_add_days (GDate *d, + guint ndays) +{ + g_return_if_fail (g_date_valid (d)); + + if (!d->julian) + g_date_update_julian (d); + + g_return_if_fail (d->julian); + g_return_if_fail (ndays <= G_MAXUINT32 - d->julian_days); + + d->julian_days += ndays; + d->dmy = FALSE; +} + +/** + * g_date_subtract_days: + * @date: a #GDate to decrement + * @n_days: number of days to move + * + * Moves a date some number of days into the past. + * To move by weeks, just move by weeks*7 days. + * The date must be valid. + */ +void +g_date_subtract_days (GDate *d, + guint ndays) +{ + g_return_if_fail (g_date_valid (d)); + + if (!d->julian) + g_date_update_julian (d); + + g_return_if_fail (d->julian); + g_return_if_fail (d->julian_days > ndays); + + d->julian_days -= ndays; + d->dmy = FALSE; +} + +/** + * g_date_add_months: + * @date: a #GDate to increment + * @n_months: number of months to move forward + * + * Increments a date by some number of months. + * If the day of the month is greater than 28, + * this routine may change the day of the month + * (because the destination month may not have + * the current day in it). The date must be valid. + */ +void +g_date_add_months (GDate *d, + guint nmonths) +{ + guint years, months; + gint idx; + + g_return_if_fail (g_date_valid (d)); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_if_fail (d->dmy != 0); + g_return_if_fail (nmonths <= G_MAXUINT - (d->month - 1)); + + nmonths += d->month - 1; + + years = nmonths/12; + months = nmonths%12; + + g_return_if_fail (years <= (guint) (G_MAXUINT16 - d->year)); + + d->month = months + 1; + d->year += years; + + idx = g_date_is_leap_year (d->year) ? 1 : 0; + + if (d->day > days_in_months[idx][d->month]) + d->day = days_in_months[idx][d->month]; + + d->julian = FALSE; + + g_return_if_fail (g_date_valid (d)); +} + +/** + * g_date_subtract_months: + * @date: a #GDate to decrement + * @n_months: number of months to move + * + * Moves a date some number of months into the past. + * If the current day of the month doesn't exist in + * the destination month, the day of the month + * may change. The date must be valid. + */ +void +g_date_subtract_months (GDate *d, + guint nmonths) +{ + guint years, months; + gint idx; + + g_return_if_fail (g_date_valid (d)); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_if_fail (d->dmy != 0); + + years = nmonths/12; + months = nmonths%12; + + g_return_if_fail (d->year > years); + + d->year -= years; + + if (d->month > months) d->month -= months; + else + { + months -= d->month; + d->month = 12 - months; + d->year -= 1; + } + + idx = g_date_is_leap_year (d->year) ? 1 : 0; + + if (d->day > days_in_months[idx][d->month]) + d->day = days_in_months[idx][d->month]; + + d->julian = FALSE; + + g_return_if_fail (g_date_valid (d)); +} + +/** + * g_date_add_years: + * @date: a #GDate to increment + * @n_years: number of years to move forward + * + * Increments a date by some number of years. + * If the date is February 29, and the destination + * year is not a leap year, the date will be changed + * to February 28. The date must be valid. + */ +void +g_date_add_years (GDate *d, + guint nyears) +{ + g_return_if_fail (g_date_valid (d)); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_if_fail (d->dmy != 0); + g_return_if_fail (nyears <= (guint) (G_MAXUINT16 - d->year)); + + d->year += nyears; + + if (d->month == 2 && d->day == 29) + { + if (!g_date_is_leap_year (d->year)) + d->day = 28; + } + + d->julian = FALSE; +} + +/** + * g_date_subtract_years: + * @date: a #GDate to decrement + * @n_years: number of years to move + * + * Moves a date some number of years into the past. + * If the current day doesn't exist in the destination + * year (i.e. it's February 29 and you move to a non-leap-year) + * then the day is changed to February 29. The date + * must be valid. + */ +void +g_date_subtract_years (GDate *d, + guint nyears) +{ + g_return_if_fail (g_date_valid (d)); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_if_fail (d->dmy != 0); + g_return_if_fail (d->year > nyears); + + d->year -= nyears; + + if (d->month == 2 && d->day == 29) + { + if (!g_date_is_leap_year (d->year)) + d->day = 28; + } + + d->julian = FALSE; +} + +/** + * g_date_is_leap_year: + * @year: year to check + * + * Returns %TRUE if the year is a leap year. + * + * For the purposes of this function, leap year is every year + * divisible by 4 unless that year is divisible by 100. If it + * is divisible by 100 it would be a leap year only if that year + * is also divisible by 400. + * + * Returns: %TRUE if the year is a leap year + */ +gboolean +g_date_is_leap_year (GDateYear year) +{ + g_return_val_if_fail (g_date_valid_year (year), FALSE); + + return ( (((year % 4) == 0) && ((year % 100) != 0)) || + (year % 400) == 0 ); +} + +/** + * g_date_get_days_in_month: + * @month: month + * @year: year + * + * Returns the number of days in a month, taking leap + * years into account. + * + * Returns: number of days in @month during the @year + */ +guint8 +g_date_get_days_in_month (GDateMonth month, + GDateYear year) +{ + gint idx; + + g_return_val_if_fail (g_date_valid_year (year), 0); + g_return_val_if_fail (g_date_valid_month (month), 0); + + idx = g_date_is_leap_year (year) ? 1 : 0; + + return days_in_months[idx][month]; +} + +/** + * g_date_get_monday_weeks_in_year: + * @year: a year + * + * Returns the number of weeks in the year, where weeks + * are taken to start on Monday. Will be 52 or 53. The + * date must be valid. (Years always have 52 7-day periods, + * plus 1 or 2 extra days depending on whether it's a leap + * year. This function is basically telling you how many + * Mondays are in the year, i.e. there are 53 Mondays if + * one of the extra days happens to be a Monday.) + * + * Returns: number of Mondays in the year + */ +guint8 +g_date_get_monday_weeks_in_year (GDateYear year) +{ + GDate d; + + g_return_val_if_fail (g_date_valid_year (year), 0); + + g_date_clear (&d, 1); + g_date_set_dmy (&d, 1, 1, year); + if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53; + g_date_set_dmy (&d, 31, 12, year); + if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53; + if (g_date_is_leap_year (year)) + { + g_date_set_dmy (&d, 2, 1, year); + if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53; + g_date_set_dmy (&d, 30, 12, year); + if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53; + } + return 52; +} + +/** + * g_date_get_sunday_weeks_in_year: + * @year: year to count weeks in + * + * Returns the number of weeks in the year, where weeks + * are taken to start on Sunday. Will be 52 or 53. The + * date must be valid. (Years always have 52 7-day periods, + * plus 1 or 2 extra days depending on whether it's a leap + * year. This function is basically telling you how many + * Sundays are in the year, i.e. there are 53 Sundays if + * one of the extra days happens to be a Sunday.) + * + * Returns: the number of weeks in @year + */ +guint8 +g_date_get_sunday_weeks_in_year (GDateYear year) +{ + GDate d; + + g_return_val_if_fail (g_date_valid_year (year), 0); + + g_date_clear (&d, 1); + g_date_set_dmy (&d, 1, 1, year); + if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53; + g_date_set_dmy (&d, 31, 12, year); + if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53; + if (g_date_is_leap_year (year)) + { + g_date_set_dmy (&d, 2, 1, year); + if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53; + g_date_set_dmy (&d, 30, 12, year); + if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53; + } + return 52; +} + +/** + * g_date_compare: + * @lhs: first date to compare + * @rhs: second date to compare + * + * qsort()-style comparison function for dates. + * Both dates must be valid. + * + * Returns: 0 for equal, less than zero if @lhs is less than @rhs, + * greater than zero if @lhs is greater than @rhs + */ +gint +g_date_compare (const GDate *lhs, + const GDate *rhs) +{ + g_return_val_if_fail (lhs != NULL, 0); + g_return_val_if_fail (rhs != NULL, 0); + g_return_val_if_fail (g_date_valid (lhs), 0); + g_return_val_if_fail (g_date_valid (rhs), 0); + + /* Remember the self-comparison case! I think it works right now. */ + + while (TRUE) + { + if (lhs->julian && rhs->julian) + { + if (lhs->julian_days < rhs->julian_days) return -1; + else if (lhs->julian_days > rhs->julian_days) return 1; + else return 0; + } + else if (lhs->dmy && rhs->dmy) + { + if (lhs->year < rhs->year) return -1; + else if (lhs->year > rhs->year) return 1; + else + { + if (lhs->month < rhs->month) return -1; + else if (lhs->month > rhs->month) return 1; + else + { + if (lhs->day < rhs->day) return -1; + else if (lhs->day > rhs->day) return 1; + else return 0; + } + + } + + } + else + { + if (!lhs->julian) g_date_update_julian (lhs); + if (!rhs->julian) g_date_update_julian (rhs); + g_return_val_if_fail (lhs->julian, 0); + g_return_val_if_fail (rhs->julian, 0); + } + + } + return 0; /* warnings */ +} + +/** + * g_date_to_struct_tm: + * @date: a #GDate to set the struct tm from + * @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 safe but meaningless. + */ +void +g_date_to_struct_tm (const GDate *d, + struct tm *tm) +{ + GDateWeekday day; + + g_return_if_fail (g_date_valid (d)); + g_return_if_fail (tm != NULL); + + if (!d->dmy) + g_date_update_dmy (d); + + g_return_if_fail (d->dmy != 0); + + /* zero all the irrelevant fields to be sure they're valid */ + + /* On Linux and maybe other systems, there are weird non-POSIX + * fields on the end of struct tm that choke strftime if they + * contain garbage. So we need to 0 the entire struct, not just the + * fields we know to exist. + */ + + memset (tm, 0x0, sizeof (struct tm)); + + tm->tm_mday = d->day; + tm->tm_mon = d->month - 1; /* 0-11 goes in tm */ + tm->tm_year = ((int)d->year) - 1900; /* X/Open says tm_year can be negative */ + + day = g_date_get_weekday (d); + if (day == 7) day = 0; /* struct tm wants days since Sunday, so Sunday is 0 */ + + tm->tm_wday = (int)day; + + tm->tm_yday = g_date_get_day_of_year (d) - 1; /* 0 to 365 */ + tm->tm_isdst = -1; /* -1 means "information not available" */ +} + +/** + * g_date_clamp: + * @date: a #GDate to clamp + * @min_date: minimum accepted value for @date + * @max_date: maximum accepted value for @date + * + * If @date is prior to @min_date, sets @date equal to @min_date. + * If @date falls after @max_date, sets @date equal to @max_date. + * Otherwise, @date is unchanged. + * Either of @min_date and @max_date may be %NULL. + * All non-%NULL dates must be valid. + */ +void +g_date_clamp (GDate *date, + const GDate *min_date, + const GDate *max_date) +{ + g_return_if_fail (g_date_valid (date)); + + if (min_date != NULL) + g_return_if_fail (g_date_valid (min_date)); + + if (max_date != NULL) + g_return_if_fail (g_date_valid (max_date)); + + if (min_date != NULL && max_date != NULL) + g_return_if_fail (g_date_compare (min_date, max_date) <= 0); + + if (min_date && g_date_compare (date, min_date) < 0) + *date = *min_date; + + if (max_date && g_date_compare (max_date, date) < 0) + *date = *max_date; +} + +/** + * g_date_order: + * @date1: the first date + * @date2: the second date + * + * Checks if @date1 is less than or equal to @date2, + * and swap the values if this is not the case. + */ +void +g_date_order (GDate *date1, + GDate *date2) +{ + g_return_if_fail (g_date_valid (date1)); + g_return_if_fail (g_date_valid (date2)); + + if (g_date_compare (date1, date2) > 0) + { + GDate tmp = *date1; + *date1 = *date2; + *date2 = tmp; + } +} + +#ifdef G_OS_WIN32 +static gboolean +append_month_name (GArray *result, + LCID lcid, + SYSTEMTIME *systemtime, + gboolean abbreviated, + gboolean alternative) +{ + int n; + WORD base; + LPCWSTR lpFormat; + + if (alternative) + { + base = abbreviated ? LOCALE_SABBREVMONTHNAME1 : LOCALE_SMONTHNAME1; + n = GetLocaleInfoW (lcid, base + systemtime->wMonth - 1, NULL, 0); + if (n == 0) + return FALSE; + + g_array_set_size (result, result->len + n); + if (GetLocaleInfoW (lcid, base + systemtime->wMonth - 1, + ((wchar_t *) result->data) + result->len - n, n) != n) + return FALSE; + + g_array_set_size (result, result->len - 1); + } + else + { + /* According to MSDN, this is the correct method to obtain + * the form of the month name used when formatting a full + * date; it must be a genitive case in some languages. + * + * (n == 0) indicates an error, whereas (n < 2) is something we’d never + * expect from the given format string, and would break the subsequent code. + */ + lpFormat = abbreviated ? L"ddMMM" : L"ddMMMM"; + n = GetDateFormatW (lcid, 0, systemtime, lpFormat, NULL, 0); + if (n < 2) + return FALSE; + + g_array_set_size (result, result->len + n); + if (GetDateFormatW (lcid, 0, systemtime, lpFormat, + ((wchar_t *) result->data) + result->len - n, n) != n) + return FALSE; + + /* We have obtained a day number as two digits and the month name. + * Now let's get rid of those two digits: overwrite them with the + * month name. + */ + memmove (((wchar_t *) result->data) + result->len - n, + ((wchar_t *) result->data) + result->len - n + 2, + (n - 2) * sizeof (wchar_t)); + g_array_set_size (result, result->len - 3); + } + + return TRUE; +} + +static gsize +win32_strftime_helper (const GDate *d, + const gchar *format, + const struct tm *tm, + gchar *s, + gsize slen) +{ + SYSTEMTIME systemtime; + TIME_ZONE_INFORMATION tzinfo; + LCID lcid; + int n, k; + GArray *result; + const gchar *p; + gunichar c, modifier; + const wchar_t digits[] = L"0123456789"; + gchar *convbuf; + glong convlen = 0; + gsize retval; + + systemtime.wYear = tm->tm_year + 1900; + systemtime.wMonth = tm->tm_mon + 1; + systemtime.wDayOfWeek = tm->tm_wday; + systemtime.wDay = tm->tm_mday; + systemtime.wHour = tm->tm_hour; + systemtime.wMinute = tm->tm_min; + systemtime.wSecond = tm->tm_sec; + systemtime.wMilliseconds = 0; + + lcid = GetThreadLocale (); + result = g_array_sized_new (FALSE, FALSE, sizeof (wchar_t), MAX (128, strlen (format) * 2)); + + p = format; + while (*p) + { + c = g_utf8_get_char (p); + if (c == '%') + { + p = g_utf8_next_char (p); + if (!*p) + { + s[0] = '\0'; + g_array_free (result, TRUE); + + return 0; + } + + modifier = '\0'; + c = g_utf8_get_char (p); + if (c == 'E' || c == 'O') + { + /* "%OB", "%Ob", and "%Oh" are supported, ignore other modified + * conversion specifiers for now. + */ + modifier = c; + p = g_utf8_next_char (p); + if (!*p) + { + s[0] = '\0'; + g_array_free (result, TRUE); + + return 0; + } + + c = g_utf8_get_char (p); + } + + switch (c) + { + case 'a': + if (systemtime.wDayOfWeek == 0) + k = 6; + else + k = systemtime.wDayOfWeek - 1; + n = GetLocaleInfoW (lcid, LOCALE_SABBREVDAYNAME1+k, NULL, 0); + g_array_set_size (result, result->len + n); + GetLocaleInfoW (lcid, LOCALE_SABBREVDAYNAME1+k, ((wchar_t *) result->data) + result->len - n, n); + g_array_set_size (result, result->len - 1); + break; + case 'A': + if (systemtime.wDayOfWeek == 0) + k = 6; + else + k = systemtime.wDayOfWeek - 1; + n = GetLocaleInfoW (lcid, LOCALE_SDAYNAME1+k, NULL, 0); + g_array_set_size (result, result->len + n); + GetLocaleInfoW (lcid, LOCALE_SDAYNAME1+k, ((wchar_t *) result->data) + result->len - n, n); + g_array_set_size (result, result->len - 1); + break; + case 'b': + case 'h': + if (!append_month_name (result, lcid, &systemtime, TRUE, modifier == 'O')) + { + /* Ignore the error; this placeholder will be replaced with nothing */ + } + break; + case 'B': + if (!append_month_name (result, lcid, &systemtime, FALSE, modifier == 'O')) + { + /* Ignore the error; this placeholder will be replaced with nothing */ + } + break; + case 'c': + n = GetDateFormatW (lcid, 0, &systemtime, NULL, NULL, 0); + if (n > 0) + { + g_array_set_size (result, result->len + n); + GetDateFormatW (lcid, 0, &systemtime, NULL, ((wchar_t *) result->data) + result->len - n, n); + g_array_set_size (result, result->len - 1); + } + g_array_append_vals (result, L" ", 1); + n = GetTimeFormatW (lcid, 0, &systemtime, NULL, NULL, 0); + if (n > 0) + { + g_array_set_size (result, result->len + n); + GetTimeFormatW (lcid, 0, &systemtime, NULL, ((wchar_t *) result->data) + result->len - n, n); + g_array_set_size (result, result->len - 1); + } + break; + case 'C': + g_array_append_vals (result, digits + systemtime.wYear/1000, 1); + g_array_append_vals (result, digits + (systemtime.wYear/1000)%10, 1); + break; + case 'd': + g_array_append_vals (result, digits + systemtime.wDay/10, 1); + g_array_append_vals (result, digits + systemtime.wDay%10, 1); + break; + case 'D': + g_array_append_vals (result, digits + systemtime.wMonth/10, 1); + g_array_append_vals (result, digits + systemtime.wMonth%10, 1); + g_array_append_vals (result, L"/", 1); + g_array_append_vals (result, digits + systemtime.wDay/10, 1); + g_array_append_vals (result, digits + systemtime.wDay%10, 1); + g_array_append_vals (result, L"/", 1); + g_array_append_vals (result, digits + (systemtime.wYear/10)%10, 1); + g_array_append_vals (result, digits + systemtime.wYear%10, 1); + break; + case 'e': + if (systemtime.wDay >= 10) + g_array_append_vals (result, digits + systemtime.wDay/10, 1); + else + g_array_append_vals (result, L" ", 1); + g_array_append_vals (result, digits + systemtime.wDay%10, 1); + break; + + /* A GDate has no time fields, so for now we can + * hardcode all time conversions into zeros (or 12 for + * %I). The alternative code snippets in the #else + * branches are here ready to be taken into use when + * needed by a g_strftime() or g_date_and_time_format() + * or whatever. + */ + case 'H': +#if 1 + g_array_append_vals (result, L"00", 2); +#else + g_array_append_vals (result, digits + systemtime.wHour/10, 1); + g_array_append_vals (result, digits + systemtime.wHour%10, 1); +#endif + break; + case 'I': +#if 1 + g_array_append_vals (result, L"12", 2); +#else + if (systemtime.wHour == 0) + g_array_append_vals (result, L"12", 2); + else + { + g_array_append_vals (result, digits + (systemtime.wHour%12)/10, 1); + g_array_append_vals (result, digits + (systemtime.wHour%12)%10, 1); + } +#endif + break; + case 'j': + g_array_append_vals (result, digits + (tm->tm_yday+1)/100, 1); + g_array_append_vals (result, digits + ((tm->tm_yday+1)/10)%10, 1); + g_array_append_vals (result, digits + (tm->tm_yday+1)%10, 1); + break; + case 'm': + g_array_append_vals (result, digits + systemtime.wMonth/10, 1); + g_array_append_vals (result, digits + systemtime.wMonth%10, 1); + break; + case 'M': +#if 1 + g_array_append_vals (result, L"00", 2); +#else + g_array_append_vals (result, digits + systemtime.wMinute/10, 1); + g_array_append_vals (result, digits + systemtime.wMinute%10, 1); +#endif + break; + case 'n': + g_array_append_vals (result, L"\n", 1); + break; + case 'p': + n = GetTimeFormatW (lcid, 0, &systemtime, L"tt", NULL, 0); + if (n > 0) + { + g_array_set_size (result, result->len + n); + GetTimeFormatW (lcid, 0, &systemtime, L"tt", ((wchar_t *) result->data) + result->len - n, n); + g_array_set_size (result, result->len - 1); + } + break; + case 'r': + /* This is a rather odd format. Hard to say what to do. + * Let's always use the POSIX %I:%M:%S %p + */ +#if 1 + g_array_append_vals (result, L"12:00:00", 8); +#else + if (systemtime.wHour == 0) + g_array_append_vals (result, L"12", 2); + else + { + g_array_append_vals (result, digits + (systemtime.wHour%12)/10, 1); + g_array_append_vals (result, digits + (systemtime.wHour%12)%10, 1); + } + g_array_append_vals (result, L":", 1); + g_array_append_vals (result, digits + systemtime.wMinute/10, 1); + g_array_append_vals (result, digits + systemtime.wMinute%10, 1); + g_array_append_vals (result, L":", 1); + g_array_append_vals (result, digits + systemtime.wSecond/10, 1); + g_array_append_vals (result, digits + systemtime.wSecond%10, 1); + g_array_append_vals (result, L" ", 1); +#endif + n = GetTimeFormatW (lcid, 0, &systemtime, L"tt", NULL, 0); + if (n > 0) + { + g_array_set_size (result, result->len + n); + GetTimeFormatW (lcid, 0, &systemtime, L"tt", ((wchar_t *) result->data) + result->len - n, n); + g_array_set_size (result, result->len - 1); + } + break; + case 'R': +#if 1 + g_array_append_vals (result, L"00:00", 5); +#else + g_array_append_vals (result, digits + systemtime.wHour/10, 1); + g_array_append_vals (result, digits + systemtime.wHour%10, 1); + g_array_append_vals (result, L":", 1); + g_array_append_vals (result, digits + systemtime.wMinute/10, 1); + g_array_append_vals (result, digits + systemtime.wMinute%10, 1); +#endif + break; + case 'S': +#if 1 + g_array_append_vals (result, L"00", 2); +#else + g_array_append_vals (result, digits + systemtime.wSecond/10, 1); + g_array_append_vals (result, digits + systemtime.wSecond%10, 1); +#endif + break; + case 't': + g_array_append_vals (result, L"\t", 1); + break; + case 'T': +#if 1 + g_array_append_vals (result, L"00:00:00", 8); +#else + g_array_append_vals (result, digits + systemtime.wHour/10, 1); + g_array_append_vals (result, digits + systemtime.wHour%10, 1); + g_array_append_vals (result, L":", 1); + g_array_append_vals (result, digits + systemtime.wMinute/10, 1); + g_array_append_vals (result, digits + systemtime.wMinute%10, 1); + g_array_append_vals (result, L":", 1); + g_array_append_vals (result, digits + systemtime.wSecond/10, 1); + g_array_append_vals (result, digits + systemtime.wSecond%10, 1); +#endif + break; + case 'u': + if (systemtime.wDayOfWeek == 0) + g_array_append_vals (result, L"7", 1); + else + g_array_append_vals (result, digits + systemtime.wDayOfWeek, 1); + break; + case 'U': + n = g_date_get_sunday_week_of_year (d); + g_array_append_vals (result, digits + n/10, 1); + g_array_append_vals (result, digits + n%10, 1); + break; + case 'V': + n = g_date_get_iso8601_week_of_year (d); + g_array_append_vals (result, digits + n/10, 1); + g_array_append_vals (result, digits + n%10, 1); + break; + case 'w': + g_array_append_vals (result, digits + systemtime.wDayOfWeek, 1); + break; + case 'W': + n = g_date_get_monday_week_of_year (d); + g_array_append_vals (result, digits + n/10, 1); + g_array_append_vals (result, digits + n%10, 1); + break; + case 'x': + n = GetDateFormatW (lcid, 0, &systemtime, NULL, NULL, 0); + if (n > 0) + { + g_array_set_size (result, result->len + n); + GetDateFormatW (lcid, 0, &systemtime, NULL, ((wchar_t *) result->data) + result->len - n, n); + g_array_set_size (result, result->len - 1); + } + break; + case 'X': + n = GetTimeFormatW (lcid, 0, &systemtime, NULL, NULL, 0); + if (n > 0) + { + g_array_set_size (result, result->len + n); + GetTimeFormatW (lcid, 0, &systemtime, NULL, ((wchar_t *) result->data) + result->len - n, n); + g_array_set_size (result, result->len - 1); + } + break; + case 'y': + g_array_append_vals (result, digits + (systemtime.wYear/10)%10, 1); + g_array_append_vals (result, digits + systemtime.wYear%10, 1); + break; + case 'Y': + g_array_append_vals (result, digits + systemtime.wYear/1000, 1); + g_array_append_vals (result, digits + (systemtime.wYear/100)%10, 1); + g_array_append_vals (result, digits + (systemtime.wYear/10)%10, 1); + g_array_append_vals (result, digits + systemtime.wYear%10, 1); + break; + case 'Z': + n = GetTimeZoneInformation (&tzinfo); + if (n == TIME_ZONE_ID_UNKNOWN || n == TIME_ZONE_ID_STANDARD) + g_array_append_vals (result, tzinfo.StandardName, wcslen (tzinfo.StandardName)); + else if (n == TIME_ZONE_ID_DAYLIGHT) + g_array_append_vals (result, tzinfo.DaylightName, wcslen (tzinfo.DaylightName)); + break; + case '%': + g_array_append_vals (result, L"%", 1); + break; + } + } + else if (c <= 0xFFFF) + { + wchar_t wc = c; + g_array_append_vals (result, &wc, 1); + } + else + { + glong nwc; + wchar_t *ws; + + ws = g_ucs4_to_utf16 (&c, 1, NULL, &nwc, NULL); + g_array_append_vals (result, ws, nwc); + g_free (ws); + } + p = g_utf8_next_char (p); + } + + convbuf = g_utf16_to_utf8 ((wchar_t *) result->data, result->len, NULL, &convlen, NULL); + g_array_free (result, TRUE); + + if (!convbuf) + { + s[0] = '\0'; + return 0; + } + + g_assert (convlen >= 0); + if ((gsize) convlen >= slen) + { + /* Ensure only whole characters are copied into the buffer. */ + gchar *end = g_utf8_find_prev_char (convbuf, convbuf + slen); + g_assert (end != NULL); + convlen = end - convbuf; + + /* Return 0 because the buffer isn't large enough. */ + retval = 0; + } + else + retval = convlen; + + memcpy (s, convbuf, convlen); + s[convlen] = '\0'; + g_free (convbuf); + + return retval; +} + +#endif + +/** + * g_date_strftime: + * @s: destination buffer + * @slen: buffer size + * @format: format string + * @date: valid #GDate + * + * Generates a printed representation of the date, in a + * [locale][setlocale]-specific way. + * Works just like the platform's C library strftime() function, + * but only accepts date-related formats; time-related formats + * give undefined results. Date must be valid. Unlike strftime() + * (which uses the locale encoding), works on a UTF-8 format + * string and stores a UTF-8 result. + * + * This function does not provide any conversion specifiers in + * addition to those implemented by the platform's C library. + * For example, don't expect that using g_date_strftime() would + * make the \%F provided by the C99 strftime() work on Windows + * where the C library only complies to C89. + * + * Returns: number of characters written to the buffer, or 0 the buffer was too small + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + +gsize +g_date_strftime (gchar *s, + gsize slen, + const gchar *format, + const GDate *d) +{ + struct tm tm; +#ifndef G_OS_WIN32 + gsize locale_format_len = 0; + gchar *locale_format; + gsize tmplen; + gchar *tmpbuf; + gsize tmpbufsize; + gsize convlen = 0; + gchar *convbuf; + GError *error = NULL; + gsize retval; +#endif + + g_return_val_if_fail (g_date_valid (d), 0); + g_return_val_if_fail (slen > 0, 0); + g_return_val_if_fail (format != NULL, 0); + g_return_val_if_fail (s != NULL, 0); + + g_date_to_struct_tm (d, &tm); + +#ifdef G_OS_WIN32 + if (!g_utf8_validate (format, -1, NULL)) + { + s[0] = '\0'; + return 0; + } + return win32_strftime_helper (d, format, &tm, s, slen); +#else + + locale_format = g_locale_from_utf8 (format, -1, NULL, &locale_format_len, &error); + + if (error) + { + g_warning (G_STRLOC "Error converting format to locale encoding: %s", error->message); + g_error_free (error); + + s[0] = '\0'; + return 0; + } + + tmpbufsize = MAX (128, locale_format_len * 2); + while (TRUE) + { + tmpbuf = g_malloc (tmpbufsize); + + /* Set the first byte to something other than '\0', to be able to + * recognize whether strftime actually failed or just returned "". + */ + tmpbuf[0] = '\1'; + tmplen = strftime (tmpbuf, tmpbufsize, locale_format, &tm); + + if (tmplen == 0 && tmpbuf[0] != '\0') + { + g_free (tmpbuf); + tmpbufsize *= 2; + + if (tmpbufsize > 65536) + { + g_warning (G_STRLOC "Maximum buffer size for g_date_strftime exceeded: giving up"); + g_free (locale_format); + + s[0] = '\0'; + return 0; + } + } + else + break; + } + g_free (locale_format); + + convbuf = g_locale_to_utf8 (tmpbuf, tmplen, NULL, &convlen, &error); + g_free (tmpbuf); + + if (error) + { + g_warning (G_STRLOC "Error converting results of strftime to UTF-8: %s", error->message); + g_error_free (error); + + s[0] = '\0'; + return 0; + } + + if (slen <= convlen) + { + /* Ensure only whole characters are copied into the buffer. + */ + gchar *end = g_utf8_find_prev_char (convbuf, convbuf + slen); + g_assert (end != NULL); + convlen = end - convbuf; + + /* Return 0 because the buffer isn't large enough. + */ + retval = 0; + } + else + retval = convlen; + + memcpy (s, convbuf, convlen); + s[convlen] = '\0'; + g_free (convbuf); + + return retval; +#endif +} + +#pragma GCC diagnostic pop diff --git a/glib/gdate.h b/glib/gdate.h new file mode 100644 index 0000000..65fe811 --- /dev/null +++ b/glib/gdate.h @@ -0,0 +1,307 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __G_DATE_H__ +#define __G_DATE_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +#include +#include + +G_BEGIN_DECLS + +/* GDate + * + * Date calculations (not time for now, to be resolved). These are a + * mutant combination of Steffen Beyer's DateCalc routines + * (http://www.perl.com/CPAN/authors/id/STBEY/) and Jon Trowbridge's + * date routines (written for in-house software). Written by Havoc + * Pennington + */ + +typedef gint32 GTime GLIB_DEPRECATED_TYPE_IN_2_62_FOR(GDateTime); +typedef guint16 GDateYear; +typedef guint8 GDateDay; /* day of the month */ +typedef struct _GDate GDate; + +/* enum used to specify order of appearance in parsed date strings */ +typedef enum +{ + G_DATE_DAY = 0, + G_DATE_MONTH = 1, + G_DATE_YEAR = 2 +} GDateDMY; + +/* actual week and month values */ +typedef enum +{ + G_DATE_BAD_WEEKDAY = 0, + G_DATE_MONDAY = 1, + G_DATE_TUESDAY = 2, + G_DATE_WEDNESDAY = 3, + G_DATE_THURSDAY = 4, + G_DATE_FRIDAY = 5, + G_DATE_SATURDAY = 6, + G_DATE_SUNDAY = 7 +} GDateWeekday; +typedef enum +{ + G_DATE_BAD_MONTH = 0, + G_DATE_JANUARY = 1, + G_DATE_FEBRUARY = 2, + G_DATE_MARCH = 3, + G_DATE_APRIL = 4, + G_DATE_MAY = 5, + G_DATE_JUNE = 6, + G_DATE_JULY = 7, + G_DATE_AUGUST = 8, + G_DATE_SEPTEMBER = 9, + G_DATE_OCTOBER = 10, + G_DATE_NOVEMBER = 11, + G_DATE_DECEMBER = 12 +} GDateMonth; + +#define G_DATE_BAD_JULIAN 0U +#define G_DATE_BAD_DAY 0U +#define G_DATE_BAD_YEAR 0U + +/* Note: directly manipulating structs is generally a bad idea, but + * in this case it's an *incredibly* bad idea, because all or part + * of this struct can be invalid at any given time. Use the functions, + * or you will get hosed, I promise. + */ +struct _GDate +{ + guint julian_days : 32; /* julian days representation - we use a + * bitfield hoping that 64 bit platforms + * will pack this whole struct in one big + * int + */ + + guint julian : 1; /* julian is valid */ + guint dmy : 1; /* dmy is valid */ + + /* DMY representation */ + guint day : 6; + guint month : 4; + guint year : 16; +}; + +/* g_date_new() returns an invalid date, you then have to _set() stuff + * to get a usable object. You can also allocate a GDate statically, + * then call g_date_clear() to initialize. + */ +GLIB_AVAILABLE_IN_ALL +GDate* g_date_new (void); +GLIB_AVAILABLE_IN_ALL +GDate* g_date_new_dmy (GDateDay day, + GDateMonth month, + GDateYear year); +GLIB_AVAILABLE_IN_ALL +GDate* g_date_new_julian (guint32 julian_day); +GLIB_AVAILABLE_IN_ALL +void g_date_free (GDate *date); +GLIB_AVAILABLE_IN_2_56 +GDate* g_date_copy (const GDate *date); + +/* check g_date_valid() after doing an operation that might fail, like + * _parse. Almost all g_date operations are undefined on invalid + * dates (the exceptions are the mutators, since you need those to + * return to validity). + */ +GLIB_AVAILABLE_IN_ALL +gboolean g_date_valid (const GDate *date); +GLIB_AVAILABLE_IN_ALL +gboolean g_date_valid_day (GDateDay day) G_GNUC_CONST; +GLIB_AVAILABLE_IN_ALL +gboolean g_date_valid_month (GDateMonth month) G_GNUC_CONST; +GLIB_AVAILABLE_IN_ALL +gboolean g_date_valid_year (GDateYear year) G_GNUC_CONST; +GLIB_AVAILABLE_IN_ALL +gboolean g_date_valid_weekday (GDateWeekday weekday) G_GNUC_CONST; +GLIB_AVAILABLE_IN_ALL +gboolean g_date_valid_julian (guint32 julian_date) G_GNUC_CONST; +GLIB_AVAILABLE_IN_ALL +gboolean g_date_valid_dmy (GDateDay day, + GDateMonth month, + GDateYear year) G_GNUC_CONST; + +GLIB_AVAILABLE_IN_ALL +GDateWeekday g_date_get_weekday (const GDate *date); +GLIB_AVAILABLE_IN_ALL +GDateMonth g_date_get_month (const GDate *date); +GLIB_AVAILABLE_IN_ALL +GDateYear g_date_get_year (const GDate *date); +GLIB_AVAILABLE_IN_ALL +GDateDay g_date_get_day (const GDate *date); +GLIB_AVAILABLE_IN_ALL +guint32 g_date_get_julian (const GDate *date); +GLIB_AVAILABLE_IN_ALL +guint g_date_get_day_of_year (const GDate *date); +/* First monday/sunday is the start of week 1; if we haven't reached + * that day, return 0. These are not ISO weeks of the year; that + * routine needs to be added. + * these functions return the number of weeks, starting on the + * corrsponding day + */ +GLIB_AVAILABLE_IN_ALL +guint g_date_get_monday_week_of_year (const GDate *date); +GLIB_AVAILABLE_IN_ALL +guint g_date_get_sunday_week_of_year (const GDate *date); +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 safe state before use. You can clear a whole array at + * once with the ndates argument. + */ +GLIB_AVAILABLE_IN_ALL +void g_date_clear (GDate *date, + guint n_dates); + +/* The parse routine is meant for dates typed in by a user, so it + * permits many formats but tries to catch common typos. If your data + * needs to be strictly validated, it is not an appropriate function. + */ +GLIB_AVAILABLE_IN_ALL +void g_date_set_parse (GDate *date, + const gchar *str); +GLIB_AVAILABLE_IN_ALL +void g_date_set_time_t (GDate *date, + time_t timet); +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +GLIB_DEPRECATED_IN_2_62_FOR(g_date_set_time_t) +void g_date_set_time_val (GDate *date, + GTimeVal *timeval); +GLIB_DEPRECATED_FOR(g_date_set_time_t) +void g_date_set_time (GDate *date, + GTime time_); +G_GNUC_END_IGNORE_DEPRECATIONS +GLIB_AVAILABLE_IN_ALL +void g_date_set_month (GDate *date, + GDateMonth month); +GLIB_AVAILABLE_IN_ALL +void g_date_set_day (GDate *date, + GDateDay day); +GLIB_AVAILABLE_IN_ALL +void g_date_set_year (GDate *date, + GDateYear year); +GLIB_AVAILABLE_IN_ALL +void g_date_set_dmy (GDate *date, + GDateDay day, + GDateMonth month, + GDateYear y); +GLIB_AVAILABLE_IN_ALL +void g_date_set_julian (GDate *date, + guint32 julian_date); +GLIB_AVAILABLE_IN_ALL +gboolean g_date_is_first_of_month (const GDate *date); +GLIB_AVAILABLE_IN_ALL +gboolean g_date_is_last_of_month (const GDate *date); + +/* To go forward by some number of weeks just go forward weeks*7 days */ +GLIB_AVAILABLE_IN_ALL +void g_date_add_days (GDate *date, + guint n_days); +GLIB_AVAILABLE_IN_ALL +void g_date_subtract_days (GDate *date, + guint n_days); + +/* If you add/sub months while day > 28, the day might change */ +GLIB_AVAILABLE_IN_ALL +void g_date_add_months (GDate *date, + guint n_months); +GLIB_AVAILABLE_IN_ALL +void g_date_subtract_months (GDate *date, + guint n_months); + +/* If it's feb 29, changing years can move you to the 28th */ +GLIB_AVAILABLE_IN_ALL +void g_date_add_years (GDate *date, + guint n_years); +GLIB_AVAILABLE_IN_ALL +void g_date_subtract_years (GDate *date, + guint n_years); +GLIB_AVAILABLE_IN_ALL +gboolean g_date_is_leap_year (GDateYear year) G_GNUC_CONST; +GLIB_AVAILABLE_IN_ALL +guint8 g_date_get_days_in_month (GDateMonth month, + GDateYear year) G_GNUC_CONST; +GLIB_AVAILABLE_IN_ALL +guint8 g_date_get_monday_weeks_in_year (GDateYear year) G_GNUC_CONST; +GLIB_AVAILABLE_IN_ALL +guint8 g_date_get_sunday_weeks_in_year (GDateYear year) G_GNUC_CONST; + +/* Returns the number of days between the two dates. If date2 comes + before date1, a negative value is return. */ +GLIB_AVAILABLE_IN_ALL +gint g_date_days_between (const GDate *date1, + const GDate *date2); + +/* qsort-friendly (with a cast...) */ +GLIB_AVAILABLE_IN_ALL +gint g_date_compare (const GDate *lhs, + const GDate *rhs); +GLIB_AVAILABLE_IN_ALL +void g_date_to_struct_tm (const GDate *date, + struct tm *tm); + +GLIB_AVAILABLE_IN_ALL +void g_date_clamp (GDate *date, + const GDate *min_date, + const GDate *max_date); + +/* Swap date1 and date2's values if date1 > date2. */ +GLIB_AVAILABLE_IN_ALL +void g_date_order (GDate *date1, GDate *date2); + +/* Just like strftime() except you can only use date-related formats. + * Using a time format is undefined. + */ +GLIB_AVAILABLE_IN_ALL +gsize g_date_strftime (gchar *s, + gsize slen, + const gchar *format, + const GDate *date); + +#define g_date_weekday g_date_get_weekday GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_weekday) +#define g_date_month g_date_get_month GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_month) +#define g_date_year g_date_get_year GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_year) +#define g_date_day g_date_get_day GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_day) +#define g_date_julian g_date_get_julian GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_julian) +#define g_date_day_of_year g_date_get_day_of_year GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_day_of_year) +#define g_date_monday_week_of_year g_date_get_monday_week_of_year GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_monday_week_of_year) +#define g_date_sunday_week_of_year g_date_get_sunday_week_of_year GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_sunday_week_of_year) +#define g_date_days_in_month g_date_get_days_in_month GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_days_in_month) +#define g_date_monday_weeks_in_year g_date_get_monday_weeks_in_year GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_monday_weeks_in_year) +#define g_date_sunday_weeks_in_year g_date_get_sunday_weeks_in_year GLIB_DEPRECATED_MACRO_IN_2_26_FOR(g_date_get_sunday_weeks_in_year) + +G_END_DECLS + +#endif /* __G_DATE_H__ */ diff --git a/glib/gdatetime.c b/glib/gdatetime.c new file mode 100644 index 0000000..0ec390c --- /dev/null +++ b/glib/gdatetime.c @@ -0,0 +1,3527 @@ +/* gdatetime.c + * + * Copyright (C) 2009-2010 Christian Hergert + * Copyright (C) 2010 Thiago Santos + * Copyright (C) 2010 Emmanuele Bassi + * Copyright © 2010 Codethink Limited + * Copyright © 2018 Tomasz Miąsko + * + * 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 + * licence, or (at your option) any later version. + * + * This 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: Christian Hergert + * Thiago Santos + * Emmanuele Bassi + * Ryan Lortie + * Robert Ancell + */ + +/* Algorithms within this file are based on the Calendar FAQ by + * Claus Tondering. It can be found at + * http://www.tondering.dk/claus/cal/calendar29.txt + * + * Copyright and disclaimer + * ------------------------ + * This document is Copyright (C) 2008 by Claus Tondering. + * E-mail: claus@tondering.dk. (Please include the word + * "calendar" in the subject line.) + * The document may be freely distributed, provided this + * copyright notice is included and no money is charged for + * the document. + * + * This document is provided "as is". No warranties are made as + * to its correctness. + */ + +/* Prologue {{{1 */ + +#include "config.h" + +/* langinfo.h in glibc 2.27 defines ALTMON_* only if _GNU_SOURCE is defined. */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include + +#ifdef HAVE_LANGINFO_TIME +#include +#endif + +#include "gatomic.h" +#include "gcharset.h" +#include "gcharsetprivate.h" +#include "gconvert.h" +#include "gconvertprivate.h" +#include "gdatetime.h" +#include "gfileutils.h" +#include "ghash.h" +#include "glibintl.h" +#include "gmain.h" +#include "gmappedfile.h" +#include "gslice.h" +#include "gstrfuncs.h" +#include "gtestutils.h" +#include "gthread.h" +#include "gtimezone.h" + +#ifndef G_OS_WIN32 +#include +#include +#else +#if defined (_MSC_VER) && (_MSC_VER < 1800) +/* fallback implementation for isnan() on VS2012 and earlier */ +#define isnan _isnan +#endif +#endif /* !G_OS_WIN32 */ + +/** + * SECTION:date-time + * @title: GDateTime + * @short_description: a structure representing Date and Time + * @see_also: #GTimeZone + * + * #GDateTime is a structure that combines a Gregorian date and time + * into a single structure. It provides many conversion and methods to + * manipulate dates and times. Time precision is provided down to + * microseconds and the time can range (proleptically) from 0001-01-01 + * 00:00:00 to 9999-12-31 23:59:59.999999. #GDateTime follows POSIX + * time in the sense that it is oblivious to leap seconds. + * + * #GDateTime is an immutable object; once it has been created it cannot + * be modified further. All modifiers will create a new #GDateTime. + * Nearly all such functions can fail due to the date or time going out + * of range, in which case %NULL will be returned. + * + * #GDateTime is reference counted: the reference count is increased by calling + * g_date_time_ref() and decreased by calling g_date_time_unref(). When the + * reference count drops to 0, the resources allocated by the #GDateTime + * structure are released. + * + * Many parts of the API may produce non-obvious results. As an + * example, adding two months to January 31st will yield March 31st + * whereas adding one month and then one month again will yield either + * March 28th or March 29th. Also note that adding 24 hours is not + * always the same as adding one day (since days containing daylight + * savings time transitions are either 23 or 25 hours in length). + * + * #GDateTime is available since GLib 2.26. + */ + +struct _GDateTime +{ + /* Microsecond timekeeping within Day */ + guint64 usec; + + /* TimeZone information */ + GTimeZone *tz; + gint interval; + + /* 1 is 0001-01-01 in Proleptic Gregorian */ + gint32 days; + + gint ref_count; /* (atomic) */ +}; + +/* Time conversion {{{1 */ + +#define UNIX_EPOCH_START 719163 +#define INSTANT_TO_UNIX(instant) \ + ((instant)/USEC_PER_SECOND - UNIX_EPOCH_START * SEC_PER_DAY) +#define INSTANT_TO_UNIX_USECS(instant) \ + ((instant) - UNIX_EPOCH_START * SEC_PER_DAY * USEC_PER_SECOND) +#define UNIX_TO_INSTANT(unix) \ + (((gint64) (unix) + UNIX_EPOCH_START * SEC_PER_DAY) * USEC_PER_SECOND) +#define UNIX_USECS_TO_INSTANT(unix_usecs) \ + ((gint64) (unix_usecs) + UNIX_EPOCH_START * SEC_PER_DAY * USEC_PER_SECOND) +#define UNIX_TO_INSTANT_IS_VALID(unix) \ + ((gint64) (unix) <= INSTANT_TO_UNIX (G_MAXINT64)) +#define UNIX_USECS_TO_INSTANT_IS_VALID(unix_usecs) \ + ((gint64) (unix_usecs) <= INSTANT_TO_UNIX_USECS (G_MAXINT64)) + +#define DAYS_IN_4YEARS 1461 /* days in 4 years */ +#define DAYS_IN_100YEARS 36524 /* days in 100 years */ +#define DAYS_IN_400YEARS 146097 /* days in 400 years */ + +#define USEC_PER_SECOND (G_GINT64_CONSTANT (1000000)) +#define USEC_PER_MINUTE (G_GINT64_CONSTANT (60000000)) +#define USEC_PER_HOUR (G_GINT64_CONSTANT (3600000000)) +#define USEC_PER_MILLISECOND (G_GINT64_CONSTANT (1000)) +#define USEC_PER_DAY (G_GINT64_CONSTANT (86400000000)) +#define SEC_PER_DAY (G_GINT64_CONSTANT (86400)) + +#define SECS_PER_MINUTE (60) +#define SECS_PER_HOUR (60 * SECS_PER_MINUTE) +#define SECS_PER_DAY (24 * SECS_PER_HOUR) +#define SECS_PER_YEAR (365 * SECS_PER_DAY) +#define SECS_PER_JULIAN (DAYS_PER_PERIOD * SECS_PER_DAY) + +#define GREGORIAN_LEAP(y) ((((y) % 4) == 0) && (!((((y) % 100) == 0) && (((y) % 400) != 0)))) +#define JULIAN_YEAR(d) ((d)->julian / 365.25) +#define DAYS_PER_PERIOD (G_GINT64_CONSTANT (2914695)) + +static const guint16 days_in_months[2][13] = +{ + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const guint16 days_in_year[2][13] = +{ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } +}; + +#ifdef HAVE_LANGINFO_TIME + +#define GET_AMPM(d) ((g_date_time_get_hour (d) < 12) ? \ + nl_langinfo (AM_STR) : \ + nl_langinfo (PM_STR)) +#define GET_AMPM_IS_LOCALE TRUE + +#define PREFERRED_DATE_TIME_FMT nl_langinfo (D_T_FMT) +#define PREFERRED_DATE_FMT nl_langinfo (D_FMT) +#define PREFERRED_TIME_FMT nl_langinfo (T_FMT) +#define PREFERRED_12HR_TIME_FMT nl_langinfo (T_FMT_AMPM) + +static const gint weekday_item[2][7] = +{ + { ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, ABDAY_1 }, + { DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7, DAY_1 } +}; + +static const gint month_item[2][12] = +{ + { ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12 }, + { MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, MON_10, MON_11, MON_12 }, +}; + +#define WEEKDAY_ABBR(d) nl_langinfo (weekday_item[0][g_date_time_get_day_of_week (d) - 1]) +#define WEEKDAY_ABBR_IS_LOCALE TRUE +#define WEEKDAY_FULL(d) nl_langinfo (weekday_item[1][g_date_time_get_day_of_week (d) - 1]) +#define WEEKDAY_FULL_IS_LOCALE TRUE +#define MONTH_ABBR(d) nl_langinfo (month_item[0][g_date_time_get_month (d) - 1]) +#define MONTH_ABBR_IS_LOCALE TRUE +#define MONTH_FULL(d) nl_langinfo (month_item[1][g_date_time_get_month (d) - 1]) +#define MONTH_FULL_IS_LOCALE TRUE + +#else + +#define GET_AMPM(d) (get_fallback_ampm (g_date_time_get_hour (d))) +#define GET_AMPM_IS_LOCALE FALSE + +/* Translators: this is the preferred format for expressing the date and the time */ +#define PREFERRED_DATE_TIME_FMT C_("GDateTime", "%a %b %e %H:%M:%S %Y") + +/* Translators: this is the preferred format for expressing the date */ +#define PREFERRED_DATE_FMT C_("GDateTime", "%m/%d/%y") + +/* Translators: this is the preferred format for expressing the time */ +#define PREFERRED_TIME_FMT C_("GDateTime", "%H:%M:%S") + +/* Translators: this is the preferred format for expressing 12 hour time */ +#define PREFERRED_12HR_TIME_FMT C_("GDateTime", "%I:%M:%S %p") + +#define WEEKDAY_ABBR(d) (get_weekday_name_abbr (g_date_time_get_day_of_week (d))) +#define WEEKDAY_ABBR_IS_LOCALE FALSE +#define WEEKDAY_FULL(d) (get_weekday_name (g_date_time_get_day_of_week (d))) +#define WEEKDAY_FULL_IS_LOCALE FALSE +/* We don't yet know if nl_langinfo (MON_n) returns standalone or complete-date + * format forms but if nl_langinfo (ALTMON_n) is not supported then we will + * have to use MONTH_FULL as standalone. The same if nl_langinfo () does not + * exist at all. MONTH_ABBR is similar: if nl_langinfo (_NL_ABALTMON_n) is not + * supported then we will use MONTH_ABBR as standalone. + */ +#define MONTH_ABBR(d) (get_month_name_abbr_standalone (g_date_time_get_month (d))) +#define MONTH_ABBR_IS_LOCALE FALSE +#define MONTH_FULL(d) (get_month_name_standalone (g_date_time_get_month (d))) +#define MONTH_FULL_IS_LOCALE FALSE + +static const gchar * +get_month_name_standalone (gint month) +{ + switch (month) + { + case 1: + /* Translators: Some languages (Baltic, Slavic, Greek, and some more) + * need different grammatical forms of month names depending on whether + * they are standalone or in a complete date context, with the day + * number. Some other languages may prefer starting with uppercase when + * they are standalone and with lowercase when they are in a complete + * date context. Here are full month names in a form appropriate when + * they are used standalone. If your system is Linux with the glibc + * version 2.27 (released Feb 1, 2018) or newer or if it is from the BSD + * family (which includes OS X) then you can refer to the date command + * line utility and see what the command `date +%OB' produces. Also in + * the latest Linux the command `locale alt_mon' in your native locale + * produces a complete list of month names almost ready to copy and + * paste here. Note that in most of the languages (western European, + * non-European) there is no difference between the standalone and + * complete date form. + */ + return C_("full month name", "January"); + case 2: + return C_("full month name", "February"); + case 3: + return C_("full month name", "March"); + case 4: + return C_("full month name", "April"); + case 5: + return C_("full month name", "May"); + case 6: + return C_("full month name", "June"); + case 7: + return C_("full month name", "July"); + case 8: + return C_("full month name", "August"); + case 9: + return C_("full month name", "September"); + case 10: + return C_("full month name", "October"); + case 11: + return C_("full month name", "November"); + case 12: + return C_("full month name", "December"); + + default: + g_warning ("Invalid month number %d", month); + } + + return NULL; +} + +static const gchar * +get_month_name_abbr_standalone (gint month) +{ + switch (month) + { + case 1: + /* Translators: Some languages need different grammatical forms of + * month names depending on whether they are standalone or in a complete + * date context, with the day number. Some may prefer starting with + * uppercase when they are standalone and with lowercase when they are + * in a full date context. However, as these names are abbreviated + * the grammatical difference is visible probably only in Belarusian + * and Russian. In other languages there is no difference between + * the standalone and complete date form when they are abbreviated. + * If your system is Linux with the glibc version 2.27 (released + * Feb 1, 2018) or newer then you can refer to the date command line + * utility and see what the command `date +%Ob' produces. Also in + * the latest Linux the command `locale ab_alt_mon' in your native + * locale produces a complete list of month names almost ready to copy + * and paste here. Note that this feature is not yet supported by any + * other platform. Here are abbreviated month names in a form + * appropriate when they are used standalone. + */ + return C_("abbreviated month name", "Jan"); + case 2: + return C_("abbreviated month name", "Feb"); + case 3: + return C_("abbreviated month name", "Mar"); + case 4: + return C_("abbreviated month name", "Apr"); + case 5: + return C_("abbreviated month name", "May"); + case 6: + return C_("abbreviated month name", "Jun"); + case 7: + return C_("abbreviated month name", "Jul"); + case 8: + return C_("abbreviated month name", "Aug"); + case 9: + return C_("abbreviated month name", "Sep"); + case 10: + return C_("abbreviated month name", "Oct"); + case 11: + return C_("abbreviated month name", "Nov"); + case 12: + return C_("abbreviated month name", "Dec"); + + default: + g_warning ("Invalid month number %d", month); + } + + return NULL; +} + +static const gchar * +get_weekday_name (gint day) +{ + switch (day) + { + case 1: + return C_("full weekday name", "Monday"); + case 2: + return C_("full weekday name", "Tuesday"); + case 3: + return C_("full weekday name", "Wednesday"); + case 4: + return C_("full weekday name", "Thursday"); + case 5: + return C_("full weekday name", "Friday"); + case 6: + return C_("full weekday name", "Saturday"); + case 7: + return C_("full weekday name", "Sunday"); + + default: + g_warning ("Invalid week day number %d", day); + } + + return NULL; +} + +static const gchar * +get_weekday_name_abbr (gint day) +{ + switch (day) + { + case 1: + return C_("abbreviated weekday name", "Mon"); + case 2: + return C_("abbreviated weekday name", "Tue"); + case 3: + return C_("abbreviated weekday name", "Wed"); + case 4: + return C_("abbreviated weekday name", "Thu"); + case 5: + return C_("abbreviated weekday name", "Fri"); + case 6: + return C_("abbreviated weekday name", "Sat"); + case 7: + return C_("abbreviated weekday name", "Sun"); + + default: + g_warning ("Invalid week day number %d", day); + } + + return NULL; +} + +#endif /* HAVE_LANGINFO_TIME */ + +#ifdef HAVE_LANGINFO_ALTMON + +/* If nl_langinfo () supports ALTMON_n then MON_n returns full date format + * forms and ALTMON_n returns standalone forms. + */ + +#define MONTH_FULL_WITH_DAY(d) MONTH_FULL(d) +#define MONTH_FULL_WITH_DAY_IS_LOCALE MONTH_FULL_IS_LOCALE + +static const gint alt_month_item[12] = +{ + ALTMON_1, ALTMON_2, ALTMON_3, ALTMON_4, ALTMON_5, ALTMON_6, + ALTMON_7, ALTMON_8, ALTMON_9, ALTMON_10, ALTMON_11, ALTMON_12 +}; + +#define MONTH_FULL_STANDALONE(d) nl_langinfo (alt_month_item[g_date_time_get_month (d) - 1]) +#define MONTH_FULL_STANDALONE_IS_LOCALE TRUE + +#else + +/* If nl_langinfo () does not support ALTMON_n then either MON_n returns + * standalone forms or nl_langinfo (MON_n) does not work so we have defined + * it as standalone form. + */ + +#define MONTH_FULL_STANDALONE(d) MONTH_FULL(d) +#define MONTH_FULL_STANDALONE_IS_LOCALE MONTH_FULL_IS_LOCALE +#define MONTH_FULL_WITH_DAY(d) (get_month_name_with_day (g_date_time_get_month (d))) +#define MONTH_FULL_WITH_DAY_IS_LOCALE FALSE + +static const gchar * +get_month_name_with_day (gint month) +{ + switch (month) + { + case 1: + /* Translators: Some languages need different grammatical forms of + * month names depending on whether they are standalone or in a full + * date context, with the day number. Some may prefer starting with + * uppercase when they are standalone and with lowercase when they are + * in a full date context. Here are full month names in a form + * appropriate when they are used in a full date context, with the + * day number. If your system is Linux with the glibc version 2.27 + * (released Feb 1, 2018) or newer or if it is from the BSD family + * (which includes OS X) then you can refer to the date command line + * utility and see what the command `date +%B' produces. Also in + * the latest Linux the command `locale mon' in your native locale + * produces a complete list of month names almost ready to copy and + * paste here. In older Linux systems due to a bug the result is + * incorrect in some languages. Note that in most of the languages + * (western European, non-European) there is no difference between the + * standalone and complete date form. + */ + return C_("full month name with day", "January"); + case 2: + return C_("full month name with day", "February"); + case 3: + return C_("full month name with day", "March"); + case 4: + return C_("full month name with day", "April"); + case 5: + return C_("full month name with day", "May"); + case 6: + return C_("full month name with day", "June"); + case 7: + return C_("full month name with day", "July"); + case 8: + return C_("full month name with day", "August"); + case 9: + return C_("full month name with day", "September"); + case 10: + return C_("full month name with day", "October"); + case 11: + return C_("full month name with day", "November"); + case 12: + return C_("full month name with day", "December"); + + default: + g_warning ("Invalid month number %d", month); + } + + return NULL; +} + +#endif /* HAVE_LANGINFO_ALTMON */ + +#ifdef HAVE_LANGINFO_ABALTMON + +/* If nl_langinfo () supports _NL_ABALTMON_n then ABMON_n returns full + * date format forms and _NL_ABALTMON_n returns standalone forms. + */ + +#define MONTH_ABBR_WITH_DAY(d) MONTH_ABBR(d) +#define MONTH_ABBR_WITH_DAY_IS_LOCALE MONTH_ABBR_IS_LOCALE + +static const gint ab_alt_month_item[12] = +{ + _NL_ABALTMON_1, _NL_ABALTMON_2, _NL_ABALTMON_3, _NL_ABALTMON_4, + _NL_ABALTMON_5, _NL_ABALTMON_6, _NL_ABALTMON_7, _NL_ABALTMON_8, + _NL_ABALTMON_9, _NL_ABALTMON_10, _NL_ABALTMON_11, _NL_ABALTMON_12 +}; + +#define MONTH_ABBR_STANDALONE(d) nl_langinfo (ab_alt_month_item[g_date_time_get_month (d) - 1]) +#define MONTH_ABBR_STANDALONE_IS_LOCALE TRUE + +#else + +/* If nl_langinfo () does not support _NL_ABALTMON_n then either ABMON_n + * returns standalone forms or nl_langinfo (ABMON_n) does not work so we + * have defined it as standalone form. Now it's time to swap. + */ + +#define MONTH_ABBR_STANDALONE(d) MONTH_ABBR(d) +#define MONTH_ABBR_STANDALONE_IS_LOCALE MONTH_ABBR_IS_LOCALE +#define MONTH_ABBR_WITH_DAY(d) (get_month_name_abbr_with_day (g_date_time_get_month (d))) +#define MONTH_ABBR_WITH_DAY_IS_LOCALE FALSE + +static const gchar * +get_month_name_abbr_with_day (gint month) +{ + switch (month) + { + case 1: + /* Translators: Some languages need different grammatical forms of + * month names depending on whether they are standalone or in a full + * date context, with the day number. Some may prefer starting with + * uppercase when they are standalone and with lowercase when they are + * in a full date context. Here are abbreviated month names in a form + * appropriate when they are used in a full date context, with the + * day number. However, as these names are abbreviated the grammatical + * difference is visible probably only in Belarusian and Russian. + * In other languages there is no difference between the standalone + * and complete date form when they are abbreviated. If your system + * is Linux with the glibc version 2.27 (released Feb 1, 2018) or newer + * then you can refer to the date command line utility and see what the + * command `date +%b' produces. Also in the latest Linux the command + * `locale abmon' in your native locale produces a complete list of + * month names almost ready to copy and paste here. In other systems + * due to a bug the result is incorrect in some languages. + */ + return C_("abbreviated month name with day", "Jan"); + case 2: + return C_("abbreviated month name with day", "Feb"); + case 3: + return C_("abbreviated month name with day", "Mar"); + case 4: + return C_("abbreviated month name with day", "Apr"); + case 5: + return C_("abbreviated month name with day", "May"); + case 6: + return C_("abbreviated month name with day", "Jun"); + case 7: + return C_("abbreviated month name with day", "Jul"); + case 8: + return C_("abbreviated month name with day", "Aug"); + case 9: + return C_("abbreviated month name with day", "Sep"); + case 10: + return C_("abbreviated month name with day", "Oct"); + case 11: + return C_("abbreviated month name with day", "Nov"); + case 12: + return C_("abbreviated month name with day", "Dec"); + + default: + g_warning ("Invalid month number %d", month); + } + + return NULL; +} + +#endif /* HAVE_LANGINFO_ABALTMON */ + +/* Format AM/PM indicator if the locale does not have a localized version. */ +static const gchar * +get_fallback_ampm (gint hour) +{ + if (hour < 12) + /* Translators: 'before midday' indicator */ + return C_("GDateTime", "AM"); + else + /* Translators: 'after midday' indicator */ + return C_("GDateTime", "PM"); +} + +static inline gint +ymd_to_days (gint year, + gint month, + gint day) +{ + gint64 days; + + days = ((gint64) year - 1) * 365 + ((year - 1) / 4) - ((year - 1) / 100) + + ((year - 1) / 400); + + days += days_in_year[0][month - 1]; + if (GREGORIAN_LEAP (year) && month > 2) + day++; + + days += day; + + return days; +} + +static void +g_date_time_get_week_number (GDateTime *datetime, + gint *week_number, + gint *day_of_week, + gint *day_of_year) +{ + gint a, b, c, d, e, f, g, n, s, month = -1, day = -1, year = -1; + + g_date_time_get_ymd (datetime, &year, &month, &day); + + if (month <= 2) + { + a = g_date_time_get_year (datetime) - 1; + b = (a / 4) - (a / 100) + (a / 400); + c = ((a - 1) / 4) - ((a - 1) / 100) + ((a - 1) / 400); + s = b - c; + e = 0; + f = day - 1 + (31 * (month - 1)); + } + else + { + a = year; + b = (a / 4) - (a / 100) + (a / 400); + c = ((a - 1) / 4) - ((a - 1) / 100) + ((a - 1) / 400); + s = b - c; + e = s + 1; + f = day + (((153 * (month - 3)) + 2) / 5) + 58 + s; + } + + g = (a + b) % 7; + d = (f + g - e) % 7; + n = f + 3 - d; + + if (week_number) + { + if (n < 0) + *week_number = 53 - ((g - s) / 5); + else if (n > 364 + s) + *week_number = 1; + else + *week_number = (n / 7) + 1; + } + + if (day_of_week) + *day_of_week = d + 1; + + if (day_of_year) + *day_of_year = f + 1; +} + +/* Lifecycle {{{1 */ + +static GDateTime * +g_date_time_alloc (GTimeZone *tz) +{ + GDateTime *datetime; + + datetime = g_slice_new0 (GDateTime); + datetime->tz = g_time_zone_ref (tz); + datetime->ref_count = 1; + + return datetime; +} + +/** + * g_date_time_ref: + * @datetime: a #GDateTime + * + * Atomically increments the reference count of @datetime by one. + * + * Returns: the #GDateTime with the reference count increased + * + * Since: 2.26 + */ +GDateTime * +g_date_time_ref (GDateTime *datetime) +{ + g_return_val_if_fail (datetime != NULL, NULL); + g_return_val_if_fail (datetime->ref_count > 0, NULL); + + g_atomic_int_inc (&datetime->ref_count); + + return datetime; +} + +/** + * g_date_time_unref: + * @datetime: a #GDateTime + * + * Atomically decrements the reference count of @datetime by one. + * + * When the reference count reaches zero, the resources allocated by + * @datetime are freed + * + * Since: 2.26 + */ +void +g_date_time_unref (GDateTime *datetime) +{ + g_return_if_fail (datetime != NULL); + g_return_if_fail (datetime->ref_count > 0); + + if (g_atomic_int_dec_and_test (&datetime->ref_count)) + { + g_time_zone_unref (datetime->tz); + g_slice_free (GDateTime, datetime); + } +} + +/* Internal state transformers {{{1 */ +/*< internal > + * g_date_time_to_instant: + * @datetime: a #GDateTime + * + * Convert a @datetime into an instant. + * + * An instant is a number that uniquely describes a particular + * microsecond in time, taking time zone considerations into account. + * (ie: "03:00 -0400" is the same instant as "02:00 -0500"). + * + * An instant is always positive but we use a signed return value to + * avoid troubles with C. + */ +static gint64 +g_date_time_to_instant (GDateTime *datetime) +{ + gint64 offset; + + offset = g_time_zone_get_offset (datetime->tz, datetime->interval); + offset *= USEC_PER_SECOND; + + return datetime->days * USEC_PER_DAY + datetime->usec - offset; +} + +/*< internal > + * g_date_time_from_instant: + * @tz: a #GTimeZone + * @instant: an instant in time + * + * Creates a #GDateTime from a time zone and an instant. + * + * This might fail if the time ends up being out of range. + */ +static GDateTime * +g_date_time_from_instant (GTimeZone *tz, + gint64 instant) +{ + GDateTime *datetime; + gint64 offset; + + if (instant < 0 || instant > G_GINT64_CONSTANT (1000000000000000000)) + return NULL; + + datetime = g_date_time_alloc (tz); + datetime->interval = g_time_zone_find_interval (tz, + G_TIME_TYPE_UNIVERSAL, + INSTANT_TO_UNIX (instant)); + offset = g_time_zone_get_offset (datetime->tz, datetime->interval); + offset *= USEC_PER_SECOND; + + instant += offset; + + datetime->days = instant / USEC_PER_DAY; + datetime->usec = instant % USEC_PER_DAY; + + if (datetime->days < 1 || 3652059 < datetime->days) + { + g_date_time_unref (datetime); + datetime = NULL; + } + + return datetime; +} + + +/*< internal > + * g_date_time_deal_with_date_change: + * @datetime: a #GDateTime + * + * This function should be called whenever the date changes by adding + * days, months or years. It does three things. + * + * First, we ensure that the date falls between 0001-01-01 and + * 9999-12-31 and return %FALSE if it does not. + * + * Next we update the ->interval field. + * + * Finally, we ensure that the resulting date and time pair exists (by + * ensuring that our time zone has an interval containing it) and + * adjusting as required. For example, if we have the time 02:30:00 on + * March 13 2010 in Toronto and we add 1 day to it, we would end up with + * 2:30am on March 14th, which doesn't exist. In that case, we bump the + * time up to 3:00am. + */ +static gboolean +g_date_time_deal_with_date_change (GDateTime *datetime) +{ + GTimeType was_dst; + gint64 full_time; + gint64 usec; + + if (datetime->days < 1 || datetime->days > 3652059) + return FALSE; + + was_dst = g_time_zone_is_dst (datetime->tz, datetime->interval); + + full_time = datetime->days * USEC_PER_DAY + datetime->usec; + + + usec = full_time % USEC_PER_SECOND; + full_time /= USEC_PER_SECOND; + full_time -= UNIX_EPOCH_START * SEC_PER_DAY; + + datetime->interval = g_time_zone_adjust_time (datetime->tz, + was_dst, + &full_time); + full_time += UNIX_EPOCH_START * SEC_PER_DAY; + full_time *= USEC_PER_SECOND; + full_time += usec; + + datetime->days = full_time / USEC_PER_DAY; + datetime->usec = full_time % USEC_PER_DAY; + + /* maybe daylight time caused us to shift to a different day, + * but it definitely didn't push us into a different year */ + return TRUE; +} + +static GDateTime * +g_date_time_replace_days (GDateTime *datetime, + gint days) +{ + GDateTime *new; + + new = g_date_time_alloc (datetime->tz); + new->interval = datetime->interval; + new->usec = datetime->usec; + new->days = days; + + if (!g_date_time_deal_with_date_change (new)) + { + g_date_time_unref (new); + new = NULL; + } + + return new; +} + +/* now/unix/timeval Constructors {{{1 */ + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +/*< internal > + * g_date_time_new_from_timeval: + * @tz: a #GTimeZone + * @tv: a #GTimeVal + * + * Creates a #GDateTime corresponding to the given #GTimeVal @tv in the + * given time zone @tz. + * + * The time contained in a #GTimeVal is always stored in the form of + * seconds elapsed since 1970-01-01 00:00:00 UTC, regardless of the + * given time zone. + * + * This call can fail (returning %NULL) if @tv represents a time outside + * of the supported range of #GDateTime. + * + * You should release the return value by calling g_date_time_unref() + * when you are done with it. + * + * Returns: a new #GDateTime, or %NULL + * + * Since: 2.26 + **/ +static GDateTime * +g_date_time_new_from_timeval (GTimeZone *tz, + const GTimeVal *tv) +{ + gint64 tv_sec = tv->tv_sec; + + if (tv_sec > G_MAXINT64 - 1 || !UNIX_TO_INSTANT_IS_VALID (tv_sec + 1)) + return NULL; + + return g_date_time_from_instant (tz, tv->tv_usec + + UNIX_TO_INSTANT (tv->tv_sec)); +} +G_GNUC_END_IGNORE_DEPRECATIONS + +/*< internal > + * g_date_time_new_from_unix: + * @tz: a #GTimeZone + * @usecs: the Unix time, in microseconds since the epoch + * + * Creates a #GDateTime corresponding to the given Unix time @t_us in the + * given time zone @tz. + * + * Unix time is the number of seconds that have elapsed since 1970-01-01 + * 00:00:00 UTC, regardless of the time zone given. + * + * This call can fail (returning %NULL) if @t represents a time outside + * of the supported range of #GDateTime. + * + * You should release the return value by calling g_date_time_unref() + * when you are done with it. + * + * Returns: a new #GDateTime, or %NULL + * + * Since: 2.26 + **/ +static GDateTime * +g_date_time_new_from_unix (GTimeZone *tz, + gint64 usecs) +{ + if (!UNIX_USECS_TO_INSTANT_IS_VALID (usecs)) + return NULL; + + return g_date_time_from_instant (tz, UNIX_USECS_TO_INSTANT (usecs)); +} + +/** + * g_date_time_new_now: (constructor) + * @tz: a #GTimeZone + * + * Creates a #GDateTime corresponding to this exact instant in the given + * 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 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. + * + * Returns: (transfer full) (nullable): a new #GDateTime, or %NULL + * + * Since: 2.26 + **/ +GDateTime * +g_date_time_new_now (GTimeZone *tz) +{ + gint64 now_us; + + now_us = g_get_real_time (); + + return g_date_time_new_from_unix (tz, now_us); +} + +/** + * g_date_time_new_now_local: (constructor) + * + * Creates a #GDateTime corresponding to this exact instant in the local + * time zone. + * + * This is equivalent to calling g_date_time_new_now() with the time + * zone returned by g_time_zone_new_local(). + * + * Returns: (transfer full) (nullable): a new #GDateTime, or %NULL + * + * Since: 2.26 + **/ +GDateTime * +g_date_time_new_now_local (void) +{ + GDateTime *datetime; + GTimeZone *local; + + local = g_time_zone_new_local (); + datetime = g_date_time_new_now (local); + g_time_zone_unref (local); + + return datetime; +} + +/** + * g_date_time_new_now_utc: (constructor) + * + * Creates a #GDateTime corresponding to this exact instant in UTC. + * + * This is equivalent to calling g_date_time_new_now() with the time + * zone returned by g_time_zone_new_utc(). + * + * Returns: (transfer full) (nullable): a new #GDateTime, or %NULL + * + * Since: 2.26 + **/ +GDateTime * +g_date_time_new_now_utc (void) +{ + GDateTime *datetime; + GTimeZone *utc; + + utc = g_time_zone_new_utc (); + datetime = g_date_time_new_now (utc); + g_time_zone_unref (utc); + + return datetime; +} + +/** + * g_date_time_new_from_unix_local: (constructor) + * @t: the Unix time + * + * Creates a #GDateTime corresponding to the given Unix time @t in the + * local time zone. + * + * Unix time is the number of seconds that have elapsed since 1970-01-01 + * 00:00:00 UTC, regardless of the local time offset. + * + * This call can fail (returning %NULL) if @t represents a time outside + * of the supported range of #GDateTime. + * + * You should release the return value by calling g_date_time_unref() + * when you are done with it. + * + * Returns: (transfer full) (nullable): a new #GDateTime, or %NULL + * + * Since: 2.26 + **/ +GDateTime * +g_date_time_new_from_unix_local (gint64 t) +{ + GDateTime *datetime; + GTimeZone *local; + + if (t > G_MAXINT64 / USEC_PER_SECOND || + t < G_MININT64 / USEC_PER_SECOND) + return NULL; + + local = g_time_zone_new_local (); + datetime = g_date_time_new_from_unix (local, t * USEC_PER_SECOND); + g_time_zone_unref (local); + + return datetime; +} + +/** + * g_date_time_new_from_unix_utc: (constructor) + * @t: the Unix time + * + * Creates a #GDateTime corresponding to the given Unix time @t in UTC. + * + * Unix time is the number of seconds that have elapsed since 1970-01-01 + * 00:00:00 UTC. + * + * This call can fail (returning %NULL) if @t represents a time outside + * of the supported range of #GDateTime. + * + * You should release the return value by calling g_date_time_unref() + * when you are done with it. + * + * Returns: (transfer full) (nullable): a new #GDateTime, or %NULL + * + * Since: 2.26 + **/ +GDateTime * +g_date_time_new_from_unix_utc (gint64 t) +{ + GDateTime *datetime; + GTimeZone *utc; + + if (t > G_MAXINT64 / USEC_PER_SECOND || + t < G_MININT64 / USEC_PER_SECOND) + return NULL; + + utc = g_time_zone_new_utc (); + datetime = g_date_time_new_from_unix (utc, t * USEC_PER_SECOND); + g_time_zone_unref (utc); + + return datetime; +} + +/** + * g_date_time_new_from_timeval_local: (constructor) + * @tv: a #GTimeVal + * + * Creates a #GDateTime corresponding to the given #GTimeVal @tv in the + * local time zone. + * + * The time contained in a #GTimeVal is always stored in the form of + * seconds elapsed since 1970-01-01 00:00:00 UTC, regardless of the + * local time offset. + * + * This call can fail (returning %NULL) if @tv represents a time outside + * of the supported range of #GDateTime. + * + * You should release the return value by calling g_date_time_unref() + * when you are done with it. + * + * Returns: (transfer full) (nullable): a new #GDateTime, or %NULL + * + * Since: 2.26 + * Deprecated: 2.62: #GTimeVal is not year-2038-safe. Use + * g_date_time_new_from_unix_local() instead. + **/ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +GDateTime * +g_date_time_new_from_timeval_local (const GTimeVal *tv) +{ + GDateTime *datetime; + GTimeZone *local; + + local = g_time_zone_new_local (); + datetime = g_date_time_new_from_timeval (local, tv); + g_time_zone_unref (local); + + return datetime; +} +G_GNUC_END_IGNORE_DEPRECATIONS + +/** + * g_date_time_new_from_timeval_utc: (constructor) + * @tv: a #GTimeVal + * + * Creates a #GDateTime corresponding to the given #GTimeVal @tv in UTC. + * + * The time contained in a #GTimeVal is always stored in the form of + * seconds elapsed since 1970-01-01 00:00:00 UTC. + * + * This call can fail (returning %NULL) if @tv represents a time outside + * of the supported range of #GDateTime. + * + * You should release the return value by calling g_date_time_unref() + * when you are done with it. + * + * Returns: (transfer full) (nullable): a new #GDateTime, or %NULL + * + * Since: 2.26 + * Deprecated: 2.62: #GTimeVal is not year-2038-safe. Use + * g_date_time_new_from_unix_utc() instead. + **/ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +GDateTime * +g_date_time_new_from_timeval_utc (const GTimeVal *tv) +{ + GDateTime *datetime; + GTimeZone *utc; + + utc = g_time_zone_new_utc (); + datetime = g_date_time_new_from_timeval (utc, tv); + g_time_zone_unref (utc); + + return datetime; +} +G_GNUC_END_IGNORE_DEPRECATIONS + +/* Parse integers in the form d (week days), dd (hours etc), ddd (ordinal days) or dddd (years) */ +static gboolean +get_iso8601_int (const gchar *text, gsize length, gint *value) +{ + gsize i; + guint v = 0; + + if (length < 1 || length > 4) + return FALSE; + + for (i = 0; i < length; i++) + { + const gchar c = text[i]; + if (c < '0' || c > '9') + return FALSE; + v = v * 10 + (c - '0'); + } + + *value = v; + return TRUE; +} + +/* Parse seconds in the form ss or ss.sss (variable length decimal) */ +static gboolean +get_iso8601_seconds (const gchar *text, gsize length, gdouble *value) +{ + gsize i; + guint64 divisor = 1, v = 0; + + if (length < 2) + return FALSE; + + for (i = 0; i < 2; i++) + { + const gchar c = text[i]; + if (c < '0' || c > '9') + return FALSE; + v = v * 10 + (c - '0'); + } + + 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; + + for (; i < length; i++) + { + const gchar c = text[i]; + if (c < '0' || c > '9' || + v > (G_MAXUINT64 - (c - '0')) / 10 || + divisor > G_MAXUINT64 / 10) + return FALSE; + v = v * 10 + (c - '0'); + divisor *= 10; + } + + *value = (gdouble) v / divisor; + return TRUE; +} + +static GDateTime * +g_date_time_new_ordinal (GTimeZone *tz, gint year, gint ordinal_day, gint hour, gint minute, gdouble seconds) +{ + GDateTime *dt; + + if (ordinal_day < 1 || ordinal_day > (GREGORIAN_LEAP (year) ? 366 : 365)) + return NULL; + + dt = g_date_time_new (tz, year, 1, 1, hour, minute, seconds); + if (dt == NULL) + return NULL; + dt->days += ordinal_day - 1; + + return dt; +} + +static GDateTime * +g_date_time_new_week (GTimeZone *tz, gint year, gint week, gint week_day, gint hour, gint minute, gdouble seconds) +{ + gint64 p; + gint max_week, jan4_week_day, ordinal_day; + GDateTime *dt; + + p = (year * 365 + (year / 4) - (year / 100) + (year / 400)) % 7; + max_week = p == 4 ? 53 : 52; + + if (week < 1 || week > max_week || week_day < 1 || week_day > 7) + return NULL; + + dt = g_date_time_new (tz, year, 1, 4, 0, 0, 0); + if (dt == NULL) + return NULL; + g_date_time_get_week_number (dt, NULL, &jan4_week_day, NULL); + g_date_time_unref (dt); + + ordinal_day = (week * 7) + week_day - (jan4_week_day + 3); + if (ordinal_day < 0) + { + year--; + ordinal_day += GREGORIAN_LEAP (year) ? 366 : 365; + } + else if (ordinal_day > (GREGORIAN_LEAP (year) ? 366 : 365)) + { + ordinal_day -= (GREGORIAN_LEAP (year) ? 366 : 365); + year++; + } + + return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds); +} + +static GDateTime * +parse_iso8601_date (const gchar *text, gsize length, + gint hour, gint minute, gdouble seconds, GTimeZone *tz) +{ + /* YYYY-MM-DD */ + if (length == 10 && text[4] == '-' && text[7] == '-') + { + int year, month, day; + if (!get_iso8601_int (text, 4, &year) || + !get_iso8601_int (text + 5, 2, &month) || + !get_iso8601_int (text + 8, 2, &day)) + return NULL; + return g_date_time_new (tz, year, month, day, hour, minute, seconds); + } + /* YYYY-DDD */ + else if (length == 8 && text[4] == '-') + { + gint year, ordinal_day; + if (!get_iso8601_int (text, 4, &year) || + !get_iso8601_int (text + 5, 3, &ordinal_day)) + return NULL; + return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds); + } + /* YYYY-Www-D */ + else if (length == 10 && text[4] == '-' && text[5] == 'W' && text[8] == '-') + { + gint year, week, week_day; + if (!get_iso8601_int (text, 4, &year) || + !get_iso8601_int (text + 6, 2, &week) || + !get_iso8601_int (text + 9, 1, &week_day)) + return NULL; + return g_date_time_new_week (tz, year, week, week_day, hour, minute, seconds); + } + /* YYYYWwwD */ + else if (length == 8 && text[4] == 'W') + { + gint year, week, week_day; + if (!get_iso8601_int (text, 4, &year) || + !get_iso8601_int (text + 5, 2, &week) || + !get_iso8601_int (text + 7, 1, &week_day)) + return NULL; + return g_date_time_new_week (tz, year, week, week_day, hour, minute, seconds); + } + /* YYYYMMDD */ + else if (length == 8) + { + int year, month, day; + if (!get_iso8601_int (text, 4, &year) || + !get_iso8601_int (text + 4, 2, &month) || + !get_iso8601_int (text + 6, 2, &day)) + return NULL; + return g_date_time_new (tz, year, month, day, hour, minute, seconds); + } + /* YYYYDDD */ + else if (length == 7) + { + gint year, ordinal_day; + if (!get_iso8601_int (text, 4, &year) || + !get_iso8601_int (text + 4, 3, &ordinal_day)) + return NULL; + return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds); + } + else + return FALSE; +} + +static GTimeZone * +parse_iso8601_timezone (const gchar *text, gsize length, gssize *tz_offset) +{ + gint i, tz_length, offset_hours, offset_minutes; + gint offset_sign = 1; + GTimeZone *tz; + + /* UTC uses Z suffix */ + if (length > 0 && text[length - 1] == 'Z') + { + *tz_offset = length - 1; + return g_time_zone_new_utc (); + } + + /* Look for '+' or '-' of offset */ + for (i = length - 1; i >= 0; i--) + if (text[i] == '+' || text[i] == '-') + { + offset_sign = text[i] == '-' ? -1 : 1; + break; + } + if (i < 0) + return NULL; + tz_length = length - i; + + /* +hh:mm or -hh:mm */ + if (tz_length == 6 && text[i+3] == ':') + { + if (!get_iso8601_int (text + i + 1, 2, &offset_hours) || + !get_iso8601_int (text + i + 4, 2, &offset_minutes)) + return NULL; + } + /* +hhmm or -hhmm */ + else if (tz_length == 5) + { + if (!get_iso8601_int (text + i + 1, 2, &offset_hours) || + !get_iso8601_int (text + i + 3, 2, &offset_minutes)) + return NULL; + } + /* +hh or -hh */ + else if (tz_length == 3) + { + if (!get_iso8601_int (text + i + 1, 2, &offset_hours)) + return NULL; + offset_minutes = 0; + } + else + return NULL; + + *tz_offset = i; + tz = g_time_zone_new_identifier (text + i); + + /* Double-check that the GTimeZone matches our interpretation of the timezone. + * This can fail because our interpretation is less strict than (for example) + * parse_time() in gtimezone.c, which restricts the range of the parsed + * integers. */ + if (tz == NULL || g_time_zone_get_offset (tz, 0) != offset_sign * (offset_hours * 3600 + offset_minutes * 60)) + { + g_clear_pointer (&tz, g_time_zone_unref); + return NULL; + } + + return tz; +} + +static gboolean +parse_iso8601_time (const gchar *text, gsize length, + gint *hour, gint *minute, gdouble *seconds, GTimeZone **tz) +{ + gssize tz_offset = -1; + + /* Check for timezone suffix */ + *tz = parse_iso8601_timezone (text, length, &tz_offset); + if (tz_offset >= 0) + length = tz_offset; + + /* hh:mm:ss(.sss) */ + if (length >= 8 && text[2] == ':' && text[5] == ':') + { + return get_iso8601_int (text, 2, hour) && + get_iso8601_int (text + 3, 2, minute) && + get_iso8601_seconds (text + 6, length - 6, seconds); + } + /* hhmmss(.sss) */ + else if (length >= 6) + { + return get_iso8601_int (text, 2, hour) && + get_iso8601_int (text + 2, 2, minute) && + get_iso8601_seconds (text + 4, length - 4, seconds); + } + else + return FALSE; +} + +/** + * g_date_time_new_from_iso8601: (constructor) + * @text: an ISO 8601 formatted time string. + * @default_tz: (nullable): a #GTimeZone to use if the text doesn't contain a + * timezone, or %NULL. + * + * 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