libexplain  1.4.D001
libexplain/buffer/errno/open.c
Go to the documentation of this file.
00001 /*
00002  * libexplain - Explain errno values returned by libc functions
00003  * Copyright (C) 2008-2011, 2013 Peter Miller
00004  * Written by Peter Miller <pmiller@opensource.org.au>
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU Lesser General Public License as published by
00008  * the Free Software Foundation; either version 3 of the License, or (at
00009  * your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Lesser General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Lesser General Public License
00017  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00018  */
00019 
00020 #include <libexplain/ac/assert.h>
00021 #include <libexplain/ac/errno.h>
00022 #include <libexplain/ac/fcntl.h>
00023 #include <libexplain/ac/sys/stat.h> /* for major()/minor() except Solaris */
00024 #include <libexplain/ac/sys/sysmacros.h> /* for major()/minor() on Solaris */
00025 #include <libexplain/ac/unistd.h>
00026 
00027 #include <libexplain/buffer/dac.h>
00028 #include <libexplain/buffer/eexist.h>
00029 #include <libexplain/buffer/efault.h>
00030 #include <libexplain/buffer/einval.h>
00031 #include <libexplain/buffer/eisdir.h>
00032 #include <libexplain/buffer/eloop.h>
00033 #include <libexplain/buffer/emfile.h>
00034 #include <libexplain/buffer/enametoolong.h>
00035 #include <libexplain/buffer/enfile.h>
00036 #include <libexplain/buffer/enoent.h>
00037 #include <libexplain/buffer/enomedium.h>
00038 #include <libexplain/buffer/enomem.h>
00039 #include <libexplain/buffer/enospc.h>
00040 #include <libexplain/buffer/enotdir.h>
00041 #include <libexplain/buffer/erofs.h>
00042 #include <libexplain/buffer/errno/generic.h>
00043 #include <libexplain/buffer/errno/open.h>
00044 #include <libexplain/buffer/errno/path_resolution.h>
00045 #include <libexplain/buffer/etxtbsy.h>
00046 #include <libexplain/buffer/file_type.h>
00047 #include <libexplain/buffer/gettext.h>
00048 #include <libexplain/buffer/mount_point.h>
00049 #include <libexplain/buffer/open_flags.h>
00050 #include <libexplain/buffer/path_to_pid.h>
00051 #include <libexplain/buffer/permission_mode.h>
00052 #include <libexplain/buffer/pointer.h>
00053 #include <libexplain/buffer/pretty_size.h>
00054 #include <libexplain/buffer/uid.h>
00055 #include <libexplain/capability.h>
00056 #include <libexplain/explanation.h>
00057 #include <libexplain/option.h>
00058 #include <libexplain/string_buffer.h>
00059 
00060 
00061 static void
00062 explain_buffer_errno_open_system_call(explain_string_buffer_t *sb,
00063     int errnum, const char *pathname, int flags, int mode)
00064 {
00065     explain_string_buffer_printf(sb, "open(pathname = ");
00066     if (errnum == EFAULT)
00067         explain_buffer_pointer(sb, pathname);
00068     else
00069         explain_string_buffer_puts_quoted(sb, pathname);
00070     explain_string_buffer_puts(sb, ", flags = ");
00071     explain_buffer_open_flags(sb, flags);
00072     if (flags & O_CREAT)
00073     {
00074         explain_string_buffer_puts(sb, ", mode = ");
00075         explain_buffer_permission_mode(sb, mode);
00076     }
00077     explain_string_buffer_putc(sb, ')');
00078 }
00079 
00080 
00081 static void
00082 you_can_not_open_a_socket(explain_string_buffer_t *sb)
00083 {
00084     explain_buffer_gettext
00085     (
00086         sb,
00087         /*
00088          * xgettext:  This message is used when open(2) received an
00089          * ENODEV error, and the pathname it attempted to open was a
00090          * socket (first character "s" is ls(1) long output).  They
00091          * probably meant to use a named pipe (first character "p" in
00092          * ls(1) long outout).
00093          */
00094         i18n("you cannot use open(2) to open socket files, you must use "
00095         "connect(2) instead; a named pipe may be what was intended")
00096     );
00097 }
00098 
00099 
00100 static void
00101 no_corresponding_device(explain_string_buffer_t *sb, const struct stat *st)
00102 {
00103     char            ftype[FILE_TYPE_BUFFER_SIZE_MIN];
00104     char            numbers[40];
00105     explain_string_buffer_t ftype_sb;
00106     explain_string_buffer_t numbers_sb;
00107 
00108     explain_string_buffer_init(&ftype_sb, ftype, sizeof(ftype));
00109     explain_buffer_file_type_st(&ftype_sb, st);
00110 
00111     explain_string_buffer_init(&numbers_sb, numbers, sizeof(numbers));
00112     explain_string_buffer_printf
00113     (
00114         &numbers_sb,
00115         "(%ld, %ld)",
00116         (long)major(st->st_dev),
00117         (long)minor(st->st_dev)
00118     );
00119     explain_string_buffer_printf_gettext
00120     (
00121         sb,
00122         /*
00123          * xgettext: This message is used to explain an ENODEV error reported by
00124          * an open(2) system call, and the device does not actually exist.
00125          *
00126          * %1$s => the file type of the special file,
00127          *         already translated.
00128          * %2$s => the major and minor device numbers
00129          *
00130          * Example: "pathname refers to a block special file (42, 13) and no
00131          *           corresponding device exists"
00132          */
00133         i18n("pathname refers to a %s %s and no corresponding device exists"),
00134         ftype,
00135         numbers
00136     );
00137 }
00138 
00139 
00140 static int
00141 is_device_file(const struct stat *st)
00142 {
00143     switch (st->st_mode & S_IFMT)
00144     {
00145     case S_IFCHR:
00146     case S_IFBLK:
00147 #ifdef S_IFMPC
00148     case S_IFMPC:
00149 #endif
00150 #ifdef S_IFMPB
00151    case S_IFMPB:
00152 #endif
00153         return 1;
00154 
00155     default:
00156         break;
00157     }
00158     return 0;
00159 }
00160 
00161 
00162 void
00163 explain_buffer_errno_open_explanation(explain_string_buffer_t *sb,
00164     int errnum, const char *syscall_name, const char *pathname, int flags,
00165     int mode)
00166 {
00167     explain_final_t final_component;
00168 
00169     (void)mode;
00170     explain_final_init(&final_component);
00171     switch (flags & O_ACCMODE)
00172     {
00173     case O_RDONLY:
00174         final_component.want_to_read = 1;
00175         break;
00176 
00177     case O_RDWR:
00178         final_component.want_to_read = 1;
00179         final_component.want_to_write = 1;
00180         break;
00181 
00182     case O_WRONLY:
00183         final_component.want_to_write = 1;
00184         break;
00185 
00186     default:
00187         assert(!"unknown open access mode");
00188         break;
00189     }
00190     if (flags & O_CREAT)
00191     {
00192         final_component.want_to_create = 1;
00193         final_component.must_exist = 0;
00194         if (flags & O_EXCL)
00195             final_component.must_not_exist = 1;
00196     }
00197     if (flags & O_DIRECTORY)
00198     {
00199         final_component.must_be_a_st_mode = 1;
00200         final_component.st_mode = S_IFDIR;
00201     }
00202     if (flags & O_NOFOLLOW)
00203         final_component.follow_symlink = 0;
00204 
00205     switch (errnum)
00206     {
00207     case EACCES:
00208         if
00209         (
00210             explain_buffer_errno_path_resolution
00211             (
00212                 sb,
00213                 errnum,
00214                 pathname,
00215                 "pathname",
00216                 &final_component
00217             )
00218         )
00219         {
00220             struct stat     st;
00221 
00222             if
00223             (
00224                 stat(pathname, &st) >= 0
00225             &&
00226                 is_device_file(&st)
00227             &&
00228                 explain_mount_point_nodev(&st)
00229             )
00230             {
00231                 explain_string_buffer_t file_type_sb;
00232                 char            file_type[FILE_TYPE_BUFFER_SIZE_MIN];
00233 
00234                 explain_string_buffer_init(&file_type_sb, file_type,
00235                     sizeof(file_type));
00236                 explain_buffer_file_type_st(&file_type_sb, &st);
00237                 explain_string_buffer_printf_gettext
00238                 (
00239                     sb,
00240                     /*
00241                      * xgettext:  This message is used when explaining an EACCES
00242                      * error returned by an open(2) system call, in the case
00243                      * where the file is a character special device or a block
00244                      * special device, and the file system has been mounted with
00245                      * the "nodev" option.
00246                      *
00247                      * %1$s => the file type (character sepcial device, etc)
00248                      *         already translated.
00249                      */
00250                     i18n("the %s is on a file system mounted with the "
00251                         "\"nodev\" option"),
00252                     file_type
00253                 );
00254                 explain_buffer_mount_point_stat(sb, &st);
00255                 break;
00256             }
00257 
00258             explain_buffer_gettext
00259             (
00260                 sb,
00261                 /*
00262                  * xgettext:  This message is used when explaining an
00263                  * EACCES error returned by an open(2) system call.
00264                  * Usually path_resolution(7) will have a better
00265                  * explanation, this explanation is only used when a
00266                  * more specific explanation is not available.
00267                  */
00268                 i18n("the requested access to the file is not allowed, or "
00269                 "search permission is denied for one of the directories "
00270                 "in the path prefix of pathname, or the file did not "
00271                 "exist yet and write access to the parent directory is "
00272                 "not allowed")
00273             );
00274         }
00275         break;
00276 
00277     case EINVAL:
00278         explain_buffer_einval_bits(sb, "flags");
00279         break;
00280 
00281     case EEXIST:
00282         if
00283         (
00284             explain_buffer_errno_path_resolution
00285             (
00286                 sb,
00287                 errnum,
00288                 pathname,
00289                 "pathname",
00290                 &final_component
00291             )
00292         )
00293         {
00294             explain_buffer_eexist(sb, pathname);
00295         }
00296         break;
00297 
00298     case EFAULT:
00299         explain_buffer_efault(sb, "pathname");
00300         break;
00301 
00302 #if defined(O_LARGEFILE) && (O_LARGEFILE != 0)
00303     case EFBIG:
00304     case EOVERFLOW:
00305         if (!(flags & O_LARGEFILE))
00306         {
00307             struct stat     st;
00308 
00309             if (stat(pathname, &st) >= 0 && S_ISREG(st.st_mode))
00310             {
00311                 char            siz[20];
00312                 explain_string_buffer_t siz_sb;
00313 
00314                 explain_string_buffer_init(&siz_sb, siz, sizeof(siz));
00315                 {
00316                     explain_string_buffer_putc(sb, '(');
00317                     explain_buffer_pretty_size(sb, st.st_size);
00318                     explain_string_buffer_putc(sb, ')');
00319                 }
00320                 explain_string_buffer_printf_gettext
00321                 (
00322                     sb,
00323                     /*
00324                      * xgettext:  This message is used to explain an EFBIG
00325                      * or EOVERFLOW error reported by and open(2) system
00326                      * call.  The file is, in fact, too large to be opened
00327                      * without the O_LARGEFILE flag.
00328                      *
00329                      * %1$s => The size of the file, in parentheses
00330                      */
00331                     i18n("pathname referes to a regular file that is too large "
00332                         "to be opened %s, the O_LARGEFILE flag is necessary"),
00333                     siz
00334                 );
00335             }
00336         }
00337         break;
00338 #endif
00339 
00340     case EISDIR:
00341         if ((flags & O_ACCMODE) != O_RDONLY)
00342         {
00343             if (explain_buffer_eisdir(sb, pathname, "pathname"))
00344                 break;
00345         }
00346         goto generic;
00347 
00348     case ELOOP:
00349     case EMLINK: /* BSD */
00350         if (flags & O_NOFOLLOW)
00351         {
00352             struct stat     st;
00353 
00354             if (lstat(pathname, &st) >= 0 && S_ISLNK(st.st_mode))
00355             {
00356                 explain_buffer_gettext
00357                 (
00358                     sb,
00359                     /*
00360                      * xgettext: This message is used to explain an
00361                      * ELOOP or EMLINK error reported by an open(2)
00362                      * system call, in the case where the O_NOFOLLOW
00363                      * flags was specified but the final path component
00364                      * was a symbolic link.
00365                      */
00366                     i18n("O_NOFOLLOW was specified but pathname refers to a "
00367                     "symbolic link")
00368                 );
00369                 /* FIXME: mention this may indicate some kind of attack? */
00370                 break;
00371             }
00372         }
00373         explain_buffer_eloop(sb, pathname, "pathname", &final_component);
00374         break;
00375 
00376     case EMFILE:
00377         explain_buffer_emfile(sb);
00378         break;
00379 
00380     case ENAMETOOLONG:
00381         explain_buffer_enametoolong
00382         (
00383             sb,
00384             pathname,
00385             "pathname",
00386             &final_component
00387         );
00388         break;
00389 
00390     case ENFILE:
00391         explain_buffer_enfile(sb);
00392         break;
00393 
00394     case ENOENT:
00395         explain_buffer_enoent(sb, pathname, "pathname", &final_component);
00396         break;
00397 
00398 #ifdef ENOMEDIUM
00399     case ENOMEDIUM:
00400         explain_buffer_enomedium(sb, pathname);
00401         break;
00402 #endif
00403 
00404     case ENOMEM:
00405         explain_buffer_enomem_kernel(sb);
00406         break;
00407 
00408     case ENOSPC:
00409         explain_buffer_enospc(sb, pathname, "pathname");
00410         break;
00411 
00412     case ENOTDIR:
00413         explain_buffer_enotdir(sb, pathname, "pathname", &final_component);
00414         break;
00415 
00416     case ENXIO:
00417         {
00418             struct stat     st;
00419 
00420             if (stat(pathname, &st) < 0)
00421             {
00422                 enxio_generic:
00423                 explain_buffer_gettext
00424                 (
00425                     sb,
00426                     /*
00427                      * xgettext:  This message is used to explain an
00428                      * ENXIO error returned by an open(2) system call.
00429                      * This is the generic explanation, used when no
00430                      * more specific cause can be determined.
00431                      */
00432                     i18n("O_NONBLOCK | O_WRONLY is set, the named file "
00433                     "is a FIFO and no process has the file open for "
00434                     "reading; or, the file is a device special file and "
00435                     "no corresponding device exists")
00436                 );
00437                 break;
00438             }
00439             switch (st.st_mode & S_IFMT)
00440             {
00441             case S_IFIFO:
00442                 explain_buffer_gettext
00443                 (
00444                     sb,
00445                     /*
00446                      * xgettext: This message is used to explain an
00447                      * ENXIO error returned by an open(2) system call,
00448                      * in the case where a named pipe has no readers,
00449                      * and a non-blocking writer tried to open it.
00450                      */
00451                     i18n("O_NONBLOCK | O_WRONLY is set, and the named file "
00452                     "is a FIFO, and no process has the file open for "
00453                     "reading")
00454                 );
00455                 /* FIXME: what happens if you open a named pipe O_RDWR? */
00456                 break;
00457 
00458             case S_IFCHR:
00459             case S_IFBLK:
00460                 no_corresponding_device(sb, &st);
00461                 break;
00462 
00463             case S_IFSOCK:
00464                 you_can_not_open_a_socket(sb);
00465                 break;
00466 
00467             default:
00468                 goto enxio_generic;
00469             }
00470         }
00471         break;
00472 
00473 #if defined(O_NOATIME) && (O_NOATIME != 0)
00474     case EPERM:
00475         if (flags & O_NOATIME)
00476         {
00477             struct stat     st;
00478             explain_string_buffer_t puid_sb;
00479             explain_string_buffer_t ftype_sb;
00480             explain_string_buffer_t fuid_sb;
00481             char            puid[100];
00482             char            ftype[FILE_TYPE_BUFFER_SIZE_MIN];
00483             char            fuid[100];
00484 
00485             explain_string_buffer_init(&puid_sb, puid, sizeof(puid));
00486             explain_buffer_uid(&puid_sb, geteuid());
00487             explain_string_buffer_init(&ftype_sb, ftype, sizeof(ftype));
00488             explain_string_buffer_init(&fuid_sb, fuid, sizeof(fuid));
00489             if (stat(pathname, &st) >= 0)
00490             {
00491                 explain_buffer_file_type_st(&ftype_sb, &st);
00492                 explain_buffer_uid(&fuid_sb, st.st_uid);
00493             }
00494             else
00495                 explain_buffer_file_type(&ftype_sb, S_IFREG);
00496             explain_string_buffer_printf_gettext
00497             (
00498                 sb,
00499                 /*
00500                  * xgettext: This message is used when an EPERM erro is
00501                  * returned by an open(2) system call, and the O_NOATIME
00502                  * open flag was specified, but the process lacked the
00503                  * permissions required.
00504                  *
00505                  * %1$s => the number and name of the process effective UID,
00506                  *         already quoted if needed
00507                  * %2$s => the file type of the file in question,
00508                  *         almost always "regular file" (already translated)
00509                  * %3$s => the number and name of the file owner UID,
00510                  *         already quoted if needed
00511                  */
00512                 i18n("the O_NOATIME flags was specified, but the process "
00513                 "effective UID %s does not match the %s owner UID %s"),
00514                 puid,
00515                 ftype,
00516                 fuid
00517             );
00518 
00519             /*
00520              * also explain the necessary priviledge
00521              */
00522             explain_buffer_dac_fowner(sb);
00523         }
00524         break;
00525 #endif
00526 
00527     case EROFS:
00528         explain_buffer_erofs(sb, pathname, "pathname");
00529         break;
00530 
00531     case ETXTBSY:
00532         explain_buffer_etxtbsy(sb, pathname);
00533         break;
00534 
00535     case EWOULDBLOCK:
00536         if (flags & O_NONBLOCK)
00537         {
00538             explain_buffer_gettext
00539             (
00540                 sb,
00541                 /*
00542                  * xgettext: This message is used to explain an
00543                  * EWOULDBLOCK error returned by an open(2) system call,
00544                  * when the use of thr O_NONBLOCK flags would otherwise
00545                  * cause the open(2) system call to block.
00546                  */
00547                 i18n("the O_NONBLOCK flag was specified, and an "
00548                 "incompatible lease was held on the file")
00549             );
00550 
00551             /*
00552              * Look for other processes with this file open,
00553              * and list their PIDs.
00554              */
00555             explain_buffer_path_to_pid(sb, pathname);
00556         }
00557         break;
00558 
00559     case ENODEV:
00560         {
00561             struct stat     st;
00562 
00563             if (stat(pathname, &st) >= 0)
00564             {
00565                 switch (st.st_mode & S_IFMT)
00566                 {
00567                 case S_IFSOCK:
00568                     you_can_not_open_a_socket(sb);
00569                     break;
00570 
00571                 case S_IFBLK:
00572                 case S_IFCHR:
00573                     no_corresponding_device(sb, &st);
00574 #ifdef __linux__
00575                     if (explain_option_dialect_specific())
00576                     {
00577                         explain_string_buffer_puts(sb->footnotes, "; ");
00578                         explain_buffer_gettext
00579                         (
00580                             sb->footnotes,
00581                             /*
00582                              * xgettext: This message is used to explain an
00583                              * ENODEV error reported by an open(2) system
00584                              * call, which shoudl actually have been a ENXIO
00585                              * error instead.  They are easy to confuse,
00586                              * they have exactly the same English text
00587                              * returned from strerror(3).
00588                              */
00589                             i18n("this is a Linux kernel bug, in this "
00590                             "situation POSIX says ENXIO should have been "
00591                             "returned")
00592                         );
00593                     }
00594 #endif
00595                     break;
00596 
00597                 default:
00598                     break;
00599                 }
00600             }
00601         }
00602         break;
00603 
00604     default:
00605         generic:
00606         explain_buffer_errno_generic(sb, errnum, syscall_name);
00607         break;
00608     }
00609 
00610     if (flags & O_TRUNC)
00611     {
00612         if ((flags & O_ACCMODE) == O_RDONLY)
00613         {
00614             explain_string_buffer_puts(sb->footnotes, "; ");
00615             explain_buffer_gettext
00616             (
00617                 sb->footnotes,
00618                 /*
00619                  * xgettext: This message is used to supplement an explanation
00620                  * for an error reported by open(2) system call, in the case
00621                  * where the caller used a flags combination with explicitly
00622                  * undefined behavior.
00623                  */
00624                 i18n("note that the behavior of (O_RDONLY | O_TRUNC) is "
00625                     "undefined")
00626             );
00627         }
00628         else
00629         {
00630             struct stat st;
00631 
00632             if (stat(pathname, &st) >= 0)
00633             {
00634                 explain_string_buffer_t file_type_sb;
00635                 char            file_type[FILE_TYPE_BUFFER_SIZE_MIN];
00636 
00637                 explain_string_buffer_init(&file_type_sb, file_type,
00638                     sizeof(file_type));
00639                 explain_buffer_file_type_st(&file_type_sb, &st);
00640 
00641                 switch (st.st_mode & S_IFMT)
00642                 {
00643                 default:
00644                     break;
00645 
00646                 case S_IFSOCK:
00647                 case S_IFIFO:
00648                     /* explicitly ignored */
00649                     {
00650                         explain_string_buffer_puts(sb->footnotes, "; ");
00651                         explain_string_buffer_printf_gettext
00652                         (
00653                             sb->footnotes,
00654                             /*
00655                              * xgettext: This message is used to supplement an
00656                              * explanation for an error reported by open(2)
00657                              * system call, in the case where the caller used
00658                              * O_TRUNC in a combination that explicitly
00659                              * ignores O_TRUNC.
00660                              *
00661                              * %1$s => the type of the special file, already
00662                              *         translated
00663                              */
00664                             i18n("note that a %s will ignore the O_TRUNC flag"),
00665                             file_type
00666                         );
00667                     }
00668                     break;
00669 
00670                 case S_IFCHR:
00671                     /*
00672                      * The O_TRUNC flags is explicitly ignored for TTY devices,
00673                      * but is undefined for all other character devices.  Since
00674                      * we can't tell which case we have at this moment, we fall
00675                      * through...
00676                      */
00677 
00678                 case S_IFBLK:
00679 #ifdef S_IFMPC
00680                 case S_IFMPC:
00681 #endif
00682 #ifdef S_IFMPB
00683                 case S_IFMPB:
00684 #endif
00685                     /* explicitly undefined */
00686                     {
00687                         explain_string_buffer_puts(sb->footnotes, "; ");
00688                         explain_string_buffer_printf_gettext
00689                         (
00690                             sb->footnotes,
00691                             /*
00692                              * xgettext: This message is used to supplement an
00693                              * explanation for an error reported by open(2)
00694                              * system call, in the case where the caller used
00695                              * O_TRUNC in a combination with explicitly
00696                              * undefined behavior.
00697                              *
00698                              * %1$s => the type of the special file, already
00699                              *         translated
00700                              */
00701                             i18n("note that the behavior of O_TRUNC on a %s is "
00702                                 "undefined"),
00703                             file_type
00704                         );
00705                     }
00706                     break;
00707                 }
00708             }
00709         }
00710     }
00711 
00712     if ((flags & O_EXCL) && !(flags & O_CREAT))
00713     {
00714         explain_string_buffer_puts(sb->footnotes, "; ");
00715         /* FIXME: except for a TTY, where it means single open */
00716         explain_buffer_gettext
00717         (
00718             sb->footnotes,
00719             /*
00720              * xgettext: This message is used to supplement an explanation for
00721              * an error reported by open(2) system call, and the caller used a
00722              * flags combination with explicitly undefined behavior.
00723              */
00724             i18n("note that the behavior of O_EXCL is undefined if "
00725             "O_CREAT is not specified")
00726         );
00727     }
00728 }
00729 
00730 
00731 void
00732 explain_buffer_errno_open(explain_string_buffer_t *sb, int errnum,
00733     const char *pathname, int flags, int mode)
00734 {
00735     explain_explanation_t exp;
00736 
00737     explain_explanation_init(&exp, errnum);
00738     explain_buffer_errno_open_system_call
00739     (
00740         &exp.system_call_sb,
00741         errnum,
00742         pathname,
00743         flags,
00744         mode
00745     );
00746     explain_buffer_errno_open_explanation
00747     (
00748         &exp.explanation_sb,
00749         errnum,
00750         "open",
00751         pathname,
00752         flags,
00753         mode
00754     );
00755     explain_explanation_assemble(&exp, sb);
00756 }
00757 
00758 
00759 /* vim: set ts=8 sw=4 et : */