1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// When run with 2 or more arguments the file_poller tool will open a port on
6// the device, print it on its standard output and then start collect file
7// contents.  The first argument is the polling rate in Hz, and the following
8// arguments are file to poll.
9// When run with the port of an already running file_poller, the tool will
10// contact the first instance, retrieve the sample and print those on its
11// standard output. This will also terminate the first instance.
12
13#include <errno.h>
14#include <fcntl.h>
15#include <netinet/in.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <sys/socket.h>
19#include <sys/stat.h>
20#include <sys/time.h>
21#include <sys/types.h>
22#include <unistd.h>
23
24#include "base/logging.h"
25
26// Context containing the files to poll and the polling rate.
27struct Context {
28  size_t nb_files;
29  int* file_fds;
30  int poll_rate;
31};
32
33// Write from the buffer to the given file descriptor.
34void safe_write(int fd, const char* buffer, int size) {
35  const char* index = buffer;
36  size_t to_write = size;
37  while (to_write > 0) {
38    int written = write(fd, index, to_write);
39    if (written < 0)
40      PLOG(FATAL);
41    index += written;
42    to_write -= written;
43  }
44}
45
46// Transfer the content of a file descriptor to another.
47void transfer_to_fd(int fd_in, int fd_out) {
48  char buffer[1024];
49  int n;
50  while ((n = read(fd_in, buffer, sizeof(buffer))) > 0)
51    safe_write(fd_out, buffer, n);
52}
53
54// Transfer the content of a file descriptor to a buffer.
55int transfer_to_buffer(int fd_in, char* bufffer, size_t size) {
56  char* index = bufffer;
57  size_t to_read = size;
58  int n;
59  while (to_read > 0 && ((n = read(fd_in, index, to_read)) > 0)) {
60    index += n;
61    to_read -= n;
62  }
63  if (n < 0)
64    PLOG(FATAL);
65  return size - to_read;
66}
67
68// Try to open the file at the given path for reading. Exit in case of failure.
69int checked_open(const char* path) {
70  int fd = open(path, O_RDONLY);
71  if (fd < 0)
72    PLOG(FATAL);
73  return fd;
74}
75
76void transfer_measurement(int fd_in, int fd_out, bool last) {
77  char buffer[1024];
78  if (lseek(fd_in, 0, SEEK_SET) < 0)
79    PLOG(FATAL);
80  int n = transfer_to_buffer(fd_in, buffer, sizeof(buffer));
81  safe_write(fd_out, buffer, n - 1);
82  safe_write(fd_out, last ? "\n" : " ", 1);
83}
84
85// Acquire a sample and save it to the given file descriptor.
86void acquire_sample(int fd, const Context& context) {
87  struct timeval tv;
88  gettimeofday(&tv, NULL);
89  char buffer[1024];
90  int n = snprintf(buffer, sizeof(buffer), "%d.%06d ", tv.tv_sec, tv.tv_usec);
91  safe_write(fd, buffer, n);
92
93  for (int i = 0; i < context.nb_files; ++i)
94    transfer_measurement(context.file_fds[i], fd, i == (context.nb_files - 1));
95}
96
97void poll_content(const Context& context) {
98  // Create and bind the socket so that the port can be written to stdout.
99  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
100  struct sockaddr_in socket_info;
101  socket_info.sin_family = AF_INET;
102  socket_info.sin_addr.s_addr = htonl(INADDR_ANY);
103  socket_info.sin_port = htons(0);
104  if (bind(sockfd, (struct sockaddr*)&socket_info, sizeof(socket_info)) < 0)
105    PLOG(FATAL);
106  socklen_t size = sizeof(socket_info);
107  getsockname(sockfd, (struct sockaddr*)&socket_info, &size);
108  printf("%d\n", ntohs(socket_info.sin_port));
109  // Using a pipe to ensure child is diconnected from the terminal before
110  // quitting.
111  int pipes[2];
112  pipe(pipes);
113  pid_t pid = fork();
114  if (pid < 0)
115    PLOG(FATAL);
116  if (pid != 0) {
117    close(pipes[1]);
118    // Not expecting any data to be received.
119    read(pipes[0], NULL, 1);
120    signal(SIGCHLD, SIG_IGN);
121    return;
122  }
123
124  // Detach from terminal.
125  setsid();
126  close(STDIN_FILENO);
127  close(STDOUT_FILENO);
128  close(STDERR_FILENO);
129  close(pipes[0]);
130
131  // Start listening for incoming connection.
132  if (listen(sockfd, 1) < 0)
133    PLOG(FATAL);
134
135  // Signal the parent that it can now safely exit.
136  close(pipes[1]);
137
138  // Prepare file to store the samples.
139  int fd;
140  char filename[] = "/data/local/tmp/fileXXXXXX";
141  fd = mkstemp(filename);
142  unlink(filename);
143
144  // Collect samples until a client connect on the socket.
145  fd_set rfds;
146  struct timeval timeout;
147  do {
148    acquire_sample(fd, context);
149    timeout.tv_sec = 0;
150    timeout.tv_usec = 1000000 / context.poll_rate;
151    FD_ZERO(&rfds);
152    FD_SET(sockfd, &rfds);
153  } while (select(sockfd + 1, &rfds, NULL, NULL, &timeout) == 0);
154
155  // Collect a final sample.
156  acquire_sample(fd, context);
157
158  // Send the result back.
159  struct sockaddr_in remote_socket_info;
160  int rfd = accept(sockfd, (struct sockaddr*)&remote_socket_info, &size);
161  if (rfd < 0)
162    PLOG(FATAL);
163  if (lseek(fd, 0, SEEK_SET) < 0)
164    PLOG(FATAL);
165  transfer_to_fd(fd, rfd);
166}
167
168void content_collection(int port) {
169  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
170  // Connect to localhost.
171  struct sockaddr_in socket_info;
172  socket_info.sin_family = AF_INET;
173  socket_info.sin_addr.s_addr = htonl(0x7f000001);
174  socket_info.sin_port = htons(port);
175  if (connect(sockfd, (struct sockaddr*)&socket_info, sizeof(socket_info)) <
176      0) {
177    PLOG(FATAL);
178  }
179  transfer_to_fd(sockfd, STDOUT_FILENO);
180}
181
182int main(int argc, char** argv) {
183  if (argc == 1) {
184    fprintf(stderr,
185            "Usage: \n"
186            " %s port\n"
187            " %s rate FILE...\n",
188            argv[0],
189            argv[0]);
190    exit(EXIT_FAILURE);
191  }
192  if (argc == 2) {
193    // Argument is the port to connect to.
194    content_collection(atoi(argv[1]));
195  } else {
196    // First argument is the poll frequency, in Hz, following arguments are the
197    // file to poll.
198    Context context;
199    context.poll_rate = atoi(argv[1]);
200    context.nb_files = argc - 2;
201    context.file_fds = new int[context.nb_files];
202    for (int i = 2; i < argc; ++i)
203      context.file_fds[i - 2] = checked_open(argv[i]);
204    poll_content(context);
205  }
206  return EXIT_SUCCESS;
207}
208