tail.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /*++
  2. Copyright (c) 2013 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. tail.c
  5. Abstract:
  6. This module implements the tail utility.
  7. Author:
  8. Evan Green 3-Jul-2013
  9. Environment:
  10. POSIX
  11. --*/
  12. //
  13. // ------------------------------------------------------------------- Includes
  14. //
  15. #include <minoca/lib/types.h>
  16. #include <assert.h>
  17. #include <ctype.h>
  18. #include <errno.h>
  19. #include <getopt.h>
  20. #include <libgen.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <unistd.h>
  24. #include "swlib.h"
  25. //
  26. // ---------------------------------------------------------------- Definitions
  27. //
  28. #define TAIL_VERSION_MAJOR 1
  29. #define TAIL_VERSION_MINOR 0
  30. #define TAIL_USAGE \
  31. "usage: tail [-f] [-c number | -n number] [file]\n" \
  32. "The tail command copies its input to standard output starting at the \n" \
  33. "given position. Positions start with + to specify an offset from the \n" \
  34. "beginning of the file, or - for offsets from the end of the file. \n" \
  35. "Both lines and bytes start counting at 1, not 0. Specifying a number \n" \
  36. "without a sign is the same as specifying a -. Valid options are:\n" \
  37. " -f, --follow -- If the input is a regular file or the operand \n" \
  38. " specifies a FIFO, do not terminate after the last line of the \n"\
  39. " input has been copied. Read and copy further bytes as they \n" \
  40. " become available. If no file operand is specified and standard \n"\
  41. " in is a pipe, this option is ignored.\n" \
  42. " -c, --bytes=number -- Output the first or last number of bytes, \n" \
  43. " depending on whether a + or - is prepended to the number.\n" \
  44. " -n, --lines=number -- Output the first or last number of lines.\n" \
  45. " --help -- Show this help text and exit.\n" \
  46. " --version - Show the application version information and exit.\n"
  47. #define TAIL_OPTIONS_STRING "fc:n:"
  48. //
  49. // Define tail options.
  50. //
  51. //
  52. // This option is set to continue printing even after the end of the file.
  53. //
  54. #define TAIL_OPTION_FOLLOW 0x00000001
  55. //
  56. // This option is set to count lines instead of bytes.
  57. //
  58. #define TAIL_OPTION_LINES 0x00000002
  59. //
  60. // This option is set to indicate that the offset is from the end of the file.
  61. //
  62. #define TAIL_OPTION_FROM_END 0x00000004
  63. //
  64. // Define the default offset.
  65. //
  66. #define TAIL_DEFAULT_OFFSET 10
  67. //
  68. // Define the maximum size of a "line" as far as keeping a buffer for it goes.
  69. //
  70. #define TAIL_MAX_LINE 2048
  71. //
  72. // ------------------------------------------------------ Data Type Definitions
  73. //
  74. //
  75. // ----------------------------------------------- Internal Function Prototypes
  76. //
  77. //
  78. // -------------------------------------------------------------------- Globals
  79. //
  80. struct option TailLongOptions[] = {
  81. {"follow", no_argument, 0, 'f'},
  82. {"bytes", required_argument, 0, 'c'},
  83. {"lines", required_argument, 0, 'n'},
  84. {"help", no_argument, 0, 'h'},
  85. {"version", no_argument, 0, 'V'},
  86. {NULL, 0, 0, 0},
  87. };
  88. //
  89. // ------------------------------------------------------------------ Functions
  90. //
  91. INT
  92. TailMain (
  93. INT ArgumentCount,
  94. CHAR **Arguments
  95. )
  96. /*++
  97. Routine Description:
  98. This routine is the main entry point for the tail utility.
  99. Arguments:
  100. ArgumentCount - Supplies the number of command line arguments the program
  101. was invoked with.
  102. Arguments - Supplies a tokenized array of command line arguments.
  103. Return Value:
  104. Returns an integer exit code. 0 for success, nonzero otherwise.
  105. --*/
  106. {
  107. PSTR Argument;
  108. ULONG ArgumentIndex;
  109. UINTN BackupCount;
  110. PUCHAR Buffer;
  111. UINTN BufferNextIndex;
  112. UINTN BufferSize;
  113. UINTN BufferValidSize;
  114. INT Character;
  115. PSTR FileName;
  116. FILE *Input;
  117. ULONGLONG Multiplier;
  118. ULONGLONG Offset;
  119. INT Option;
  120. ULONG Options;
  121. UINTN StartIndex;
  122. struct stat Stat;
  123. int Status;
  124. Buffer = NULL;
  125. Input = NULL;
  126. Multiplier = 1;
  127. Offset = TAIL_DEFAULT_OFFSET;
  128. Options = TAIL_OPTION_FROM_END | TAIL_OPTION_LINES;
  129. //
  130. // Handle something like tail -40 myfile or tail -4.
  131. //
  132. if (((ArgumentCount == 2) || (ArgumentCount == 3)) &&
  133. (Arguments[1][0] == '-') && (isdigit(Arguments[1][1]))) {
  134. Offset = strtoull(Arguments[1] + 1, NULL, 10);
  135. Options |= TAIL_OPTION_FROM_END;
  136. ArgumentIndex = 2;
  137. } else {
  138. //
  139. // Process the control arguments.
  140. //
  141. while (TRUE) {
  142. Option = getopt_long(ArgumentCount,
  143. Arguments,
  144. TAIL_OPTIONS_STRING,
  145. TailLongOptions,
  146. NULL);
  147. if (Option == -1) {
  148. break;
  149. }
  150. if ((Option == '?') || (Option == ':')) {
  151. Status = 1;
  152. goto MainEnd;
  153. }
  154. switch (Option) {
  155. case 'f':
  156. Options |= TAIL_OPTION_FOLLOW;
  157. break;
  158. case 'c':
  159. case 'n':
  160. if (Option == 'c') {
  161. Options &= ~TAIL_OPTION_LINES;
  162. } else {
  163. Options |= TAIL_OPTION_LINES;
  164. }
  165. Argument = optarg;
  166. assert(Argument != NULL);
  167. if (*Argument == '+') {
  168. Options &= ~TAIL_OPTION_FROM_END;
  169. Argument += 1;
  170. } else {
  171. Options |= TAIL_OPTION_FROM_END;
  172. if (*Argument == '-') {
  173. Argument += 1;
  174. }
  175. }
  176. Offset = SwParseFileSize(Argument);
  177. if (Offset == -1ULL) {
  178. SwPrintError(0, Argument, "Invalid size");
  179. Status = EINVAL;
  180. goto MainEnd;
  181. }
  182. break;
  183. case 'V':
  184. SwPrintVersion(TAIL_VERSION_MAJOR, TAIL_VERSION_MINOR);
  185. return 1;
  186. case 'h':
  187. printf(TAIL_USAGE);
  188. return 1;
  189. default:
  190. assert(FALSE);
  191. Status = 1;
  192. goto MainEnd;
  193. }
  194. }
  195. ArgumentIndex = optind;
  196. }
  197. if (ArgumentIndex > ArgumentCount) {
  198. ArgumentIndex = ArgumentCount;
  199. }
  200. FileName = NULL;
  201. if (ArgumentIndex < ArgumentCount) {
  202. FileName = Arguments[ArgumentIndex];
  203. ArgumentIndex += 1;
  204. }
  205. if (ArgumentIndex < ArgumentCount) {
  206. SwPrintError(0, Arguments[ArgumentIndex], "Unexpected operand");
  207. Status = EINVAL;
  208. goto MainEnd;
  209. }
  210. //
  211. // Open up the file if one was specified, or use standard in (in which case
  212. // the follow option is ignored).
  213. //
  214. if (FileName != NULL) {
  215. Input = fopen(FileName, "r");
  216. if (Input == NULL) {
  217. Status = errno;
  218. SwPrintError(Status, FileName, "Unable to open");
  219. goto MainEnd;
  220. }
  221. } else {
  222. FileName = "(stdin)";
  223. Input = stdin;
  224. Options &= ~TAIL_OPTION_FOLLOW;
  225. }
  226. assert(Offset != 0);
  227. if ((Options & TAIL_OPTION_FROM_END) == 0) {
  228. Offset -= 1;
  229. }
  230. Offset *= Multiplier;
  231. //
  232. // If it's a regular file in byte mode, use seek.
  233. //
  234. if ((Input != stdin) & ((Options & TAIL_OPTION_LINES) == 0)) {
  235. Status = SwOsStat(FileName, TRUE, &Stat);
  236. if ((Status == 0) && (S_ISREG(Stat.st_mode))) {
  237. if ((Options & TAIL_OPTION_FROM_END) != 0) {
  238. if (Offset > Stat.st_size) {
  239. Offset = Stat.st_size;
  240. }
  241. Status = fseek(Input, Stat.st_size - Offset, SEEK_SET);
  242. } else {
  243. Status = fseek(Input, Offset, SEEK_SET);
  244. }
  245. if (Status == 0) {
  246. Offset = 0;
  247. }
  248. }
  249. }
  250. //
  251. // If there's still an offset, go to it.
  252. //
  253. if (Offset != 0) {
  254. if ((Options & TAIL_OPTION_FROM_END) != 0) {
  255. BufferSize = Offset;
  256. if ((Options & TAIL_OPTION_LINES) != 0) {
  257. BufferSize *= TAIL_MAX_LINE;
  258. }
  259. Buffer = malloc(BufferSize);
  260. if (Buffer == NULL) {
  261. Status = ENOMEM;
  262. goto MainEnd;
  263. }
  264. BufferNextIndex = 0;
  265. BufferValidSize = 0;
  266. //
  267. // Get to the end of the file.
  268. //
  269. while (TRUE) {
  270. Character = fgetc(Input);
  271. if (Character == EOF) {
  272. break;
  273. }
  274. Buffer[BufferNextIndex] = Character;
  275. BufferNextIndex += 1;
  276. if (BufferNextIndex == BufferSize) {
  277. BufferNextIndex = 0;
  278. }
  279. if (BufferValidSize < BufferSize) {
  280. BufferValidSize += 1;
  281. }
  282. }
  283. //
  284. // Back up by the requested number of bytes or lines.
  285. //
  286. if ((Options & TAIL_OPTION_LINES) != 0) {
  287. BackupCount = 0;
  288. //
  289. // Start from the character before the very last character,
  290. // in order to skip a newline at the very end.
  291. //
  292. StartIndex = BufferNextIndex;
  293. if (StartIndex == 0) {
  294. StartIndex = BufferSize - 1;
  295. } else {
  296. StartIndex -= 1;
  297. }
  298. while (BackupCount < BufferValidSize) {
  299. if (StartIndex == 0) {
  300. StartIndex = BufferSize - 1;
  301. } else {
  302. StartIndex -= 1;
  303. }
  304. if (Buffer[StartIndex] == '\n') {
  305. Offset -= 1;
  306. //
  307. // Go forward by one character at the end so everything
  308. // doesn't begin with that newline.
  309. //
  310. if (Offset == 0) {
  311. StartIndex += 1;
  312. if (StartIndex == BufferSize) {
  313. StartIndex = 0;
  314. }
  315. break;
  316. }
  317. }
  318. BackupCount += 1;
  319. }
  320. //
  321. // Account for that last character that was skipped.
  322. //
  323. BackupCount += 1;
  324. } else {
  325. assert(BufferSize == Offset);
  326. if (BufferNextIndex < BufferValidSize) {
  327. StartIndex = BufferNextIndex - BufferValidSize + BufferSize;
  328. } else {
  329. StartIndex = BufferNextIndex - BufferValidSize;
  330. }
  331. BackupCount = BufferValidSize;
  332. }
  333. //
  334. // Print out the buffered contents.
  335. //
  336. while (BackupCount != 0) {
  337. fputc(Buffer[StartIndex], stdout);
  338. if (StartIndex == BufferSize - 1) {
  339. StartIndex = 0;
  340. } else {
  341. StartIndex += 1;
  342. }
  343. BackupCount -= 1;
  344. }
  345. //
  346. // Seek from the beginning.
  347. //
  348. } else {
  349. while (Offset != 0) {
  350. Character = fgetc(Input);
  351. if (Character == EOF) {
  352. break;
  353. }
  354. if (((Options & TAIL_OPTION_LINES) == 0) ||
  355. (Character == '\n')) {
  356. Offset -= 1;
  357. }
  358. }
  359. }
  360. }
  361. //
  362. // Now the easy part, just print the contents.
  363. //
  364. Status = 0;
  365. while (TRUE) {
  366. Character = fgetc(Input);
  367. if (Character == EOF) {
  368. if (ferror(Input) != 0) {
  369. Status = errno;
  370. break;
  371. }
  372. //
  373. // If following and this is just the end of the file, sleep for a
  374. // second and try again.
  375. //
  376. if ((Options & TAIL_OPTION_FOLLOW)) {
  377. SwSleep(1000000);
  378. continue;
  379. }
  380. //
  381. // Otherwise, stop.
  382. //
  383. break;
  384. }
  385. fputc(Character, stdout);
  386. }
  387. MainEnd:
  388. if (Buffer != NULL) {
  389. free(Buffer);
  390. }
  391. if ((Input != NULL) && (Input != stdin)) {
  392. fclose(Input);
  393. }
  394. return Status;
  395. }
  396. //
  397. // --------------------------------------------------------- Internal Functions
  398. //