1// Copyright 2011 The Kyua Authors.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9//   notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright
11//   notice, this list of conditions and the following disclaimer in the
12//   documentation and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors
14//   may be used to endorse or promote products derived from this software
15//   without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29#include "utils/fs/lua_module.hpp"
30
31extern "C" {
32#include <dirent.h>
33}
34
35#include <cerrno>
36#include <cstring>
37#include <stdexcept>
38#include <string>
39
40#include <lutok/operations.hpp>
41#include <lutok/stack_cleaner.hpp>
42#include <lutok/state.ipp>
43
44#include "utils/format/macros.hpp"
45#include "utils/fs/operations.hpp"
46#include "utils/fs/path.hpp"
47#include "utils/sanity.hpp"
48
49namespace fs = utils::fs;
50
51
52namespace {
53
54
55/// Given a path, qualifies it with the module's start directory if necessary.
56///
57/// \param state The Lua state.
58/// \param path The path to qualify.
59///
60/// \return The original path if it was absolute; otherwise the original path
61/// appended to the module's start directory.
62///
63/// \throw std::runtime_error If the module's state has been corrupted.
64static fs::path
65qualify_path(lutok::state& state, const fs::path& path)
66{
67    lutok::stack_cleaner cleaner(state);
68
69    if (path.is_absolute()) {
70        return path;
71    } else {
72        state.get_global("_fs_start_dir");
73        if (!state.is_string(-1))
74            throw std::runtime_error("Missing _fs_start_dir global variable; "
75                                     "state corrupted?");
76        return fs::path(state.to_string(-1)) / path;
77    }
78}
79
80
81/// Safely gets a path from the Lua state.
82///
83/// \param state The Lua state.
84/// \param index The position in the Lua stack that contains the path to query.
85///
86/// \return The queried path.
87///
88/// \throw fs::error If the value is not a valid path.
89/// \throw std::runtime_error If the value on the Lua stack is not convertible
90///     to a path.
91static fs::path
92to_path(lutok::state& state, const int index)
93{
94    if (!state.is_string(index))
95        throw std::runtime_error("Need a string parameter");
96    return fs::path(state.to_string(index));
97}
98
99
100/// Lua binding for fs::path::basename.
101///
102/// \pre stack(-1) The input path.
103/// \post stack(-1) The basename of the input path.
104///
105/// \param state The Lua state.
106///
107/// \return The number of result values, i.e. 1.
108static int
109lua_fs_basename(lutok::state& state)
110{
111    lutok::stack_cleaner cleaner(state);
112
113    const fs::path path = to_path(state, -1);
114    state.push_string(path.leaf_name().c_str());
115    cleaner.forget();
116    return 1;
117}
118
119
120/// Lua binding for fs::path::dirname.
121///
122/// \pre stack(-1) The input path.
123/// \post stack(-1) The directory part of the input path.
124///
125/// \param state The Lua state.
126///
127/// \return The number of result values, i.e. 1.
128static int
129lua_fs_dirname(lutok::state& state)
130{
131    lutok::stack_cleaner cleaner(state);
132
133    const fs::path path = to_path(state, -1);
134    state.push_string(path.branch_path().c_str());
135    cleaner.forget();
136    return 1;
137}
138
139
140/// Lua binding for fs::path::exists.
141///
142/// \pre stack(-1) The input path.
143/// \post stack(-1) Whether the input path exists or not.
144///
145/// \param state The Lua state.
146///
147/// \return The number of result values, i.e. 1.
148static int
149lua_fs_exists(lutok::state& state)
150{
151    lutok::stack_cleaner cleaner(state);
152
153    const fs::path path = qualify_path(state, to_path(state, -1));
154    state.push_boolean(fs::exists(path));
155    cleaner.forget();
156    return 1;
157}
158
159
160/// Lua binding for the files iterator.
161///
162/// This function takes an open directory from the closure of the iterator and
163/// returns the next entry.  See lua_fs_files() for the iterator generator
164/// function.
165///
166/// \pre upvalue(1) The userdata containing an open DIR* object.
167///
168/// \param state The lua state.
169///
170/// \return The number of result values, i.e. 0 if there are no more entries or
171/// 1 if an entry has been read.
172static int
173files_iterator(lutok::state& state)
174{
175    lutok::stack_cleaner cleaner(state);
176
177    DIR** dirp = state.to_userdata< DIR* >(state.upvalue_index(1));
178    const struct dirent* entry = ::readdir(*dirp);
179    if (entry == NULL)
180        return 0;
181    else {
182        state.push_string(entry->d_name);
183        cleaner.forget();
184        return 1;
185    }
186}
187
188
189/// Lua binding for the destruction of the files iterator.
190///
191/// This function takes an open directory and closes it.  See lua_fs_files() for
192/// the iterator generator function.
193///
194/// \pre stack(-1) The userdata containing an open DIR* object.
195/// \post The DIR* object is closed.
196///
197/// \param state The lua state.
198///
199/// \return The number of result values, i.e. 0.
200static int
201files_gc(lutok::state& state)
202{
203    lutok::stack_cleaner cleaner(state);
204
205    PRE(state.is_userdata(-1));
206
207    DIR** dirp = state.to_userdata< DIR* >(-1);
208    // For some reason, this may be called more than once.  I don't know why
209    // this happens, but we must protect against it.
210    if (*dirp != NULL) {
211        ::closedir(*dirp);
212        *dirp = NULL;
213    }
214
215    return 0;
216}
217
218
219/// Lua binding to create an iterator to scan the contents of a directory.
220///
221/// \pre stack(-1) The input path.
222/// \post stack(-1) The iterator function.
223///
224/// \param state The Lua state.
225///
226/// \return The number of result values, i.e. 1.
227static int
228lua_fs_files(lutok::state& state)
229{
230    lutok::stack_cleaner cleaner(state);
231
232    const fs::path path = qualify_path(state, to_path(state, -1));
233
234    DIR** dirp = state.new_userdata< DIR* >();
235
236    state.new_table();
237    state.push_string("__gc");
238    state.push_cxx_function(files_gc);
239    state.set_table(-3);
240
241    state.set_metatable(-2);
242
243    *dirp = ::opendir(path.c_str());
244    if (*dirp == NULL) {
245        const int original_errno = errno;
246        throw std::runtime_error(F("Failed to open directory: %s") %
247                                 std::strerror(original_errno));
248    }
249
250    state.push_cxx_closure(files_iterator, 1);
251
252    cleaner.forget();
253    return 1;
254}
255
256
257/// Lua binding for fs::path::is_absolute.
258///
259/// \pre stack(-1) The input path.
260/// \post stack(-1) Whether the input path is absolute or not.
261///
262/// \param state The Lua state.
263///
264/// \return The number of result values, i.e. 1.
265static int
266lua_fs_is_absolute(lutok::state& state)
267{
268    lutok::stack_cleaner cleaner(state);
269
270    const fs::path path = to_path(state, -1);
271
272    state.push_boolean(path.is_absolute());
273    cleaner.forget();
274    return 1;
275}
276
277
278/// Lua binding for fs::path::operator/.
279///
280/// \pre stack(-2) The first input path.
281/// \pre stack(-1) The second input path.
282/// \post stack(-1) The concatenation of the two paths.
283///
284/// \param state The Lua state.
285///
286/// \return The number of result values, i.e. 1.
287static int
288lua_fs_join(lutok::state& state)
289{
290    lutok::stack_cleaner cleaner(state);
291
292    const fs::path path1 = to_path(state, -2);
293    const fs::path path2 = to_path(state, -1);
294    state.push_string((path1 / path2).c_str());
295    cleaner.forget();
296    return 1;
297}
298
299
300}  // anonymous namespace
301
302
303/// Creates a Lua 'fs' module with a default start directory of ".".
304///
305/// \post The global 'fs' symbol is set to a table that contains functions to a
306/// variety of utilites from the fs C++ module.
307///
308/// \param s The Lua state.
309void
310fs::open_fs(lutok::state& s)
311{
312    open_fs(s, fs::current_path());
313}
314
315
316/// Creates a Lua 'fs' module with an explicit start directory.
317///
318/// \post The global 'fs' symbol is set to a table that contains functions to a
319/// variety of utilites from the fs C++ module.
320///
321/// \param s The Lua state.
322/// \param start_dir The start directory to use in all operations that reference
323///     the underlying file sytem.
324void
325fs::open_fs(lutok::state& s, const fs::path& start_dir)
326{
327    lutok::stack_cleaner cleaner(s);
328
329    s.push_string(start_dir.str());
330    s.set_global("_fs_start_dir");
331
332    std::map< std::string, lutok::cxx_function > members;
333    members["basename"] = lua_fs_basename;
334    members["dirname"] = lua_fs_dirname;
335    members["exists"] = lua_fs_exists;
336    members["files"] = lua_fs_files;
337    members["is_absolute"] = lua_fs_is_absolute;
338    members["join"] = lua_fs_join;
339    lutok::create_module(s, "fs", members);
340}
341