Remake
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
Functions
Server

Functions

static void complete_job (int job_id, bool success)
 
static std::string prepare_script (rule_t const &rule)
 
static bool run_script (int job_id, rule_t const &rule)
 
static bool start (std::string const &target, client_list::iterator &current)
 
static void complete_request (client_t &client, bool success)
 
static bool has_free_slots ()
 
static bool handle_clients ()
 
static void create_server ()
 
void accept_client ()
 
void finalize_job (pid_t pid, bool res)
 
void server_loop ()
 
void server_mode (std::string const &remakefile, string_list const &targets)
 

Detailed Description

Function Documentation

void accept_client ( )

Accept a connection from a client, get the job it spawned from, get the targets, and mark them as dependencies of the job targets.

Definition at line 2418 of file remake.cpp.

Referenced by server_loop().

2419 {
2420  DEBUG_open << "Handling client request... ";
2421 
2422  // Accept connection.
2423 #ifdef WINDOWS
2424  socket_t fd = accept(socket_fd, NULL, NULL);
2425  if (fd == INVALID_SOCKET) return;
2426  if (!SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, 0))
2427  {
2428  error2:
2429  std::cerr << "Unexpected failure while setting connection with client" << std::endl;
2430  closesocket(fd);
2431  return;
2432  }
2433  // WSAEventSelect puts sockets into nonblocking mode, so disable it here.
2434  u_long nbio = 0;
2435  if (ioctlsocket(fd, FIONBIO, &nbio)) goto error2;
2436 #elif defined(LINUX)
2437  int fd = accept4(socket_fd, NULL, NULL, SOCK_CLOEXEC);
2438  if (fd < 0) return;
2439 #else
2440  int fd = accept(socket_fd, NULL, NULL);
2441  if (fd < 0) return;
2442  if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) return;
2443 #endif
2444  clients.push_front(client_t());
2445  client_list::iterator proc = clients.begin();
2446 
2447  if (false)
2448  {
2449  error:
2450  DEBUG_close << "failed\n";
2451  std::cerr << "Received an ill-formed client message" << std::endl;
2452  #ifdef WINDOWS
2453  closesocket(fd);
2454  #else
2455  close(fd);
2456  #endif
2457  clients.erase(proc);
2458  return;
2459  }
2460 
2461  // Receive message. Stop when encountering two nuls in a row.
2462  std::vector<char> buf;
2463  size_t len = 0;
2464  while (len < sizeof(int) + 2 || buf[len - 1] || buf[len - 2])
2465  {
2466  buf.resize(len + 1024);
2467  ssize_t l = recv(fd, &buf[0] + len, 1024, 0);
2468  if (l <= 0) goto error;
2469  len += l;
2470  }
2471 
2472  // Parse job that spawned the client.
2473  int job_id;
2474  memcpy(&job_id, &buf[0], sizeof(int));
2475  proc->socket = fd;
2476  proc->job_id = job_id;
2477  job_targets_map::const_iterator i = job_targets.find(job_id);
2478  if (i == job_targets.end()) goto error;
2479  DEBUG << "receiving request from job " << job_id << std::endl;
2480 
2481  // Parse the targets and mark them as dependencies from the job targets.
2482  dependency_t &dep = *dependencies[job_targets[job_id].front()];
2483  char const *p = &buf[0] + sizeof(int);
2484  while (true)
2485  {
2486  len = strlen(p);
2487  if (len == 0)
2488  {
2489  ++waiting_jobs;
2490  return;
2491  }
2492  std::string target(p, p + len);
2493  DEBUG << "adding dependency " << target << " to job\n";
2494  proc->pending.push_back(target);
2495  dep.deps.insert(target);
2496  p += len + 1;
2497  }
2498 }
static void complete_job ( int  job_id,
bool  success 
)
static

Handle job completion.

Definition at line 1914 of file remake.cpp.

Referenced by complete_request(), finalize_job(), and run_script().

