1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with this
4  * work for additional information regarding copyright ownership. The ASF
5  * licenses this file to you under the Apache License, Version 2.0 (the
6  * "License"); you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14  * License for the specific language governing permissions and limitations under
15  * the License.
16  */
17 
18 #include "winutils.h"
19 
20 
21 //----------------------------------------------------------------------------
22 // The Windows SDK does not include the definition of REPARSE_DATA_BUFFER. To
23 // avoid adding a dependency on the WDK we define the structure here.
24 // Reference: http://msdn.microsoft.com/en-us/library/ff552012.aspx
25 //
26 #pragma warning(push)
27 #pragma warning(disable: 4201)  // nonstandard extension: nameless struct/union
28 #pragma pack(push, 1)
29 typedef struct _REPARSE_DATA_BUFFER {
30   ULONG  ReparseTag;
31   USHORT ReparseDataLength;
32   USHORT Reserved;
33   union {
34     struct {
35       USHORT SubstituteNameOffset;
36       USHORT SubstituteNameLength;
37       USHORT PrintNameOffset;
38       USHORT PrintNameLength;
39       ULONG  Flags;
40       WCHAR  PathBuffer[1];
41     } SymbolicLinkReparseBuffer;
42     struct {
43       USHORT SubstituteNameOffset;
44       USHORT SubstituteNameLength;
45       USHORT PrintNameOffset;
46       USHORT PrintNameLength;
47       WCHAR  PathBuffer[1];
48     } MountPointReparseBuffer;
49     struct {
50       UCHAR DataBuffer[1];
51     } GenericReparseBuffer;
52   };
53 } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
54 #pragma pack(pop)
55 #pragma warning(pop)
56 
57 
58 //----------------------------------------------------------------------------
59 // Function: Readlink
60 //
61 // Description:
62 //  Prints the target of a symbolic link to stdout.
63 //
64 //  The return codes and output are modeled after the UNIX readlink command.
65 //  Hence no error messages are printed. Unlike the UNIX readlink, no options
66 //  are accepted.
67 //
68 // Returns:
69 //  0: on success
70 //  1: on all errors
71 //
72 // Notes:
73 //
Readlink(__in int argc,__in_ecount (argc)wchar_t * argv[])74 int Readlink(__in int argc, __in_ecount(argc) wchar_t *argv[])
75 {
76   DWORD bytesReturned;
77   DWORD bufferSize = 1024;                  // Start off with a 1KB buffer.
78   HANDLE hFile = INVALID_HANDLE_VALUE;
79   PWSTR longLinkName = NULL;
80   PWCHAR printName = NULL;
81   PREPARSE_DATA_BUFFER pReparseData = NULL;
82   USHORT printNameLength;
83   USHORT printNameOffset;
84   DWORD result;
85   BOOLEAN succeeded = FALSE;
86 
87   if (argc != 2)
88   {
89     ReadlinkUsage();
90     goto Cleanup;
91   }
92 
93   if (ConvertToLongPath(argv[1], &longLinkName) != ERROR_SUCCESS)
94   {
95     goto Cleanup;
96   }
97 
98   // Get a handle to the link to issue the FSCTL.
99   // FILE_FLAG_BACKUP_SEMANTICS is needed to open directories.
100   // FILE_FLAG_OPEN_REPARSE_POINT disables normal reparse point processing
101   // so we can query the symlink.
102   //
103   hFile = CreateFileW(longLinkName,
104                       0,        // no rights needed to issue the FSCTL.
105                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
106                       NULL,
107                       OPEN_EXISTING,
108                       FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
109                       NULL);
110 
111   if (hFile == INVALID_HANDLE_VALUE)
112   {
113     goto Cleanup;
114   }
115 
116   for (;;)
117   {
118     pReparseData = (PREPARSE_DATA_BUFFER) LocalAlloc(LMEM_FIXED, bufferSize);
119 
120     if (pReparseData == NULL)
121     {
122       goto Cleanup;
123     }
124 
125     // Issue the FSCTL to query the link information.
126     //
127     result = DeviceIoControl(hFile,
128                              FSCTL_GET_REPARSE_POINT,
129                              NULL,
130                              0,
131                              pReparseData,
132                              bufferSize,
133                              &bytesReturned,
134                              NULL);
135 
136     if (result != 0)
137     {
138       // Success!
139       //
140       break;
141     }
142     else if ((GetLastError() == ERROR_INSUFFICIENT_BUFFER) ||
143              (GetLastError() == ERROR_MORE_DATA))
144     {
145       // Retry with a larger buffer.
146       //
147       LocalFree(pReparseData);
148       bufferSize *= 2;
149     }
150     else
151     {
152       // Unrecoverable error.
153       //
154       goto Cleanup;
155     }
156   }
157 
158   if (pReparseData->ReparseTag != IO_REPARSE_TAG_SYMLINK)
159   {
160     // Doesn't look like a symlink.
161     //
162     goto Cleanup;
163   }
164 
165   // MSDN does not guarantee that the embedded paths in REPARSE_DATA_BUFFER
166   // will be NULL terminated. So we copy the string to a separate buffer and
167   // NULL terminate it before printing.
168   //
169   printNameLength = pReparseData->SymbolicLinkReparseBuffer.PrintNameLength;
170   printNameOffset = pReparseData->SymbolicLinkReparseBuffer.PrintNameOffset;
171   printName = (PWCHAR) LocalAlloc(LMEM_FIXED, printNameLength + 1);
172 
173   if (printName == NULL)
174   {
175     goto Cleanup;
176   }
177 
178   memcpy(
179       printName,
180       pReparseData->SymbolicLinkReparseBuffer.PathBuffer + printNameOffset,
181       printNameLength);
182 
183   printName[printNameLength / sizeof(WCHAR)] = L'\0';
184 
185   fwprintf(stdout, L"%ls", printName);
186   succeeded = TRUE;
187 
188 Cleanup:
189   if (hFile != INVALID_HANDLE_VALUE)
190   {
191     CloseHandle(hFile);
192   }
193 
194   if (printName != NULL)
195   {
196     LocalFree(printName);
197   }
198 
199   if (pReparseData != NULL)
200   {
201     LocalFree(pReparseData);
202   }
203 
204   if (longLinkName != NULL)
205   {
206     LocalFree(longLinkName);
207   }
208 
209   return (succeeded ? EXIT_SUCCESS : EXIT_FAILURE);
210 }
211 
ReadlinkUsage()212 void ReadlinkUsage()
213 {
214     fwprintf(stdout, L"\
215 Usage: readlink [LINKNAME]\n\
216 Prints the target of a symbolic link\n\
217 The output and returned error codes are similar to the UNIX\n\
218 readlink command. However no options are accepted.\n\
219 \n\
220 0 is returned on success.\n\
221 1 is returned for all errors.\n\
222 \n");
223 }
224 
225