Random postitions when using g_seekable_seek

I’m updating an old gtk2 application to gtk3. First time using gtk3. The old app used gnome_vfs, and I’ve been using gio as the replacement. Everything finally compiles and I’m starting to debug. So far so good. Until day before yesterday when I reached a part in the code that tries to seek to offset 700 in a file. Instead of positioning to 700, it positions to +1 past the furthest point read in the file (179 as a recall in that case).

After much hair pulling it came down to not seeking to the correct location. So I changed the internal code that opens files to include a simple test every time a file is opened and it doesn’t appear to matter what the file is, it won’t seek. Reads work fine.

Here is the code (minus return value checks, no errors returned in my tests, but reduces code complexity for easier viewing here):

fh->gfile = g_file_new_for_path(filename);
fh->f.inputStream = g_file_read(fh->gfile, NULL, NULL);
printf("TEST: Open fpos:%ld\n", g_seekable_tell(G_SEEKABLE(fh->f.inputStream)));

g_seekable_seek(G_SEEKABLE(fh->f.inputStream), G_SEEK_SET, 700, NULL, NULL);
printf("TEST: After SET 700 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(fh->f.inputStream)));

g_seekable_seek(G_SEEKABLE(fh->f.inputStream), G_SEEK_END, 0, NULL, NULL);
printf("TEST: After END 0 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(fh->f.inputStream)));

g_seekable_seek(G_SEEKABLE(fh->f.inputStream), G_SEEK_SET, 700, NULL, NULL);
printf("TEST: After second SET 700 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(fh->f.inputStream)));

g_seekable_seek(G_SEEKABLE(fh->f.inputStream), G_SEEK_END, 0, NULL, NULL);
printf("TEST: After second END 0 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(fh->f.inputStream)));

g_seekable_seek(G_SEEKABLE(fh->f.inputStream), G_SEEK_SET, 700, NULL, NULL);
printf("TEST: After third SET 700 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(fh->f.inputStream)));

g_seekable_seek(G_SEEKABLE(fh->f.inputStream), G_SEEK_SET, 0, NULL, NULL);
printf("TEST: After SET 0 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(fh->f.inputStream)));

Without fail, what I get for every file opened is:
TEST: Open fpos:0
TEST: After SET 700 fpos:1
TEST: After END 0 fpos:3
TEST: After second SET 700 fpos:4
TEST: After second END 0 fpos:6
TEST: After third SET 700 fpos:7
TEST: After SET 0 fpos:8

It is as if it some some kind of linear stream. Is g_file_read not the correct call to open a file on disk? I feel like this is some kind of too close to see the problem.

Thanks for any and all help!

So I created a simple test program with basically just the above code preceded with a gtk_init() and compiled with gcc -Wall -g test.c -o test `pkg-config --cflags gtk±3.0 --libs gtk±3.0`

When I run it, it gives me the exact same output showing that g_seekable_seek() is not able to seek properly. I even threw in a g_seekable_can_seek() and it returns true as expected.

I can’t believe this is a bug in gio, so someone please show it is me!

This is my full test.c.

/* Simple Test of g_seekable_seek
 * build with:  gcc -Wall -g test.c -o test `pkg-config --cflags gtk+-3.0 --libs gtk+-3.0`
* run with: ./test test.c
 */

#include <gtk/gtk.h>

int main (int argc, char *argv[])
{
 GFile *gfile;
 GFileInputStream* inputStream;
 const char *filename;

	gtk_init(&argc, &argv);

	if (argc >= 2)
	{
		filename = argv[1];
		printf("Opening file %s\n", filename);

		if ((gfile = g_file_new_for_path(filename)))
		{
			if ((inputStream = g_file_read(gfile, NULL, NULL)))
			{
				printf("TEST: Can seek? %d\n", g_seekable_can_seek(G_SEEKABLE(inputStream)));

				printf("TEST: Initial fpos:%ld\n", g_seekable_tell(G_SEEKABLE(inputStream)));

				if (g_seekable_seek(G_SEEKABLE(inputStream), G_SEEK_SET, 700, NULL, NULL))
				{
					printf("TEST: After SET 700 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(inputStream)));

					if (g_seekable_seek(G_SEEKABLE(inputStream), G_SEEK_END, 0, NULL, NULL))
					{
						printf("TEST: After END 0 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(inputStream)));

						if (g_seekable_seek(G_SEEKABLE(inputStream), G_SEEK_SET, 700, NULL, NULL))
						{
							printf("TEST: After second SET 700 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(inputStream)));

							if (g_seekable_seek(G_SEEKABLE(inputStream), G_SEEK_END, 0, NULL, NULL))
							{
								printf("TEST: After second END 0 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(inputStream)));

								if (g_seekable_seek(G_SEEKABLE(inputStream), G_SEEK_SET, 700, NULL, NULL))
								{
									printf("TEST: After third SET 700 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(inputStream)));

									if (g_seekable_seek(G_SEEKABLE(inputStream), G_SEEK_SET, 0, NULL, NULL))
									{
										printf("TEST: After SET 0 fpos:%ld\n", g_seekable_tell(G_SEEKABLE(inputStream)));
									}
								}
							}
						}
					}
				}

				g_input_stream_close(G_INPUT_STREAM(inputStream), NULL, NULL);
			}
			g_object_unref(gfile);
		}
	}

	return 0;
}

I played around with this a bit, I’m also confused - but possibly overlooking something

Try running under strace -e trace=file and see if the syscalls make sense.

#include <gio/gio.h>

int
main (int argc, char *argv[])
{
  g_autoptr (GFile) file = NULL;
  g_autoptr (GFileInputStream) stream = NULL;
  g_autoptr (GError) error = NULL;

  file = g_file_new_for_path ("seek-test.c");
  stream = g_file_read (file, NULL, &error);
  if (error) {
    g_error ("read: %s", error->message);
  }

  g_print ("TEST: Open fpos: %ld seekable? %i\n",
           g_seekable_tell (G_SEEKABLE (stream)),
           g_seekable_can_seek (G_SEEKABLE (stream)));

  g_seekable_seek (G_SEEKABLE (stream), G_SEEK_SET, 700, NULL, &error);
  if (error) {
    g_error ("read: %s", error->message);
  }
  g_print ("TEST: After SET 700 fpos:%ld\n",
           g_seekable_tell (G_SEEKABLE (stream)));

  g_seekable_seek (G_SEEKABLE (stream), G_SEEK_END, 0, NULL, &error);
  if (error) {
    g_error ("read: %s", error->message);
  }
  g_print ("TEST: After END 0 fpos:%ld\n",
           g_seekable_tell (G_SEEKABLE (stream)));

  g_seekable_seek (G_SEEKABLE (stream), G_SEEK_SET, 700, NULL, &error);
  if (error) {
    g_error ("read: %s", error->message);
  }
  g_print ("TEST: After second SET 700 fpos:%ld\n",
           g_seekable_tell (G_SEEKABLE (stream)));

  g_seekable_seek (G_SEEKABLE (stream), G_SEEK_END, 0, NULL, &error);
  if (error) {
    g_error ("read: %s", error->message);
  }
  g_print ("TEST: After second END 0 fpos:%ld\n",
           g_seekable_tell (G_SEEKABLE (stream)));

  g_seekable_seek (G_SEEKABLE (stream), G_SEEK_SET, 700, NULL, &error);
  if (error) {
    g_error ("read: %s", error->message);
  }
  g_print ("TEST: After third SET 700 fpos:%ld\n",
           g_seekable_tell (G_SEEKABLE (stream)));

  g_seekable_seek (G_SEEKABLE (stream), G_SEEK_SET, 0, NULL, &error);
  if (error) {
    g_error ("read: %s", error->message);
  }
  g_print ("TEST: After SET 0 fpos:%ld\n",
           g_seekable_tell (G_SEEKABLE (stream)));

  return 0;
}
[…]
openat(AT_FDCWD, "/home/zbrown/seek-test.c", O_RDONLY) = 7
statx(7, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_TYPE, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0644, stx_size=1573, ...}) = 0
futex(0x7fe54c6230c8, FUTEX_WAKE_PRIVATE, 2147483647) = 0
futex(0x7fe54c6230c8, FUTEX_WAKE_PRIVATE, 2147483647) = 0
lseek(7, 0, SEEK_CUR)                   = 0
lseek(7, 0, SEEK_CUR)                   = 0
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x3), ...}, AT_EMPTY_PATH) = 0
write(1, "TEST: Open fpos: 0 seekable? 1\n", 31TEST: Open fpos: 0 seekable? 1
) = 31
lseek(7, 1, SEEK_CUR)                   = 1
lseek(7, 0, SEEK_CUR)                   = 1
write(1, "TEST: After SET 700 fpos:1\n", 27TEST: After SET 700 fpos:1
) = 27
lseek(7, 2, SEEK_CUR)                   = 3
lseek(7, 0, SEEK_CUR)                   = 3
write(1, "TEST: After END 0 fpos:3\n", 25TEST: After END 0 fpos:3
) = 25
lseek(7, 1, SEEK_CUR)                   = 4
lseek(7, 0, SEEK_CUR)                   = 4
write(1, "TEST: After second SET 700 fpos:"..., 34TEST: After second SET 700 fpos:4
) = 34
lseek(7, 2, SEEK_CUR)                   = 6
lseek(7, 0, SEEK_CUR)                   = 6
write(1, "TEST: After second END 0 fpos:6\n", 32TEST: After second END 0 fpos:6
) = 32
lseek(7, 1, SEEK_CUR)                   = 7
lseek(7, 0, SEEK_CUR)                   = 7
write(1, "TEST: After third SET 700 fpos:7"..., 33TEST: After third SET 700 fpos:7
) = 33
lseek(7, 1, SEEK_CUR)                   = 8
lseek(7, 0, SEEK_CUR)                   = 8
write(1, "TEST: After SET 0 fpos:8\n", 25TEST: After SET 0 fpos:8
) = 25
close(7)                                = 0
exit_group(0)                           = ?

So the lseek(7, 0, SEEK_CUR)s are _tell() so that figures, but why is G_SEEK_SET, 700 mapped to lseek(7, 1, SEEK_CUR)?

And G_SEEK_END, 0 is mapped to lseek(7, 2, SEEK_CUR)?

It looks to me like something is swapping goffset offset and GSeekType type, and then local seek_type_to_lseek() is flatting the 700 in the case default to SEEK_CUR.

And there you have it. I’m the something. The parameters are swapped in my calls.

I called it in my original post. “I feel like this is some kind of too close to see the problem.”

Thanks to both of you for your posts, and sorry to waste your time. Feeling pretty stupid now. My original error came about because the original call was gnome_vfs_seek(file, type, offset). I can’t wait to do this all over again when I update to gtk4… Please tell me things are not so different :slight_smile:

:woman_facepalming:

Well now I feel silly

GIO is independent of Gtk so it’s the same no matter if your using Gtk2, 3, 4 (or not at all)

Once you migrate to GIO, you’re set: everything, these days, uses it, so no more migrations necessary. :grinning_face_with_smiling_eyes:

For GTK3 → GTK4, there’s a whole migration guide.

Glad you managed to work out the problem.

It’s possible that compiling with -Wenum-conversion would have caught this error (although I haven’t tested and am only guessing). It’s enabled by -Wall -Wextra, which you might want to consider enabling in your build to catch things like this in future.

Sadly, it does not catch it. Thanks for the suggestion!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.