1915 {
1916  DEBUG_open << "Completing job " << job_id << "... ";
1917  job_targets_map::iterator i = job_targets.find(job_id);
1918  assert(i != job_targets.end());
1919  string_list const &targets = i->second;
1920  if (success)
1921  {
1922  for (string_list::const_iterator j = targets.begin(),
1923  j_end = targets.end(); j != j_end; ++j)
1924  {
1925  update_status(*j);
1926  }
1927  }
1928  else
1929  {
1930  DEBUG_close << "failed\n";
1931  std::cerr << "Failed to build";
1932  for (string_list::const_iterator j = targets.begin(),
1933  j_end = targets.end(); j != j_end; ++j)
1934  {
1935  status[*j].status = Failed;
1936  std::cerr << ' ' << *j;
1937  remove(j->c_str());
1938  }
1939  std::cerr << std::endl;
1940  }
1941  job_targets.erase(i);
1942 }
static void complete_request ( client_t client,
bool  success 
)
static

Send a reply to a client then remove it. If the client was a dependency client, start the actual script.

Definition at line 2184 of file remake.cpp.

Referenced by handle_clients().

2185 {
2186  DEBUG_open << "Completing request from client of job " << client.job_id << "... ";
2187  if (client.delayed)
2188  {
2189  assert(client.socket == INVALID_SOCKET);
2190  if (success)
2191  {
2192  if (still_need_rebuild(client.delayed->targets.front()))
2193  run_script(client.job_id, *client.delayed);
2194  else complete_job(client.job_id, true);
2195  }
2196  else complete_job(client.job_id, false);
2197  delete client.delayed;
2198  }
2199  else if (client.socket != INVALID_SOCKET)
2200  {
2201  char res = success ? 1 : 0;
2202  send(client.socket, &res, 1, 0);
2203  #ifdef WINDOWS
2204  closesocket(client.socket);
2205  #else
2206  close(client.socket);
2207  #endif
2208  --waiting_jobs;
2209  }
2210 
2211  if (client.job_id < 0 && !success) build_failure = true;
2212 }
static void create_server ( )
static

Create a named unix socket that listens for build requests. Also set the REMAKE_SOCKET environment variable that will be inherited by all the job scripts.

Definition at line 2338 of file remake.cpp.

Referenced by server_mode().

2339 {
2340  if (false)
2341  {
2342  error:
2343  perror("Failed to create server");
2344 #ifndef WINDOWS
2345  error2:
2346 #endif
2347  exit(EXIT_FAILURE);
2348  }
2349  DEBUG_open << "Creating server... ";
2350 
2351 #ifdef WINDOWS
2352  // Prepare a windows socket.
2353  struct sockaddr_in socket_addr;
2354  socket_addr.sin_family = AF_INET;
2355  socket_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
2356  socket_addr.sin_port = 0;
2357 
2358  // Create and listen to the socket.
2359  socket_fd = socket(AF_INET, SOCK_STREAM, 0);
2360  if (socket_fd == INVALID_SOCKET) goto error;
2361  if (!SetHandleInformation((HANDLE)socket_fd, HANDLE_FLAG_INHERIT, 0))
2362  goto error;
2363  if (bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(sockaddr_in)))
2364  goto error;
2365  int len = sizeof(sockaddr_in);
2366  if (getsockname(socket_fd, (struct sockaddr *)&socket_addr, &len))
2367  goto error;
2368  std::ostringstream buf;
2369  buf << socket_addr.sin_port;
2370  if (!SetEnvironmentVariable("REMAKE_SOCKET", buf.str().c_str()))
2371  goto error;
2372  if (listen(socket_fd, 1000)) goto error;
2373 #else
2374  // Set signal handlers for SIGCHLD and SIGINT.
2375  // Block SIGCHLD (unblocked during select).
2376  sigset_t sigmask;
2377  sigemptyset(&sigmask);
2378  sigaddset(&sigmask, SIGCHLD);
2379  if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) goto error;
2380  struct sigaction sa;
2381  sa.sa_flags = 0;
2382  sigemptyset(&sa.sa_mask);
2383  sa.sa_handler = &sigchld_handler;
2384  if (sigaction(SIGCHLD, &sa, NULL) == -1) goto error;
2385  sa.sa_handler = &sigint_handler;
2386  if (sigaction(SIGINT, &sa, NULL) == -1) goto error;
2387 
2388  // Prepare a named unix socket in temporary directory.
2389  socket_name = tempnam(NULL, "rmk-");
2390  if (!socket_name) goto error2;
2391  struct sockaddr_un socket_addr;
2392  size_t len = strlen(socket_name);
2393  if (len >= sizeof(socket_addr.sun_path) - 1) goto error2;
2394  socket_addr.sun_family = AF_UNIX;
2395  strcpy(socket_addr.sun_path, socket_name);
2396  len += sizeof(socket_addr.sun_family);
2397  if (setenv("REMAKE_SOCKET", socket_name, 1)) goto error;
2398 
2399  // Create and listen to the socket.
2400 #ifdef LINUX
2401  socket_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
2402  if (socket_fd == INVALID_SOCKET) goto error;
2403 #else
2404  socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
2405  if (socket_fd == INVALID_SOCKET) goto error;
2406  if (fcntl(socket_fd, F_SETFD, FD_CLOEXEC) < 0) goto error;
2407 #endif
2408  if (bind(socket_fd, (struct sockaddr *)&socket_addr, len))
2409  goto error;
2410  if (listen(socket_fd, 1000)) goto error;
2411 #endif
2412 }
void finalize_job ( pid_t  pid,
bool  res 
)

