win32-symlink 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 08a2aebcc89a9a0521a80869c5f9c045a6774374
4
+ data.tar.gz: 4aebff75b3ccd259ae3983f1577b959829f150e1
5
+ SHA512:
6
+ metadata.gz: 2a9451a6762f0cf0153dc760008a2f2e36f41a7884154db1bd0b6293b56d72ead206be01eecf60b8eef46ee37dffa9e308b133471be389d364167ffa7eafb4a4
7
+ data.tar.gz: 7e1af87d5b5cc8ffcdc135182b6563ec4a8cbc7bd42a074ac27684955ee7f5d3cf7a1eddb637c54506d0676a769acd195f18eef62a9ee90856bdafbf430d7dc6
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # -*- ruby -*-
2
+ source 'https://rubygems.org'
3
+
4
+ # Specify your gem's dependencies in win32-symlink.gemspec
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 boris
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Win32::Symlink
2
+
3
+ Since the standard ruby library lacks symlink functions on Windows, this
4
+ little library attempts to fill the gap.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'win32-symlink'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install win32-symlink
19
+
20
+ ## Usage
21
+
22
+ This library implements the following functons that mimic their ruby
23
+ counterparts:
24
+
25
+ readlink(link_name) -> file_name
26
+ symlink?(link_name) -> true or false
27
+ symlink(old_name, new_name) -> 0
28
+
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ # -*- ruby -*-
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+
13
+ $gemspec = Bundler.load_gemspec('win32-symlink.gemspec')
14
+
15
+ Rake::TestTask.new do |t|
16
+ t.warning = true
17
+ end
18
+
19
+ load 'tasks/ext.rake'
20
+
21
+ task :default => [:compile, :test]
@@ -0,0 +1,12 @@
1
+ require 'mkmf'
2
+
3
+ if CONFIG['CXXFLAGS'] == '$(cxxflags)'
4
+ CONFIG['CXXFLAGS'] = CONFIG['cxxflags'].clone
5
+ end
6
+
7
+ # these are not relevant for C++
8
+ CONFIG['warnflags'].sub! '-Wdeclaration-after-statement', ''
9
+ CONFIG['warnflags'].sub! '-Wimplicit-function-declaration', ''
10
+
11
+ try_cppflags $CPPFLAGS.sub(/_WIN32_WINNT=\S+/, '_WIN32_WINNT=0x600')
12
+ create_makefile 'win32_symlink'
@@ -0,0 +1,297 @@
1
+ #include "ruby.h"
2
+
3
+ #define filecp_to_wstr(str, plen) cstr_to_wstr(filecp(), str, -1, plen)
4
+
5
+ typedef BOOL (WINAPI* create_symbolic_linkW_t)(LPWSTR src, LPWSTR dest, DWORD flags);
6
+ create_symbolic_linkW_t create_symbolic_linkW = NULL;
7
+
8
+
9
+ typedef struct _REPARSE_DATA_BUFFER
10
+ {
11
+ ULONG ReparseTag;
12
+ USHORT ReparseDataLength;
13
+ USHORT Reserved;
14
+ union
15
+ {
16
+ struct
17
+ {
18
+ USHORT SubstituteNameOffset;
19
+ USHORT SubstituteNameLength;
20
+ USHORT PrintNameOffset;
21
+ USHORT PrintNameLength;
22
+ ULONG Flags;
23
+ WCHAR PathBuffer[1];
24
+ } SymbolicLinkReparseBuffer;
25
+ struct
26
+ {
27
+ USHORT SubstituteNameOffset;
28
+ USHORT SubstituteNameLength;
29
+ USHORT PrintNameOffset;
30
+ USHORT PrintNameLength;
31
+ WCHAR PathBuffer[1];
32
+ } MountPointReparseBuffer;
33
+ struct
34
+ {
35
+ UCHAR DataBuffer[1];
36
+ } GenericReparseBuffer;
37
+ } DUMMYUNIONNAME;
38
+ } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
39
+
40
+
41
+ static inline UINT
42
+ filecp(void)
43
+ {
44
+ UINT cp = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
45
+ return cp;
46
+ }
47
+
48
+ static wchar_t *
49
+ cstr_to_wstr(UINT code_page, const char *str, int clen, int *wstr_len)
50
+ {
51
+ int len;
52
+ wchar_t* wstr;
53
+
54
+ len = MultiByteToWideChar(code_page, 0, str, clen, NULL, 0);
55
+ wstr = (wchar_t *)xmalloc(len * sizeof(wchar_t));
56
+
57
+ MultiByteToWideChar(code_page, 0, str, clen, wstr, len);
58
+ if( wstr_len )
59
+ *wstr_len = len;
60
+ return wstr;
61
+ }
62
+
63
+ static VALUE
64
+ wstr_to_str(UINT code_page, const wchar_t* wstr, int clen)
65
+ {
66
+ int len, term_len;
67
+ VALUE v;
68
+ term_len = clen < 0 ? 1 : 0;
69
+ len = WideCharToMultiByte(code_page, 0, wstr, clen, NULL, 0, NULL, NULL) - term_len;
70
+ v = rb_str_new(NULL, len);
71
+ WideCharToMultiByte(code_page, 0, wstr, clen, RSTRING_PTR(v), len + term_len, NULL, NULL);
72
+ return v;
73
+ }
74
+
75
+ static VALUE
76
+ make_error_str(VALUE klass, const char* fmt, ...)
77
+ {
78
+ VALUE exc;
79
+ VALUE str;
80
+
81
+ va_list va;
82
+ va_start(va, fmt);
83
+ str = rb_vsprintf(fmt, va);
84
+ va_end(va);
85
+ exc = rb_exc_new3(klass, str);
86
+ rb_str_free(str);
87
+ return exc;
88
+ }
89
+
90
+ static VALUE
91
+ make_api_error(const char* msg)
92
+ {
93
+ DWORD w32_err;
94
+ int myerrno;
95
+ w32_err = GetLastError();
96
+ myerrno = rb_w32_map_errno(w32_err);
97
+ return rb_syserr_new( myerrno == EINVAL ? w32_err : myerrno, msg);
98
+ }
99
+
100
+ static wchar_t*
101
+ resolve_api_path(wchar_t* path)
102
+ {
103
+ if( 0 == wcsncmp(L"\\??\\UNC\\", path, 8) )
104
+ {
105
+ path += 6;
106
+ *path = L'\\';
107
+ }
108
+ else if( 0 == wcsncmp(L"\\??\\", path, 4) )
109
+ {
110
+ path += 4;
111
+ }
112
+ return path;
113
+ }
114
+
115
+ static VALUE
116
+ win32_readlink(const char* file)
117
+ {
118
+ VALUE error = Qnil;
119
+ VALUE target = Qnil;
120
+ HANDLE h = INVALID_HANDLE_VALUE;
121
+ bool ok = true;
122
+ REPARSE_DATA_BUFFER* buf = NULL;
123
+ const size_t buf_size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
124
+
125
+ buf = (REPARSE_DATA_BUFFER*)xmalloc(buf_size);
126
+ if(ok)
127
+ {
128
+ const DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
129
+ const DWORD access_mode = FILE_READ_EA;
130
+ const DWORD attributes =
131
+ FILE_FLAG_BACKUP_SEMANTICS
132
+ | FILE_ATTRIBUTE_REPARSE_POINT
133
+ | FILE_FLAG_OPEN_REPARSE_POINT;
134
+
135
+ h = CreateFile( file, access_mode, share_mode,
136
+ 0, OPEN_EXISTING, attributes, NULL);
137
+
138
+ if(!(ok = (h != INVALID_HANDLE_VALUE)))
139
+ {
140
+ error = make_api_error(file);
141
+ }
142
+ }
143
+
144
+ if(ok)
145
+ {
146
+ DWORD returned_size;
147
+ ok = 0 != DeviceIoControl (h, FSCTL_GET_REPARSE_POINT,
148
+ NULL, 0, buf, buf_size, &returned_size, NULL);
149
+ if(!ok)
150
+ {
151
+ error = make_api_error(file);
152
+ }
153
+ }
154
+ if(ok)
155
+ {
156
+ wchar_t* raw_target = NULL;
157
+ long target_len = 0;
158
+
159
+ switch (buf->ReparseTag)
160
+ {
161
+ case IO_REPARSE_TAG_MOUNT_POINT:
162
+ {
163
+ raw_target = &buf->MountPointReparseBuffer.PathBuffer[
164
+ buf->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t)];
165
+ target_len = buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
166
+ break;
167
+ }
168
+ case IO_REPARSE_TAG_SYMLINK:
169
+ {
170
+ raw_target = &buf->SymbolicLinkReparseBuffer.PathBuffer[
171
+ buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t)];
172
+ target_len = buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
173
+ break;
174
+ }
175
+ default:
176
+ ok = false;
177
+ error = make_error_str(rb_eIOError, "file %s is not a symlink", file);
178
+ }
179
+ if(ok)
180
+ {
181
+ wchar_t* resolved;
182
+ resolved = resolve_api_path(raw_target);
183
+ target_len -= resolved - raw_target;
184
+ target = wstr_to_str(filecp(), resolved, target_len);
185
+ }
186
+ }
187
+
188
+
189
+ if( h != INVALID_HANDLE_VALUE )
190
+ {
191
+ CloseHandle(h);
192
+ }
193
+ xfree(buf);
194
+
195
+ if( !NIL_P(error) )
196
+ {
197
+ rb_exc_raise(error);
198
+ }
199
+ return target;
200
+ }
201
+
202
+ static VALUE
203
+ rb_readlink(VALUE mod, VALUE file)
204
+ {
205
+ (void)mod;
206
+ FilePathValue(file);
207
+ file = rb_str_encode_ospath(file);
208
+ return win32_readlink(RSTRING_PTR(file));
209
+ }
210
+
211
+ static VALUE
212
+ rb_symlink_p(VALUE mod, VALUE file)
213
+ {
214
+ (void)mod;
215
+ DWORD attrs;
216
+ FilePathValue(file);
217
+ file = rb_str_encode_ospath(file);
218
+ attrs = GetFileAttributes(RSTRING_PTR(file));
219
+ if( attrs == INVALID_FILE_ATTRIBUTES )
220
+ {
221
+ rb_exc_raise(make_api_error(RSTRING_PTR(file)));
222
+ }
223
+ return (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
224
+ }
225
+
226
+ static bool
227
+ is_directory(wchar_t* path)
228
+ {
229
+ HANDLE h = INVALID_HANDLE_VALUE;
230
+ WIN32_FIND_DATAW fd;
231
+ h = FindFirstFileW(path, &fd);
232
+ if( h != INVALID_HANDLE_VALUE )
233
+ {
234
+ FindClose(h);
235
+ return (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ;
236
+ }
237
+ return false;
238
+ }
239
+
240
+ static VALUE
241
+ rb_symlink(VALUE mod, VALUE target, VALUE symlink)
242
+ {
243
+ (void)mod;
244
+
245
+ VALUE error = Qnil;
246
+ wchar_t* wtarget = NULL;
247
+ wchar_t* wsymlink = NULL;
248
+ BOOL res;
249
+ DWORD flags = 0;
250
+
251
+ target = FilePathValue(target);
252
+ symlink = FilePathValue(symlink);
253
+ target = rb_str_encode_ospath(target);
254
+ symlink = rb_str_encode_ospath(symlink);
255
+
256
+ wtarget = filecp_to_wstr(RSTRING_PTR(target), NULL);
257
+ wsymlink = filecp_to_wstr(RSTRING_PTR(symlink), NULL);
258
+
259
+ if( is_directory(wsymlink) )
260
+ {
261
+ flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
262
+ }
263
+ res = create_symbolic_linkW(wsymlink, wtarget, flags);
264
+ if( !res )
265
+ {
266
+ error = make_api_error(RSTRING_PTR(target));
267
+ }
268
+
269
+ xfree(wtarget);
270
+ xfree(wsymlink);
271
+
272
+ if( !NIL_P(error) )
273
+ {
274
+ rb_exc_raise(error);
275
+ }
276
+ return INT2FIX(0);
277
+ }
278
+
279
+ extern "C" void
280
+ Init_win32_symlink()
281
+ {
282
+ VALUE mod_win32;
283
+ VALUE mod;
284
+
285
+ mod_win32 = rb_define_module("Win32");
286
+ mod = rb_define_module_under(mod_win32, "Symlink");
287
+
288
+ rb_define_module_function(mod, "readlink", RUBY_METHOD_FUNC(rb_readlink), 1);
289
+ rb_define_module_function(mod, "symlink?", RUBY_METHOD_FUNC(rb_symlink_p), 1);
290
+
291
+ create_symbolic_linkW = (create_symbolic_linkW_t)GetProcAddress(
292
+ GetModuleHandle("kernel32.dll"), "CreateSymbolicLinkW");
293
+ if( create_symbolic_linkW )
294
+ {
295
+ rb_define_module_function(mod, "symlink", RUBY_METHOD_FUNC(rb_symlink), 2);
296
+ }
297
+ }
@@ -0,0 +1,2 @@
1
+ require_relative 'symlink/win32_symlink'
2
+ require_relative 'symlink/version'
@@ -0,0 +1,12 @@
1
+ module Win32
2
+ module Symlink
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+ BUILD = nil
8
+ STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join '.'
9
+ end
10
+ VERSION = Version::STRING
11
+ end
12
+ end
Binary file
data/tasks/ext.rake ADDED
@@ -0,0 +1,7 @@
1
+ # -*- ruby -*-
2
+ require 'rake/extensiontask'
3
+ Rake::ExtensionTask.new('win32_symlink', $gemspec) do |ext|
4
+ ext.ext_dir = 'ext/win32'
5
+ ext.lib_dir = 'lib/win32/symlink'
6
+ end
7
+
@@ -0,0 +1,60 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(File.dirname(__FILE__)), 'ext')
2
+
3
+ # puts $LOAD_PATH
4
+
5
+ require 'test/unit'
6
+ require 'win32/symlink'
7
+ include Win32
8
+
9
+ class Errno::E4390 < SystemCallError
10
+ # The file is not a reparse point
11
+ end
12
+ class Errno::E1314 < SystemCallError
13
+ # A required privilege is not set
14
+ end
15
+
16
+ class TC_Win32Symlink < Test::Unit::TestCase
17
+
18
+ def setup
19
+ @users_dir = File.join(ENV['SYSTEMDRIVE'], '/Users')
20
+ end
21
+
22
+ def test_bad_args
23
+ assert_raises(Errno::ENOENT) {Symlink.symlink? ""}
24
+ assert_raises(TypeError) { Symlink.symlink? nil }
25
+ assert_raises(TypeError) { Symlink.symlink?(Object.new) }
26
+ assert_raises(TypeError) { Symlink.symlink "", nil }
27
+ assert_raises(TypeError) { Symlink.symlink nil, "" }
28
+ end
29
+
30
+ def test_system_links
31
+ assert(Symlink.symlink?(File.join(@users_dir, "All Users")))
32
+ assert(!Symlink.symlink?(ENV['WINDIR']))
33
+ end
34
+
35
+ def test_readlink
36
+ assert_equal(Symlink.readlink(File.join(@users_dir, "All Users")), ENV['ALLUSERSPROFILE'])
37
+ end
38
+
39
+ def test_nonlink
40
+ assert_raises(Errno::E4390) { Symlink.readlink(ENV['WINDIR']) }
41
+ end
42
+
43
+ def test_makelink
44
+ Dir.chdir(ENV["TMP"]) do
45
+ IO::write("symlink-test-file", "test")
46
+ begin
47
+ Symlink.symlink("symlink-test-file", "symlink-test-symlink")
48
+ assert(Symlink.symlink? "symlink-test-symlink")
49
+ rescue Errno::E1314 => e
50
+ skip e.message
51
+ end
52
+ end
53
+ end
54
+
55
+ def teardown
56
+ Dir.glob(File.join(ENV['TMP'], 'symlink-test*')).each {|f| File::unlink f}
57
+ end
58
+
59
+ end
60
+
@@ -0,0 +1,35 @@
1
+ # -*- ruby -*-
2
+ require File.expand_path('../lib/win32/symlink/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['Boris Kropivnitsky']
6
+ gem.email = ['boris.kro@gmail.com']
7
+ gem.description = <<DESC
8
+ Symlink functions for Windows (Vista and above)
9
+ DESC
10
+ gem.summary = 'symlink functions for Windows'
11
+ gem.homepage = ''
12
+
13
+ gem.files = %w{
14
+ Gemfile
15
+ LICENSE
16
+ README.md
17
+ Rakefile
18
+ ext/win32/extconf.rb
19
+ ext/win32/win32-symlink.cpp
20
+ lib/win32/symlink.rb
21
+ lib/win32/symlink/version.rb
22
+ lib/win32/symlink/win32_symlink.so
23
+ tasks/ext.rake
24
+ test/test_win32_symlink.rb
25
+ win32-symlink.gemspec
26
+ }
27
+ gem.extensions = ['ext/win32/extconf.rb']
28
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
29
+ gem.name = 'win32-symlink'
30
+ gem.require_paths = ['lib']
31
+ gem.version = Win32::Symlink::VERSION
32
+ gem.licenses = ['MIT']
33
+ gem.add_development_dependency 'bundler'
34
+ gem.add_development_dependency 'rake-compiler'
35
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: win32-symlink
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Boris Kropivnitsky
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake-compiler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: |
42
+ Symlink functions for Windows (Vista and above)
43
+ email:
44
+ - boris.kro@gmail.com
45
+ executables: []
46
+ extensions:
47
+ - ext/win32/extconf.rb
48
+ extra_rdoc_files: []
49
+ files:
50
+ - Gemfile
51
+ - LICENSE
52
+ - README.md
53
+ - Rakefile
54
+ - ext/win32/extconf.rb
55
+ - ext/win32/win32-symlink.cpp
56
+ - lib/win32/symlink.rb
57
+ - lib/win32/symlink/version.rb
58
+ - lib/win32/symlink/win32_symlink.so
59
+ - tasks/ext.rake
60
+ - test/test_win32_symlink.rb
61
+ - win32-symlink.gemspec
62
+ homepage: ''
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 2.1.11
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: symlink functions for Windows
86
+ test_files:
87
+ - test/test_win32_symlink.rb