xref: /llvm-project/mlir/lib/Tools/lsp-server-support/Transport.cpp (revision e40c5b42fe8f489ea4bac4694ef58f09bd95263b)
17bc52733SRiver Riddle //===--- JSONTransport.cpp - sending and receiving LSP messages over JSON -===//
27bc52733SRiver Riddle //
37bc52733SRiver Riddle // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47bc52733SRiver Riddle // See https://llvm.org/LICENSE.txt for license information.
57bc52733SRiver Riddle // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67bc52733SRiver Riddle //
77bc52733SRiver Riddle //===----------------------------------------------------------------------===//
87bc52733SRiver Riddle 
9305d7185SRiver Riddle #include "mlir/Tools/lsp-server-support/Transport.h"
10516ccce7SIngo Müller #include "mlir/Support/ToolUtilities.h"
11305d7185SRiver Riddle #include "mlir/Tools/lsp-server-support/Logging.h"
12305d7185SRiver Riddle #include "mlir/Tools/lsp-server-support/Protocol.h"
137bc52733SRiver Riddle #include "llvm/ADT/SmallString.h"
147bc52733SRiver Riddle #include "llvm/Support/Errno.h"
157bc52733SRiver Riddle #include "llvm/Support/Error.h"
16305d7185SRiver Riddle #include <optional>
177bc52733SRiver Riddle #include <system_error>
187bc52733SRiver Riddle #include <utility>
197bc52733SRiver Riddle 
207bc52733SRiver Riddle using namespace mlir;
217bc52733SRiver Riddle using namespace mlir::lsp;
227bc52733SRiver Riddle 
237bc52733SRiver Riddle //===----------------------------------------------------------------------===//
247bc52733SRiver Riddle // Reply
257bc52733SRiver Riddle //===----------------------------------------------------------------------===//
267bc52733SRiver Riddle 
277bc52733SRiver Riddle namespace {
287bc52733SRiver Riddle /// Function object to reply to an LSP call.
297bc52733SRiver Riddle /// Each instance must be called exactly once, otherwise:
307bc52733SRiver Riddle ///  - if there was no reply, an error reply is sent
317bc52733SRiver Riddle ///  - if there were multiple replies, only the first is sent
327bc52733SRiver Riddle class Reply {
337bc52733SRiver Riddle public:
34cbdf2ef8SRiver Riddle   Reply(const llvm::json::Value &id, StringRef method, JSONTransport &transport,
35cbdf2ef8SRiver Riddle         std::mutex &transportOutputMutex);
367bc52733SRiver Riddle   Reply(Reply &&other);
377bc52733SRiver Riddle   Reply &operator=(Reply &&) = delete;
387bc52733SRiver Riddle   Reply(const Reply &) = delete;
397bc52733SRiver Riddle   Reply &operator=(const Reply &) = delete;
407bc52733SRiver Riddle 
417bc52733SRiver Riddle   void operator()(llvm::Expected<llvm::json::Value> reply);
427bc52733SRiver Riddle 
437bc52733SRiver Riddle private:
44d90159adSWalter Erquinigo   std::string method;
457bc52733SRiver Riddle   std::atomic<bool> replied = {false};
467bc52733SRiver Riddle   llvm::json::Value id;
477bc52733SRiver Riddle   JSONTransport *transport;
48cbdf2ef8SRiver Riddle   std::mutex &transportOutputMutex;
497bc52733SRiver Riddle };
507bc52733SRiver Riddle } // namespace
517bc52733SRiver Riddle 
527bc52733SRiver Riddle Reply::Reply(const llvm::json::Value &id, llvm::StringRef method,
53cbdf2ef8SRiver Riddle              JSONTransport &transport, std::mutex &transportOutputMutex)
54b811ad6fSBrian Gesiak     : method(method), id(id), transport(&transport),
55cbdf2ef8SRiver Riddle       transportOutputMutex(transportOutputMutex) {}
567bc52733SRiver Riddle 
577bc52733SRiver Riddle Reply::Reply(Reply &&other)
58b811ad6fSBrian Gesiak     : method(other.method), replied(other.replied.load()),
59b811ad6fSBrian Gesiak       id(std::move(other.id)), transport(other.transport),
60cbdf2ef8SRiver Riddle       transportOutputMutex(other.transportOutputMutex) {
617bc52733SRiver Riddle   other.transport = nullptr;
627bc52733SRiver Riddle }
637bc52733SRiver Riddle 
647bc52733SRiver Riddle void Reply::operator()(llvm::Expected<llvm::json::Value> reply) {
657bc52733SRiver Riddle   if (replied.exchange(true)) {
667bc52733SRiver Riddle     Logger::error("Replied twice to message {0}({1})", method, id);
677bc52733SRiver Riddle     assert(false && "must reply to each call only once!");
687bc52733SRiver Riddle     return;
697bc52733SRiver Riddle   }
707bc52733SRiver Riddle   assert(transport && "expected valid transport to reply to");
717bc52733SRiver Riddle 
72cbdf2ef8SRiver Riddle   std::lock_guard<std::mutex> transportLock(transportOutputMutex);
737bc52733SRiver Riddle   if (reply) {
747bc52733SRiver Riddle     Logger::info("--> reply:{0}({1})", method, id);
757bc52733SRiver Riddle     transport->reply(std::move(id), std::move(reply));
767bc52733SRiver Riddle   } else {
777bc52733SRiver Riddle     llvm::Error error = reply.takeError();
78*e40c5b42SLily Brown     Logger::info("--> reply:{0}({1}): {2}", method, id, error);
797bc52733SRiver Riddle     transport->reply(std::move(id), std::move(error));
807bc52733SRiver Riddle   }
817bc52733SRiver Riddle }
827bc52733SRiver Riddle 
837bc52733SRiver Riddle //===----------------------------------------------------------------------===//
847bc52733SRiver Riddle // MessageHandler
857bc52733SRiver Riddle //===----------------------------------------------------------------------===//
867bc52733SRiver Riddle 
877bc52733SRiver Riddle bool MessageHandler::onNotify(llvm::StringRef method, llvm::json::Value value) {
887bc52733SRiver Riddle   Logger::info("--> {0}", method);
897bc52733SRiver Riddle 
907bc52733SRiver Riddle   if (method == "exit")
917bc52733SRiver Riddle     return false;
927bc52733SRiver Riddle   if (method == "$cancel") {
937bc52733SRiver Riddle     // TODO: Add support for cancelling requests.
947bc52733SRiver Riddle   } else {
957bc52733SRiver Riddle     auto it = notificationHandlers.find(method);
967bc52733SRiver Riddle     if (it != notificationHandlers.end())
977bc52733SRiver Riddle       it->second(std::move(value));
987bc52733SRiver Riddle   }
997bc52733SRiver Riddle   return true;
1007bc52733SRiver Riddle }
1017bc52733SRiver Riddle 
1027bc52733SRiver Riddle bool MessageHandler::onCall(llvm::StringRef method, llvm::json::Value params,
1037bc52733SRiver Riddle                             llvm::json::Value id) {
1047bc52733SRiver Riddle   Logger::info("--> {0}({1})", method, id);
1057bc52733SRiver Riddle 
106cbdf2ef8SRiver Riddle   Reply reply(id, method, transport, transportOutputMutex);
1077bc52733SRiver Riddle 
1087bc52733SRiver Riddle   auto it = methodHandlers.find(method);
1097bc52733SRiver Riddle   if (it != methodHandlers.end()) {
1107bc52733SRiver Riddle     it->second(std::move(params), std::move(reply));
1117bc52733SRiver Riddle   } else {
1127bc52733SRiver Riddle     reply(llvm::make_error<LSPError>("method not found: " + method.str(),
1137bc52733SRiver Riddle                                      ErrorCode::MethodNotFound));
1147bc52733SRiver Riddle   }
1157bc52733SRiver Riddle   return true;
1167bc52733SRiver Riddle }
1177bc52733SRiver Riddle 
1187bc52733SRiver Riddle bool MessageHandler::onReply(llvm::json::Value id,
1197bc52733SRiver Riddle                              llvm::Expected<llvm::json::Value> result) {
120e24a7bbfSBrian Gesiak   // Find the response handler in the mapping. If it exists, move it out of the
121e24a7bbfSBrian Gesiak   // mapping and erase it.
122e24a7bbfSBrian Gesiak   ResponseHandlerTy responseHandler;
123e24a7bbfSBrian Gesiak   {
124e24a7bbfSBrian Gesiak     std::lock_guard<std::mutex> responseHandlersLock(responseHandlersMutex);
125e24a7bbfSBrian Gesiak     auto it = responseHandlers.find(debugString(id));
126e24a7bbfSBrian Gesiak     if (it != responseHandlers.end()) {
127e24a7bbfSBrian Gesiak       responseHandler = std::move(it->second);
128e24a7bbfSBrian Gesiak       responseHandlers.erase(it);
129e24a7bbfSBrian Gesiak     }
130e24a7bbfSBrian Gesiak   }
131e24a7bbfSBrian Gesiak 
132e24a7bbfSBrian Gesiak   // If we found a response handler, invoke it. Otherwise, log an error.
133e24a7bbfSBrian Gesiak   if (responseHandler.second) {
134e24a7bbfSBrian Gesiak     Logger::info("--> reply:{0}({1})", responseHandler.first, id);
135e24a7bbfSBrian Gesiak     responseHandler.second(std::move(id), std::move(result));
136e24a7bbfSBrian Gesiak   } else {
1377bc52733SRiver Riddle     Logger::error(
138e24a7bbfSBrian Gesiak         "received a reply with ID {0}, but there was no such outgoing request",
139e24a7bbfSBrian Gesiak         id);
1407bc52733SRiver Riddle     if (!result)
1417bc52733SRiver Riddle       llvm::consumeError(result.takeError());
142e24a7bbfSBrian Gesiak   }
1437bc52733SRiver Riddle   return true;
1447bc52733SRiver Riddle }
1457bc52733SRiver Riddle 
1467bc52733SRiver Riddle //===----------------------------------------------------------------------===//
1477bc52733SRiver Riddle // JSONTransport
1487bc52733SRiver Riddle //===----------------------------------------------------------------------===//
1497bc52733SRiver Riddle 
1507bc52733SRiver Riddle /// Encode the given error as a JSON object.
1517bc52733SRiver Riddle static llvm::json::Object encodeError(llvm::Error error) {
1527bc52733SRiver Riddle   std::string message;
1537bc52733SRiver Riddle   ErrorCode code = ErrorCode::UnknownErrorCode;
1547bc52733SRiver Riddle   auto handlerFn = [&](const LSPError &lspError) -> llvm::Error {
1557bc52733SRiver Riddle     message = lspError.message;
1567bc52733SRiver Riddle     code = lspError.code;
1577bc52733SRiver Riddle     return llvm::Error::success();
1587bc52733SRiver Riddle   };
1597bc52733SRiver Riddle   if (llvm::Error unhandled = llvm::handleErrors(std::move(error), handlerFn))
1607bc52733SRiver Riddle     message = llvm::toString(std::move(unhandled));
1617bc52733SRiver Riddle 
1627bc52733SRiver Riddle   return llvm::json::Object{
1637bc52733SRiver Riddle       {"message", std::move(message)},
1647bc52733SRiver Riddle       {"code", int64_t(code)},
1657bc52733SRiver Riddle   };
1667bc52733SRiver Riddle }
1677bc52733SRiver Riddle 
1687bc52733SRiver Riddle /// Decode the given JSON object into an error.
1697bc52733SRiver Riddle llvm::Error decodeError(const llvm::json::Object &o) {
17030c67587SKazu Hirata   StringRef msg = o.getString("message").value_or("Unspecified error");
1711da3a795SFangrui Song   if (std::optional<int64_t> code = o.getInteger("code"))
1727bc52733SRiver Riddle     return llvm::make_error<LSPError>(msg.str(), ErrorCode(*code));
1737bc52733SRiver Riddle   return llvm::make_error<llvm::StringError>(llvm::inconvertibleErrorCode(),
1747bc52733SRiver Riddle                                              msg.str());
1757bc52733SRiver Riddle }
1767bc52733SRiver Riddle 
1777bc52733SRiver Riddle void JSONTransport::notify(StringRef method, llvm::json::Value params) {
1787bc52733SRiver Riddle   sendMessage(llvm::json::Object{
1797bc52733SRiver Riddle       {"jsonrpc", "2.0"},
1807bc52733SRiver Riddle       {"method", method},
1817bc52733SRiver Riddle       {"params", std::move(params)},
1827bc52733SRiver Riddle   });
1837bc52733SRiver Riddle }
1847bc52733SRiver Riddle void JSONTransport::call(StringRef method, llvm::json::Value params,
1857bc52733SRiver Riddle                          llvm::json::Value id) {
1867bc52733SRiver Riddle   sendMessage(llvm::json::Object{
1877bc52733SRiver Riddle       {"jsonrpc", "2.0"},
1887bc52733SRiver Riddle       {"id", std::move(id)},
1897bc52733SRiver Riddle       {"method", method},
1907bc52733SRiver Riddle       {"params", std::move(params)},
1917bc52733SRiver Riddle   });
1927bc52733SRiver Riddle }
1937bc52733SRiver Riddle void JSONTransport::reply(llvm::json::Value id,
1947bc52733SRiver Riddle                           llvm::Expected<llvm::json::Value> result) {
1957bc52733SRiver Riddle   if (result) {
1967bc52733SRiver Riddle     return sendMessage(llvm::json::Object{
1977bc52733SRiver Riddle         {"jsonrpc", "2.0"},
1987bc52733SRiver Riddle         {"id", std::move(id)},
1997bc52733SRiver Riddle         {"result", std::move(*result)},
2007bc52733SRiver Riddle     });
2017bc52733SRiver Riddle   }
2027bc52733SRiver Riddle 
2037bc52733SRiver Riddle   sendMessage(llvm::json::Object{
2047bc52733SRiver Riddle       {"jsonrpc", "2.0"},
2057bc52733SRiver Riddle       {"id", std::move(id)},
2067bc52733SRiver Riddle       {"error", encodeError(result.takeError())},
2077bc52733SRiver Riddle   });
2087bc52733SRiver Riddle }
2097bc52733SRiver Riddle 
2107bc52733SRiver Riddle llvm::Error JSONTransport::run(MessageHandler &handler) {
2117bc52733SRiver Riddle   std::string json;
2127bc52733SRiver Riddle   while (!feof(in)) {
2137bc52733SRiver Riddle     if (ferror(in)) {
2147bc52733SRiver Riddle       return llvm::errorCodeToError(
2157bc52733SRiver Riddle           std::error_code(errno, std::system_category()));
2167bc52733SRiver Riddle     }
2177bc52733SRiver Riddle 
2187bc52733SRiver Riddle     if (succeeded(readMessage(json))) {
2197bc52733SRiver Riddle       if (llvm::Expected<llvm::json::Value> doc = llvm::json::parse(json)) {
2207bc52733SRiver Riddle         if (!handleMessage(std::move(*doc), handler))
2217bc52733SRiver Riddle           return llvm::Error::success();
2227bc52733SRiver Riddle       } else {
2237bc52733SRiver Riddle         Logger::error("JSON parse error: {0}", llvm::toString(doc.takeError()));
2247bc52733SRiver Riddle       }
2257bc52733SRiver Riddle     }
2267bc52733SRiver Riddle   }
2277bc52733SRiver Riddle   return llvm::errorCodeToError(std::make_error_code(std::errc::io_error));
2287bc52733SRiver Riddle }
2297bc52733SRiver Riddle 
2307bc52733SRiver Riddle void JSONTransport::sendMessage(llvm::json::Value msg) {
2317bc52733SRiver Riddle   outputBuffer.clear();
2327bc52733SRiver Riddle   llvm::raw_svector_ostream os(outputBuffer);
2337bc52733SRiver Riddle   os << llvm::formatv(prettyOutput ? "{0:2}\n" : "{0}", msg);
2347bc52733SRiver Riddle   out << "Content-Length: " << outputBuffer.size() << "\r\n\r\n"
2357bc52733SRiver Riddle       << outputBuffer;
2367bc52733SRiver Riddle   out.flush();
2377bc52733SRiver Riddle   Logger::debug(">>> {0}\n", outputBuffer);
2387bc52733SRiver Riddle }
2397bc52733SRiver Riddle 
2407bc52733SRiver Riddle bool JSONTransport::handleMessage(llvm::json::Value msg,
2417bc52733SRiver Riddle                                   MessageHandler &handler) {
2427bc52733SRiver Riddle   // Message must be an object with "jsonrpc":"2.0".
2437bc52733SRiver Riddle   llvm::json::Object *object = msg.getAsObject();
2447bc52733SRiver Riddle   if (!object ||
2450a81ace0SKazu Hirata       object->getString("jsonrpc") != std::optional<StringRef>("2.0"))
2467bc52733SRiver Riddle     return false;
2477bc52733SRiver Riddle 
2487bc52733SRiver Riddle   // `id` may be any JSON value. If absent, this is a notification.
2490a81ace0SKazu Hirata   std::optional<llvm::json::Value> id;
2507bc52733SRiver Riddle   if (llvm::json::Value *i = object->get("id"))
2517bc52733SRiver Riddle     id = std::move(*i);
2521da3a795SFangrui Song   std::optional<StringRef> method = object->getString("method");
2537bc52733SRiver Riddle 
2547bc52733SRiver Riddle   // This is a response.
2557bc52733SRiver Riddle   if (!method) {
2567bc52733SRiver Riddle     if (!id)
2577bc52733SRiver Riddle       return false;
2587bc52733SRiver Riddle     if (auto *err = object->getObject("error"))
2597bc52733SRiver Riddle       return handler.onReply(std::move(*id), decodeError(*err));
2607bc52733SRiver Riddle     // result should be given, use null if not.
2617bc52733SRiver Riddle     llvm::json::Value result = nullptr;
2627bc52733SRiver Riddle     if (llvm::json::Value *r = object->get("result"))
2637bc52733SRiver Riddle       result = std::move(*r);
2647bc52733SRiver Riddle     return handler.onReply(std::move(*id), std::move(result));
2657bc52733SRiver Riddle   }
2667bc52733SRiver Riddle 
2677bc52733SRiver Riddle   // Params should be given, use null if not.
2687bc52733SRiver Riddle   llvm::json::Value params = nullptr;
2697bc52733SRiver Riddle   if (llvm::json::Value *p = object->get("params"))
2707bc52733SRiver Riddle     params = std::move(*p);
2717bc52733SRiver Riddle 
2727bc52733SRiver Riddle   if (id)
2737bc52733SRiver Riddle     return handler.onCall(*method, std::move(params), std::move(*id));
2747bc52733SRiver Riddle   return handler.onNotify(*method, std::move(params));
2757bc52733SRiver Riddle }
2767bc52733SRiver Riddle 
2777bc52733SRiver Riddle /// Tries to read a line up to and including \n.
2787bc52733SRiver Riddle /// If failing, feof(), ferror(), or shutdownRequested() will be set.
2797bc52733SRiver Riddle LogicalResult readLine(std::FILE *in, SmallVectorImpl<char> &out) {
2807bc52733SRiver Riddle   // Big enough to hold any reasonable header line. May not fit content lines
2817bc52733SRiver Riddle   // in delimited mode, but performance doesn't matter for that mode.
2827bc52733SRiver Riddle   static constexpr int bufSize = 128;
2837bc52733SRiver Riddle   size_t size = 0;
2847bc52733SRiver Riddle   out.clear();
2857bc52733SRiver Riddle   for (;;) {
2867bc52733SRiver Riddle     out.resize_for_overwrite(size + bufSize);
2877bc52733SRiver Riddle     if (!std::fgets(&out[size], bufSize, in))
2887bc52733SRiver Riddle       return failure();
2897bc52733SRiver Riddle 
2907bc52733SRiver Riddle     clearerr(in);
2917bc52733SRiver Riddle 
2927bc52733SRiver Riddle     // If the line contained null bytes, anything after it (including \n) will
2937bc52733SRiver Riddle     // be ignored. Fortunately this is not a legal header or JSON.
2947bc52733SRiver Riddle     size_t read = std::strlen(&out[size]);
2957bc52733SRiver Riddle     if (read > 0 && out[size + read - 1] == '\n') {
2967bc52733SRiver Riddle       out.resize(size + read);
2977bc52733SRiver Riddle       return success();
2987bc52733SRiver Riddle     }
2997bc52733SRiver Riddle     size += read;
3007bc52733SRiver Riddle   }
3017bc52733SRiver Riddle }
3027bc52733SRiver Riddle 
30370c73d1bSKazu Hirata // Returns std::nullopt when:
3047bc52733SRiver Riddle //  - ferror(), feof(), or shutdownRequested() are set.
3057bc52733SRiver Riddle //  - Content-Length is missing or empty (protocol error)
3067bc52733SRiver Riddle LogicalResult JSONTransport::readStandardMessage(std::string &json) {
3077bc52733SRiver Riddle   // A Language Server Protocol message starts with a set of HTTP headers,
3087bc52733SRiver Riddle   // delimited  by \r\n, and terminated by an empty line (\r\n).
3097bc52733SRiver Riddle   unsigned long long contentLength = 0;
3107bc52733SRiver Riddle   llvm::SmallString<128> line;
3117bc52733SRiver Riddle   while (true) {
3127bc52733SRiver Riddle     if (feof(in) || ferror(in) || failed(readLine(in, line)))
3137bc52733SRiver Riddle       return failure();
3147bc52733SRiver Riddle 
3157bc52733SRiver Riddle     // Content-Length is a mandatory header, and the only one we handle.
3167bc52733SRiver Riddle     StringRef lineRef = line;
3177bc52733SRiver Riddle     if (lineRef.consume_front("Content-Length: ")) {
3187bc52733SRiver Riddle       llvm::getAsUnsignedInteger(lineRef.trim(), 0, contentLength);
3197bc52733SRiver Riddle     } else if (!lineRef.trim().empty()) {
3207bc52733SRiver Riddle       // It's another header, ignore it.
3217bc52733SRiver Riddle       continue;
3227bc52733SRiver Riddle     } else {
3237bc52733SRiver Riddle       // An empty line indicates the end of headers. Go ahead and read the JSON.
3247bc52733SRiver Riddle       break;
3257bc52733SRiver Riddle     }
3267bc52733SRiver Riddle   }
3277bc52733SRiver Riddle 
3287bc52733SRiver Riddle   // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"
3297bc52733SRiver Riddle   if (contentLength == 0 || contentLength > 1 << 30)
3307bc52733SRiver Riddle     return failure();
3317bc52733SRiver Riddle 
3327bc52733SRiver Riddle   json.resize(contentLength);
3337bc52733SRiver Riddle   for (size_t pos = 0, read; pos < contentLength; pos += read) {
3347bc52733SRiver Riddle     read = std::fread(&json[pos], 1, contentLength - pos, in);
3357bc52733SRiver Riddle     if (read == 0)
3367bc52733SRiver Riddle       return failure();
3377bc52733SRiver Riddle 
3387bc52733SRiver Riddle     // If we're done, the error was transient. If we're not done, either it was
3397bc52733SRiver Riddle     // transient or we'll see it again on retry.
3407bc52733SRiver Riddle     clearerr(in);
3417bc52733SRiver Riddle     pos += read;
3427bc52733SRiver Riddle   }
3437bc52733SRiver Riddle   return success();
3447bc52733SRiver Riddle }
3457bc52733SRiver Riddle 
3467bc52733SRiver Riddle /// For lit tests we support a simplified syntax:
3477bc52733SRiver Riddle /// - messages are delimited by '// -----' on a line by itself
3487bc52733SRiver Riddle /// - lines starting with // are ignored.
3497bc52733SRiver Riddle /// This is a testing path, so favor simplicity over performance here.
3507bc52733SRiver Riddle /// When returning failure: feof(), ferror(), or shutdownRequested() will be
3517bc52733SRiver Riddle /// set.
3527bc52733SRiver Riddle LogicalResult JSONTransport::readDelimitedMessage(std::string &json) {
3537bc52733SRiver Riddle   json.clear();
3547bc52733SRiver Riddle   llvm::SmallString<128> line;
3557bc52733SRiver Riddle   while (succeeded(readLine(in, line))) {
3567bc52733SRiver Riddle     StringRef lineRef = line.str().trim();
35788d319a2SKazu Hirata     if (lineRef.starts_with("//")) {
3587bc52733SRiver Riddle       // Found a delimiter for the message.
359516ccce7SIngo Müller       if (lineRef == kDefaultSplitMarker)
3607bc52733SRiver Riddle         break;
3617bc52733SRiver Riddle       continue;
3627bc52733SRiver Riddle     }
3637bc52733SRiver Riddle 
3647bc52733SRiver Riddle     json += line;
3657bc52733SRiver Riddle   }
3667bc52733SRiver Riddle 
3677bc52733SRiver Riddle   return failure(ferror(in));
3687bc52733SRiver Riddle }
369