Merge from coreutils.
[gnulib.git] / lib / fsusage.c
1 /* fsusage.c -- return space usage of mounted file systems
2
3    Copyright (C) 1991, 1992, 1996, 1998, 1999, 2002, 2003, 2004, 2005, 2006
4    Free Software Foundation, Inc.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software Foundation,
18    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include "fsusage.h"
25
26 #include <limits.h>
27 #include <sys/types.h>
28
29 #if STAT_STATVFS                /* POSIX 1003.1-2001 (and later) with XSI */
30 # include <sys/statvfs.h>
31 #else
32 /* Don't include backward-compatibility files unless they're needed.
33    Eventually we'd like to remove all this cruft.  */
34 # include <fcntl.h>
35 # include <unistd.h>
36 # include <sys/stat.h>
37 # if HAVE_SYS_PARAM_H
38 #  include <sys/param.h>
39 # endif
40 # if HAVE_SYS_MOUNT_H
41 #  include <sys/mount.h>
42 # endif
43 # if HAVE_SYS_VFS_H
44 #  include <sys/vfs.h>
45 # endif
46 # if HAVE_SYS_FS_S5PARAM_H      /* Fujitsu UXP/V */
47 #  include <sys/fs/s5param.h>
48 # endif
49 # if defined HAVE_SYS_FILSYS_H && !defined _CRAY
50 #  include <sys/filsys.h>       /* SVR2 */
51 # endif
52 # if HAVE_SYS_STATFS_H
53 #  include <sys/statfs.h>
54 # endif
55 # if HAVE_DUSTAT_H              /* AIX PS/2 */
56 #  include <sys/dustat.h>
57 # endif
58 # include "full-read.h"
59 #endif
60
61 #ifndef UINTMAX_MAX
62 # define UINTMAX_MAX ((uintmax_t) -1)
63 #endif
64
65 /* Many space usage primitives use all 1 bits to denote a value that is
66    not applicable or unknown.  Propagate this information by returning
67    a uintmax_t value that is all 1 bits if X is all 1 bits, even if X
68    is unsigned and narrower than uintmax_t.  */
69 #define PROPAGATE_ALL_ONES(x) \
70   ((sizeof (x) < sizeof (uintmax_t) \
71     && (~ (x) == (sizeof (x) < sizeof (int) \
72                   ? - (1 << (sizeof (x) * CHAR_BIT)) \
73                   : 0))) \
74    ? UINTMAX_MAX : (x))
75
76 /* Extract the top bit of X as an uintmax_t value.  */
77 #define EXTRACT_TOP_BIT(x) ((x) \
78                             & ((uintmax_t) 1 << (sizeof (x) * CHAR_BIT - 1)))
79
80 /* If a value is negative, many space usage primitives store it into an
81    integer variable by assignment, even if the variable's type is unsigned.
82    So, if a space usage variable X's top bit is set, convert X to the
83    uintmax_t value V such that (- (uintmax_t) V) is the negative of
84    the original value.  If X's top bit is clear, just yield X.
85    Use PROPAGATE_TOP_BIT if the original value might be negative;
86    otherwise, use PROPAGATE_ALL_ONES.  */
87 #define PROPAGATE_TOP_BIT(x) ((x) | ~ (EXTRACT_TOP_BIT (x) - 1))
88
89 /* Fill in the fields of FSP with information about space usage for
90    the file system on which FILE resides.
91    DISK is the device on which FILE is mounted, for space-getting
92    methods that need to know it.
93    Return 0 if successful, -1 if not.  When returning -1, ensure that
94    ERRNO is either a system error value, or zero if DISK is NULL
95    on a system that requires a non-NULL value.  */
96 int
97 get_fs_usage (char const *file, char const *disk, struct fs_usage *fsp)
98 {
99 #if defined STAT_STATVFS                /* POSIX */
100
101   struct statvfs fsd;
102
103   if (statvfs (file, &fsd) < 0)
104     return -1;
105
106   /* f_frsize isn't guaranteed to be supported.  */
107   fsp->fsu_blocksize = (fsd.f_frsize
108                         ? PROPAGATE_ALL_ONES (fsd.f_frsize)
109                         : PROPAGATE_ALL_ONES (fsd.f_bsize));
110
111 #elif defined STAT_STATFS2_FS_DATA      /* Ultrix */
112
113   struct fs_data fsd;
114
115   if (statfs (file, &fsd) != 1)
116     return -1;
117
118   fsp->fsu_blocksize = 1024;
119   fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.fd_req.btot);
120   fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.fd_req.bfree);
121   fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.fd_req.bfreen);
122   fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.fd_req.bfreen) != 0;
123   fsp->fsu_files = PROPAGATE_ALL_ONES (fsd.fd_req.gtot);
124   fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.fd_req.gfree);
125
126 #elif defined STAT_READ_FILSYS          /* SVR2 */
127 # ifndef SUPERBOFF
128 #  define SUPERBOFF (SUPERB * 512)
129 # endif
130
131   struct filsys fsd;
132   int fd;
133
134   if (! disk)
135     {
136       errno = 0;
137       return -1;
138     }
139
140   fd = open (disk, O_RDONLY);
141   if (fd < 0)
142     return -1;
143   lseek (fd, (off_t) SUPERBOFF, 0);
144   if (full_read (fd, (char *) &fsd, sizeof fsd) != sizeof fsd)
145     {
146       close (fd);
147       return -1;
148     }
149   close (fd);
150
151   fsp->fsu_blocksize = (fsd.s_type == Fs2b ? 1024 : 512);
152   fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.s_fsize);
153   fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.s_tfree);
154   fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.s_tfree);
155   fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.s_tfree) != 0;
156   fsp->fsu_files = (fsd.s_isize == -1
157                     ? UINTMAX_MAX
158                     : (fsd.s_isize - 2) * INOPB * (fsd.s_type == Fs2b ? 2 : 1));
159   fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.s_tinode);
160
161 #elif defined STAT_STATFS3_OSF1
162
163   struct statfs fsd;
164
165   if (statfs (file, &fsd, sizeof (struct statfs)) != 0)
166     return -1;
167
168   fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);
169
170 #elif defined STAT_STATFS2_BSIZE        /* 4.3BSD, SunOS 4, HP-UX, AIX */
171
172   struct statfs fsd;
173
174   if (statfs (file, &fsd) < 0)
175     return -1;
176
177   fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_bsize);
178
179 # ifdef STATFS_TRUNCATES_BLOCK_COUNTS
180
181   /* In SunOS 4.1.2, 4.1.3, and 4.1.3_U1, the block counts in the
182      struct statfs are truncated to 2GB.  These conditions detect that
183      truncation, presumably without botching the 4.1.1 case, in which
184      the values are not truncated.  The correct counts are stored in
185      undocumented spare fields.  */
186   if (fsd.f_blocks == 0x7fffffff / fsd.f_bsize && fsd.f_spare[0] > 0)
187     {
188       fsd.f_blocks = fsd.f_spare[0];
189       fsd.f_bfree = fsd.f_spare[1];
190       fsd.f_bavail = fsd.f_spare[2];
191     }
192 # endif /* STATFS_TRUNCATES_BLOCK_COUNTS */
193
194 #elif defined STAT_STATFS2_FSIZE        /* 4.4BSD */
195
196   struct statfs fsd;
197
198   if (statfs (file, &fsd) < 0)
199     return -1;
200
201   fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);
202
203 #elif defined STAT_STATFS4              /* SVR3, Dynix, Irix, AIX */
204
205 # if !_AIX && !defined _SEQUENT_ && !defined DOLPHIN
206 #  define f_bavail f_bfree
207 # endif
208
209   struct statfs fsd;
210
211   if (statfs (file, &fsd, sizeof fsd, 0) < 0)
212     return -1;
213
214   /* Empirically, the block counts on most SVR3 and SVR3-derived
215      systems seem to always be in terms of 512-byte blocks,
216      no matter what value f_bsize has.  */
217 # if _AIX || defined _CRAY
218    fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_bsize);
219 # else
220    fsp->fsu_blocksize = 512;
221 # endif
222
223 #endif
224
225 #if (defined STAT_STATVFS \
226      || (!defined STAT_STATFS2_FS_DATA && !defined STAT_READ_FILSYS))
227
228   fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.f_blocks);
229   fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.f_bfree);
230   fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.f_bavail);
231   fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.f_bavail) != 0;
232   fsp->fsu_files = PROPAGATE_ALL_ONES (fsd.f_files);
233   fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.f_ffree);
234
235 #endif
236
237   return 0;
238 }
239
240 #if defined _AIX && defined _I386
241 /* AIX PS/2 does not supply statfs.  */
242
243 int
244 statfs (char *file, struct statfs *fsb)
245 {
246   struct stat stats;
247   struct dustat fsd;
248
249   if (stat (file, &stats) != 0)
250     return -1;
251   if (dustat (stats.st_dev, 0, &fsd, sizeof (fsd)))
252     return -1;
253   fsb->f_type   = 0;
254   fsb->f_bsize  = fsd.du_bsize;
255   fsb->f_blocks = fsd.du_fsize - fsd.du_isize;
256   fsb->f_bfree  = fsd.du_tfree;
257   fsb->f_bavail = fsd.du_tfree;
258   fsb->f_files  = (fsd.du_isize - 2) * fsd.du_inopb;
259   fsb->f_ffree  = fsd.du_tinode;
260   fsb->f_fsid.val[0] = fsd.du_site;
261   fsb->f_fsid.val[1] = fsd.du_pckno;
262   return 0;
263 }
264
265 #endif /* _AIX && _I386 */