マイtruncateを作る(その3)

前回まで自作のしょぼいtruncateを作ってましたが、本物のtruncateのソースを見て答え合わせしてみます。
※truncateのソースは、全文を記事の最後に載せています。

まずオプション解析。getopt_longを使ってます。まあそうでしょう。

   231	  while ((c = getopt_long (argc, argv, "cor:s:", longopts, NULL)) != -1)
   232	    {
   233	      switch (c)

argvとargcはオプション解析後に、調整して使うと、プログラムが分かりやすくなります。

   302	  argv += optind;
   303	  argc -= optind;

やはりstatを使ってるんですね。

   333	  if (ref_file)
   334	    {
   335	      struct stat sb;
   336	      off_t file_size = -1;
   337	      if (stat (ref_file, &sb) != 0)
   338	        die (EXIT_FAILURE, errno, _("cannot stat %s"), quoteaf (ref_file));

ファイルオープンは、fopenじゃなくて、大元のopen使ってます。statもopenもカーネルのシステムコールです。

   339	      if (usable_st_size (&sb))
   340	        file_size = sb.st_size;
   341	      else
   342	        {
   343	          int ref_fd = open (ref_file, O_RDONLY);

複数ファイルの処理です。369行目なかなかエレガントです。普段try~catchに慣れてしまうと、エラー処理ってどう書くのか忘れがちです。このソースを見ると初心を思い出してまだまだ勉強せねば、と思います。

   369	  while ((fname = *argv++) != NULL)
   370	    {
   371	      if ((fd = open (fname, oflags, MODE_RW_UGO)) == -1)
   372	        {
   373	          /* 'truncate -s0 -c no-such-file'  shouldn't gen error
   374	             'truncate -s0 no-such-dir/file' should gen ENOENT error
   375	             'truncate -s0 no-such-dir/' should gen EISDIR error
   376	             'truncate -s0 .' should gen EISDIR error */
   377	          if (!(no_create && errno == ENOENT))
   378	            {
   379	              error (0, errno, _("cannot open %s for writing"),
   380	                     quoteaf (fname));
   381	              errors = true;
   382	            }
   383	          continue;
   384	        }

気になる切り詰め処理は、do_ftruncateという関数になってました。中身も後で見てみます。最後はファイルのclose処理です。openとは違って != 0でエラー判定していますが、openにしてもcloseにしてもエラーは-1が返ります。謎が深まる。

   387	      if (fd != -1)
   388	        {
   389	          errors |= !do_ftruncate (fd, fname, size, rsize, rel_mode);
   390	          if (close (fd) != 0)
   391	            {
   392	              error (0, errno, _("failed to close %s"), quoteaf (fname));
   393	              errors = true;
   394	            }
   395	        }
   396	    }
   397	
   398	  return errors ? EXIT_FAILURE : EXIT_SUCCESS;
   399	}

さて、do_ftruncateの中身を見ていきます。

   105	static bool
   106	do_ftruncate (int fd, char const *fname, off_t ssize, off_t rsize,
   107	              rel_mode_t rel_mode)
   108	{

なんと、システムコールftruncate呼ぶんですね。

   201	  if (ftruncate (fd, nsize) == -1)      /* note updates mtime & ctime */
   202	    {
   203	      error (0, errno,
   204	             _("failed to truncate %s at %" PRIdMAX " bytes"), quoteaf (fname),
   205	             (intmax_t) nsize);
   206	      return false;
   207	    }

man 2 ftruncateで確認。

TRUNCATE(2)                Linux Programmer's Manual               TRUNCATE(2)

名前
       truncate, ftruncate - 指定した長さにファイルを切り詰める

書式
       #include <unistd.h>
       #include <sys/types.h>

       int truncate(const char *path, off_t length);
       int ftruncate(int fd, off_t length);


これを知っていれば、自作truncateもこれを使ってることと思います。
結局ライブラリではなく、システムコールをするのかあ。

今まで見てきたtruncateの全文です。

takk@deb9:~/src/coreutils-8.26/src$ cat -n truncate.c 
     1	/* truncate -- truncate or extend the length of files.
     2	   Copyright (C) 2008-2016 Free Software Foundation, Inc.
     3	
     4	   This program is free software: you can redistribute it and/or modify
     5	   it under the terms of the GNU General Public License as published by
     6	   the Free Software Foundation, either version 3 of the License, or
     7	   (at your option) any later version.
     8	
     9	   This program is distributed in the hope that it will be useful,
    10	   but WITHOUT ANY WARRANTY; without even the implied warranty of
    11	   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12	   GNU General Public License for more details.
    13	
    14	   You should have received a copy of the GNU General Public License
    15	   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
    16	
    17	/* Written by Padraig Brady
    18	
    19	   This is backwards compatible with the FreeBSD utility, but is more
    20	   flexible wrt the size specifications and the use of long options,
    21	   to better fit the "GNU" environment.  */
    22	
    23	#include <config.h>             /* sets _FILE_OFFSET_BITS=64 etc. */
    24	#include <stdio.h>
    25	#include <getopt.h>
    26	#include <sys/types.h>
    27	
    28	#include "system.h"
    29	#include "die.h"
    30	#include "error.h"
    31	#include "quote.h"
    32	#include "stat-size.h"
    33	#include "xdectoint.h"
    34	
    35	/* The official name of this program (e.g., no 'g' prefix).  */
    36	#define PROGRAM_NAME "truncate"
    37	
    38	#define AUTHORS proper_name ("Padraig Brady")
    39	
    40	/* (-c) If true, don't create if not already there */
    41	static bool no_create;
    42	
    43	/* (-o) If true, --size refers to blocks not bytes */
    44	static bool block_mode;
    45	
    46	/* (-r) Reference file to use size from */
    47	static char const *ref_file;
    48	
    49	static struct option const longopts[] =
    50	{
    51	  {"no-create", no_argument, NULL, 'c'},
    52	  {"io-blocks", no_argument, NULL, 'o'},
    53	  {"reference", required_argument, NULL, 'r'},
    54	  {"size", required_argument, NULL, 's'},
    55	  {GETOPT_HELP_OPTION_DECL},
    56	  {GETOPT_VERSION_OPTION_DECL},
    57	  {NULL, 0, NULL, 0}
    58	};
    59	
    60	typedef enum
    61	{ rm_abs = 0, rm_rel, rm_min, rm_max, rm_rdn, rm_rup } rel_mode_t;
    62	
    63	void
    64	usage (int status)
    65	{
    66	  if (status != EXIT_SUCCESS)
    67	    emit_try_help ();
    68	  else
    69	    {
    70	      printf (_("Usage: %s OPTION... FILE...\n"), program_name);
    71	      fputs (_("\
    72	Shrink or extend the size of each FILE to the specified size\n\
    73	\n\
    74	A FILE argument that does not exist is created.\n\
    75	\n\
    76	If a FILE is larger than the specified size, the extra data is lost.\n\
    77	If a FILE is shorter, it is extended and the extended part (hole)\n\
    78	reads as zero bytes.\n\
    79	"), stdout);
    80	
    81	      emit_mandatory_arg_note ();
    82	
    83	      fputs (_("\
    84	  -c, --no-create        do not create any files\n\
    85	"), stdout);
    86	      fputs (_("\
    87	  -o, --io-blocks        treat SIZE as number of IO blocks instead of bytes\n\
    88	"), stdout);
    89	      fputs (_("\
    90	  -r, --reference=RFILE  base size on RFILE\n\
    91	  -s, --size=SIZE        set or adjust the file size by SIZE bytes\n"), stdout);
    92	      fputs (HELP_OPTION_DESCRIPTION, stdout);
    93	      fputs (VERSION_OPTION_DESCRIPTION, stdout);
    94	      emit_size_note ();
    95	      fputs (_("\n\
    96	SIZE may also be prefixed by one of the following modifying characters:\n\
    97	'+' extend by, '-' reduce by, '<' at most, '>' at least,\n\
    98	'/' round down to multiple of, '%' round up to multiple of.\n"), stdout);
    99	      emit_ancillary_info (PROGRAM_NAME);
   100	    }
   101	  exit (status);
   102	}
   103	
   104	/* return true on success, false on error.  */
   105	static bool
   106	do_ftruncate (int fd, char const *fname, off_t ssize, off_t rsize,
   107	              rel_mode_t rel_mode)
   108	{
   109	  struct stat sb;
   110	  off_t nsize;
   111	
   112	  if ((block_mode || (rel_mode && rsize < 0)) && fstat (fd, &sb) != 0)
   113	    {
   114	      error (0, errno, _("cannot fstat %s"), quoteaf (fname));
   115	      return false;
   116	    }
   117	  if (block_mode)
   118	    {
   119	      off_t const blksize = ST_BLKSIZE (sb);
   120	      if (ssize < OFF_T_MIN / blksize || ssize > OFF_T_MAX / blksize)
   121	        {
   122	          error (0, 0,
   123	                 _("overflow in %" PRIdMAX
   124	                   " * %" PRIdMAX " byte blocks for file %s"),
   125	                 (intmax_t) ssize, (intmax_t) blksize,
   126	                 quoteaf (fname));
   127	          return false;
   128	        }
   129	      ssize *= blksize;
   130	    }
   131	  if (rel_mode)
   132	    {
   133	      uintmax_t fsize;
   134	
   135	      if (0 <= rsize)
   136	        fsize = rsize;
   137	      else
   138	        {
   139	          off_t file_size;
   140	          if (usable_st_size (&sb))
   141	            {
   142	              file_size = sb.st_size;
   143	              if (file_size < 0)
   144	                {
   145	                  /* Sanity check.  Overflow is the only reason I can think
   146	                     this would ever go negative. */
   147	                  error (0, 0, _("%s has unusable, apparently negative size"),
   148	                         quoteaf (fname));
   149	                  return false;
   150	                }
   151	            }
   152	          else
   153	            {
   154	              file_size = lseek (fd, 0, SEEK_END);
   155	              if (file_size < 0)
   156	                {
   157	                  error (0, errno, _("cannot get the size of %s"),
   158	                         quoteaf (fname));
   159	                  return false;
   160	                }
   161	            }
   162	          fsize = file_size;
   163	        }
   164	
   165	      if (rel_mode == rm_min)
   166	        nsize = MAX (fsize, (uintmax_t) ssize);
   167	      else if (rel_mode == rm_max)
   168	        nsize = MIN (fsize, (uintmax_t) ssize);
   169	      else if (rel_mode == rm_rdn)
   170	        /* 0..ssize-1 -> 0 */
   171	        nsize = (fsize / ssize) * ssize;
   172	      else if (rel_mode == rm_rup)
   173	        /* 1..ssize -> ssize */
   174	        {
   175	          /* Here ssize>=1 && fsize>=0 */
   176	          uintmax_t const overflow = ((fsize + ssize - 1) / ssize) * ssize;
   177	          if (overflow > OFF_T_MAX)
   178	            {
   179	              error (0, 0, _("overflow rounding up size of file %s"),
   180	                     quoteaf (fname));
   181	              return false;
   182	            }
   183	          nsize = overflow;
   184	        }
   185	      else
   186	        {
   187	          if (ssize > OFF_T_MAX - (off_t)fsize)
   188	            {
   189	              error (0, 0, _("overflow extending size of file %s"),
   190	                     quoteaf (fname));
   191	              return false;
   192	            }
   193	          nsize = fsize + ssize;
   194	        }
   195	    }
   196	  else
   197	    nsize = ssize;
   198	  if (nsize < 0)
   199	    nsize = 0;
   200	
   201	  if (ftruncate (fd, nsize) == -1)      /* note updates mtime & ctime */
   202	    {
   203	      error (0, errno,
   204	             _("failed to truncate %s at %" PRIdMAX " bytes"), quoteaf (fname),
   205	             (intmax_t) nsize);
   206	      return false;
   207	    }
   208	
   209	  return true;
   210	}
   211	
   212	int
   213	main (int argc, char **argv)
   214	{
   215	  bool got_size = false;
   216	  bool errors = false;
   217	  off_t size IF_LINT ( = 0);
   218	  off_t rsize = -1;
   219	  rel_mode_t rel_mode = rm_abs;
   220	  int c, fd = -1, oflags;
   221	  char const *fname;
   222	
   223	  initialize_main (&argc, &argv);
   224	  set_program_name (argv[0]);
   225	  setlocale (LC_ALL, "");
   226	  bindtextdomain (PACKAGE, LOCALEDIR);
   227	  textdomain (PACKAGE);
   228	
   229	  atexit (close_stdout);
   230	
   231	  while ((c = getopt_long (argc, argv, "cor:s:", longopts, NULL)) != -1)
   232	    {
   233	      switch (c)
   234	        {
   235	        case 'c':
   236	          no_create = true;
   237	          break;
   238	
   239	        case 'o':
   240	          block_mode = true;
   241	          break;
   242	
   243	        case 'r':
   244	          ref_file = optarg;
   245	          break;
   246	
   247	        case 's':
   248	          /* skip any whitespace */
   249	          while (isspace (to_uchar (*optarg)))
   250	            optarg++;
   251	          switch (*optarg)
   252	            {
   253	            case '<':
   254	              rel_mode = rm_max;
   255	              optarg++;
   256	              break;
   257	            case '>':
   258	              rel_mode = rm_min;
   259	              optarg++;
   260	              break;
   261	            case '/':
   262	              rel_mode = rm_rdn;
   263	              optarg++;
   264	              break;
   265	            case '%':
   266	              rel_mode = rm_rup;
   267	              optarg++;
   268	              break;
   269	            }
   270	          /* skip any whitespace */
   271	          while (isspace (to_uchar (*optarg)))
   272	            optarg++;
   273	          if (*optarg == '+' || *optarg == '-')
   274	            {
   275	              if (rel_mode)
   276	                {
   277	                  error (0, 0, _("multiple relative modifiers specified"));
   278	                  /* Note other combinations are flagged as invalid numbers */
   279	                  usage (EXIT_FAILURE);
   280	                }
   281	              rel_mode = rm_rel;
   282	            }
   283	          /* Support dd BLOCK size suffixes + lowercase g,t,m for bsd compat.
   284	             Note we don't support dd's b=512, c=1, w=2 or 21x512MiB formats. */
   285	          size = xdectoimax (optarg, OFF_T_MIN, OFF_T_MAX, "EgGkKmMPtTYZ0",
   286	                             _("Invalid number"), 0);
   287	          /* Rounding to multiple of 0 is nonsensical */
   288	          if ((rel_mode == rm_rup || rel_mode == rm_rdn) && size == 0)
   289	            die (EXIT_FAILURE, 0, _("division by zero"));
   290	          got_size = true;
   291	          break;
   292	
   293	        case_GETOPT_HELP_CHAR;
   294	
   295	        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
   296	
   297	        default:
   298	          usage (EXIT_FAILURE);
   299	        }
   300	    }
   301	
   302	  argv += optind;
   303	  argc -= optind;
   304	
   305	  /* must specify either size or reference file */
   306	  if (!ref_file && !got_size)
   307	    {
   308	      error (0, 0, _("you must specify either %s or %s"),
   309	             quote_n (0, "--size"), quote_n (1, "--reference"));
   310	      usage (EXIT_FAILURE);
   311	    }
   312	  /* must specify a relative size with a reference file */
   313	  if (ref_file && got_size && !rel_mode)
   314	    {
   315	      error (0, 0, _("you must specify a relative %s with %s"),
   316	             quote_n (0, "--size"), quote_n (1, "--reference"));
   317	      usage (EXIT_FAILURE);
   318	    }
   319	  /* block_mode without size is not valid */
   320	  if (block_mode && !got_size)
   321	    {
   322	      error (0, 0, _("%s was specified but %s was not"),
   323	             quote_n (0, "--io-blocks"), quote_n (1, "--size"));
   324	      usage (EXIT_FAILURE);
   325	    }
   326	  /* must specify at least 1 file */
   327	  if (argc < 1)
   328	    {
   329	      error (0, 0, _("missing file operand"));
   330	      usage (EXIT_FAILURE);
   331	    }
   332	
   333	  if (ref_file)
   334	    {
   335	      struct stat sb;
   336	      off_t file_size = -1;
   337	      if (stat (ref_file, &sb) != 0)
   338	        die (EXIT_FAILURE, errno, _("cannot stat %s"), quoteaf (ref_file));
   339	      if (usable_st_size (&sb))
   340	        file_size = sb.st_size;
   341	      else
   342	        {
   343	          int ref_fd = open (ref_file, O_RDONLY);
   344	          if (0 <= ref_fd)
   345	            {
   346	              off_t file_end = lseek (ref_fd, 0, SEEK_END);
   347	              int saved_errno = errno;
   348	              close (ref_fd); /* ignore failure */
   349	              if (0 <= file_end)
   350	                file_size = file_end;
   351	              else
   352	                {
   353	                  /* restore, in case close clobbered it. */
   354	                  errno = saved_errno;
   355	                }
   356	            }
   357	        }
   358	      if (file_size < 0)
   359	        die (EXIT_FAILURE, errno, _("cannot get the size of %s"),
   360	             quoteaf (ref_file));
   361	      if (!got_size)
   362	        size = file_size;
   363	      else
   364	        rsize = file_size;
   365	    }
   366	
   367	  oflags = O_WRONLY | (no_create ? 0 : O_CREAT) | O_NONBLOCK;
   368	
   369	  while ((fname = *argv++) != NULL)
   370	    {
   371	      if ((fd = open (fname, oflags, MODE_RW_UGO)) == -1)
   372	        {
   373	          /* 'truncate -s0 -c no-such-file'  shouldn't gen error
   374	             'truncate -s0 no-such-dir/file' should gen ENOENT error
   375	             'truncate -s0 no-such-dir/' should gen EISDIR error
   376	             'truncate -s0 .' should gen EISDIR error */
   377	          if (!(no_create && errno == ENOENT))
   378	            {
   379	              error (0, errno, _("cannot open %s for writing"),
   380	                     quoteaf (fname));
   381	              errors = true;
   382	            }
   383	          continue;
   384	        }
   385	
   386	
   387	      if (fd != -1)
   388	        {
   389	          errors |= !do_ftruncate (fd, fname, size, rsize, rel_mode);
   390	          if (close (fd) != 0)
   391	            {
   392	              error (0, errno, _("failed to close %s"), quoteaf (fname));
   393	              errors = true;
   394	            }
   395	        }
   396	    }
   397	
   398	  return errors ? EXIT_FAILURE : EXIT_SUCCESS;
   399	}
takk@deb9:~/src/coreutils-8.26/src$ 

Leave a Reply

Your email address will not be published. Required fields are marked *

CAPTCHA