Richard W.M. Jones
2009-Oct-26 09:19 UTC
[Libguestfs] [PATCH 0/3] Handle NTFS 3g case sensitive Windows paths in the daemon
In the current Perl library we have a function called resolve_windows_path which handles the useful [for Windows guests] case where we need to resolve the true, case-sensitive path behind a case-insensitive Windows path. For example, a Windows path like "C:\Windows\System32" can be presented to Linux by NTFS 3g in a variety of ways, eg: /WINDOWS/system32 /WINDOWS/SYSTEM32 depending entirely on the inconsequential details of how it was originally created. Windows doesn't care about case sensitivity, but NTFS 3g does. Since resolve_windows_path is a very useful function, the first patch moves it into the daemon. The new API is called "guestfs_case_sensitive_path". The second patch adds some useful functionality to guestfish, so you can use Windows-style paths, like this: touch win:C:\windows\foo (Note that all ordinary paths must begin with a '/' char, so this can't conflict with any existing, working scripts). The third patch deprecates resolve_windows_path in the Perl code and replaces it with calls to $g->case_sensitive_path. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
Richard W.M. Jones
2009-Oct-26 09:20 UTC
[Libguestfs] [PATCH 1/3] New API: case-sensitive-path to return case sensitive path on NTFS 3g fs
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw -------------- next part -------------->From 36a0fc94e218706cb0ae10178d62c801fb875430 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 26 Oct 2009 08:20:00 +0000 Subject: [PATCH 1/3] New API: case-sensitive-path to return case sensitive path on NTFS 3g fs This function handles an annoyance/peculiarity of the Linux NTFS 3g driver, which is that it exports NTFS filesystems with names case sensitive, even though under Windows they would be case insensitive. This causes problems because the location of (eg.) c:\windows might appear as /windows or /WINDOWS (etc) depending on the inconsequential details of how it was originally created. Example of this problem on a real Windows guest: ><fs> file /windows/system32/config/system.log libguestfs: error: file: access: /windows/system32/config/system.log: No such file or directory ><fs> case-sensitive-path /windows/system32/config/system.log /WINDOWS/system32/config/system.LOG ><fs> file /WINDOWS/system32/config/system.LOG MS Windows registry file, NT/2000 or above --- daemon/realpath.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/MAX_PROC_NR | 2 +- src/generator.ml | 39 ++++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletions(-) diff --git a/daemon/realpath.c b/daemon/realpath.c index 706af42..c638a21 100644 --- a/daemon/realpath.c +++ b/daemon/realpath.c @@ -23,6 +23,10 @@ #include <string.h> #include <unistd.h> #include <limits.h> +#include <sys/types.h> +#include <dirent.h> + +#include "ignore-value.h" #include "daemon.h" #include "actions.h" @@ -42,3 +46,111 @@ do_realpath (const char *path) return ret; /* caller frees */ } + +char * +do_case_sensitive_path (const char *path) +{ + char ret[PATH_MAX+1] = "/"; + size_t next = 1; + + /* MUST chdir ("/") before leaving this function. */ + if (chdir (sysroot) == -1) { + reply_with_perror ("%s", sysroot); + return NULL; + } + + /* First character is a '/'. Take each subsequent path element + * and follow it. + */ + path++; + while (*path) { + size_t i = strcspn (path, "/"); + if (i == 0) { /* "//" in path */ + path++; + continue; + } + if ((i == 1 && path[0] == '.') || + (i == 2 && path[0] == '.' && path[1] == '.')) { + reply_with_error ("case_sensitive_path: path contained . or .. elements"); + goto error; + } + if (i > NAME_MAX) { + reply_with_error ("case_sensitive_path: path element too long"); + goto error; + } + + char name[NAME_MAX+1]; + memcpy (name, path, i); + name[i] = '\0'; + + /* Skip to next element in path (for the next loop iteration). */ + path += i; + if (*path == '/') path++; + + /* Read the current directory looking (case insensitively) for + * this element of the path. + */ + DIR *dir = opendir ("."); + if (dir == NULL) { + reply_with_perror ("opendir"); + goto error; + } + + struct dirent *d = NULL; + + while ((d = readdir (dir)) != NULL) { + if (strcasecmp (d->d_name, name) == 0) + break; + } + + if (closedir (dir) == -1) { + reply_with_perror ("closedir"); + goto error; + } + + if (d == NULL) { + reply_with_error ("%s: no file or directory found with this name", name); + goto error; + } + + /* Add the real name of this path element to the return value. */ + if (next > 1) + ret[next++] = '/'; + + i = strlen (d->d_name); + if (next + i >= PATH_MAX) { + reply_with_error ("final path too long"); + goto error; + } + + strcpy (&ret[next], d->d_name); + next += i; + + /* Is it a directory? Try going into it. */ + if (chdir (d->d_name) == 0) + continue; + + /* This is OK provided we've reached the end of the path. */ + if (errno == ENOTDIR) { + if (*path) { + reply_with_error ("non-directory element in path"); + goto error; + } + break; + } + } + + ignore_value (chdir ("/")); + + ret[next] = '\0'; + char *retp = strdup (ret); + if (retp == NULL) { + reply_with_perror ("strdup"); + return NULL; + } + return retp; /* caller frees */ + + error: + ignore_value (chdir ("/")); + return NULL; +} diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR index 0f11735..5381652 100644 --- a/src/MAX_PROC_NR +++ b/src/MAX_PROC_NR @@ -1 +1 @@ -196 +197 diff --git a/src/generator.ml b/src/generator.ml index cea5178..668002e 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -3645,6 +3645,45 @@ The result list is not sorted. =back"); + ("case_sensitive_path", (RString "rpath", [Pathname "path"]), 197, [], + [InitISOFS, Always, TestOutput ( + [["case_sensitive_path"; "/DIRECTORY"]], "/directory"); + InitISOFS, Always, TestOutput ( + [["case_sensitive_path"; "/Known-1"]], "/known-1"); + InitBasicFS, Always, TestOutput ( + [["mkdir"; "/a"]; + ["mkdir"; "/a/bbb"]; + ["touch"; "/a/bbb/c"]; + ["case_sensitive_path"; "/A/bbB/C"]], "/a/bbb/c")], + "return true path on case-insensitive filesystem", + "\ +This command handles a peculiarity of the Linux ntfs-3g +filesystem driver (and probably others), which is that although +the underlying filesystem is case-insensitive, the driver +exports the filesystem to Linux as case-sensitive. + +One consequence of this is that special directories such +as C<c:\\windows> may appear as C</WINDOWS> or C</windows> +(or other things) depending on the precise details of how +they were created. In Windows itself this would not be +a problem. + +Bug or feature? You decide: +L<http://www.tuxera.com/community/ntfs-3g-faq/#posixfilenames1> + +This function resolves the true case of each element in the +path and returns the case-sensitive path. + +Thus C<guestfs_case_sensitive_path> (\"/Windows/System32\") +might return C<\"/WINDOWS/system32\"> (the exact return value +would depend on details of how the directories were originally +created under Windows). + +I<Note>: +This function does not handle drive names, backslashes etc. + +See also C<guestfs_realpath>."); + ] let all_functions = non_daemon_functions @ daemon_functions -- 1.6.5.rc2
Richard W.M. Jones
2009-Oct-26 09:20 UTC
[Libguestfs] [PATCH 2/3] guestfish: Add win: prefix to use Windows paths.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v -------------- next part -------------->From f94645be0ac751e3fdeafc916803a4626f0a6e91 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 26 Oct 2009 09:04:42 +0000 Subject: [PATCH 2/3] guestfish: Add win: prefix to use Windows paths. Add a win: prefix for path arguments in guestfish:><fs> file win:c:\windows\system32\config\system.logMS Windows registry file, NT/2000 or above --- fish/fish.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ fish/fish.h | 1 + guestfish.pod | 17 +++++++++++++++++ src/generator.ml | 16 ++++++++++++---- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/fish/fish.c b/fish/fish.c index 3300536..f699603 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -1295,3 +1295,51 @@ xwrite (int fd, const void *v_buf, size_t len) return 0; } + +/* Resolve the special "win:..." form for Windows-specific paths. + * This always returns a newly allocated string which is freed by the + * caller function in "cmds.c". + */ +char * +resolve_win_path (const char *path) +{ + char *ret; + size_t i; + + if (strncasecmp (path, "win:", 4) != 0) { + ret = strdup (path); + if (ret == NULL) + perror ("strdup"); + return ret; + } + + path += 4; + + /* Drop drive letter, if it's "C:". */ + if (strncasecmp (path, "c:", 2) == 0) + path += 2; + + if (!*path) { + ret = strdup ("/"); + if (ret == NULL) + perror ("strdup"); + return ret; + } + + ret = strdup (path); + if (ret == NULL) { + perror ("strdup"); + return NULL; + } + + /* Blindly convert any backslashes into forward slashes. Is this good? */ + for (i = 0; i < strlen (ret); ++i) + if (ret[i] == '\\') + ret[i] = '/'; + + char *t = guestfs_case_sensitive_path (g, ret); + free (ret); + ret = t; + + return ret; +} diff --git a/fish/fish.h b/fish/fish.h index 642c269..8c5dba1 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -46,6 +46,7 @@ extern int launch (guestfs_h *); extern int is_true (const char *str); extern char **parse_string_list (const char *str); extern int xwrite (int fd, const void *buf, size_t len); +extern char *resolve_win_path (const char *path); /* in cmds.c (auto-generated) */ extern void list_commands (void); diff --git a/guestfish.pod b/guestfish.pod index e4b2333..b635419 100644 --- a/guestfish.pod +++ b/guestfish.pod @@ -350,6 +350,23 @@ it, eg: echo "~" +=head1 WINDOWS PATHS + +If a path is prefixed with C<win:> then you can use Windows-style +paths (with some limitations). The following commands are equivalent: + + file /WINDOWS/system32/config/system.LOG + + file win:/windows/system32/config/system.log + + file win:\windows\system32\config\system.log + + file WIN:C:\Windows\SYSTEM32\conFIG\SYSTEM.LOG + +This syntax implicitly calls C<case-sensitive-path> (q.v.) so it also +handles case insensitivity like Windows would. This only works in +argument positions that expect a path. + =head1 EXIT ON ERROR BEHAVIOUR By default, guestfish will ignore any errors when in interactive mode diff --git a/src/generator.ml b/src/generator.ml index 668002e..2b94601 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -6338,12 +6338,13 @@ and generate_fish_cmds () ); List.iter ( function - | Pathname n - | Device n | Dev_or_Path n + | Device n | String n | OptString n | FileIn n | FileOut n -> pr " const char *%s;\n" n + | Pathname n + | Dev_or_Path n -> pr " char *%s;\n" n | StringList n | DeviceList n -> pr " char **%s;\n" n | Bool n -> pr " int %s;\n" n | Int n -> pr " int %s;\n" n @@ -6360,8 +6361,13 @@ and generate_fish_cmds () iteri ( fun i -> function + | Device name + | String name -> + pr " %s = argv[%d];\n" name i | Pathname name - | Device name | Dev_or_Path name | String name -> pr " %s = argv[%d];\n" name i + | Dev_or_Path name -> + pr " %s = resolve_win_path (argv[%d]);\n" name i; + pr " if (%s == NULL) return -1;\n" name | OptString name -> pr " %s = strcmp (argv[%d], \"\") != 0 ? argv[%d] : NULL;\n" name i i @@ -6390,9 +6396,11 @@ and generate_fish_cmds () List.iter ( function - | Pathname name | Device name | Dev_or_Path name | String name + | Device name | String name | OptString name | FileIn name | FileOut name | Bool name | Int name -> () + | Pathname name | Dev_or_Path name -> + pr " free (%s);\n" name | StringList name | DeviceList name -> pr " free_strings (%s);\n" name ) (snd style); -- 1.6.5.rc2
Richard W.M. Jones
2009-Oct-26 09:21 UTC
[Libguestfs] [PATCH 3/3] Deprecate Sys::Guestfs::Lib::resolve_windows_path.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora -------------- next part -------------->From ef9136c7e5e177a266c0738c7003116fb4698d29 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 26 Oct 2009 09:12:12 +0000 Subject: [PATCH 3/3] Deprecate Sys::Guestfs::Lib::resolve_windows_path. This function can be directly replaced by $g->case_sensitive_path ($path). --- perl/lib/Sys/Guestfs/Lib.pm | 45 +++++++++--------------------------------- 1 files changed, 10 insertions(+), 35 deletions(-) diff --git a/perl/lib/Sys/Guestfs/Lib.pm b/perl/lib/Sys/Guestfs/Lib.pm index 8ea2c1b..f7aaf07 100644 --- a/perl/lib/Sys/Guestfs/Lib.pm +++ b/perl/lib/Sys/Guestfs/Lib.pm @@ -247,6 +247,9 @@ sub _is_pv { =head2 resolve_windows_path +I<This function is deprecated>. +Use C<$g-E<gt>case_sensitive_path> as a direct substitute. + $path = resolve_windows_path ($g, $path); $path = resolve_windows_path ($g, "/windows/system"); @@ -267,38 +270,10 @@ by C</> characters. Do not use C<\>, drive names, etc. sub resolve_windows_path { - local $_; my $g = shift; my $path = shift; - if (substr ($path, 0, 1) ne "/") { - warn __"resolve_windows_path: path must start with a / character"; - return undef; - } - - my @elems = split (/\//, $path); - shift @elems; - - # Start reconstructing the path at the top. - $path = "/"; - - foreach my $dir (@elems) { - my $found = 0; - foreach ($g->ls ($path)) { - if (lc ($_) eq lc ($dir)) { - if ($path eq "/") { - $path = "/$_"; - $found = 1; - } else { - $path = "$path/$_"; - $found = 1; - } - } - } - return undef unless $found; - } - - return $path; + return $g->case_sensitive_path ($path); } =head2 file_architecture @@ -914,7 +889,7 @@ sub _check_windows_root my $r = shift; my $use_windows_registry = shift; - my $boot_ini = resolve_windows_path ($g, "/boot.ini"); + my $boot_ini = $g->case_sensitive_path ("/boot.ini"); $r->{boot_ini} = $boot_ini; if (defined $r->{boot_ini}) { @@ -935,7 +910,7 @@ sub _check_windows_root } if (defined $systemroot) { - $r->{systemroot} = resolve_windows_path ($g, "/$systemroot"); + $r->{systemroot} = $g->case_sensitive_path ("/$systemroot"); if (defined $r->{systemroot}) { _check_windows_arch ($g, $r, $r->{systemroot}); if ($use_windows_registry) { @@ -956,7 +931,7 @@ sub _check_windows_arch my $systemroot = shift; my $cmd_exe - resolve_windows_path ($g, $r->{systemroot} . "/system32/cmd.exe"); + $g->case_sensitive_path ($r->{systemroot} . "/system32/cmd.exe"); $r->{arch} = file_architecture ($g, $cmd_exe) if $cmd_exe; } @@ -970,14 +945,14 @@ sub _check_windows_registry # Download the system registry files. Only download the # interesting ones, and we don't bother with user profiles at all. - my $configdir = resolve_windows_path ($g, "$systemroot/system32/config"); + my $configdir = $g->case_sensitive_path ("$systemroot/system32/config"); if (defined $configdir) { - my $softwaredir = resolve_windows_path ($g, "$configdir/software"); + my $softwaredir = $g->case_sensitive_path ("$configdir/software"); if (defined $softwaredir) { _load_windows_registry ($g, $r, $softwaredir, "HKEY_LOCAL_MACHINE\\SOFTWARE"); } - my $systemdir = resolve_windows_path ($g, "$configdir/system"); + my $systemdir = $g->case_sensitive_path ("$configdir/system"); if (defined $systemdir) { _load_windows_registry ($g, $r, $systemdir, "HKEY_LOCAL_MACHINE\\System"); -- 1.6.5.rc2
Reasonably Related Threads
- [PATCH v2] inspection: Fix calls to case_sensitive_path (RHBZ#858126).
- [PATCH] docs: guestfs_case_sensitive_path returns error on non-existent path
- [PATCH] windows: Fix creation of /Temp/V2V directory (RHBZ#868073).
- [PATCH 0/2] Fix calls to case_sensitive_path.
- [PATCH v11 09/10] daemon: Implement inspection of Windows.