Handle child process exit status.

Definition at line 2503 of file remake.cpp.

Referenced by server_loop().

2504 {
2505  pid_job_map::iterator i = job_pids.find(pid);
2506  assert(i != job_pids.end());
2507  int job_id = i->second;
2508  job_pids.erase(i);
2509  --running_jobs;
2510  complete_job(job_id, res);
2511 }
static bool handle_clients ( )
static

Handle client requests:

  • check for running targets that have finished,
  • start as many pending targets as allowed,
  • complete the request if there are neither running nor pending targets left or if any of them failed.
Returns
true if some child processes are still running.
Postcondition
If there are pending requests, at least one child process is running.

Definition at line 2234 of file remake.cpp.

Referenced by server_loop().

2235 {
2236  DEBUG_open << "Handling client requests... ";
2237  restart:
2238 
2239  for (client_list::iterator i = clients.begin(), i_next = i,
2240  i_end = clients.end(); i != i_end && has_free_slots(); i = i_next)
2241  {
2242  ++i_next;
2243  DEBUG_open << "Handling client from job " << i->job_id << "... ";
2244  if (false)
2245  {
2246  failed:
2247  complete_request(*i, false);
2248  clients.erase(i);
2249  DEBUG_close << "failed\n";
2250  continue;
2251  }
2252 
2253  // Remove running targets that have finished.
2254  for (string_set::iterator j = i->running.begin(), j_next = j,
2255  j_end = i->running.end(); j != j_end; j = j_next)
2256  {
2257  ++j_next;
2258  status_map::const_iterator k = status.find(*j);
2259  assert(k != status.end());
2260  switch (k->second.status)
2261  {
2262  case Running:
2263  break;
2264  case Failed:
2265  if (!keep_going) goto failed;
2266  i->failed = true;
2267  // no break
2268  case Uptodate:
2269  case Remade:
2270  i->running.erase(j);
2271  break;
2272  case Recheck:
2273  case Todo:
2274  assert(false);
2275  }
2276  }
2277 
2278  // Start pending targets.
2279  while (!i->pending.empty())
2280  {
2281  std::string target = i->pending.front();
2282  i->pending.pop_front();
2283  switch (get_status(target).status)
2284  {
2285  case Running:
2286  i->running.insert(target);
2287  break;
2288  case Failed:
2289  pending_failed:
2290  if (!keep_going) goto failed;
2291  i->failed = true;
2292  // no break
2293  case Uptodate:
2294  case Remade:
2295  break;
2296  case Recheck:
2297  case Todo:
2298  client_list::iterator j = i;
2299  if (!start(target, i)) goto pending_failed;
2300  j->running.insert(target);
2301  if (!has_free_slots()) return true;
2302  // Job start might insert a dependency client.
2303  i_next = i;
2304  ++i_next;
2305  break;
2306  }
2307  }
2308 
2309  // Try to complete the request.
2310  // (This might start a new job if it was a dependency client.)
2311  if (i->running.empty())
2312  {
2313  if (i->failed) goto failed;
2314  complete_request(*i, true);
2315  clients.erase(i);
2316  DEBUG_close << "finished\n";
2317  }
2318  }
2319 
2320  if (running_jobs != waiting_jobs) return true;
2321  if (running_jobs == 0 && clients.empty()) return false;
2322 
2323  // There is a circular dependency.
2324  // Try to break it by completing one of the requests.
2325  assert(!clients.empty());
2326  std::cerr << "Circular dependency detected" << std::endl;
2327  client_list::iterator i = clients.begin();
2328  complete_request(*i, false);
2329  clients.erase(i);
2330  goto restart;
2331 }
static bool has_free_slots ( )
static

