[email protected] | 35911f7 | 2012-05-22 21:35:01 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | // |
| 5 | // This is a simple utility for creating NTFS junction points on XP and later |
| 6 | // versions of Windows. |
| 7 | |
| 8 | #define _WIN32_WINNT 0x0500 |
| 9 | |
| 10 | #include <stdio.h> |
| 11 | #include <windows.h> |
| 12 | |
| 13 | // This struct definition is absent from the system header files, |
| 14 | // but is described here: |
| 15 | // http://msdn.microsoft.com/en-us/library/ff552012.aspx |
| 16 | typedef struct _REPARSE_DATA_BUFFER { |
| 17 | ULONG ReparseTag; |
| 18 | USHORT ReparseDataLength; |
| 19 | USHORT Reserved; |
| 20 | union { |
| 21 | struct { |
| 22 | USHORT SubstituteNameOffset; |
| 23 | USHORT SubstituteNameLength; |
| 24 | USHORT PrintNameOffset; |
| 25 | USHORT PrintNameLength; |
| 26 | ULONG Flags; |
| 27 | WCHAR PathBuffer[1]; |
| 28 | } SymbolicLinkReparseBuffer; |
| 29 | struct { |
| 30 | USHORT SubstituteNameOffset; |
| 31 | USHORT SubstituteNameLength; |
| 32 | USHORT PrintNameOffset; |
| 33 | USHORT PrintNameLength; |
| 34 | WCHAR PathBuffer[1]; |
| 35 | } MountPointReparseBuffer; |
| 36 | struct { |
| 37 | UCHAR DataBuffer[1]; |
| 38 | } GenericReparseBuffer; |
| 39 | }; |
| 40 | } REPARSE_DATA_BUFFER; |
| 41 | |
| 42 | int main (int argc, char *argv[]) |
| 43 | { |
| 44 | char buf[MAX_PATH*sizeof(WCHAR) + sizeof(REPARSE_DATA_BUFFER)]={'\0'}; |
| 45 | char* dest_path=NULL; |
| 46 | char* src_path=NULL; |
| 47 | char* src_link=NULL; |
| 48 | char* src_vol=NULL; |
| 49 | char* dest_vol=NULL; |
| 50 | HANDLE dir=NULL; |
| 51 | REPARSE_DATA_BUFFER* reparse = (REPARSE_DATA_BUFFER*) buf; |
| 52 | int path_len=0, data_len=0; |
| 53 | DWORD ioctl_return = 0xdeadbeef; |
| 54 | |
| 55 | // To allow this to be used as a drop-in replacement for the posix ln command, |
| 56 | // allow (and ignore) extra flags. |
| 57 | if (argc != 3 && (argc !=4 || *argv[1] != '-')) { |
| 58 | fputs("Usage: create-ntfs-junction <destination dir> <source dir>\n", stderr); |
| 59 | return -1; |
| 60 | } |
| 61 | |
| 62 | src_path = argv[argc-2]; |
| 63 | path_len = strlen(src_path); |
| 64 | if (src_path[path_len-1] == '\\') |
| 65 | src_path[path_len-1] = '\0'; |
| 66 | |
| 67 | dest_path = argv[argc-1]; |
| 68 | path_len = strlen(dest_path); |
| 69 | if (dest_path[path_len-1] == '\\') |
| 70 | dest_path[path_len-1] = '\0'; |
| 71 | |
| 72 | if (GetFileAttributes(src_path) == INVALID_FILE_ATTRIBUTES) { |
| 73 | fprintf(stderr, "%s: No such animal.\n", src_path); |
| 74 | return -1; |
| 75 | } |
| 76 | |
| 77 | if (!GetVolumePathName(src_path, buf, MAX_PATH)) { |
| 78 | fprintf(stderr, "Couldn't get volume name for '%s'.\n", src_path); |
| 79 | return -1; |
| 80 | } |
| 81 | src_vol = _strdup(buf); |
| 82 | |
| 83 | if (!GetVolumePathName(dest_path, buf, MAX_PATH)) { |
| 84 | fprintf(stderr, "Couldn't get volume name for '%s'.\n", dest_path); |
| 85 | return -1; |
| 86 | } |
| 87 | dest_vol = _strdup(buf); |
| 88 | |
| 89 | if (strcmp(src_vol, dest_vol)) { |
| 90 | fprintf(stderr, "Cannot create junction point across volume boundary.\n"); |
| 91 | fprintf(stderr, " (from volume '%s' to volume '%s')\n", src_vol, dest_vol); |
| 92 | return -1; |
| 93 | } |
| 94 | |
| 95 | // End of input sanity checks; file system modifications may now occur. |
| 96 | |
| 97 | if (GetFileAttributes(dest_path) == INVALID_FILE_ATTRIBUTES) { |
| 98 | if (!CreateDirectory(dest_path, NULL)) { |
| 99 | switch(GetLastError()) { |
| 100 | case ERROR_ALREADY_EXISTS: |
| 101 | fprintf(stderr, "Can't create directory %s because it already exists " |
| 102 | "(this should never happen).\n", dest_path); |
| 103 | return -1; |
| 104 | break; |
| 105 | case ERROR_PATH_NOT_FOUND: |
| 106 | fprintf(stderr, "Can't create directory %s because some part of the " |
| 107 | "intermediate path doesn't exist.", dest_path); |
| 108 | return -1; |
| 109 | break; |
| 110 | default: |
| 111 | fprintf(stderr, "Unknown error occurred while trying to create " |
| 112 | "directory %s.", dest_path); |
| 113 | return -1; |
| 114 | break; |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | dir = CreateFile(dest_path, |
| 120 | GENERIC_WRITE, |
| 121 | 0, |
| 122 | NULL, |
| 123 | OPEN_EXISTING, |
| 124 | FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, |
| 125 | NULL); |
| 126 | |
| 127 | strcpy_s(buf, 5, "\\??\\"); |
| 128 | GetFullPathName(src_path, MAX_PATH, buf+4, NULL); |
| 129 | src_link = _strdup(buf); |
| 130 | |
| 131 | memset(buf, 0, sizeof(buf)); |
| 132 | path_len = MultiByteToWideChar(CP_ACP, |
| 133 | 0, |
| 134 | src_link, |
| 135 | -1, |
| 136 | reparse->MountPointReparseBuffer.PathBuffer, |
| 137 | MAX_PATH*sizeof(WCHAR)); |
| 138 | |
| 139 | reparse->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; |
| 140 | reparse->ReparseDataLength = (path_len+2)*sizeof(WCHAR) + 6; |
| 141 | reparse->MountPointReparseBuffer.SubstituteNameLength = |
| 142 | (path_len-1) * sizeof(WCHAR); |
| 143 | reparse->MountPointReparseBuffer.PrintNameOffset = |
| 144 | path_len * sizeof(WCHAR); |
| 145 | data_len = reparse->ReparseDataLength + 8; |
| 146 | |
| 147 | if (!DeviceIoControl(dir, |
| 148 | FSCTL_SET_REPARSE_POINT, |
| 149 | &buf, |
| 150 | data_len, |
| 151 | NULL, |
| 152 | 0, |
| 153 | &ioctl_return, |
| 154 | NULL)) { |
| 155 | fprintf(stderr, "Junction point creation failed (ioctl_return=0x%x) (%d)\n", |
| 156 | ioctl_return, GetLastError()); |
| 157 | return 1; |
| 158 | } |
| 159 | |
| 160 | return 0; |
| 161 | } |