b70af55d764ec27834e9e57e92aba4db1bf1f16b
[captive.git] / src / libcaptive / storage / size.c
1 /* $Id$
2  * Detect media size of given GIOChannel for libcaptive
3  * Copyright (C) 2002 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
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, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include "captive/storage.h"    /* self */
23 #include "../client/giochannel-blind.h" /* for captive_giochannel_blind_get_size() */
24 #include "../sandbox/client-CaptiveIOChannel.h" /* for captive_io_channel_get_size() */
25 #include <glib/gmessages.h>
26 #include <glib/gtypes.h>
27 #include <fcntl.h>
28 #include <sys/ioctl.h>
29 #include <unistd.h>
30 /* Do not: #include <linux/fs.h>        * for 'BLKGETSIZE64' *
31  * as including any Linux kernel include files is too much incompatible.
32  */
33 #include <sys/mount.h>  /* for 'BLKGETSIZE' */
34
35
36 static guint64 size_blind(GIOChannel *iochannel)
37 {
38 guint64 r;
39
40         g_return_val_if_fail(iochannel!=NULL,0);
41
42         if (!captive_giochannel_blind_get_size(iochannel,&r))
43                 return 0;
44
45         return r;
46 }
47
48
49 static guint64 size_sandbox(GIOChannel *iochannel)
50 {
51 guint64 r;
52
53         g_return_val_if_fail(iochannel!=NULL,0);
54
55         if (!captive_io_channel_get_size(iochannel,&r))
56                 return 0;
57
58         return r;
59 }
60
61
62 static GIOChannel *iochannel_null;
63
64 static int iounixchannel_get_fd(GIOChannel *iochannel)
65 {
66 int r;
67
68         g_return_val_if_fail(iochannel!=NULL,-1);
69
70         if (!iochannel_null) {
71 int fd;
72
73                 fd=open("/dev/null",O_RDONLY);
74                 g_return_val_if_fail(fd!=-1,-1);
75                 iochannel_null=g_io_channel_unix_new(fd);
76                 g_return_val_if_fail(iochannel_null!=NULL,-1);
77                 }
78
79         if (iochannel->funcs!=iochannel_null->funcs) {
80                 /* Not a UNIX file descriptor */
81                 return -1;
82                 }
83
84         /* It is forbidden to callg_io_channel_unix_get_fd()
85          * if you are not sure it is a 'GIOUnixChannel'.
86          */
87         r=g_io_channel_unix_get_fd(iochannel);
88         g_return_val_if_fail(r!=-1,-1);
89
90         return r;
91 }
92
93
94 static guint64 size_ioctl(GIOChannel *iochannel)
95 {
96 int fd;
97 guint64 r;
98 long r_long;
99
100         g_return_val_if_fail(iochannel!=NULL,0);
101
102         if (-1==(fd=iounixchannel_get_fd(iochannel)))
103                 return 0;
104
105 #ifdef BLKGETSIZE64
106         if (!ioctl(fd,BLKGETSIZE64,&r))
107                 return r;
108 #endif
109         if (ioctl(fd,BLKGETSIZE,&r_long))
110                 return 0;
111         if (r_long<0)
112                 return 0;
113         r=((guint64)512)*r_long;
114
115         return r;
116 }
117
118
119 static guint64 size_seek(GIOChannel *iochannel)
120 {
121 int fd;
122 off_t offset_orig,offset;
123
124         g_return_val_if_fail(iochannel!=NULL,0);
125
126         /* We may need '_FILE_OFFSET_BITS=64'.
127          * Setting '__USE_FILE_OFFSET64' did not help.
128          * Done by 'AC_SYS_LARGEFILE' of configure.in.
129          */
130         g_return_val_if_fail(sizeof(offset)==sizeof(guint64),0);
131
132         if (-1==(fd=iounixchannel_get_fd(iochannel)))
133                 return 0;
134
135         if (-1==(offset_orig=lseek(fd,0,SEEK_CUR)))
136                 return 0;
137         g_return_val_if_fail(offset_orig>=0,0);
138         offset=lseek(fd,0,SEEK_END);
139         if (offset_orig!=lseek(fd,offset_orig,SEEK_SET))
140                 g_assert_not_reached();
141
142         if (-1==offset)
143                 return 0;
144         g_return_val_if_fail(offset>=0,0);
145
146         return offset;
147 }
148
149
150
151 static guint64 size_read(GIOChannel *iochannel)
152 {
153 gint64 low,high,mid;
154 GIOStatus erriostatus;
155 gchar bufchar;
156 gsize bufchargot;
157
158         g_return_val_if_fail(iochannel!=NULL,0);
159         /* Default "UTF-8" encoding is not much usable for us */
160         g_return_val_if_fail(g_io_channel_get_encoding(iochannel)==NULL,0);
161
162         /* low  ==high: low (high)
163          * low+1==high: mid==low; NORMAL: no change: high
164          * low+1==high: mid==low; EOF   : high=mid => 'low==high' case
165          */
166         for (low=0,
167                         high=
168                                         G_MAXINT64;
169                         low+1<high;) {
170                 mid=low+(high-low)/2;   /* beware of 'gint64' overflow! */
171
172                 erriostatus=g_io_channel_seek_position(iochannel,mid,G_SEEK_SET,
173                                 NULL);  /* error */
174                 /* During seek in block device such as on URL file:///dev/hda1#captive-fastfat.sys-ro:/
175                  * we will do llseek(2) on "/dev/hda1" device from captive_giochannel_size().
176                  * Although we are allowed to seek behind EOF on regular files
177                  * at least linux-kernel-2.4.19-ac4/fs/block_dev.c/block_llseek() will give
178                  * EINVAL on seek behind EOF therefore it must be accepted without complaints by us.
179                  */
180                 if (erriostatus!=G_IO_STATUS_NORMAL) {
181                         erriostatus=G_IO_STATUS_EOF;
182                         bufchargot=0;
183                         }
184                 else {
185                         g_assert(sizeof(bufchar)==1);
186                         erriostatus=g_io_channel_read_chars(iochannel,
187                                         &bufchar, /* buf */
188                                         sizeof(bufchar),        /* count */
189                                         &bufchargot,    /* bytes_read */
190                                         NULL);  /* error */
191                         /* During read on the end boundary of Linux kernel block device we will
192                          * get GNOME_VFS_ERROR_IO at least from linux-kernel-2.4.19-ac4
193                          * which will get mapped to G_IO_STATUS_ERROR by captive_gnomevfs_giognomevfs_io_read().
194                          */
195                         g_assert(0
196                                         || erriostatus==G_IO_STATUS_NORMAL
197                                         || erriostatus==G_IO_STATUS_EOF
198                                         || erriostatus==G_IO_STATUS_ERROR);
199                         g_assert(0
200                                         || (erriostatus==G_IO_STATUS_NORMAL && bufchargot==1)
201                                         || (erriostatus==G_IO_STATUS_EOF    && bufchargot==0)
202                                         || (erriostatus==G_IO_STATUS_ERROR  && bufchargot==0));
203                         }
204
205                 if (erriostatus==G_IO_STATUS_NORMAL)
206                         low=mid;
207                 else
208                         high=mid;
209                 }
210         
211         g_assert(high>=0);
212         return high;
213 }
214
215
216 guint64 captive_giochannel_size(GIOChannel *iochannel)
217 {
218 guint64 r;
219
220         if ((r=size_blind(iochannel)))
221                 return r;
222         if ((r=size_sandbox(iochannel)))
223                 return r;
224         if ((r=size_ioctl(iochannel)))
225                 return r;
226         if ((r=size_seek(iochannel)))
227                 return r;
228         if ((r=size_read(iochannel)))
229                 return r;
230
231         return r;
232 }