Return whether there are slots for starting new jobs.

Definition at line 2217 of file remake.cpp.

Referenced by handle_clients().

2218 {
2219  if (max_active_jobs <= 0) return true;
2221 }
static std::string prepare_script ( rule_t const &  rule)
static

Return the script obtained by substituting variables.

Definition at line 1947 of file remake.cpp.

Referenced by run_script().

1948 {
1949  std::string const &s = rule.script;
1950  std::istringstream in(s);
1951  std::ostringstream out;
1952  size_t len = s.size();
1953 
1954  while (!in.eof())
1955  {
1956  size_t pos = in.tellg(), p = s.find('$', pos);
1957  if (p == std::string::npos || p == len - 1) p = len;
1958  out.write(&s[pos], p - pos);
1959  if (p == len) break;
1960  ++p;
1961  switch (s[p])
1962  {
1963  case '$':
1964  out << '$';
1965  in.seekg(p + 1);
1966  break;
1967  case '<':
1968  if (!rule.deps.empty())
1969  out << rule.deps.front();
1970  in.seekg(p + 1);
1971  break;
1972  case '^':
1973  {
1974  bool first = true;
1975  for (string_list::const_iterator i = rule.deps.begin(),
1976  i_end = rule.deps.end(); i != i_end; ++i)
1977  {
1978  if (first) first = false;
1979  else out << ' ';
1980  out << *i;
1981  }
1982  in.seekg(p + 1);
1983  break;
1984  }
1985  case '@':
1986  assert(!rule.targets.empty());
1987  out << rule.targets.front();
1988  in.seekg(p + 1);
1989  break;
1990  case '*':
1991  out << rule.stem;
1992  in.seekg(p + 1);
1993  break;
1994  case '(':
1995  {
1996  in.seekg(p - 1);
1997  bool first = true;
1998  input_generator gen(in, &rule.vars, true);
1999  while (true)
2000  {
2001  std::string w;
2002  input_status s = gen.next(w);
2003  if (s == SyntaxError)
2004  {
2005  // TODO
2006  return "false";
2007  }
2008  if (s == Eof) break;
2009  if (first) first = false;
2010  else out << ' ';
2011  out << w;
2012  }
2013  break;
2014  }
2015  default:
2016  // Let dollars followed by an unrecognized character
2017  // go through. This differs from Make, which would
2018  // use a one-letter variable.
2019  out << '$';
2020  in.seekg(p);
2021  }
2022  }
2023 
2024  return out.str();
2025 }
static bool run_script ( int  job_id,
rule_t const &  rule 
)
static

Execute the script from rule.

Definition at line 2030 of file remake.cpp.

Referenced by complete_request(), and start().

2031 {
2032  if (show_targets)
2033  {
2034  std::cout << "Building";
2035  for (string_list::const_iterator i = rule.targets.begin(),
2036  i_end = rule.targets.end(); i != i_end; ++i)
2037  {
2038  std::cout << ' ' << *i;
2039  }
2040  std::cout << std::endl;
2041  }
2042 
2044  dep->targets = rule.targets;
2045  dep->deps.insert(rule.deps.begin(), rule.deps.end());
2046  for (string_list::const_iterator i = rule.targets.begin(),
2047  i_end = rule.targets.end(); i != i_end; ++i)
2048  {
2049  dependencies[*i] = dep;
2050  }
2051 
2052  std::string script = prepare_script(rule);
2053 
2054  std::ostringstream job_id_buf;
2055  job_id_buf << job_id;
2056  std::string job_id_ = job_id_buf.str();
2057 
2058  DEBUG_open << "Starting script for job " << job_id << "... ";
2059  if (false)
2060  {
2061  error:
2062  DEBUG_close << "failed\n";
2063  complete_job(job_id, false);
2064  return false;
2065  }
2066 
2067 #ifdef WINDOWS
2068  HANDLE pfd[2];
2069  if (false)
2070  {
2071  error2:
2072  CloseHandle(pfd[0]);
2073  CloseHandle(pfd[1]);
2074  goto error;
2075  }
2076  if (!CreatePipe(&pfd[0], &pfd[1], NULL, 0))
2077  goto error;
2078  if (!SetHandleInformation(pfd[0], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
2079  goto error2;
2080  STARTUPINFO si;
2081  ZeroMemory(&si, sizeof(STARTUPINFO));
2082  si.cb = sizeof(STARTUPINFO);
2083  si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
2084  si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
2085  si.hStdInput = pfd[0];
2086  si.dwFlags |= STARTF_USESTDHANDLES;
2087  PROCESS_INFORMATION pi;
2088  ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
2089  if (!SetEnvironmentVariable("REMAKE_JOB_ID", job_id_.c_str()))
2090  goto error2;
2091  char const *argv = echo_scripts ? "SH.EXE -e -s -v" : "SH.EXE -e -s";
2092  if (!CreateProcess(NULL, (char *)argv, NULL, NULL,
2093  true, 0, NULL, NULL, &si, &pi))
2094  {
2095  goto error2;
2096  }
2097  CloseHandle(pi.hThread);
2098  DWORD len = script.length(), wlen;
2099  if (!WriteFile(pfd[1], script.c_str(), len, &wlen, NULL) || wlen < len)
2100  std::cerr << "Unexpected failure while sending script to shell" << std::endl;
2101  CloseHandle(pfd[0]);
2102  CloseHandle(pfd[1]);
2103  ++running_jobs;
2104  job_pids[pi.hProcess] = job_id;
2105  return true;
2106 #else
2107  int pfd[2];
2108  if (false)
2109  {
2110  error2:
2111  close(pfd[0]);
2112  close(pfd[1]);
2113  goto error;
2114  }
2115  if (pipe(pfd) == -1)
2116  goto error;
2117  if (pid_t pid = fork())
2118  {
2119  if (pid == -1) goto error2;
2120  ssize_t len = script.length();
2121  if (write(pfd[1], script.c_str(), len) < len)
2122  std::cerr << "Unexpected failure while sending script to shell" << std::endl;
2123  close(pfd[0]);
2124  close(pfd[1]);
2125  ++running_jobs;
2126  job_pids[pid] = job_id;
2127  return true;
2128  }
2129  // Child process starts here.
2130  if (setenv("REMAKE_JOB_ID", job_id_.c_str(), 1))
2131  _exit(EXIT_FAILURE);
2132  char const *argv[5] = { "sh", "-e", "-s", NULL, NULL };
2133  if (echo_scripts) argv[3] = "-v";
2134  if (pfd[0] != 0)
2135  {
2136  dup2(pfd[0], 0);
2137  close(pfd[0]);
2138  }
2139  close(pfd[1]);
2140  execv("/bin/sh", (char **)argv);
2141  _exit(EXIT_FAILURE);
2142 #endif
2143 }
void server_loop ( )

Loop until all the jobs have finished.

Postcondition
There are no client requests left, not even virtual ones.

Definition at line 2518 of file remake.cpp.

Referenced by server_mode().

2519 {
2520  while (handle_clients())
2521  {
2522  DEBUG_open << "Handling events... ";
2523  #ifdef WINDOWS
2524  size_t len = job_pids.size() + 1;
2525  HANDLE h[len];
2526  int num = 0;
2527  for (pid_job_map::const_iterator i = job_pids.begin(),
2528  i_end = job_pids.end(); i != i_end; ++i, ++num)
2529  {
2530  h[num] = i->first;
2531  }
2532  WSAEVENT aev = WSACreateEvent();
2533  h[num] = aev;
2534  WSAEventSelect(socket_fd, aev, FD_ACCEPT);
2535  DWORD w = WaitForMultipleObjects(len, h, false, INFINITE);
2536  WSAEventSelect(socket_fd, aev, 0);
2537  WSACloseEvent(aev);
2538  if (len <= w)
2539  continue;
2540  if (w == len - 1)
2541  {
2542  accept_client();
2543  continue;
2544  }
2545  pid_t pid = h[w];
2546  DWORD s = 0;
2547  bool res = GetExitCodeProcess(pid, &s) && s == 0;
2548  CloseHandle(pid);
2549  finalize_job(pid, res);
2550  #else
2551  sigset_t emptymask;
2552  sigemptyset(&emptymask);
2553  fd_set fdset;
2554  FD_ZERO(&fdset);
2555  FD_SET(socket_fd, &fdset);
2556  int ret = pselect(socket_fd + 1, &fdset, NULL, NULL, NULL, &emptymask);
2557  if (ret > 0 /* && FD_ISSET(socket_fd, &fdset)*/) accept_client();
2558  if (!got_SIGCHLD) continue;
2559  got_SIGCHLD = 0;
2560  pid_t pid;
2561  int status;
2562  while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
2563  {
2564  bool res = WIFEXITED(status) && WEXITSTATUS(status) == 0;
2565  finalize_job(pid, res);
2566  }
2567  #endif
2568  }
2569 
2570  assert(clients.empty());
2571 }
void server_mode ( std::string const &  remakefile,
string_list const &  targets 
)

Load dependencies and rules, listen to client requests, and loop until all the requests have completed. If Remakefile is obsolete, perform a first run with it only, then reload the rules, and perform a second with the original clients.

Definition at line 2579 of file remake.cpp.

Referenced by main().

2580 {
2582  load_rules(remakefile);
2583  create_server();
2584  if (get_status(remakefile).status != Uptodate)
2585  {
2586  clients.push_back(client_t());
2587  clients.back().pending.push_back(remakefile);
2588  server_loop();
2589  if (build_failure) goto early_exit;
2590  variables.clear();
2591  specific_rules.clear();
2592  generic_rules.clear();
2593  first_target.clear();
2594  load_rules(remakefile);
2595  }
2596  clients.push_back(client_t());
2597  if (!targets.empty()) clients.back().pending = targets;
2598  else if (!first_target.empty())
2599  clients.back().pending.push_back(first_target);
2600  server_loop();
2601  early_exit:
2602  close(socket_fd);
2603 #ifndef WINDOWS
2604  remove(socket_name);
2605  free(socket_name);
2606 #endif
2608  exit(build_failure ? EXIT_FAILURE : EXIT_SUCCESS);
2609 }
static bool start ( std::string const &  target,
client_list::iterator &  current 
)
static

Create a job for target according to the loaded rules. Mark all the targets from the rule as running and reset their dependencies. If the rule has dependencies, create a new client to build them just before current, and change current so that it points to it.

Definition at line 2151 of file remake.cpp.

Referenced by handle_clients().

2152 {
2153  DEBUG_open << "Starting job " << job_counter << " for " << target << "... ";
2154  rule_t rule = find_rule(target);
2155  if (rule.targets.empty())
2156  {
2157  status[target].status = Failed;
2158  DEBUG_close << "failed\n";
2159  std::cerr << "No rule for building " << target << std::endl;
2160  return false;
2161  }
2162  for (string_list::const_iterator i = rule.targets.begin(),
2163  i_end = rule.targets.end(); i != i_end; ++i)
2164  {
2165  status[*i].status = Running;
2166  }
2167  int job_id = job_counter++;
2168  job_targets[job_id] = rule.targets;
2169  if (!rule.deps.empty())
2170  {
2171  current = clients.insert(current, client_t());
2172  current->job_id = job_id;
2173  current->pending = rule.deps;
2174  current->delayed = new rule_t(rule);
2175  return true;
2176  }
2177  return run_script(job_id, rule);
2178 }